From 9248570f79736bd0199edcc0bfcab4e799e7296f Mon Sep 17 00:00:00 2001 From: JT Smith Date: Sat, 16 Jun 2007 19:33:12 +0000 Subject: [PATCH] Added Newsletter Asset (Funded by United Knowledge) --- docs/changelog/7.x.x.txt | 6 +- .../PBtmpl0000000000000029.tmpl | 90 ++++++ .../PBtmpl0000000000000068.tmpl | 103 +++++++ .../newsletter0000000000001.tmpl | 18 ++ .../newslettercs000000001.tmpl | 67 +++++ .../newslettersubscrip00001.tmpl | 22 ++ docs/upgrades/upgrade_7.3.19-7.4.0.pl | 31 ++ lib/WebGUI/Asset.pm | 17 +- lib/WebGUI/Asset/Wobject/Collaboration.pm | 281 +++++++++--------- .../Asset/Wobject/Collaboration/Newsletter.pm | 242 +++++++++++++++ lib/WebGUI/AssetMetaData.pm | 13 +- lib/WebGUI/HTMLForm.pm | 2 +- lib/WebGUI/Help/Asset_Newsletter.pm | 128 ++++++++ .../Workflow/Activity/SendNewsletters.pm | 207 +++++++++++++ lib/WebGUI/i18n/English/Asset.pm | 9 + lib/WebGUI/i18n/English/Asset_Newsletter.pm | 208 +++++++++++++ 16 files changed, 1295 insertions(+), 149 deletions(-) create mode 100644 docs/upgrades/templates-7.4.0/PBtmpl0000000000000029.tmpl create mode 100644 docs/upgrades/templates-7.4.0/PBtmpl0000000000000068.tmpl create mode 100644 docs/upgrades/templates-7.4.0/newsletter0000000000001.tmpl create mode 100644 docs/upgrades/templates-7.4.0/newslettercs000000001.tmpl create mode 100644 docs/upgrades/templates-7.4.0/newslettersubscrip00001.tmpl create mode 100644 lib/WebGUI/Asset/Wobject/Collaboration/Newsletter.pm create mode 100644 lib/WebGUI/Help/Asset_Newsletter.pm create mode 100644 lib/WebGUI/Workflow/Activity/SendNewsletters.pm create mode 100644 lib/WebGUI/i18n/English/Asset_Newsletter.pm diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 05e690a53..ac5147624 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -35,7 +35,11 @@ - Added a simple Single Sign On mechanism. - Added the SessionId macro. - fix: Package deploy: hidden assets become visible (Yung Han Khoe, United Knowledge) - - RFE: Added Private Messaging to WebGUI (United Knowledge) + - Added Private Messaging (Funded by United Knowledge) + - Added Newsletter Asset (Funded by United Knowledge) + - Extended content profiling to CS Threads (Funded by United Knowledge) + - Added User Invitations (Funded by United Knowledge) + 7.3.19 - Fixed a formatting problem in the workflow editor screen. diff --git a/docs/upgrades/templates-7.4.0/PBtmpl0000000000000029.tmpl b/docs/upgrades/templates-7.4.0/PBtmpl0000000000000029.tmpl new file mode 100644 index 000000000..43b07672e --- /dev/null +++ b/docs/upgrades/templates-7.4.0/PBtmpl0000000000000029.tmpl @@ -0,0 +1,90 @@ +#PBtmpl0000000000000029 + + + +

+
+ + +

+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +

+ +
+ + +~~~ + diff --git a/docs/upgrades/templates-7.4.0/PBtmpl0000000000000068.tmpl b/docs/upgrades/templates-7.4.0/PBtmpl0000000000000068.tmpl new file mode 100644 index 000000000..7326180f1 --- /dev/null +++ b/docs/upgrades/templates-7.4.0/PBtmpl0000000000000068.tmpl @@ -0,0 +1,103 @@ +#PBtmpl0000000000000068 + + +

+

+

+ + +

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+

+ +
+
+ +~~~ + diff --git a/docs/upgrades/templates-7.4.0/newsletter0000000000001.tmpl b/docs/upgrades/templates-7.4.0/newsletter0000000000001.tmpl new file mode 100644 index 000000000..fbcee1fb9 --- /dev/null +++ b/docs/upgrades/templates-7.4.0/newsletter0000000000001.tmpl @@ -0,0 +1,18 @@ +#newsletter000000000001 +#url: newsletterdefaulttemplate +#title: Summary Newsletter (default) +#menuTitle: Summary Newsletter +#namespace:newsletter +#create + +

+ + +

+
+

+

+
+ +

+ diff --git a/docs/upgrades/templates-7.4.0/newslettercs000000001.tmpl b/docs/upgrades/templates-7.4.0/newslettercs000000001.tmpl new file mode 100644 index 000000000..8cf6419b1 --- /dev/null +++ b/docs/upgrades/templates-7.4.0/newslettercs000000001.tmpl @@ -0,0 +1,67 @@ +#newslettercs0000000001 +#url: newslettercstemplate +#title: Newsletter Manager (default) +#menuTitle: Newsletter Manager +#namespace:Collaboration +#create + + + +

+
+ + +

+
+ + + + + +

+ + + | + + + ^International("my subscriptions","Asset_Newsletter"); + | + + +

+ +

+ + +


+

+ + + + + +~~~ + diff --git a/docs/upgrades/templates-7.4.0/newslettersubscrip00001.tmpl b/docs/upgrades/templates-7.4.0/newslettersubscrip00001.tmpl new file mode 100644 index 000000000..90a62d772 --- /dev/null +++ b/docs/upgrades/templates-7.4.0/newslettersubscrip00001.tmpl @@ -0,0 +1,22 @@ +#newslettersubscrip0001 +#url: newslettermysubscriptionstemplate +#title: My Subscriptions (default) +#menuTitle: My Subscriptions +#namespace:newsletter/mysubscriptions +#create + +

^International("my subscriptions","Asset_Newsletter");

+ + +

