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{
};
+
+}
+
+#-------------------------------------------------------------------
+
+=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 = '
};
+ if ($canEdit) {
+ $out .= q{ {id}).q{">[X] };
+ }
+ $out .= q{}.$comment->{alias}.q{: "}.WebGUI::HTML::format($comment->{comment},'text').q{"
},
+ 1 => q{
},
+ 2 => q{
},
+ 3 => q{
},
+ 4 => q{
},
+ 5 => q{
},
+ );
+ 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{
};
+}
+
+
+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