From 0697673846eda308560f1b969492816ebae9552d Mon Sep 17 00:00:00 2001 From: Doug Bell Date: Wed, 9 Sep 2009 17:24:00 -0500 Subject: [PATCH] added Subscribable AssetAspect to Wiki --- docs/changelog/7.x.x.txt | 1 + .../default-wiki-front-page.wgpkg | Bin 0 -> 1422 bytes .../packages-7.8.0/default-wiki-page.wgpkg | Bin 0 -> 2394 bytes ..._untitled_default-asset-subscription.wgpkg | Bin 0 -> 879 bytes docs/upgrades/upgrade_7.7.19-7.8.0.pl | 21 + lib/WebGUI/Asset/WikiPage.pm | 111 ++-- lib/WebGUI/Asset/Wobject/WikiMaster.pm | 73 ++- lib/WebGUI/AssetAspect/Subscribable.pm | 514 ++++++++++++++++++ lib/WebGUI/Help/Asset_WikiMaster.pm | 12 + lib/WebGUI/Help/Asset_WikiPage.pm | 27 + .../i18n/English/AssetAspect_Subscribable.pm | 14 + lib/WebGUI/i18n/English/Asset_WikiMaster.pm | 29 + lib/WebGUI/i18n/English/Asset_WikiPage.pm | 35 ++ t/Asset/WikiPage/subscribable.t | 57 ++ t/Asset/Wobject/WikiMaster/subscribable.t | 98 ++++ 15 files changed, 936 insertions(+), 56 deletions(-) create mode 100644 docs/upgrades/packages-7.8.0/default-wiki-front-page.wgpkg create mode 100644 docs/upgrades/packages-7.8.0/default-wiki-page.wgpkg create mode 100644 docs/upgrades/packages-7.8.0/home_untitled_default-asset-subscription.wgpkg create mode 100644 lib/WebGUI/AssetAspect/Subscribable.pm create mode 100644 lib/WebGUI/i18n/English/AssetAspect_Subscribable.pm create mode 100644 t/Asset/WikiPage/subscribable.t create mode 100644 t/Asset/Wobject/WikiMaster/subscribable.t diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index ecf1432e6..972c0d1a8 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -25,6 +25,7 @@ - fixed #10925: Wrong message in i18n - relabel Help in the Admin Console to Template Help - fixed #10928: EMS Print Ticket -- Time not processed for timezone + - added Subscribable AssetAspect to Wiki 7.7.19 - fixed #10838: Forwarded forum post email to new CS adds reply to original thread diff --git a/docs/upgrades/packages-7.8.0/default-wiki-front-page.wgpkg b/docs/upgrades/packages-7.8.0/default-wiki-front-page.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..b868f20c4a277a5b358a991203a62f7c167ce948 GIT binary patch literal 1422 zcmV;91#$WxiwFP!00000|Ls_9Z`(Ey_UHZz!N7nPXkz(&b2I~aU4kZEvO3F#c{6B< zwiwHzKvHqNq5pkHN){f1G|3H#eM3sj!}iMc=wLR;bKJSMyS!M53Kw^$wyp5$=y`+xGN zsGIt;+f%<=`}drNPjm*b*y}*O;k&H<{eHVssDG!`?$<2)iTqcZd9M2JS9*Oh!oS|Y zC9Dg)i~=O^1POs$e~ClE>(;>f?f3Ue&IM&?j?JZr(Fij^u-d51NJ}omV8=UJ%f0n> zUFl0ffVepQ98m_+L!}eOL6C{zdI@E}9>vpua36bGH^i$u5_)u{jM`ej*CCBK**1qi z3x>`y@-Ezz>l*|k^r@p0#IL9JYx+4p6#%L%;kU@2I;G?M{ zj9H+9_V5hFf!JHY{=J!0*o$OFvjh-+PCO5X#Um1a&8VPa9VO{8PCsWfjxM}Yj|ipJ zY-w|wiYkF4Ah-#cSsZgtXt@7?*go=>B>XWPI?*tZ!=mLvV2lQQ=rmxtk?IY{Y#cfs zd9b)x2l)GOeHKt81{Nc8UySRCeB*@hPw|wyj7+hoBxB(q%V&57irQ5i^3Aqe77SA> zWAVpDC@>3=fIw03Hrw?6P^Nt%6MhA~gxR|T85S6CAR_=Aa9rUvGgsx)2<@M}=9AQb zEE&;2&OO56ZmmZ|1bFE9EhF0^S!>9}oEepGl}}j|56f&uG9ig+zik-d7%|tsL{l7q z6%gf>6Y&{kOL-kI+tR#=FAuNHH^)Z$=*n^oxls8FD2=R~iV9b5l8+OR z+}~x_3*ix+>&X6mLnnxTaAYAhuOHL$<4MG*>u7 zAy)xewnt{a;{4!Kp1&doZUC2TU`?s;Wdl1|rj(An^13z)yk1e}9S1b_3OnU)J)%kI zV@3pI2>@hyjTkh2ar!G*Om0FMrjih+uv}NzQ$pmlKNldFe?-d!NAB?Shue$6Kuy)b zV3aZdy`2^Ta6Y=a+)g9mmsHY^gc0Qk2;phcil;KGu4GMkG5XeScl!qVGyFg# zn4fHjQ(1k0JplRH^oYSrxKCFXp&aK)It)?Fal)83{HYjT2tC9jhyVOOIX%rk(bBxqQMhcRNlO6D} zoI+10lS?T4%~-}3kI+ib#vvTVAIfT4#v7iVFE~{^L4iye7bq=v1@N1#qLZ2sgPo|Z zQCuO{7GkFzQGwLE(q(|^BM{ozjm-ZRjq_)c_7$$?+MyJA<&J8^)k6q}E?Y-JVUc_# zH72M)^YkrlviR`R>EupC_~^rT^UIS%FSz;jMjL^FvW8cVWpNU!8N8Y8<_jbsntInQ z!!-4ZoEzNDUboY;n|%XV0dRm0q~YCk;$J4m-EJSTQPew`+^>$B_eOZx#7^Ur@9baR ce0eedWOxnaH3P31c##?S8#!~ig#Z`;02z+B5&!@I literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.8.0/default-wiki-page.wgpkg b/docs/upgrades/packages-7.8.0/default-wiki-page.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..e449eced77568c01afbebb74032420892f3d12cb GIT binary patch literal 2394 zcmV-g38nTQiwFP!00000|Lt0BQyV!F=JWmv9s2=8VUG>i>>0;Z*+7=m{Np8ON97zhAdB8qEx2paN{=t}>}&Y;~)<)!piTB&W6Z_)ojt-tTtT*6`iy znRk07&o7kU)KE<|Okt&dIR7w2{A_&POpqIY824fl~2BA_g zGL>u$BflS^DAicbc`%yuI0`f!U2?Vvs8UQT_q7GHHVA6*2zF5U4!a-MG8_a}(s1y#ib9bjEYw^if&bW?@#NuD zu$W=O2HCqgVwygz1{d+{y+SyqQz;TAkA^=k_ytd4_p~5|nhCvxtRe0*i8-NPMnXnR z_B-ikqD0Ii@*|YWgR9ju!Ic`8oK0+Mkya;LkA?#B$4ff7N zig^-2?!`j(M=^{X*w2e8*X*FAs=45ip7z@ZDUBkYObQm%F9?nI?kK4?UU34xMxt}m zjLHd5jGLO^J2|Kug z&5S8J^yO155+>g(CSTa20xMXs> z?2L)2_L(!#Vti}|Ahrd#<B>|tbwKY57X{Y}G30K{ zMF!hpDI@ci=;ajJ+Sm8SJiUA#ujr9AoY#6K^5i=zFCpIcEpL6PftKdEW{QKpp_(H> z)=WPD?0XAn-~?Jfz&ZPLJTHI*(^n%lrt??>aJt}4#EG7Tt79HTEU6xQxOyvv7I{w5g?(xIippec${#0pLW zFdSs_hPd)U#4m|K6>!QJ1ESG=B5`}d*}>4A60Y9ON7!jcEHac+J)rK0d{!{zj}Vz}a0Whud0b_x za2PJ=iB6OQ`M~Bwk#C2r)F6?~3P$6}_a*jAFdPZs=Rpv2vjSsC_9suTkS3Uncqp#Z z!+>^82@cjNDJP#(HwfmCDw+4pDlseFGSOpxF-5kVwdCy8ypF%%jf@#w;6m(-a&Op4 zmE^r(%LViqWwU7Wpiiu?VgUysM|=ri3hpZG5W6k%0bEcv$pnLBK0;|Qkd_>i8&!0W z%d$|+%)Jjr!)#l-_wC?{71poYPKVNx#~$^SyzIiBE5dD8XL}88@A9mhh|e- zB57=7Iq`S6d|Ga7uhi(Kz%d!5H|kV2N@&vQ!nZ-JeX`*LHRX6mx3s%vW4oH?ZgNC5 zeJ8`ZsaX70PYmH&kR8J8KGAZ{s`9Tky+f|DA`F>%wb`&=@{1&#Sn3)XsOXcQ;9y>= zMSgLLQ5jCN>RcUmg@O*re~7PSIU8!638mkPbRJWAO-g}wn<%(A#IyLbDNQC!RVAUK z62q0~`JF{zboU+Hb#ql3gErcD(I18g!Hi>3E8Q8@Ja&gZj{5hN@?v(9KWoq8(j0 zH`mMSG=(eg%i^`PL3{7`zwH`&-Tkh&Af*!SRgB&h&g%D%PhP#e?mDhs3cRnjSLmfn za;`E*Ro7FDw4rt2m$aYSyWRc5Ww~UR9FZREVry|-OaNK}$aZ;Abm>$qUL?%?Fc(7ZditOt~U;&Np5umCcTNe&tw z6fhc)XjWej46i%J13{#fFp;@@cQECV0iJ_|pbm4%20BM~r4>1Fau+0*<%v6=B-ZNTS3Dkvo8q7=#x4`gk7Kn!e|ZTDoCPmqP+oL#dwhE zuSQW0EnVlpt-7)8K@nmiz(7~<4-ikV_36`aE672QiIO`eKRVzc1w@mT6uY_5uLYV1 z7xlpDX0%hL-YUx>nrj9*f|p>E$hT6KgmRf`(=rTL@gz+r*=@`CZ+{%WR=t1iUK}01 z{OynPz2~F99!@+bRQe@c5X`Er$uvdRY>U5F1Gq51R@cD`^CNeulXiODy&cs zGV^}joBww9tlQnE@+9pYo}Dj#?wtSX&C6S8W*`3Qe)YrEH}l^Fz8mu01K&OHf9Qe# M0u~6(U;rop00fYq@c;k- literal 0 HcmV?d00001 diff --git a/docs/upgrades/packages-7.8.0/home_untitled_default-asset-subscription.wgpkg b/docs/upgrades/packages-7.8.0/home_untitled_default-asset-subscription.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..555bd2860b0a4e85ac6f71975c73231462f90810 GIT binary patch literal 879 zcmV-#1Cab5iwFP!00000|Ls+6PunmM?q_~Qluw()OPVyL>n0@LTk#?gZxdrfZsMjM zsqNVgrA++y*_RZXmP*qE5|gmhMD_X3&pprGWx4kHD+q$sIIh*qyV-lGCe9&bY z{wW!KQS>@)v!V6Y@8|T7qG)wW|0rIKYF_Z7DCPuvE&Z40-1o+|e=q1J^c5cR94UN_ zltRg1h^dmk*Y>`BUj(~UOrQ+ATU;=Xg(4UZLKqV%5m?7bH^%0yANk;K3^|f&`;IfA zt?S@Ji#&fc>-JI*-{(ayCvt#O>r3(Ig3y$WAZTC>a!eVQWHubWQv!7`N>5QHk5F=> z!)ZWIu4E&M#hJvS3pN+*6ZaWMBqdgrQ%;5lH#e=If3Okte;r<)_v_7YWDyo32hamH z#480=MCCYLNpT+)xmtoqOS0(6L=dhBqt=ti4oOo?!M*P89|@+I8uOAbhmK6Jf_KyH zlqm3W=$|MI`(Kon`}!6M4>(x!esrnAf+B+elD4YRc)aVHi3K=oKGv z-M_bPk7B0UimqhL}(|hGOc+!1DR?JQ3<%$kA z;J+S6te3x1h9w)0sgSrF7n9*J4Ug(Vk{l%^ zPoR3)AS-TzSfrE2vqQ+YJ@e1)XG4^2Xa7I8n}vXBBF0=DO_*@&7-iKU>t=UfukYin zB91rrdfD+lYx3~e1|jlfEOj!|e1H-Yh20sdD;viBbF~d$i5dCoaoBRC&}FA*LItnc zl6#}Atp6$5?ZmC6!^`BXk!Kd(qiz*G^Vxb>cz9!9+jltc47@Y&&cNGY;17QB7#9E# F006~$tEm70 literal 0 HcmV?d00001 diff --git a/docs/upgrades/upgrade_7.7.19-7.8.0.pl b/docs/upgrades/upgrade_7.7.19-7.8.0.pl index 37d6204b4..c7c757ade 100644 --- a/docs/upgrades/upgrade_7.7.19-7.8.0.pl +++ b/docs/upgrades/upgrade_7.7.19-7.8.0.pl @@ -33,6 +33,7 @@ my $session = start(); # this line required # upgrade functions go here reorganizeAdSpaceProperties($session); fixTemplateSettingsFromShunt($session); +addSubscribableAspect( $session ); finish($session); # this line required @@ -46,6 +47,26 @@ finish($session); # this line required # print "DONE!\n" unless $quiet; #} +#---------------------------------------------------------------------------- +# Add tables for the subscribable aspect +sub addSubscribableAspect { + my $session = shift; + print "\tAdding Subscribable aspect..." unless $quiet; + + $session->db->write( <<'ESQL' ); +CREATE TABLE assetAspect_Subscribable ( + assetId CHAR(22) BINARY NOT NULL, + revisionDate BIGINT NOT NULL, + subscriptionGroupId CHAR(22) BINARY, + subscriptionTemplateId CHAR(22) BINARY, + skipNotification INT, + PRIMARY KEY ( assetId, revisionDate ) +) +ESQL + + print "DONE!\n" unless $quiet; +} + #---------------------------------------------------------------------------- # Describe what our function does sub reorganizeAdSpaceProperties { diff --git a/lib/WebGUI/Asset/WikiPage.pm b/lib/WebGUI/Asset/WikiPage.pm index e5a423229..fce1f881b 100644 --- a/lib/WebGUI/Asset/WikiPage.pm +++ b/lib/WebGUI/Asset/WikiPage.pm @@ -12,7 +12,11 @@ package WebGUI::Asset::WikiPage; use strict; use Class::C3; -use base qw(WebGUI::AssetAspect::Comments WebGUI::Asset); +use base qw( + WebGUI::AssetAspect::Subscribable + WebGUI::AssetAspect::Comments + WebGUI::Asset +); use Tie::IxHash; use WebGUI::International; use WebGUI::Utility; @@ -236,6 +240,73 @@ sub getEditForm { #------------------------------------------------------------------- +=head2 getSubscriptionTemplate ( ) + +=cut + +sub getSubscriptionTemplate { + my ( $self ) = @_; + return $self->getParent->getSubscriptionTemplate; +} + +#------------------------------------------------------------------- + +=head2 getTemplateVars ( ) + +Get the common template vars for this asset + +=cut + +sub getTemplateVars { + my ( $self ) = @_; + my $i18n = WebGUI::International->new($self->session, "Asset_WikiPage"); + my $wiki = $self->getWiki; + my $owner = WebGUI::User->new( $self->session, $self->get('ownerUserId') ); + my $keywords = WebGUI::Keyword->new($self->session)->getKeywordsForAsset({ + asset => $self, + asArrayRef => 1, + }); + my @keywordsLoop = (); + foreach my $word (@{$keywords}) { + push @keywordsLoop, { + keyword => $word, + url => $wiki->getUrl("func=byKeyword;keyword=".$word), + }; + } + my $var = { + %{ $self->get }, + url => $self->getUrl, + keywordsLoop => \@keywordsLoop, + viewLabel => $i18n->get("viewLabel"), + editLabel => $i18n->get("editLabel"), + historyLabel => $i18n->get("historyLabel"), + wikiHomeLabel => $i18n->get("wikiHomeLabel", "Asset_WikiMaster"), + searchLabel => $i18n->get("searchLabel", "Asset_WikiMaster"), + searchUrl => $wiki->getUrl("func=search"), + recentChangesUrl => $wiki->getUrl("func=recentChanges"), + recentChangesLabel => $i18n->get("recentChangesLabel", "Asset_WikiMaster"), + mostPopularUrl => $wiki->getUrl("func=mostPopular"), + mostPopularLabel => $i18n->get("mostPopularLabel", "Asset_WikiMaster"), + wikiHomeUrl => $wiki->getUrl, + historyUrl => $self->getUrl("func=getHistory"), + editContent => $self->getEditForm, + allowsAttachments => $wiki->get("allowAttachments"), + comments => $self->getFormattedComments(), + canEdit => $self->canEdit, + content => $wiki->autolinkHtml( + $self->scrubContent, + {skipTitles => [$self->get('title')]}, + ), + isSubscribed => $self->isSubscribed, + subscribeUrl => $self->getSubscribeUrl, + unsubscribeUrl => $self->getUnsubscribeUrl, + owner => $owner->get('alias'), + }; + return $var; +} + +#------------------------------------------------------------------- + =head2 getWiki Returns an object referring to the wiki that contains this page. If it is not a WikiMaster, @@ -438,43 +509,7 @@ Renders this asset. sub view { my $self = shift; - my $i18n = WebGUI::International->new($self->session, "Asset_WikiPage"); - my $keywords = WebGUI::Keyword->new($self->session)->getKeywordsForAsset({ - asset=>$self, - asArrayRef=>1, - }); - my $wiki = $self->getWiki; - my @keywordsLoop = (); - foreach my $word (@{$keywords}) { - push(@keywordsLoop, { - keyword=>$word, - url=>$wiki->getUrl("func=byKeyword;keyword=".$word), - }); - } - my $var = { - keywordsLoop => \@keywordsLoop, - viewLabel => $i18n->get("viewLabel"), - editLabel => $i18n->get("editLabel"), - historyLabel => $i18n->get("historyLabel"), - wikiHomeLabel => $i18n->get("wikiHomeLabel", "Asset_WikiMaster"), - searchLabel => $i18n->get("searchLabel", "Asset_WikiMaster"), - searchUrl => $self->getParent->getUrl("func=search"), - recentChangesUrl => $self->getParent->getUrl("func=recentChanges"), - recentChangesLabel => $i18n->get("recentChangesLabel", "Asset_WikiMaster"), - mostPopularUrl => $self->getParent->getUrl("func=mostPopular"), - mostPopularLabel => $i18n->get("mostPopularLabel", "Asset_WikiMaster"), - wikiHomeUrl => $self->getParent->getUrl, - historyUrl => $self->getUrl("func=getHistory"), - editContent => $self->getEditForm, - allowsAttachments => $self->getWiki->get("allowAttachments"), - comments => $self->getFormattedComments(), - canEdit => $self->canEdit, - content => $self->getWiki->autolinkHtml( - $self->scrubContent, - {skipTitles => [$self->get('title')]}, - ), - }; - return $self->processTemplate($var, $self->getWiki->get("pageTemplateId")); + return $self->processTemplate($self->getTemplateVars, $self->getWiki->get("pageTemplateId")); } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Wobject/WikiMaster.pm b/lib/WebGUI/Asset/Wobject/WikiMaster.pm index 6fb0b6385..fa4e9a409 100644 --- a/lib/WebGUI/Asset/Wobject/WikiMaster.pm +++ b/lib/WebGUI/Asset/Wobject/WikiMaster.pm @@ -11,7 +11,11 @@ package WebGUI::Asset::Wobject::WikiMaster; #------------------------------------------------------------------- use Class::C3; -use base qw(WebGUI::AssetAspect::RssFeed WebGUI::Asset::Wobject); +use base qw( + WebGUI::AssetAspect::Subscribable + WebGUI::AssetAspect::RssFeed + WebGUI::Asset::Wobject +); use strict; use Tie::IxHash; use WebGUI::International; @@ -444,6 +448,36 @@ sub getRssFeedItems { return $var; } +#---------------------------------------------------------------------------- + +=head2 getTemplateVars ( ) + +Get the common template variables for all views of the wiki. + +=cut + +sub getTemplateVars { + my ( $self ) = @_; + my $i18n = WebGUI::International->new($self->session, "Asset_WikiMaster"); + my $var = { %{$self->get}, + url => $self->getUrl, + searchLabel => $i18n->get("searchLabel"), + mostPopularUrl => $self->getUrl("func=mostPopular"), + mostPopularLabel => $i18n->get("mostPopularLabel"), + addPageLabel => $i18n->get("addPageLabel"), + addPageUrl => $self->getUrl("func=add;class=WebGUI::Asset::WikiPage"), + recentChangesUrl => $self->getUrl("func=recentChanges"), + recentChangesLabel => $i18n->get("recentChangesLabel"), + restoreLabel => $i18n->get("restoreLabel"), + canAdminister => $self->canAdminister, + isSubscribed => $self->isSubscribed, + subscribeUrl => $self->getSubscribeUrl, + unsubscribeUrl => $self->getUnsubscribeUrl, + }; + + return $var; +} + #------------------------------------------------------------------- =head2 prepareView @@ -492,6 +526,19 @@ sub processPropertiesFromFormPost { #------------------------------------------------------------------- +=head2 shouldSkipNotification ( ) + +WikiMasters do not send notification + +=cut + +sub shouldSkipNotification { + my ( $self ) = @_; + return 1; +} + +#------------------------------------------------------------------- + =head2 view Render the front page of the wiki. @@ -500,23 +547,13 @@ Render the front page of the wiki. sub view { my $self = shift; - my $i18n = WebGUI::International->new($self->session, "Asset_WikiMaster"); - my $var = { - description => $self->autolinkHtml($self->get('description')), - searchLabel=>$i18n->get("searchLabel"), - mostPopularUrl=>$self->getUrl("func=mostPopular"), - mostPopularLabel=>$i18n->get("mostPopularLabel"), - addPageLabel=>$i18n->get("addPageLabel"), - addPageUrl=>$self->getUrl("func=add;class=WebGUI::Asset::WikiPage"), - recentChangesUrl=>$self->getUrl("func=recentChanges"), - recentChangesLabel=>$i18n->get("recentChangesLabel"), - restoreLabel => $i18n->get("restoreLabel"), - canAdminister => $self->canAdminister, - keywordCloud => WebGUI::Keyword->new($self->session)->generateCloud({ - startAsset=>$self, - displayFunc=>"byKeyword", - }), - }; + my $var = $self->getTemplateVars; + $var->{ description } = $self->autolinkHtml( $var->{ description } ); + $var->{ keywordCloud } + = WebGUI::Keyword->new($self->session)->generateCloud({ + startAsset=>$self, + displayFunc=>"byKeyword", + }); my $template = $self->{_frontPageTemplate}; $self->appendSearchBoxVars($var); $self->appendRecentChanges($var, $self->get('recentChangesCountFront')); diff --git a/lib/WebGUI/AssetAspect/Subscribable.pm b/lib/WebGUI/AssetAspect/Subscribable.pm new file mode 100644 index 000000000..6e2026762 --- /dev/null +++ b/lib/WebGUI/AssetAspect/Subscribable.pm @@ -0,0 +1,514 @@ +package WebGUI::AssetAspect::Subscribable; + +use strict; +use Class::C3; + +=head1 NAME + +WebGUI::AssetAspect::Subscribable - Let users subscribe to your asset + +=head1 SYNOPSIS + + +=head1 DESCRIPTION + + +=head1 METHODS + +#---------------------------------------------------------------------------- + +=head2 definition ( session [, definition ] ) + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = __PACKAGE__->i18n($session); + + tie my %properties, 'Tie::IxHash', ( + subscriptionGroupId => { + tab => "security", + fieldType => "subscriptionGroup", + label => $i18n->echo("Subscription Group"), + hoverHelp => $i18n->echo("The users who are subscribed to recieve e-mail alerts for this asset"), + defaultValue => undef, + noFormPost => 1, + }, + subscriptionTemplateId => { + tab => "display", + fieldType => "template", + namespace => $class->getSubscriptionTemplateNamespace, + label => $i18n->echo("Subscription Template"), + hoverHelp => $i18n->echo("The template to use to send out e-mail notifications"), + }, + skipNotification => { + autoGenerate => 0, + noFormPost => 1, + fieldType => 'yesNo', + }, + ); + + push @{ $definition }, { + autoGenerateForms => 1, + tableName => "assetAspect_Subscribable", + properties => \%properties, + }; + + return $class->maybe::next::method( $session, $definition ); +} + +#---------------------------------------------------------------------------- + +=head2 addRevision ( properties [, revisionDate, options ] ) + +Override addRevision to set skipNotification to 0 for each new revision. + +=cut + +sub addRevision { + my $self = shift; + my $properties = shift; + + $properties->{ skipNotification } = 0; + + return $self->maybe::next::method( $properties, @_ ); +} + +#---------------------------------------------------------------------------- + +=head2 canSubscribe ( [userId ] ) + +Returns true if the user is allowed to subscribe to this asset. C is +a userId to check, defaults to the current user. + +By default, Visitors are not allowed to subscribe. Anyone else who canView, +canSubscribe. + +=cut + +sub canSubscribe { + my $self = shift; + my $userId = shift || $self->session->user->userId; + + return 0 if $userId eq "1"; + return $self->canView( $userId ); +} + +#---------------------------------------------------------------------------- + +=head2 commit ( ) + +By default, send the notification out when the asset is committed. Override +this if you don't want this asset to send out notifications (but you still +want to be able to subscribe to children) + +=cut + +sub commit { + my ( $self, @args ) = @_; + $self->maybe::next::method( @args ); + if ( !$self->shouldSkipNotification ) { + $self->notifySubscribers; + } + return; +} + +#---------------------------------------------------------------------------- + +=head2 createSubscriptionGroup ( ) + +Create a group to hold subscribers to this asset, if there is not one already. + +=cut + +sub createSubscriptionGroup { + my $self = shift; + + if ( my $groupId = $self->get('subscriptionGroupId') ) { + return WebGUI::Group->new( $self->session, $groupId ); + } + else { + my $group = WebGUI::Group->new($self->session, "new"); + $group->name( "Subscription " . $self->getTitle ); + $group->description( "Subscription Group for " . $self->getTitle . "(" . $self->getId . ")" ); + $group->isEditable( 0 ); + $group->showInForms( 0 ); + $group->deleteGroups( [ "3" ] ); # admins don't want to be auto subscribed to this thing + $self->update({ + subscriptionGroupId => $group->getId + }); + + return $group; + } +} + +#---------------------------------------------------------------------------- + +=head2 DOES ( role ) + +Returns true if the asset does the specified role. This mixin does the +"Subscribable" role. + +=cut + +sub DOES { + my $self = shift; + my $role = shift; + + return 1 if ( lc $role eq "subscribable" ); + return $self->maybe::next::method( $role ); +} + +#---------------------------------------------------------------------------- + +=head2 getSubscriptionContent ( ) + +Get the content to send to subscribers. By default, will process the template +from C with the variables from C or +C. + +=cut + +sub getSubscriptionContent { + my $self = shift; + my $template = $self->getSubscriptionTemplate; + my $var; + if ( $self->can("getTemplateVars") ) { + # Rely on getTemplateVars sub judgement + $var = $self->getTemplateVars; + } + else { + # Try to make sense of the asset properties + $var = { + %{ $self->get }, + url => $self->session->url->getSiteURL . $self->getUrl, + } + } + + return $template->process( $var ); +} + +#---------------------------------------------------------------------------- + +=head2 getSubscriptionGroup ( ) + +Gets the WebGUI::Group for the subscribers group. + +=cut + +sub getSubscriptionGroup { + my $self = shift; + my $groupId = $self->get( "subscriptionGroupId" ); + my $group = $groupId ? WebGUI::Group->new( $self->session, $groupId ) : $self->createSubscriptionGroup; + return $group; +} + +#---------------------------------------------------------------------------- + +=head2 getSubscriptionTemplate ( ) + +Get a WebGUI::Asset::Template object for the subscription template. + +=cut + +sub getSubscriptionTemplate { + my $self = shift; + my $templateId = $self->get( "subscriptionTemplateId" ); + my $template = WebGUI::Asset::Template->new( $self->session, $templateId ); # This should throw if we don't + return $template; +} + +#---------------------------------------------------------------------------- + +=head2 getSubscriptionTemplateNamespace ( ) + +Get the namespace for the subscription template. + +=cut + +sub getSubscriptionTemplateNamespace { + return "AssetAspect/Subscribable"; +} + +#---------------------------------------------------------------------------- + +=head2 getSubscribeUrl ( ) + +Get the URL to subscribe to this asset. + +=cut + +sub getSubscribeUrl { + my $self = shift; + return $self->getUrl( 'func=subscribe' ); +} + +#---------------------------------------------------------------------------- + +=head2 getUnsubscribeUrl ( ) + +Get the URL to unsubscribe from this asset. + +=cut + +sub getUnsubscribeUrl { + my $self = shift; + return $self->getUrl( 'func=unsubscribe' ); +} + +#---------------------------------------------------------------------------- + +=head2 i18n ( ) + +Get the i18n for RSSCapable + +=cut + +sub i18n { + my $class = shift; + my $session = shift; + + return WebGUI::International->new( $session ); +} + +#---------------------------------------------------------------------------- + +=head2 isSubscribed ( [userId] ) + +Returns true if the user is subscribed to the asset. C is a userId to +check, defaults to the current user. + +=cut + +sub isSubscribed { + my $self = shift; + my $userId = shift; + my $user = $userId + ? WebGUI::User->new( $self->session, $userId ) + : $self->session->user + ; + my $group = $self->getSubscriptionGroup; + # TODO: Make WebGUI::Group throw error if group not found + if ( !$group ) { + return 0; + } + else { + return $user->isInGroup( $group->getId ); + } +} + +#---------------------------------------------------------------------------- + +=head2 _makeMessageId ( string ) + +Make the message ID following proper RFC2822. C is a unique identifier +for the message. + +=cut + +sub _makeMessageId { + my $self = shift; + my $string = shift; + my $domain = $self->session->config->get( "sitename" )->[ 0 ]; + return "wg-" . $string . "@" . $domain; +} + +#---------------------------------------------------------------------------- + +=head2 notifySubscribers ( [options] ) + +Notify all the subscribers of this asset. C is a hash reference of +options with the following keys: + + content -> Content to send to the subscribers. Defaults to getSubscriptionContent + subject -> E-mail subject. Defaults to the asset title. + from -> E-mail address this message is from. Defaults to the e-mail address of + the owner of this asset, or the Company E-Mail from settings + replyTo -> E-mail address to reply to. Defaults to the listAddress, the Mail + Return Path from settings, or the Company E-Mail from settings + inReplyTo -> Asset ID of the asset this subscription message is replying to + listAddress -> The address of the mailing list this is being sent from, if necessary + +=cut + +sub notifySubscribers { + my $self = shift; + my $opt = shift; + my $session = $self->session; + my $setting = $self->session->setting; + my $companyEmail = $setting->get( "companyEmail" ); + my $mailReturnPath = $setting->get( "mailReturnPath" ); + + $opt->{ subject } ||= $self->getTitle; + $opt->{ content } ||= $self->getSubscriptionContent; + WebGUI::Macro::process( $self->session, \$opt->{content} ); + + if ( !$opt->{ from } ) { + my $owner = WebGUI::User->new( $self->session, $self->get( "ownerUserId" ) ); + $opt->{ from } = $owner->profileField( "email" ) || $opt->{ listAddress } || $companyEmail; + } + + if ( !$opt->{ replyTo } ) { + $opt->{ replyTo } = $opt->{listAddress} || $mailReturnPath || $companyEmail; + } + + $opt->{ returnPath } = $mailReturnPath || $opt->{listAddress} || $companyEmail || $opt->{ from }; + + my $messageId = $self->_makeMessageId( $self->getId ); + + ### Get all the people we need to send to + # Any parent asset that does subscribable + # First asset in this list is the topmost parent, and is the list ID + my @assets = ( $self ); + my $parentAsset = $self->getParent; + while ( $parentAsset ) { + last if !$parentAsset->DOES( "subscribable" ); + unshift @assets, $parentAsset; + $parentAsset = $parentAsset->getParent; + } + + ### Prepare the actual sender address (the address of the process sending, + # not the address of the user who initiated the sending) + my $sender = $opt->{listAddress} || $companyEmail || $opt->{from}; + my $siteurl = $session->url->getSiteURL; + # XXX This doesnt seem right... + my $listId = $sender; + $listId =~ s/\@/\./; + + for my $asset ( @assets ) { + my $group = $asset->getSubscriptionGroup; + my $mail + = WebGUI::Mail::Send->create( $self->session, { + from => '<' . $opt->{ from } . '>', + returnPath => '<' . $opt->{ returnPath } . '>', + replyTo => '<' . $opt->{ replyTo } . '>', + toGroup => $group->getId, + subject => $opt->{ subject }, + messageId => '<' . $messageId . '>', + } ); + + # Add threading headers + if ( $opt->{ inReplyTo } ) { + $mail->addHeaderField( "In-Reply-To", '<' . $opt->{inReplyTo} . '>' ); + $mail->addHeaderField( "References", '<' . $opt->{inReplyTo} . '>' ); + } + + $mail->addHeaderField("List-ID", $assets[0]->getTitle." <".$listId.">"); + $mail->addHeaderField("List-Help", ", <".$setting->get("companyURL").">"); + $mail->addHeaderField("List-Owner", ", <".$setting->get("companyURL")."> (".$setting->get("companyName").")"); + $mail->addHeaderField("Sender", "<".$sender.">"); + $mail->addHeaderField("List-Unsubscribe", "<".$siteurl.$asset->getUnsubscribeUrl.">"); + $mail->addHeaderField("X-Unsubscribe-Web", "<".$siteurl.$asset->getUnsubscribeUrl.">"); + $mail->addHeaderField("List-Subscribe", "<".$siteurl.$asset->getSubscribeUrl.">"); + $mail->addHeaderField("X-Subscribe-Web", "<".$siteurl.$asset->getSubscribeUrl.">"); + $mail->addHeaderField("List-Archive", "<".$siteurl.$assets[0]->getUrl.">"); + $mail->addHeaderField("X-Archives", "<".$siteurl.$assets[0]->getUrl.">"); + if ( $opt->{listAddress} ) { + $mail->addHeaderField("List-Post", "{listAddress}.">"); + } + else { + $mail->addHeaderField("List-Post", "No"); + } + $mail->addHtml($opt->{content}); + $mail->addFooter; + $mail->queue; + } +} + +#---------------------------------------------------------------------------- + +=head2 setSkipNotification ( ) + +Set a flag so that this asset does not send out notifications for this +revision. + +=cut + +sub setSkipNotification { + my $self = shift; + my $value = shift; + $value = defined $value ? $value : 1; + + $self->update( { + skipNotification => $value, + } ); + + return; +} + +#---------------------------------------------------------------------------- + +=head2 shouldSkipNotification ( ) + +Returns true if the asset should skip notifications. + +=cut + +sub shouldSkipNotification { + my $self = shift; + return $self->get( "skipNotification" ) ? 1 : 0; +} + +#---------------------------------------------------------------------------- + +=head2 subscribe ( [userId] ) + +Subscribe a user to this asset. C is a userId to subscribe, defaults +to the current user. + +=cut + +sub subscribe { + my $self = shift; + my $userId = shift || $self->session->user->userId; + $self->getSubscriptionGroup->addUsers( [$userId] ); + return; +} + +#---------------------------------------------------------------------------- + +=head2 unsubscribe ( [userId] ) + +Unsubscribe a user from this asset. C is a userId to unsubscribe, +defaults to the current user. + +=cut + +sub unsubscribe { + my $self = shift; + my $userId = shift || $self->session->user->userId; + $self->getSubscriptionGroup->deleteUsers( [$userId] ); + return; +} + +#---------------------------------------------------------------------------- + +=head2 www_subscribe ( ) + +Subscribe the current user to this asset. + +=cut + +sub www_subscribe { + my $self = shift; + $self->subscribe if $self->canSubscribe; + return $self->www_view; +} + +#---------------------------------------------------------------------------- + +=head2 www_unsubscribe ( ) + +Unsubscribe the current user from this asset. + +=cut + +sub www_unsubscribe { + my $self = shift; + $self->unsubscribe; + return $self->www_view; +} + +1; # You can't handle the truth diff --git a/lib/WebGUI/Help/Asset_WikiMaster.pm b/lib/WebGUI/Help/Asset_WikiMaster.pm index 987a8b5b5..3c4d3d8ec 100644 --- a/lib/WebGUI/Help/Asset_WikiMaster.pm +++ b/lib/WebGUI/Help/Asset_WikiMaster.pm @@ -146,6 +146,18 @@ our $HELP = { { 'name' => 'useContentFilter', }, { 'name' => 'filterCode', }, { 'name' => 'maxImageSize', }, + { + name => 'isSubscribed', + description => 'help isSubscribed', + }, + { + name => 'subscribeUrl', + description => 'help subscribeUrl', + }, + { + name => 'unsubscribeUrl', + description => 'help unsubscribeUrl', + }, ], fields => [], related => [], diff --git a/lib/WebGUI/Help/Asset_WikiPage.pm b/lib/WebGUI/Help/Asset_WikiPage.pm index 3f81fe097..36354c338 100644 --- a/lib/WebGUI/Help/Asset_WikiPage.pm +++ b/lib/WebGUI/Help/Asset_WikiPage.pm @@ -76,6 +76,22 @@ our $HELP = { }, { 'name' => 'editContent', }, { 'name' => 'content', }, + { + name => 'isSubscribed', + description => 'help isSubscribed', + }, + { + name => 'subscribeUrl', + description => 'help subscribeUrl', + }, + { + name => 'unsubscribeUrl', + description => 'help unsubscribeUrl', + }, + { + name => 'owner', + description => 'help owner', + }, ], related => [], }, @@ -110,6 +126,17 @@ our $HELP = { related => [], }, + + 'subscription template' => { + title => 'help subscription title', + body => 'help subscription body', + isa => [ + { + tag => 'view template', + namespace => 'Asset_WikiPage', + }, + ], + }, }; 1; diff --git a/lib/WebGUI/i18n/English/AssetAspect_Subscribable.pm b/lib/WebGUI/i18n/English/AssetAspect_Subscribable.pm new file mode 100644 index 000000000..41ba60bfc --- /dev/null +++ b/lib/WebGUI/i18n/English/AssetAspect_Subscribable.pm @@ -0,0 +1,14 @@ +package WebGUI::i18n::English::AssetAspect_Subscribable; + +use strict; + +our $I18N = { + "new post" => { + message => 'has posted to one of your subscriptions', + lastUpdated => 0, + context => 'Title of the e-mail that is sent for subscriptions', + }, +}; + +1; +#vim:ft=perl diff --git a/lib/WebGUI/i18n/English/Asset_WikiMaster.pm b/lib/WebGUI/i18n/English/Asset_WikiMaster.pm index 121e21db9..8fca0288b 100644 --- a/lib/WebGUI/i18n/English/Asset_WikiMaster.pm +++ b/lib/WebGUI/i18n/English/Asset_WikiMaster.pm @@ -494,6 +494,35 @@ listing of pages that are related to a specific keyword?| }, lastUpdated => 1166848379, }, + 'help isSubscribed' => { + message => q{This variable is true if the user is subscribed to the entire wiki}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'help subscribeUrl' => { + message => q{The URL to subscribe to the entire wiki}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'help unsubscribeUrl' => { + message => q{The URL to unsubscribe from the entire wiki}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'subscribe' => { + message => q{Subscribe}, + lastUpdated => 0, + context => q{Label for link to subscribe to e-mail notifications}, + }, + + 'unsubscribe' => { + message => q{Unsubscribe}, + lastUpdated => 0, + context => q{Label for link to unsubscribe from e-mail notifications}, + }, }; 1; diff --git a/lib/WebGUI/i18n/English/Asset_WikiPage.pm b/lib/WebGUI/i18n/English/Asset_WikiPage.pm index 6949d85c8..bd13bddf4 100644 --- a/lib/WebGUI/i18n/English/Asset_WikiPage.pm +++ b/lib/WebGUI/i18n/English/Asset_WikiPage.pm @@ -268,6 +268,41 @@ our $I18N = lastUpdated => 1169141075, }, + 'help isSubscribed' => { + message => q{This variable is true if the user is subscribed to this wiki page}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'help subscribeUrl' => { + message => q{The URL to subscribe to this wiki page}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'help unsubscribeUrl' => { + message => q{The URL to unsubscribe from this wiki page}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'help owner' => { + message => q{The username of the owner of the page}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'help subscription title' => { + message => q{Wiki Page Subscription E-mail}, + lastUpdated => 0, + context => 'Title for help page', + }, + + 'help subscription body' => { + message => q{The template to send via e-mail to the people subscribed to the wiki}, + lastUpdated => 0, + context => 'Body text for help page', + }, }; 1; diff --git a/t/Asset/WikiPage/subscribable.t b/t/Asset/WikiPage/subscribable.t new file mode 100644 index 000000000..468a83cd8 --- /dev/null +++ b/t/Asset/WikiPage/subscribable.t @@ -0,0 +1,57 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# 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 +#------------------------------------------------------------------ + +# Test the subscribable features of the Wiki +# +# + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use Test::More; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $import = WebGUI::Asset->getImportNode( $session ); +my $wiki + = $import->addChild( { + className => 'WebGUI::Asset::Wobject::WikiMaster', + subscriptionTemplateId => 'limMkk80fMB3fqNZVf162w', + groupIdView => '7', # Everyone + } ); + +my $page + = $wiki->addChild( { + className => 'WebGUI::Asset::WikiPage', + }, undef, undef, { skipAutoCommitWorkflows => 1 } ); + +WebGUI::Test->tagsToRollback( WebGUI::VersionTag->getWorking( $session ) ); + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 4; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# Test subscribable methods +ok( $page->DOES('subscribable'), 'WikiMaster is subscribable' ); + +ok( my $template = $page->getSubscriptionTemplate, 'getSubscriptionTemplate returns something' ); +isa_ok( $template, 'WebGUI::Asset::Template', 'getSubscriptionTemplate' ); +is( $template->getId, 'limMkk80fMB3fqNZVf162w', 'getSubscriptionTemplate gets wikimaster template' ); + +#---------------------------------------------------------------------------- +# Cleanup + +#vim:ft=perl diff --git a/t/Asset/Wobject/WikiMaster/subscribable.t b/t/Asset/Wobject/WikiMaster/subscribable.t new file mode 100644 index 000000000..2fbff29d7 --- /dev/null +++ b/t/Asset/Wobject/WikiMaster/subscribable.t @@ -0,0 +1,98 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# 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 +#------------------------------------------------------------------ + +# Test the subscribable features of the Wiki +# +# + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../../lib"; +use Test::More; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $import = WebGUI::Asset->getImportNode( $session ); +my $wiki + = $import->addChild( { + className => 'WebGUI::Asset::Wobject::WikiMaster', + subscriptionTemplateId => 'limMkk80fMB3fqNZVf162w', + groupIdView => '7', # Everyone + } ); + +WebGUI::Test->tagsToRollback( WebGUI::VersionTag->getWorking( $session ) ); + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 17; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# Test subscribable methods +ok( $wiki->DOES('subscribable'), 'WikiMaster is subscribable' ); +ok( $wiki->shouldSkipNotification, "WikiMaster never notifies" ); + +ok( my $template = $wiki->getSubscriptionTemplate, 'getSubscriptionTemplate returns something' ); +isa_ok( $template, 'WebGUI::Asset::Template', 'getSubscriptionTemplate' ); + +is( $wiki->getSubscriptionTemplateNamespace, 'AssetAspect/Subscribable', 'getSubscriptionNamespace' ); + +ok( my $subgroup = $wiki->getSubscriptionGroup, 'getSubscriptionGroup returns something' ); +isa_ok( $subgroup, 'WebGUI::Group', 'getSubscriptionGroup' ); + +is( $wiki->getSubscribeUrl, $wiki->getUrl('func=subscribe'), 'getSubscribeUrl' ); +is( $wiki->getUnsubscribeUrl, $wiki->getUrl('func=unsubscribe'), 'getUnsubscribeUrl' ); + + +#---------------------------------------------------------------------------- +# canSubscribe permissions +$session->user({ userId => '1' }); +ok( !$wiki->canSubscribe, 'Visitor cannot subscribe' ); +ok( $wiki->canSubscribe( '3' ), 'Admin can subscribe' ); + +# subscribe +$wiki->subscribe('3'); +ok( + WebGUI::User->new( $session, '3' )->isInGroup( $wiki->getSubscriptionGroup->getId ), + 'subscribe' +); + +# isSubscribed +ok( $wiki->isSubscribed( '3' ), 'isSubscribed' ); + +# unsubscribe +$wiki->unsubscribe('3'); +ok( + !WebGUI::User->new( $session, '3' )->isInGroup( $wiki->getSubscriptionGroup->getId ), + 'unsubscribe' +); + + +#---------------------------------------------------------------------------- +# skip notification +ok( !$wiki->get('skipNotification'), 'skipNotification defaults to false' ); +$wiki->setSkipNotification(1); +ok( $wiki->get('skipNotification'), 'setSkipNotification sets skipNotification' ); + +# add revision +my $new_rev = $wiki->addRevision({},time+1); +ok( !$new_rev->get('skipNotification'), 'addRevision resets skipNotification to false' ); + +# notify subscribers +# subscription content + +#---------------------------------------------------------------------------- +# Cleanup + +#vim:ft=perl