Tags:
create new tag
view all tags

Module name TWiki::Store StoreDotPm
Location TWIKIROOT/lib/TWiki/Store.pm
Summary This module implements the storage backend
Primary Author NicholasLee / JohnTalintyre
CVS history CVS:lib/TWiki/Store.pm
CVS alpha CVSget:lib/TWiki/Store.pm
Contributing authors (see CVS History)
Is Class NO
First Release to be filled out
File Hierarchy
  TWIKIROOT
  lib
  TWiki
  Store.pm

Purpose

This module hosts the backend implementation. Currently it is hardwired to RCS-like backends.

Used by

This module is primarily used by FuncDotPm and TWikiDotPm.

Please see CodevDocumentationProject and CodevDocumentationProjectDev to comment on the format of these pages.

Note: Below documentation is extracted from the currently installed TWiki::Store Perl module, which is done by the PerlDocPlugin

package TWiki::Store

This module hosts the generic storage backend. This module provides the interface layer between the "real" store provider - which is hidden behind a handler - and the rest of the system. it is responsible for checking for topic existance, access permissions, and all the other general admin tasks that are common to all store implementations.

This module knows nothing about how the data is actually stored - that knowledge is entirely encapsulated in the handlers.

The general contract for methods in the class requires that errors are signalled using exceptions. TWiki::AccessControlException is used for access control exceptions, and Error::Simple for all other types of error.

ClassMethod new($session)

Construct a Store module, linking in the chosen sub-implementation.

=begin twiki

ObjectMethod finish()

Break circular references.

ObjectMethod readTopic($user, $web, $topic, $version) -> ($metaObject, $text)

Reads the given version of a topic and it's meta-data. If the version is undef, then read the most recent version. The version number must be an integer, or undef for the latest version.

if $user is defined, view permission will be required for the topic read to be successful. Access control violations are flagged by a TWiki::AccessControlException. Permissions are checked for the user name passed in.

If the topic contains a web specification (is of the form Web.Topic) the web specification will override whatever is passed in $web.

The metadata and topic text are returned separately, with the metadata in a TWiki::Meta object. (The topic text is, as usual, just a string.)

ObjectMethod _findAttachments($session, $web, $topic, $knownAttachments) -> @attachmentsFoundInPub

Synchronise the attachment list with what's actually on disk Returns an ARRAY of FILEATTACHMENTs. These can be put in the new meta using meta->put('FILEATTACHMENTS', $tree)

This function is only called when the AutoAttachPubFiles configuration option is set.

IDEA On Windows machines where the underlying filesystem can store arbitary meta data against files, this might replace/fulfil the COMMENT purpose

TODO consider logging when things are added to metadata

ObjectMethod readTopicRaw( $user, $web, $topic, $version ) -> $topicText

Reads the given version of a topic, without separating out any embedded meta-data. If the version is undef, then read the most recent version. The version number must be an integer or undef.

If $user is defined, view permission will be required for the topic read to be successful. Access control violations are flagged by a TWiki::AccessControlException. Permissions are checked for the user name passed in.

If the topic contains a web specification (is of the form Web.Topic) the web specification will override whatever is passed in $web.

SMELL: DO NOT CALL THIS METHOD UNLESS YOU HAVE NO CHOICE. This method breaks encapsulation of the store, as it assumes meta is stored embedded in the text. Other implementors of store will be forced to insert meta-data to ensure correct operation of View raw=debug and the 'repRev' mode of Edit.

$web and $topic must be untainted.

ObjectMethod moveAttachment( $oldWeb, $oldTopic, $oldAttachment, $newWeb, $newTopic, $newAttachment, $user )

Move an attachment from one topic to another.

The caller to this routine should check that all topics are valid.

All parameters must be defined, and must be untainted.

ObjectMethod getAttachmentStream( $user, $web, $topic, $attName ) -> \*STREAM

  • $user - the user doing the reading, or undef if no access checks
  • $web - The web
  • $topic - The topic
  • $attName - Name of the attachment

Open a standard input stream from an attachment.

If $user is defined, view permission will be required for the topic read to be successful. Access control violations and errors will cause exceptions to be thrown.

Permissions are checked for the user name passed in.

ObjectMethod getAttachmentList($web, $topic)

returns @($attachmentName => [stat]) for any given web, topic

ObjectMethod attachmentExists( $web, $topic, $att ) -> $boolean

Determine if the attachment already exists on the given topic

