diff --git a/data/System/TopicRecursePlugin.txt b/data/System/TopicRecursePlugin.txt new file mode 100644 index 0000000..5499fcd --- /dev/null +++ b/data/System/TopicRecursePlugin.txt @@ -0,0 +1,33 @@ +%META:TOPICPARENT{name="Plugins"}% +---+ Topic Recurse Plugin + +%SHORTDESCRIPTION% + + + +Aims to provide an alternative to: + * Foswiki:Extensions.TreePlugin, which is hard-wired to topic parent relationship, and doesn't allow delayed macros in the format string + * Foswiki:Extensions.DBCachePlugin DBRECURSE, which has a subtly different query syntax and bypasses the configured Query and Search implementations + +%INCLUDE{"VarTOPICRECURSE"}% + +---++ Installation +%$INSTALL_INSTRUCTIONS% + +---++ Plugin Info + +| Author: | Foswiki:Main.PaulHarvey | +| Copyright: | © 2010-2011, Paul.W.Harvey@csiro.au TRIN http://trin.org.au/ & %BR%\ + Centre for Australian National Biodiversity Research http://anbg.gov.au/cpbr %BR%\ + © 2008-2011, Foswiki Contributors | +| License: | GPL ([[http://www.gnu.org/copyleft/gpl.html][GNU General Public License]]) | +| Release: | %$RELEASE% | +| Version: | %$VERSION% | +| Change History: |   | +| 04 Nov 2010 (0.1) | Foswikitask:Item9874 - Initial version | +| Home: | http://foswiki.org/Extensions/%TOPIC% | +| Support: | http://foswiki.org/Support/%TOPIC% | +| Development: | http://foswiki.org/Development/%TOPIC% | diff --git a/data/System/VarTOPICRECURSE.txt b/data/System/VarTOPICRECURSE.txt new file mode 100644 index 0000000..2cd8498 --- /dev/null +++ b/data/System/VarTOPICRECURSE.txt @@ -0,0 +1,79 @@ +%META:TOPICINFO{author="ProjectContributor" date="1287902127" format="1.1" version="$Rev$"}% +%META:TOPICPARENT{name="Macros"}% +
%X% This plugin is incomplete. "%X%" against parameters, tokens means they aren't yet implemented. Lack of an %X% means the functionality exists or is untested...
+ +#VarTOPICRECURSE +---+++ TOPICRECURSE{"< topic >" ...} -- traverse related topics + * Traverse topics related by some %SYSTEMWEB%.QuerySearch expression in a recursive (tree-like) pattern, Eg. =parent.name='$super'= + * Syntax: =%TOPICRECURSE{"< topic >" ...}%= + * Supported parameters: + | *Parameter:* | *Description:* | *Default:* | + | ="Web.SomeTopic"= %BR% \ + =root="Web.SomeTopic"= | 'Root', or starting topic. The initial value of =$super= in the =query= param on the first iteration. | =%BASETOPIC%= (current topic) | + | =query="<= [[%SYSTEMWEB%.QuerySearch][QuerySearch expression]] =>"= | %SYSTEMWEB%.QuerySearch expression to apply at each recursion step; may be thought of as generating a list of children for a given parent. See [[#QueryTokens][query tokens]] for a list of special tokens allowed in the query string. | =parent.name='$supertopic'= | + | =header="..."= %BR% \ + =footer="..."= | Custom format results: see [[FormattedSearch]] for usage & examples | =*Search: '$rootquery'*$n= %BR% =*Total: $ntopics*= | + | =format="..."= %BR% \ + =formatbranch="..."= %BR% \ + =formatleaf="..."= | Custom format results: see [[FormattedSearch]] for usage & examples; "branch" format is for nodes which have descendents; "leaf" format is for terminating nodes for which there are no others below. | =$indent* [[$web.$topic][$topic]]= | + | =separator="..."= | Separator _between_ search hits. See [[#FormatTokens]] | =$n= | + | =nodelimit= | Maximum number of nodes to be displayed. 0 = no limit. | =999= | + | =depthlimit= | Maximum depth to recurse to. 0 = no limit. | =0= | + | =breadthlimit= %X% | Maximum number of nodes to be displayed _at a given depth_. 0 = no limit. | =0= | + +#QueryTokens +---++++ Query Tokens +Each "iteration" is a query that produces a list of children under a particular parent which itself is a result of a query under some super-parent (and so-on). That query may have the following tokens: +| *Name* | *Expands To* | +| =$root=, =$rootweb=, =$roottopic= | =root= param | +| =$super=, =$superweb=, =$supertopic= | The topic under consideration (which will be the parent to the topics in the query result) in a given iteration. Same as =$root[web%VBAR%topic]= on first iteration. | +| =$depth= | The =$super= topic's zero-based depth below =$root=. | +| =$nodeindex= | The =$super= topic's zero-based index among all rendered nodes of the entire =TOPICRECURSE= expression. | +| =$siblingindex= | The =$super= topic's zero-based index among its siblings. | +
%X% No other [[FormatTokens][tokens]] are supported in the =query= param
+ +#FormatTokens +---++++ Standard Tokens +| *Name* | *Expands To* | +| =$web= | Name of the web | +| =$topic= | Topic name | +| =$parent= | Name of parent topic; empty if not set | +| =$locked= %X% | LOCKED flag (if any) | +| =$date= %X% epoch seconds | Time stamp of last topic update, e.g. =%GMTIME{"$day $mon $year - $hour:$min"}%= | +| =$isodate= %X% | Time stamp of last topic update, e.g. =%GMTIME{"$year-$mo-$dayT$hour:$minZ"}%= | +| =$index= | number of total results - can be used as a running counter in the =format=, or in the =footer=. This =$index= is not affected by web based partitioning of results. | +| =$item= | the full name of a result item - in a SEARCH context, equivalent to =$web.$topic= | +| =$rev= | Number of last topic revision, e.g. =4= | +| =$username= | Login name of last topic update, e.g. =jsmith= | +| =$wikiname= | Wiki user name of last topic update, e.g. =JohnSmith= | +| =$wikiusername= | Wiki user name of last topic update, like =%USERSWEB%.JohnSmith= | +| =$createdate= %X% epoch seconds | Time stamp of topic revision 1 | +| =$createusername= | Login name of topic revision 1, e.g. =jsmith= | +| =$createwikiname= | Wiki user name of topic revision 1, e.g. =JohnSmith= | +| =$createwikiusername= | Wiki user name of topic revision 1, e.g. =%USERSWEB%.JohnSmith= | +| =$summary= %X% | Topic summary, just the plain text, all formatting and line breaks removed; up to 162 characters | +| =$formname= | The name of the form attached to the topic; empty if none | +| =$formfield(name)= %X% =(works but bypasses Foswiki::Form::...renderFor*= | The field value of a form field; for example, if FAQWhatIsWikiWiki was a search hit, =$formfield(TopicClassification)= would get expanded to =%QUERY{"'FAQWhatIsWikiWiki'/TopicClassification"}%=. This applies only to topics that have a [[DataForms][DataForm]]. For multi-line textfields new lines are replace by an HTML <br /> | +| =$ntopics= | Number of topics found in current web. This is the current topic count, not the total number of topics | +| =$nhits= | Number of hits if =multiple="on"=. Cumulative across all topics in current web. Identical to =$ntopics= unless =multiple="on"= | +%INCLUDE{"FormatTokens"}% + +---+++ =TOPICRECURSE= Format Tokens +| *Name* | *Expands To* | +| =$depth= | Branch depth, starting at 1 (the level below the root node) | +| =$indent= | 3 spaces per depth | +| =$indent(<string>)= | Repeats =<string>= _depth_ times | +| =$ntopicsdepth= %X% | Number of topics found in current web at the current _depth_ | +| =$ntopicsbranch= %X% | Number of topics found in current web in the current branch | +| =$indexdepth= %X% | Running counter of topics found in current web at the current _depth_ | +| =$indexbranch= %X% | Running counter of topics found in current web in the current branch | +| =$nodepath= %X% | Comma separated list of =$indexdepth= for all depths between the root node and current, representing the "path" by =$indexdepth= to the current node. | + +---++++ Separator tokens +| *Name* | *Expands To* | +| =$nextweb=, =$nexttopic=, =$nextwebtopic= %X% | =$web.$topic= of the next node | +| =$prevweb=, =$prevtopic=, =$prevwebtopic= %X% | =$web.$topic= of the previous node | +%STOPINCLUDE% +--- +*Related topics:* FormattedSearch, QuerySearch + diff --git a/lib/Foswiki/Plugins/TopicRecursePlugin.pm b/lib/Foswiki/Plugins/TopicRecursePlugin.pm new file mode 100644 index 0000000..11be40e --- /dev/null +++ b/lib/Foswiki/Plugins/TopicRecursePlugin.pm @@ -0,0 +1,118 @@ +# See bottom of file for default license and copyright information + +=begin TML + +---+ package TopicRecursePlugin + +=cut + +package Foswiki::Plugins::TopicRecursePlugin; +use strict; +use warnings; + +use Foswiki::Func (); # The plugins API +use Foswiki::Plugins (); # For the API version + +our $VERSION = '$Rev$ (06-11-2010)'; +our $RELEASE = '0.1.0'; + +our $SHORTDESCRIPTION = + 'Query topics recursively, inspired by DBCachePlugin\'s DBRECURSE'; +our $NO_PREFS_IN_TOPIC = 1; + +my $coreLoaded; +my $debuglevel; + +=begin TML + +---++ initPlugin($topic, $web, $user) -> $boolean + * =$topic= - the name of the topic in the current CGI query + * =$web= - the name of the web in the current CGI query + * =$user= - the login name of the user + * =$installWeb= - the name of the web the plugin topic is in + (usually the same as =$Foswiki::cfg{SystemWebName}=) +=cut + +sub initPlugin { + my ( $topic, $web, $user, $installWeb ) = @_; + + # check for Plugins.pm versions + if ( $Foswiki::Plugins::VERSION < 2.0 ) { + Foswiki::Func::writeWarning( 'Version mismatch between ', + __PACKAGE__, ' and Plugins.pm' ); + return 0; + } + + $coreLoaded = 0; + Foswiki::Func::registerTagHandler( 'TOPICRECURSE', \&_TOPICRECURSE ); + + # Plugin correctly initialized + return 1; +} + +sub _TOPICRECURSE { + my ( $session, $params, $topic, $web, $topicObject ) = @_; + + if ( not $coreLoaded ) { + require Foswiki::Plugins::TopicRecursePlugin::Core; + Foswiki::Plugins::TopicRecursePlugin::Core::init(); + $coreLoaded = 1; + } + + return Foswiki::Plugins::TopicRecursePlugin::Core::TOPICRECURSE( $session, + $params, $topic, $web, $topicObject ); +} + +sub writeDebug { + my ( $message, $method, $level, $package, $refdebuglevel ) = @_; + my @lines; + + if ( not defined $refdebuglevel ) { + $refdebuglevel = + ( $debuglevel + || $Foswiki::cfg{Plugins}{TopicRecursePlugin}{Debug} + || 0 ); + } + if ( $refdebuglevel and ( not defined $level or $level <= $refdebuglevel ) ) + { + @lines = split( /[\r\n]+/, $message ); + foreach my $line (@lines) { + my @packparts = split( /::/, ( $package || __PACKAGE__ ) ); + my $logline = '::' + . $packparts[ scalar(@packparts) - 1 ] + . "::$method():\t$line\n"; + + if ( defined &Foswiki::Func::writeDebug ) { + Foswiki::Func::writeDebug($logline); + } + else { # CLI + print STDERR $logline; + } + } + } + + return; +} + +1; + +__END__ +Foswiki - The Free and Open Source Wiki, http://foswiki.org/ + +Copyright (C) 2010-2011 Paul.W.Harvey@csiro.au, TRIN http://trin.org.au/ & +Centre for Australian National Biodiversity Research http://anbg.gov.au/cpbr +Copyright (C) 2008-2010 Foswiki Contributors. Foswiki Contributors +are listed in the AUTHORS file in the root of this distribution. +NOTE: Please extend that file, not this notice. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. For +more details read LICENSE in the root of this distribution. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +As per the GPL, removal of this notice is prohibited. diff --git a/lib/Foswiki/Plugins/TopicRecursePlugin/Config.spec b/lib/Foswiki/Plugins/TopicRecursePlugin/Config.spec new file mode 100644 index 0000000..0503b84 --- /dev/null +++ b/lib/Foswiki/Plugins/TopicRecursePlugin/Config.spec @@ -0,0 +1,8 @@ +# ---+ Extensions +# ---++ TopicRecursePlugin +# ---+++ Debug +# **NUMBER** +# 0 = no debug log output, 1 = some, 2 = lots, 3 = maximum, 4 = insane +$Foswiki::cfg{Plugins}{TopicRecursePlugin}{Debug} = 0; + +1; diff --git a/lib/Foswiki/Plugins/TopicRecursePlugin/Core.pm b/lib/Foswiki/Plugins/TopicRecursePlugin/Core.pm new file mode 100644 index 0000000..0818736 --- /dev/null +++ b/lib/Foswiki/Plugins/TopicRecursePlugin/Core.pm @@ -0,0 +1,185 @@ +# See bottom of file for default license and copyright information + +=begin TML + +---+ package TopicRecursePlugin + +=cut + +package Foswiki::Plugins::TopicRecursePlugin::Core; +use strict; +use warnings; + +use Assert; +use Data::Dumper; +use Foswiki(); +use Foswiki::Func(); +use Foswiki::Plugins::TopicRecursePlugin(); +use Foswiki::Plugins::TopicRecursePlugin::Node(); + +sub init { + + return; +} + +sub extractParams { + my ( $params, @list ) = @_; + my %args; + + foreach my $item (@list) { + $args{$item} = $params->{$item}; + } + $args{header} ||= '*Search: \'$rootquery\' from $root* $n'; + $args{format} = '$indent* [[$web.$topic][$topic]]'; + $args{formatbranch} ||= $args{format}; + $args{formatleaf} ||= $args{format}; + $args{separator} ||= '$n'; + $args{footer} ||= '*Total: $ntopics*'; + $args{nodelimit} ||= '999'; + + return \%args; +} + +sub TOPICRECURSE { + my ( $session, $params, $baseTopic, $baseWeb, $topicObject ) = @_; + my $currentWeb = $session->{webName}; + my $currentTopic = $session->{topicName}; + my %queryArgs = %{$params}; + my $rootParam = $params->{root} || $params->{_DEFAULT}; + my ( $rootWeb, $rootTopic ) = + Foswiki::Func::normalizeWebTopicName( $currentWeb, $rootParam ); + my $rootNode; + my $result; + + $rootNode = Foswiki::Plugins::TopicRecursePlugin::Node->new( + undef, undef, + webtopic => "$rootWeb.$rootTopic", + query => $params->{query} || 'parent.name=\'$supertopic\'', + queryArgs => \%queryArgs + ); + + if ($rootNode) { + writeDebug( "rootNode: $rootNode->{webtopic}", 'TOPICRECURSE', 4 ); + my $spec = extractParams( + $params, + qw(header formatbranch formatleaf separator footer), + qw(nodelimit depthlimit breadthlimit) + ); + my @renderednodes = formatNodes( $rootNode, $spec ); + my $separator = Foswiki::expandStandardEscapes( $spec->{separator} ); + writeDebug( + "Separator: $separator, rendered: " . Dumper( \@renderednodes ), + 'TOPICRECURSE', 4 ); + $result = renderNode( $rootNode, $spec->{header} ) + . join( $separator, + @renderednodes, renderNode( $rootNode, $spec->{footer} ) ); + } + else { + $result = + 'Error executing query: ' + . $params->{query} + . ''; + } + + return $result; +} + +sub formatNodes { + my ( $node, $spec ) = @_; + my @renderednodes; + + writeDebug( "$node->{webtopic}: rendering..." . Dumper($spec), + 'TOPICRECURSE', 4 ); + + # Is a branch? + if ( $node->hasNext() ) { + my $result = renderNode( $node, $spec->{formatbranch} ); + + if ( not $node->isRoot() ) { + push( @renderednodes, $result ); + } + } + else { + + # is a leaf. + my $result = renderNode( $node, $spec->{formatleaf} ); + + if ( not $node->isRoot() ) { + push( @renderednodes, $result ); + } + } + while ( $node->hasNext() ) { + my $child = $node->next(); + push( @renderednodes, formatNodes( $child, $spec ) ); + } + + return @renderednodes; +} + +sub renderNode { + my ( $node, $format ) = @_; + my %tokens = ( $node->getStandardTokens() ); + + return execFormat( $node, \%tokens, $format ); +} + +sub execFormat { + my ( $node, $tokens, $format ) = @_; + my $result = $format; + + writeDebug( "Format: $format", 'execFormat', 4 ); + $result =~ + s/(\$([a-z]+)(\(([^\)]+)\))?)\b/execToken($node, $tokens, $1, $2, $4)/ge; + $result = Foswiki::expandStandardEscapes($result); + + return $result; +} + +sub execToken { + my ( $node, $tokens, $completetoken, $tokenname, $tokenarg ) = @_; + my $result; + + if ( exists $tokens->{$tokenname} ) { + $result = $tokens->{$tokenname}; + } + elsif ( exists $tokens->{"$tokenname()"} ) { + $result = $tokens->{"$tokenname()"}->( $node, $tokenarg ); + } + else { + $result = $completetoken; + } + writeDebug( "\$$tokenname($tokenarg) = '$result' depth: $node->{depth}", + 'execToken', 4 ); + + return $result; +} + +sub writeDebug { + my ( $message, $method, $level ) = @_; + + return Foswiki::Plugins::TopicRecursePlugin::writeDebug( $message, $method, + $level, __PACKAGE__ ); +} + +1; + +__END__ +Foswiki - The Free and Open Source Wiki, http://foswiki.org/ + +Copyright (C) 2010-2011 Paul.W.Harvey@csiro.au, TRIN http://trin.org.au/ & +Centre for Australian National Biodiversity Research http://anbg.gov.au/cpbr +Copyright (C) 2008-2010 Foswiki Contributors. Foswiki Contributors +are listed in the AUTHORS file in the root of this distribution. +NOTE: Please extend that file, not this notice. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. For +more details read LICENSE in the root of this distribution. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +As per the GPL, removal of this notice is prohibited. diff --git a/lib/Foswiki/Plugins/TopicRecursePlugin/DEPENDENCIES b/lib/Foswiki/Plugins/TopicRecursePlugin/DEPENDENCIES new file mode 100644 index 0000000..e69de29 diff --git a/lib/Foswiki/Plugins/TopicRecursePlugin/MANIFEST b/lib/Foswiki/Plugins/TopicRecursePlugin/MANIFEST new file mode 100644 index 0000000..ca1f3f8 --- /dev/null +++ b/lib/Foswiki/Plugins/TopicRecursePlugin/MANIFEST @@ -0,0 +1,6 @@ +data/System/TopicRecursePlugin.txt 0644 +data/System/VarTOPICRECURSE.txt 0644 +lib/Foswiki/Plugins/TopicRecursePlugin.pm 0644 +lib/Foswiki/Plugins/TopicRecursePlugin/Config.spec 0644 +lib/Foswiki/Plugins/TopicRecursePlugin/Core.pm 0644 +lib/Foswiki/Plugins/TopicRecursePlugin/Node.pm 0644 diff --git a/lib/Foswiki/Plugins/TopicRecursePlugin/Node.pm b/lib/Foswiki/Plugins/TopicRecursePlugin/Node.pm new file mode 100644 index 0000000..99227d9 --- /dev/null +++ b/lib/Foswiki/Plugins/TopicRecursePlugin/Node.pm @@ -0,0 +1,445 @@ +# See bottom of file for default license and copyright information + +=begin TML + +---+ package TopicRecursePlugin::Node + +=cut + +package Foswiki::Plugins::TopicRecursePlugin::Node; +use strict; +use warnings; + +use Assert; +use Data::Dumper; +use Foswiki::Func(); +use Foswiki::Plugins::TopicRecursePlugin(); + +use Foswiki::Iterator(); +our @ISA = ('Foswiki::Iterator'); + +sub new { + my ( $class, $rootNode, $superNode, %args ) = @_; + my $this = bless( \%args, $class ); + + # writeDebug("args: " . Dumper(\%args), 'new', 4); + ASSERT( defined $this->{webtopic} ); + ( $this->{web}, $this->{topic} ) = + Foswiki::Func::normalizeWebTopicName( '', $this->{webtopic} ); + ASSERT( $this->{web} and $this->{topic} ); + + $this->{superNode} = $superNode; + $this->{rootNode} = $rootNode; + if ( not defined $this->{rootNode} ) { + ASSERT( not defined $this->{superNode} ); + $this->{rootNode} = $this; + $this->{superNode} = $this; + $this->{isRoot} = 1; + $this->{depth} = 0; + $this->{nodecount} = 0; + $this->{nodeindex} = 0; + $this->{siblingindex} = 0; + } + else { + ASSERT( defined $this->{superNode} ); + $this->{isRoot} = 0; + ASSERT( not defined $this->{nodecount} ); + $this->{nodeindex} = $this->{rootNode}->{nodecount}; + $this->{rootNode}->{nodecount} += 1; + $this->{depth} ||= $this->{superNode}->{depth} + 1; + writeDebug( "$this->{webtopic} has depth $this->{depth}", 'new', 4 ); + } + + # Only root node has the initial query + # Only root node has the query args + # Only root node has a total nodecount + # Root node = zero depth, nodeindex, siblingindex; subsequent nodes non-zero + writeDebug( <<"HERE", 'new', 4 ); +$this->{superNode}->{webtopic} now has child \@depth,siblingindex,nodeindex=$this->{depth},$this->{siblingindex},$this->{nodeindex}:\t$this->{webtopic} +HERE + if ( $this->{isRoot} ) { + ASSERT( $this->{query} ); + ASSERT( defined $this->{queryArgs} ); + ASSERT( defined $this->{nodecount} ); + ASSERT( + not( $this->{depth} or $this->{nodeindex} or $this->{siblingindex} ) + ); + } + else { + ASSERT( not defined $this->{query} ); + ASSERT( not defined $this->{queryArgs} ); + ASSERT( not defined $this->{nodecount} ); + ASSERT( $this->{depth} > 0 ); + ASSERT( defined $this->{nodeindex} ); + ASSERT( defined $this->{siblingindex} ); + } + ASSERT( not defined $this->{childnodes} ); + $this->{childNodes} = []; + ASSERT( not defined $this->{childCursor} ); + + # -1 because we have to ->next() to populate the first child + $this->{childCursor} = 0 - 1; + + #Lazily build query string when we call getQueryIterator() + $this->{ourQuery} ||= ''; + $this->{queryIterator} = undef; + $this->{queryIteratorExhausted} = 0; + + return $this; +} + +sub destroy { + my ($this) = @_; + + if ( $this->{topicObject} ) { + $this->{topicObject}->finish(); + } + $this->{topicObject} = undef; + $this->{topicObjectFirstRev} = undef; + $this->{web} = undef; + $this->{topic} = undef; + $this->{webtopic} = undef; + $this->{superNode} = undef; + $this->{rootNode} = undef; + $this->{isRoot} = undef; + $this->{childNodes} = undef; + $this->{childCursor} = undef; + $this->{depth} = undef; + $this->{nodecount} = undef; + $this->{nodeindex} = undef; + $this->{siblingindex} = undef; + $this->{query} = undef; + $this->{ourQuery} = undef; + $this->{queryArgs} = undef; + $this->{queryIterator} = undef; + $this->{queryIteratorExhausted} = undef; + + return; +} + +############################################################################### +# Foswiki::Iterator interface to the child nodes. +# Is there a next child node? +sub hasNext { + my ($this) = @_; + my $haveNext = 0; + my $populatedChildren = scalar( @{ $this->{childNodes} } ); + + ASSERT( $this->{childCursor} < $populatedChildren ); + + # Are we at the end of our copy of the query resultset already? + if ( $this->{childCursor} == $populatedChildren - 1 ) { + + # And does the query resultset have more to give us? + if ( not $this->{queryIteratorExhausted} ) { + my $queryArgs = $this->{rootNode}->{queryArgs}; + if ( + $this->getQueryIterator()->hasNext() + and ( + ( + not $queryArgs->{nodelimit} + or $this->{rootNode}->{nodecount} < + $queryArgs->{nodelimit} + ) + and ( not $queryArgs->{depthlimit} + or $this->{depth} < $queryArgs->{depthlimit} ) + ) + ) + { + $haveNext = 1; + writeDebug( + "$this->{webtopic} has more than " + . ( $this->{childCursor} + 1 ) + . " children", + 'hasNext', 4 + ); + } + else { + + # We were already on the last element of the resultset. + $this->{queryIteratorExhausted} = 1; + writeDebug( + "$this->{webtopic} has " + . ( $this->{childCursor} + 1 ) + . " children, no more left", + 'hasNext', 4 + ); + } + } + } + else { + $haveNext = 1; + } + + return $haveNext; +} + +# Get the next child node +sub next { + my ($this) = @_; + my $populatedChildren = scalar( @{ $this->{childNodes} } ); + my $nextNode; + + ASSERT( $this->{childCursor} < $populatedChildren ); + + # We work on the assumption that there is a next node either in our list or + # in the query iterator. So, do we need to add to list from query iterator? + if ( $this->{childCursor} == $populatedChildren - 1 ) { + my $nextWebTopic = $this->getQueryIterator()->next(); + + ASSERT($nextWebTopic); + $this->{childCursor} += 1; + $nextNode = Foswiki::Plugins::TopicRecursePlugin::Node->new( + $this->{rootNode}, + $this->{superNode}, + webtopic => $nextWebTopic, + siblingindex => $this->{childCursor}, + depth => $this->{depth} + 1 + ); + push( @{ $this->{childNodes} }, $nextNode ); + ASSERT( ( $this->{childCursor} + 1 ) == + scalar( @{ $this->{childNodes} } ) ); + } + else { + + # We already have this node + $nextNode = $this->{childNodes}->[ $this->{childCursor} ]; + $this->{childCursor} += 1; + writeDebug( "$this->{webtopic} already had child $nextNode->{webtopic}", + 'next', 4 ); + } + ASSERT($nextNode); + + return $nextNode; +} + +# Reset the child node cursor to the start +sub reset { + my ($this) = @_; + + # minus one because the first node needs a ->next(), *then* the cursor is @0 + $this->{childCursor} = 0 - 1; + + return; +} +############################################################################### + +sub getQueryString { + my ($this) = @_; + my $rootNode = $this->{rootNode}; + my $superNode = $this->{superNode}; + + if ( not $this->{ourQuery} ) { + my $query = $rootNode->{query}; + + ASSERT($query); + $query =~ s/\$root\b/$rootNode->{webtopic}/g; + $query =~ s/\$rootweb\b/$rootNode->{web}/g; + $query =~ s/\$roottopic\b/$rootNode->{topic}/g; + $query =~ s/\$super\b/$this->{webtopic}/g; + $query =~ s/\$superweb\b/$this->{web}/g; + $query =~ s/\$supertopic\b/$this->{topic}/g; + $query =~ s/\$depth\b/$this->{depth}/g; + $query =~ s/\$nodeindex\b/$this->{nodeindex}/g; + $query =~ s/\$siblingindex\b/$this->{siblingindex}/g; + $this->{ourQuery} = $query; + writeDebug( "$this->{webtopic} now has query: '$this->{ourQuery}'", + 'getQueryString', 4 ); + } + ASSERT( $this->{ourQuery} ); + + return $this->{ourQuery}; +} + +sub getQueryIterator { + my ($this) = @_; + + if ( not $this->{queryIterator} ) { + my %queryArgs = %{ $this->{rootNode}->{queryArgs} }; + + $queryArgs{web} = $this->{web}; + $this->{queryIterator} = + Foswiki::Func::query( $this->getQueryString(), undef, \%queryArgs ); + writeDebug( "$this->{webtopic} created queryIterator", + 'getQueryIterator', 4 ); + } + ASSERT( $this->{queryIterator} ); + + return $this->{queryIterator}; +} + +sub depth { + my ($this) = @_; + + return $this->{depth}; +} + +sub siblingindex { + my ($this) = @_; + + return $this->{siblingindex}; +} + +sub nodeindex { + my ($this) = @_; + + return $this->{nodeindex}; +} + +sub web { + my ($this) = @_; + + return $this->{web}; +} + +sub topic { + my ($this) = @_; + + return $this->{topic}; +} + +sub webtopic { + my ($this) = @_; + + return $this->{webtopic}; +} + +sub super { + my ($this) = @_; + + return $this->{superNode}; +} + +sub root { + my ($this) = @_; + + return $this->{rootNode}; +} + +sub isRoot { + my ($this) = @_; + + return $this->{isRoot}; +} + +sub getStandardTokens { + my ($this) = @_; + my ( $createdate, $createcuid ) = + Foswiki::Func::getRevisionInfo( $this->{web}, $this->{topic}, 1 ), + + return ( + super => $this->{superNode}->{webtopic}, + superweb => $this->{superNode}->{web}, + supertopic => $this->{superNode}->{topic}, + root => $this->{rootNode}->{webtopic}, + rootweb => $this->{rootNode}->{web}, + roottopic => $this->{rootNode}->{topic}, + web => $this->{web}, + topic => $this->{topic}, + depth => $this->{depth}, + rootquery => $this->{rootNode}->{query}, + siblingindex => $this->{siblingindex}, + nodeindex => $this->{nodeindex}, + indent => $this->buildIndent(' '), + 'indent()' => sub { $this->buildIndent(@_) }, + 'parent()' => sub { $this->getTopicObject()->getParent() }, + 'date()' => sub { $this->getTopicObject()->getRevisionInfo()->{date} }, + index => $this->{nodeindex}, + item => $this->{webtopic}, + 'rev()' => sub { $this->getTopicObject()->getLoadedRev() }, + 'username()' => + sub { $this->getTopicObject()->getRevisionInfo()->{author} }, + 'wikiname()' => sub { + Foswiki::Func::getWikiName( + $this->getTopicObject()->getRevisionInfo()->{author} ); + }, + 'wikiusername()' => sub { + Foswiki::Func::getWikiUserName( + $this->getTopicObject()->getRevisionInfo()->{author} ); + }, + 'createdate()' => + sub { $this->getTopicObject(1)->getRevisionInfo()->{date} }, + 'createusername()' => + sub { $this->getTopicObject(1)->getRevisionInfo()->{author} }, + 'createwikiname()' => sub { + Foswiki::Func::getWikiName( + $this->getTopicObject(1)->getRevisionInfo()->{author} ); + }, + 'createwikiusername()' => sub { + Foswiki::Func::getWikiUserName( + $this->getTopicObject(1)->getRevisionInfo()->{author} ); + }, + 'formname()' => sub { $this->getTopicObject()->getFormName() }, + + # TODO: Make this actually do the SEARCH equivalent (bypassing renderFor) + 'formfield()' => sub { $this->getTopicObject()->get( 'FIELD', $_[0] ) }, + ntopics => $this->{rootNode}->{nodecount}, + + # SMELL: What the... + nhits => $this->{rootNode}->{nodecount}, + ); +} + +sub getTopicObject { + my ( $this, $rev ) = @_; + my $theTopic; + + if ($rev) { + if ( not $this->{topicObjectFirstRev} ) { + $this->{topicObjectFirstRev} = + Foswiki::Func::Meta->new( $Foswiki::Plugins::SESSION, + $this->{web}, $this->{topic} ); + $this->{topicObjectFirstRev}->load($rev); + } + $theTopic = $this->{topicObjectFirstRev}; + } + elsif ( not $this->{topicObject} ) { + ( $this->{topicObject} ) = + Foswiki::Func::readTopic( $this->{web}, $this->{topic} ); + $theTopic = $this->{topicObject}; + } + + return $theTopic; +} + +sub buildIndent { + my ( $this, $string ) = @_; + my $result = ''; + + foreach ( 1 .. ( $this->{depth} ) ) { + $result .= $string; + } + writeDebug( "Did string indent: '$result'", 'buildIndent', 4 ); + + return $result; +} + +sub writeDebug { + my ( $message, $method, $level ) = @_; + + return Foswiki::Plugins::TopicRecursePlugin::writeDebug( $message, $method, + $level, __PACKAGE__ ); +} + +1; + +__END__ +Foswiki - The Free and Open Source Wiki, http://foswiki.org/ + +Copyright (C) 2010-2011 Paul.W.Harvey@csiro.au, TRIN http://trin.org.au/ & +Centre for Australian National Biodiversity Research http://anbg.gov.au/cpbr +Copyright (C) 2008-2010 Foswiki Contributors. Foswiki Contributors +are listed in the AUTHORS file in the root of this distribution. +NOTE: Please extend that file, not this notice. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. For +more details read LICENSE in the root of this distribution. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +As per the GPL, removal of this notice is prohibited. diff --git a/lib/Foswiki/Plugins/TopicRecursePlugin/build.pl b/lib/Foswiki/Plugins/TopicRecursePlugin/build.pl new file mode 100755 index 0000000..f8cb0aa --- /dev/null +++ b/lib/Foswiki/Plugins/TopicRecursePlugin/build.pl @@ -0,0 +1,55 @@ +#!/usr/bin/perl -w +# +# Example build class. Copy this file to the equivalent place in your +# plugin or contrib and edit. +# +# Read the comments at the top of lib/Foswiki/Contrib/Build.pm for +# details of how the build process works, and what files you +# have to provide and where. +# +# Requires the environment variable FOSWIKI_LIBS (a colon-separated path +# list) to be set to point at the build system and any required dependencies. +# Usage: ./build.pl [-n] [-v] [target] +# where [target] is the optional build target (build, test, +# install, release, uninstall), test is the default. +# Two command-line options are supported: +# -n Don't actually do anything, just print commands +# -v Be verbose +# + +# Standard preamble +use strict; + +BEGIN { + unshift @INC, split( /:/, $ENV{FOSWIKI_LIBS} ); +} + +use Foswiki::Contrib::Build; + +# Declare our build package +package BuildBuild; +use Foswiki::Contrib::Build; +our @ISA = qw( Foswiki::Contrib::Build ); + +sub new { + my $class = shift; + return bless( $class->SUPER::new( "QueryRecursePlugin" ), $class ); +} + +# Example: Override the build target +sub target_build { + my $this = shift; + + $this->SUPER::target_build(); + + # Do other build stuff here +} + +package main; + +# Create the build object +my $build = new BuildBuild(); + +# Build the target on the command line, or the default target +$build->build($build->{target}); +