^International("newsletter categories","Asset_Newsletter");

+ +


+

+ +
+
+

+
+ + + diff --git a/docs/upgrades/upgrade_7.3.19-7.4.0.pl b/docs/upgrades/upgrade_7.3.19-7.4.0.pl index 2e805c39f..5f04d1bd1 100644 --- a/docs/upgrades/upgrade_7.3.19-7.4.0.pl +++ b/docs/upgrades/upgrade_7.3.19-7.4.0.pl @@ -28,10 +28,41 @@ addAttachmentsToEvents($session); addMetaDataPostsToCS($session); addUserInvitations($session); addPrivateMessaging($session); +addNewsletter($session); finish($session); # this line required +#------------------------------------------------- +sub addNewsletter { + my $session = shift; + print "\tAdding a newsletter management system.\n" unless ($quiet); + $session->config->addToArray("assets","WebGUI::Asset::Wobject::Collaboration::Newsletter"); + my $db = $session->db; + $db->write("create table Newsletter ( + assetId varchar(22) binary not null, + revisionDate bigint not null, + newsletterTemplateId varchar(22) binary not null default 'newsletter000000000001', + mySubscriptionsTemplateId varchar(22) binary not null default 'newslettersubscrip0001', + newsletterHeader mediumtext, + newsletterFooter mediumtext, + newsletterCategories text, + primary key (assetId, revisionDate) + )"); + $db->write("create table Newsletter_subscriptions ( + assetId varchar(22) binary not null, + userId varchar(22) binary not null, + subscriptions text, + lastTimeSent bigint not null default 0, + primary key (assetId, userId) + )"); + $db->write("alter table Newsletter_subscriptions add index lastTimeSent_assetId_userId + (lastTimeSent,assetId,userId)"); + my $workflow = WebGUI::Workflow->new($session, "pbworkflow000000000002"); + my $activity = $workflow->addActivity("WebGUI::Workflow::Activity::SendNewsletters","newslettersendactivity"); + $activity->set("title","Send Newsletters For Newsletter Assets"); +} + #------------------------------------------------- sub addRealtimeWorkflow { my $session = shift; diff --git a/lib/WebGUI/Asset.pm b/lib/WebGUI/Asset.pm index 02ae08444..bb59baf71 100644 --- a/lib/WebGUI/Asset.pm +++ b/lib/WebGUI/Asset.pm @@ -1694,10 +1694,11 @@ anything. sub processPropertiesFromFormPost { my $self = shift; my %data; + my $form = $self->session->form; foreach my $definition (@{$self->definition($self->session)}) { foreach my $property (keys %{$definition->{properties}}) { if ($definition->{properties}{$property}{noFormPost}) { - if ($self->session->form->process("assetId") eq "new" && $self->get($property) eq "") { + if ($form->process("assetId") eq "new" && $self->get($property) eq "") { $data{$property} = $definition->{properties}{$property}{defaultValue}; } next; @@ -1705,7 +1706,7 @@ sub processPropertiesFromFormPost { my %params = %{$definition->{properties}{$property}}; $params{name} = $property; $params{value} = $self->get($property); - $data{$property} = $self->session->form->process( + $data{$property} = $form->process( $property, $definition->{properties}{$property}{fieldType}, $definition->{properties}{$property}{defaultValue}, @@ -1713,11 +1714,13 @@ sub processPropertiesFromFormPost { ); } } - foreach my $form ($self->session->form->param) { - if ($form =~ /^metadata_(.*)$/) { - $self->updateMetaData($1,$self->session->form->process($form)); - } - } + if ($self->session->setting->get("metaDataEnabled")) { + my $meta = $self->getMetaDataFields; + foreach my $field (keys %{$meta}) { + my $value = $form->process("metadata_".$field, $meta->{$field}{fieldType}, $meta->{$field}{defaultValue}); + $self->updateMetaData($field, $value); + } + } $self->session->db->beginTransaction; $self->update(\%data); $self->session->db->commit; diff --git a/lib/WebGUI/Asset/Wobject/Collaboration.pm b/lib/WebGUI/Asset/Wobject/Collaboration.pm index 00dc6e14b..05fa20de4 100644 --- a/lib/WebGUI/Asset/Wobject/Collaboration.pm +++ b/lib/WebGUI/Asset/Wobject/Collaboration.pm @@ -24,6 +24,54 @@ use WebGUI::Asset::RSSCapable; use base 'WebGUI::Asset::RSSCapable'; use base 'WebGUI::Asset::Wobject'; +#------------------------------------------------------------------- +sub _computePostCount { + my $self = shift; + return scalar @{$self->getLineage(['descendants'], {includeOnlyClasses => ['WebGUI::Asset::Post']})}; +} + +#------------------------------------------------------------------- +sub _computeThreadCount { + my $self = shift; + return scalar @{$self->getLineage(['children'], {includeOnlyClasses => ['WebGUI::Asset::Post::Thread']})}; +} + +#------------------------------------------------------------------- +# format the date according to rfc 822 (for RSS export) +my @_months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); +sub _get_rfc822_date { + my $self = shift; + my ($time) = @_; + my ($year, $mon, $mday, $hour, $min, $sec) = $self->session->datetime->localtime($time); + my $month = $_months[$mon - 1]; + return sprintf("%02d %s %04d %02d:%02d:%02d GMT", + $mday, $month, $year, $hour, $min, $sec); +} + +#------------------------------------------------------------------- +sub _visitorCacheKey { + my $self = shift; + my $pn = $self->session->form->process('pn'); + return "view_".$self->getId."?pn=".$pn; +} + +#------------------------------------------------------------------- +sub _visitorCacheOk { + my $self = shift; + return ($self->session->user->userId eq '1' + && !$self->session->form->process('sortBy')); +} + +#------------------------------------------------------------------- +# encode a string to include in xml (for RSS export) +sub _xml_encode { + my $text = shift; + $text =~ s/&/&/g; + $text =~ s//\]\]>/g; + return $text; +} + #------------------------------------------------------------------- sub addChild { my $self = shift; @@ -554,7 +602,7 @@ sub definition { fieldType=>"template", namespace=>"Collaboration/Notification", defaultValue=>'PBtmpl0000000000000027', - tab=>'display', + tab=>'mail', label=>$i18n->get('notification template'), hoverHelp=>$i18n->get('notification template description'), }, @@ -664,6 +712,27 @@ sub duplicate { return $newAsset; } +#------------------------------------------------------------------- +sub getEditTabs { + my $self = shift; + my $i18n = WebGUI::International->new($self->session,"Asset_Collaboration"); + + return (['mail', $i18n->get('mail'), 9]); +} + +#------------------------------------------------------------------- + +=head2 getNewThreadUrl( ) + +Formats the url to start a new thread. + +=cut + +sub getNewThreadUrl { + my $self = shift; + $self->getUrl("func=add;class=WebGUI::Asset::Post::Thread"); +} + #------------------------------------------------------------------- sub getRssItems { my $self = shift; @@ -726,27 +795,6 @@ SQL return @posts; } -#------------------------------------------------------------------- -sub getEditTabs { - my $self = shift; - my $i18n = WebGUI::International->new($self->session,"Asset_Collaboration"); - - return (['mail', $i18n->get('mail'), 9]); -} - -#------------------------------------------------------------------- - -=head2 getNewThreadUrl( ) - -Formats the url to start a new thread. - -=cut - -sub getNewThreadUrl { - my $self = shift; - $self->getUrl("func=add;class=WebGUI::Asset::Post::Thread"); -} - #------------------------------------------------------------------- =head2 getSearchUrl ( ) @@ -806,14 +854,78 @@ sub getUnsubscribeUrl { #------------------------------------------------------------------- -sub _computeThreadCount { +sub getViewTemplateVars { my $self = shift; - return scalar @{$self->getLineage(['children'], {includeOnlyClasses => ['WebGUI::Asset::Post::Thread']})}; -} - -sub _computePostCount { - my $self = shift; - return scalar @{$self->getLineage(['descendants'], {includeOnlyClasses => ['WebGUI::Asset::Post']})}; + my $scratchSortBy = $self->getId."_sortBy"; + my $scratchSortOrder = $self->getId."_sortDir"; + my $sortBy = $self->session->form->process("sortBy") || $self->session->scratch->get($scratchSortBy) || $self->get("sortBy"); + my $sortOrder = $self->session->scratch->get($scratchSortOrder) || $self->get("sortOrder"); + if ($sortBy ne $self->session->scratch->get($scratchSortBy) && $self->session->form->process("func") ne "editSave") { + $self->session->scratch->set($scratchSortBy,$self->session->form->process("sortBy")); + } elsif ($self->session->form->process("sortBy") && $self->session->form->process("func") ne "editSave") { + if ($sortOrder eq "asc") { + $sortOrder = "desc"; + } else { + $sortOrder = "asc"; + } + $self->session->scratch->set($scratchSortOrder, $sortOrder); + } + $sortBy ||= "dateUpdated"; + $sortOrder ||= "desc"; + my %var; + $var{'user.canPost'} = $self->canPost; + $var{"add.url"} = $self->getNewThreadUrl; + $var{"rss.url"} = $self->getRssUrl; + $var{'user.isModerator'} = $self->canModerate; + $var{'user.isVisitor'} = ($self->session->user->userId eq '1'); + $var{'user.isSubscribed'} = $self->isSubscribed; + $var{'sortby.title.url'} = $self->getSortByUrl("title"); + $var{'sortby.username.url'} = $self->getSortByUrl("username"); + $var{'karmaIsEnabled'} = $self->session->setting->get("useKarma"); + $var{'sortby.karmaRank.url'} = $self->getSortByUrl("karmaRank"); + $var{'sortby.date.url'} = $self->getSortByUrl("dateSubmitted"); + $var{'sortby.lastreply.url'} = $self->getSortByUrl("lastPostDate"); + $var{'sortby.views.url'} = $self->getSortByUrl("views"); + $var{'sortby.replies.url'} = $self->getSortByUrl("replies"); + $var{'sortby.rating.url'} = $self->getSortByUrl("rating"); + $var{"search.url"} = $self->getSearchUrl; + $var{"subscribe.url"} = $self->getSubscribeUrl; + $var{"unsubscribe.url"} = $self->getUnsubscribeUrl; + $var{"collaborationAssetId"} = $self->getId; + my $sql = " + select + asset.assetId, + asset.className, + assetData.revisionDate as revisionDate + from Thread + left join asset on Thread.assetId=asset.assetId + left join Post on Post.assetId=Thread.assetId and Thread.revisionDate = Post.revisionDate + left join assetData on assetData.assetId=Thread.assetId and Thread.revisionDate = assetData.revisionDate + where + asset.parentId=".$self->session->db->quote($self->getId)." + and asset.state='published' + and asset.className='WebGUI::Asset::Post::Thread' + and assetData.revisionDate=( + select + max(revisionDate) + from + assetData + where + assetData.assetId=asset.assetId + and (status='approved' or status='archived') + ) + and status='approved' + group by + assetData.assetId + order by + Thread.isSticky desc, + ".$sortBy." + ".$sortOrder; + my $p = WebGUI::Paginator->new($self->session,$self->getUrl,$self->get("threadsPerPage")); + $p->setDataByQuery($sql); + $self->appendPostListTemplateVars(\%var, $p); + $self->appendTemplateLabels(\%var); + return \%var; } #------------------------------------------------------------------- @@ -1053,18 +1165,6 @@ sub unsubscribe { #------------------------------------------------------------------- -sub _visitorCacheOk { - my $self = shift; - return ($self->session->user->userId eq '1' - && !$self->session->form->process('sortBy')); -} - -sub _visitorCacheKey { - my $self = shift; - my $pn = $self->session->form->process('pn'); - return "view_".$self->getId."?pn=".$pn; -} - sub view { my $self = shift; if ($self->_visitorCacheOk) { @@ -1072,87 +1172,16 @@ sub view { $self->session->errorHandler->debug("HIT") if $out; return $out if $out; } - my $scratchSortBy = $self->getId."_sortBy"; - my $scratchSortOrder = $self->getId."_sortDir"; - my $sortBy = $self->session->form->process("sortBy") || $self->session->scratch->get($scratchSortBy) || $self->get("sortBy"); - my $sortOrder = $self->session->scratch->get($scratchSortOrder) || $self->get("sortOrder"); - if ($sortBy ne $self->session->scratch->get($scratchSortBy) && $self->session->form->process("func") ne "editSave") { - $self->session->scratch->set($scratchSortBy,$self->session->form->process("sortBy")); - } elsif ($self->session->form->process("sortBy") && $self->session->form->process("func") ne "editSave") { - if ($sortOrder eq "asc") { - $sortOrder = "desc"; - } else { - $sortOrder = "asc"; - } - $self->session->scratch->set($scratchSortOrder, $sortOrder); - } - $sortBy ||= "dateUpdated"; - $sortOrder ||= "desc"; - my %var; - $var{'user.canPost'} = $self->canPost; - $var{"add.url"} = $self->getNewThreadUrl; - $var{"rss.url"} = $self->getRssUrl; - $var{'user.isModerator'} = $self->canModerate; - $var{'user.isVisitor'} = ($self->session->user->userId eq '1'); - $var{'user.isSubscribed'} = $self->isSubscribed; - $var{'sortby.title.url'} = $self->getSortByUrl("title"); - $var{'sortby.username.url'} = $self->getSortByUrl("username"); - $var{'karmaIsEnabled'} = $self->session->setting->get("useKarma"); - $var{'sortby.karmaRank.url'} = $self->getSortByUrl("karmaRank"); - $var{'sortby.date.url'} = $self->getSortByUrl("dateSubmitted"); - $var{'sortby.lastreply.url'} = $self->getSortByUrl("lastPostDate"); - $var{'sortby.views.url'} = $self->getSortByUrl("views"); - $var{'sortby.replies.url'} = $self->getSortByUrl("replies"); - $var{'sortby.rating.url'} = $self->getSortByUrl("rating"); - $var{"search.url"} = $self->getSearchUrl; - $var{"subscribe.url"} = $self->getSubscribeUrl; - $var{"unsubscribe.url"} = $self->getUnsubscribeUrl; - $var{"collaborationAssetId"} = $self->getId; - my $sql = " - select - asset.assetId, - asset.className, - assetData.revisionDate as revisionDate - from Thread - left join asset on Thread.assetId=asset.assetId - left join Post on Post.assetId=Thread.assetId and Thread.revisionDate = Post.revisionDate - left join assetData on assetData.assetId=Thread.assetId and Thread.revisionDate = assetData.revisionDate - where - asset.parentId=".$self->session->db->quote($self->getId)." - and asset.state='published' - and asset.className='WebGUI::Asset::Post::Thread' - and assetData.revisionDate=( - select - max(revisionDate) - from - assetData - where - assetData.assetId=asset.assetId - and (status='approved' or status='archived') - ) - and status='approved' - group by - assetData.assetId - order by - Thread.isSticky desc, - ".$sortBy." - ".$sortOrder; - my $p = WebGUI::Paginator->new($self->session,$self->getUrl,$self->get("threadsPerPage")); - $p->setDataByQuery($sql); - $self->appendPostListTemplateVars(\%var, $p); - $self->appendTemplateLabels(\%var); # If the asset is not called through the normal prepareView/view cycle, first call prepareView. # This happens for instance in the viewDetail method in the Matrix. In that case the Collaboration # is called through the api. $self->prepareView unless ($self->{_viewTemplate}); - my $out = $self->processTemplate(\%var,undef,$self->{_viewTemplate}); + my $out = $self->processTemplate($self->getViewTemplateVars,undef,$self->{_viewTemplate}); if ($self->_visitorCacheOk) { - WebGUI::Cache->new($self->session,$self->_visitorCacheKey) - ->set($out,$self->get("visitorCacheTimeout")); - $self->session->errorHandler->debug("MISS"); + WebGUI::Cache->new($self->session,$self->_visitorCacheKey)->set($out,$self->get("visitorCacheTimeout")); } - return $out; + return $out; } #------------------------------------------------------------------- @@ -1221,28 +1250,6 @@ sub www_unsubscribe { return $self->www_view; } -#------------------------------------------------------------------- -# format the date according to rfc 822 (for RSS export) -my @_months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); -sub _get_rfc822_date { - my $self = shift; - my ($time) = @_; - my ($year, $mon, $mday, $hour, $min, $sec) = $self->session->datetime->localtime($time); - my $month = $_months[$mon - 1]; - return sprintf("%02d %s %04d %02d:%02d:%02d GMT", - $mday, $month, $year, $hour, $min, $sec); -} - -#------------------------------------------------------------------- -# encode a string to include in xml (for RSS export) -sub _xml_encode { - my $text = shift; - $text =~ s/&/&/g; - $text =~ s//\]\]>/g; - return $text; -} - #------------------------------------------------------------------- sub www_view { my $self = shift; diff --git a/lib/WebGUI/Asset/Wobject/Collaboration/Newsletter.pm b/lib/WebGUI/Asset/Wobject/Collaboration/Newsletter.pm new file mode 100644 index 000000000..c6713205a --- /dev/null +++ b/lib/WebGUI/Asset/Wobject/Collaboration/Newsletter.pm @@ -0,0 +1,242 @@ +package WebGUI::Asset::Wobject::Collaboration::Newsletter; + +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2006 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 +#------------------------------------------------------------------- + +use strict; +use Tie::IxHash; +use WebGUI::Form; +use WebGUI::International; +use WebGUI::Utility; +use base 'WebGUI::Asset::Wobject::Collaboration'; + +#------------------------------------------------------------------- + +=head2 definition ( ) + +defines wobject properties for Newsletter instances. You absolutely need +this method in your new Wobjects. If you choose to "autoGenerateForms", the +getEditForm method is unnecessary/redundant/useless. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new($session, 'Asset_Newsletter'); + my %properties; + tie %properties, 'Tie::IxHash'; + %properties = ( + newsletterHeader => { + defaultValue=>undef, + fieldType=>"HTMLArea", + tab=>"mail", + label=>$i18n->get("newsletter header"), + hoverHelp=>$i18n->get("newsletter header help"), + }, + newsletterFooter => { + defaultValue=>undef, + fieldType=>"HTMLArea", + tab=>"mail", + label=>$i18n->get("newsletter footer"), + hoverHelp=>$i18n->get("newsletter footer help"), + }, + newsletterTemplateId => { + defaultValue=>'newsletter000000000001', + fieldType=>"template", + namespace=>"newsletter", + tab=>"mail", + label=>$i18n->get("newsletter template"), + hoverHelp=>$i18n->get("newsletter template help"), + }, + mySubscriptionsTemplateId => { + defaultValue=>'newslettersubscrip0001', + fieldType=>"template", + namespace=>"newsletter/mysubscriptions", + tab=>"display", + label=>$i18n->get("my subscriptions template"), + hoverHelp=>$i18n->get("my subscriptions template help"), + }, + ); + if ($session->setting->get("metaDataEnabled")) { + $properties{newsletterCategories} = { + defaultValue=>undef, + fieldType=>"checkList", + tab=>"properties", + options=>$session->db->buildHashRef("select fieldId, fieldName from metaData_properties where + fieldType in ('selectBox', 'checkList', 'radioList') order by fieldName"), + label=>$i18n->get("newsletter categories"), + hoverHelp=>$i18n->get("newsletter categories help"), + vertical=>1, + }; + } + else { + $properties{newsletterCategories} = { + fieldType=>"readOnly", + value=>''.$i18n->get("content profiling needed").'', + }; + } + push(@{$definition}, { + assetName=>$i18n->get('assetName'), + icon=>'newsletter.gif', + autoGenerateForms=>1, + tableName=>'Newsletter', + className=>'WebGUI::Asset::Wobject::Collaboration::Newsletter', + properties=>\%properties + }); + return $class->SUPER::definition($session, $definition); +} + + +#------------------------------------------------------------------- + +=head2 duplicate ( ) + +duplicates a Newsletter. This method is unnecessary, but if you have +auxiliary, ancillary, or "collateral" data or files related to your +wobject instances, you will need to duplicate them here. + +=cut + +sub duplicate { + my $self = shift; + my $newAsset = $self->SUPER::duplicate(@_); + return $newAsset; +} + + +#------------------------------------------------------------------- +sub getUserSubscriptions { + my $self = shift; + my $userId = shift || $self->session->user->userId; + my ($subscriptionString) = $self->session->db->quickArray("select subscriptions from Newsletter_subscriptions where + assetId=? and userId=?", [$self->getId, $userId]); + return split("\n", $subscriptionString); +} + +#------------------------------------------------------------------- + +sub getViewTemplateVars { + my $self = shift; + my $var = $self->SUPER::getViewTemplateVars; + $var->{mySubscriptionsUrl} = $self->getUrl("func=mySubscriptions"); + return $var; +} + + + +#------------------------------------------------------------------- +sub purge { + my $self = shift; + $self->session->db->write("delete from Newsletter_subscriptions where assetId=?", [$self->getId]); + $self->SUPER::purge(@_); +} + + +#------------------------------------------------------------------- +sub setUserSubscriptions { + my $self = shift; + my $subscriptions = shift; + my $userId = shift || $self->session->user->userId; + $self->session->db->write("replace into Newsletter_subscriptions (assetId, userId, subscriptions, lastSendTime) + values (?,?,?,?)", [$self->getId, $userId, $subscriptions, time()]); +} + +#------------------------------------------------------------------- + +=head2 view ( ) + +method called by the www_view method. Returns a processed template +to be displayed within the page style. + +=cut + +sub view { + my $self = shift; + my $session = $self->session; + + #This automatically creates template variables for all of your wobject's properties. + my $var = $self->getViewTemplateVars; + + #This is an example of debugging code to help you diagnose problems. + #WebGUI::ErrorHandler::warn($self->get("templateId")); + + return $self->processTemplate($var, undef, $self->{_viewTemplate}); +} + +#------------------------------------------------------------------- + +=head2 www_edit ( ) + +Web facing method which is the default edit page. This method is entirely +optional. Take it out unless you specifically want to set a submenu in your +adminConsole views. + +=cut + +sub www_edit { + my $self = shift; + return $self->session->privilege->insufficient() unless $self->canEdit; + return $self->session->privilege->locked() unless $self->canEditIfLocked; + my $i18n = WebGUI::International->new($self->session, "Asset_Newsletter"); + return $self->getAdminConsole->render($self->getEditForm->print, $i18n->get("edit title")); +} + +#------------------------------------------------------------------- +sub www_mySubscriptions { + my $self = shift; + return $self->session->privilege->insufficient unless ($self->canView && $self->session->user->userId ne "1"); + my %var = (); + my $meta = $self->getMetaDataFields; + my @categories = (); + my @userPrefs = $self->getUserSubscriptions; + foreach my $id (keys %{$meta}) { + my @options = (); + if (isIn($id, split("\n", $self->get("newsletterCategories")))) { + foreach my $option (split("\n", $meta->{$id}{possibleValues})) { + $option =~ s/\s+$//; # remove trailing spaces + next if $option eq ""; # skip blank values + my $preferenceName = $id."~".$option; + push(@options, { + optionName => $option, + optionForm => WebGUI::Form::checkbox($self->session, { + name => "subscriptions", + value => $preferenceName, + checked => isIn($preferenceName, @userPrefs), + }) + }); + } + push (@categories, { + categoryName => $meta->{$id}{fieldName}, + optionsLoop => \@options + }); + } + } + $var{categoriesLoop} = \@categories; + if (scalar(@categories)) { + $var{formHeader} = WebGUI::Form::formHeader($self->session, {action=>$self->getUrl, method=>"post"}) + .WebGUI::Form::hidden($self->session, {name=>"func", value=>"mySubscriptionsSave"}); + $var{formFooter} = WebGUI::Form::formFooter($self->session); + $var{formSubmit} = WebGUI::Form::submit($self->session); + } + return $self->processStyle($self->processTemplate(\%var, $self->get("mySubscriptionsTemplateId"))); +} + +#------------------------------------------------------------------- +sub www_mySubscriptionsSave { + my $self = shift; + return $self->session->privilege->insufficient unless ($self->canView && $self->session->user->userId ne "1"); + my $subscriptions = $self->session->form->process("subscriptions", "checkList"); + $self->setUserSubscriptions($subscriptions); + return $self->www_view; +} + +1; diff --git a/lib/WebGUI/AssetMetaData.pm b/lib/WebGUI/AssetMetaData.pm index dba0547c7..9fb82d79c 100644 --- a/lib/WebGUI/AssetMetaData.pm +++ b/lib/WebGUI/AssetMetaData.pm @@ -60,7 +60,7 @@ wasting space in the db for this field. =head3 fieldType -The form field type for metaData, select list, text, integer. +The form field type for metaData: selectBox, text, integer, or checkList, yesNo, radioList. =head3 possibleValues @@ -251,13 +251,14 @@ sub www_editMetaDataField { -name=>"description", -label=>$i18n->get(85), -hoverHelp=>$i18n->get('Metadata Description description'), - -value=>$fieldInfo->{description}); + -value=>$fieldInfo->{description} + ); $f->fieldType( -name=>"fieldType", -label=>$i18n->get(486), -hoverHelp=>$i18n->get('Data Type description'), -value=>$fieldInfo->{fieldType} || "text", - -types=> [ qw /text integer yesNo selectList radioList/ ] + -types=> [ qw /text integer yesNo selectBox radioList checkList/ ] ); $f->textarea( -name=>"possibleValues", @@ -265,6 +266,12 @@ sub www_editMetaDataField { -hoverHelp=>$i18n->get('Possible Values description'), -value=>$fieldInfo->{possibleValues} ); + $f->textarea( + -name=>"defaultValue", + -label=>$i18n->get('default value'), + -hoverHelp=>$i18n->get('default value description'), + -value=>$fieldInfo->{defaultValue} + ); $f->submit(); $ac->setHelp("metadata edit property","Asset"); return $ac->render($f->print, $i18n->get('Edit Metadata')); diff --git a/lib/WebGUI/HTMLForm.pm b/lib/WebGUI/HTMLForm.pm index 32df308fc..9a0824012 100644 --- a/lib/WebGUI/HTMLForm.pm +++ b/lib/WebGUI/HTMLForm.pm @@ -82,7 +82,7 @@ sub AUTOLOAD { my $name = ucfirst((split /::/, $AUTOLOAD)[-1]); my %params = @_; $params{uiLevelOverride} ||= $self->{_uiLevelOverride}; - $params{rowClass} = $self->{_class}; + $params{rowClass} ||= $self->{_class}; my $cmd = "use WebGUI::Form::".$name; eval ($cmd); if ($@) { diff --git a/lib/WebGUI/Help/Asset_Newsletter.pm b/lib/WebGUI/Help/Asset_Newsletter.pm new file mode 100644 index 000000000..1f9cd9e78 --- /dev/null +++ b/lib/WebGUI/Help/Asset_Newsletter.pm @@ -0,0 +1,128 @@ +package WebGUI::Help::Asset_Newsletter; ## Be sure to change the package name to match your filename. + +##Stub document for creating help documents. + +our $HELP = { ##hashref of hashes + 'newsletter add/edit' => { + title => 'newsletter add/edit', + body => 'newsletter add/edit desc', + isa => [ + { + tag => 'collaboration add/edit', + namespace => 'Asset_Collaboration', + }, + ], + fields => [ + { + title => 'newsletter header', + description => 'newsletter header help', + namespace => 'Asset_Newsletter', + }, + { + title => 'newsletter footer', + description => 'newsletter footer help', + namespace => 'Asset_Newsletter', + }, + { + title => 'newsletter template', + description => 'newsletter template help', + namespace => 'Asset_Newsletter', + }, + { + title => 'my subscriptions template', + description => 'my subscriptions template help', + namespace => 'Asset_Newsletter', + }, + { + title => 'newsletter categories', + description => 'newsletter categories help', + namespace => 'Asset_Newsletter', + }, + ], + variables => [ + { + name => "mySubscriptionsUrl", + }, + ], + }, + + 'my subscriptions template' => { + title => 'my subscriptions template', + body => 'my subscriptions template help', + variables => [ + { + name => "formHeader", + }, + { + name => "formFooter", + }, + { + name => "formSubmit", + }, + { + name => "categoriesLoop", + variables => [ + { + name => "categoryName", + }, + { + name => "optionsLoop", + variables => [ + { + name => "optionName", + }, + { + name => "optionForm", + }, + ], + }, + ], + }, + }, + + 'newsletter template' => { + title => 'newsletter template', + body => 'newsletter template help', + variables => [ + { + name => "title", + description => "newsletterTitle", + }, + { + name => "description", + description => "newsletterDescription", + }, + { + name => "header", + description => "newsletter header", + }, + { + name => "footer", + description => "newsletter header", + }, + { + name => "thread_loop", + variables => [ + { + name => "title", + description => "threadTitle", + }, + { + name => "synopsis", + description => "threadSynopsis", + }, + { + name => "body", + description => "threadBody", + }, + { + name => "url", + description => "threadUrl", + }, + ], + }, + }, + +}; + +1; ##All perl modules must return true diff --git a/lib/WebGUI/Workflow/Activity/SendNewsletters.pm b/lib/WebGUI/Workflow/Activity/SendNewsletters.pm new file mode 100644 index 000000000..2858595cc --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/SendNewsletters.pm @@ -0,0 +1,207 @@ +package WebGUI::Workflow::Activity::SendNewsletters; + + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2006 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::Workflow::Activity'; +use WebGUI::Asset; +use WebGUI::Mail::Send; + +=head1 NAME + +Package WebGUI::Workflow::Activity::Skeleton + +=head1 DESCRIPTION + +Tell a little about what this activity does. + +=head1 SYNOPSIS + +See WebGUI::Workflow::Activity for details on how to use any activity. + +=head1 METHODS + +These methods are available from this class: + +=cut + + +#------------------------------------------------------------------- + +=head2 definition ( session, definition ) + +See WebGUI::Workflow::Activity::defintion() for details. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new($session, "Asset_Newsletter"); + push(@{$definition}, { + name=>$i18n->get("send activity name"), + properties=> { } + }); + return $class->SUPER::definition($session,$definition); +} + + +#------------------------------------------------------------------- + +=head2 execute ( [ object ] ) + +See WebGUI::Workflow::Activity::execute() for details. + +=cut + +sub execute { + my $self = shift; + my $object = shift; + my $instance = shift; + my ($db,$eh) = $self->session->quick(qw(db errorHandler)); + + my $time = time(); + my $newsletter = undef; + + $eh->info("Getting subscriptions"); + my $subscriptionResultSet = $db->read("select assetId, userId, subscriptions, lastTimeSent + from Newsletter_subscriptions where lastTimeSent < unix_timestamp() - 60*60*23 + order by assetId, userId"); # only sending to people who haven't been sent to in the past 23 hours + while (my ($assetId, $userId, $subscriptions, $lastTimeSent) = $subscriptionResultSet->array) { + + # get user object + $eh->info("Getting user $userId"); + my $user = WebGUI::User->new($self->session, $userId); + next if ($user->userId eq "1"); + my $emailAddress = $user->profileField("email"); + next if ($emailAddress eq ""); + + + # get newsletter asset + unless (defined $newsletter && $newsletter->getId eq $assetId) { # cache newsletter object + $eh->info("Getting newsletter asset $assetId"); + $newsletter = WebGUI::Asset->new($self->session, $assetId); + } + + # find matching threads + my @threads = (); + $eh->info("Find threads in $assetId matching $userId subscriptions."); + foreach my $subscription (split("\n", $subscriptions)) { + $eh->info("Found subscription $subscription"); + my ($fieldId, $value) = split("~", $subscription); + $eh->info("Searching for threads that match $subscription"); + my $matchingThreads = $db->read("select metaData_values.assetId from metaData_values + left join asset using (assetId) where fieldId=? and value like ? and creationDate > ? + and className like ? and lineage like ?", + [$fieldId, '%'.$value.'%', $lastTimeSent, 'WebGUI::Asset::Post::Thread%', $newsletter->get("lineage").'%']); + while (my ($threadId) = $matchingThreads->array) { + my $thread = WebGUI::Asset->new($self->session, $threadId); + if (defined $thread) { + $eh->info("Found thread $threadId"); + push(@threads, $thread); + } + else { + $eh->error("Couldn't instanciate thread $threadId"); + } + } + } + unless (scalar(@threads)) { # don't send a message if there aren't matching threads + $eh->info("No threads found matching $userId subscriptions."); + next; + } + + # build newsletter + $eh->info("Building newsletter for $userId."); + my $siteurl = $self->session->url->getSiteURL(); + my @threadLoop = (); + foreach my $thread (@threads) { + push(@threadLoop, { + title => $thread->getTitle, + synopsis => $thread->get("synopsis"), + body => $thread->get("body"), + url => $siteurl.$thread->getUrl, + }); + } + my %var = ( + title => $newsletter->getTitle, + description => $newsletter->get("description"), + header => $newsletter->get("newsletterHeader"), + footer => $newsletter->get("newsletterFooter"), + thread_loop => \@threadLoop, + ); + my $template = WebGUI::Asset->new($self->session, $newsletter->get("newsletterTemplateId")); + my $content = $template->process(\%var); + + # send newsletter + $eh->info("Sending newsletter for $userId."); + my $setting = $self->session->setting; + my $returnAddress = $setting->get("mailReturnPath"); + my $companyAddress = $setting->get("companyEmail"); + my $listAddress = $newsletter->get("mailAddress"); + my $from = $listAddress || $companyAddress; + my $replyTo = $listAddress || $returnAddress || $companyAddress; + my $sender = $listAddress || $companyAddress; + my $returnPath = $returnAddress || $sender; + my $listId = $sender; + $listId =~ s/\@/\./; + my $domain = $newsletter->get("mailAddress"); + $domain =~ s/.*\@(.*)/$1/; + my $messageId = "cs-".$self->getId.'@'.$domain; + my $subject = $newsletter->get("mailPrefix").$newsletter->getTitle; + my $mail = WebGUI::Mail::Send->create($self->session, { + to => "<".$emailAddress.">", + from => "<".$from.">", + returnPath => "<".$returnPath.">", + replyTo => "<".$replyTo.">", + subject => $subject, + messageId => '<'.$messageId.'>' + }); + $mail->addHeaderField("List-ID", $newsletter->getTitle." <".$listId.">"); + $mail->addHeaderField("List-Help", ", <".$setting->get("companyURL").">"); + $mail->addHeaderField("List-Unsubscribe", "<".$siteurl.$newsletter->getUrl("func=mySubscriptions").">"); + $mail->addHeaderField("List-Owner", ", <".$setting->get("companyURL")."> (".$setting->get("companyName").")"); + $mail->addHeaderField("Sender", "<".$sender.">"); + if ($listAddress eq "") { + $mail->addHeaderField("List-Post", "No"); + } else { + $mail->addHeaderField("List-Post", ""); + } + $mail->addHeaderField("List-Archive", "<".$siteurl.$newsletter->getUrl.">"); + $mail->addHeaderField("X-Unsubscribe-Web", "<".$siteurl.$newsletter->getUrl("func=mySubscriptions").">"); + $mail->addHeaderField("X-Archives", "<".$siteurl.$newsletter->getUrl.">"); + $mail->addHtml($content); + $mail->queue; + + # mark sent + $eh->info("Email sent."); + $db->write("update Newsletter_subscriptions set lastTimeSent = ?", [time()]); + + # timeout if we're taking too long + if (time() - $time > 50) { + $eh->info("Oops. Ran out of time. Will continue building newsletters in a bit."); + $subscriptionResultSet->finish; + return $self->WAITING; + } + } + return $self->COMPLETE; +} + + + +1; + + diff --git a/lib/WebGUI/i18n/English/Asset.pm b/lib/WebGUI/i18n/English/Asset.pm index 912911cef..47cc44559 100644 --- a/lib/WebGUI/i18n/English/Asset.pm +++ b/lib/WebGUI/i18n/English/Asset.pm @@ -723,6 +723,15 @@ each asset under the tab "Meta" in the asset properties.

