From 55d2e510d6dea604958bcf09acb4c63958ccb9b8 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Fri, 16 Apr 2010 20:46:55 -0700 Subject: [PATCH] Add top-level keywords, which allows for hierarchial categorization of wiki pages. --- docs/changelog/7.x.x.txt | 2 + .../default-wiki-front-page.wgpkg | Bin 0 -> 1706 bytes docs/upgrades/upgrade_7.9.2-7.9.3.pl | 19 ++ lib/WebGUI/Asset/Wobject/WikiMaster.pm | 144 +++++++++- lib/WebGUI/Help/Asset_WikiMaster.pm | 20 ++ lib/WebGUI/i18n/English/Asset_WikiMaster.pm | 49 ++++ lib/WebGUI/i18n/English/WebGUI.pm | 6 + t/Asset/Wobject/WikiMaster.t | 250 ++++++++++++++++++ 8 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 docs/upgrades/packages-7.9.3/default-wiki-front-page.wgpkg create mode 100644 t/Asset/Wobject/WikiMaster.t diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index f41265ec2..37b341d2b 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -5,6 +5,8 @@ - fixed #11520: Wiki Locked - fixed Missing Template variables for the Wiki Page view template. - added #10944: Wiki Keyword Page + - added #10946: Wiki - Hierarchical Keyword Report + - added #10945: Wiki - Top-level keyword list 7.9.2 - added: Workflow to extend recurring Calendar events 2 years from the diff --git a/docs/upgrades/packages-7.9.3/default-wiki-front-page.wgpkg b/docs/upgrades/packages-7.9.3/default-wiki-front-page.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..5bd7d736b641a25cd8bc8e0921ca01a17416ba25 GIT binary patch literal 1706 zcmV;b237eViwFP!00000|Ls`qZyGlk=X3uG3u$X3HGw0%bc7)#Y0{!4UDDEZ(y}3Q zXTZgrPqr_VP5s~Z*uFa(IMQ}Yq)E$t2*i({7yq6Y|G0z7!$0+Uz0+w`D)oBMY1Q>- zJ!m#76;wR+N)Q~j8;!S20f{ z-8M%)DS=-T91ZbQUg2;K<{+RomwXu$@gCZGU}S!i{reNGu%Bo|3jnBmO{0j!n@6PbN^r&1DzWf*r=JR*CWGiK zqRMCm4QDRTs1fuO1UI4+B#D%i#|O7q9N=g{x3ETRws6P(F@uLjGtOs{_v z_ab_Wq*^iZ<4twKI96RG=yaxTs#=;mpz>WhmZ6|y62bb7>R~h!WCBXgNgB(1(|f`C z#=?#IV-PDMVyqxQ%$|9ho*!$#M;hu&s3Icvj*RIVE?g5P$xeS?;jP$mw=W-%8X!{m z2W%rMp@&vOsu=0_W(^NjW!GwY(KC_brh$)$MlI2Y*G6ZDp1LGhgtH4gCJgjJwpJlc3#zu3wViX$0b}iCEKn_rW+OWxbnBE0=zhd>Tp4VaSPA-R z?svRZNxom&0XO%>6oTr`Nh(qfz^Xl`}oNdH%W0MJ8|^?Tfa zhi)-TwKK2WY>n9kV+dvmOgL-i`*OAtPN>HUbJ&ojS;~1F?|gXyWafFZSqh1!vGWuJ={Rv|?nU zj?j`uYSu-q`gd?D(9;yubX&=s5+nrUr!yQ+iOeRI3v^AIH)j#7v716!8n8^&ttn)# z%{E58|IA8`FqOx*m_moaNU^bMasHG6yM?ENaovCoESsCT#r)Hq(yF6pZUu+Bo`!f% z;{M)-O)m+BsakP7zX^IZ07s`So1(iC_QfPExKvj>Nf{PfDe`3H3F2!B^0s?u_aOdn zM7p=@%-|1huJThuuVB$dW3FbpG;?HNw}!~!pH0D2#?#2#brZYGpT;vHr~*8Ink=rc zfHL0X6^3xx+Ad>jG$tA0s~T2Q@)N=N3xV9pH!$Rl6FY~xU2|)9yTgoIy6u_@Uh;@e zsAXl}=784vn}5=#&}&I*;99qIltraWRM z=QMf46$D`i;CL_SeOr;YOf@@vYBa4Pkso|FpIaTRuTdumB2boa_Lu z+kHK}yx4}3$_uWEt>xNl)RxK&w9QlbS+_Fy(fy)+*y?z^QjlA!Y2wSAl9%NNcq!28 zqDCC#a>kc~SQ7~ghle@*hPGThXXYjZ&$GVvWP-!2kP5;#GK|^z$l0x9_St>^FF5X9 z4A*ua7+>SJr8k?$TeEqzaZ`I6O2{Va0jovUpBc`jP71nO?xGOyc2G=eJ7A~2m<*voC3dUgXvxg)q?r??EoTfOEQdVqb1m*szN$)OFV(z*!@=;oa z=_Yr}z}=`|2Z)vLBoiJy`U2ge3t6e&K^1nv<=yOA$GD(8*#=4I?WPC+loYI=N!vFB zXssMdiNKdhfSZma4#g@_=91KJVY~sW@HBgI(LNh=RQr0>Q0dz-d;0^v4X4r68G%W+ zg5TPU%@J)bhy2p1dcz&ATJw6R?6Ng)h1PDApxtV=_4{Dw5pF`@DOq^e&GgsNX{*)2 zVwkjFjy^6=f{)j3b8(eq@#)u8pPzibk5^)kHXadpMBovD{{;en1BJ=TssJ1S06nrw ABme*a literal 0 HcmV?d00001 diff --git a/docs/upgrades/upgrade_7.9.2-7.9.3.pl b/docs/upgrades/upgrade_7.9.2-7.9.3.pl index 381c7f668..760eed9cf 100644 --- a/docs/upgrades/upgrade_7.9.2-7.9.3.pl +++ b/docs/upgrades/upgrade_7.9.2-7.9.3.pl @@ -31,6 +31,7 @@ my $quiet; # this line required my $session = start(); # this line required reindexSiteForDefaultSynopsis( $session ); +addTopLevelWikiKeywords( $session ); finish($session); # this line required @@ -44,6 +45,24 @@ finish($session); # this line required # print "DONE!\n" unless $quiet; #} +#---------------------------------------------------------------------------- +sub addTopLevelWikiKeywords { + my $session = shift; + print "\tAdding top level keywords page to WikiMaster... " unless $quiet; + + my $sth = $session->db->read('DESCRIBE `WikiMaster`'); + while (my ($col) = $sth->array) { + if ($col eq 'topLevelKeywords') { + print "Skipped.\n" unless $quiet; + return; + } + } + $session->db->write('ALTER TABLE WikiMaster ADD COLUMN topLevelKeywords LONGTEXT'); + + print "Done.\n" unless $quiet; +} + + #---------------------------------------------------------------------------- # Reindex the site to clear out default synopsis sub reindexSiteForDefaultSynopsis { diff --git a/lib/WebGUI/Asset/Wobject/WikiMaster.pm b/lib/WebGUI/Asset/Wobject/WikiMaster.pm index 093033c5f..35256ffd2 100644 --- a/lib/WebGUI/Asset/Wobject/WikiMaster.pm +++ b/lib/WebGUI/Asset/Wobject/WikiMaster.pm @@ -22,6 +22,8 @@ use WebGUI::International; use WebGUI::Utility; use HTML::Parser; use URI::Escape; +use WebGUI::Form; +use Clone qw/clone/; #------------------------------------------------------------------- @@ -43,12 +45,29 @@ sub appendFeaturedPageVars { #------------------------------------------------------------------- +=head2 appendKeywordPageVars ( var ) + +Append the template variables to C for keyword (catagory) pages. + +=cut + +sub appendKeywordPageVars { + my ( $self, $var ) = @_; + my $session = $self->session; + my $topKeywords = $self->getTopLevelKeywordsList; + my $keywordHierarchy = $self->getKeywordHierarchy( $topKeywords, ); + $var->{keywords_loop} = $self->getKeywordVariables( $keywordHierarchy ); + return $var; +} + +#------------------------------------------------------------------- + =head2 appendMostPopular ($var, [ $limit ]) =head3 $var A hash reference of template variables. An array reference containing the most popular wiki pages -in order of popularity. +in order of popularity will be appended to it. =head3 $limit @@ -425,6 +444,13 @@ sub definition { label=>$i18n->get('filter code'), hoverHelp=>$i18n->get('filter code description'), }, + topLevelKeywords =>{ + fieldType => "keywords", + defaultValue => '', + tab => 'properties', + label => $i18n->get('top level keywords'), + hoverHelp => $i18n->get('top level keywords description'), + }, ); push @$definition, @@ -461,6 +487,108 @@ sub getFeaturedPageIds { #------------------------------------------------------------------- +=head2 getKeywordHierarchy ( $keywords, $seen ) + +Starting with the top level keywords, return the hierarchy of keywords as a recursive arrayref of hashrefs. +The traversal is left-most, depth first. + +The hierarchy data structure that looks like this: + + [ + { + title => 'title', # same as the keyword, since this is a keyword (category) page + url => 'url', # url from the keyword page, via getUrl so it contains the gateway URL + # If a keyword page does not exist for the keyword, this key/value pair will not be present. + children => [ # Array reference of sub-categories referenced by this category + { # If there are no children, this key/value pair will not be present + ... + } + ] + } + ] + +=head3 $keywords + +An array reference of keywords. If this is blank, then it will use the top level keywords from +itself as a default. + +=head3 $seen + +A hash reference that keeps track of which keywords have already been seen. This prevents +infinite loops from happening during the traversal. + +=cut + +sub getKeywordHierarchy { + my ( $self, $keywords, $seen ) = @_; + my $session = $self->session; + my $hierarchy = []; + $keywords ||= $self->getTopLevelKeywordsList; + $seen ||= {}; + KEYWORD: foreach my $keyword (sort @{ $keywords }) { + my $page = $self->getLineage(['children'], { + returnObjects => 1, + whereClause => 'assetData.title = '.$session->db->quote($keyword), + limit => 1, + includeOnlyClasses => [qw/WebGUI::Asset::WikiPage/], + })->[0]; + if (! $page) { + push @{ $hierarchy }, { title => $keyword, url => '', }; + next KEYWORD; + } + my $datum = { + title => $keyword, ##Note, same as keyword + url => $page->getUrl, + }; + ##Prevent recursion if seen again + if (! $seen->{$keyword}++) { + my $children = $self->getKeywordHierarchy(WebGUI::Keyword::string2list($page->get('keywords')), $seen, ); + if (@{ $children } ) { + $datum->{children} = $children; + } + } + push @{ $hierarchy }, $datum; + } + return $hierarchy; +} + +#------------------------------------------------------------------- + +=head2 getKeywordVariables ( $hierarchy, $level ) + +Take a data structure representing a hierarchy of keywords, and append template variables +to them similar to a Navigation so you can build useful things with them. + +=head3 $hierarchy + +A data structure similar to that produced by getKeywordHierarchy + +=head3 $level + +The current level in any part of the hierarchy. + +=cut + +sub getKeywordVariables { + my ( $self, $hierarchy, $level ) = @_; + $level ||= 0; + my $variables = []; + + KEYWORD: foreach my $member (@{ $hierarchy }) { + my $varBlock = clone $member; + $varBlock->{level} = $level; + $varBlock->{indent_loop} = [ map { { indent => $_ } } 1..$level ]; + delete $varBlock->{children}; + push @{$variables}, $varBlock; + if ( exists $member->{children} ) { + push @{$variables}, @{ $self->getKeywordVariables($member->{children}, $level+1) }; + } + } + return $variables; +} + +#------------------------------------------------------------------- + =head2 getRssFeedItems () Returns an array reference of hash references. Each hash reference has a title, @@ -520,6 +648,19 @@ sub getTemplateVars { return $var; } +#---------------------------------------------------------------------------- + +=head2 getTopLevelKeywordsList ( ) + +Return the top level keywords as an array reference. + +=cut + +sub getTopLevelKeywordsList { + my ( $self ) = @_; + return WebGUI::Keyword::string2list($self->get('topLevelKeywords')); +} + #------------------------------------------------------------------- =head2 prepareView @@ -610,6 +751,7 @@ sub view { $self->appendSearchBoxVars($var); $self->appendRecentChanges($var, $self->get('recentChangesCountFront')); $self->appendMostPopular($var, $self->get('mostPopularCountFront')); + $self->appendKeywordPageVars($var); return $self->processTemplate($var, undef, $template); } diff --git a/lib/WebGUI/Help/Asset_WikiMaster.pm b/lib/WebGUI/Help/Asset_WikiMaster.pm index 3c4d3d8ec..03b375a6b 100644 --- a/lib/WebGUI/Help/Asset_WikiMaster.pm +++ b/lib/WebGUI/Help/Asset_WikiMaster.pm @@ -102,6 +102,26 @@ our $HELP = { { 'name' => 'recentChangesLabel variable', }, { 'name' => 'addPageUrl', }, { 'name' => 'addPageLabel', }, + { 'name' => 'keywords_loop', + 'variables' => [ + { 'name' => 'title', + 'description' => 'keyword title', + }, + { 'name' => 'url', + 'description' => 'keyword url', + }, + { 'name' => 'level', + 'description' => 'keyword level', + }, + { 'name' => 'indent_loop', + 'variables' => [ + { 'name' => 'indent', + 'description' => 'keyword indent', + }, + ], + }, + ], + }, ], fields => [], related => [], diff --git a/lib/WebGUI/i18n/English/Asset_WikiMaster.pm b/lib/WebGUI/i18n/English/Asset_WikiMaster.pm index 7fb044c8a..a275ecc0e 100644 --- a/lib/WebGUI/i18n/English/Asset_WikiMaster.pm +++ b/lib/WebGUI/i18n/English/Asset_WikiMaster.pm @@ -42,6 +42,18 @@ our $I18N = { context => q|Hover help for edit wobject screen|, }, + 'top level keywords' => { + message => q|Top Level Keywords|, + lastUpdated => 0, + context => q|Label for edit wobject screen|, + }, + + 'top level keywords description' => { + message => q|These keywords provide the root for the hierarchial keyword display.|, + lastUpdated => 0, + context => q|Hover help for edit wobject screen|, + }, + 'content filter' => { message => q|Use Content Filter?|, lastUpdated => 0, @@ -523,6 +535,43 @@ listing of pages that are related to a specific keyword?| }, lastUpdated => 0, context => q{Label for link to unsubscribe from e-mail notifications}, }, + + 'keywords_loop' => { + message => q{A loop containing all the top level keywords, links to their keyword pages, and all sub pages below them.}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'keyword title' => { + message => q{The name of a keyword.}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'keyword url' => { + message => q{The URL to the keyword page for that keyword. If no page exists, this variable will be empty.}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'keyword level' => { + message => q{The depth of this keyword. Top-level keywords for the wiki are level 0.}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'indent_loop' => { + message => q{A loop that runs 1 time for each level.}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + + 'keyword indent' => { + message => q{The loop iterator for the indent_loop.}, + lastUpdated => 0, + context => q{Help for template variable}, + }, + }; 1; diff --git a/lib/WebGUI/i18n/English/WebGUI.pm b/lib/WebGUI/i18n/English/WebGUI.pm index 2ae04ff8b..621dcdeee 100644 --- a/lib/WebGUI/i18n/English/WebGUI.pm +++ b/lib/WebGUI/i18n/English/WebGUI.pm @@ -4676,6 +4676,12 @@ Users may override this setting in their profile. context => 'Message shown to the user when data is being loaded, typically via AJAX, like in the Survey.' }, + 'Go' => { + message => 'Go', + lastUpdated => 0, + context => 'Label for buttons that take you someplace else' + }, + }; 1; diff --git a/t/Asset/Wobject/WikiMaster.t b/t/Asset/Wobject/WikiMaster.t new file mode 100644 index 000000000..5c60959c9 --- /dev/null +++ b/t/Asset/Wobject/WikiMaster.t @@ -0,0 +1,250 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------ +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------ + +# Test the featured page of the Wiki +# +# + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use Test::More; +use Test::Differences; +use Test::Deep; +use Data::Dumper; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $import = WebGUI::Asset->getImportNode( $session ); + +my @childCoda = (undef, undef, { skipAutoCommitWorkflows => 1, skipNotification => 1, } ); +my @revCoda = (undef, { skipAutoCommitWorkflows => 1, skipNotification => 1, } ); + +my $wiki + = $import->addChild( { + className => 'WebGUI::Asset::Wobject::WikiMaster', + topLevelKeywords => 'criminals,inmates,staff', + url => 'testwiki', + title => 'testwiki', + }, @childCoda ); + +my $wikitag = WebGUI::VersionTag->getWorking( $session ); +$wikitag->commit; +WebGUI::Test->addToCleanup($wikitag); +$wiki = $wiki->cloneFromDb; + +my %page_set = (); + +foreach my $keywords (qw/staff inmates criminals/) { + $page_set{$keywords} = $wiki->addChild({ + className => 'WebGUI::Asset::WikiPage', + title => $keywords, + }, @childCoda); +} + +my $tag_set1 = WebGUI::VersionTag->getWorking($session); +$tag_set1->commit; +WebGUI::Test->addToCleanup($tag_set1); + + +#---------------------------------------------------------------------------- +# Tests + +plan tests => 11; # Increment this number for each test you create + +#---------------------------------------------------------------------------- +# + +is $wiki->get('topLevelKeywords'), 'criminals,inmates,staff', 'checking wiki setup'; +cmp_deeply($wiki->getTopLevelKeywordsList, [qw/criminals inmates staff/], 'getTopLevelKeywordList returns keywords'); + +cmp_deeply( + $wiki->getKeywordHierarchy(), + [ + superhashof({ title => 'criminals', }), + superhashof({ title => 'inmates', }), + superhashof({ title => 'staff', }), + ], + "getKeywordHierarchy, simple setup", +); + +my $hierarchy = $wiki->getKeywordHierarchy(); +my $variables = $wiki->getKeywordVariables($hierarchy); + +cmp_deeply( + $hierarchy->[0], + { + title => 'criminals', + url => '/testwiki/criminals', + }, + "getKeywordVariables, does not alter the original hierarchy passed in", +); + +cmp_deeply( + $variables, + [ + { + title => 'criminals', + url => '/testwiki/criminals', + level => 0, + indent_loop => [], + }, + { + title => 'inmates', + url => '/testwiki/inmates', + level => 0, + indent_loop => [], + }, + { + title => 'staff', + url => '/testwiki/staff', + level => 0, + indent_loop => [], + }, + ], + "... variables", +); + +$wiki->update({topLevelKeywords => 'criminals,criminals,inmates,staff'}); + +is $wiki->get('topLevelKeywords'), 'criminals,criminals,inmates,staff', 'checking wiki setup 2'; +cmp_deeply($wiki->getTopLevelKeywordsList, [qw/criminals criminals inmates staff/], 'getTopLevelKeywordList returns keywords, even with duplicates'); + +cmp_deeply( + $wiki->getKeywordHierarchy(), + [ + superhashof({ title => 'criminals', }), + superhashof({ title => 'criminals', }), + superhashof({ title => 'inmates', }), + superhashof({ title => 'staff', }), + ], + "getKeywordHierarchy, simple setup, duplicates listed", +); + +$wiki->update({topLevelKeywords => 'criminals,inmates,staff'}); +$page_set{criminals}->update({keywords => 'red,andy'}); +$page_set{inmates}->update({keywords => 'brooks,heywood'}); +$page_set{staff}->update({keywords => 'norton,hadley'}); + +foreach my $title (qw/red andy brooks heywood norton hadley/) { + $page_set{$title} = $wiki->addChild({ + className => 'WebGUI::Asset::WikiPage', + title => $title, + }, @childCoda); +} + +my $tag_set2 = WebGUI::VersionTag->getWorking($session); +$tag_set2->commit; +WebGUI::Test->addToCleanup($tag_set2); + +cmp_bag( + $wiki->getKeywordHierarchy(), + [ + { + title => 'criminals', url => '/testwiki/criminals', + children => bag( + superhashof({ title => 'red', }), + superhashof({ title => 'andy', }), + ), + }, + { + title => 'inmates', url => '/testwiki/inmates', + children => bag( + superhashof({ title => 'heywood', }), + superhashof({ title => 'brooks', }), + ), + }, + { + title => 'staff', url => '/testwiki/staff', + children => bag( + superhashof({ title => 'norton', }), + superhashof({ title => 'hadley', }), + ), + }, + ], + "getKeywordHierarchy: simple hierarchy", +); + +##Check depth-first display, and try to make a keyword loop +$page_set{andy}->update({keywords => 'criminals,inmates'}); +$page_set{brooks}->update({keywords => 'criminals'}); + +my $tag_set3 = WebGUI::VersionTag->getWorking($session); +$tag_set3->commit; +WebGUI::Test->addToCleanup($tag_set3); + +cmp_bag( + $wiki->getKeywordHierarchy(), + [ + superhashof({ + title => 'criminals', + children => bag( + superhashof({ + title => 'andy', + children => bag( + superhashof({ + title => 'inmates', + children => bag( + superhashof({ title => 'heywood', }), + superhashof({ + title => 'brooks', + children => bag( + superhashof({ title => 'criminals', }), + ), + }), + ), + }), + superhashof({ title => 'criminals', }), + ), + }), + superhashof({ title => 'red', }), + ), + }), + superhashof({ + title => 'inmates', + }), + superhashof({ + title => 'staff', + children => bag( + superhashof({ title => 'norton', }), + superhashof({ title => 'hadley', }), + ), + }), + ], + "getKeywordHierarchy: complex hierarcy, depth-first display and loop handling", +); + +cmp_deeply( + $wiki->getKeywordVariables([ + { + title => 'title 0', url => 'url 0', + children => [ { + title => 'title 1', url => 'url 1', + children => [ { + title => 'title 2', url => 'url 2', + }, ], + }, ], + }, + ]), + [ + { title => 'title 0', url => 'url 0', level => 0, indent_loop => [], }, + { title => 'title 1', url => 'url 1', level => 1, indent_loop => [{indent => 1}], }, + { title => 'title 2', url => 'url 2', level => 2, indent_loop => [{indent => 1,}, {indent => 2,},], }, + ], + 'getKeywordVariables: checking deeply' +); + +$page_set{criminals}->update({keywords => 'red,andy,tommy'}); + +#vim:ft=perl