From cf63a4e4f3a9158f1922680c5f8fdb27092d3a9c Mon Sep 17 00:00:00 2001 From: Matthew Wilson Date: Mon, 30 Mar 2009 21:38:12 +0000 Subject: [PATCH] (Matthew Wilson) add AssetAspect/RssFeed. See exportAssetCollateral for a good time. --- docs/changelog/7.x.x.txt | 7 + docs/upgrades/upgrade_7.7.1-7.7.2.pl | 46 ++ lib/WebGUI/Asset/Wobject/Collaboration.pm | 57 ++- lib/WebGUI/Asset/Wobject/Gallery.pm | 46 +- lib/WebGUI/Asset/Wobject/GalleryAlbum.pm | 50 ++- lib/WebGUI/Asset/Wobject/SyndicatedContent.pm | 115 +++-- lib/WebGUI/Asset/Wobject/WikiMaster.pm | 45 +- lib/WebGUI/AssetAspect/RssFeed.pm | 398 ++++++++++++++++++ lib/WebGUI/AssetExportHtml.pm | 7 +- .../i18n/English/AssetAspect_RssFeed.pm | 99 +++++ 10 files changed, 762 insertions(+), 108 deletions(-) create mode 100644 lib/WebGUI/AssetAspect/RssFeed.pm create mode 100644 lib/WebGUI/i18n/English/AssetAspect_RssFeed.pm diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index d47ac2f80..5fd307798 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -10,6 +10,13 @@ sku's view screen for the whole form. [TEMPLATE] - fixed #9933: Matrix 2.0 - Unable to view/edit product maintainer account - fixed #9951: Matrix 2.0: Median not calculated correctly + - added new AssetAspect::RssFeed (Matthew Wilson) - to convert an asset to use it (see + Collaboration.pm as an example), inherit from Class::C3 as in Collaboration + and you'll need to remove all your ->SUPER::xxxxx invocations - usually replace it + with ->next::method, but when your SUPER was previously calling a method with + a name different from your current method, you'll need to specify the parent/super + module name explicity (e.g. ->WebGUI::Asset::Wobject::canEdit()). You'll also + need to implement the getRssFeedItems method as explained in AssetAspect/RssFeed.pm 7.7.1 - the AdSku project: create a Sku that allows buyers to purchase advertising in select AdSpaces at selected priorities diff --git a/docs/upgrades/upgrade_7.7.1-7.7.2.pl b/docs/upgrades/upgrade_7.7.1-7.7.2.pl index df25f82e5..a53701e15 100644 --- a/docs/upgrades/upgrade_7.7.1-7.7.2.pl +++ b/docs/upgrades/upgrade_7.7.1-7.7.2.pl @@ -33,6 +33,9 @@ my $session = start(); # this line required # upgrade functions go here recalculateMatrixListingMedianValue( $session ); +addRssFeedAspect($session); +addRssFeedAspectToAssets($session); +removeRssCapableAsset($session); finish($session); # this line required @@ -68,6 +71,49 @@ category = ?",[$medianValue,$listing->{listingId},$category]); print "Done.\n" unless $quiet; } +#---------------------------------------------------------------------------- +sub addRssFeedAspect { + my $session = shift; + print "\tAdding RssFeed asset aspect..." unless $quiet; + $session->db->write("create table assetAspectRssFeed ( + assetId char(22) binary not null, + revisionDate bigint not null, + itemsPerFeed int(11) default 25, + feedCopyright text, + feedTitle text, + feedDescription mediumtext, + feedImage char(22) binary, + feedImageLink text, + feedImageDescription mediumtext, + primary key (assetId, revisionDate) + )"); + print "Done.\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +sub addRssFeedAspectToAssets { + my $session = shift; + foreach my $asset_class (qw( WikiMaster Collaboration SyndicatedContent Gallery GalleryAlbum )) { + print "\tAdding RssFeed aspect to $asset_class table..." unless $quiet; + my $db = $session->db; + my $pages = $db->read("select assetId,revisionDate from $asset_class"); + while (my ($id, $rev) = $pages->array) { + $db->write("insert into assetAspectRssFeed (assetId, revisionDate, itemsPerFeed, feedTitle, feedDescription, feedImage, feedImageLink, feedImageDescription) values (?,?,25,'','',NULL,'','')",[$id,$rev]); + } + print "Done.\n" unless $quiet; + } +} + +#---------------------------------------------------------------------------- +sub removeRssCapableAsset { + my $session = shift; + print "\tRemoving prior RssCapable asset..." unless $quiet; + $session->db->write("drop table RSSCapable"); + $session->db->write("drop table RSSFromParent"); + print "Done.\n" unless $quiet; +} + + #---------------------------------------------------------------------------- # Describe what our function does #sub exampleFunction { diff --git a/lib/WebGUI/Asset/Wobject/Collaboration.pm b/lib/WebGUI/Asset/Wobject/Collaboration.pm index da641d788..5275e27fd 100644 --- a/lib/WebGUI/Asset/Wobject/Collaboration.pm +++ b/lib/WebGUI/Asset/Wobject/Collaboration.pm @@ -20,9 +20,9 @@ use WebGUI::Paginator; use WebGUI::Utility; use WebGUI::Asset::Wobject; use WebGUI::Workflow::Cron; -use WebGUI::Asset::RSSCapable; -use base 'WebGUI::Asset::RSSCapable'; -use base 'WebGUI::Asset::Wobject'; +use Class::C3; +use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); + #------------------------------------------------------------------- sub _computePostCount { @@ -77,12 +77,11 @@ sub addChild { my $self = shift; my $properties = shift; my @other = @_; - if ($properties->{className} ne "WebGUI::Asset::Post::Thread" - and $properties->{className} ne 'WebGUI::Asset::RSSFromParent') { + if ($properties->{className} ne "WebGUI::Asset::Post::Thread") { $self->session->errorHandler->security("add a ".$properties->{className}." to a ".$self->get("className")); return undef; } - return $self->SUPER::addChild($properties, @other); + return $self->next::method($properties, @other); } @@ -263,7 +262,7 @@ sub canEdit { ) && $self->canStartThread( $userId ) ) || # account for new threads - $self->SUPER::canEdit( $userId ) + $self->next::method( $userId ) ); } @@ -271,7 +270,7 @@ sub canEdit { sub canModerate { my $self = shift; my $userId = shift || $self->session->user->userId; - return $self->SUPER::canEdit( $userId ); + return $self->WebGUI::Asset::Wobject::canEdit( $userId ); } #------------------------------------------------------------------- @@ -294,7 +293,7 @@ sub canPost { } # Users who can edit the collab can post else { - return $self->SUPER::canEdit( $userId ); + return $self->WebGUI::Asset::Wobject::canEdit( $userId ); } } @@ -322,7 +321,7 @@ sub canStartThread { ; return ( $user->isInGroup($self->get("canStartThreadGroupId")) - || $self->SUPER::canEdit( $userId ) + || $self->WebGUI::Asset::Wobject::canEdit( $userId ) ); } @@ -331,13 +330,13 @@ sub canStartThread { sub canView { my $self = shift; my $userId = shift || $self->session->user->userId; - return $self->SUPER::canView( $userId ) || $self->canPost( $userId ); + return $self->next::method( $userId ) || $self->canPost( $userId ); } #------------------------------------------------------------------- sub commit { my $self = shift; - $self->SUPER::commit; + $self->next::method; my $cron = undef; if ($self->get("getMailCronId")) { $cron = WebGUI::Workflow::Cron->new($self->session, $self->get("getMailCronId")); @@ -799,13 +798,13 @@ sub definition { className=>'WebGUI::Asset::Wobject::Collaboration', properties=>\%properties, }); - return $class->SUPER::definition($session, $definition); + return $class->next::method($session, $definition); } #------------------------------------------------------------------- sub duplicate { my $self = shift; - my $newAsset = $self->SUPER::duplicate(@_); + my $newAsset = $self->next::method(@_); $newAsset->createSubscriptionGroup; return $newAsset; } @@ -821,7 +820,7 @@ Add a tab for the mail interface. sub getEditTabs { my $self = shift; my $i18n = WebGUI::International->new($self->session,"Asset_Collaboration"); - return ($self->SUPER::getEditTabs(), ['mail', $i18n->get('mail'), 9]); + return ($self->next::method, ['mail', $i18n->get('mail'), 9]); } #------------------------------------------------------------------- @@ -838,7 +837,7 @@ sub getNewThreadUrl { } #------------------------------------------------------------------- -sub getRssItems { +sub getRssFeedItems { my $self = shift; # XXX copied and reformatted this query from www_viewRSS, but why is it constructed like this? @@ -861,7 +860,7 @@ SQL my $datetime = $self->session->datetime; my @posts; - my $rssLimit = $self->get('rssCapableRssLimit') || 10; + my $rssLimit = $self->get('itemsPerFeed'); for my $postId (@postIds) { my $post = WebGUI::Asset->new($self->session, $postId, 'WebGUI::Asset::Post::Thread'); my $postUrl = $siteUrl . $post->getUrl; @@ -882,15 +881,15 @@ SQL } } - push @posts, { + push @posts, { author => $post->get('username'), title => $post->get('title'), - 'link' => $postUrl, + 'link' => $postUrl, guid => $postUrl, description => $post->get('synopsis'), epochDate => $post->get('creationDate'), pubDate => $datetime->epochToMail($post->get('creationDate')), - attachmentLoop => $attachmentLoop, + attachmentLoop => $attachmentLoop, userDefined1 => $post->get("userDefined1"), userDefined2 => $post->get("userDefined2"), userDefined3 => $post->get("userDefined3"), @@ -901,7 +900,7 @@ SQL last if $rssLimit <= scalar(@posts); } - return @posts; + return \@posts; } #------------------------------------------------------------------- @@ -1067,7 +1066,7 @@ sub getViewTemplateVars { $var{'user.canPost'} = $self->canPost; $var{'user.canStartThread'} = $self->canStartThread; $var{"add.url"} = $self->getNewThreadUrl; - $var{"rss.url"} = $self->getRssUrl; + $var{"rss.url"} = $self->getRssFeedUrl; $var{'user.isModerator'} = $self->canModerate; $var{'user.isVisitor'} = ($self->session->user->isVisitor); $var{'user.isSubscribed'} = $self->isSubscribed; @@ -1173,11 +1172,9 @@ See WebGUI::Asset::prepareView() for details. sub prepareView { my $self = shift; - $self->SUPER::prepareView(); + $self->next::method; my $template = WebGUI::Asset::Template->new($self->session, $self->get("collaborationTemplateId")) or die "no good: ".$self->get("collaborationTemplateId"); - if ($self->get('rssCapableRssEnabled')) { - $self->session->style->setLink($self->getRssUrl,{ rel=>'alternate', type=>'application/rss+xml', title=>$self->get('title') . ' RSS' }); - } + $self->session->style->setLink($self->getRssFeedUrl,{ rel=>'alternate', type=>'application/rss+xml', title=>$self->get('title') . ' RSS' }); $template->prepare($self->getMetaDataAsTemplateVariables); $self->{_viewTemplate} = $template; } @@ -1187,7 +1184,7 @@ sub prepareView { sub processPropertiesFromFormPost { my $self = shift; my $updatePrivs = ($self->session->form->process("groupIdView") ne $self->get("groupIdView") || $self->session->form->process("groupIdEdit") ne $self->get("groupIdEdit")); - $self->SUPER::processPropertiesFromFormPost; + $self->next::method; if ($self->get("subscriptionGroupId") eq "") { $self->createSubscriptionGroup; } @@ -1215,7 +1212,7 @@ sub purge { my $cron = WebGUI::Workflow::Cron->new($self->session, $self->get("getMailCronId")); $cron->delete if defined $cron; } - $self->SUPER::purge; + $self->next::method; } #------------------------------------------------------------------- @@ -1230,7 +1227,7 @@ sub purgeCache { my $self = shift; WebGUI::Cache->new($self->session,"view_".$self->getId)->delete; WebGUI::Cache->new($self->session,$self->_visitorCacheKey)->delete; - $self->SUPER::purgeCache; + $self->next::method; } #------------------------------------------------------------------- @@ -1463,7 +1460,7 @@ sub www_view { my $self = shift; my $disableCache = ($self->session->form->process("sortBy") ne ""); $self->session->http->setCacheControl($self->get("visitorCacheTimeout")) if ($self->session->user->isVisitor && !$disableCache); - return $self->SUPER::www_view(@_); + return $self->next::method(@_); } 1; diff --git a/lib/WebGUI/Asset/Wobject/Gallery.pm b/lib/WebGUI/Asset/Wobject/Gallery.pm index a8110175b..f3272c80e 100644 --- a/lib/WebGUI/Asset/Wobject/Gallery.pm +++ b/lib/WebGUI/Asset/Wobject/Gallery.pm @@ -11,7 +11,8 @@ package WebGUI::Asset::Wobject::Gallery; #------------------------------------------------------------------- use strict; -use base 'WebGUI::Asset::Wobject'; +use Class::C3; +use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); use JSON; use Tie::IxHash; use WebGUI::International; @@ -338,7 +339,7 @@ sub definition { properties => \%properties, }; - return $class->SUPER::definition($session, $definition); + return $class->next::method($session, $definition); } #---------------------------------------------------------------------------- @@ -366,7 +367,7 @@ sub addChild { return undef; } - return $self->SUPER::addChild( $properties, @_ ); + return $self->next::method( $properties, @_ ); } #---------------------------------------------------------------------------- @@ -740,6 +741,41 @@ sub getPreviousAlbumId { } } +#------------------------------------------------------------------- + +=head2 getRssFeedItems () + +Returns an array reference of hash references. Each hash reference has a title, +description, link, and date field. The date field can be either an epoch date, an RFC 1123 +date, or a ISO date in the format of YYYY-MM-DD HH:MM::SS. Optionally specify an +author, and a guid field. + +=cut + +sub getRssFeedItems { + my $self = shift; + + my $p + = $self->getAlbumPaginator( { + perpage => $self->get('itemsPerFeed'), + } ); + + my $var = []; + for my $assetId ( @{ $p->getPageData } ) { + my $asset = WebGUI::Asset::Wobject::GalleryAlbum->newPending( $self->session, $assetId ); + push @{ $var }, { + 'link' => $asset->getUrl, + 'guid' => $asset->{_properties}->{ 'assetId' }, + 'title' => $asset->getTitle, + 'description' => $asset->{_properties}->{ 'description' }, + 'date' => $asset->{_properties}->{ 'creationDate' }, + 'author' => WebGUI::User->new($self->session, $asset->{_properties}->{ 'ownerUserId' })->username + }; + } + + return $var; +} + #---------------------------------------------------------------------------- =head2 getSearchPaginator ( rules ) @@ -934,7 +970,7 @@ See WebGUI::Asset::prepareView() for details. sub prepareView { my $self = shift; - $self->SUPER::prepareView(); + $self->next::method(); if ( $self->get("viewDefault") eq "album" && $self->get("viewAlbumAssetId") && $self->get("viewAlbumAssetId") ne 'PBasset000000000000001') { @@ -1042,7 +1078,7 @@ sub www_add { return $self->processStyle($i18n->get("error add uncommitted")); } - return $self->SUPER::www_add( @_ ); + return $self->next::method( @_ ); } #---------------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm index ce9ebe02d..58ab8608a 100644 --- a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm +++ b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm @@ -11,7 +11,8 @@ package WebGUI::Asset::Wobject::GalleryAlbum; #------------------------------------------------------------------- use strict; -use base 'WebGUI::Asset::Wobject'; +use Class::C3; +use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); use Carp qw( croak ); use File::Find; use File::Spec; @@ -77,7 +78,7 @@ sub definition { properties => \%properties, }; - return $class->SUPER::definition($session, $definition); + return $class->next::method($session, $definition); } #---------------------------------------------------------------------------- @@ -174,7 +175,7 @@ sub addChild { return undef; } - return $self->SUPER::addChild( $properties, @_ ); + return $self->next::method( $properties, @_ ); } #---------------------------------------------------------------------------- @@ -400,7 +401,7 @@ sub getCurrentRevisionDate { return $revisionDate; } else { - return $class->SUPER::getCurrentRevisionDate( $session, $assetId ); + return $class->next::method( $session, $assetId ); } } @@ -497,6 +498,41 @@ sub getPreviousAlbum { return $self->{_previousAlbum}; } +#------------------------------------------------------------------- + +=head2 getRssFeedItems () + +Returns an array reference of hash references. Each hash reference has a title, +description, link, and date field. The date field can be either an epoch date, an RFC 1123 +date, or a ISO date in the format of YYYY-MM-DD HH:MM::SS. Optionally specify an +author, and a guid field. + +=cut + +sub getRssFeedItems { + my $self = shift; + + my $p + = $self->getFilePaginator( { + perpage => $self->get('itemsPerFeed'), + } ); + + my $var = []; + for my $assetId ( @{ $p->getPageData } ) { + my $asset = WebGUI::Asset::Wobject::GalleryAlbum->newPending( $self->session, $assetId ); + push @{ $var }, { + 'link' => $asset->getUrl, + 'guid' => $asset->{_properties}->{ 'assetId' }, + 'title' => $asset->getTitle, + 'description' => $asset->{_properties}->{ 'description' }, + 'date' => $asset->{_properties}->{ 'creationDate' }, + 'author' => WebGUI::User->new($self->session, $asset->{_properties}->{ 'ownerUserId' })->username + }; + } + + return $var; +} + #---------------------------------------------------------------------------- =head2 getTemplateVars ( ) @@ -639,7 +675,7 @@ See WebGUI::Asset::prepareView() for details. sub prepareView { my $self = shift; - $self->SUPER::prepareView(); + $self->next::method(); my $templateId = $self->getParent->get("templateIdViewAlbum"); @@ -719,7 +755,7 @@ approval workflow. sub processPropertiesFromFormPost { my $self = shift; my $form = $self->session->form; - my $errors = $self->SUPER::processPropertiesFromFormPost || []; + my $errors = $self->next::method || []; # Return if error return $errors if @$errors; @@ -762,7 +798,7 @@ Override update to force isHidden=1 on all albums. sub update { my $self = shift; my $properties = shift; - return $self->SUPER::update({ %{ $properties }, isHidden=>1 }); + return $self->next::method({ %{ $properties }, isHidden=>1 }); } #---------------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm b/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm index 0c209dc9f..312f2c977 100644 --- a/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm +++ b/lib/WebGUI/Asset/Wobject/SyndicatedContent.pm @@ -17,7 +17,8 @@ use WebGUI::Cache; use WebGUI::Exception; use WebGUI::HTML; use WebGUI::International; -use base 'WebGUI::Asset::Wobject'; +use Class::C3; +use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); use WebGUI::Macro; use XML::FeedPP; @@ -116,7 +117,7 @@ sub definition { className=>'WebGUI::Asset::Wobject::SyndicatedContent', properties=>\%properties }); - return $class->SUPER::definition($session, $definition); + return $class->next::method($session, $definition); } #------------------------------------------------------------------- @@ -129,6 +130,7 @@ Combines all feeds into a single XML::FeedPP object. sub generateFeed { my $self = shift; + my $limit = shift || $self->get('maxHeadlines'); my $feed = XML::FeedPP::Atom->new(); my $log = $self->session->log; @@ -173,8 +175,8 @@ sub generateFeed { # sort them by date $feed->sort_item(); - # limit the feed to the maxium number of headlines - $feed->limit_item($self->get('maxHeadlines')); + # limit the feed to the maximum number of headlines (or the feed generator limit). + $feed->limit_item($limit); # mark this asset as updated $self->update({}) if ($newlyCached); @@ -184,6 +186,53 @@ sub generateFeed { #------------------------------------------------------------------- +=head2 getFeed () + +Override the one in the parent... + +=cut + +sub getFeed { + my $self = shift; + my $feed = shift; + foreach my $item ($self->generateFeed( $self->get('itemsPerFeed') )->get_item) { + my $set_permalink_false = 0; + my $new_item = $feed->add_item( $item ); + warn "creating item !"; + if (!$new_item->guid) { + if ($new_item->link) { + $new_item->guid( $new_item->link ); + } else { + $new_item->guid( $self->session->id->generate ); + $set_permalink_false = 1; + } + } + $new_item->guid( $new_item->guid, isPermaLink => 0 ) if $set_permalink_false; + } + $feed->title( $self->get('feedTitle') || $self->get('title') ); + $feed->description( $self->get('feedDescription') || $self->get('synopsis') ); + $feed->pubDate( $self->getContentLastModified ); + $feed->copyright( $self->get('feedCopyright') ); + $feed->link( $self->getUrl ); + # $feed->language( $lang ); + if ($self->get('feedImage')) { + my $storage = WebGUI::Storage->get($self->session, $self->get('feedImage')); + my @files = @{ $storage->getFiles }; + if (scalar @files) { + $feed->image( + $storage->getUrl( $files[0] ), + $self->get('feedImageDescription') || $self->getTitle, + $self->get('feedImageUrl') || $self->getUrl, + $self->get('feedImageDescription') || $self->getTitle, + ( $storage->getSizeInPixels( $files[0] ) ) # expands to width and height + ); + } + } + return $feed; +} + +#------------------------------------------------------------------- + =head2 getTemplateVariables Returns a hash reference of template variables. @@ -256,7 +305,7 @@ See WebGUI::Asset::prepareView() for details. sub prepareView { my $self = shift; - $self->SUPER::prepareView(); + $self->next::method; my $template = WebGUI::Asset::Template->new($self->session, $self->get("templateId")); $template->prepare($self->getMetaDataAsTemplateVariables); $self->{_viewTemplate} = $template; @@ -279,7 +328,7 @@ See WebGUI::Asset::purgeCache() for details. sub purgeCache { my $self = shift; WebGUI::Cache->new($self->session,"view_".$self->getId)->delete; - $self->SUPER::purgeCache; + $self->next::method; } #------------------------------------------------------------------- @@ -318,59 +367,7 @@ See WebGUI::Asset::Wobject::www_view() for details. sub www_view { my $self = shift; $self->session->http->setCacheControl($self->get("cacheTimeout")); - $self->SUPER::www_view(@_); -} - - -#------------------------------------------------------------------- - -=head2 www_viewAtom ( ) - -Emit an Atom 0.3 feed. - -=cut - -sub www_viewAtom { - my $self = shift; - my $feed = $self->generateFeed; - my $atom = XML::FeedPP::Atom->new; - $atom->merge($feed); - $self->session->http->setMimeType('application/atom+xml'); - return $atom->to_string; -} - -#------------------------------------------------------------------- - -=head2 www_viewRdf ( ) - -Emit an RSS 1.0 / RDF feed. - -=cut - -sub www_viewRdf { - my $self = shift; - my $feed = $self->generateFeed; - my $rdf = XML::FeedPP::RDF->new; - $rdf->merge($feed); - $self->session->http->setMimeType('application/rdf+xml'); - return $rdf->to_string; -} - -#------------------------------------------------------------------- - -=head2 www_viewRss ( ) - -Emit an RSS 2.0 feed. - -=cut - -sub www_viewRss { - my $self = shift; - my $feed = $self->generateFeed; - my $rss = XML::FeedPP::RSS->new; - $rss->merge($feed); - $self->session->http->setMimeType('application/rss+xml'); - return $rss->to_string; + $self->next::method(@_); } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/WikiMaster.pm b/lib/WebGUI/Asset/Wobject/WikiMaster.pm index 9a4d2ca44..dd0fc4581 100644 --- a/lib/WebGUI/Asset/Wobject/WikiMaster.pm +++ b/lib/WebGUI/Asset/Wobject/WikiMaster.pm @@ -10,7 +10,8 @@ package WebGUI::Asset::Wobject::WikiMaster; # http://www.plainblack.com info@plainblack.com #------------------------------------------------------------------- -use base 'WebGUI::Asset::Wobject'; +use Class::C3; +use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); use strict; use Tie::IxHash; use WebGUI::International; @@ -70,6 +71,7 @@ sub appendRecentChanges { username=>$user->username, date=>$self->session->datetime->epochToHuman($asset->get("revisionDate")), isAvailable=>$isAvailable, + assetId=>$id, }); } } @@ -139,7 +141,7 @@ sub autolinkHtml { #------------------------------------------------------------------- sub canAdminister { my $self = shift; - return $self->session->user->isInGroup($self->get('groupToAdminister')) || $self->SUPER::canEdit; + return $self->session->user->isInGroup($self->get('groupToAdminister')) || $self->WebGUI::Asset::Wobject::canEdit; } #------------------------------------------------------------------- @@ -164,7 +166,7 @@ sub canEdit { ) && $self->canEditPages ) || # account for new posts - $self->SUPER::canEdit() + $self->next::method() ); } @@ -337,13 +339,44 @@ sub definition { properties => \%properties, }; - return $class->SUPER::definition($session, $definition); + return $class->next::method($session, $definition); +} + +#------------------------------------------------------------------- + +=head2 getRssFeedItems () + +Returns an array reference of hash references. Each hash reference has a title, +description, link, and date field. The date field can be either an epoch date, an RFC 1123 +date, or a ISO date in the format of YYYY-MM-DD HH:MM::SS. Optionally specify an +author, and a guid field. + +=cut + +sub getRssFeedItems { + my $self = shift; + my $vars = {}; + $self->appendRecentChanges( $vars, $self->get('itemsPerFeed') ); + my $var = []; + foreach my $item ( @{ $vars->{recentChanges} } ) { + my $asset = WebGUI::Asset->newByDynamicClass( $self->session, $item->{assetId} ); + push @{ $var }, { + 'link' => $asset->getUrl, + 'guid' => $item->{ 'assetId' } . $asset->get( 'revisionDate' ), + 'title' => $asset->getTitle, + 'description' => $item->{ 'actionTaken' }, + 'date' => $item->{ 'date' }, + 'author' => $item->{ 'username' }, + }; + } + + return $var; } #------------------------------------------------------------------- sub prepareView { my $self = shift; - $self->SUPER::prepareView; + $self->next::method; $self->{_frontPageTemplate} = WebGUI::Asset::Template->new($self->session, $self->get('frontPageTemplateId')); $self->{_frontPageTemplate}->prepare; @@ -355,7 +388,7 @@ sub processPropertiesFromFormPost { my $groupsChanged = (($self->session->form->process('groupIdView') ne $self->get('groupIdView')) or ($self->session->form->process('groupIdEdit') ne $self->get('groupIdEdit'))); - my $ret = $self->SUPER::processPropertiesFromFormPost(@_); + my $ret = $self->next::method(@_); if ($groupsChanged) { foreach my $child (@{$self->getLineage(['children'], {returnObjects => 1})}) { $child->update({ groupIdView => $self->get('groupIdView'), diff --git a/lib/WebGUI/AssetAspect/RssFeed.pm b/lib/WebGUI/AssetAspect/RssFeed.pm new file mode 100644 index 000000000..386dbf9c5 --- /dev/null +++ b/lib/WebGUI/AssetAspect/RssFeed.pm @@ -0,0 +1,398 @@ +package WebGUI::AssetAspect::RssFeed; + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2009 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 Class::C3; +use WebGUI::Exception; +use WebGUI::Storage; +use XML::FeedPP; +use Path::Class::File; + +=head1 NAME + +Package WebGUI::AssetAspect::RssFeed + +=head1 DESCRIPTION + +This is an aspect which exposes an asset's items as an RSS or Atom feed. + +=head1 SYNOPSIS + + use Class::C3; + use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset); + +And then wherever you would call $self->SUPER::someMethodName call $self->next::method instead. + +=head1 METHODS + +These methods are available from this class: + +=cut + +#------------------------------------------------------------------- + +=head2 definition + +Extends the definition to add the RSS fields. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new($session,'AssetAspect_RssFeed'); + my %properties; + tie %properties, 'Tie::IxHash'; + %properties = ( + itemsPerFeed => { + noFormPost => 0, + fieldType => "integer", + defaultValue => 25, + tab => "rss", + label => $i18n->get('itemsPerFeed'), + hoverHelp => $i18n->get('itemsPerFeed hoverHelp') + }, + feedCopyright => { + noFormPost => 0, + fieldType => "text", + defaultValue => "", + tab => "rss", + label => $i18n->get('feedCopyright'), + hoverHelp => $i18n->get('feedCopyright hoverHelp') + }, + feedTitle => { + noFormPost => 0, + fieldType => "text", + defaultValue => "", + tab => "rss", + label => $i18n->get('feedTitle'), + hoverHelp => $i18n->get('feedTitle hoverHelp') + }, + feedDescription => { + noFormPost => 0, + fieldType => "textarea", + defaultValue => "", + tab => "rss", + label => $i18n->get('feedDescription'), + hoverHelp => $i18n->get('feedDescription hoverHelp') + }, + feedImage => { + noFormPost => 0, + fieldType => "image", + tab => "rss", + label => $i18n->get('feedImage'), + hoverHelp => $i18n->get('feedImage hoverHelp') + }, + feedImageLink => { + noFormPost => 0, + fieldType => "text", + defaultValue => "", + tab => "rss", + label => $i18n->get('feedImageLink'), + hoverHelp => $i18n->get('feedImageLink hoverHelp') + }, + feedImageDescription => { + noFormPost => 0, + fieldType => "text", + defaultValue => "", + tab => "rss", + label => $i18n->get('feedImageDescription'), + hoverHelp => $i18n->get('feedImageDescription hoverHelp') + }, + ); + push(@{$definition}, { + autoGenerateForms => 1, + tableName => 'assetAspectRssFeed', + className => 'WebGUI::AssetAspect::RssFeed', + properties => \%properties + }); + return $class->next::method($session, $definition); +} + +#------------------------------------------------------------------- + +=head2 exportAssetCollateral () + +Extended from WebGUI::Asset and exports the www_viewRss() and +www_viewAtom() methods with filenames generated by +getStaticAtomFeedUrl() and getStaticRssFeedUrl(). + +This method will be called with the following parameters: + +=head3 basePath + +A L object representing the base filesystem path for this +particular asset. + +=head3 params + +A hashref with the quiet, userId, depth, and indexFileName parameters from +L. + +=cut + +sub exportAssetCollateral { + # Lots of copy/paste here from AssetExportHtml.pm, since none of the methods there were + # directly useful without ginormous refactoring. + my $self = shift; + my $basepath = shift; + my $args = shift; + + my $basename = $basepath->basename; + my $filedir; + my $filenameBase; + + # We want our .rss and .atom files to "appear" at the same level as the asset. + if ($basename eq 'index.html') { + # Get the 2nd ancestor, since the asset url had no dot in it (and it therefore + # had its own directory created for it). + $filedir = $basepath->parent->parent->absolute->stringify; + # Get the parent dir's *path* (essentially the name of the dir) relative to + # its own parent dir. + $filenameBase = $basepath->parent->relative( $basepath->parent->parent )->stringify; + } else { + # Get the 1st ancestor, since the asset is a file recognized by apache, so + # we want our files in the same dir. + $filedir = $basepath->parent->absolute->stringify; + # just use the basename. + $filenameBase = $basename; + } + + $self->{ '_masterSession' }->output->print('
') unless ($args->{quiet}); + + foreach my $ext (qw( rss atom )) { + my $dest = Path::Class::File->new($filedir, $filenameBase . '.' . $ext); + + # tell the user which asset we're exporting. + unless ($args->{quiet}) { + my $message = sprintf $self->{ '_masteri18n' }->get('exporting page'), $dest->absolute->stringify; + $self->{ '_masterSession' }->output->print('      '.$message); + } + + # open another session as the user doing the exporting... + my $tempSession = WebGUI::Session->open($self->session->config->getWebguiRoot,$self->session->config->getFilename); + $tempSession->user( { userId => $self->session->user->userId } ); + + my $selfdupe = WebGUI::Asset->newByDynamicClass( $tempSession, $self->getId ); + + + # next, get the contents, open the file, and write the contents to the file. + my $fh = eval { $dest->open('>:utf8') }; + if($@) { + WebGUI::Error->throw(error => "can't open " . $dest->absolute->stringify . " for writing: $!"); + } + my $previousHandle = $selfdupe->session->{_handle}; + my $previousDefaultAsset = $selfdupe->session->asset; + $selfdupe->session->asset($selfdupe); + $selfdupe->session->output->setHandle($fh); + my $contents; + if ($ext eq 'rss') { + $contents = $selfdupe->www_viewRss; + } else { + $contents = $selfdupe->www_viewAtom; + } # add more for more extensions. + + # chunked content is already printed, no need to print it again + unless($contents eq 'chunked') { + $tempSession->output->print($contents); + } + + $tempSession->output->setHandle($previousHandle); + + # properly close the temp session + $tempSession->var->end; + $tempSession->close; + + # tell the user we did this asset collateral correctly + unless( $args->{quiet} ) { + $self->{ '_masterSession' }->output->print($self->{ '_masteri18n' }->get('done')); + } + + } + return $self->next::method($basepath, $args); +} + +#------------------------------------------------------------------- + +=head2 getRssFeedItems () + +This method should throw an exception if it's not overridden. Its intention is +to be overridden by whatever class is using it and should return an array +reference of hash references. Each hash reference should contain at minimum a title, +description, link, and date field. The date field can be either an epoch date, an RFC 1123 +date, or a ISO date in the format of YYYY-MM-DD HH:MM::SS. Optionally specify an +author, and a guid field. + +=cut + +sub getRssFeedItems { + WebGUI::Error::OverrideMe->throw(); +} + +#------------------------------------------------------------------- + +=head2 getAtomFeedUrl () + +Returns $self->getUrl(“func=viewAtom”). + +=cut + +sub getAtomFeedUrl { + shift->getUrl("func=viewAtom"); +} + +#------------------------------------------------------------------- + +=head2 getRssFeedUrl () + +Returns $self->getUrl(“func=viewRss”). + +=cut + +sub getRssFeedUrl { + shift->getUrl("func=viewRss"); +} + +#------------------------------------------------------------------- + +=head2 getStaticAtomFeedUrl () + +Returns the current asset's URL with .atom concatenated onto it. + +=cut + +sub getStaticAtomFeedUrl { + shift->getUrl() . '.atom'; +} + +#------------------------------------------------------------------- + +=head2 getStaticRssFeedUrl () + +Returns the current asset's URL with .rss concatenated onto it. + +=cut + +sub getStaticRssFeedUrl { + shift->getUrl() . '.rss'; +} + +#------------------------------------------------------------------- + +=head2 getFeed () + +Adds the syndicated items to the feed; returns the stringified edition. +TODO: convert dates? + +=cut + +sub getFeed { + my $self = shift; + my $feed = shift; + foreach my $item ( @{ $self->getRssFeedItems } ) { + my $set_permalink_false = 0; + my $new_item = $feed->add_item( %{ $item } ); + if (!$new_item->guid) { + if ($new_item->link) { + $new_item->guid( $new_item->link ); + } else { + $new_item->guid( $self->session->id->generate ); + $set_permalink_false = 1; + } + } + $new_item->guid( $new_item->guid, isPermaLink => 0 ) if $set_permalink_false; + } + $feed->title( $self->get('feedTitle') || $self->get('title') ); + $feed->description( $self->get('feedDescription') || $self->get('synopsis') ); + $feed->pubDate( $self->getContentLastModified ); + $feed->copyright( $self->get('feedCopyright') ); + $feed->link( $self->getUrl ); + # $feed->language( $lang ); + if ($self->get('feedImage')) { + my $storage = WebGUI::Storage->get($self->session, $self->get('feedImage')); + my @files = @{ $storage->getFiles }; + if (scalar @files) { + $feed->image( + $storage->getUrl( $files[0] ), + $self->get('feedImageDescription') || $self->getTitle, + $self->get('feedImageUrl') || $self->getUrl, + $self->get('feedImageDescription') || $self->getTitle, + ( $storage->getSizeInPixels( $files[0] ) ) # expands to width and height + ); + } + } + return $feed; +} + +#------------------------------------------------------------------- + +=head2 www_viewAtom () + +Return Atom view of the syndicated items. + +=cut + +sub www_viewAtom { + my $self = shift; + $self->session->http->setMimeType('application/atom+xml'); + return $self->getFeed( XML::FeedPP::Atom->new )->to_string; +} + +#------------------------------------------------------------------- + +=head2 www_viewRdf () + +Return Rdf view of the syndicated items. + +=cut + +sub www_viewRdf { + my $self = shift; + $self->session->http->setMimeType('application/rdf+xml'); + return $self->getFeed( XML::FeedPP::Rdf->new )->to_string; +} + +#------------------------------------------------------------------- + +=head2 www_viewRss () + +Return RSS view of the syndicated items. + +=cut + +sub www_viewRss { + my $self = shift; + $self->session->http->setMimeType('application/rss+xml'); + return $self->getFeed( XML::FeedPP::RSS->new )->to_string; +} + +#------------------------------------------------------------------- + +=head2 getEditTabs () + +Adds an RSS tab to the Edit Tabs. + +=cut + +sub getEditTabs { + my $self = shift; + my $i18n = WebGUI::International->new($self->session,'AssetAspect_RssFeed'); + return ($self->next::method, ['rss', $i18n->get('RSS tab'), 1]); +} + +1; + diff --git a/lib/WebGUI/AssetExportHtml.pm b/lib/WebGUI/AssetExportHtml.pm index 6be9e950c..cfca521d6 100644 --- a/lib/WebGUI/AssetExportHtml.pm +++ b/lib/WebGUI/AssetExportHtml.pm @@ -330,7 +330,12 @@ sub exportAsHtml { $exportSession->close; return ($returnCode, $message); } - + + # Stash the current session and i18n into the asset so that exportAssetCollateral can + # also write informative messages to the output terminal. :) + $asset->{ '_masterSession' } = $self->session; + $asset->{ '_masteri18n' } = $i18n; + # next, tell the asset that we're exporting, so that it can export any # of its collateral or other extra data. eval { $asset->exportAssetCollateral($asset->exportGetUrlAsPath, $args) }; diff --git a/lib/WebGUI/i18n/English/AssetAspect_RssFeed.pm b/lib/WebGUI/i18n/English/AssetAspect_RssFeed.pm new file mode 100644 index 000000000..9e71c4d8b --- /dev/null +++ b/lib/WebGUI/i18n/English/AssetAspect_RssFeed.pm @@ -0,0 +1,99 @@ +package WebGUI::i18n::English::AssetAspect_RssFeed; +use strict; + +our $I18N = { + + 'itemsPerFeed' => { + message => q|Items Per Feed|, + lastUpdated => 1236820473, + context => q|The name of the itemsPerFeed field.| + }, + + 'itemsPerFeed hoverHelp' => { + message => q|The number of items to include in the feed.|, + lastUpdated => 1236820473, + context => q|The hoverhelp of the itemsPerFeed field.| + }, + + 'feedCopyright' => { + message => q|Feed Copyright|, + lastUpdated => 1236820473, + context => q|The name of the feedCopyright field.| + }, + + 'feedCopyright hoverHelp' => { + message => q|An optional copyright notice for the feed.|, + lastUpdated => 1236820473, + context => q|The hoverhelp of the feedCopyright field.| + }, + + 'feedTitle' => { + message => q|Feed Title|, + lastUpdated => 1236820473, + context => q|The name of the feedTitle field.| + }, + + 'feedTitle hoverHelp' => { + message => q|An optional title for the feed. If not specified the asset's title will be used instead.|, + lastUpdated => 1236820473, + context => q|The hoverhelp of the feedTitle field.| + }, + + 'feedDescription' => { + message => q|Feed Description|, + lastUpdated => 1236820473, + context => q|The name of the feedDescription field.| + }, + + 'feedDescription hoverHelp' => { + message => q|An optional description for the feed. If not specified the asset's synopsis will be used instead.|, + lastUpdated => 1236820473, + context => q|The hoverhelp of the feedDescription field.| + }, + + 'feedImage' => { + message => q|Feed Image|, + lastUpdated => 1236820473, + context => q|The name of the feedImage field.| + }, + + 'feedImage hoverHelp' => { + message => q|An optional image that can be uploaded for the feed.|, + lastUpdated => 1236820473, + context => q|The hoverhelp of the feedImage field.| + }, + + 'feedImageLink' => { + message => q|Feed Image Link|, + lastUpdated => 1236820473, + context => q|The name of the feedImageLink field.| + }, + + 'feedImageLink hoverHelp' => { + message => q|An optional URL that will link the image to a specific location. If not specified the asset's URL will be used instead.|, + lastUpdated => 1236820473, + context => q|The hoverhelp of the feedImageLink field.| + }, + + 'feedImageDescription' => { + message => q|Feed Image Description|, + lastUpdated => 1236820473, + context => q|The name of the feedImageDescription field.| + }, + + 'feedImageDescription hoverHelp' => { + message => q|An optional description for the image. If not specified the asset's title will be used instead.|, + lastUpdated => 1236820473, + context => q|The hoverhelp of the feedImageDescription field.| + }, + + 'RSS tab' => { + message => q|RSS|, + lastUpdated => 1236820473, + context => q|The title of the RSS tab on the asset's edit form.| + }, + +}; + +1; +#vim:ft=perl \ No newline at end of file