ObjectMethod _removeAutoAttachmentsFromMeta

This is where we are going to remove from meta any entry that is marked as an automatic attachment.

ObjectMethod moveTopic( $oldWeb, $oldTopic, $newWeb, $newTopic, $user )

All parameters must be defined and must be untainted.

ObjectMethod moveWeb( $oldWeb, $newWeb, $user )

Move a web.

All parrameters must be defined and must be untainted.

ObjectMethod readAttachment( $user, $web, $topic, $attachment, $theRev ) -> $text

Read the given version of an attachment, returning the content.

View permission on the topic is required for the read to be successful. Access control violations are flagged by a TWiki::AccessControlException. Permissions are checked for the user passed in.

If $theRev is not given, the most recent rev is assumed.

ObjectMethod getRevisionNumber ( $web, $topic, $attachment ) -> $integer

Get the revision number of the most recent revision. Returns the integer revision number or '' if the topic doesn't exist.

WORKS FOR ATTACHMENTS AS WELL AS TOPICS

ObjectMethod getWorkArea( $key ) -> $directorypath

Gets a private directory uniquely identified by $key. The directory is intended as a work area for plugins. The directory will exist.

ObjectMethod getRevisionDiff ( $user, $web, $topic, $rev1, $rev2, $contextLines ) -> \@diffArray

Return reference to an array of [ diffType, $right, $left ]

  • $user - the user id, or undef to suppress access control checks
  • $web - the web
  • $topic - the topic
  • $rev1 Integer revision number
  • $rev2 Integer revision number
  • $contextLines - number of lines of context required

ObjectMethod getRevisionInfo($web, $topic, $rev, $attachment) -> ( $date, $user, $rev, $comment )

Get revision info of a topic.

  • $web Web name, optional, e.g. 'Main'
  • $topic Topic name, required, e.g. 'TokyoOffice'
  • $rev revision number. If 0, undef, or out-of-range, will get info about the most recent revision.
  • $attachment attachment filename; undef for a topic
