From 82e9bf8448714dca66e404f60cde35f1e49faab0 Mon Sep 17 00:00:00 2001 From: Doug Bell Date: Mon, 17 Dec 2007 22:22:06 +0000 Subject: [PATCH] Finished? Ready for merge at least. --- docs/upgrades/_upgrade.skeleton | 128 ++-- .../root_import_gallery-templates-2.wgpkg | Bin 0 -> 21091 bytes docs/upgrades/upgrade_7.4.10-7.5.0.pl | 147 +++-- lib/WebGUI/Asset.pm | 13 +- lib/WebGUI/Asset/File.pm | 152 ++--- lib/WebGUI/Asset/File/Image/Photo.pm | 286 +++++++-- lib/WebGUI/Asset/Shortcut.pm | 20 + lib/WebGUI/Asset/Wobject/Gallery.pm | 564 +++++++++++++++--- lib/WebGUI/Asset/Wobject/GalleryAlbum.pm | 441 ++++++++++++-- lib/WebGUI/AssetLineage.pm | 5 +- lib/WebGUI/AssetPackage.pm | 98 +-- lib/WebGUI/AssetVersioning.pm | 13 +- lib/WebGUI/Form.pm | 53 +- lib/WebGUI/Form/SelectRichEditor.pm | 101 ++++ lib/WebGUI/Help/Asset_Gallery.pm | 229 +++++++ lib/WebGUI/Help/Asset_GalleryAlbum.pm | 309 ++++++++++ lib/WebGUI/Help/Asset_Photo.pm | 258 ++++++++ lib/WebGUI/Keyword.pm | 3 + lib/WebGUI/Search.pm | 38 +- lib/WebGUI/Storage.pm | 88 ++- lib/WebGUI/Storage/Image.pm | 83 +-- lib/WebGUI/i18n/English/Asset_Gallery.pm | 548 +++++++++++++++++ lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm | 289 +++++++++ lib/WebGUI/i18n/English/Asset_Photo.pm | 291 +++++++++ t/Asset/Wobject/Gallery/00base.t | 78 +++ t/Asset/Wobject/Gallery/delete.t | 96 +++ t/Asset/Wobject/Gallery/listFilesForUser.t | 78 +++ t/Asset/Wobject/Gallery/permission.t | 107 ++++ t/Asset/Wobject/Gallery/rss.t | 66 ++ t/Asset/Wobject/Gallery/search.t | 78 +++ t/Asset/Wobject/Gallery/view.t | 130 ++++ t/Asset/Wobject/GalleryAlbum/addArchive.t | 69 +++ t/Asset/Wobject/GalleryAlbum/delete.t | 96 +++ t/Asset/Wobject/GalleryAlbum/permission.t | 107 ++++ t/Asset/Wobject/GalleryAlbum/rss.t | 66 ++ t/Asset/Wobject/GalleryAlbum/slideshow.t | 69 +++ t/Asset/Wobject/GalleryAlbum/thumbnails.t | 69 +++ t/Asset/Wobject/GalleryAlbum/view.t | 132 ++++ t/Form.t | 104 ++++ t/Form/SelectRichEditor.t | 59 ++ t/Storage.t | 10 + t/_test.skeleton | 30 +- t/lib/WebGUI/Test/Maker/HTML.pm | 2 + t/lib/WebGUI/Test/Maker/Permission.pm | 22 +- t/supporting_collateral/elephant_images.zip | Bin 0 -> 50141 bytes 45 files changed, 5112 insertions(+), 513 deletions(-) create mode 100644 docs/upgrades/templates-7.5.0/root_import_gallery-templates-2.wgpkg create mode 100644 lib/WebGUI/Form/SelectRichEditor.pm create mode 100644 lib/WebGUI/Help/Asset_Gallery.pm create mode 100644 lib/WebGUI/Help/Asset_GalleryAlbum.pm create mode 100644 lib/WebGUI/Help/Asset_Photo.pm create mode 100644 lib/WebGUI/i18n/English/Asset_Photo.pm create mode 100644 t/Asset/Wobject/Gallery/00base.t create mode 100644 t/Asset/Wobject/Gallery/delete.t create mode 100644 t/Asset/Wobject/Gallery/listFilesForUser.t create mode 100644 t/Asset/Wobject/Gallery/permission.t create mode 100644 t/Asset/Wobject/Gallery/rss.t create mode 100644 t/Asset/Wobject/Gallery/search.t create mode 100644 t/Asset/Wobject/Gallery/view.t create mode 100644 t/Asset/Wobject/GalleryAlbum/addArchive.t create mode 100644 t/Asset/Wobject/GalleryAlbum/delete.t create mode 100644 t/Asset/Wobject/GalleryAlbum/permission.t create mode 100644 t/Asset/Wobject/GalleryAlbum/rss.t create mode 100644 t/Asset/Wobject/GalleryAlbum/slideshow.t create mode 100644 t/Asset/Wobject/GalleryAlbum/thumbnails.t create mode 100644 t/Asset/Wobject/GalleryAlbum/view.t create mode 100644 t/Form.t create mode 100644 t/Form/SelectRichEditor.t create mode 100644 t/supporting_collateral/elephant_images.zip diff --git a/docs/upgrades/_upgrade.skeleton b/docs/upgrades/_upgrade.skeleton index e13a7b7d9..7821157a7 100644 --- a/docs/upgrades/_upgrade.skeleton +++ b/docs/upgrades/_upgrade.skeleton @@ -12,6 +12,8 @@ use lib "../../lib"; use strict; use Getopt::Long; use WebGUI::Session; +use WebGUI::Storage; +use WebGUI::Asset; my $toVersion = "0.0.0"; # make this match what version you're going to @@ -33,8 +35,71 @@ finish($session); # this line required #} +# --------------- DO NOT EDIT BELOW THIS LINE -------------------------------- -# ---- DO NOT EDIT BELOW THIS LINE ---- +#---------------------------------------------------------------------------- +# Add a package to the import node +sub addPackage { + my $session = shift; + my $file = shift; + + # Make a storage location for the package + my $storage = WebGUI::Storage->createTemp( $session ); + $storage->addFileFromFilesystem( $file ); + + # Import the package into the import node + WebGUI::Asset->getImportNode($session)->importPackage( $storage ); + + # Make the package not a package anymore +} + +#---------------------------------------------------------------------------- +# Add a template from a file +sub addTemplate { + my $session = shift; + my $file = shift; + my $newFolder = shift; + + open(FILE,"<",$file); + my $first = 1; + my $create = 0; + my $head = 0; + my %properties = (className=>"WebGUI::Asset::Template"); + while (my $line = ) { + if ($first) { + $line =~ m/^\#(.*)$/; + $properties{id} = $1; + $first = 0; + } + elsif ($line =~ m/^\#create$/) { + $create = 1; + } + elsif ($line =~ m/^\#(.*):(.*)$/) { + $properties{$1} = $2; + } + elsif ($line =~ m/^~~~$/) { + $head = 1; + } + elsif ($head) { + $properties{headBlock} .= $line; + } + else { + $properties{template} .= $line; + } + } + close(FILE); + if ($create) { + $$newFolder = createNewTemplatesFolder(WebGUI::Asset->getImportNode($session)) + unless (defined $$newFolder); + my $template = $$newFolder->addChild(\%properties, $properties{id}); + } + else { + my $template = WebGUI::Asset->new($session,$properties{id}, "WebGUI::Asset::Template"); + if (defined $template) { + my $newRevision = $template->addRevision(\%properties); + } + } +} #------------------------------------------------- sub start { @@ -63,49 +128,24 @@ sub finish { #------------------------------------------------- sub updateTemplates { - my $session = shift; - return undef unless (-d "templates-".$toVersion); - print "\tUpdating templates.\n" unless ($quiet); - opendir(DIR,"templates-".$toVersion); - my @files = readdir(DIR); - closedir(DIR); - my $importNode = WebGUI::Asset->getImportNode($session); - my $newFolder = undef; - foreach my $file (@files) { - next unless ($file =~ /\.tmpl$/); - open(FILE,""WebGUI::Asset::Template"); - while (my $line = ) { - if ($first) { - $line =~ m/^\#(.*)$/; - $properties{id} = $1; - $first = 0; - } elsif ($line =~ m/^\#create$/) { - $create = 1; - } elsif ($line =~ m/^\#(.*):(.*)$/) { - $properties{$1} = $2; - } elsif ($line =~ m/^~~~$/) { - $head = 1; - } elsif ($head) { - $properties{headBlock} .= $line; - } else { - $properties{template} .= $line; - } - } - close(FILE); - if ($create) { - $newFolder = createNewTemplatesFolder($importNode) unless (defined $newFolder); - my $template = $newFolder->addChild(\%properties, $properties{id}); - } else { - my $template = WebGUI::Asset->new($session,$properties{id}, "WebGUI::Asset::Template"); - if (defined $template) { - my $newRevision = $template->addRevision(\%properties); - } - } - } + my $session = shift; + return undef unless (-d "templates-".$toVersion); + print "\tUpdating templates.\n" unless ($quiet); + opendir(DIR,"templates-".$toVersion); + my @files = readdir(DIR); + closedir(DIR); + my $newFolder = undef; + foreach my $file (@files) { + next unless ($file =~ /\.(tmpl|wgpkg)$/); + my $type = $1; + $file = "templates-" . $toVersion . "/" . $file; + if ($type eq "tmpl") { + addTemplate( $session, $file, \$newFolder ); + } + elsif ($type eq "wgpkg") { + addPackage( $session, $file ); + } + } } #------------------------------------------------- diff --git a/docs/upgrades/templates-7.5.0/root_import_gallery-templates-2.wgpkg b/docs/upgrades/templates-7.5.0/root_import_gallery-templates-2.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..8b71ce9d9999cac292822d70153c60ce79749dd9 GIT binary patch literal 21091 zcmV)4K+3-#iwFP!000001MOW0d{jjf2Sh=NB7QVcID>R9$(7`i5Q6E1^hO{Aj?3L8 zx#SvGNJ14A5Rk5PL8$^FO;8a;n)D_}7X&HNdoTHB_qBaj(nuoc!H;lxw{LfLc6Mjx zzq7OMmCFB*r>CdP%d1i)^uNrL`(MER@vJ2Blu1NhGKoYcsU#4IL{MG;uay5EtJ$J3 z0cpAF^h~S9d3!~D51oTQxO4b_=QTZ7-(adm>`#%W$UWO^(3dHVP`kHO%8fhxj^%(J zezCXM+h+VFGH+2O0dTYY|CHDGyO%%dWzj;v8fX;U%oc-5k*RhQ_zAk>C+I2u(4!qy z!e}xW)h3HZZAOKt*YK+#rrR;`vO zpfsr!@LfPdd`IR|eZ!nrQj@mQi z>eZ%Xv)UA)vTsqHXE7-{sTHauMW&;dttKsg$z(8CJTyAgEssovR;xDU3oU9L@WBH7 zA$q4Uo7EP&rOXIbHc%0Q;AShU;}MwnB5_%e;< zB~jCt;+RAvCh66fqYTO%rlU5zCOLse-`7{>>+2;G^C&f`k+2x_Aj=>59yeW=~C$PU6{H zFBKYtB!R|m)=ZwPY0QcFW@z4i0+?YQ25OC2igdLF21%=>7871exaSP-`0ET_f{JE} zQDIW+naLa%goldlGycFEEu%@JwO==K%K_WK=yAhK?SL8s%3@_NULBZjp8ltv zl%*8_H5Q`(MIx!U*hc?LrBY9q{x9ALK3l10*Z==64UcCvy(DEvhk6BthIH!^q76#* z$&QE*^c7~ulj~%$>;VszIzwUAT7-7gteFPd+ zhcvg4*hD#PvZc8Nq?yw6_@Biet>f}O3X^~$FU(-l$&qhlp$b{JDikW6MxSB={iQag zxe2i405w~{dJ-zMnoPZ)R-Iwt*PbN~2vqe^=#^@fAQAW9-vgeJ?W46)phPAEREFC~ zLXV(@bb|>j1bFJnR0`ypXu8tivx@8mn$+XY7@x?$F(OJN>O*ZI>TU6 zla*6FLNm}_oLOm+I-X$ylO{8Zony`zYPP29G|U8sR&2RRwe5k;6o%*U%-;i+3;K75 z1Eto}^NOk<-6Ze`@W(%y2~EAG$)Hygxk+t?CD6=Nik2g0-OwpanHs$itr>nIFJoT& z41?Yx%vGZf`lW+&(4H=lZm?JkIzKU#Vb=lZl!h2slOZ?QVAWfI&6)r~0@bJh7%xb3 z;}s-CUK5b!=1z+h*Kw+6?+S&2bTyDCFlQ;i)f5PY0eB%~)+y?-2(2clPyEMX$B*&a zV_zCZnqm)b8ajrG;5D0Guj4dIixn8jl!H-+Qi#N`h_2D00$7fXU}dB3>JXvaY~w!p z8mvujE)DGo8lk>a(Qe> zh+GcC#7$Zt99N;%nqf{@Of2UVT2iW^mWtbSVeSPaJYxO;x0 z=Izk{XmptZvq=dZ<7C;AW!0tY6&h`_Neja-fL7sqVyaOp$IoogYE*)BEqFr$5s0Qy zp;7^p{NOVb2BGI>6zVeSm5LQ?r-b?XHg9jEEt#6BX<#Gbbk(3Qi5?*5724-1QK}%r zTcpfMox;pbrWBMTrO>ERhNvyT6A&=A54_3g$(wjoH9`3Tf3wA8&}S0Fu^@{cE$03n zbOBBb(Yw@wK`a}Xf%qGRAj_oA;EfoVT4XC zUwLv#2^^Abq9!PWj1lSynxOO~wdJ7#)(VUg@TLot!cuJ2Yt?460A?v&%3Wg))0oWI zI}Y%7&~2iq5xKAis~LIHl-MHiLu!<&HlT&p(w-9#CvPT!l}3*gO%0C(U?0CyuA^xd zp^*HIY6`CbWLrR86!6a7YD_YSJn5!j6(Gu>u`Hsq)NnJj!0dO}F*J#sx4`wqcJ**L z#E$#tXvm@f)I_6DR44R69xCT>v>bMbc>|A9i%e!b*^%N}<~m1@J~uRci6?&JbQ&md zQlsH1LKP%`qAF==U^qxsftC$sCMvaH&V!z|WTC~Doov)6(#ataDo1c{;iw-4OOW-k z)*|+BY|upp$s=DVCKwn(xoZLj$D_G zv;wTVFb?_obxMkka>TDf@y0Ab$%C|jN8-9+frH`8VV3Ql9_&S45~l)}vGe;C{+AYFQ~r z0Mo8J%u`K953uD(@gpY^=6>;^USF|Hxici!= zOB5!tZ;n?YZ%qO#6)(VrD?3~K7q+qyP?~PlnT37O&Lpv6T>eNf?M{Q=ZbmzS?%*Z5 zV>K14CyZ<*U@#^Cp$H2c8<@x=a z>1JblM?M4$iJWQ~+(moLSrH&;p@V&NgQdN|+3RS0!ZhHif$9skn$WHv3K&53Mdu6R zvS5qe%=a^?9)hKyROka$D)h2L-vAK@2tptldsH1u3cMsx=mq!-RJD!OO}mdaEJP?A zXK+*YznxP}88TU7)?Vj>X&7)rb`~GGj|Q+#V+1^&F@h=Ing=%Uz=P)q2lM-ffG}!C zlb8WB)m#7=-WB4IITrv{P5>AL0x?sTze3^mSmzx5Mm-@m$PiTK90{Y|K&uH}QRuh% zm>5+j4^w23E?DijH(hv`3lBqJ5ndo^{ED*@2&Awa@Gy{EPJ&4wi_n62bIoSkNr+UX ztrR;zY>|#f76jJi<^(hBdD)0Qw;@p^PNLBPd}#JCna%E*nhZcjsEvw*#6C!JV|gd!LMhD zeJnBfWygsBF8EdEXv&LMp%eU1Qjz$d6b}3F&;LODCmaKEo&P9*d2-=@&hbA_uP|e_ zQXSPTtG85X4iiP@=87WAApR3!eiZ&k>WcD5{tChEI83h$P`h-6S)-(Of}CJ~R9KpA z=*M#e>T%8p#SgRzCU=Op6&;l0pCt(h%J(Z4+!%{aCn1M{BfvJ627!Z6BR111M}{)# z+0mt=#e!)Bm+(G#h+VoBqUrQD!DI|#Mc|m6w*-EK6MqLVO$>`7$`O^kUa;358;rOKt0PN+q5ntpG zLu?!hy#!YArh`op9CQw4xXH^zlS4sr#saNCEr$aTFEFBzOa><6$pj4hur9hvSc=MKOc z9&QRXbcF=sP*RA|VuKO6eM4sUX@%l{5(n{rQWySL{^(z&N-q4*IsVt%E7n`A=p;2}CwDcQ z;u9osy`@^-3I`@>DdPW-VMO77LJkKY7fu%x=z;=GE+~+5QJnh!@_!i==m49mSa7hU zP#_QL_@@9s3c_)CvKixsbd&+dNgfizKbDMuG42R2dyv@vEHAGPj43%m14%K)7%s#$pdUQAkVC}q_iTD91pYE07S%I}7aW~!pGBRuS6D3uDy)cV zltl;}^^QF%%tH-PNoRWtKvDZ7j4GsaPE*vllFvx~*1C>3j zPmflGpMBv6Fv7&+hcGg$5uI*9pf~8%s0_-i;5?iW1b`ME$66v|Td zkXAVfzmyW$lalpSJhCU?CIv}I#ejTH8ZRN<=V^Oxvm#D6zd&J*aI{s%3!yD1oX@c& zJ&%Z=9Y_A-h#wzSa)SSPRUrN+5qUd^|MPa?fBcaupWj^gpL6^#GB8@DNmg~v&&iJQ z6bHw3GUlhomM#9rM)L6WadFc?^LBK~CL1KSkg(V6iUg!G^EpUV=!yh%>VLT+0TucG zOe7%dlt3zTnm_U4F0vvfZ0eE?AB)^dq6t0IB=+5FuuRY~c49uV2ZQ z)@qDG6C{B&=xxda3}MyUSC3Br*_DK{C^`}F3@wDjK|;y&d_m{Lc930COQn@$Tv`e> z^7pVpv=iGigx0Vzgp|im!Agt+O?di{%~20&Ol>H@PCH)LQKC(u!UU-@3Etp`R7kK8 z`|R)?l{B%qEnn#~Y`)*_>KLL6Dd%bcuwJqM+n$!@F#|&Kkgn zI_)}6J$(l|w7!V5_RF-Mj3Ao0u)t|cSf|s2vThT{B zJ!a+`RE~mbndO6mn&DxFRj)+XM+i_Y{jIsRpdZy!^r}YCK_J5K zQCOqH5M@i2#teB|a80c=ZQHgJwB&ShGa!Z<9(4BrWKts;%g!zi$wVkCy@Nm@Z!IZ^ z9I#4c{jiYc0ET^RXDo)yOszT&=3gte50D3?)w@jp-80n|FlW)-G5M`pqAdngxMgd$ zbee@N3RnjK68gE!ZvDm(iNTD@3erTq!JSdX1%%La%~LYM?q+_$cxQP~2kK zM0MP1(P-U6`v3t*!qCxHH5^N6m5XoN0YO8Rv}}h3nc)moPob~3BvyEMyQNaAQRcu0 zPK2mRAYClJQwFO)(l06l0M?7F?;ikQS%ZwEqk`~Phi#V#ewB*&SG)XsPMI_78neXH zrcWrk56avs->UIQw`#O1k9Hj*sE2_zbavHXcmh-I-^cnT%z)d>1w zxk$56VFaM?AdCRjz-kii71rBwPz^F6Y0F2a$P?1%nxwu`y1FzAdQ8VB2>8#@tpR=l zqOLn1AIt#A!{R5vO;VR9`3a;x_Tx3f&(0~bN zwm;xzS`z?uOiBa$;_w0+$m}SF8F$MA)oIsXu+SYowu_TR;kGO*>BPm5Mb=LY?g9NP z8WKPI%#1W!Sqj-8UE7Wj5HC51cBDAD7zt5>?kp<~HMUJVj9%u2F55&c69P=o3IkEL z$q}fL0}cehC;m*K(j;K6(uoV$+{TDw%ygmPbOE{)2FW|Uo>MRLlYZI&%+#evZTm`#o?Y9v;)fmmHY%?5PX@IBOX(Nj$wGbK;R%_NR3Vki7uZ`qJqMN z9nA4U#)#}2<~_HuLQFA-i9v|zL9H5ap&Lje5zxA{agu&%QUdI@d z+%BJP3EtHya@2`g29rf;g&w#C1e*+SJ9eBk9d<~v$OFKHtq*P+paiuU?t^3H2zQgJiayECK0(!U*)!~q`nRW1Dh@d(UWJ!Ahq4677Z24+ z*P_8gV3!eM7oj@{_`(nupADFP(jl~UmKkrCM<9^sU>=|i11fwr0Hq!fgn>>GBx?Gq zZL(nVsv=~X|C|HTc+ZOF;oZ$Lcy(3a7#RYmxUTgA-zmQH zAWD`@%VATRJ-UH&B=a&E6D89uaR!Kh#d(bF0Z|@A&mp4RAo6Mo$``Ozu!4l6wb`8t zgfQc+E3^RGR7mIWt<5K{fWRupgqK?^XLk1c%D(5(z z4oJWWicQAxVzdB_k||qI<+hvM)U)FB3~2~+F&cH&Ev_TTj{sDbH%1OQ;E))ob$u!l=&z^b1u09cu}|X0H-Dhfds}?B-53$?YeAV}_Jsc%uwT93~AF!t!S; zSMZFJJuO`n$h0f#h#n8Tc2Je_E9U)%&q#)LjR+H{6cz;&b&Vz$^_MbUZD^yY!ls2f zPm@8}6%J-BNxwyr>5z0@C;KS0R=CL7Ne|&U?{E}DBc6g>PA7i^ za>%a5lI`92hS$SFnLs9 zGEs^$Piay)r`}NmA!_OzI>fUP6;Qq`=Lq3u_)(FpLqs-)>~&0iCc%3*aE(wm70GB}-vxX%?XLA{~n&%4jku)oNe~=f7ZrScicaPvoM4vA|_| zK;5)qj{$8E=wXb+>=+^cfERH_Be*Cknu2ee`TF{j;H{7jnC4Xhl{oZ^;K4L&VWtAZM5w> z_Ivm{N_1_prc4{sfp`pKq4TuB92^^rvt!CRAyJskYD)z8)J$3qlM;$@-{Z8zdWA+y zC*h!Cp*bJf6cB{@ii&VVn#WM`Ds7)WIUBGV%Gq)#GySPTYj zy21np;30F7#X|=N1Cotwx*G=fpa&lR>W8xU!px?6k!*yIbUt6&i|0F#z(6Oos?@fm zsvs|T35gAXS4u!GD$Ws%rwAntK&D1-v|5;>)h2~XgVLqreX=4(oyB4uu?d0wNN^|_ zVH2xD)fj)^2?{4jG@uC+S7XNKhuIeclv!$Jj#>pT(w{)oSXc>$g7n^@SH<(D>7nE8 zvQqiSVUG>THLDlo^5EFyn557Ixf~?b7VT$Tdq>y_UD^g9aJZbZ{h>Mo{UZSeYS^gt zjqIN79v{6ONoB#OH|mK6ip4p&&f(=A)^5;RM3?bXv!V}m=p1-Tbenq@>HRR~B4I=~ zRg84#%K)D6A?vFscmf*?SPaS5%M+e(++vB~39(qpOO;$f@C1kl&H|BTQfI&%Ei!T% zTWjuAo(Bh+BTy14s0N9)hix(lXbS9(?yyKigbHLm=$T)N=H(i-tP~_b^W6dW)@1Y$ z3Ij;VAmzM-@vgp}KA!xL;4;Nbun=Pdn54Iufv#Q62|;nep-Iw6d30uoG~W=Bk&(~4 zFSF194dwB(w#rBWoRkt|LCfZ+KspK@D)1nG~b}iD0t=N`ZGb?Dkomc_R=`7HEPqjL6F4 zlFITcj)pinjghGuw)41*m9}m;ZMfQc*;+91L7g*^5spy~&VuKxOKgMk4`P?D$-;0C zrw!YMl$EY+I(U>ANSi@ry$sx4Jc(F;rgTkG+pfiJk=c5E#zr0Pv=`b{cEg<+g3|St z7kt10Lw1vg&u21xp@{Y!IAS~xv(zpa&-rHa1T!vjFm}nD%}a241$^(FiV!1w>_i+g zR!9X@ze&%IeqzHa1E4E3a#mh0o-e64D%8cpD<;``xx09FEtbf|6Ujup?RuAsXYrts zOd)t7kdf5_=^wEUdyZT@AF-E@xcrVZ;%#w+ACQ7vOb^c-Y4+44=_H=L^-`fRND^r5 zX3ga736-UXXUm4tczA`H|CJp3e3%XlJ17PEm2y_-DR%Juo z@KJAmb_UKrc%D22#~TQo4f!kqp*S>vusJD%oU5DKl2Di+0uf^Uh=P=H%CLzvPmw{$ zkH8{EfXdlNM9VT+W*vqoJ$Lvx)i=J)%viykxlGDG85QCv4g$hv)QU{XrgWyb$oeYE z3XX;eEnpIvkOY(`I_|X5l3BtcDbJ#ES;8-hCCneUGPQ&;K#t_gkxW0QrZAr!r7?vI zHukbytYq((<| z1ZFG!As6z#p_4omhp2;6^mcVeF7AXpQs4~OrCKX)d_UOWj<*b)?-axs$lxxg70C0| zC~-D>)`lN0)(u@c!0j?{Rx#Aa9wFd9Ar_G4Wf|DZya4!$vX?{PK$k@=sL=BQ&e|-I zwe0K75NukuT>KphyM#EYDq fv=Y-2nt_UORIc~#={V%CV``=6K!2Wlg z|ELhDaoPXQ?f<^YuEN}iWTnM3QLZ@tUSrD2ODyYt^-SNxC5{@1H>Q#5+W z`C}g=%A7Yv-{|=!JS4&}DLSbYDiq7?;Co2$qsBLxMW}GSJjGr-M4xwHid4*WejZ$J zbWm!fJSwhB-`-j2(xfg@U35ZH=S;G6d06~7>zCOaXilP!cVSwJ9~_*h*Fai0-qEOH zgkA8oeNo0%rpaJ6MyOCqTTC49gdR3~@nfe<)@gL=B*=P#OQ9VGl=p<>YtbRbc%0A; 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 literal 0 HcmV?d00001 diff --git a/docs/upgrades/upgrade_7.4.10-7.5.0.pl b/docs/upgrades/upgrade_7.4.10-7.5.0.pl index 11518cb43..a26c629e9 100644 --- a/docs/upgrades/upgrade_7.4.10-7.5.0.pl +++ b/docs/upgrades/upgrade_7.4.10-7.5.0.pl @@ -112,8 +112,9 @@ sub installGalleryAlbumAsset { CREATE TABLE IF NOT EXISTS GalleryAlbum ( assetId VARCHAR(22) BINARY NOT NULL, revisionDate BIGINT NOT NULL, - othersCanAdd INT, allowComments INT, + assetIdThumbnail VARCHAR(22) BINARY, + othersCanAdd INT, PRIMARY KEY (assetId, revisionDate) ) ENDSQL @@ -142,24 +143,29 @@ CREATE TABLE IF NOT EXISTS Gallery ( templateIdAddArchive VARCHAR(22) BINARY, templateIdDeleteAlbum VARCHAR(22) BINARY, templateIdDeleteFile VARCHAR(22) BINARY, + templateIdEditAlbum VARCHAR(22) BINARY, templateIdEditFile VARCHAR(22) BINARY, templateIdListAlbums VARCHAR(22) BINARY, templateIdListAlbumsRss VARCHAR(22) BINARY, - templateIdListUserFiles VARCHAR(22) BINARY, - templateIdListUserFilesRss VARCHAR(22) BINARY, + templateIdListFilesForUser VARCHAR(22) BINARY, + templateIdListFilesForUserRss VARCHAR(22) BINARY, templateIdMakeShortcut VARCHAR(22) BINARY, templateIdSearch VARCHAR(22) BINARY, - templateIdSlideshow VARCHAR(22) BINARY, - templateIdThumbnails VARCHAR(22) BINARY, + templateIdViewSlideshow VARCHAR(22) BINARY, + templateIdViewThumbnails VARCHAR(22) BINARY, templateIdViewAlbum VARCHAR(22) BINARY, templateIdViewAlbumRss VARCHAR(22) BINARY, templateIdViewFile VARCHAR(22) BINARY, + viewAlbumAssetId VARCHAR(22), + viewDefault ENUM('album','list'), + viewListOrderBy VARCHAR(40), + viewListOrderDirection ENUM('ASC','DESC'), workflowIdCommit VARCHAR(22) BINARY, PRIMARY KEY (assetId, revisionDate) ) ENDSQL - + $session->config->addToArray("assets","WebGUI::Asset::Wobject::Gallery"); print "DONE!\n" unless $quiet; } @@ -177,8 +183,8 @@ CREATE TABLE IF NOT EXISTS Photo ( revisionDate BIGINT NOT NULL, exifData LONGTEXT, friendsOnly INT, + location VARCHAR(255), rating INT, - storageIdPhoto VARCHAR(22) BINARY, userDefined1 TEXT, userDefined2 TEXT, userDefined3 TEXT, @@ -215,7 +221,71 @@ ENDSQL } -# ---- DO NOT EDIT BELOW THIS LINE ---- +# --------------- DO NOT EDIT BELOW THIS LINE -------------------------------- + +#---------------------------------------------------------------------------- +# Add a package to the import node +sub addPackage { + my $session = shift; + my $file = shift; + + # Make a storage location for the package + my $storage = WebGUI::Storage->createTemp( $session ); + $storage->addFileFromFilesystem( $file ); + + # Import the package into the import node + WebGUI::Asset->getImportNode($session)->importPackage( $storage ); + + # Make the package not a package anymore +} + +#---------------------------------------------------------------------------- +# Add a template from a file +sub addTemplate { + my $session = shift; + my $file = shift; + my $newFolder = shift; + + open(FILE,"<",$file); + my $first = 1; + my $create = 0; + my $head = 0; + my %properties = (className=>"WebGUI::Asset::Template"); + while (my $line = ) { + if ($first) { + $line =~ m/^\#(.*)$/; + $properties{id} = $1; + $first = 0; + } + elsif ($line =~ m/^\#create$/) { + $create = 1; + } + elsif ($line =~ m/^\#(.*):(.*)$/) { + $properties{$1} = $2; + } + elsif ($line =~ m/^~~~$/) { + $head = 1; + } + elsif ($head) { + $properties{headBlock} .= $line; + } + else { + $properties{template} .= $line; + } + } + close(FILE); + if ($create) { + $$newFolder = createNewTemplatesFolder(WebGUI::Asset->getImportNode($session)) + unless (defined $$newFolder); + my $template = $$newFolder->addChild(\%properties, $properties{id}); + } + else { + my $template = WebGUI::Asset->new($session,$properties{id}, "WebGUI::Asset::Template"); + if (defined $template) { + my $newRevision = $template->addRevision(\%properties); + } + } +} #------------------------------------------------- sub start { @@ -244,49 +314,24 @@ sub finish { #------------------------------------------------- sub updateTemplates { - my $session = shift; - return undef unless (-d "templates-".$toVersion); - print "\tUpdating templates.\n" unless ($quiet); - opendir(DIR,"templates-".$toVersion); - my @files = readdir(DIR); - closedir(DIR); - my $importNode = WebGUI::Asset->getImportNode($session); - my $newFolder = undef; - foreach my $file (@files) { - next unless ($file =~ /\.tmpl$/); - open(FILE,""WebGUI::Asset::Template"); - while (my $line = ) { - if ($first) { - $line =~ m/^\#(.*)$/; - $properties{id} = $1; - $first = 0; - } elsif ($line =~ m/^\#create$/) { - $create = 1; - } elsif ($line =~ m/^\#(.*):(.*)$/) { - $properties{$1} = $2; - } elsif ($line =~ m/^~~~$/) { - $head = 1; - } elsif ($head) { - $properties{headBlock} .= $line; - } else { - $properties{template} .= $line; - } - } - close(FILE); - if ($create) { - $newFolder = createNewTemplatesFolder($importNode) unless (defined $newFolder); - my $template = $newFolder->addChild(\%properties, $properties{id}); - } else { - my $template = WebGUI::Asset->new($session,$properties{id}, "WebGUI::Asset::Template"); - if (defined $template) { - my $newRevision = $template->addRevision(\%properties); - } - } - } + my $session = shift; + return undef unless (-d "templates-".$toVersion); + print "\tUpdating templates.\n" unless ($quiet); + opendir(DIR,"templates-".$toVersion); + my @files = readdir(DIR); + closedir(DIR); + my $newFolder = undef; + foreach my $file (@files) { + next unless ($file =~ /\.(tmpl|wgpkg)$/); + my $type = $1; + $file = "templates-" . $toVersion . "/" . $file; + if ($type eq "tmpl") { + addTemplate( $session, $file, \$newFolder ); + } + elsif ($type eq "wgpkg") { + addPackage( $session, $file ); + } + } } #------------------------------------------------- diff --git a/lib/WebGUI/Asset.pm b/lib/WebGUI/Asset.pm index bddd26f20..3e9ffbe4e 100644 --- a/lib/WebGUI/Asset.pm +++ b/lib/WebGUI/Asset.pm @@ -14,6 +14,8 @@ package WebGUI::Asset; =cut +use Carp qw( croak confess ); + use WebGUI::AssetBranch; use WebGUI::AssetClipboard; use WebGUI::AssetExportHtml; @@ -1336,12 +1338,12 @@ Loads an asset module if it's not already in memory. This is a class method. Ret sub loadModule { my ($class, $session, $className) = @_; - (my $module = $className . '.pm') =~ s{::|'}{/}g; + (my $module = $className . '.pm') =~ s{::|'}{/}g; if (eval { require $module; 1 }) { return $className; } - $session->errorHandler->error("Couldn't compile asset package: ".$className.". Root cause: ".$@); - return; + $session->errorHandler->error("Couldn't compile asset package: ".$className.". Root cause: ".$@); + return; } #------------------------------------------------------------------- @@ -1752,6 +1754,7 @@ A specific revision date for the asset to retrieve. If not specified, the most r sub newByDynamicClass { my $class = shift; my $session = shift; + confess "newByDynamicClass requires WebGUI::Session" unless $session; my $assetId = shift; my $revisionDate = shift; return undef unless defined $assetId; @@ -1895,6 +1898,7 @@ sub processPropertiesFromFormPost { $self->session->db->beginTransaction; $self->update(\%data); $self->session->db->commit; + return; } @@ -2082,6 +2086,9 @@ Updates the properties of an existing revision. If you want to create a new revi Hash reference of properties and values to set. +NOTE: C is a special property that uses the WebGUI::Keyword API +to set the keywords for this asset. + =cut sub update { diff --git a/lib/WebGUI/Asset/File.pm b/lib/WebGUI/Asset/File.pm index f58f7472f..4926fd559 100644 --- a/lib/WebGUI/Asset/File.pm +++ b/lib/WebGUI/Asset/File.pm @@ -55,17 +55,17 @@ Override the default method in order to deal with attachments. =cut sub addRevision { - my $self = shift; - my $properties = shift; - - if ($self->get("storageId") ne "") { - my $newStorage = $self->getStorageClass->get($self->session,$self->get("storageId"))->copy; - $properties->{storageId} = $newStorage->getId; - } - - my $newSelf = $self->SUPER::addRevision($properties); - - return $newSelf; + my $self = shift; + my $properties = shift; + + if ($self->get("storageId") ne "") { + my $newStorage = $self->getStorageClass->get($self->session,$self->get("storageId"))->copy; + $properties->{storageId} = $newStorage->getId; + } + + my $newSelf = $self->SUPER::addRevision($properties, @_); + + return $newSelf; } #------------------------------------------------------------------- @@ -173,23 +173,50 @@ Returns the TabForm object that will be used in generating the edit page for thi =cut sub getEditForm { - my $self = shift; - my $tabform = $self->SUPER::getEditForm(); - my $i18n = WebGUI::International->new($self->session, 'Asset_File'); - if ($self->get("filename") ne "") { - $tabform->getTab("properties")->readOnly( - -label=>$i18n->get('current file'), - -hoverHelp=>$i18n->get('current file description', 'Asset_File'), - -value=>'

