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 000000000..5bd7d736b Binary files /dev/null and b/docs/upgrades/packages-7.9.3/default-wiki-front-page.wgpkg differ 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