Return list with: ( last update date, last user id, =
$date in epochSec
$user user object
$rev the revision number
$comment WHAT COMMENT?
e.g. =( 1234561, 'phoeny', 5, 'no comment' )

NOTE NOTE NOTE if you are working within the TWiki code DO NOT USE THIS FUNCTION FOR GETTING REVISION INFO OF TOPICS - use TWiki::Meta::getRevisionInfo instead. This is essential to allow clean transition to a topic object model later, and avoids the risk of confusion coming from meta and Store revision information being out of step. (it's OK to use it for attachments)

StaticMethod dataEncode( $uncoded ) -> $coded

Encode meta-data fields, escaping out selected characters. The encoding is chosen to avoid problems with parsing the attribute values, while minimising the number of characters encoded so searches can still work (fairly) sensibly.

The encoding has to be exported because TWiki (and plugins) use encoded field data in other places e.g. RDiff, mainly as a shorthand for the properly parsed meta object. Some day we may be able to eliminate that....

StaticMethod dataDecode( $encoded ) -> $decoded

Decode escapes in a string that was encoded using dataEncode

The encoding has to be exported because TWiki (and plugins) use encoded field data in other places e.g. RDiff, mainly as a shorthand for the properly parsed meta object. Some day we may be able to eliminate that....

ObjectMethod saveTopic( $user, $web, $topic, $text, $meta, $options )

  • $user - user doing the saving (object)
  • $web - web for topic
  • $topic - topic to atach to
  • $text - topic text
  • $meta - topic meta-data
  • $options - Ref to hash of options
$options may include:
dontlog don't log this change in twiki log
hide if the attachment is to be hidden in normal topic view
comment comment for save
file Temporary file name to upload
minor True if this is a minor change (used in log)
savecmd Save command
forcedate grr
unlock  

Save a new revision of the topic, calling plugins handlers as appropriate.

ObjectMethod saveAttachment ($web, $topic, $attachment, $user, $opts )

  • $user - user doing the saving
  • $web - web for topic
  • $topic - topic to atach to
  • $attachment - name of the attachment
  • $opts - Ref to hash of options
$opts may include:
dontlog don't log this change in twiki log
comment comment for save
hide if the attachment is to be hidden in normal topic view
stream Stream of file to upload
file Name of a file to use for the attachment data. ignored is stream is set.
filepath Client path to file
filesize Size of uploaded data
filedate Date
tmpFilename Pathname of the server file the stream is attached to. Required if stream is set.

Saves a new revision of the attachment, invoking plugin handlers as appropriate.

If file is not set, this is a properties-only save.

ObjectMethod repRev( $user, $web, $topic, $text, $meta, $options )

Replace last (top) revision with different text.

Parameters and return value as saveTopic, except

  • $options - as for saveTopic, with the extra option:
    • timetravel - if we want to force the deposited revision to look as much like the revision specified in $rev as possible.
    • operation - set to the name of the operation performing the save. This is used only in the log, and is normally cmd or save. It defaults to save.

Used to try to avoid the deposition of 'unecessary' revisions, for example where a user quickly goes back and fixes a spelling error.

Also provided as a means for administrators to rewrite history (timetravel).

It is up to the store implementation if this is different to a normal save or not.

ObjectMethod delRev( $user, $web, $topic, $text, $meta, $options )

Parameters and return value as saveTopic.

Provided as a means for administrators to rewrite history.

Delete last entry in repository, restoring the previous revision.

It is up to the store implementation whether this actually does delete a revision or not; some implementations will simply promote the previous revision up to the head.

ObjectMethod lockTopic( $web, $topic )

Grab a topic lock on the given topic. A topic lock will cause other processes that also try to claim a lock to block. A lock has a maximum lifetime of 2 minutes, so operations on a locked topic must be completed within that time. You cannot rely on the lock timeout clearing the lock, though; that should always be done by calling unlockTopic. The best thing to do is to guard the locked section with a try..finally clause. See man Error for more info.

Topic locks are used to make store operations atomic. They are note the locks used when a topic is edited; those are Leases (see getLease)

ObjectMethod unlockTopic( $user, $web, $topic )

Release the topic lock on the given topic. A topic lock will cause other processes that also try to claim a lock to block. It is important to release a topic lock after a guard section is complete. This should normally be done in a 'finally' block. See man Error for more info.

Topic locks are used to make store operations atomic. They are note the locks used when a topic is edited; those are Leases (see getLease)

ObjectMethod webExists( $web ) -> $boolean

Test if web exists

  • $web - Web name, required, e.g. 'Sandbox'

A web has to have a preferences topic to be a web.

ObjectMethod topicExists( $web, $topic ) -> $boolean

Test if topic exists

  • $web - Web name, optional, e.g. 'Main'
  • $topic - Topic name, required, e.g. 'TokyoOffice', or "Main.TokyoOffice"

Warning: topicExists does not call ( $web, $topic ) = $this->{session}->normalizeWebTopicName( $web, $topic ); for you (it'd make TWiki even slower) so make sure you do so.

ObjectMethod getTopicParent ( $web, $topic ) -> $string

Get the name of the topic parent. Needs to be fast because of use by Render.pm.

ObjectMethod getTopicLatestRevTime ( $web, $topic ) -> $epochSecs

Get an approximate rev time for the latest rev of the topic. This method is used to optimise searching. Needs to be as fast as possible.

ObjectMethod eachChange( $web, $time ) -> $iterator

Get an iterator over the list of all the changes in the given web between $time and now. $time is a time in seconds since 1st Jan 1970, and is not guaranteed to return any changes that occurred before (now - {Store}{RememberChangesFor}). Changes are returned in most-recent-first order.

ObjectMethod getTopicNames( $web ) -> @topics

Get list of all topics in a web

  • $web - Web name, required, e.g. 'Sandbox'
Return a topic list, e.g. ( 'WebChanges',  'WebHome', 'WebIndex', 'WebNotify' )

ObjectMethod getListOfWebs( $filter, $web ) -> @webNames

Gets a list of webs. If $web is not specified or null, top level webs are returned. If the site allows hiearchical webs ({EnableHierarchicalWebs} is true), subwebs, subsubwebs, ... are also returned. If $web is specified and non-null, the subwebs of it are returned assuming hiearchical webs are allowed.

The returned webs are filtered according to the spec in the $filter, which may include one of:

  1. 'user' (for only user webs)
  2. 'template' (for only template webs)

$filter may also contain the following words to further filter webs.

  • 'public' (eliminates webs having NOSEARCHALL set 'on')
  • 'allowed' (eliminates webs that the user is denied access to by a *WEBVIEW)
  • 'writable' (eliminates webs not writalbe on this site. This is related to ReadOnlyAndMirrorWebs.)
  • 'canmoveto' (eliminates webs to which the current web cannot be moved to. The result is equal to or a subset of the 'writable' result. This is related both ReadOnlyAndMirrorWebs and MultipleDisks.)
  • 'cancopyto' (similar to 'writable' but does shortcut for efficiency sacrificing completeness)

If $TWiki::cfg{EnableHierarchicalWebs} is set, will also list sub-webs recursively.

ObjectMethod createWeb( $user, $newWeb, $baseWeb, $opts )

$newWeb is the name of the new web.

$baseWeb is the name of an existing web (a template web). If the base web is a system web, all topics in it will be copied into the new web. If it is a normal web, only topics starting with 'Web' will be copied. If no base web is specified, an empty web (with no topics) will be created. If it is specified but does not exist, an error will be thrown.

$opts is a ref to a hash that contains settings to be modified in the web preferences topic in the new web.

ObjectMethod removeWeb( $user, $web )

  • $user - user doing the removing (for the history)
  • $web - web being removed

Destroy a web, utterly. Removed the data and attachments in the web.

Use with great care!

The web must be a known web to be removed this way.

ObjectMethod getDebugText($meta, $text) -> $text

Generate a debug text form of the text/meta, for use in debug displays, by annotating the text with meta informtion.

ObjectMethod cleanUpRevID( $rev ) -> $integer

Cleans up (maps) a user-supplied revision ID and converts it to an integer number that can be incremented to create a new revision number.

This method should be used to sanitise user-provided revision IDs.

ObjectMethod copyTopic($user, $fromweb, $fromtopic, $toweb, $totopic)

Copy a topic and all it's attendant data from one web to another.

SMELL: Does not fix up meta-data!

ObjectMethod searchMetaData($params) -> $text

Search meta-data associated with topics. Parameters are passed in the $params hash, which may contain:

type topicmoved, parent or field
topic topic to search for, for topicmoved and parent
name form field to search, for field type searches. May be a regex.
value form field value. May be a regex.
title Title prepended to the returned search results
default defualt value if there are no results
web web to search in, default is all webs
format custom format of each search hit; default "$topic"
separator separator between hits; default "$n" (newline)
The idea is that people can search for meta-data values without having to be aware of how or where meta-data is stored.

SMELL: should be replaced with a proper SQL-like search, c.f. Plugins.DBCacheContrib.

ObjectMethod searchInWebMetaData($query, $web, \@topics) -> \%matches

Search for a meta-data expression in the content of a web. $query must be a TWiki::Query object.

Returns a reference to a hash that maps the names of topics that all matched to the result of the query expression (e.g. if the query expression is 'TOPICPARENT.name' then you will get back a hash that maps topic names to their parent.

ObjectMethod searchInWebContent($searchString, $web, \@topics, \%options ) -> \%map

Search for a string in the content of a web. The search must be over all content and all formatted meta-data, though the latter search type is deprecated (use searchMetaData instead).

  • $searchString - the search string, in egrep format if regex
  • $web - The web to search in
  • \@topics - reference to a list of topics to search
  • \%options - reference to an options hash
The \%options hash may contain the following options:
  • type - if regex will perform a egrep-syntax RE search (default '')
  • casesensitive - false to ignore case (defaulkt true)
  • files_without_match - true to return files only (default false)

The return value is a reference to a hash which maps each matching topic name to a list of the lines in that topic that matched the search, as would be returned by 'grep'. If files_without_match is specified, it will return on the first match in each topic (i.e. it will return only one match per topic, and will not return matching lines).

ObjectMethod getRevisionAtTime( $web, $topic, $time ) -> $rev

  • $web - web for topic
  • $topic - topic
  • $time - time (in epoch secs) for the rev

Get the revision number of a topic at a specific time. Returns a single-digit rev number or undef if it couldn't be determined (either because the topic isn't that old, or there was a problem)

ObjectMethod getLease( $web, $topic ) -> $lease

  • $web - web for topic
  • $topic - topic

If there is an lease on the topic, return the lease, otherwise undef. A lease is a block of meta-information about a topic that can be recovered (this is a hash containing user, taken and expires). Leases are taken out when a topic is edited. Only one lease can be active on a topic at a time. Leases are used to warn if another user is already editing a topic.

ObjectMethod setLease( $web, $topic, $user, $length )

Take out an lease on the given topic for this user for $length seconds.

See getLease for more details about Leases.

ObjectMethod clearLease( $web, $topic )

Cancel the current lease.

See getLease for more details about Leases.

ObjectMethod removeSpuriousLeases( $web )

Remove leases that are not related to a topic. These can get left behind in some store implementations when a topic is created, but never saved.

ObjectMethod getDiskInfo($web, $site, [$diskID]) -> ($dataDir, $pubDir, $diskID)

Called only if $TWiki::cfg{MultipleDisks} is true.

ObjectMethod getDiskList() -> ('', 1, 2, ...)

Contributors:
-- MartinCleaver - 23 Jun 2002
-- PeterThoeny - 02 Feb 2004

Discussions

I've resurrected this topic to raise the question of feasibility of using CgiWiki's backend.

I see the CgiWiki classes as a fast route to implementing ModularStorageBackend

-- MartinCleaver - 06 May 2003

I'm unclear how the existance of another Wiki's backend code would help with TWiki. A green field development, yes, converting an existing code base with lots of users that want less upgrade hassle, I don't see how.

If someone really wants to try a new backend I don't think it's that difficult. Certainly the hooks are there for trying something instead of RCS, as there are already two independent implementions - mind you finding all the code in TWiki core and plugins that assumes the current file system mechanism will be a fair bit of work.

-- JohnTalintyre - 07 May 2003

My analysis of performance wrt settings has led me to examine Store.pm closely since Prefs.pm calls in it heavily.

A key observation is that 90% of the time, most of Store.pm is not used. This offers an oportunity for redesign and repartitioning.

The 90% (or better) case is driven by bin/view. I estimate on my own behaviour, and I'm a pretty regular "editor", that I view at least 10 pages - get real! more like 50 - for every one edited.

The code in Store.pm has parts that do read, parts that do write, parts that manipulate the revision store (via _getTopicHandler) and parts that move stuff around. The 90% case only reads the curent revision, which is rapidly accessible since it is the topic.txt file.

Given this:

  • The other code should be load-on-demand or in a seperate module
  • The common situation can be optimized

I don't guarentee much peformance improvement, but thinking about only loading the code that has to be run is a good exercise and usually uncovers design ideosyncracies. Thinking about partitioning and thinking about whether lib/TWiki.pm has to =use everything in lib/TWiki/ will give insights into performance.

-- AntonAylward - 14 May 2003

Testing ideas would be soooooo much easier if this were object oriented already, I'd just have to use over-rides.

I can't persuade anyone to do a OO redesign for me, can I?

-- AntonAylward - 14 May 2003

I promised to do one last year when BeijingRelease was still imminent in June. I had plenty of time then. Now I don't, and so subsequently retract my offer. (For anyone wondering why my contributions peaked again recently, I've just had a two week recess in my MBA course).

TWiki does need a redesign, it also needs a CairoCoreTeam who fed up and discontent with the existing design, not both tired and happy with making small changes. The fact that TWikiAlphas are so solid reflects that the beast never changes substantially. With the exception of the work RichardDonkin did on internationalising TWiki last year, I'd argue that they are not alpha's at all, just a progression of minor bug-fixes on a sluggish 15 month cycle.

I fear that unless the CoreTeamNominationDiscussions process gets real commitment in the form of positions taken by contributors, no real progress on the code is ever going to be made.

I'm starting to think that if I can't get the (mountain) TWiki to go to the cleaner-design of (Mohammed) CgiWiki, I'm I might give up, jump ship and help push CgiWiki to replicate the functionality found in TWiki.

-- MartinCleaver - 16 May 2003

Yes, I'm tempted too. I've got this stack of notes of the OO view of TWiki. It may be too much. The new Kwiki looks good even if the code is obscure.

-- AntonAylward - 16 May 2003

The implementation of Store.pm should be in four parts:

  1. Storage of the Topic body - Store::Topic
  2. Storage of the Meta data - Store::Meta
  3. Storage of the Preferences - Store::Pref
  4. Storage of the Access Control - Store:Access

Each storage should be uncoupled from each other and orthogonal in operation. In particular, as with regular file systems

  • It should be possible to alter the access control without altering the body
    • See chmod(1) under UNIX. The equivlent under VMS does not cause a nrew revision of the file to be created.
  • Altering the body should in no way affect access control
    • See all regular file systems
  • Revising the meta data (e.g. adding attachements) should not force a new revision of the body to be created.
    • The converse does not hold. Revising the body triggers and update to the meta data TOPICINFO

Each of these should be

  • In an AUTOLOAD form so that only the code that gets used gets compiled
  • In modules arranged so that the implementation methods can be over-ridden by plug-ins
    • The basic and accepted storage methods will be implemented by the "out of the box" methods.

-- AntonAylward - 16 May 2003

I note that saveNew (which seems to be the final deepest layer of Store.pm) does the check for whether the previous save was by the same user. Is there a way around this? In several situations I want a new revision to be saved regardless).

-- MartinCleaver - 06 Oct 2003

Maybe some others think, the following addition to the StoreDotPm is useful: Writing the DontNotify information into the logfile allow other applications (in my case a GlobalChanges shell script) to utilise it (in my case: not to show every change, only those which - according to the author - is important). Here is the diff.

-- OliverKrueger - 22 Oct 2003

CoreTeam - can this go in the core?

-- MartinCleaver - 22 Oct 2003

Follow-up in DontNotifyFlagInLogFile

-- PeterThoeny - 26 Oct 2003

The hidden text feature (see HiddenTextPluginDev) needs some assistence from StoreDotPm in order to prevent the hidden text to be compromised by users viewing the topic in raw mode. Diff is attached to HiddenTextPluginDev (Store.pm.HiddenTextPlugin.diff).

-- OliverKrueger - 05 Feb 2004

The previous version switched to viewauth if someone hit a page they couldn't see. This patch at least informs the user what they should do if they want to see content.

--- TWiki/Store.pm      2004-05-13 06:02:56.000000000 -0500
+++ TWiki/Store.pm.~1.88.~      2004-05-03 22:25:58.000000000 -0500
@@ -1325,7 +1325,7 @@

     unless( $viewAccessOK ) {
         # FIXME: TWiki::Func::readTopicText will break if the following text changes
-        $text = "No permission to read topic $theWeb.$theTopic\n";
+        $text = "No permission to read topic $theWeb.$theTopic - perhaps you need to log in?\n";
         # Could note inability to read so can divert to viewauth or similar
         $TWiki::readTopicPermissionFailed = "$TWiki::readTopicPermissionFailed $theWeb.$theTopic";
     }

  • commited smile thanks for the patch Martin -- SvenDowideit - 13 May 2004

-- MartinCleaver - 13 May 2004

readTopic() - if there is no meta data should you get "", undef, or an empty Meta object?

-- MartinCleaver - 03 Oct 2004

Ok, so you get a TWiki::Meta object. I am sorry I don't have access to update the docs.

-- MartinCleaver - 03 Oct 2004

Diagnosing readTemplate

It seemed that readTemplate was returning nothing, giving the error message that the file did not exist. This error message was wrong!

To work out readTemplate was doing, I added the following call. It is also a handy way to learn how our templating system works.

    # handle %TMPL:P{"..."}% recursively
    $result =~ s/%TMPL\:P{[\s\"]*(.*?)[\"\s]*}%/&handleTmplP($1)/geo;
    $result =~ s|^(( {3})+)|"\t" x (length($1)/3)|geom;  # leading spaces to tabs

+    diagnoseReadTemplate(\%templateVars, $result);
+    die;

    return $result;
}

And add this sub:

=pod

---++ diagnoseReadTemplate($templateVarsRef, $result)

Call this to illustrate the state of the readTemplate sub

=cut

sub diagnoseReadTemplate {
    my ($templateVarsRef, $result) = @_;
    print "Content-type: text/html\n\n";
    use Data::Dumper;

    $Data::Dumper::Pad = "                           ";
    print "<PRE>".Dumper($templateVarsRef)."</PRE><BR/><BR/><FONT COLOR=BLUE>".\
$result."</FONT><BR><BR>";

    unless ($result) {
        print "<FONT COLOR=RED>Result was empty!</FONT><BR>";
    }
}

This will show the hash that it builds, followed, in blue, by the invocation of an entry ($result) of the hash. When $result is empty your template will do nothing!

-- MartinCleaver - 16 Oct 2004

Edit | Attach | Watch | Print version | History: r23 < r22 < r21 < r20 < r19 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r23 - 2006-05-04 - SamHasler
 
  • Learn about TWiki  
  • Download TWiki
This site is powered by the TWiki collaboration platform Powered by Perl Hosted by OICcam.com Ideas, requests, problems regarding TWiki? Send feedback. Ask community in the support forum.
Copyright © 1999-2026 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.