lastUpdated => 1031514049, message => q|Possible Values| }, + 'default value' => { + lastUpdated => 0, + message => q|Default Value(s)| + }, + 'default value description' => { + lastUpdated => 0, + message => q|The default value for this field. If there are multiple default values, as in the + case of the check box list, then enter one per line.| + }, 'Depth' => { lastUpdated => 1089039511, context => q|Field label for the Export Page operation|, diff --git a/lib/WebGUI/i18n/English/Asset_Newsletter.pm b/lib/WebGUI/i18n/English/Asset_Newsletter.pm new file mode 100644 index 000000000..79eca13b7 --- /dev/null +++ b/lib/WebGUI/i18n/English/Asset_Newsletter.pm @@ -0,0 +1,208 @@ +package WebGUI::i18n::English::Asset_Newsletter; + +our $I18N = { + + 'newsletterTitle' => { + message => q|Whatever this newsletter is called.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'newsletterDescription' => { + message => q|Whatever is in the description field of this newsletter.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'thread_loop' => { + message => q|A loop containing all the matching threads for this user's personalized newsletter.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'threadTitle' => { + message => q|The title of this thread.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'threadSynopsis' => { + message => q|The short version of this story.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'threadBody' => { + message => q|The full version of this story.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'threadUrl' => { + message => q|The fully qualified URL that points to this thread.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'categoriesLoop' => { + message => q|A loop containing all the categories of data the users may choose from.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'optionsLoop' => { + message => q|A loop containing all the options in a given category.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'categoryName' => { + message => q|The name of this specific category within the loop.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'optionName' => { + message => q|The name of this specific option within this category.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'optionForm' => { + message => q|The checkbox form control for this specific option within this category.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'formSubmit' => { + message => q|The save button for the form.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'formHeader' => { + message => q|The top of the subscription form.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'formFooter' => { + message => q|The bottom of the subscription form.|, + lastUpdated => 0, + context => q|template variable| + }, + + 'newsletter add/edit' => { + message => q|Newsletter, Add/Edit|, + lastUpdated => 0, + context => q|help title| + }, + + 'newsletter add/edit desc' => { + message => q|The Newsletter asset is used to create news stories and then send subscribed users an email + based upon their chosen interests. This asset requires content profiling to be enabled in order to + function.|, + lastUpdated => 0, + context => q|help description| + }, + + 'mySubscriptionsUrl' => { + message => q|The URL for a user to click on to manage their subscriptions.|, + lastUpdated => 0, + context => q|newsletter template variable| + }, + + 'content profiling needed' => { + message => q|WARNING: You need to enable content profiling for this asset to work.|, + lastUpdated => 0, + context => q|title for edit screen| + }, + + 'edit title' => { + message => q|Edit Newsletter|, + lastUpdated => 0, + context => q|title for edit screen| + }, + + 'newsletter categories' => { + message => q|Newsletter Categories|, + lastUpdated => 0, + context => q|asset property| + }, + + 'newsletter categories help' => { + message => q|Choose the metadata fields you wish to use as categories. Only select box, check list, and + radio list categories may be used.|, + lastUpdated => 0, + context => q|help for asset property| + }, + + 'newsletter template' => { + message => q|Newsletter Template|, + lastUpdated => 0, + context => q|asset property| + }, + + 'newsletter template help' => { + message => q|Which template would you like to use for the newsletter when it is sent out to users?|, + lastUpdated => 0, + context => q|help for asset property| + }, + + 'my subscriptions' => { + message => q|My Subscriptions|, + lastUpdated => 0, + context => q|label for user to click on to manage their subscriptions| + }, + + 'my subscriptions template' => { + message => q|My Subscriptions Template|, + lastUpdated => 0, + context => q|asset property| + }, + + 'my subscriptions template help' => { + message => q|Which template would you like to use for users selecting which cateogries they will subscribe + to?|, + lastUpdated => 0, + context => q|help for asset property| + }, + + 'newsletter header' => { + message => q|Newsletter Header|, + lastUpdated => 0, + context => q|asset property| + }, + + 'newsletteer header help' => { + message => q|A message the will be placed at the top of the newsletter; like a greeting.|, + lastUpdated => 0, + context => q|help for asset property| + }, + + 'newsletter footer' => { + message => q|Newsletter Footer|, + lastUpdated => 0, + context => q|asset property| + }, + + 'newsletteer footer help' => { + message => q|A message the will be placed at the bottom of the newsletter; like a salutation.|, + lastUpdated => 0, + context => q|help for asset property| + }, + + 'send activity name' => { + message => q|Send Newsletters|, + lastUpdated => 0, + context => q|the name of the workflow activity that sends out the newsletters| + }, + + 'assetName' => { + message => q|Newsletter|, + lastUpdated => 1131394072, + }, + +}; + +1;