'.$self->get( '.$self->get("filename").'

' - ); + my $self = shift; + my $tabform = $self->SUPER::getEditForm(); + my $i18n = WebGUI::International->new($self->session, 'Asset_File'); - } - $tabform->getTab("properties")->file( - -name => 'newFile', - -label => $i18n->get('new file'), - -hoverHelp => $i18n->get('new file description'), - ); - return $tabform; + $tabform->getTab("properties")->raw( + ''.$i18n->get('new file').'' + . $self->getEditFormUploadControl + . '' + ); + + return $tabform; +} + +#---------------------------------------------------------------------------- + +=head2 getEditFormUploadControl + +Returns the HTML to render the upload box and link to delete the existing +file, if necessary. + +=cut + +sub getEditFormUploadControl { + my $self = shift; + my $session = $self->session; + my $i18n = WebGUI::International->new($session, 'Asset_File'); + my $html = ''; + + if ($self->get("filename") ne "") { + $html .= WebGUI::Form::readOnly( $session, { + label => $i18n->get('current file'), + hoverHelp => $i18n->get('current file description', 'Asset_File'), + value => '

'.$self->get( '.$self->get("filename").'

' + }); + } + + # Control to upload a new file + $html .= WebGUI::Form::file( $session, { + name => 'newFile', + label => $i18n->get('new file'), + hoverHelp => $i18n->get('new file description'), + }); + + return $html; } @@ -200,10 +227,10 @@ sub getFileUrl { return $self->getStorageLocation->getUrl($self->get("filename")); } - #------------------------------------------------------------------- sub getFileIconUrl { my $self = shift; + return unless $self->get("filename"); ## Why do I have to do this when creating new Files? return $self->getStorageLocation->getFileIconUrl($self->get("filename")); } @@ -246,6 +273,7 @@ sub getStorageFromPost { my $self = shift; my $storageId = shift; my $fileStorageId = WebGUI::Form::File->new($self->session, {name => 'newFile', value=>$storageId })->getValueFromPost; + $self->session->errorHandler->info( "File Storage Id: $fileStorageId" ); return $self->getStorageClass->get($self->session, $fileStorageId); } @@ -295,45 +323,21 @@ sub prepareView { #------------------------------------------------------------------- sub processPropertiesFromFormPost { - my $self = shift; - my $session = $self->session; + my $self = shift; + my $session = $self->session; - my $errors = $self->SUPER::processPropertiesFromFormPost; - return $errors if $errors; - - #Get the storage location out of memory. If you call getStorageLocation you risk creating another one. - # How can this EVER be true? - my $storageLocation = $self->{_storageLocation}; - $session->errorHandler->error("Storage Location set magically") if defined $storageLocation; - my $storageId = undef; - $storageId = $storageLocation->getId if(defined $storageLocation); - - #Now remove the storage location to prevent wierd caching stuff. - delete $self->{_storageLocation}; - - #Clear the storage location if a file was uploaded. - if($session->form->get("newFile_file") ne "") { - $storageLocation->clear(); + my $errors = $self->SUPER::processPropertiesFromFormPost; + return $errors if $errors; + + if (my $storageId = $session->form->get('newFile','File')) { + $session->errorHandler->info("Got a new file for asset " . $self->getId); + my $storage = $self->getStorageClass->get( $session, $storageId); + my $filePath = $storage->getPath( $storage->getFiles->[0] ); + $self->setFile( $filePath ); + $storage->delete; } - # Pass in the storage Id to prevent another one from being created. - my $storage = $self->getStorageFromPost($storageId); - if (defined $storage) { - my $filename = $storage->getFiles()->[0]; - - if (defined $filename) { - my %data; - $data{filename} = $filename; - $data{storageId} = $storage->getId; - $data{title} = $filename unless ($session->form->process("title")); - $data{menuTitle} = $filename unless ($session->form->process("menuTitle")); - $data{url} = $self->getParent->get('url').'/'.$filename unless ($session->form->process("url")); - $self->setStorageLocation($storage); - $self->update(\%data); - } - } - - $self->applyConstraints; + return; } @@ -421,18 +425,18 @@ sub setSize { #------------------------------------------------------------------- sub setStorageLocation { - my $self = shift; + my $self = shift; my $storage = shift; - if (defined $storage) { + if (defined $storage) { $self->{_storageLocation} = $storage; - } - elsif ($self->get("storageId") eq "") { - $self->{_storageLocation} = $self->getStorageClass->create($self->session); - $self->update({storageId=>$self->{_storageLocation}->getId}); - } + } + elsif ($self->get("storageId") eq "") { + $self->{_storageLocation} = $self->getStorageClass->create($self->session); + $self->update({storageId=>$self->{_storageLocation}->getId}); + } else { - $self->{_storageLocation} = $self->getStorageClass->get($self->session,$self->get("storageId")); - } + $self->{_storageLocation} = $self->getStorageClass->get($self->session,$self->get("storageId")); + } } #------------------------------------------------------------------- @@ -475,9 +479,11 @@ sub updatePropertiesFromStorage { my $self = shift; my $storage = $self->getStorageLocation; my $filename = $storage->getFiles->[0]; + $self->session->errorHandler->info("Updating file asset filename to $filename"); $self->update({ filename => $filename, }); + return; } #------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/File/Image/Photo.pm b/lib/WebGUI/Asset/File/Image/Photo.pm index ccbf0dd44..568282979 100644 --- a/lib/WebGUI/Asset/File/Image/Photo.pm +++ b/lib/WebGUI/Asset/File/Image/Photo.pm @@ -22,19 +22,7 @@ use Image::ExifTool qw( :Public ); use JSON; use Tie::IxHash; -our $magick; -BEGIN { - if (eval { require Graphics::Magick; 1 }) { - $magick = 'Graphics::Magick'; - } - elsif (eval { require Image::Magick; 1 }) { - $magick = 'Image::Magick'; - } - else { - croak "You must have either Graphics::Magick or Image::Magick installed to run WebGUI.\n"; - } -} - +use WebGUI::DateTime; use WebGUI::Friends; use WebGUI::Utility; @@ -50,7 +38,6 @@ WebGUI::Asset::File::Image::Photo use WebGUI::Asset::File::Image::Photo - =head1 METHODS These methods are available from this class: @@ -72,15 +59,18 @@ sub definition { my $i18n = __PACKAGE__->i18n($session); tie my %properties, 'Tie::IxHash', ( + exifData => { + defaultValue => undef, + }, friendsOnly => { defaultValue => 0, }, + location => { + defaultValue => undef, + }, rating => { defaultValue => 0, }, - storageIdPhoto => { - defaultValue => undef, - }, ); # UserDefined Fields @@ -91,12 +81,13 @@ sub definition { } push @{$definition}, { - assetName => $i18n->get('assetName'), - icon => 'Image.gif', - tableName => 'Photo', - className => 'WebGUI::Asset::File::Image::Photo', - i18n => 'Asset_Photo', - properties => \%properties, + assetName => $i18n->get('assetName'), + autoGenerateForms => 0, + icon => 'Image.gif', + tableName => 'Photo', + className => 'WebGUI::Asset::File::Image::Photo', + i18n => 'Asset_Photo', + properties => \%properties, }; return $class->SUPER::definition($session, $definition); @@ -114,14 +105,27 @@ reference. Returns the hash reference for convenience. sub appendTemplateVarsForCommentForm { my $self = shift; my $var = shift; + my $session = $self->session; - $var->{commentForm_start} - = WebGUI::Form::formHeader( $session ); + $var->{ commentForm_start } + = WebGUI::Form::formHeader( $session ) . WebGUI::Form::hidden( $session, { name => "func", value => "addCommentSave" } ) ; - $var->{commentForm_end} + $var->{ commentForm_end } = WebGUI::Form::formFooter( $session ); + $var->{ commentForm_bodyText } + = WebGUI::Form::HTMLArea( $session, { + name => "bodyText", + richEditId => $self->getGallery->get("richEditIdComment"), + }); + + $var->{ commentForm_submit } + = WebGUI::Form::submit( $session, { + name => "submit", + value => "Save Comment", + }); + return $var; } @@ -143,14 +147,14 @@ sub applyConstraints { my $self = shift; my $gallery = $self->getGallery; - $self->makeResolutions(); - $self->updateExifDataFromFile(); - # Update the asset's size and make a thumbnail $self->SUPER::applyConstraints({ maxImageSize => $self->getGallery->get("imageViewSize"), thumbnailSize => $self->getGallery->get("imageThumbnailSize"), }); + + $self->makeResolutions(); + $self->updateExifDataFromFile(); } #---------------------------------------------------------------------------- @@ -247,6 +251,19 @@ sub deleteComment { #---------------------------------------------------------------------------- +=head2 getAutoCommitWorkflowId ( ) + +Returns the workflowId of the Gallery's approval workflow. + +=cut + +sub getAutoCommitWorkflowId { + my $self = shift; + return $self->getGallery->get("workflowIdCommit"); +} + +#---------------------------------------------------------------------------- + =head2 getComment ( commentId ) Get a comment from this asset. C is the ID of the comment to get. Returns @@ -292,6 +309,7 @@ Get a WebGUI::Paginator for the comments for this Photo. sub getCommentPaginator { my $self = shift; + my $session = $self->session; my $p = WebGUI::Paginator->new($session, $self->getUrl); $p->setDataByQuery( @@ -366,10 +384,37 @@ Get a hash reference of template variables shared by all views of this asset. sub getTemplateVars { my $self = shift; + my $session = $self->session; my $var = $self->get; + my $owner = WebGUI::User->new( $session, $self->get("ownerUserId") ); + $var->{ canComment } = $self->canComment; + $var->{ canEdit } = $self->canEdit; + $var->{ numberOfComments } = scalar @{ $self->getCommentIds }; + $var->{ ownerUsername } = $owner->username; + $var->{ url } = $self->getUrl; + $var->{ url_delete } = $self->getUrl('func=delete'); + $var->{ url_demote } = $self->getUrl('func=demote'); + $var->{ url_edit } = $self->getUrl('func=edit'); + $var->{ url_gallery } = $self->getGallery->getUrl; + $var->{ url_makeShortcut } = $self->getUrl('func=makeShortcut'); + $var->{ url_listFilesForOwner } + = $self->getGallery->getUrl('func=listFilesForUser;userId=' . $self->get("ownerUserId")); + $var->{ url_promote } = $self->getUrl('func=promote'); + + $var->{ fileUrl } = $self->getFileUrl; + $var->{ thumbnailUrl } = $self->getThumbnailUrl; + + ### Download resolutions + for my $resolution ( $self->getResolutions ) { + push @{ $var->{ resolutions_loop } }, { + url_download => $self->getStorageLocation->getPathFrag($resolution) + }; + } + ### Format exif vars my $exif = jsonToObj( delete $var->{exifData} ); + $exif = ImageInfo( $self->getStorageLocation->getPath( $self->get("filename") ) ); for my $tag ( keys %$exif ) { # Hash of exif_tag => value $var->{ "exif_" . $tag } = $exif->{$tag}; @@ -428,6 +473,7 @@ contained in. sub makeResolutions { my $self = shift; my $resolutions = shift; + my $error; croak "Photo->makeResolutions: resolutions must be an array reference" if $resolutions && ref $resolutions ne "ARRAY"; @@ -435,15 +481,14 @@ sub makeResolutions { # Get default if necessary $resolutions ||= $self->getGallery->getImageResolutions; - my $storage = $self->getStorageLocation; - my $photo = $magick->new; - $photo->Read( $storage->get( $self->get("filename") ) ); + my $storage = $self->getStorageLocation; + $self->session->errorHandler->info(" Making resolutions for '" . $self->get("filename") . q{'}); for my $res ( @$resolutions ) { # carp if resolution is bad - my $newPhoto = $photo->Clone; - $newPhoto->Resize( geometry => $res ); - $newPhoto->Write( $storage->getFilePath( "$res.jpg" ) ); + my $newFilename = $res . ".jpg"; + $storage->copyFile( $self->get("filename"), $newFilename ); + $storage->resize( $newFilename, $res ); } } @@ -512,8 +557,12 @@ sub prepareView { sub processPropertiesFromFormPost { my $self = shift; my $errors = $self->SUPER::processPropertiesFromFormPost || []; + + # Return if errors + return $errors if @$errors; - + # Passes all checks + $self->requestAutoCommit; } #---------------------------------------------------------------------------- @@ -566,7 +615,9 @@ sub updateExifDataFromFile { my $self = shift; my $storage = $self->getStorageLocation; - my $info = ImageInfo( $storage->getFilePath( $self->get('filename') ) ); + return; + my $info = ImageInfo( $storage->getPath( $self->get('filename') ) ); + use Data::Dumper; $self->session->errorHandler->info( Dumper $info ); $self->update({ exifData => objToJson( $info ), }); @@ -584,16 +635,19 @@ sub view { my $self = shift; my $session = $self->session; my $var = $self->getTemplateVars; - $var->{ controls } = $self->getToolbar; - $var->{ fileUrl } = $self->getFileUrl; - $var->{ fileIcon } = $self->getFileIconUrl; $self->appendTemplateVarsForCommentForm( $var ); my $p = $self->getCommentPaginator; - $var->{ commentLoop } = $p->getPageData; - $var->{ commentLoop_urlNext } = [$p->getNextPageLink]->[0]; - $var->{ commentLoop_urlPrev } = [$p->getPrevPageLink]->[0]; + for my $comment ( @{ $p->getPageData } ) { + my $user = WebGUI::User->new( $session, $comment->{userId} ); + $comment->{ username } = $user->username; + + my $dt = WebGUI::DateTime->new( $session, $comment->{ creationDate } ); + $comment->{ creationDate } = $dt->toUserTimeZone; + + push @{ $var->{commentLoop} }, $comment; + } $var->{ commentLoop_pageBar } = $p->getBarAdvanced; return $self->processTemplate($var, undef, $self->{_viewTemplate}); @@ -609,14 +663,15 @@ Save a new comment to the Photo. sub www_addCommentSave { my $self = shift; + my $session = $self->session; - return $self->session->privilege->insufficient unless $self->canComment; + return $session->privilege->insufficient unless $self->canComment; - my $form = $self->session; + my $form = $self->session->form; my $properties = { assetId => $self->getId, - creationDate => time, + creationDate => WebGUI::DateTime->new( $session, time )->toDatabase, userId => $session->user->userId, visitorIp => ( $session->user->userId eq "1" ? $session->env("REMOTE_ADDR") : undef ), bodyText => $form->get("bodyText"), @@ -638,14 +693,17 @@ this Photo exists in. sub www_delete { my $self = shift; + my $session = $self->session; return $self->session->privilege->insufficient unless $self->canEdit; - my $var = $self->getTemplateVar; + my $var = $self->getTemplateVars; $var->{ url_yes } = $self->getUrl("func=deleteConfirm"); + # TODO Get albums with shortcuts to this asset + return $self->processStyle( - $self->processTemplate( $var, $self->getGallery->get("templateIdDeletePhoto") ) + $self->processTemplate( $var, $self->getGallery->get("templateIdDeleteFile") ) ); } @@ -663,7 +721,7 @@ sub www_deleteConfirm { return $self->session->privilege->insufficient unless $self->canEdit; - my $i18n = $self->i18n( $self->session ); + my $i18n = __PACKAGE__->i18n( $self->session ); $self->purge; @@ -674,6 +732,25 @@ sub www_deleteConfirm { #---------------------------------------------------------------------------- +=head2 www_demote + +Override the default demote page to send the user back to the GalleryAlbum +edit screen. + +=cut + +sub www_demote { + my $self = shift; + + return $self->session->privilege->insufficient unless $self->canEdit; + + $self->demote; + + return $self->session->asset( $self->getParent )->www_edit; +} + +#---------------------------------------------------------------------------- + =head2 www_download Download the Photo with the specified resolution. If no resolution specified, @@ -719,10 +796,36 @@ sub www_edit { return $self->session->privilege->locked unless $self->canEditIfLocked; # Prepare the template variables - my $var = $self->getTemplateVars; + my $var = { + url_addArchive => $self->getParent->getUrl('func=addArchive'), + }; - $var->{ form_header } = WebGUI::Form::formHeader( $session ); - $var->{ form_footer } = WebGUI::Form::formFooter( $session ); + # Generate the form + if ($form->get("func") eq "add") { + $var->{ form_start } + = WebGUI::Form::formHeader( $session, { + action => $self->getParent->getUrl('func=editSave;assetId=new;class='.__PACKAGE__), + }); + } + else { + $var->{ form_start } + = WebGUI::Form::formHeader( $session, { + action => $self->getUrl('func=editSave'), + }); + } + $var->{ form_start } + .= WebGUI::Form::hidden( $session, { + name => "proceed", + value => "showConfirmation", + }); + + $var->{ form_end } = WebGUI::Form::formFooter( $session ); + + $var->{ form_submit } + = WebGUI::Form::submit( $session, { + name => "submit", + value => "Save", + }); $var->{ form_title } = WebGUI::Form::Text( $session, { @@ -737,12 +840,7 @@ sub www_edit { richEditId => $self->getGallery->get("assetIdRichEditFile"), }); - $var->{ form_storageIdPhoto } - = WebGUI::Form::Image( $session, { - name => "storageIdPhoto", - value => ( $form->get("storageIdPhoto") || $self->get("storageIdPhoto") ), - maxAttachments => 1, - }); + $var->{ form_photo } = $self->getEditFormUploadControl; $var->{ form_keywords } = WebGUI::Form::Text( $session, { @@ -771,6 +869,23 @@ sub www_edit { #---------------------------------------------------------------------------- +=head2 www_editSave ( ) + +Save the edit form. Overridden to display a confirm message to the user. + +=cut + +sub www_editSave { + my $self = shift; + $self->SUPER::www_editSave; + + my $i18n = __PACKAGE__->i18n( $self->session ); + + sprintf $i18n->get("save message"), $self->getUrl, +} + +#---------------------------------------------------------------------------- + =head2 www_makeShortcut ( ) Display the form to make a shortcut. @@ -781,24 +896,27 @@ This page is only available to those who can edit this Photo. sub www_makeShortcut { my $self = shift; + my $session = $self->session; return $self->session->privilege->insufficient unless $self->canEdit; # Create the form to make a shortcut my $var = $self->getTemplateVars; - $var->{ form_header } + $var->{ form_start } = WebGUI::Form::formHeader( $session ) . WebGUI::Form::hidden( $session, { name => "func", value => "makeShortcutSave" }); - $var->{ form_footer } + $var->{ form_end } = WebGUI::Form::formFooter( $session ); # Albums under this Gallery my $albums = $self->getGallery->getAlbumIds; my %albumOptions; for my $assetId ( @$albums ) { - $albumOptions{ $assetId } - = WebGUI::Asset->newByDynamicClass($session, $assetId)->get("title"); + my $asset = WebGUI::Asset->newByDynamicClass($session, $assetId); + if ($asset->canAddFile) { + $albumOptions{ $assetId } = $asset->get("title"); + } } $var->{ form_parentId } = WebGUI::Form::selectBox( $session, { @@ -827,7 +945,8 @@ sub www_makeShortcutSave { my $form = $self->session->form; return $self->session->privilege->insufficient unless $self->canEdit; - + + my $parentId = $form->get('parentId'); my $shortcut = $self->makeShortcut( $parentId ); return $shortcut->www_view; @@ -835,6 +954,46 @@ sub www_makeShortcutSave { #---------------------------------------------------------------------------- +=head2 www_promote + +Override the default promote page to send the user back to the GalleryAlbum +edit screen. + +=cut + +sub www_promote { + my $self = shift; + + return $self->session->privilege->insufficient unless $self->canEdit; + + $self->promote; + + return $self->session->asset( $self->getParent )->www_edit; +} + +#---------------------------------------------------------------------------- + +=head2 www_showConfirmation ( ) + +Shows the confirmation message after adding / editing a gallery album. +Provides links to view the photo and add more photos. + +=cut + +sub www_showConfirmation { + my $self = shift; + my $i18n = __PACKAGE__->i18n( $self->session ); + + return $self->processStyle( + sprintf( $i18n->get('save message'), + $self->getUrl, + $self->getParent->getUrl('func=add;className='.__PACKAGE__), + ) + ); +} + +#---------------------------------------------------------------------------- + =head2 www_view ( ) Shows the output of L inside of the style provided by the gallery this @@ -857,6 +1016,5 @@ sub www_view { $self->session->output->print($foot, 1); return "chunked"; } -} 1; diff --git a/lib/WebGUI/Asset/Shortcut.pm b/lib/WebGUI/Asset/Shortcut.pm index 5d6410d88..f24e88126 100644 --- a/lib/WebGUI/Asset/Shortcut.pm +++ b/lib/WebGUI/Asset/Shortcut.pm @@ -593,6 +593,26 @@ sub getPrefFieldsToImport { return split("\n",$self->getValue("prefFieldsToImport")); } +#---------------------------------------------------------------------------- + +=head2 getTemplateVars + +Gets the template vars for this shortcut. + +=cut + +sub getTemplateVars { + my $self = shift; + + my $shortcut = $self->getShortcut; + if ( $shortcut->can('getTemplateVars') ) { + return $shortcut->getTemplateVars; + } + else { + return $shortcut->get; + } +} + #------------------------------------------------------------------- sub isDashlet { my $self = shift; diff --git a/lib/WebGUI/Asset/Wobject/Gallery.pm b/lib/WebGUI/Asset/Wobject/Gallery.pm index 1133f0c6a..3bbbdb3cc 100644 --- a/lib/WebGUI/Asset/Wobject/Gallery.pm +++ b/lib/WebGUI/Asset/Wobject/Gallery.pm @@ -24,16 +24,14 @@ use base 'WebGUI::Asset::Wobject'; =head1 SYNOPSIS +=head1 DIAGNOSTICS + =head1 METHODS #------------------------------------------------------------------- =head2 definition ( ) -defines wobject properties for New Wobject 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 { @@ -51,6 +49,23 @@ sub definition { '1600' => '1600', '2880' => '2880', ); + + tie my %viewDefaultOptions, 'Tie::IxHash', ( + list => $i18n->get("viewDefault option list"), + album => $i18n->get("viewDefault option album"), + ); + + tie my %viewListOrderByOptions, 'Tie::IxHash', ( + creationDate => $i18n->get("viewListOrderBy option creationDate"), + lineage => $i18n->get("viewListOrderBy option lineage"), + revisionDate => $i18n->get("viewListOrderBy option revisionDate"), + title => $i18n->get("viewListOrderBy option title"), + ); + + tie my %viewListOrderDirectionOptions, 'Tie::IxHash', ( + ASC => $i18n->get("viewListOrderDirection option asc"), + DESC => $i18n->get("viewListOrderDirection option desc"), + ); tie my %properties, 'Tie::IxHash', ( groupIdAddComment => { @@ -77,7 +92,7 @@ sub definition { imageResolutions => { tab => "properties", fieldType => "checkList", - defaultValue => ['800', '1024', '1200', '1600', '2880'], + defaultValue => join("\n", '800', '1024', '1200', '1600', '2880'), options => \%imageResolutionOptions, label => $i18n->get("imageResolutions label"), hoverHelp => $i18n->get("imageResolutions description"), @@ -85,14 +100,14 @@ sub definition { imageViewSize => { tab => "properties", fieldType => "integer", - defaultValue => 0, + defaultValue => 700, label => $i18n->get("imageViewSize label"), hoverHelp => $i18n->get("imageViewSize description"), }, imageThumbnailSize => { tab => "properties", fieldType => "integer", - defaultValue => 0, + defaultValue => 300, label => $i18n->get("imageThumbnailSize label"), hoverHelp => $i18n->get("imageThumbnailSize description"), }, @@ -106,7 +121,7 @@ sub definition { richEditIdComment => { tab => "properties", fieldType => "selectRichEditor", - defaultValue => undef, # Rich Editor for Posts + defaultValue => "PBrichedit000000000002", # Forum Rich Editor label => $i18n->get("richEditIdFileComment label"), hoverHelp => $i18n->get("richEditIdFileComment description"), }, @@ -134,6 +149,14 @@ sub definition { label => $i18n->get("templateIdDeleteFile label"), hoverHelp => $i18n->get("templateIdDeleteFile description"), }, + templateIdEditAlbum => { + tab => "display", + fieldType => "template", + defaultValue => "", + namespace => "GalleryAlbum/Edit", + label => $i18n->get("templateIdEditAlbum label"), + hoverHelp => $i18n->get("templateIdEditAlbum description"), + }, templateIdEditFile => { tab => "display", fieldType => "template", @@ -158,21 +181,21 @@ sub definition { label => $i18n->get("templateIdListAlbumsRss label"), hoverHelp => $i18n->get("templateIdListAlbumsRss description"), }, - templateIdListUserFiles => { + templateIdListFilesForUser => { tab => "display", fieldType => "template", defaultValue => "", - namespace => "Gallery/ListUserFiles", - label => $i18n->get("templateIdListUserFiles label"), - hoverHelp => $i18n->get("templateIdListUserFiles description"), + namespace => "Gallery/ListFilesForUser", + label => $i18n->get("templateIdListFilesForUser label"), + hoverHelp => $i18n->get("templateIdListFilesForUser description"), }, - templateIdListUserFilesRss => { + templateIdListFilesForUserRss => { tab => "display", fieldType => "template", defaultValue => "", - namespace => "Gallery/ListUserFilesRss", - label => $i18n->get("templateIdListUserFilesRss label"), - hoverHelp => $i18n->get("templateIdListUserFilesRss description"), + namespace => "Gallery/ListFilesForUserRss", + label => $i18n->get("templateIdListFilesForUserRss label"), + hoverHelp => $i18n->get("templateIdListFilesForUserRss description"), }, templateIdMakeShortcut => { tab => "display", @@ -190,21 +213,21 @@ sub definition { label => $i18n->get("templateIdSearch label"), hoverHelp => $i18n->get("templateIdSearch description"), }, - templateIdSlideshow => { + templateIdViewSlideshow => { tab => "display", fieldType => "template", defaultValue => "", - namespace => "GalleryAlbum/Slideshow", - label => $i18n->get("templateIdSlideshow label"), - hoverHelp => $i18n->get("templateIdSlideshow description"), + namespace => "GalleryAlbum/ViewSlideshow", + label => $i18n->get("templateIdViewSlideshow label"), + hoverHelp => $i18n->get("templateIdViewSlideshow description"), }, - templateIdThumbnails => { + templateIdViewThumbnails => { tab => "display", fieldType => "template", defaultValue => "", - namespace => "GalleryAlbum/Thumbnails", - label => $i18n->get("templateIdThumbnails label"), - hoverHelp => $i18n->get("templateIdThumbnails description"), + namespace => "GalleryAlbum/ViewThumbnails", + label => $i18n->get("templateIdViewThumbnails label"), + hoverHelp => $i18n->get("templateIdViewThumbnails description"), }, templateIdViewAlbum => { tab => "display", @@ -230,6 +253,37 @@ sub definition { label => $i18n->get("templateIdViewFile label"), hoverHelp => $i18n->get("templateIdViewFile description"), }, + viewDefault => { + tab => "display", + fieldType => "selectBox", + defaultValue => "list", + options => \%viewDefaultOptions, + label => $i18n->get("viewDefault label"), + hoverHelp => $i18n->get("viewDefault description"), + }, + viewAlbumAssetId => { + tab => "display", + fieldType => "asset", + class => "WebGUI::Asset::Wobject::GalleryAlbum", + label => $i18n->get("viewAlbumAssetId label"), + hoverHelp => $i18n->get("viewAlbumAssetId description"), + }, + viewListOrderBy => { + tab => "display", + fieldType => "selectBox", + defaultValue => "lineage", # "Sequence Number" + options => \%viewListOrderByOptions, + label => $i18n->get("viewListOrderBy label"), + hoverHelp => $i18n->get("viewListOrderBy description"), + }, + viewListOrderDirection => { + tab => "display", + fieldType => "selectBox", + defaultValue => "ASC", + options => \%viewListOrderDirectionOptions, + label => $i18n->get("viewListOrderDirection label"), + hoverHelp => $i18n->get("viewListOrderDirection description"), + }, workflowIdCommit => { tab => "security", fieldType => "workflow", @@ -254,6 +308,91 @@ sub definition { #---------------------------------------------------------------------------- +=head2 appendTemplateVarsSearchForm ( var ) + +Appends the template vars for the search form to the hash reference C. +Returns the hash reference for convenience. + +=cut + +sub appendTemplateVarsSearchForm { + my $self = shift; + my $var = shift; + my $session = $self->session; + my $form = $self->session->form; + my $i18n = WebGUI::International->new($session, 'Asset_Gallery'); + + $var->{ searchForm_start } + = WebGUI::Form::formHeader( $session, { + action => $self->getUrl('func=search'), + method => "GET", + }); + + $var->{ searchForm_end } + = WebGUI::Form::formFooter( $session ); + + $var->{ searchForm_basicSearch } + = WebGUI::Form::text( $session, { + name => "basicSearch", + value => $form->get("basicSearch"), + }); + + $var->{ searchForm_title } + = WebGUI::Form::text( $session, { + name => "title", + value => $form->get("title"), + }); + + $var->{ searchForm_description } + = WebGUI::Form::text( $session, { + name => "description", + value => $form->get("description"), + }); + + $var->{ searchForm_keywords } + = WebGUI::Form::text( $session, { + name => "keywords", + value => $form->get("keywords"), + }); + + # Search classes + tie my %searchClassOptions, 'Tie::IxHash', ( + 'WebGUI::Asset::File::Image::Photo' => $i18n->get("search class photo"), + 'WebGUI::Asset::Wobject::GalleryAlbum' => $i18n->get("search class galleryalbum"), + '' => $i18n->get("search class any"), + ); + $var->{ searchForm_className } + = WebGUI::Form::radioList( $session, { + name => "className", + value => $form->get("className"), + options => \%searchClassOptions, + }); + + # Search creationDate + my $oneYearAgo = WebGUI::DateTime->new( $session, time )->add( years => -1 )->epoch; + $var->{ searchForm_creationDate_after } + = WebGUI::Form::dateTime( $session, { + name => "creationDate_after", + value => $form->get("creationDate_after","dateTime") || $oneYearAgo, + }); + $var->{ searchForm_creationDate_before } + = WebGUI::Form::dateTime( $session, { + name => "creationDate_before", + value => $form->get("creationDate_before","dateTime"), + }); + + # Buttons + $var->{ searchForm_submit } + = WebGUI::Form::submit( $session, { + name => "submit", + value => $i18n->get("search submit"), + }); + + return $var; +} + +#---------------------------------------------------------------------------- + =head2 canAddFile ( [userId] ) Returns true if the user can add files to this Gallery. C is the @@ -356,23 +495,33 @@ Gets an array reference of all the album IDs under this Gallery. sub getAlbumIds { my $self = shift; - return $self->getLineage(['descendants'], { - includeOnlyClasses => ['WebGUI::Asset::Wobject::GalleryAlbum'], - }); + my $assets + = $self->getLineage(['descendants'], { + includeOnlyClasses => ['WebGUI::Asset::Wobject::GalleryAlbum'], + }); + + return $assets; } #---------------------------------------------------------------------------- -=head2 getAlbumPaginator ( ) +=head2 getAlbumPaginator ( options ) -Gets a WebGUI::Paginator for all the albums in this Gallery. +Gets a WebGUI::Paginator for all the albums in this Gallery. C is a +hash reference with the following keys. + + perpage => The number of results to show per page. Default: 20 =cut sub getAlbumPaginator { my $self = shift; + my $options = shift; + + my $perpage = $options->{ perpage } || 20; - my $p = WebGUI::Paginator->new( $self->session, $self->getUrl ); + my $p + = WebGUI::Paginator->new( $self->session, $self->getUrl, $perpage ); $p->setDataByArrayRef( $self->getAlbumIds ); return $p; @@ -392,7 +541,7 @@ sub getAssetClassForFile { my $filepath = shift; # Checks for Photo assets - if ( $filepath =~ /\.(jpe?g|gif|png)/i ) { + if ( $filepath =~ /\.(jpe?g|gif|png)$/i ) { return "WebGUI::Asset::File::Image::Photo"; } @@ -416,9 +565,9 @@ sub getImageResolutions { #---------------------------------------------------------------------------- -=head2 getSearchPaginator ( options ) +=head2 getSearchPaginator ( rules ) -Gets a WebGUI::Paginator for a search. C is a hash reference of +Gets a WebGUI::Paginator for a search. C is a hash reference of options with the following keys: keywords => Keywords to search on @@ -431,11 +580,11 @@ sub getSearchPaginator { my $self = shift; my $rules = shift; - $rules->{ lineage } = $self->get("lineage"); + $rules->{ lineage } = [ $self->get("lineage") ]; my $search = WebGUI::Search->new( $self->session ); $search->search( $rules ); - my $paginator = $search->getPaginatorResultSet( $self->getUrl('func=search') ); + my $paginator = $search->getPaginatorResultSet( $rules->{url} ); return $paginator; } @@ -451,7 +600,7 @@ classes of files inside of a Gallery. =cut -sub getTemplateEditFile { +sub getTemplateIdEditFile { my $self = shift; return $self->get("templateIdEditFile"); } @@ -467,37 +616,25 @@ Gets a hash reference of vars common to all templates. sub getTemplateVars { my $self = shift; my $var = $self->get; + + # Add the search form variables + $self->appendTemplateVarsSearchForm( $var ); + + $var->{ url } = $self->getUrl; + $var->{ url_addAlbum } = $self->getUrl('func=add;class=WebGUI::Asset::Wobject::GalleryAlbum'); + $var->{ url_listAlbums } = $self->getUrl('func=listAlbums'); + $var->{ url_listAlbumsRss } = $self->getUrl('func=listAlbumsRss'); + $var->{ url_listFilesForCurrentUser } = $self->getUrl('func=listFilesForUser'); + $var->{ url_search } = $self->getUrl('func=search'); + + $var->{ canEdit } = $self->canEdit; + $var->{ canAddFile } = $self->canAddFile; return $var; } #---------------------------------------------------------------------------- -=head2 getUserFileIds ( [userId] ) - -Gets an array reference of assetIds for the files in this Gallery owned by -the specified C. If userId is not defined, will use the current user. - -=cut - -sub getUserFileIds { - my $self = shift; - my $userId = shift || $self->session->user->userId; - - my $db = $self->session->db; - - # Note: We use excludeClasses to avoid getting GalleryAlbum assets - my $assetIds - = $self->getLineage( ['descendants'], { - excludeClasses => [ 'WebGUI::Asset::Wobject::GalleryAlbum' ], - whereClause => "ownerUserId = " . $db->quote($userId), - }); - - return $assetIds; -} - -#---------------------------------------------------------------------------- - =head2 getUserAlbumIds ( [userId] ) Gets an array reference of assetIds for the GalleryAlbums in this Gallery @@ -523,6 +660,55 @@ sub getUserAlbumIds { #---------------------------------------------------------------------------- +=head2 getUserFileIds ( [userId] ) + +Gets an array reference of assetIds for the files in this Gallery owned by +the specified C. If userId is not defined, will use the current user. + +=cut + +sub getUserFileIds { + my $self = shift; + my $userId = shift || $self->session->user->userId; + + my $db = $self->session->db; + + # Note: We use excludeClasses to avoid getting GalleryAlbum assets + my $assetIds + = $self->getLineage( ['descendants'], { + excludeClasses => [ 'WebGUI::Asset::Wobject::GalleryAlbum' ], + whereClause => "ownerUserId = " . $db->quote($userId), + }); + + return $assetIds; +} + +#---------------------------------------------------------------------------- + +=head2 getUserFilePaginator ( options ) + +Gets a WebGUI::Paginator for the files owned by a specific C. +C is a hash reference of options with the following keys: + + userId => The user who owns the asset. Defaults to the current user. + url => The URL to give to the paginator + +=cut + +sub getUserFilePaginator { + my $self = shift; + my $options = shift; + my $userId = delete $options->{userId}; + my $url = delete $options->{url}; + + my $p = WebGUI::Paginator->new( $self->session, $url ); + $p->setDataByArrayRef( $self->getUserFileIds( $userId ) ); + + return $p; +} + +#---------------------------------------------------------------------------- + =head2 prepareView ( ) See WebGUI::Asset::prepareView() for details. @@ -532,7 +718,30 @@ See WebGUI::Asset::prepareView() for details. sub prepareView { my $self = shift; $self->SUPER::prepareView(); - my $template = WebGUI::Asset::Template->new($self->session, $self->get("templateId")); + + if ( $self->get("viewDefault") eq "album" ) { + my $asset + = WebGUI::Asset->newByDynamicClass( $self->session, $self->get("viewAlbumAssetId") ); + $asset->prepareView; + $self->{_viewAsset} = $asset; + } + else { + $self->prepareViewListAlbums; + } +} + +#---------------------------------------------------------------------------- + +=head2 prepareViewListAlbums ( ) + +Prepare the template for listing multiple albums. + +=cut + +sub prepareViewListAlbums { + my $self = shift; + my $template + = WebGUI::Asset::Template->new($self->session, $self->get("templateIdListAlbums")); $template->prepare; $self->{_viewTemplate} = $template; } @@ -541,8 +750,7 @@ sub prepareView { =head2 view ( ) -method called by the www_view method. Returns a processed template -to be displayed within the page style. +Show the default view based on the Gallery settings. =cut @@ -551,7 +759,41 @@ sub view { my $session = $self->session; my $var = $self->get; - return $self->processTemplate($var, undef, $self->{_viewTemplate}); + if ( $self->get("viewDefault") eq "album" ) { + return $self->{_viewAsset}->view; + } + else { + return $self->view_listAlbums; + } +} + +#---------------------------------------------------------------------------- + +=head2 view_listAlbums ( ) + +Show a paginated list of the albums in this gallery. This method does the +actual work. + +=cut + +sub view_listAlbums { + my $self = shift; + my $session = $self->session; + my $var = $self->getTemplateVars; + my $form = $self->session->form; + + my $p + = $self->getAlbumPaginator( { + perpage => $form->get('perpage'), + } ); + $p->appendTemplateVars( $var ); + + for my $assetId ( @{ $p->getPageData } ) { + my $asset = WebGUI::Asset->newByDynamicClass( $session, $assetId ); + push @{ $var->{albums} }, $asset->getTemplateVars; + } + + return $self->processTemplate( $var, undef, $self->{_viewTemplate} ); } #---------------------------------------------------------------------------- @@ -565,30 +807,218 @@ Show a paginated list of the albums in this gallery. sub www_listAlbums { my $self = shift; + # Perform the prepareView ourselves + $self->prepareViewListAlbums; + + return $self->processStyle( + $self->view_listAlbums + ); } #---------------------------------------------------------------------------- =head2 www_listAlbumsRss ( ) +Show an RSS feed for the albums in this gallery. + =cut +sub www_listAlbumsRss { + my $self = shift; + my $session = $self->session; + my $var = $self->getTemplateVars; + + for my $assetId ( @{ $self->getAlbumIds } ) { + my $asset = WebGUI::Asset->newByDynamicClass( $session, $assetId ); + my $assetVar = $asset->getTemplateVars; + + # Fix URLs + for my $key ( qw( url ) ) { + $assetVar->{ $key } = $self->session->url->getSiteURL . $assetVar->{ $key }; + } + + # Additional vars for RSS + $assetVar->{ rssDate } + = $session->datetime->epochToMail( $assetVar->{ creationDate } ); + + push @{ $var->{albums} }, $assetVar; + } + + $self->session->http->setMimeType('text/xml'); + return $self->processTemplate( $var, $self->get("templateIdListAlbumsRss") ); +} + #---------------------------------------------------------------------------- =head2 www_search ( ) +Search through the GalleryAlbums and files in this gallery. Show the form to +search and display the results if necessary. + =cut +sub www_search { + my $self = shift; + my $form = $self->session->form; + my $db = $self->session->db; + + my $var = $self->getTemplateVars; + # NOTE: Search form is added as part of getTemplateVars() + + # Get search results, if necessary. + if ($form->get("submit")) { + # Keywords to search on + my $keywords = join " ", $form->get('basicSearch'), + $form->get('keywords'), + $form->get('title'), + $form->get('description') + ; + + # Build a where clause from the advanced options + # Lineage search can capture gallery + my $where = q{assetIndex.assetId <> '} . $self->getId . q{'}; + if ( $form->get("title") ) { + $where .= q{ AND assetData.title LIKE } + . $db->quote( '%' . $form->get("title") . '%' ) + ; + } + if ( $form->get("description") ) { + $where .= q{ AND assetData.synopsis LIKE } + . $db->quote( '%' . $form->get("description") . '%' ) + ; + } + if ( $form->get("className") ) { + $where .= q{ AND asset.className IN ('} + . $db->quoteAndJoin( [$form->get('className','checkList')] ) + . q{)} + ; + } + + # Build a URL for the pagination + my $url + = $self->getUrl( + 'func=search;submit=1;' + . 'basicSearch=' . $form->get('basicSearch') . ';' + . 'keywords=' . $form->get('keywords') . ';' + . 'title=' . $form->get('title') . ';' + . 'description=' . $form->get('description') . ';' + . 'className=' . $form->get('className') . ';' + . 'creationDate_after=' . $form->get('creationDate_after') . ';' + . 'creationDate_before=' . $form->get('creationDate_before') . ';' + ); + + my $p + = $self->getSearchPaginator( { + url => $url, + keywords => $keywords, + where => $where, + joinClass => ['WebGUI::Asset::Wobject::GalleryAlbum', 'WebGUI::Asset::File::Image::Photo'], + } ); + + $var->{ keywords } = $keywords; + + $p->appendTemplateVars( $var ); + for my $result ( @{ $p->getPageData } ) { + my $asset = WebGUI::Asset->newByDynamicClass( $self->session, $result->{assetId} ); + push @{ $var->{search_results} }, $asset->getTemplateVars; + } + } + + return $self->processStyle( + $self->processTemplate( $var, $self->get("templateIdSearch") ) + ); +} + #---------------------------------------------------------------------------- -=head2 www_userGallery ( ) +=head2 www_listFilesForUser ( ) + +Show all the GalleryAlbums and files owned by a given userId. If no userId is +given, will use the current user. =cut +sub www_listFilesForUser { + my $self = shift; + my $session = $self->session; + my $var = $self->getTemplateVars; + my $userId = $self->session->form->get("userId") || $self->session->user->userId; + my $user = WebGUI::User->new( $session, $userId ); + + $var->{ url_rss } = $self->getUrl('func=listFilesForUserRss;userId=' . $userId); + $var->{ userId } = $userId; + $var->{ username } = $user->username; + + # Get all the albums + my $albumIds = $self->getUserAlbumIds( $userId ); + for my $albumId ( @$albumIds ) { + my $asset = WebGUI::Asset->newByDynamicClass( $session, $albumId ); + push @{ $var->{user_albums} }, $asset->getTemplateVars; + } + + # Get a page of files + my $p + = $self->getUserFilePaginator({ + userId => $userId, + url => $self->getUrl("func=listFilesForUser") + }); + $p->appendTemplateVars( $var ); + + for my $fileId ( @{ $p->getPageData } ) { + my $asset = WebGUI::Asset->newByDynamicClass( $session, $fileId ); + push @{ $var->{user_files} }, $asset->getTemplateVars; + } + + return $self->processStyle( + $self->processTemplate( $var, $self->get("templateIdListFilesForUser") ) + ); +} + #---------------------------------------------------------------------------- -=head2 www_userGalleryRss ( ) +=head2 www_listFilesForUserRss ( ) =cut +sub www_listFilesForUserRss { + my $self = shift; + my $session = $self->session; + my $var = $self->getTemplateVars; + my $userId = $self->session->form("userId") || $self->session->user->userId; + + # Fix URLs for template vars + for my $key ( qw( url ) ) { + $var->{ $key } = $self->session->url->getSiteURL . $var->{ $key }; + } + + # Get all the albums + my $albumIds = $self->getUserAlbumIds( $userId ); + for my $albumId ( @$albumIds ) { + my $asset = WebGUI::Asset->newByDynamicClass( $session, $albumId ); + my $assetVar = $asset->getTemplateVars; + + for my $key ( qw( url ) ) { + $assetVar->{ $key } = $self->session->url->getSiteURL . $assetVar->{ $key }; + } + + push @{ $var->{user_albums} }, $assetVar; + } + + # Get all the files + my $fileIds = $self->getUserFileIds( $userId ); + for my $fileId ( @$fileIds ) { + my $asset = WebGUI::Asset->newByDynamicClass( $session, $fileId ); + my $assetVar = $asset->getTemplateVars; + + for my $key ( qw( url ) ) { + $assetVar->{ $key } = $self->session->url->getSiteURL . $assetVar->{ $key }; + } + + push @{ $var->{user_files} }, $assetVar; + } + + $self->session->http->setMimeType('text/xml'); + return $self->processTemplate( $var, $self->get("templateIdListFilesForUserRss") ); +} + 1; diff --git a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm index 94ab45b36..2f62447e3 100644 --- a/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm +++ b/lib/WebGUI/Asset/Wobject/GalleryAlbum.pm @@ -14,6 +14,10 @@ $VERSION = "1.0.0"; use strict; use base 'WebGUI::Asset::Wobject'; +use Carp qw( croak ); +use File::Find; +use File::Spec; +use File::Temp qw{ tempdir }; use Tie::IxHash; use WebGUI::International; use WebGUI::Utility; @@ -47,22 +51,22 @@ sub definition { tie my %properties, 'Tie::IxHash', ( allowComments => { fieldType => "yesNo", - defaultValue => 0, - label => $i18n->get("allowComments label"), - hoverHelp => $i18n->get("allowComments description"), + defaultValue => 1, }, othersCanAdd => { fieldType => "yesNo", defaultValue => 0, - label => $i18n->get("othersCanAdd label"), - hoverHelp => $i18n->get("othersCanAdd description"), + }, + assetIdThumbnail => { + fieldType => "asset", + defaultValue => undef, }, ); push @{$definition}, { assetName => $i18n->get('assetName'), + autoGenerateForms => 0, icon => 'newWobject.gif', - autoGenerateForms => 1, tableName => 'GalleryAlbum', className => __PACKAGE__, properties => \%properties, @@ -90,43 +94,56 @@ sub addArchive { my $self = shift; my $filename = shift; my $properties = shift; + my $gallery = $self->getParent; my $archive = Archive::Any->new( $filename ); croak "Archive will extract to directory outside of storage location!" if $archive->is_naughty; - use File::Temp qw{ tempdir }; my $tempdirName = tempdir( "WebGUI-Gallery-XXXXXXXX", TMPDIR => 1, CLEANUP => 1); - $archive->extract( $tempdir ); + $archive->extract( $tempdirName ); - opendir my $dh, $tempdirName or die "Could not open temp dir $tempdirName: $!"; - for my $file (readdir $dh) { - my $class = $gallery->getAssetClassForFile( $file ); + # Get all the files in the archive + my @files; + my $wanted = sub { push @files, $File::Find::name }; + find( { + wanted => $wanted, + }, $tempdirName ); + + for my $filePath (@files) { + my ($volume, $directory, $filename) = File::Spec->splitpath( $filePath ); + $self->session->errorHandler->info( "trying $filename" ); + next if $filename =~ m{^[.]}; + my $class = $gallery->getAssetClassForFile( $filePath ); next unless $class; # class is undef for those files the Gallery can't handle - $self->addChild({ - className => $class, - title => $properties->{title}, - menuTitle => $properties->{menuTitle} || $properties->{title}, - synopsis => $properties->{synopsis}, - }); + $self->session->errorHandler->info( "Adding $filename to album!" ); + $properties->{ className } = $class; + $properties->{ menuTitle } = $filename; + $properties->{ title } = $filename; + $properties->{ url } = $self->getUrl . "/" . $filename; + + my $asset = $self->addChild( $properties, undef, undef, { skipAutoCommitWorkflows => 1 } ); + $asset->setFile( $filePath ); } - closedir $dh; + + my $versionTag = WebGUI::VersionTag->getWorking( $self->session ); + $versionTag->set({ + "workflowId" => $self->getParent->get("workflowIdCommit"), + }); + $versionTag->requestCommit; + + return; } #---------------------------------------------------------------------------- -=head2 appendTemplateVarsFileLoop ( vars, options ) +=head2 appendTemplateVarsFileLoop ( vars, assetIds ) -Append template vars for a file loop with the specified options. C is -a hash reference to add the file loop to. C is a hash reference of -options with the following keys: - - perpage => number | "all" - If "all", no pagination will be done. - url => url - The URL to the current page +Append template vars for a file loop for the specified assetIds. C is +a hash reference to add the file loop to. C is an array reference +of assetIds for the loop. Returns the hash reference for convenience. @@ -135,17 +152,10 @@ Returns the hash reference for convenience. sub appendTemplateVarsFileLoop { my $self = shift; my $var = shift; - my $options = shift; + my $assetIds = shift; + my $session = $self->session; - my @assetIds; - if ($options->{perpage} eq "all") { - @assetIds = @{ $self->getFileIds }; - } - else { - @assetIds = @{ $self->getFilePaginator($options->{url})->getPageData }; - } - - for my $assetId (@assetIds) { + for my $assetId (@$assetIds) { push @{$var->{file_loop}}, WebGUI::Asset->newByDynamicClass($session, $assetId)->getTemplateVars; } @@ -168,6 +178,7 @@ C is true and the Gallery allows them to add files. sub canAddFile { my $self = shift; my $userId = shift || $self->session->user->userId; + my $gallery = $self->getParent; return 1 if $userId eq $self->get("ownerUserId"); return 1 if $self->get("othersCanAdd") && $gallery->canAddFile( $userId ); @@ -214,11 +225,15 @@ sub canEdit { my $self = shift; my $userId = shift || $self->session->user->userId; my $gallery = $self->getParent; + my $form = $self->session->form; # Handle adding a photo if ( $form->get("func") eq "add" ) { return $self->canAddFile; } + elsif ( $form->get("func") eq "editSave" && $form->get("className") eq __PACKAGE__ ) { + return $self->canAddFile; + } else { return 1 if $userId eq $self->get("ownerUserId"); @@ -233,13 +248,23 @@ sub canEdit { Returns true if the user can view this asset. C is a WebGUI user ID. If no userId is given, checks the current user. +Users can view this album if they can view the containing Gallery. + +NOTE: It may be possible to view a GalleryAlbum that has no public files. In +such cases, the GalleryAlbum will appear empty to unprivileged users. This is +not a bug. + =cut -# Inherited from superclass +sub canView { + my $self = shift; + my $userId = shift || $self->session->user->userId; + return $self->getParent->canView($userId); +} #---------------------------------------------------------------------------- -=head2 i18n ( [ session ] ) +=head2 i18n ( session ) Get a WebGUI::International object for this class. @@ -260,6 +285,19 @@ sub i18n { #---------------------------------------------------------------------------- +=head2 getAutoCommitWorkflowId ( ) + +Returns the workflowId of the Gallery's approval workflow. + +=cut + +sub getAutoCommitWorkflowId { + my $self = shift; + return $self->getParent->get("workflowIdCommit"); +} + +#---------------------------------------------------------------------------- + =head2 getFileIds ( ) Gets an array reference of asset IDs for all the files in this album. @@ -270,9 +308,7 @@ sub getFileIds { my $self = shift; my $gallery = $self->getParent; - return $self->assetLineage( ['descendants'], { - includeOnlyClasses => $gallery->getAllAssetClassesForFile, - }); + return $self->getLineage( ['descendants'], { } ); } #---------------------------------------------------------------------------- @@ -286,7 +322,7 @@ url to the current page that will be given to the paginator. sub getFilePaginator { my $self = shift; - my $url = shift; + my $url = shift || $self->getUrl; my $p = WebGUI::Paginator->new( $self->session, $url ); $p->setDataByArrayRef( $self->getFileIds ); @@ -304,15 +340,77 @@ Gets template vars common to all views. sub getTemplateVars { my $self = shift; + my $session = $self->session; + my $gallery = $self->getParent; my $var = $self->get; + my $owner = WebGUI::User->new( $session, $self->get("ownerUserId") ); + + # Permissions + $var->{ canAddFile } = $self->canAddFile; + $var->{ canEdit } = $self->canEdit; + + # Add some common template vars from Gallery + $gallery->appendTemplateVarsSearchForm( $var ); + $var->{ url_listAlbums } = $gallery->getUrl('func=listAlbums'); + $var->{ url_listAlbumsRss } = $gallery->getUrl('func=listAlbumsRss'); + $var->{ url_listFilesForCurrentUser } = $gallery->getUrl('func=listFilesForUser'); + $var->{ url_search } = $gallery->getUrl('func=search'); - $var->{ url } = $self->getUrl; + # Friendly URLs + $var->{ url } = $self->getUrl; + $var->{ url_addArchive } = $self->getUrl('func=addArchive'); + $var->{ url_addPhoto } = $self->getUrl("func=add;class=WebGUI::Asset::File::Image::Photo"); + $var->{ url_addNoClass } = $self->getUrl("func=add"); + $var->{ url_delete } = $self->getUrl("func=delete"); + $var->{ url_edit } = $self->getUrl("func=edit"); + $var->{ url_listFilesForOwner } = $gallery->getUrl("func=listFilesForUser;userId=".$var->{ownerUserId}); + $var->{ url_viewRss } = $self->getUrl("func=viewRss"); + $var->{ url_slideshow } = $self->getUrl("func=slideshow"); + $var->{ url_thumbnails } = $self->getUrl("func=thumbnails"); + + $var->{ fileCount } = $self->getChildCount; + $var->{ ownerUsername } = $owner->username; + $var->{ thumbnailUrl } = $self->getThumbnailUrl; return $var; } #---------------------------------------------------------------------------- +=head2 getThumbnailUrl ( ) + +Gets the URL for the thumbnail for this asset. If no asset is set, gets the +first child. + +NOTE: If the asset does not have a getThumbnailUrl method, this method will +return undef. + +=cut + +sub getThumbnailUrl { + my $self = shift; + my $asset = undef; + + if ( $self->get("assetIdThumbnail") ) { + $asset = WebGUI::Asset->newByDynamicClass( $self->session, $self->get("assetIdThumbnail") ); + } + elsif ( $self->getFirstChild ) { + $asset = $self->getFirstChild; + } + else { + return undef; + } + + if ( $asset->can("getThumbnailUrl") ) { + return $asset->getThumbnailUrl; + } + else { + return undef; + } +} + +#---------------------------------------------------------------------------- + =head2 othersCanAdd ( ) Returns true if people other than the owner can add files to this album. @@ -360,6 +458,26 @@ sub processStyle { #---------------------------------------------------------------------------- +=head2 processPropertiesFromFormPost ( ) + +Process the form to save the asset. Request approval from the Gallery's +approval workflow. + +=cut + +sub processPropertiesFromFormPost { + my $self = shift; + my $errors = $self->SUPER::processPropertiesFromFormPost || []; + + # Return if error + return $errors if @$errors; + + # Passes all checks + $self->requestAutoCommit; +} + +#---------------------------------------------------------------------------- + =head2 view ( ) method called by the www_view method. Returns a processed template @@ -371,8 +489,10 @@ sub view { my $self = shift; my $session = $self->session; my $var = $self->getTemplateVars; - - $self->appendTemplateVarsFileLoop( $var ); + + my $p = $self->getFilePaginator; + $p->appendTemplateVars( $var ); + $self->appendTemplateVarsFileLoop( $var, $p->getPageData ); return $self->processTemplate($var, undef, $self->{_viewTemplate}); } @@ -384,6 +504,8 @@ sub view { method called by the www_slideshow method. Returns a processed template to be displayed within the page style. +Show a slideshow of the GalleryAlbum's files. + =cut sub view_slideshow { @@ -391,9 +513,9 @@ sub view_slideshow { my $session = $self->session; my $var = $self->getTemplateVars; - $self->appendTemplateVarsFileLoop( $var, { perpage => "all" } ); + $self->appendTemplateVarsFileLoop( $var, $self->getFileIds ); - return $self->processTemplate($var, $self->getParent->get("templateIdSlideshow")); + return $self->processTemplate($var, $self->getParent->get("templateIdViewSlideshow")); } #---------------------------------------------------------------------------- @@ -403,6 +525,9 @@ sub view_slideshow { method called by the www_thumbnails method. Returns a processed template to be displayed within the page style. +Shows all the thumbnails for this GalleryAlbum. In addition, shows details +about a specific thumbnail. + =cut sub view_thumbnails { @@ -410,9 +535,31 @@ sub view_thumbnails { my $session = $self->session; my $var = $self->getTemplateVars; - $self->appendTemplateVarsFileLoop( $var, { perpage => "all" } ); + my $fileId = $session->form->get("fileId"); - return $self->processTemplate($var, $self->getParent->get("templateIdThumbnails")); + $self->appendTemplateVarsFileLoop( $var, $self->getFileIds ); + + # Process the file loop to add an additional URL + for my $file ( @{ $var->{file_loop} } ) { + $file->{ url_albumViewThumbnails } + = $self->getUrl('func=thumbnails;fileId=' . $file->{assetId}); + } + + # Add direct vars for the requested file + my $asset; + if ($fileId) { + $asset = WebGUI::Asset->newByDynamicClass( $session, $fileId ); + } + # If no fileId given or fileId does not exist + if (!$asset) { + $asset = $self->getFirstChild; + } + my %assetVars = %{ $asset->getTemplateVars }; + for my $key ( keys %assetVars ) { + $var->{ 'file_' . $key } = $assetVars{ $key }; + } + + return $self->processTemplate($var, $self->getParent->get("templateIdViewThumbnails")); } #---------------------------------------------------------------------------- @@ -428,8 +575,42 @@ sub www_addArchive { return $self->session->privilege->insufficient unless $self->canAddFile; + my $session = $self->session; + my $form = $self->session->form; my $var = $self->getTemplateVars; + $var->{ form_start } + = WebGUI::Form::formHeader( $session, { + action => $self->getUrl('func=addArchiveSave'), + }); + $var->{ form_end } + = WebGUI::Form::formFooter( $session ); + + $var->{ form_submit } + = WebGUI::Form::submit( $session, { + name => "submit", + value => "Submit", + }); + + $var->{ form_archive } + = WebGUI::Form::File( $session, { + name => "archive", + maxAttachments => 1, + value => ( $form->get("archive") ), + }); + + $var->{ form_keywords } + = WebGUI::Form::text( $session, { + name => "keywords", + value => ( $form->get("keywords") ), + }); + + $var->{ form_friendsOnly } + = WebGUI::Form::yesNo( $session, { + name => "friendsOnly", + value => ( $form->get("friendsOnly") ), + }); + return $self->processStyle( $self->processTemplate($var, $self->getParent->get("templateIdAddArchive")) ); @@ -446,20 +627,27 @@ Process the form for adding an archive. sub www_addArchiveSave { my $self = shift; - return $self->session->privilege->insufficient unless $self->canAddfile; + return $self->session->privilege->insufficient unless $self->canAddFile; + my $session = $self->session; my $form = $self->session->form; + my $i18n = __PACKAGE__->i18n( $session ); my $properties = { keywords => $form->get("keywords"), friendsOnly => $form->get("friendsOnly"), }; - my $storage = $form->get("archive", "File"); - my $filename = $storage->getFilePath( $storage->getFiles->[0] ); + my $storageId = $form->get("archive", "File"); + my $storage = WebGUI::Storage->get( $session, $storageId ); + my $filename = $storage->getPath( $storage->getFiles->[0] ); $self->addArchive( $filename, $properties ); - return $self->www_view; + $storage->delete; + + return $self->processStyle( + sprintf $i18n->get('addArchive message'), $self->getUrl, + ); } #----------------------------------------------------------------------------- @@ -476,7 +664,7 @@ sub www_delete { return $self->session->privilege->insufficient unless $self->canEdit; my $var = $self->getTemplateVars; - $var->{ url_yes } = $self->getUrl("?func=deleteConfirm"); + $var->{ url_yes } = $self->getUrl("func=deleteConfirm"); return $self->processStyle( $self->processTemplate( $var, $self->getParent->get("templateIdDeleteAlbum") ) @@ -496,9 +684,130 @@ sub www_deleteConfirm { return $self->session->privilege->insufficient unless $self->canEdit; + my $gallery = $self->getParent; + $self->purge; - return $self->getParent->www_view; + return $gallery->www_view; +} + +#---------------------------------------------------------------------------- + +=head2 www_edit ( ) + +Show the form to add / edit a GalleryAlbum asset. + +=cut + +sub www_edit { + my $self = shift; + my $session = $self->session; + my $form = $self->session->form; + my $var = $self->getTemplateVars; + my $i18n = __PACKAGE__->i18n($session); + + # Generate the form + if ($form->get("func") eq "add") { + $var->{ form_start } + = WebGUI::Form::formHeader( $session, { + action => $self->getParent->getUrl('func=editSave;assetId=new;class='.__PACKAGE__), + }); + } + else { + $var->{ form_start } + = WebGUI::Form::formHeader( $session, { + action => $self->getUrl('func=editSave'), + }); + } + $var->{ form_start } + .= WebGUI::Form::hidden( $session, { + name => "proceed", + value => "showConfirmation", + }); + + $var->{ form_end } + = WebGUI::Form::formFooter( $session ); + + $var->{ form_cancel } + = WebGUI::Form::button( $session, { + name => "cancel", + value => $i18n->get("cancel"), + extras => 'onclick="history.go(-1)"', + }); + + $var->{ form_submit } + = WebGUI::Form::submit( $session, { + name => "save", + value => $i18n->get("save"), + }); + + $var->{ form_title } + = WebGUI::Form::text( $session, { + name => "title", + value => $form->get("title") || $self->get("title"), + }); + + $var->{ form_description } + = WebGUI::Form::HTMLArea( $session, { + name => "description", + value => $form->get("description") || $self->get("description"), + }); + + # Generate the file loop + my $thumbnailUrl = $self->getThumbnailUrl; + $self->appendTemplateVarsFileLoop( $var, $self->getFileIds ); + for my $file ( @{ $var->{file_loop} } ) { + if ( $thumbnailUrl eq $file->{thumbnailUrl} ) { + $file->{ isAlbumThumbnail } = 1; + } + } + + return $self->processStyle( + $self->processTemplate( $var, $self->getParent->get("templateIdEditAlbum") ) + ); +} + +#----------------------------------------------------------------------------- + +=head2 www_editSave ( ) + +Save the asset edit form. Overridden to give a nice message when a photo or +album is added + +=cut + +sub www_editSave { + my $self = shift; + my $form = $self->session->form; + my $i18n = __PACKAGE__->i18n($self->session); + $self->SUPER::www_editSave; + + if ( $form->get("assetId") eq "new" ) { + return sprintf $i18n->get("addFile message"), $self->getUrl, + } + else { + return sprintf $i18n->get("save message"), $self->getUrl, + } +} + +#---------------------------------------------------------------------------- + +=head2 www_showConfirmation ( ) + +Shows the confirmation message after adding / editing a gallery album. +Provides links to view the album. + +=cut + +sub www_showConfirmation { + my $self = shift; + my $i18n = __PACKAGE__->i18n( $self->session ); + + my $output = sprintf $i18n->get('save message'), $self->getUrl; + + return $self->processStyle( + $output + ); } #----------------------------------------------------------------------------- @@ -547,7 +856,27 @@ sub www_viewRss { return $self->session->privilege->insufficient unless $self->canView; + my $var = $self->getTemplateVars; + $self->appendTemplateVarsFileLoop( $var, $self->getFileIds ); + # Fix URLs to be full URLs + for my $key ( qw( url url_viewRss ) ) { + $var->{ $key } = $self->session->url->getSiteURL . $var->{ $key }; + } + + # Process the file loop to add additional params + for my $file ( @{ $var->{file_loop} } ) { + # Fix URLs to be full URLs + for my $key ( qw( url ) ) { + $file->{ $key } = $self->session->url->getSiteURL . $file->{$key}; + } + + $file->{ rssDate } + = $self->session->datetime->epochToMail( $file->{creationDate} ); + } + + $self->session->http->setMimeType('text/xml'); + return $self->processTemplate( $var, $self->getParent->get('templateIdViewAlbumRss') ); } 1; diff --git a/lib/WebGUI/AssetLineage.pm b/lib/WebGUI/AssetLineage.pm index b5c37f4c1..373eef46e 100644 --- a/lib/WebGUI/AssetLineage.pm +++ b/lib/WebGUI/AssetLineage.pm @@ -15,6 +15,7 @@ package WebGUI::Asset; =cut use strict; +use Carp qw( croak ); =head1 NAME @@ -82,9 +83,9 @@ sub addChild { $self->session->db->commit; $properties->{assetId} = $id; $properties->{parentId} = $self->getId; - my $temp = WebGUI::Asset->newByPropertyHashRef($self->session,$properties); + my $temp = WebGUI::Asset->newByPropertyHashRef($self->session,$properties) || croak "Couldn't create a new $properties->{className} asset!"; $temp->{_parent} = $self; - my $newAsset = $temp->addRevision($properties,$now, {skipAutoCommitWorkflows=>$options->{skipAutoCommitWorkflows}}); + my $newAsset = $temp->addRevision($properties, $now, $options); $self->updateHistory("added child ".$id); $self->session->http->setStatus(201,"Asset Creation Successful"); return $newAsset; diff --git a/lib/WebGUI/AssetPackage.pm b/lib/WebGUI/AssetPackage.pm index 983acce7e..5c505b36a 100644 --- a/lib/WebGUI/AssetPackage.pm +++ b/lib/WebGUI/AssetPackage.pm @@ -125,18 +125,22 @@ A hash reference containing the exported data. =cut sub importAssetData { - my $self = shift; - my $data = shift; - my $error = $self->session->errorHandler; - my $id = $data->{properties}{assetId}; - my $class = $data->{properties}{className}; - my $version = $data->{properties}{revisionDate}; + my $self = shift; + my $data = shift; + my $error = $self->session->errorHandler; + my $id = $data->{properties}{assetId}; + my $class = $data->{properties}{className}; + my $version = $data->{properties}{revisionDate}; + + # Load the class + WebGUI::Asset->loadModule( $self->session, $class ); + my $asset; - my $assetExists = WebGUI::Asset->assetExists($self->session, $id, $class, $version); - if ($assetExists) { # update an existing revision + my $assetExists = WebGUI::Asset->assetExists($self->session, $id, $class, $version); + if ($assetExists) { # update an existing revision $asset = WebGUI::Asset->new($self->session, $id, $class, $version); - $error->info("Updating an existing revision of asset $id"); - $asset->update($data->{properties}); + $error->info("Updating an existing revision of asset $id"); + $asset->update($data->{properties}); ##Pending assets are assigned a new version tag if ($data->{properties}->{status} eq 'pending') { $self->session->db->write( @@ -144,19 +148,19 @@ sub importAssetData { [WebGUI::VersionTag->getWorking($self->session)->getId, $data->{properties}->{assetId}] ); } - } + } else { - $asset = WebGUI::Asset->new($self->session, $id, $class); - if (defined $asset) { # create a new revision of an existing asset - $error->info("Creating a new revision of asset $id"); - $asset = $asset->addRevision($data->{properties}, $version, {skipAutoCommitWorkflows => 1}); - } + $asset = WebGUI::Asset->new($self->session, $id, $class); + if (defined $asset) { # create a new revision of an existing asset + $error->info("Creating a new revision of asset $id"); + $asset = $asset->addRevision($data->{properties}, $version, {skipAutoCommitWorkflows => 1}); + } else { # add an entirely new asset - $error->info("Adding $id that didn't previously exist."); - $asset = $self->addChild($data->{properties}, $id, $version, {skipAutoCommitWorkflows => 1}); - } - } - return $asset; + $error->info("Adding $id that didn't previously exist."); + $asset = $self->addChild($data->{properties}, $id, $version, {skipAutoCommitWorkflows => 1}); + } + } + return $asset; } #------------------------------------------------------------------- @@ -188,40 +192,40 @@ A reference to a WebGUI::Storage object that contains a webgui package file. =cut sub importPackage { - my $self = shift; - my $storage = shift; - my $decompressed = $storage->untar($storage->getFiles->[0]); - my %assets = (); - my $error = $self->session->errorHandler; - $error->info("Importing package."); - foreach my $file (sort(@{$decompressed->getFiles})) { - next unless ($decompressed->getFileExtension($file) eq "json"); - $error->info("Found data file $file"); - my $data = eval{ + my $self = shift; + my $storage = shift; + my $decompressed = $storage->untar($storage->getFiles->[0]); + my %assets = (); + my $error = $self->session->errorHandler; + $error->info("Importing package."); + foreach my $file (sort(@{$decompressed->getFiles})) { + next unless ($decompressed->getFileExtension($file) eq "json"); + $error->info("Found data file $file"); + my $data = eval{ local $JSON::UnMapping = 1; JSON::jsonToObj($decompressed->getFileContentsAsScalar($file)) }; - if ($@ || $data->{properties}{assetId} eq "" || $data->{properties}{className} eq "" || $data->{properties}{revisionDate} eq "") { - $error->error("package corruption: ".$@) if ($@); - return "corrupt"; - } - $error->info("Data file $file is valid and represents asset ".$data->{properties}{assetId}); - foreach my $storageId (@{$data->{storage}}) { - my $assetStorage = WebGUI::Storage->get($self->session, $storageId); - $decompressed->untar($storageId.".storage", $assetStorage); - } - my $asset = $assets{$data->{properties}{parentId}} || $self; - my $newAsset = $asset->importAssetData($data); + if ($@ || $data->{properties}{assetId} eq "" || $data->{properties}{className} eq "" || $data->{properties}{revisionDate} eq "") { + $error->error("package corruption: ".$@) if ($@); + return "corrupt"; + } + $error->info("Data file $file is valid and represents asset ".$data->{properties}{assetId}); + foreach my $storageId (@{$data->{storage}}) { + my $assetStorage = WebGUI::Storage->get($self->session, $storageId); + $decompressed->untar($storageId.".storage", $assetStorage); + } + my $asset = $assets{$data->{properties}{parentId}} || $self; + my $newAsset = $asset->importAssetData($data); $newAsset->importAssetCollateralData($data); - $assets{$newAsset->getId} = $newAsset; - } - if ($self->session->setting->get("autoRequestCommit")) { + $assets{$newAsset->getId} = $newAsset; + } + if ($self->session->setting->get("autoRequestCommit")) { if ($self->session->setting->get("skipCommitComments")) { WebGUI::VersionTag->getWorking($self->session)->requestCommit; } else { - $self->session->http->setRedirect($self->getUrl("op=commitVersionTag;tagId=".WebGUI::VersionTag->getWorking($self->session)->getId)); + $self->session->http->setRedirect($self->getUrl("op=commitVersionTag;tagId=".WebGUI::VersionTag->getWorking($self->session)->getId)); } - } + } return undef; } diff --git a/lib/WebGUI/AssetVersioning.pm b/lib/WebGUI/AssetVersioning.pm index 023cc8ecd..ce3c5052e 100644 --- a/lib/WebGUI/AssetVersioning.pm +++ b/lib/WebGUI/AssetVersioning.pm @@ -79,12 +79,12 @@ If this is set to 1 then assets that normally send notifications will (like CS P sub addRevision { my $self = shift; my $properties = shift; - my $now = shift || $self->session->datetime->time(); - my $options = shift; - + my $now = shift || $self->session->datetime->time(); + my $options = shift; + my $autoCommitId = $self->getAutoCommitWorkflowId() unless ($options->{skipAutoCommitWorkflows}); - - my $workingTag + + my $workingTag = ($autoCommitId) ? WebGUI::VersionTag->create($self->session, {groupToUse=>'12', workflowId=>$autoCommitId}) : WebGUI::VersionTag->getWorking($self->session) @@ -104,8 +104,7 @@ sub addRevision { $self->session->user->userId, $workingTag->getId, $self->getId, - ] - ); + ]); foreach my $definition (@{$self->definition($self->session)}) { unless ($definition->{tableName} eq "assetData") { diff --git a/lib/WebGUI/Form.pm b/lib/WebGUI/Form.pm index b5b5291d6..ef552ed77 100644 --- a/lib/WebGUI/Form.pm +++ b/lib/WebGUI/Form.pm @@ -15,6 +15,8 @@ package WebGUI::Form; =cut use strict; +use Carp qw( croak ); +use Scalar::Util qw( blessed ); use Tie::IxHash; use WebGUI::Asset; use WebGUI::Asset::RichEdit; @@ -92,7 +94,7 @@ sub formFooter { #------------------------------------------------------------------- -=head2 formHeader ( session, hashRef ) +=head2 formHeader ( session, options ) Returns a form header. @@ -100,7 +102,7 @@ Returns a form header. A reference to the current session. -=head3 hashRef +=head3 options A hash reference that contains one or more of the following parameters. @@ -108,6 +110,9 @@ A hash reference that contains one or more of the following parameters. The form action. Defaults to the current page. +NOTE: If the C contains a query string (?param=value), C +will translate the parameters into hidden form elements automatically. + =head4 method The form method. Defaults to "post". @@ -118,28 +123,36 @@ The form enctype. Defaults to "multipart/form-data". =head4 extras -If you want to add anything special to the form header like javascript actions or stylesheet info, then use this. +If you want to add anything special to the form header like javascript +actions or stylesheet info, then use this. =cut sub formHeader { - my $session = shift; - my $params = shift; - my $action = $params->{action} || $session->url->page(); - my $hidden; - if ($action =~ /\?/) { - my ($path,$query) = split(/\?/,$action); - $action = $path; - my @params = split(/\;/,$query); - foreach my $param (@params) { - $param =~ s/amp;(.*)/$1/; - my ($name,$value) = split(/\=/,$param); - $hidden .= hidden($session,{name=>$name,value=>$value}); - } - } - my $method = $params->{method} || "post"; - my $enctype = $params->{enctype} || "multipart/form-data"; - return '
{extras}.'>
'.$hidden; + my $session = shift; + my $params = shift || {}; + + croak "First parameter must be WebGUI::Session object" + unless blessed $session && $session->isa( "WebGUI::Session" ); + croak "Second parameter must be hash reference" + if ref $params ne "HASH"; + + my $action = exists $params->{ action } ? $params->{ action } : $session->url->page(); + my $method = exists $params->{ method } ? $params->{ method } : "post"; + my $enctype = exists $params->{ enctype } ? $params->{ enctype } : "multipart/form-data"; + + # Fix a query string in the action URL + my $hidden; + if ($action =~ /\?/) { + ($action, my $query) = split /\?/, $action, 2; + my @params = split /[&;]/, $query; + foreach my $param ( @params ) { + my ($name, $value) = split /=/, $param; + $hidden .= hidden( $session, { name => $name, value => $value } ); + } + } + + return '{extras}.'>
'.$hidden; } diff --git a/lib/WebGUI/Form/SelectRichEditor.pm b/lib/WebGUI/Form/SelectRichEditor.pm new file mode 100644 index 000000000..1093dda31 --- /dev/null +++ b/lib/WebGUI/Form/SelectRichEditor.pm @@ -0,0 +1,101 @@ +package WebGUI::Form::SelectRichEditor; + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2007 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'WebGUI::Form::SelectBox'; +use WebGUI::International; + +=head1 NAME + +WebGUI::Form::SelectRichEditor + +=head1 DESCRIPTION + +Creates a select box to choose a Rich Text Editor asset. + +=head1 SEE ALSO + +This is a subclass of WebGUI::Form::SelectBox. + +=head1 METHODS + +The following methods are specifically available from this class. Check the superclass for additional methods. + +=cut + +#---------------------------------------------------------------------------- + +=head2 definition ( [ additionalTerms ] ) + +See the super class for additional details. + +=head3 additionalTerms + +The following additional parameters have been added via this sub class. + +=head4 defaultValue + +Defaults to the Post Rich Editor, the least-featured Rich Text Editor and the +one most likely to be selected by users of this form control. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift || []; + my $i18n = WebGUI::International->new($session); + push @{$definition}, { + formName => { + defaultValue => $i18n->get("475"), + }, + defaultValue => { + defaultValue => '', + }, + }; + return $class->SUPER::definition($session, $definition); +} + +#---------------------------------------------------------------------------- + +=head2 new + +Create a new WebGUI::Form::SelectRichEditor object and populate it with all +the available Rich Text Editor assets. + +=cut + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + + # Get all the RTEs available to this site + my $options + = $self->session->db->buildHashRef( q{ + SELECT DISTINCT(assetData.assetId), assetData.title + FROM asset, assetData + WHERE asset.className='WebGUI::Asset::RichEdit' + AND asset.assetId=assetData.assetId + ORDER BY assetData.title + }); + + $self->set( "options", $options ); + + return $self; +} + +1; + diff --git a/lib/WebGUI/Help/Asset_Gallery.pm b/lib/WebGUI/Help/Asset_Gallery.pm new file mode 100644 index 000000000..d449681f2 --- /dev/null +++ b/lib/WebGUI/Help/Asset_Gallery.pm @@ -0,0 +1,229 @@ +package WebGUI::Help::Asset_Gallery; + +our $HELP = { + 'help searchForm' => { + title => 'help searchForm title', + body => 'help searchForm body', + variables => [ + { + name => 'searchForm_start', + description => 'helpvar searchForm_start', + }, + { + name => 'searchForm_end', + description => 'helpvar searchForm_end', + }, + { + name => 'searchForm_basicSearch', + description => 'helpvar searchForm_basicSearch', + }, + { + name => 'searchForm_title', + description => 'helpvar searchForm_title', + }, + { + name => 'searchForm_description', + description => 'helpvar searchForm_description', + }, + { + name => 'searchForm_keywords', + description => 'helpvar searchForm_keywords', + }, + { + name => 'searchForm_className', + description => 'helpvar searchForm_className', + }, + { + name => 'searchForm_creationDate_after', + description => 'helpvar searchForm_creationDate_after', + }, + { + name => 'searchForm_creationDate_before', + description => 'helpvar searchForm_creationDate_before', + }, + { + name => 'searchForm_submit', + description => 'helpvar searchForm_submit', + }, + ], + }, + + 'help common' => { + title => 'help common title', + body => 'help common body', + isa => [ + { + tag => 'help searchForm', + namespace => 'Asset_Gallery', + }, + ], + variables => [ + { + name => 'url_addAlbum', + description => 'helpvar url_addAlbum', + }, + { + name => 'url_listAlbums', + description => 'helpvar url_listAlbums', + }, + { + name => 'url_listAlbumsRss', + description => 'helpvar url_listAlbumsRss', + }, + { + name => 'url_listFilesForCurrentUser', + description => 'helpvar url_listFilesForCurrentUser', + }, + { + name => 'url_search', + description => 'helpvar url_search', + }, + { + name => 'canEdit', + description => 'helpvar canEdit', + }, + { + name => 'canAddFile', + description => 'helpvar canAddFile', + }, + ], + }, + + 'help listAlbums' => { + title => 'help listAlbums title', + body => 'help listAlbums body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_Gallery', + }, + ], + variables => [ + { + name => 'albums', + description => 'helpvar albums', + }, + ], + related => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + ], + }, + + 'help listAlbumsRss' => { + title => 'help listAlbumsRss title', + body => 'help listAlbumsRss body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_Gallery', + }, + ], + variables => [ + { + name => 'albums', + description => 'helpvar albums rss', + variables => [ + { + name => 'rssDate', + description => 'helpvar rssDate', + }, + ], + }, + ], + related => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + ], + }, + + 'help search' => { + title => 'help search title', + body => 'help search body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_Gallery', + }, + ], + variables => [ + { + name => 'search_results', + description => 'helpvar search_results', + }, + ], + # All classes that can be found by a Gallery search go in here + related => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + { + tag => 'help common', + namespace => 'Asset_Photo', + }, + ], + }, + + 'help listFilesForUser' => { + title => 'help listFilesForUser title', + body => 'help listFilesForUser body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_Gallery', + }, + ], + variables => [ + { + name => 'user_albums', + description => 'helpvar user_albums', + }, + { + name => 'user_files', + description => 'helpvar user_files', + }, + { + name => 'userId', + description => 'helpvar userId', + }, + { + name => 'url_rss', + description => 'helpvar url_rss', + }, + { + name => 'username', + description => 'helpvar username', + }, + ], + # All classes that can be found by a Gallery search go in here + related => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + { + tag => 'help common', + namespace => 'Asset_Photo', + }, + ], + }, + + 'help listFilesForUserRss' => { + title => 'help listFilesForUserRss title', + body => 'help listFilesForUserRss body', + isa => [ + { + tag => 'help listFilesForUser', + namespace => 'Asset_Gallery', + }, + ], + }, + +}; + +1; diff --git a/lib/WebGUI/Help/Asset_GalleryAlbum.pm b/lib/WebGUI/Help/Asset_GalleryAlbum.pm new file mode 100644 index 000000000..25cc4598a --- /dev/null +++ b/lib/WebGUI/Help/Asset_GalleryAlbum.pm @@ -0,0 +1,309 @@ +package WebGUI::Help::Asset_GalleryAlbum; + +our $HELP = { + + 'help common' => { + title => 'help common title', + body => 'help common body', + isa => [ + { + tag => 'help searchForm', + namespace => 'Asset_Gallery', + }, + ], + variables => [ + { + name => 'canAddFile', + description => 'helpvar canAddFile', + }, + { + name => 'canEdit', + description => 'helpvar canEdit', + }, + { + name => 'url_listAlbums', + description => 'helpvar url_listAlbums', + }, + { + name => 'url_listAlbumsRss', + description => 'helpvar url_listAlbumsRss', + }, + { + name => 'url_listFilesForCurrentUser', + description => 'helpvar url_listFilesForCurrentUser', + }, + { + name => 'url_search', + description => 'helpvar url_search', + }, + { + name => 'url_addArchive', + description => 'helpvar url_addArchive', + }, + { + name => 'url_addPhoto', + description => 'helpvar url_addPhoto', + }, + { + name => 'url_addNoClass', + description => 'helpvar url_addNoClass', + }, + { + name => 'url_delete', + description => 'helpvar url_delete', + }, + { + name => 'url_edit', + description => 'helpvar url_edit', + }, + { + name => 'url_listFilesForOwner', + description => 'helpvar url_listFilesForOwner', + }, + { + name => 'url_viewRss', + description => 'helpvar url_viewRss', + }, + { + name => 'url_slideshow', + description => 'helpvar url_slideshow', + }, + { + name => 'url_thumbnails', + description => 'helpvar url_thumbnails', + }, + { + name => 'fileCount', + description => 'helpvar fileCount', + }, + { + name => 'ownerUsername', + description => 'helpvar ownerUsername', + }, + { + name => 'thumbnailUrl', + description => 'helpvar thumbnailUrl', + }, + ], + }, + + 'help fileLoop' => { + title => 'help fileLoop title', + body => 'help fileLoop body', + variables => [ + { + name => 'file_loop', + description => 'helpvar file_loop', + }, + ], + + # ADD ALL GalleryAlbum FILE CLASSES HERE!!! + related => [ + { + tag => 'help common', + namespace => 'Asset_Photo', + }, + ], + }, + + 'help view' => { + title => 'help view title', + body => 'help view body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + { + tag => 'help fileLoop', + namespace => 'Asset_GalleryAlbum', + }, + ], + }, + + 'help slideshow' => { + title => 'help slideshow title', + body => 'help slideshow body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + { + tag => 'help fileLoop', + namespace => 'Asset_GalleryAlbum', + }, + ], + }, + + 'help thumbnails' => { + title => 'help thumbnails title', + body => 'help thumbnails body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + { + tag => 'help fileLoop', + namespace => 'Asset_GalleryAlbum', + }, + ], + + variables => [ + { + name => 'file_*', + description => 'helpvar file_*', + }, + ], + + # PUT ALL GalleryAlbum FILE CLASSES HERE ALSO!!! + related => [ + { + tag => 'help common', + namespace => 'Asset_Photo', + }, + ], + }, + + 'help addArchive' => { + title => 'help addArchive title', + body => 'help addArchive body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + ], + variables => { + { + name => 'form_start', + description => 'helpvar form_start', + required => 1, + }, + { + name => 'form_end', + description => 'helpvar form_end', + required => 1, + }, + { + name => 'form_submit', + description => 'helpvar form_submit', + }, + { + name => 'form_archive', + description => 'helpvar form_archive', + required => 1, + }, + { + name => 'form_keywords', + description => 'helpvar form_keywords', + }, + { + name => 'form_friendsOnly', + description => 'helpvar form_friendsOnly', + }, + }, + }, + + 'help delete' => { + title => 'help delete title', + body => 'help delete body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + ], + variables => [ + { + name => 'url_yes', + description => 'helpvar url_yes', + }, + ], + }, + + 'help edit' => { + title => 'help edit title', + body => 'help edit body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + { + tag => 'help fileLoop', + namespace => 'Asset_GalleryAlbum', + }, + ], + variables => [ + { + name => 'form_start', + description => 'helpvar form_start', + required => 1, + }, + { + name => 'form_end', + description => 'helpvar form_end', + required => 1, + }, + { + name => 'form_cancel', + description => 'helpvar form_cancel', + }, + { + name => 'form_submit', + description => 'helpvar form_submit', + }, + { + name => 'form_title', + description => 'helpvar form_title', + }, + { + name => 'form_description', + description => 'helpvar form_description', + required => 1, + }, + { + name => 'file_loop', + description => 'helpvar file_loop edit', + variables => [ + { + name => 'isAlbumThumbnail', + description => 'helpvar isAlbumThumbnail', + }, + ], + }, + ], + }, + + 'help viewRss' => { + title => 'help viewRss title', + body => 'help viewRss body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_GalleryAlbum', + }, + { + tag => 'help fileLoop', + namespace => 'Asset_GalleryAlbum', + }, + ], + variables => [ + { + name => 'file_loop', + description => 'helpvar file_loop viewRss', + variables => [ + { + name => 'rssDate', + description => 'helpvar rssDate', + }, + ], + }, + ], + }, + +}; + +1; diff --git a/lib/WebGUI/Help/Asset_Photo.pm b/lib/WebGUI/Help/Asset_Photo.pm new file mode 100644 index 000000000..9a733ed42 --- /dev/null +++ b/lib/WebGUI/Help/Asset_Photo.pm @@ -0,0 +1,258 @@ +package WebGUI::Help::Asset_Photo; + +our $HELP = { + 'help commentForm' => { + title => 'help commentForm title', + body => 'help commentForm body', + variables => [ + { + name => 'commentForm_start', + description => 'helpvar commentForm_start', + }, + { + name => 'commentForm_end', + description => 'helpvar commentForm_end', + }, + { + name => 'commentForm_bodyText', + description => 'helpvar commentForm_bodyText', + }, + { + name => 'commentForm_submit', + description => 'helpvar commentForm_submit', + }, + ], + }, + + 'help common' => { + title => 'help common title', + body => 'help common body', + isa => [ + { + tag => 'help searchForm', + namespace => 'Asset_Gallery', + }, + { + tag => 'help commentForm', + namespace => 'Asset_Photo', + }, + ], + variables => [ + { + name => 'canComment', + description => 'helpvar canComment', + }, + { + name => 'canEdit', + description => 'helpvar canEdit', + }, + { + name => 'fileUrl', + description => 'helpvar fileUrl', + }, + { + name => 'numberOfComments', + description => 'helpvar numberOfComments', + }, + { + name => 'ownerUsername', + description => 'helpvar ownerUsername', + }, + { + name => 'thumbnailUrl', + description => 'helpvar thumbnailUrl', + }, + { + name => 'url_delete', + description => 'helpvar url_delete', + }, + { + name => 'url_demote', + description => 'helpvar url_demote', + }, + { + name => 'url_edit', + description => 'helpvar url_edit', + }, + { + name => 'url_gallery', + description => 'helpvar url_gallery', + }, + { + name => 'url_makeShortcut', + description => 'helpvar url_makeShortcut', + }, + { + name => 'url_listFilesForOwner', + description => 'helpvar url_listFilesForOwner', + }, + { + name => 'url_promote', + description => 'helpvar url_promote', + }, + { + name => 'resolutions_loop', + description => 'helpvar resolutions_loop', + variables => [ + { + name => 'url_download', + description => 'helpvar resolutions_loop url_download', + }, + ], + }, + { + name => 'exif_*', + description => 'helpvar exif_*', + }, + { + name => 'exifLoop', + description => 'helpvar exifLoop', + variables => [ + { + name => 'tag', + description => 'helpvar exifLoop tag', + }, + { + name => 'value', + description => 'helpvar exifLoop value', + }, + ], + }, + ], + }, + + 'help delete' => { + title => 'help delete title', + body => 'help delete body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_Photo', + }, + ], + variables => [ + { + name => 'url_yes', + description => 'helpvar url_yes', + }, + ], + }, + + 'help edit' => { + title => 'help edit title', + body => 'htlp edit body', + variables => [ + { + name => 'url_addArchive', + description => 'helpvar url_addArchive', + }, + { + name => 'form_start', + description => 'helpvar form_start', + required => 1, + }, + { + name => 'form_end', + description => 'helpvar form_end', + required => 1, + }, + { + name => 'form_submit', + description => 'helpvar form_submit', + }, + { + name => 'form_title', + description => 'helpvar form_title', + }, + { + name => 'form_synopsis', + description => 'helpvar form_synopsis', + }, + { + name => 'form_photo', + description => 'helpvar form_photo', + }, + { + name => 'form_keywords', + description => 'helpvar form_keywords', + }, + { + name => 'form_location', + description => 'helpvar form_location', + }, + { + name => 'form_friendsOnly', + description => 'helpvar form_friendsOnly', + }, + ], + }, + + 'help makeShortcut' => { + title => 'help makeShortcut title', + body => 'htlp makeShortcut body', + variables => [ + { + name => 'form_start', + description => 'helpvar form_start', + required => 1, + }, + { + name => 'form_end', + description => 'helpvar form_end', + required => 1, + }, + { + name => 'form_parentId', + description => 'helpvar form_parentId', + required => 1, + }, + ], + } + 'help view' => { + title => 'help view title', + body => 'help view body', + isa => [ + { + tag => 'help common', + namespace => 'Asset_Photo', + }, + ], + variables => [ + { + name => 'commentLoop', + description => 'helpvar commentLoop', + variables => [ + { + name => 'userId', + description => 'helpvar commentLoop userId', + }, + { + name => 'visitorIp', + description => 'helpvar commentLoop visitorIp', + }, + { + name => 'creationDate', + description => 'helpvar commentLoop creationDate', + }, + { + name => 'bodyText', + description => 'helpvar commentLoop bodyText', + }, + { + name => 'username', + description => 'helpvar commentLoop username', + }, + ], + }, + { + name => 'commentLoop_pageBar', + description => 'helpvar commentLoop_pageBar', + }, + ], + }, + + +}; + +1; + diff --git a/lib/WebGUI/Keyword.pm b/lib/WebGUI/Keyword.pm index 3ae518bc7..a5fbc9adc 100644 --- a/lib/WebGUI/Keyword.pm +++ b/lib/WebGUI/Keyword.pm @@ -27,6 +27,9 @@ Package WebGUI::Keyword This package provides an API to create and modify keywords used by the asset sysetm. +Assets can use the C property to set keywords automatically. See +WebGUI::Asset::update() for more details. + =head1 SYNOPSIS use WebGUI::Keyword; diff --git a/lib/WebGUI/Search.pm b/lib/WebGUI/Search.pm index b19ec0316..138b9b80b 100644 --- a/lib/WebGUI/Search.pm +++ b/lib/WebGUI/Search.pm @@ -15,6 +15,7 @@ package WebGUI::Search; =cut use strict; +use Carp qw( croak ); use WebGUI::Asset; =head1 NAME @@ -329,6 +330,9 @@ This rule allows for an array reference of table join clauses. join => 'join assetData on assetId = assetData.assetId' +NOTE: This rule is deprecated and will be removed in a future release. Use +joinClass instead. + =head4 columns This rule allows for additional columns to be returned by getResultSet(). @@ -344,6 +348,11 @@ placeholders and parameters. sub search { my $self = shift; my $rules = shift; + + # Send the rules through some sanity checks + croak "'lineage' rule must be array reference" + if ( $rules->{lineage} && ref $rules->{lineage} ne "ARRAY" ); + my @params = (); my $query = ""; my @clauses = (); @@ -410,10 +419,31 @@ sub search { if ($rules->{where}) { push(@clauses, $rules->{where}); } - if ($rules->{join}) { # This join happens in _getQuery - $rules->{join} = [$rules->{join}] - unless (ref $rules->{join} eq "ARRAY"); - $self->{_join} = $rules->{join}; + # deal with custom joined tables if we must + if (exists $rules->{joinClass}) { + my $join = [ "left join assetData on assetIndex.assetId=assetData.assetId" ]; + for my $className ( @{ $rules->{ joinClass } } ) { + my $cmd = "use " . $className; + eval { $cmd }; + $self->session->errorHandler->fatal("Couldn't compile asset package: ".$className.". Root cause: ".$@) if ($@); + foreach my $definition (@{$className->definition($self->session)}) { + unless ($definition->{tableName} eq "asset") { + my $tableName = $definition->{tableName}; + push @$join, + "left join $tableName on assetData.assetId=".$tableName.".assetId and assetData.revisionDate=".$tableName.".revisionDate"; + } + last; + } + } + # Get only the latest revision + push @clauses, "assetData.revisionDate = (SELECT MAX(revisionDate) FROM assetData ad WHERE ad.assetId = assetData.assetId)"; + # Join happens in _getQuery + $self->{_join} = $join; + } + elsif ($rules->{join}) { # This join happens in _getQuery + $rules->{join} = [$rules->{join}] + unless (ref $rules->{join} eq "ARRAY"); + $self->{_join} = $rules->{join}; } if ($rules->{columns}) { $rules->{columns} = [$rules->{columns}] diff --git a/lib/WebGUI/Storage.pm b/lib/WebGUI/Storage.pm index 9291d0c79..5c7178c4c 100644 --- a/lib/WebGUI/Storage.pm +++ b/lib/WebGUI/Storage.pm @@ -15,6 +15,8 @@ package WebGUI::Storage; =cut use Archive::Tar; +use Carp qw( croak ); +use Cwd; use File::Copy qw(cp); use FileHandle; use File::Find; @@ -24,7 +26,6 @@ use Storable qw(nstore retrieve); use strict; use warnings; use WebGUI::Utility; -use Carp; =head1 NAME @@ -64,6 +65,7 @@ This package provides a mechanism for storing and retrieving files that are not $newstore = $store->untar($filename); + $store->copyFile($filename, $newFilename); $store->delete; $store->deleteFile($filename); $store->rename($filename, $newFilename); @@ -202,6 +204,7 @@ sub addFileFromFormPost { my $filename; my $attachmentCount = 1; foreach my $upload ($self->session->request->upload($formVariableName)) { + $self->session->errorHandler->info("Trying to get " . $upload->filename); return $filename if $attachmentCount > $attachmentLimit; my $tempFilename = $upload->filename(); next unless $tempFilename; @@ -225,6 +228,7 @@ sub addFileFromFormPost { print $file $buffer; } close($file); + $self->session->errorHandler->info("Got ".$upload->filename); } else { $self->_addError("Couldn't open file ".$self->getPath($filename)." for writing due to error: ".$!); return undef; @@ -343,6 +347,31 @@ sub copy { #------------------------------------------------------------------- +=head2 copyFile ( filename, newFilename ) + +Copy a file in this storage location. C is the file to copy. +C is the new file to create. + +=cut + +sub copyFile { + my $self = shift; + my $filename = shift; + my $newFilename = shift; + + croak "Can't find '$filename' in storage location " . $self->getId + unless -e $self->getPath($filename); + croak "Second argument must be a filename" + unless $newFilename; + + cp( $self->getPath($filename), $self->getPath($newFilename) ) + || croak "Couldn't copy '$filename' to '$newFilename': $!"; + + return; +} + +#------------------------------------------------------------------- + =head2 create ( session ) Creates a new storage location on the file system. @@ -354,9 +383,9 @@ A reference to the current session; =cut sub create { - my $class = shift; - my $session = shift; - my $id = $session->id->generate(); + my $class = shift; + my $session = shift; + my $id = $session->id->generate(); #Determine whether or not to use case insensitive files my $config = $session->config; @@ -365,14 +394,16 @@ sub create { #$session->errorHandler->warn($caseInsensitive.": $id\n".Carp::longmess()."\n"); #For case insensitive operating systems, convert guid to hex - if($caseInsensitive) { + if ($caseInsensitive) { my $hexId = $session->id->toHex($id); $db->write("insert into storageTranslation (guidValue,hexValue) values (?,?)",[$id,$hexId]); } my $self = $class->get($session,$id); - $self->_makePath; - return $self; + $self->_makePath; + + $session->errorHandler->info("Created storage location $id as a $class"); + return $self; } @@ -446,6 +477,7 @@ sub delete { $db->write("delete from storageTranslation where guidValue=?",[$self->getId]); } } + $self->session->errorHandler->info("Deleted storage ".$self->getId); return; } @@ -753,6 +785,8 @@ Returns a full path to this storage location. If specified, we'll return a path to the file rather than the storage location. +NOTE: Does not check if the file exists. This is a feature. + =cut sub getPath { @@ -898,21 +932,22 @@ Pass in a storage location object to create the tar file in, instead of having a =cut sub tar { - my $self = shift; - my $filename = shift; - my $temp = shift || WebGUI::Storage->createTemp($self->session); + my $self = shift; + my $filename = shift; + my $temp = shift || WebGUI::Storage->createTemp($self->session); chdir $self->getPath or croak 'Unable to chdir to ' . $self->getPath . ": $!"; - my @files = (); - find(sub { push(@files, $File::Find::name)}, "."); - if ($Archive::Tar::VERSION eq '0.072') { - my $tar = Archive::Tar->new(); - $tar->add_files(@files); - $tar->write($temp->getPath($filename),1); - - } else { - Archive::Tar->create_archive($temp->getPath($filename),1,@files); - } - return $temp; + my @files = (); + find(sub { push(@files, $File::Find::name)}, "."); + if ($Archive::Tar::VERSION eq '0.072') { + my $tar = Archive::Tar->new(); + $tar->add_files(@files); + $tar->write($temp->getPath($filename),1); + + } + else { + Archive::Tar->create_archive($temp->getPath($filename),1,@files); + } + return $temp; } #------------------------------------------------------------------- @@ -932,12 +967,17 @@ Pass in a storage location object to extract the contents to, instead of having =cut sub untar { - my $self = shift; - my $filename = shift; - my $temp = shift || WebGUI::Storage->createTemp($self->session); + my $self = shift; + my $filename = shift; + my $temp = shift || WebGUI::Storage->createTemp($self->session); + + my $originalDir = cwd; chdir $temp->getPath; + Archive::Tar->extract_archive($self->getPath($filename),1); $self->_addError(Archive::Tar->error) if (Archive::Tar->error); + + chdir $originalDir; return $temp; } diff --git a/lib/WebGUI/Storage/Image.pm b/lib/WebGUI/Storage/Image.pm index 008a6ed6c..241d1a783 100644 --- a/lib/WebGUI/Storage/Image.pm +++ b/lib/WebGUI/Storage/Image.pm @@ -52,11 +52,11 @@ use WebGUI::Storage::Image; These methods are available from this class: -my $boolean = $self->generateThumbnail($filename); -my $url = $self->getThumbnailUrl($filename); -my $boolean = $self->isImage($filename); -my ($captchaFile, $challenge) = $self->addFileFromCaptcha; -$self->resize($imageFile, $width, $height); + my $boolean = $self->generateThumbnail($filename); + my $url = $self->getThumbnailUrl($filename); + my $boolean = $self->isImage($filename); + my ($captchaFile, $challenge) = $self->addFileFromCaptcha; + $self->resize($imageFile, $width, $height); =cut @@ -284,7 +284,7 @@ sub getThumbnailUrl { return ''; } if (! isIn($filename, @{ $self->getFiles() })) { - $self->session->errorHandler->error("Can't make a thumbnail for a file that is not in my storage location."); + $self->session->errorHandler->error("Can't make a thumbnail for a file named '$filename' that is not in my storage location."); return ''; } return $self->getUrl("thumb-".$filename); @@ -331,41 +331,42 @@ The new height of the image in pixels. =cut sub resize { - my $self = shift; - my $filename = shift; - my $width = shift; - my $height = shift; - unless (defined $filename) { - $self->session->errorHandler->error("Can't resize when you haven't specified a file."); - return 0; - } - unless ($self->isImage($filename)) { - $self->session->errorHandler->error("Can't resize something that's not an image."); - return 0; - } - unless ($width || $height) { - $self->session->errorHandler->error("Can't resize with no resizing parameters."); - return 0; - } - my $image = $graphicsPackage->new; - my $error = $image->Read($self->getPath($filename)); - if ($error) { - $self->session->errorHandler->error("Couldn't read image for resizing: ".$error); - return 0; - } - my ($x, $y) = $image->Get('width','height'); - if ($width && !$height) { # proportional scale by width - $height = $width / $x * $y; - } elsif (!$width && $height) { # proportional scale by height - $width = $height * $x / $y; - } - $image->Scale(width=>$width, height=>$height); - $error = $image->Write($self->getPath($filename)); - if ($error) { - $self->session->errorHandler->error("Couldn't create thumbnail: ".$error); - return 0; - } - return 1; + my $self = shift; + my $filename = shift; + my $width = shift; + my $height = shift; + unless (defined $filename) { + $self->session->errorHandler->error("Can't resize when you haven't specified a file."); + return 0; + } + unless ($self->isImage($filename)) { + $self->session->errorHandler->error("Can't resize something that's not an image."); + return 0; + } + unless ($width || $height) { + $self->session->errorHandler->error("Can't resize with no resizing parameters."); + return 0; + } + my $image = $graphicsPackage->new; + my $error = $image->Read($self->getPath($filename)); + if ($error) { + $self->session->errorHandler->error("Couldn't read image for resizing: ".$error); + return 0; + } + my $geometry; + if (!$width || !$height) { + $geometry = $width || $height; + } + else { + $geometry = $width . "x" . $height; + } + $image->Resize( geometry => $geometry, filter => "lanczos" ); + $error = $image->Write($self->getPath($filename)); + if ($error) { + $self->session->errorHandler->error("Couldn't resize image: ".$error); + return 0; + } + return 1; } diff --git a/lib/WebGUI/i18n/English/Asset_Gallery.pm b/lib/WebGUI/i18n/English/Asset_Gallery.pm index 41bc3d843..a93676cf9 100644 --- a/lib/WebGUI/i18n/English/Asset_Gallery.pm +++ b/lib/WebGUI/i18n/English/Asset_Gallery.pm @@ -5,6 +5,554 @@ our $I18N = { message => 'Gallery', lastUpdated => 1131394072, }, + "groupIdAddComment label" => { + message => "Group to Add Comments", + lastUpdated => 0, + context => '', + }, + "groupIdAddComment description" => { + message => "The group that is allowed to add comments to files.", + lastUpdated => 0, + context => '', + }, + "groupIdAddFile label" => { + message => "Group to Add Files", + lastUpdated => 0, + context => '', + }, + "groupIdAddFile description" => { + message => "The group that is allowed to add files and albums to this gallery", + lastUpdated => 0, + context => '', + }, + "groupIdModerator label" => { + message => "Group to Moderate Comments", + lastUpdated => 0, + context => '', + }, + "groupIdModerator description" => { + message => "The group that is allowed to edit / delete comments in this gallery", + lastUpdated => 0, + context => '', + }, + "imageResolutions label" => { + message => "Image Resolutions", + lastUpdated => 0, + context => '', + }, + "imageResolutions description" => { + message => "The sizes of images available for download.", + lastUpdated => 0, + context => '', + }, + "imageViewSize label" => { + message => "Image View Size", + lastUpdated => 0, + context => '', + }, + "imageViewSize description" => { + message => "The size for images in the gallery. Will default to the Image Size + in the site settings.", + lastUpdated => 0, + context => '', + }, + "imageThumbnailSize label" => { + message => "Image Thumbnail Size", + lastUpdated => 0, + context => '', + }, + "imageThumbnailSize description" => { + message => "The size for thumbnails of images in the gallery. Will default to the + Thumbnail Size in the site settings.", + lastUpdated => 0, + context => '', + }, + "maxSpacePerUser label" => { + message => "Max Disk Space Per User", + lastUpdated => 0, + context => '', + }, + "maxSpacePerUser description" => { + message => "The maximum amount of disk space a user is allowed to use in this Gallery.", + lastUpdated => 0, + context => '', + }, + "richEditIdFileComment label" => { + message => "Rich Editor for Comments", + lastUpdated => 0, + context => '', + }, + "richEditIdFileComment description" => { + message => "The Rich Text Editor to use for comments", + lastUpdated => 0, + context => '', + }, + 'search class galleryalbum' => { + message => 'Album', + lastUpdated => 0, + context => 'Asset name for WebGUI::Asset::Wobject::GalleryAlbum', + }, + 'search class any' => { + message => 'Any', + lastUpdated => 0, + context => 'Label to not restrict gallery search by class', + }, + 'search class photo' => { + message => "Photo", + lastUpdated => 0, + context => 'Asset name for WebGUI::Asset::File::Image::Photo class', + }, + "search submit" => { + message => "Search", + lastUpdated => 0, + context => 'Label for button to submit search form', + }, + "templateIdAddArchive label" => { + message => "Template to Add Multiple", + lastUpdated => 0, + context => '', + }, + "templateIdAddArchive description" => { + message => "Display the form to add an archive of files to the gallery.", + lastUpdated => 0, + context => '', + }, + "templateIdDeleteAlbum label" => { + message => "Template to Delete Albums", + lastUpdated => 0, + context => '', + }, + "templateIdDeleteAlbum description" => { + message => "Display the confirmation to delete an album from the gallery.", + lastUpdated => 0, + context => '', + }, + "templateIdDeleteFile label" => { + message => "Template to Delete Files", + lastUpdated => 0, + context => '', + }, + "templateIdDeleteFile description" => { + message => "Display the confirmation to delete a file from the gallery.", + lastUpdated => 0, + context => '', + }, + "templateIdEditAlbum label" => { + message => "Template to Edit Albums", + lastUpdated => 0, + context => '', + }, + "templateIdEditAlbum description" => { + message => "The template to add / edit an album.", + lastUpdated => 0, + context => '', + }, + "templateIdEditFile label" => { + message => "Template to Edit Files", + lastUpdated => 0, + context => '', + }, + "templateIdEditFile description" => { + message => "The template to add / edit a file.", + lastUpdated => 0, + context => '', + }, + "templateIdListAlbums label" => { + message => "Template to List Albums", + lastUpdated => 0, + context => '', + }, + "templateIdListAlbums description" => { + message => "Template to show a list of albums in the gallery.", + lastUpdated => 0, + context => '', + }, + "templateIdListAlbumsRss label" => { + message => "Template to List Albums RSS", + lastUpdated => 0, + context => '', + }, + "templateIdListAlbumsRss description" => { + message => "Template to show an RSS feed of the albums in this gallery.", + lastUpdated => 0, + context => '', + }, + "templateIdMakeShortcut label" => { + message => "Template to Cross Post Files", + lastUpdated => 0, + context => '', + }, + "templateIdMakeShortcut description" => { + message => "Display the form to copy an image to another album.", + lastUpdated => 0, + context => '', + }, + "templateIdSearch label" => { + message => "Template to Search", + lastUpdated => 0, + context => '', + }, + "templateIdSearch description" => { + message => "Display the form to search the gallery. Display search results.", + lastUpdated => 0, + context => '', + }, + "templateIdViewSlideshow label" => { + message => "Template for Slideshow", + lastUpdated => 0, + context => '', + }, + "templateIdViewSlideshow description" => { + message => "Display all the images in an album as a slideshow.", + lastUpdated => 0, + context => '', + }, + "templateIdViewThumbnails label" => { + message => "Template for Thumbnails", + lastUpdated => 0, + context => '', + }, + "templateIdViewThumbnails description" => { + message => "Display all the images in an album as their thumbnails", + lastUpdated => 0, + context => '', + }, + "templateIdListFilesForUser label" => { + message => "Template to List Files for User", + lastUpdated => 0, + context => '', + }, + "templateIdListFilesForUser description" => { + message => "Display all the files and albums for a specific user.", + lastUpdated => 0, + context => '', + }, + "templateIdListFilesForUserRss label" => { + message => "Template to List Files for User RSS", + lastUpdated => 0, + context => '', + }, + "templateIdListFilesForUserRss description" => { + message => "RSS feed for all the files for a specific user.", + lastUpdated => 0, + context => '', + }, + "templateIdViewAlbum label" => { + message => "Template to View Album", + lastUpdated => 0, + context => '', + }, + "templateIdViewAlbum description" => { + message => "Default view for albums", + lastUpdated => 0, + context => '', + }, + "templateIdViewAlbumRss label" => { + message => "Template to View Album RSS", + lastUpdated => 0, + context => '', + }, + "templateIdViewAlbumRss description" => { + message => "RSS feed for a single album", + lastUpdated => 0, + context => '', + }, + "templateIdViewFile label" => { + message => "Template to View a File", + lastUpdated => 0, + context => '', + }, + "templateIdViewFile description" => { + message => "Show the details and comments for a specific file", + lastUpdated => 0, + context => '', + }, + "viewDefault label" => { + message => "Default View", + lastUpdated => 0, + context => '', + }, + "viewDefault description" => { + message => "Select the default view when a user enters the gallery.", + lastUpdated => 0, + context => '', + }, + "viewDefault option list" => { + message => "List Albums", + lastUpdated => 0, + context => '', + }, + "viewDefault option album" => { + message => "Single Album", + lastUpdated => 0, + context => '', + }, + "viewAlbumAssetId label" => { + message => "Default View Album", + lastUpdated => 0, + context => '', + }, + "viewAlbumAssetId description" => { + message => "The album to view when the default view is 'Album'", + lastUpdated => 0, + context => '', + }, + "viewListOrderBy label" => { + message => "List Albums Order By", + lastUpdated => 0, + context => '', + }, + "viewListOrderBy description" => { + message => "The field to order the album list by", + lastUpdated => 0, + context => '', + }, + "viewListOrderBy option creationDate" => { + message => "Creation Date", + lastUpdated => 0, + context => '', + }, + "viewListOrderBy option lineage" => { + message => "Sequence Number", + lastUpdated => 0, + context => 'Label to order by sequence (as in asset manager)', + }, + "viewListOrderBy option revisionDate" => { + message => "Revision Date", + lastUpdated => 0, + context => '', + }, + "viewListOrderBy option title" => { + message => "Title", + lastUpdated => 0, + context => '', + }, + "viewListOrderDirection label" => { + message => "List Albums Direction", + lastUpdated => 0, + context => '', + }, + "viewListOrderDirection description" => { + message => "The direction to order the album list", + lastUpdated => 0, + context => '', + }, + "viewListOrderDirection option asc" => { + message => "Ascending", + lastUpdated => 0, + context => 'Label for sorting in ascending order', + }, + "viewListOrderDirection option desc" => { + message => "Descending", + lastUpdated => 0, + context => 'Label for sorting in descending order', + }, + "workflowIdCommit label" => { + message => "Approval Workflow", + lastUpdated => 0, + context => '', + }, + "workflowIdCommit description" => { + message => "Workflow to approve new Files.", + lastUpdated => 0, + context => '', + }, + + 'helpvar searchForm_start' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_end' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_basicSearch' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_title' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_description' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_keywords' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_className' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_creationDate_after' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_creationDate_before' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar searchForm_submit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_addAlbum' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_listAlbums' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_listAlbumsRss' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_listFilesForCurrentUser' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_search' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar canEdit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar canAddFile' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar albums' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar albums rss' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar rssDate' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar search_results' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar user_albums' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar user_files' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar userId' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_rss' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar username' => { + message => '', + lastUpdated => 0, + }, + + 'help searchForm title' => { + message => '', + lastUpdated => 0, + }, + + 'help searchForm body' => { + message => '', + lastUpdated => 0, + }, + + 'help common title' => { + message => '', + lastUpdated => 0, + }, + + 'help common body' => { + message => '', + lastUpdated => 0, + }, + + 'help listAlbums title' => { + message => '', + lastUpdated => 0, + }, + + 'help listAlbums body' => { + message => '', + lastUpdated => 0, + }, + + 'help listAlbumsRss title' => { + message => '', + lastUpdated => 0, + }, + + 'help search body' => { + message => '', + lastUpdated => 0, + }, + + 'help search title' => { + message => '', + lastUpdated => 0, + }, + + 'help listFilesForUser title' => { + message => '', + lastUpdated => 0, + }, + + 'help listFilesForUser body' => { + message => '', + lastUpdated => 0, + }, + + 'help listFilesForUserRss title' => { + message => '', + lastUpdated => 0, + }, + + 'help listFilesForUserRss body' => { + message => '', + lastUpdated => 0, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm b/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm index cff839973..7724c47fc 100644 --- a/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm +++ b/lib/WebGUI/i18n/English/Asset_GalleryAlbum.pm @@ -1,10 +1,299 @@ package WebGUI::i18n::English::Asset_GalleryAlbum; our $I18N = { + 'addArchive message' => { + message => 'Your files have been submitted for approval and commit. Return to Album', + lastUpdated => 0, + }, 'assetName' => { message => 'Gallery Album', lastUpdated => 1131394072, }, + 'cancel' => { + message => "Cancel", + lastUpdated => 0, + context => "Label for Cancel button", + }, + 'save' => { + message => "Save", + lastUpdated => 0, + context => "Label for Save button", + }, + 'save message' => { + message => 'Album settings saved. Return to Album', + lastUpdated => 0, + }, + + 'help common title' => { + message => '', + lastUpdated => 0, + }, + + 'help common body' => { + message => '', + lastUpdated => 0, + }, + + 'help fileLoop title' => { + message => '', + lastUpdated => 0, + }, + + 'help fileLoop body' => { + message => '', + lastUpdated => 0, + }, + + 'help view title' => { + message => '', + lastUpdated => 0, + }, + + 'help view body' => { + message => '', + lastUpdated => 0, + }, + + 'help slideshow title' => { + message => '', + lastUpdated => 0, + }, + + 'help slideshow body' => { + message => '', + lastUpdated => 0, + }, + + 'help thumbnails title' => { + message => '', + lastUpdated => 0, + }, + + 'help thumbnails body' => { + message => '', + lastUpdated => 0, + }, + + 'help addArchive title' => { + message => '', + lastUpdated => 0, + }, + + 'help addArchive body' => { + message => '', + lastUpdated => 0, + }, + + 'help delete title' => { + message => '', + lastUpdated => 0, + }, + + 'help delete body' => { + message => '', + lastUpdated => 0, + }, + + 'help edit title' => { + message => '', + lastUpdated => 0, + }, + + 'help viewRss title' => { + message => '', + lastUpdated => 0, + }, + + 'help viewRss body' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar canAddFile' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar canEdit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_listAlbums' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_listAlbumsRss' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_listFilesForCurrentUser' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_search' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_addArchive' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_addPhoto' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_addNoClass' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_delete' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_edit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_listFilesForOwner' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_viewRss' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_slideshow' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_thumbnails' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar fileCount' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar ownerUsername' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar thumbnailUrl' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar file_loop' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar file_*' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_start' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_end' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_submit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_archive' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_keywords' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_friendsOnly' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_yes' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_start' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_end' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_cancel' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_submit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_title' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_description' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar file_loop edit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar isAlbumThumbnail' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar file_loop viewRss' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar rssDate' => { + message => '', + lastUpdated => 0, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/Asset_Photo.pm b/lib/WebGUI/i18n/English/Asset_Photo.pm new file mode 100644 index 000000000..bca896348 --- /dev/null +++ b/lib/WebGUI/i18n/English/Asset_Photo.pm @@ -0,0 +1,291 @@ +package WebGUI::i18n::English::Asset_Photo; + +our $I18N = { + 'assetName' => { + message => q{Photo}, + lastUpdated => 0, + }, + + 'delete message' => { + message => q{The photo has been deleted. Return to Album}, + lastUpdated => 0, + }, + + 'save message' => { + message => q{Your photo has been submitted for approval and commit. View Photo. Add another photo.}, + lastUpdated => 0, + }, + + 'help commentForm title' => { + message => 'Photo -- Comment Form', + lastUpdated => 0, + }, + + 'help commentForm body' => { + message => 'These template variables make up the form to allow users to post comments on Photos', + lastUpdated => 0, + }, + + 'help common title' => { + message => 'Photo -- Common', + lastUpdated => 0, + }, + + 'help common body' => { + message => 'These template variables are shared by all views of the Photo asset.', + lastUpdated => 0, + }, + + 'help edit title' => { + message => 'Photo -- Edit Form', + lastUpdated => 0, + }, + + 'help edit body' => { + message => 'These template variables make up the form to add / edit Photo assets', + lastUpdated => 0, + }, + + 'help makeShortcut title' => { + message => 'Photo -- Make Shortcut Form', + lastUpdated => 0, + }, + + 'help makeShortcut body' => { + message => 'These template variables make up the form to cross-post Photo assets', + lastUpdated => 0, + }, + + 'help view title' => { + message => 'Photo -- Normal View', + lastUpdated => 0, + }, + + 'help view body' => { + message => 'These template variables make up the normal view of Photo assets', + lastUpdated => 0, + }, + + 'helpvar commentForm_start' => { + message => 'Begin the comment form', + lastUpdated => 0, + }, + + 'helpvar commentForm_end' => { + message => 'End the comment form', + lastUpdated => 0, + }, + + 'helpvar commentForm_bodyText' => { + message => 'The body of the comment. A rich editor as configured by the parent Gallery.', + lastUpdated => 0, + }, + + 'helpvar commentForm_submit' => { + message => 'Submit the comment form', + lastUpdated => 0, + }, + + 'helpvar canComment' => { + message => 'This is true if the current user can comment on this photo', + lastUpdated => 0, + }, + + 'helpvar canEdit' => { + message => 'This is true if the current user can edit this photo', + lastUpdated => 0, + }, + + 'helpvar fileUrl' => { + message => 'The URL to the normal-sized photo', + lastUpdated => 0, + }, + + 'helpvar numberOfComments' => { + message => 'The total number of comments on this photo', + lastUpdated => 0, + }, + + 'helpvar ownerUsername' => { + message => 'The username of the user who posted this photo', + lastUpdated => 0, + }, + + 'helpvar thumbnailUrl' => { + message => 'The URL to the thumbnail of this photo', + lastUpdated => 0, + }, + + 'helpvar url_delete' => { + message => 'The URL to delete this photo.', + lastUpdated => 0, + }, + + 'helpvar url_demote' => { + message => 'The URL to demote this photo in rank. Will return the user directly to the parent GalleryAlbum edit form', + lastUpdated => 0, + }, + + 'helpvar url_edit' => { + message => 'The URL to edit this photo', + lastUpdated => 0, + }, + + 'helpvar url_gallery' => { + message => 'The URL to the Gallery that contains this photo.', + lastUpdated => 0, + }, + + 'helpvar url_makeShortcut' => { + message => 'The URL to make a shortcut to this photo.', + lastUpdated => 0, + }, + + 'helpvar url_listFilesForOwner' => { + message => 'The URL to list files and albums posted by the owner of this photo', + lastUpdated => 0, + }, + + 'helpvar url_promote' => { + message => 'The URL to promote this photo in rank. Will return the user directly to the parent GalleryAlbum edit form', + lastUpdated => 0, + }, + + 'helpvar resolutions_loop' => { + message => 'The available resolutions this photo has for download.', + lastUpdated => 0, + }, + + 'helpvar resolutions_loop url_download' => { + message => 'The URL to the resolution to download.', + lastUpdated => 0, + }, + + 'helpvar exif_*' => { + message => 'Each EXIF tag can be referenced by name.', + lastUpdated => 0, + }, + + 'helpvar exifLoop' => { + message => 'A loop of EXIF tags', + lastUpdated => 0, + }, + + 'helpvar exifLoop tag' => { + message => 'The name of the EXIF tag', + lastUpdated => 0, + }, + + 'helpvar exifLoop value' => { + message => 'The value of the EXIF tag', + lastUpdated => 0, + }, + + 'helpvar url_addArchive' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_start' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_end' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_submit' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_title' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_synopsis' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_photo' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_keywords' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_location' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_friendsOnly' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_start' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_end' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar form_parentId' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar commentLoop' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar commentLoop userId' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar commentLoop visitorIp' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar commentLoop creationDate' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar commentLoop bodyText' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar commentLoop username' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar commentLoop_pageBar' => { + message => '', + lastUpdated => 0, + }, + + 'helpvar url_yes' => { + message => '', + lastUpdated => 0, + }, + +}; + +1; diff --git a/t/Asset/Wobject/Gallery/00base.t b/t/Asset/Wobject/Gallery/00base.t new file mode 100644 index 000000000..0119dc465 --- /dev/null +++ b/t/Asset/Wobject/Gallery/00base.t @@ -0,0 +1,78 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the creation and deletion of album assets + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan tests => 5; + +#---------------------------------------------------------------------------- +# Test module compiles okay +# plan tests => 1 +use_ok("WebGUI::Asset::Wobject::GalleryAlbum"); + +#---------------------------------------------------------------------------- +# Test creating an album +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + }); + +is( + blessed $album, "WebGUI::Asset::Wobject::GalleryAlbum", + "Album is a WebGUI::Asset::Wobject::GalleryAlbum object", +); + +isa_ok( + $album, "WebGUI::Asset::Wobject", +); + +#---------------------------------------------------------------------------- +# Test deleting a album +my $properties = $album->get; +$album->purge; + +is( + $album, undef, + "Album is undefined", +); + +is( + WebGUI::Asset->newByDynamicClass($session, $properties->{assetId}), undef, + "Album no longer able to be instanciated", +); + diff --git a/t/Asset/Wobject/Gallery/delete.t b/t/Asset/Wobject/Gallery/delete.t new file mode 100644 index 000000000..f49603f93 --- /dev/null +++ b/t/Asset/Wobject/Gallery/delete.t @@ -0,0 +1,96 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the deleting of GalleryAlbums + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::Html->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Delete page gives error for those who can't edit the GalleryAlbum +$maker->prepare({ + object => $album, + method => "www_delete", + test_privilege => "insufficient", + userId => 1, +}, { + object => $album, + method => "www_deleteConfirm", + test_privilege => "insufficient", + userId => 1, +}); +$maker->run; + +#---------------------------------------------------------------------------- +# Delete confirm page appears for those allowed to edit the GalleryAlbum +$maker->prepare({ + object => $album, + method => "www_delete", + test_regex => [ qr/func=deleteConfirm/, ], + userId => 3, +}); +$maker->run; + +#---------------------------------------------------------------------------- +# www_deleteConfirm deletes the asset +my $assetId = $album->getId; +$maker->prepare({ + object => $album, + method => "www_deleteConfirm", + test_regex => [ qr/has been deleted/, ], + userId => 3, +}); +$maker->run; + +is( + WebGUI::Asset->newByDynamicClass( $session, $assetId ), + undef, + "GalleryAlbum cannot be instanciated after www_deleteConfirm", +); + diff --git a/t/Asset/Wobject/Gallery/listFilesForUser.t b/t/Asset/Wobject/Gallery/listFilesForUser.t new file mode 100644 index 000000000..0119dc465 --- /dev/null +++ b/t/Asset/Wobject/Gallery/listFilesForUser.t @@ -0,0 +1,78 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the creation and deletion of album assets + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan tests => 5; + +#---------------------------------------------------------------------------- +# Test module compiles okay +# plan tests => 1 +use_ok("WebGUI::Asset::Wobject::GalleryAlbum"); + +#---------------------------------------------------------------------------- +# Test creating an album +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + }); + +is( + blessed $album, "WebGUI::Asset::Wobject::GalleryAlbum", + "Album is a WebGUI::Asset::Wobject::GalleryAlbum object", +); + +isa_ok( + $album, "WebGUI::Asset::Wobject", +); + +#---------------------------------------------------------------------------- +# Test deleting a album +my $properties = $album->get; +$album->purge; + +is( + $album, undef, + "Album is undefined", +); + +is( + WebGUI::Asset->newByDynamicClass($session, $properties->{assetId}), undef, + "Album no longer able to be instanciated", +); + diff --git a/t/Asset/Wobject/Gallery/permission.t b/t/Asset/Wobject/Gallery/permission.t new file mode 100644 index 000000000..2d91342ed --- /dev/null +++ b/t/Asset/Wobject/Gallery/permission.t @@ -0,0 +1,107 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the permissions of GalleryAlbum assets + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use WebGUI::Test::Maker::Permission; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::Permission->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); + +my %user; +$user{"2"} = WebGUI::User->new( $session, "new" ); +$user{"2"}->addToGroups( ['2'] ); # Registered user + +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); + $user{"2"}->delete; +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# By default, GalleryAlbum inherits its permissions from the Gallery, but +# only the owner of the GalleryAlbum is allowed to add files +$maker->prepare({ + object => $album, + method => "canView", + pass => [ 1, 3, $user{"2"}, ], +}, { + object => $album, + method => "canEdit", + pass => [ 3, ], + fail => [ 1, $user{"2"}, ], +}, { + object => $album, + method => "canAddFile", + pass => [ 3, ], + fail => [ 1, $user{"2"}, ], +}, { + object => $album, + method => "canAddComment", + pass => [ 3, $user{"2"}, ], + fail => [ 1, ], +}); +$maker->run; + +#---------------------------------------------------------------------------- +# GalleryAlbums with "allowComments" false do not allow anyone to comment +$album->update({ allowComments => 0 }); +$maker->prepare({ + object => $album, + method => "canComment", + fail => [ 1, 3, $user{"2"}, ], +}); +$maker->run; + +#---------------------------------------------------------------------------- +# GalleryAlbum with "othersCanAdd" true allows anyone who can add files to +# the Gallery to add files to this GalleryAlbum +$album->update({ othersCanAdd => 1 }); +$maker->prepare({ + object => $album, + method => "canAddFile", + pass => [ 3, $user{"2"}, ], + fail => [ 1, ], +}); +$maker->run; + diff --git a/t/Asset/Wobject/Gallery/rss.t b/t/Asset/Wobject/Gallery/rss.t new file mode 100644 index 000000000..1f96b786c --- /dev/null +++ b/t/Asset/Wobject/Gallery/rss.t @@ -0,0 +1,66 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the rss view of GalleryAlbums + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use WebGUI::Test::Maker::HTML; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::HTML->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); +my @photos; +for my $i ( 0 .. 5 ) { + $photos[ $i ] + = $album->addChild({ + className => "WebGUI::Asset::File::Image::Photo", + filename => "$i.jpg", + }); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Test www_viewRss + diff --git a/t/Asset/Wobject/Gallery/search.t b/t/Asset/Wobject/Gallery/search.t new file mode 100644 index 000000000..0119dc465 --- /dev/null +++ b/t/Asset/Wobject/Gallery/search.t @@ -0,0 +1,78 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the creation and deletion of album assets + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan tests => 5; + +#---------------------------------------------------------------------------- +# Test module compiles okay +# plan tests => 1 +use_ok("WebGUI::Asset::Wobject::GalleryAlbum"); + +#---------------------------------------------------------------------------- +# Test creating an album +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + }); + +is( + blessed $album, "WebGUI::Asset::Wobject::GalleryAlbum", + "Album is a WebGUI::Asset::Wobject::GalleryAlbum object", +); + +isa_ok( + $album, "WebGUI::Asset::Wobject", +); + +#---------------------------------------------------------------------------- +# Test deleting a album +my $properties = $album->get; +$album->purge; + +is( + $album, undef, + "Album is undefined", +); + +is( + WebGUI::Asset->newByDynamicClass($session, $properties->{assetId}), undef, + "Album no longer able to be instanciated", +); + diff --git a/t/Asset/Wobject/Gallery/view.t b/t/Asset/Wobject/Gallery/view.t new file mode 100644 index 000000000..dedd4f74a --- /dev/null +++ b/t/Asset/Wobject/Gallery/view.t @@ -0,0 +1,130 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../lib"; + +## The goal of this test is to test the default view and associated subs + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use Test::Deep; +use WebGUI::Test::Maker::HTML; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::HTML->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 2, # Registered Users + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); +my @photos; +for my $i ( 0 .. 5 ) { + $photos[ $i ] + = $album->addChild({ + className => "WebGUI::Asset::File::Image::Photo", + filename => "$i.jpg", + }); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Test getFileIds and getFilePaginator +cmp_bag( $album->getFileIds, [ map { $_->getId } @photos ] ); + +my $p = $album->getFilePaginator; +isa_ok( $p, "WebGUI::Paginator" ); +cmp_deeply( $p->getPageData, subbagof( map { $_->getId } @photos ) ); + +#---------------------------------------------------------------------------- +# Test getTemplateVars + +# Is a superset of Asset->get +# NOTE: url is Asset->getUrl +cmp_deeply( $album->getTemplateVars, superhashof( { %{$album->get}, url => $album->getUrl, } ) ); + +# Contains specific keys/values +my $expected = { + "url_addPhoto" + => all( + re( qr/className=WebGUI::Asset::File::Image::Photo/ ), + re( qr/func=add/ ), + re( $album->getUrl ), + ), + "url_addNoClass" + => all( + re( $album->getUrl ), + re( qr/func=add$/ ), + ), + "url_slideshow" + => all( + re( $album->getUrl ), + re( qr/func=slideshow/ ), + ), + "url_thumbnails" + => all( + re( $album->getUrl ), + re( qr/func=thumbnails/ ), + ), + "url_viewRss" + => all( + re( $album->getUrl ), + re( qr/func=viewRss/ ), + ), +}; + +cmp_deeply( $album->getTemplateVars, superhashof( $expected ) ); + +#---------------------------------------------------------------------------- +# Test appendTemplateVarsFileLoop +$expected = { + "file_loop" => bag( map { $_->getTemplateVars } @photos ), +}; +cmp_deeply( + $album->appendTemplateVarsFileLoop({},$self->getFilePaginator->getPageData), + $expected +); + +#---------------------------------------------------------------------------- +# Test www_view() for those without permission to view +$maker->prepare({ + object => $album, + method => "www_view", + test_privilege => "insufficient", +}); +$maker->run; + diff --git a/t/Asset/Wobject/GalleryAlbum/addArchive.t b/t/Asset/Wobject/GalleryAlbum/addArchive.t new file mode 100644 index 000000000..29d9844b3 --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/addArchive.t @@ -0,0 +1,69 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the permissions of GalleryAlbum assets + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use WebGUI::Test::Maker::Permission; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::Permission->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan tests => 2; + +#---------------------------------------------------------------------------- +# Test the addArchive sub +# elephant_images.zip contains three jpgs: Aana1.jpg, Aana2.jpg, Aana3.jpg +$album->addArchive( WebGUI::Test->getTestCollateralPath('elephant_images.zip') ); +my $images = $album->getLineage(['descendants'], { returnObjects => 1 }); + +is( scalar @$images, 3, "addArchive() adds one asset per image" ); +cmp_deeply( + [ map { $_->get("filename") } @$images ], + bag( "Aana1.jpg", "Aana2.jpg", "Aana3.jpg" ), +); + +#---------------------------------------------------------------------------- +# Test the www_addArchive page diff --git a/t/Asset/Wobject/GalleryAlbum/delete.t b/t/Asset/Wobject/GalleryAlbum/delete.t new file mode 100644 index 000000000..f49603f93 --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/delete.t @@ -0,0 +1,96 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the deleting of GalleryAlbums + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::Html->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Delete page gives error for those who can't edit the GalleryAlbum +$maker->prepare({ + object => $album, + method => "www_delete", + test_privilege => "insufficient", + userId => 1, +}, { + object => $album, + method => "www_deleteConfirm", + test_privilege => "insufficient", + userId => 1, +}); +$maker->run; + +#---------------------------------------------------------------------------- +# Delete confirm page appears for those allowed to edit the GalleryAlbum +$maker->prepare({ + object => $album, + method => "www_delete", + test_regex => [ qr/func=deleteConfirm/, ], + userId => 3, +}); +$maker->run; + +#---------------------------------------------------------------------------- +# www_deleteConfirm deletes the asset +my $assetId = $album->getId; +$maker->prepare({ + object => $album, + method => "www_deleteConfirm", + test_regex => [ qr/has been deleted/, ], + userId => 3, +}); +$maker->run; + +is( + WebGUI::Asset->newByDynamicClass( $session, $assetId ), + undef, + "GalleryAlbum cannot be instanciated after www_deleteConfirm", +); + diff --git a/t/Asset/Wobject/GalleryAlbum/permission.t b/t/Asset/Wobject/GalleryAlbum/permission.t new file mode 100644 index 000000000..2d91342ed --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/permission.t @@ -0,0 +1,107 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the permissions of GalleryAlbum assets + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use WebGUI::Test::Maker::Permission; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::Permission->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); + +my %user; +$user{"2"} = WebGUI::User->new( $session, "new" ); +$user{"2"}->addToGroups( ['2'] ); # Registered user + +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); + $user{"2"}->delete; +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# By default, GalleryAlbum inherits its permissions from the Gallery, but +# only the owner of the GalleryAlbum is allowed to add files +$maker->prepare({ + object => $album, + method => "canView", + pass => [ 1, 3, $user{"2"}, ], +}, { + object => $album, + method => "canEdit", + pass => [ 3, ], + fail => [ 1, $user{"2"}, ], +}, { + object => $album, + method => "canAddFile", + pass => [ 3, ], + fail => [ 1, $user{"2"}, ], +}, { + object => $album, + method => "canAddComment", + pass => [ 3, $user{"2"}, ], + fail => [ 1, ], +}); +$maker->run; + +#---------------------------------------------------------------------------- +# GalleryAlbums with "allowComments" false do not allow anyone to comment +$album->update({ allowComments => 0 }); +$maker->prepare({ + object => $album, + method => "canComment", + fail => [ 1, 3, $user{"2"}, ], +}); +$maker->run; + +#---------------------------------------------------------------------------- +# GalleryAlbum with "othersCanAdd" true allows anyone who can add files to +# the Gallery to add files to this GalleryAlbum +$album->update({ othersCanAdd => 1 }); +$maker->prepare({ + object => $album, + method => "canAddFile", + pass => [ 3, $user{"2"}, ], + fail => [ 1, ], +}); +$maker->run; + diff --git a/t/Asset/Wobject/GalleryAlbum/rss.t b/t/Asset/Wobject/GalleryAlbum/rss.t new file mode 100644 index 000000000..1f96b786c --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/rss.t @@ -0,0 +1,66 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the rss view of GalleryAlbums + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use WebGUI::Test::Maker::HTML; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::HTML->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); +my @photos; +for my $i ( 0 .. 5 ) { + $photos[ $i ] + = $album->addChild({ + className => "WebGUI::Asset::File::Image::Photo", + filename => "$i.jpg", + }); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Test www_viewRss + diff --git a/t/Asset/Wobject/GalleryAlbum/slideshow.t b/t/Asset/Wobject/GalleryAlbum/slideshow.t new file mode 100644 index 000000000..36513b46e --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/slideshow.t @@ -0,0 +1,69 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the slideshow view of GalleryAlbums + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use WebGUI::Test::Maker::HTML; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::HTML->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); +my @photos; +for my $i ( 0 .. 5 ) { + $photos[ $i ] + = $album->addChild({ + className => "WebGUI::Asset::File::Image::Photo", + filename => "$i.jpg", + }); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Test view_slideshow + +#---------------------------------------------------------------------------- +# Test www_slideshow + diff --git a/t/Asset/Wobject/GalleryAlbum/thumbnails.t b/t/Asset/Wobject/GalleryAlbum/thumbnails.t new file mode 100644 index 000000000..578dcd34c --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/thumbnails.t @@ -0,0 +1,69 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../../lib"; + +## The goal of this test is to test the thumbnails view of GalleryAlbums + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use WebGUI::Test::Maker::HTML; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::HTML->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 7, # Everyone + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); +my @photos; +for my $i ( 0 .. 5 ) { + $photos[ $i ] + = $album->addChild({ + className => "WebGUI::Asset::File::Image::Photo", + filename => "$i.jpg", + }); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Test view_thumbnails + +#---------------------------------------------------------------------------- +# Test www_thumbnails + diff --git a/t/Asset/Wobject/GalleryAlbum/view.t b/t/Asset/Wobject/GalleryAlbum/view.t new file mode 100644 index 000000000..b12a5f56e --- /dev/null +++ b/t/Asset/Wobject/GalleryAlbum/view.t @@ -0,0 +1,132 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../../../lib"; + +## The goal of this test is to test the default view and associated subs + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use Test::Deep; +use WebGUI::Test::Maker::HTML; + +#---------------------------------------------------------------------------- +# Init +my $maker = WebGUI::Test::Maker::HTML->new; +my $session = WebGUI::Test->session; +my $node = WebGUI::Asset->getImportNode($session); +my $versionTag = WebGUI::VersionTag->getWorking($session); +$versionTag->set({name=>"Album Test"}); +my $gallery + = $node->addChild({ + className => "WebGUI::Asset::Wobject::Gallery", + groupIdAddComment => 2, # Registered Users + groupIdAddFile => 2, # Registered Users + groupIdView => 2, # Registered Users + groupIdEdit => 3, # Admins + ownerUserId => 3, # Admin + }); +my $album + = $gallery->addChild({ + className => "WebGUI::Asset::Wobject::GalleryAlbum", + ownerUserId => "3", # Admin + }); +my @photos; +for my $i ( 0 .. 5 ) { + $photos[ $i ] + = $album->addChild({ + className => "WebGUI::Asset::File::Image::Photo", + filename => "$i.jpg", + }); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $versionTag->rollback(); +} + +#---------------------------------------------------------------------------- +# Tests +plan no_plan => 1; + +#---------------------------------------------------------------------------- +# Test getFileIds and getFilePaginator +cmp_bag( $album->getFileIds, [ map { $_->getId } @photos ] ); + +my $p = $album->getFilePaginator; +isa_ok( $p, "WebGUI::Paginator" ); +cmp_deeply( $p->getPageData, subbagof( map { $_->getId } @photos ) ); + +#---------------------------------------------------------------------------- +# Test getTemplateVars + +# Is a superset of Asset->get +# NOTE: url is Asset->getUrl +cmp_deeply( $album->getTemplateVars, superhashof( { %{$album->get}, url => $album->getUrl, } ) ); + +# Contains specific keys/values +my $expected = { + "url_addPhoto" + => all( + re( qr/className=WebGUI::Asset::File::Image::Photo/ ), + re( qr/func=add/ ), + re( $album->getUrl ), + ), + "url_addNoClass" + => all( + re( $album->getUrl ), + re( qr/func=add$/ ), + ), + "url_slideshow" + => all( + re( $album->getUrl ), + re( qr/func=slideshow/ ), + ), + "url_thumbnails" + => all( + re( $album->getUrl ), + re( qr/func=thumbnails/ ), + ), + "url_viewRss" + => all( + re( $album->getUrl ), + re( qr/func=viewRss/ ), + ), + "ownerUsername" + => WebGUI::User->new($session, 3)->username, +}; + +cmp_deeply( $album->getTemplateVars, superhashof( $expected ) ); + +#---------------------------------------------------------------------------- +# Test appendTemplateVarsFileLoop +$expected = { + "file_loop" => bag( map { $_->getTemplateVars } @photos ), +}; +cmp_deeply( + $album->appendTemplateVarsFileLoop({},$self->getFilePaginator->getPageData), + $expected +); + +#---------------------------------------------------------------------------- +# Test www_view() for those without permission to view +$maker->prepare({ + object => $album, + method => "www_view", + test_privilege => "insufficient", +}); +$maker->run; + diff --git a/t/Form.t b/t/Form.t new file mode 100644 index 000000000..01838ddd1 --- /dev/null +++ b/t/Form.t @@ -0,0 +1,104 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/lib"; +use Test::More; +use Test::Deep; +use WebGUI::Test; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + +#---------------------------------------------------------------------------- +# Cleanup +END { + +} + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 5; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# Test the formHeader method + +ok( + !eval{ WebGUI::Form::formHeader( "" ); 1 }, + "formHeader() dies if first parameter is not WebGUI Session", +); + +ok( + !eval{ WebGUI::Form::formHeader( $session, ['foo'] ); 1 }, + "formHeader() dies if second parameter is not hash reference", +); + +# Test the defaults for formHeader() +my $testDefaults = all( + re( q{]*>} ), + re( q{action=} ), + re( q{enctype="multipart/form-data"} ), + re( q{method="post"} ), +); + +cmp_deeply( + WebGUI::Form::formHeader( $session ), + $testDefaults, + "formHeader called without an options hashref", +); + +# Test options passed into formHeader() +my $testWithOptions = all( + re( q{]*>} ), + re( q{action="action"} ), + re( q{enctype="enctype"} ), + re( q{method="method"} ), +); + +cmp_deeply( + WebGUI::Form::formHeader( $session, { + action => "action", + enctype => "enctype", + method => "method", + } ), + $testWithOptions, + "formHeader called with an options hashref", +); + +# Test "action" option containing query parameters +my $testHiddenElements = all( + re( q{ "action?func=edit;a=1&b=2", + }), + $testHiddenElements, + "formHeader 'action' option containing query parameters", +); + +#---------------------------------------------------------------------------- + +TODO: { + local $TODO = "Some things on the TODO list"; + # Test the formFooter method + # Test that the autohandler works properly +} diff --git a/t/Form/SelectRichEditor.t b/t/Form/SelectRichEditor.t new file mode 100644 index 000000000..60c04e88a --- /dev/null +++ b/t/Form/SelectRichEditor.t @@ -0,0 +1,59 @@ +# $vim: syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2007 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 FindBin; +use strict; +use lib "$FindBin::Bin/../lib"; + +## The goal of this test is to test the SelectRichEditor form control + +use Scalar::Util qw( blessed ); +use WebGUI::Test; +use WebGUI::Session; +use Test::More; +use Test::Deep; + +use WebGUI::Form::SelectRichEditor; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $root = WebGUI::Asset->getRoot( $session ); + +#---------------------------------------------------------------------------- +# Cleanup +END { + +} + +#---------------------------------------------------------------------------- +# Tests +plan tests => 1; + +#---------------------------------------------------------------------------- +# Test that SelectRichEditor control contains all RichEdit assets. +my $richEditAssets + = $root->getLineage( ['descendants'], { + returnObjects => 1, + includeOnlyClasses => ['WebGUI::Asset::RichEdit'], + }); +my $richEditOptions + = { + map { $_->getId => $_->get("title") } @$richEditAssets + }; + +my $control + = WebGUI::Form::SelectRichEditor->new( $session, { name => "richEditId" } ); +cmp_deeply( + $control->get("options"), + $richEditOptions, + "SelectRichEditor control has options for all Rich Editors in this site", +); diff --git a/t/Storage.t b/t/Storage.t index 42e47b354..34fa1917c 100644 --- a/t/Storage.t +++ b/t/Storage.t @@ -227,6 +227,16 @@ ok (-e $storage1->getPath("testfile-hash.file"), 'addFileFromHashRef creates fil my $thawedHash = $storage1->getFileContentsAsHashref('testfile-hash.file'); cmp_deeply($storageHash, $thawedHash, 'getFileContentsAsHashref: thawed hash correctly'); +#################################################### +# +# copyFile +# +#################################################### + +$storage1->copyFile("testfile-hash.file", "testfile-hash-copied.file"); +ok (-e $storage1->getPath("testfile-hash-copied.file"),'copyFile created file with new name'); +ok (-e $storage1->getPath("testfile-hash.file"), "copyFile original file still exists"); + #################################################### # # renameFile diff --git a/t/_test.skeleton b/t/_test.skeleton index 55ab13a23..bf4795eab 100644 --- a/t/_test.skeleton +++ b/t/_test.skeleton @@ -1,24 +1,40 @@ +# vim:syntax=perl #------------------------------------------------------------------- -# WebGUI is Copyright 2001-2006 Plain Black Corporation. +# WebGUI is Copyright 2001-2007 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 -#------------------------------------------------------------------- +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# use FindBin; use strict; use lib "$FindBin::Bin/lib"; -use WebGUI::Test; +use Test::More; use WebGUI::Session; +use WebGUI::Test; -# load your modules here +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; -use Test::More tests => 1; # increment this value for each test you create +#---------------------------------------------------------------------------- +# Cleanup +END { -my $session = WebGUI::Test->session; +} +#---------------------------------------------------------------------------- +# Tests + +plan tests => 1; # Increment this number for each test you create + +#---------------------------------------------------------------------------- # put your tests here diff --git a/t/lib/WebGUI/Test/Maker/HTML.pm b/t/lib/WebGUI/Test/Maker/HTML.pm index 123aa8871..404be27d0 100644 --- a/t/lib/WebGUI/Test/Maker/HTML.pm +++ b/t/lib/WebGUI/Test/Maker/HTML.pm @@ -73,6 +73,8 @@ Create a new WebGUI::Test::Maker::HTML object. Get a setting. Set L for a list of settings. +=cut + #---------------------------------------------------------------------------- =head2 plan diff --git a/t/lib/WebGUI/Test/Maker/Permission.pm b/t/lib/WebGUI/Test/Maker/Permission.pm index 926b82d24..f57a23423 100644 --- a/t/lib/WebGUI/Test/Maker/Permission.pm +++ b/t/lib/WebGUI/Test/Maker/Permission.pm @@ -55,9 +55,13 @@ Test::More Create a new WebGUI::Test::Maker::Permission object. +=cut + =head2 get -Get a setting. Set L for a list of settings. +Get a setting. See C for a list of settings. + +=cut #---------------------------------------------------------------------------- @@ -116,11 +120,13 @@ The permissions method to test =item pass -An array reference of userIds that should pass the permissions test. +An array reference of userIds or WebGUI::User objects that should pass the +permissions test. =item fail -An array reference of userIds that should fail the permissions test. +An array reference of userIds or WebGUI::User objects that should fail the +permissions test. =back @@ -146,6 +152,16 @@ sub prepare { croak("Couldn't prepare: Test $test_num, fail is not an array reference") if $test->{fail} && ref $test->{fail} ne "ARRAY"; + # Make sure pass and fail arrayrefs are userIds + for my $array ( $test->{pass, fail} ) { + for ( my $i = 0; $i < @$array; $i++ ) { + # If is a User object, replace with userId + if ( blessed $array->[$i] && $array->[$i]->isa("WebGUI::User") ) { + $array->[$i] = $array->[$i]->userId; + } + } + } + push @{$self->{_tests}}, $test; } diff --git a/t/supporting_collateral/elephant_images.zip b/t/supporting_collateral/elephant_images.zip new file mode 100644 index 0000000000000000000000000000000000000000..a04bc6e69d2256a7419d40f26c08ea7544a2c97c GIT binary patch literal 50141 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 literal 0 HcmV?d00001