1 rizwank 1.1 # Module of TWiki Collaboration Platform, http://TWiki.org/
2 #
3 # Copyright (C) 1999-2004 Peter Thoeny, peter@thoeny.com
4 #
5 # For licensing info read license.txt file in the TWiki root.
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details, published at
15 # http://www.gnu.org/copyleft/gpl.html
16 #
17 # Notes:
18 # - Latest version at http://twiki.org/
19 # - Installation instructions in $dataDir/Main/TWikiDocumentation.txt
20 # - Customize variables in TWiki.cfg when installing TWiki.
21 # - Optionally change TWiki.pm for custom extensions of rendering rules.
22 rizwank 1.1 # - Upgrading TWiki is easy as long as you do not customize TWiki.pm.
23 # - Check web server error logs for errors, i.e. % tail /var/log/httpd/error_log
24 #
25 # 20000917 - NicholasLee : Split file/storage related functions from wiki.pm
26 # 200105 - JohnTalintyre : AttachmentsUnderRevisionControl & meta data in topics
27 # 200106 - JohnTalintyre : Added Form capability (replaces Category tables)
28 # 200401 - RafaelAlvarez : Added a new Plugin callback (afterSaveHandler)
29 =begin twiki
30
31 ---+ TWiki::Store Module
32
33 This module hosts the generic storage backend.
34
35 =cut
36
37 package TWiki::Store;
38
39 use File::Copy;
40 use Time::Local;
41
42 use strict;
43 rizwank 1.1
44 # 'Use locale' for internationalisation of Perl sorting in getTopicNames
45 # and other routines - main locale settings are done in TWiki::setupLocale
46 BEGIN {
47 # Do a dynamic 'use locale' for this module
48 if( $TWiki::useLocale ) {
49 require locale;
50 import locale ();
51 }
52 }
53
54 # FIXME: Move elsewhere?
55 # template variable hash: (built from %TMPL:DEF{"key"}% ... %TMPL:END%)
56 use vars qw( %templateVars ); # init in TWiki.pm so okay for modPerl
57
58 # ===========================
59 =pod
60
61 ---++ sub initialize ()
62
63 Not yet documented.
64 rizwank 1.1
65 =cut
66
67 sub initialize
68 {
69 %templateVars = ();
70 eval "use TWiki::Store::$TWiki::storeTopicImpl;";
71 }
72
73 =pod
74
75 ---++ sub _traceExec ()
76
77 Normally writes no output, uncomment writeDebug line to get output of all RCS etc command to debug file
78
79 =cut
80
81 sub _traceExec
82 {
83 #my( $cmd, $result ) = @_;
84 #TWiki::writeDebug( "Store exec: $cmd -> $result" );
85 rizwank 1.1 }
86
87 =pod
88
89 ---++ sub writeDebug ()
90
91 Not yet documented.
92
93 =cut
94
95 sub writeDebug
96 {
97 #TWiki::writeDebug( "Store: $_[0]" );
98 }
99
100 =pod
101
102 ---++ sub _getTopicHandler ( $web, $topic, $attachment )
103
104 Not yet documented.
105
106 rizwank 1.1 =cut
107
108 sub _getTopicHandler
109 {
110 my( $web, $topic, $attachment ) = @_;
111
112 $attachment = "" if( ! $attachment );
113
114 my $handlerName = "TWiki::Store::$TWiki::storeTopicImpl";
115
116 my $handler = $handlerName->new( $web, $topic, $attachment, @TWiki::storeSettings );
117 return $handler;
118 }
119
120
121 =pod
122
123 ---++ sub normalizeWebTopicName ( $theWeb, $theTopic )
124
125 Normalize a Web.TopicName
126 <pre>
127 rizwank 1.1 Input: Return:
128 ( "Web", "Topic" ) ( "Web", "Topic" )
129 ( "", "Topic" ) ( "Main", "Topic" )
130 ( "", "" ) ( "Main", "WebHome" )
131 ( "", "Web/Topic" ) ( "Web", "Topic" )
132 ( "", "Web.Topic" ) ( "Web", "Topic" )
133 ( "Web1", "Web2.Topic" ) ( "Web2", "Topic" )
134 </pre>
135 Note: Function renamed from getWebTopic
136
137 =cut
138
139 sub normalizeWebTopicName
140 {
141 my( $theWeb, $theTopic ) = @_;
142
143 if( $theTopic =~ m|^([^.]+)[\.\/](.*)$| ) {
144 $theWeb = $1;
145 $theTopic = $2;
146 }
147 $theWeb = $TWiki::webName unless( $theWeb );
148 rizwank 1.1 $theTopic = $TWiki::topicName unless( $theTopic );
149
150 return( $theWeb, $theTopic );
151 }
152
153
154 =pod
155
156 ---++ sub erase ( $web, $topic )
157
158 Get rid of a topic and its attachments completely
159 Intended for TEST purposes.
160 Use with GREAT CARE as file will be gone, including RCS history
161
162 =cut
163
164 sub erase
165 {
166 my( $web, $topic ) = @_;
167
168 my $topicHandler = _getTopicHandler( $web, $topic );
169 rizwank 1.1 $topicHandler->_delete();
170
171 writeLog( "erase", "$web.$topic", "" );
172 }
173
174 =pod
175
176 ---++ sub moveAttachment ( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment )
177
178 Move an attachment from one topic to another.
179 If there is a problem an error string is returned.
180 The caller to this routine should check that all topics are valid and
181 do lock on the topics.
182
183 =cut
184
185 sub moveAttachment
186 {
187 my( $oldWeb, $oldTopic, $newWeb, $newTopic, $theAttachment ) = @_;
188
189 my $topicHandler = _getTopicHandler( $oldWeb, $oldTopic, $theAttachment );
190 rizwank 1.1 my $error = $topicHandler->moveMe( $newWeb, $newTopic );
191 return $error if( $error );
192
193 # Remove file attachment from old topic
194 my( $meta, $text ) = readTopic( $oldWeb, $oldTopic );
195 my %fileAttachment = $meta->findOne( "FILEATTACHMENT", $theAttachment );
196 $meta->remove( "FILEATTACHMENT", $theAttachment );
197 $error .= saveNew( $oldWeb, $oldTopic, $text, $meta, "", "", "", "doUnlock", "dont notify", "" );
198
199 # Remove lock file
200 $topicHandler->setLock( "" );
201
202 # Add file attachment to new topic
203 ( $meta, $text ) = readTopic( $newWeb, $newTopic );
204
205 $fileAttachment{"movefrom"} = "$oldWeb.$oldTopic";
206 $fileAttachment{"moveby"} = $TWiki::userName;
207 $fileAttachment{"movedto"} = "$newWeb.$newTopic";
208 $fileAttachment{"movedwhen"} = time();
209 $meta->put( "FILEATTACHMENT", %fileAttachment );
210
211 rizwank 1.1 $error .= saveNew( $newWeb, $newTopic, $text, $meta, "", "", "", "doUnlock", "dont notify", "" );
212 # Remove lock file.
213 my $newTopicHandler = _getTopicHandler( $newWeb, $newTopic, $theAttachment );
214 $newTopicHandler->setLock( "" );
215
216 writeLog( "move", "$oldWeb.$oldTopic", "Attachment $theAttachment moved to $newWeb.$newTopic" );
217
218 return $error;
219 }
220
221 =pod
222
223 ---++ sub changeRefTo ( $text, $oldWeb, $oldTopic )
224
225 When moving a topic to another web, change within-web refs from this topic so that they'll work
226 when the topic is in the new web. I have a feeling this shouldn't be in Store.pm.
227
228 =cut
229
230 sub changeRefTo
231 {
232 rizwank 1.1 my( $text, $oldWeb, $oldTopic ) = @_;
233
234 my $preTopic = '^|[\*\s\[][-\(\s]*';
235 # I18N: match non-alpha before/after topic names
236 my $alphaNum = $TWiki::regex{mixedAlphaNum};
237 my $postTopic = '$|' . "[^${alphaNum}_.]" . '|\.\s';
238 my $metaPreTopic = '"|[\s[,\(-]';
239 my $metaPostTopic = "[^${alphaNum}_.]" . '|\.\s';
240
241 my $out = "";
242
243 # Get list of topics in $oldWeb, replace local refs to these topics with full web.topic
244 # references
245 my @topics = getTopicNames( $oldWeb );
246
247 my $insidePRE = 0;
248 my $insideVERBATIM = 0;
249 my $noAutoLink = 0;
250
251 foreach( split( /\n/, $text ) ) {
252 if( /^%META:TOPIC(INFO|MOVED)/ ) {
253 rizwank 1.1 $out .= "$_\n";
254 next;
255 }
256
257 # change state:
258 m|<pre>|i && ( $insidePRE = 1 );
259 m|</pre>|i && ( $insidePRE = 0 );
260 if( m|<verbatim>|i ) {
261 $insideVERBATIM = 1;
262 }
263 if( m|</verbatim>|i ) {
264 $insideVERBATIM = 0;
265 }
266 m|<noautolink>|i && ( $noAutoLink = 1 );
267 m|</noautolink>|i && ( $noAutoLink = 0 );
268
269 if( ! ( $insidePRE || $insideVERBATIM || $noAutoLink ) ) {
270 # Fairly inefficient, time will tell if this should be changed.
271 foreach my $topic ( @topics ) {
272 if( $topic ne $oldTopic ) {
273 if( /^%META:/ ) {
274 rizwank 1.1 s/^(%META:FILEATTACHMENT.*? user\=\")(\w)/$1$TWiki::TranslationToken$2/;
275 s/^(%META:TOPICMOVED.*? by\=\")(\w)/$1$TWiki::TranslationToken$2/;
276 s/($metaPreTopic)\Q$topic\E(?=$metaPostTopic)/$1$oldWeb.$topic/g;
277 s/$TWiki::TranslationToken//;
278 } else {
279 s/($preTopic)\Q$topic\E(?=$postTopic)/$1$oldWeb.$topic/g;
280 }
281 }
282 }
283 }
284 $out .= "$_\n";
285 }
286
287 return $out;
288 }
289
290
291 =pod
292
293 ---++ sub renameTopic ( $oldWeb, $oldTopic, $newWeb, $newTopic, $doChangeRefTo )
294
295 rizwank 1.1 Rename a topic, allowing for transfer between Webs
296 It is the responsibility of the caller to check for existence of webs,
297 topics & lock taken for topic
298
299 =cut
300
301 sub renameTopic
302 {
303 my( $oldWeb, $oldTopic, $newWeb, $newTopic, $doChangeRefTo ) = @_;
304
305 my $topicHandler = _getTopicHandler( $oldWeb, $oldTopic, "" );
306 my $error = $topicHandler->moveMe( $newWeb, $newTopic );
307
308 if( ! $error ) {
309 my $time = time();
310 my $user = $TWiki::userName;
311 my @args = (
312 "from" => "$oldWeb.$oldTopic",
313 "to" => "$newWeb.$newTopic",
314 "date" => "$time",
315 "by" => "$user" );
316 rizwank 1.1 my $fullText = readTopicRaw( $newWeb, $newTopic );
317 if( ( $oldWeb ne $newWeb ) && $doChangeRefTo ) {
318 $fullText = changeRefTo( $fullText, $oldWeb, $oldTopic );
319 }
320 my ( $meta, $text ) = _extractMetaData( $newWeb, $newTopic, $fullText );
321 $meta->put( "TOPICMOVED", @args );
322 saveNew( $newWeb, $newTopic, $text, $meta, "", "", "", "unlock" );
323 }
324
325 # Log rename
326 if( $TWiki::doLogRename ) {
327 writeLog( "rename", "$oldWeb.$oldTopic", "moved to $newWeb.$newTopic $error" );
328 }
329
330 # Remove old lock file
331 $topicHandler->setLock( "" );
332
333 return $error;
334 }
335
336
337 rizwank 1.1 =pod
338
339 ---++ sub updateReferingPages ( $oldWeb, $oldTopic, $wikiUserName, $newWeb, $newTopic, @refs )
340
341 Update pages that refer to the one being renamed/moved.
342
343 =cut
344
345 sub updateReferingPages
346 {
347 my ( $oldWeb, $oldTopic, $wikiUserName, $newWeb, $newTopic, @refs ) = @_;
348
349 my $lockFailure = 0;
350
351 my $result = "";
352 my $preTopic = '^|\W'; # Start of line or non-alphanumeric
353 my $postTopic = '$|\W'; # End of line or non-alphanumeric
354 my $spacedTopic = TWiki::Search::spacedTopic( $oldTopic );
355
356 while ( @refs ) {
357 my $type = shift @refs;
358 rizwank 1.1 my $item = shift @refs;
359 my( $itemWeb, $itemTopic ) = TWiki::Store::normalizeWebTopicName( "", $item );
360 if ( &TWiki::Store::topicIsLockedBy( $itemWeb, $itemTopic ) ) {
361 $lockFailure = 1;
362 } else {
363 my $resultText = "";
364 $result .= ":$item: , ";
365 #open each file, replace $topic with $newTopic
366 if ( &TWiki::Store::topicExists($itemWeb, $itemTopic) ) {
367 my $scantext = &TWiki::Store::readTopicRaw($itemWeb, $itemTopic);
368 if( ! &TWiki::Access::checkAccessPermission( "change", $wikiUserName, $scantext,
369 $itemWeb, $itemTopic ) ) {
370 # This shouldn't happen, as search will not return, but check to be on the safe side
371 &TWiki::writeWarning( "rename: attempt to change $itemWeb.$itemTopic without permission" );
372 next;
373 }
374 my $insidePRE = 0;
375 my $insideVERBATIM = 0;
376 my $noAutoLink = 0;
377 foreach( split( /\n/, $scantext ) ) {
378 if( /^%META:TOPIC(INFO|MOVED)/ ) {
379 rizwank 1.1 $resultText .= "$_\n";
380 next;
381 }
382 # FIXME This code is in far too many places - also in Search.pm and Store.pm
383 m|<pre>|i && ( $insidePRE = 1 );
384 m|</pre>|i && ( $insidePRE = 0 );
385 if( m|<verbatim>|i ) {
386 $insideVERBATIM = 1;
387 }
388 if( m|</verbatim>|i ) {
389 $insideVERBATIM = 0;
390 }
391 m|<noautolink>|i && ( $noAutoLink = 1 );
392 m|</noautolink>|i && ( $noAutoLink = 0 );
393
394 if( ! ( $insidePRE || $insideVERBATIM || $noAutoLink ) ) {
395 if( $type eq "global" ) {
396 my $insertWeb = ($itemWeb eq $newWeb) ? "" : "$newWeb.";
397 s/($preTopic)\Q$oldWeb.$oldTopic\E(?=$postTopic)/$1$insertWeb$newTopic/g;
398 } else {
399 # Only replace bare topic (i.e. not preceeded by web) if web of referring
400 rizwank 1.1 # topic is in original Web of topic that's being moved
401 if( $oldWeb eq $itemWeb ) {
402 my $insertWeb = ($oldWeb eq $newWeb) ? "" : "$newWeb.";
403 s/($preTopic)\Q$oldTopic\E(?=$postTopic)/$1$insertWeb$newTopic/g;
404 s/\[\[($spacedTopic)\]\]/[[$newTopic][$1]]/gi;
405 }
406 }
407 }
408 $resultText .= "$_\n";
409 }
410 my ( $meta, $text ) = &TWiki::Store::_extractMetaData( $itemWeb, $itemTopic, $resultText );
411 &TWiki::Store::saveTopic( $itemWeb, $itemTopic, $text, $meta, "", "unlock", "dontNotify", "" );
412 } else {
413 $result .= ";$item does not exist;";
414 }
415 }
416 }
417 return ( $lockFailure, $result );
418 }
419
420
421 rizwank 1.1 =pod
422
423 ---++ sub readTopicVersion ( $theWeb, $theTopic, $theRev )
424
425 Read a specific version of a topic
426 <pre>view: $text= &TWiki::Store::readTopicVersion( $topic, "1.$rev" );</pre>
427
428 =cut
429
430 sub readTopicVersion
431 {
432 my( $theWeb, $theTopic, $theRev ) = @_;
433 my $text = _readVersionNoMeta( $theWeb, $theTopic, $theRev );
434 my $meta = "";
435
436 ( $meta, $text ) = _extractMetaData( $theWeb, $theTopic, $text );
437
438 return( $meta, $text );
439 }
440
441 =pod
442 rizwank 1.1
443 ---++ sub _readVersionNoMeta ( $theWeb, $theTopic, $theRev )
444
445 Read a specific version of a topic
446
447 =cut
448
449 sub _readVersionNoMeta
450 {
451 my( $theWeb, $theTopic, $theRev ) = @_;
452 my $topicHandler = _getTopicHandler( $theWeb, $theTopic );
453
454 $theRev =~ s/^1\.//o;
455 return $topicHandler->getRevision( $theRev );
456 }
457
458 =pod
459
460 ---++ sub readAttachmentVersion ( $theWeb, $theTopic, $theAttachment, $theRev )
461
462 Not yet documented.
463 rizwank 1.1
464 =cut
465
466 sub readAttachmentVersion
467 {
468 my ( $theWeb, $theTopic, $theAttachment, $theRev ) = @_;
469
470 my $topicHandler = _getTopicHandler( $theWeb, $theTopic, $theAttachment );
471 $theRev =~ s/^1\.//o;
472 return $topicHandler->getRevision( $theRev );
473 }
474
475 =pod
476
477 ---++ sub getRevisionNumber ( $theWebName, $theTopic, $attachment )
478
479 Use meta information if available ...
480
481 =cut
482
483 sub getRevisionNumber
484 rizwank 1.1 {
485 my( $theWebName, $theTopic, $attachment ) = @_;
486 my $ret = getRevisionNumberX( $theWebName, $theTopic, $attachment );
487 ##TWiki::writeDebug( "Store: rev = $ret" );
488 if( ! $ret ) {
489 $ret = "1.1"; # Temporary
490 }
491
492 return $ret;
493 }
494
495
496 =pod
497
498 ---++ sub getRevisionNumberX ( $theWebName, $theTopic, $attachment )
499
500 Latest revision number. <br/>
501 Returns "" if there is no revision.
502
503 =cut
504
505 rizwank 1.1 sub getRevisionNumberX
506 {
507 my( $theWebName, $theTopic, $attachment ) = @_;
508 if( ! $theWebName ) {
509 $theWebName = $TWiki::webName;
510 }
511 if( ! $attachment ) {
512 $attachment = "";
513 }
514
515 my $topicHandler = _getTopicHandler( $theWebName, $theTopic, $attachment );
516 my $revs = $topicHandler->numRevisions();
517 $revs = "1.$revs" if( $revs );
518 return $revs;
519 }
520
521
522 =pod
523
524 ---++ sub getRevisionDiff ( $web, $topic, $rev1, $rev2, $contextLines )
525
526 rizwank 1.1 <pre>
527 rdiff: $diffArray = &TWiki::Store::getRevisionDiff( $webName, $topic, "1.$r2", "1.$r1", 3 );
528 </pre>
529 | Return: =\@diffArray= | reference to an array of [ diffType, $right, $left ] |
530
531 =cut
532
533 sub getRevisionDiff
534 {
535 my( $web, $topic, $rev1, $rev2, $contextLines ) = @_;
536
537 my $rcs = _getTopicHandler( $web, $topic );
538 my $r1 = substr( $rev1, 2 );
539 my $r2 = substr( $rev2, 2 );
540 my( $error, $diffArrayRef ) = $rcs->revisionDiff( $r1, $r2, $contextLines );
541 return $diffArrayRef;
542 }
543
544
545 # =========================
546 # Call getRevisionInfoFromMeta for faster response for topics
547 rizwank 1.1 # FIXME try and get rid of this it's a mess
548 # In direct calls changeToIsoDate always seems to be 1
549
550 =pod
551
552 ---+++ getRevisionInfo($theWebName, $theTopic, $theRev, $attachment, $topicHandler) ==> ( $date, $user, $rev, $comment )
553 | Description: | Get revision info of a topic |
554 | Parameter: =$theWebName= | Web name, optional, e.g. ="Main"= |
555 | Parameter: =$theTopic= | Topic name, required, e.g. ="TokyoOffice"= |
556 | Parameter: =$theRev= | revsion number, or tag name (can be in the format 1.2, or just the minor number) |
557 | Parameter: =$attachment= |attachment filename |
558 | Parameter: =$topicHandler= | internal store use only |
559 | Return: =( $date, $user, $rev, $comment )= | List with: ( last update date, login name of last user, minor part of top revision number ), e.g. =( 1234561, "phoeny", "5" )= |
560 | $date | in epochSec |
561 | $user | |
562 | $rev | TODO: this needs to be improves to contain the major number too (and what do we do is we have a different numbering system?) |
563 | $comment | WHAT COMMENT? |
564
565 =cut
566
567 sub getRevisionInfo
568 rizwank 1.1 {
569 my( $theWebName, $theTopic, $theRev, $attachment, $topicHandler ) = @_;
570 if( ! $theWebName ) {
571 $theWebName = $TWiki::webName;
572 }
573
574 $theRev = "" unless( $theRev );
575 $theRev =~ s/^1\.//o;
576
577 $topicHandler = _getTopicHandler( $theWebName, $theTopic, $attachment ) if( ! $topicHandler );
578 my( $rcsOut, $rev, $date, $user, $comment ) = $topicHandler->getRevisionInfo( $theRev );
579
580 return ( $date, $user, $rev, $comment );
581 }
582
583
584 =pod
585
586 ---++ sub topicIsLockedBy ( $theWeb, $theTopic )
587
588 | returns ( $lockUser, $lockTime ) | ( "", 0 ) if not locked |
589 rizwank 1.1
590 =cut
591
592 sub topicIsLockedBy
593 {
594 my( $theWeb, $theTopic ) = @_;
595
596 # pragmatic approach: Warn user if somebody else pressed the
597 # edit link within a time limit e.g. 1 hour
598
599 ( $theWeb, $theTopic ) = normalizeWebTopicName( $theWeb, $theTopic );
600
601 my $lockFilename = "$TWiki::dataDir/$theWeb/$theTopic.lock";
602 if( ( -e "$lockFilename" ) && ( $TWiki::editLockTime > 0 ) ) {
603 my $tmp = readFile( $lockFilename );
604 my( $lockUser, $lockTime ) = split( /\n/, $tmp );
605 if( $lockUser ne $TWiki::userName ) {
606 # time stamp of lock within editLockTime of current time?
607 my $systemTime = time();
608 # calculate remaining lock time in seconds
609 $lockTime = $lockTime + $TWiki::editLockTime - $systemTime;
610 rizwank 1.1 if( $lockTime > 0 ) {
611 # must warn user that it is locked
612 return( $lockUser, $lockTime );
613 }
614 }
615 }
616 return( "", 0 );
617 }
618
619
620 =pod
621
622 ---++ sub keyValue2list ( $args )
623
624 Not yet documented.
625
626 =cut
627
628 sub keyValue2list
629 {
630 my( $args ) = @_;
631 rizwank 1.1
632 my @res = ();
633
634 # Format of data is name="value" name1="value1" [...]
635 while( $args =~ s/\s*([^=]+)=\"([^"]*)\"//o ) { #" avoid confusing syntax highlighters
636 push @res, $1;
637 push @res, $2;
638 }
639
640 return @res;
641 }
642
643
644 =pod
645
646 ---++ sub metaAddTopicData ( $web, $topic, $rev, $meta, $forceDate, $forceUser )
647
648 Not yet documented.
649
650 =cut
651
652 rizwank 1.1 sub metaAddTopicData
653 {
654 my( $web, $topic, $rev, $meta, $forceDate, $forceUser ) = @_;
655
656 my $time = $forceDate || time();
657 my $user = $forceUser || $TWiki::userName;
658
659 my @args = (
660 "version" => "$rev",
661 "date" => "$time",
662 "author" => "$user",
663 "format" => $TWiki::formatVersion );
664 $meta->put( "TOPICINFO", @args );
665 }
666
667
668 =pod
669
670 ---++ sub saveTopicNew ( $web, $topic, $text, $metaData, $saveCmd, $doUnlock, $dontNotify, $dontLogSave )
671
672 Not yet documented.
673 rizwank 1.1
674 =cut
675
676 sub saveTopicNew
677 {
678 my( $web, $topic, $text, $metaData, $saveCmd, $doUnlock, $dontNotify, $dontLogSave ) = @_;
679 my $attachment = "";
680 my $meta = TWiki::Meta->new();
681 $meta->readArray( @$metaData );
682 saveNew( $web, $topic, $text, $meta, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify );
683 }
684
685 =pod
686
687 ---++ sub saveTopic ( $web, $topic, $text, $meta, $saveCmd, $doUnlock, $dontNotify, $dontLogSave, $forceDate )
688
689 Not yet documented.
690
691 =cut
692
693 sub saveTopic
694 rizwank 1.1 {
695 my( $web, $topic, $text, $meta, $saveCmd, $doUnlock, $dontNotify, $dontLogSave, $forceDate ) = @_;
696 my $attachment = "";
697 my $comment = "";
698
699 # FIXME: Inefficient code that hides meta data from Plugin callback
700 $text = $meta->write( $text ); # add meta data for Plugin callback
701 TWiki::Plugins::beforeSaveHandler( $text, $topic, $web );
702 $meta = TWiki::Meta->remove(); # remove all meta data
703 $text = $meta->read( $text ); # restore meta data
704
705 my $error = saveNew( $web, $topic, $text, $meta, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify, $comment, $forceDate );
706 $text = $meta->write( $text ); # add meta data for Plugin callback
707 TWiki::Plugins::afterSaveHandler( $text, $topic, $web, $error );
708 return $error;
709 }
710
711 =pod
712
713 ---++ sub saveAttachment ()
714
715 rizwank 1.1 Not yet documented.
716
717 =cut
718
719 sub saveAttachment
720 {
721 my( $web, $topic, $text, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify, $theComment, $theTmpFilename,
722 $forceDate) = @_;
723
724 writeDebug("saveAttachment");
725 my %attachmentAtt = ( attachment => $attachment,
726 tmpFilename => $theTmpFilename,
727 comment => $theComment,
728 user => $TWiki::userName
729 ); # pass a hash of stuff using keys
730
731 my $topicHandler = _getTopicHandler( $web, $topic, $attachment );
732 TWiki::Plugins::beforeAttachmentSaveHandler( \%attachmentAtt, $topic, $web );
733
734 $theComment = $attachmentAtt{comment};
735 my $error = $topicHandler->addRevision( $theTmpFilename, $theComment, $TWiki::userName );
736 rizwank 1.1 TWiki::Plugins::afterAttachmentSaveHandler( \%attachmentAtt, $topic, $web, $error );
737
738 $topicHandler->setLock( ! $doUnlock );
739
740 return $error;
741 }
742
743
744 =pod
745
746 ---++ sub save ( $web, $topic, $text, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify, $theComment, $forceDate )
747
748 Not yet documented.
749
750 =cut
751
752 sub save
753 {
754 my( $web, $topic, $text, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify, $theComment, $forceDate ) = @_;
755
756 # FIXME get rid of this routine
757 rizwank 1.1
758 my $meta = TWiki::Meta->new();
759
760 return saveNew( $web, $topic, $text, $meta, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify, $theComment, $forceDate );
761 }
762
763
764 =pod
765
766 ---++ sub _addMeta ( $web, $topic, $text, $attachment, $nextRev, $meta, $forceDate, $forceUser )
767
768 Add meta data to the topic.
769
770 =cut
771
772 sub _addMeta
773 {
774 my( $web, $topic, $text, $attachment, $nextRev, $meta, $forceDate, $forceUser ) = @_;
775
776 if( ! $attachment ) {
777 $nextRev = "1.1" if( ! $nextRev );
778 rizwank 1.1 metaAddTopicData( $web, $topic, $nextRev, $meta, $forceDate, $forceUser );
779 $text = $meta->write( $text );
780 }
781
782 return $text;
783 }
784
785
786 =pod
787
788 ---++ sub saveNew ( $web, $topic, $text, $meta, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify, $theComment, $forceDate )
789
790 Return non-null string if there is an (RCS) error. <br/>
791 FIXME: does rev info from meta work if user saves a topic with no change?
792
793 =cut
794
795 sub saveNew
796 {
797 my( $web, $topic, $text, $meta, $saveCmd, $attachment, $dontLogSave, $doUnlock, $dontNotify, $theComment, $forceDate ) = @_;
798 my $time = time();
799 rizwank 1.1 my $tmp = "";
800 my $rcsError = "";
801 my $dataError = "";
802
803 my $topicHandler = _getTopicHandler( $web, $topic, $attachment );
804
805 my $currentRev = $topicHandler->numRevisions();
806
807 my $nextRev = "";
808 if( ! $currentRev ) {
809 $nextRev = "1.1";
810 } else {
811 $nextRev = "1." . ($currentRev + 1);
812 }
813 $currentRev = "1." . $currentRev if( $currentRev );
814
815 if( ! $attachment ) {
816 # RCS requires a newline for the last line,
817 # so add newline if needed
818 $text =~ s/([^\n\r])$/$1\n/os;
819 }
820 rizwank 1.1
821 if( ! $theComment ) {
822 $theComment = "none";
823 }
824
825 #### Normal Save
826 if( ! $saveCmd ) {
827 $saveCmd = "";
828
829 # get time stamp of existing file
830 my $mtime1 = $topicHandler->getTimestamp();
831 my $mtime2 = time();
832
833 # how close time stamp of existing file to now?
834 if( abs( $mtime2 - $mtime1 ) < $TWiki::editLockTime ) {
835 # FIXME no previous topic?
836 my( $date, $user ) = getRevisionInfo( $web, $topic, $currentRev, $attachment, $topicHandler );
837 # TWiki::writeDebug( "Store::save date = $date" );
838 # same user?
839 if( ( $TWiki::doKeepRevIfEditLock ) && ( $user eq $TWiki::userName ) && $currentRev ) { # TODO shouldn't this also check to see if its still locked?
840 # replace last repository entry
841 rizwank 1.1 $saveCmd = "repRev";
842 if( $attachment ) {
843 $saveCmd = ""; # cmd option not supported for attachments.
844 }
845 }
846 }
847
848 if( $saveCmd ne "repRev" ) {
849 $text = _addMeta( $web, $topic, $text, $attachment, $nextRev, $meta, $forceDate );
850
851 $dataError = $topicHandler->addRevision( $text, $theComment, $TWiki::userName );
852 return $dataError if( $dataError );
853
854 $topicHandler->setLock( ! $doUnlock );
855
856 if( ! $dontNotify ) {
857 # update .changes
858 my( $fdate, $fuser, $frev ) = getRevisionInfo( $web, $topic, "", $attachment, $topicHandler );
859 $fdate = ""; # suppress warning
860 $fuser = ""; # suppress warning
861
862 rizwank 1.1 my @foo = split( /\n/, &readFile( "$TWiki::dataDir/$TWiki::webName/.changes" ) );
863 if( $#foo > 100 ) {
864 shift( @foo);
865 }
866 push( @foo, "$topic\t$TWiki::userName\t$time\t$frev" );
867 open( FILE, ">$TWiki::dataDir/$TWiki::webName/.changes" );
868 print FILE join( "\n", @foo )."\n";
869 close(FILE);
870 }
871
872 if( ( $TWiki::doLogTopicSave ) && ! ( $dontLogSave ) ) {
873 # write log entry
874 my $extra = "";
875 $extra .= "dontNotify" if( $dontNotify );
876 writeLog( "save", "$TWiki::webName.$topic", $extra );
877 }
878 }
879 }
880
881 #### Replace Revision Save
882 if( $saveCmd eq "repRev" ) {
883 rizwank 1.1 # fix topic by replacing last revision, but do not update .changes
884
885 # save topic with same userName and date
886 # FIXME why should date be the same if same user replacing with editLockTime?
887 my( $date, $user, $rev ) = getRevisionInfo( $web, $topic, "", $attachment, $topicHandler );
888 $rev = "1.$rev";
889
890 # Add one minute (make small difference, but not too big for notification)
891 my $epochSec = $date + 60; #TODO: this seems wrong. if editLockTime == 3600, and i edit, 30 mins later... why would the recorded date be 29 mins too early?
892 $text = _addMeta( $web, $topic, $text, $attachment, $rev,
893 $meta, $epochSec, $user );
894
895 my $dataError = $topicHandler->replaceRevision( $text, $theComment, $user, $epochSec );
896 return $dataError if( $dataError );
897 $topicHandler->setLock( ! $doUnlock );
898
899 if( ( $TWiki::doLogTopicSave ) && ! ( $dontLogSave ) ) {
900 # write log entry
901 my $extra = "repRev $rev ";
902 $extra .= &TWiki::userToWikiName( $user );
903 $date = &TWiki::formatTime( $epochSec, "rcs", "gmtime" );
904 rizwank 1.1 $extra .= " $date";
905 $extra .= " dontNotify" if( $dontNotify );
906 writeLog( "save", "$TWiki::webName.$topic", $extra );
907 }
908 }
909
910 #### Delete Revision
911 if( $saveCmd eq "delRev" ) {
912 # delete last revision
913
914 # delete last entry in repository (unlock, delete revision, lock operation)
915 my $rev = getRevisionNumber( $web, $topic );
916 if( $rev eq "1.1" ) {
917 # can't delete initial revision
918 return;
919 }
920 my $dataError = $topicHandler->deleteRevision();
921 return $dataError if( $dataError );
922
923 # restore last topic from repository
924 $topicHandler->restoreLatestRevision();
925 rizwank 1.1 $topicHandler->setLock( ! $doUnlock );
926
927 # delete entry in .changes : FIXME
928
929 if( $TWiki::doLogTopicSave ) {
930 # write log entry
931 writeLog( "cmd", "$TWiki::webName.$topic", "delRev $rev" );
932 }
933 }
934 return ""; # all is well
935 }
936
937 =pod
938
939 ---++ sub writeLog ( $action, $webTopic, $extra, $user )
940
941 Not yet documented.
942
943 =cut
944
945 sub writeLog
946 rizwank 1.1 {
947 my( $action, $webTopic, $extra, $user ) = @_;
948
949 # use local time for log, not UTC (gmtime)
950
951 my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() );
952 my( $tmon) = $TWiki::isoMonth[$mon];
953 $year = sprintf( "%.4u", $year + 1900 ); # Y2K fix
954 my $time = sprintf( "%.2u ${tmon} %.2u - %.2u:%.2u", $mday, $year, $hour, $min );
955 my $yearmonth = sprintf( "%.4u%.2u", $year, $mon+1 );
956
957 my $wuserName = $user || $TWiki::userName;
958 $wuserName = &TWiki::userToWikiName( $wuserName );
959 my $remoteAddr = $ENV{'REMOTE_ADDR'} || "";
960 my $text = "| $time | $wuserName | $action | $webTopic | $extra | $remoteAddr |";
961
962 my $filename = $TWiki::logFilename;
963 $filename =~ s/%DATE%/$yearmonth/go;
964
965 if( open( FILE, ">>$filename" ) ) {
966 print FILE "$text\n";
967 rizwank 1.1 close( FILE );
968 } else {
969 print STDERR "Couldn't write \"$text\" to $filename: $!\n";
970 }
971 }
972
973 =pod
974
975 ---++ sub saveFile ( $name, $text )
976
977 Not yet documented.
978
979 =cut
980
981 sub saveFile
982 {
983 my( $name, $text ) = @_;
984
985 umask( 002 );
986 unless ( open( FILE, ">$name" ) ) {
987 warn "Can't create file $name - $!\n";
988 rizwank 1.1 return;
989 }
990 print FILE $text;
991 close( FILE);
992 }
993
994 =pod
995
996 ---++ sub lockTopic ( $name, $doUnlock )
997
998 Not yet documented.
999
1000 =cut
1001
1002 sub lockTopic
1003 {
1004 my ( $name, $doUnlock ) = @_;
1005
1006 lockTopicNew( $TWiki::webName, $name, $doUnlock );
1007 }
1008
1009 rizwank 1.1 =pod
1010
1011 ---++ sub lockTopicNew ( $theWeb, $theTopic, $doUnlock )
1012
1013 Not yet documented. <br/>
1014 Called from rename and =TWiki::Func=
1015
1016 =cut
1017
1018 sub lockTopicNew
1019 {
1020 my( $theWeb, $theTopic, $doUnlock ) = @_;
1021
1022 ( $theWeb, $theTopic ) = normalizeWebTopicName( $theWeb, $theTopic );
1023
1024 my $topicHandler = _getTopicHandler( $theWeb, $theTopic );
1025 $topicHandler->setLock( ! $doUnlock );
1026 }
1027
1028 =pod
1029
1030 rizwank 1.1 ---++ sub removeObsoleteTopicLocks ( $web )
1031
1032 Not yet documented.
1033
1034 =cut
1035
1036 sub removeObsoleteTopicLocks
1037 {
1038 my( $web ) = @_;
1039
1040 # Clean all obsolete .lock files in a web.
1041 # This should be called regularly, best from a cron job (called from mailnotify)
1042
1043 my $webDir = "$TWiki::dataDir/$web";
1044 opendir( DIR, "$webDir" );
1045 my @fileList = grep /\.lock$/, readdir DIR;
1046 closedir DIR;
1047 my $file = "";
1048 my $pathFile = "";
1049 my $lockUser = "";
1050 my $lockTime = 0;
1051 rizwank 1.1 my $systemTime = time();
1052 foreach $file ( @fileList ) {
1053 $pathFile = "$webDir/$file";
1054 $pathFile =~ /(.*)/;
1055 $pathFile = $1; # untaint file
1056 ( $lockUser, $lockTime ) = split( /\n/, readFile( "$pathFile" ) );
1057 $lockTime = 0 unless( $lockTime );
1058
1059 # time stamp of lock over one hour of current time?
1060 if( abs( $systemTime - $lockTime ) > $TWiki::editLockTime ) {
1061 # obsolete, so delete file
1062 unlink "$pathFile";
1063 }
1064 }
1065 }
1066
1067 =pod
1068
1069 ---++ Functions: Content Handling
1070
1071 ---+++ webExists( $web ) ==> $flag
1072 rizwank 1.1
1073 | Description: | Test if web exists |
1074 | Parameter: =$web= | Web name, required, e.g. ="Sandbox"= |
1075 | Return: =$flag= | ="1"= if web exists, ="0"= if not |
1076
1077 =cut
1078
1079 sub webExists
1080 {
1081 my( $theWeb ) = @_;
1082 return -e "$TWiki::dataDir/$theWeb";
1083 }
1084
1085 =pod
1086
1087 ---+++ topicExists( $web, $topic ) ==> $flag
1088
1089 | Description: | Test if topic exists |
1090 | Parameter: =$web= | Web name, optional, e.g. ="Main"= |
1091 | Parameter: =$topic= | Topic name, required, e.g. ="TokyoOffice"=, or ="Main.TokyoOffice"= |
1092 | Return: =$flag= | ="1"= if topic exists, ="0"= if not |
1093 rizwank 1.1
1094 =cut
1095
1096 sub topicExists
1097 {
1098 my( $theWeb, $theTopic ) = @_;
1099 ( $theWeb, $theTopic ) = normalizeWebTopicName( $theWeb, $theTopic );
1100 return -e "$TWiki::dataDir/$theWeb/$theTopic.txt";
1101 }
1102
1103 =pod
1104
1105 ---++ sub getRevisionInfoFromMeta ( $web, $topic, $meta )
1106
1107 Try and get from meta information in topic, if this can't be done then use RCS.
1108 Note there is no "1." prefix to this data
1109
1110 =cut
1111
1112 sub getRevisionInfoFromMeta
1113 {
1114 rizwank 1.1 my( $web, $topic, $meta ) = @_;
1115
1116 my( $date, $author, $rev );
1117 my %topicinfo = ();
1118
1119 if( $meta ) {
1120 %topicinfo = $meta->findOne( "TOPICINFO" );
1121 }
1122
1123 if( %topicinfo ) {
1124 # Stored as meta data in topic for faster access
1125 $date = $topicinfo{"date"} ;
1126 $author = $topicinfo{"author"};
1127 my $tmp = $topicinfo{"version"};
1128 $tmp =~ /1\.(.*)/o;
1129 $rev = $1;
1130 } else {
1131 # Get data from RCS
1132 ( $date, $author, $rev ) = getRevisionInfo( $web, $topic, "" );
1133 }
1134
1135 rizwank 1.1 # writeDebug( "rev = $rev" );
1136
1137 return( $date, $author, $rev );
1138 }
1139
1140 =pod
1141
1142 ---++ sub convert2metaFormat ( $web, $topic, $text )
1143
1144 Not yet documented.
1145
1146 =cut
1147
1148 sub convert2metaFormat
1149 {
1150 my( $web, $topic, $text ) = @_;
1151
1152 my $meta = TWiki::Meta->new();
1153 $text = $meta->read( $text );
1154
1155 if ( $text =~ /<!--TWikiAttachment-->/ ) {
1156 rizwank 1.1 $text = TWiki::Attach::migrateToFileAttachmentMacro( $meta, $text );
1157 }
1158
1159 if ( $text =~ /<!--TWikiCat-->/ ) {
1160 $text = TWiki::Form::upgradeCategoryTable( $web, $topic, $meta, $text );
1161 }
1162
1163 return( $meta, $text );
1164 }
1165
1166 =pod
1167
1168 ---++ sub _extractMetaData ( $web, $topic, $fulltext )
1169
1170 Expect meta data at top of file, but willing to accept it anywhere.
1171 If we have an old file format without meta data, then convert.
1172
1173 =cut
1174
1175 sub _extractMetaData
1176 {
1177 rizwank 1.1 my( $web, $topic, $fulltext ) = @_;
1178
1179 my $meta = TWiki::Meta->new();
1180 my $text = $meta->read( $fulltext );
1181
1182
1183 # If there is no meta data then convert
1184 if( ! $meta->count( "TOPICINFO" ) ) {
1185 ( $meta, $text ) = convert2metaFormat( $web, $topic, $text );
1186 } else {
1187 my %topicinfo = $meta->findOne( "TOPICINFO" );
1188 if( $topicinfo{"format"} eq "1.0beta" ) {
1189 # This format used live at DrKW for a few months
1190 if( $text =~ /<!--TWikiCat-->/ ) {
1191 $text = TWiki::Form::upgradeCategoryTable( $web, $topic, $meta, $text );
1192 }
1193
1194 TWiki::Attach::upgradeFrom1v0beta( $meta );
1195
1196 if( $meta->count( "TOPICMOVED" ) ) {
1197 my %moved = $meta->findOne( "TOPICMOVED" );
1198 rizwank 1.1 $moved{"by"} = TWiki::wikiToUserName( $moved{"by"} );
1199 $meta->put( "TOPICMOVED", %moved );
1200 }
1201 }
1202 }
1203
1204 return( $meta, $text );
1205 }
1206
1207 =pod
1208
1209 ---++ sub getFileName ( $theWeb, $theTopic, $theAttachment )
1210
1211 Not yet documented. <br/>
1212 *FIXME - get rid of this because uses private part of handler*
1213
1214 =cut
1215
1216 sub getFileName
1217 {
1218 my( $theWeb, $theTopic, $theAttachment ) = @_;
1219 rizwank 1.1
1220 my $topicHandler = _getTopicHandler( $theWeb, $theTopic, $theAttachment );
1221 return $topicHandler->{file};
1222 }
1223
1224 =pod
1225
1226 ---++ sub readTopMeta ( $theWeb, $theTopic )
1227
1228 Just read the meta data at the top of the topic. <br/>
1229 Generalise for Codev.DataFramework, but needs to be fast because
1230 of use by view script.
1231
1232 =cut
1233
1234 sub readTopMeta
1235 {
1236 my( $theWeb, $theTopic ) = @_;
1237
1238 my $topicHandler = _getTopicHandler( $theWeb, $theTopic );
1239 my $filename = getFileName( $theWeb, $theTopic );
1240 rizwank 1.1
1241 my $data = "";
1242 my $line;
1243 $/ = "\n"; # read line by line
1244 open( IN_FILE, "<$filename" ) || return "";
1245 while( ( $line = <IN_FILE> ) ) {
1246 if( $line !~ /^%META:/ ) {
1247 last;
1248 } else {
1249 $data .= $line;
1250 }
1251 }
1252
1253 my( $meta, $text ) = _extractMetaData( $theWeb, $theTopic, $data );
1254
1255 close( IN_FILE );
1256
1257 return $meta;
1258 }
1259
1260 =pod
1261 rizwank 1.1
1262 ---++ readTopic( $web, $topic, $internal )
1263 Return value: ( $metaObject, $topicText )
1264
1265 Reads the most recent version of a topic. If $internal is false, view
1266 permission will be required for the topic read to be successful. A failed
1267 topic read is indicated by setting $TWiki::readTopicPermissionFailed.
1268
1269 The metadata and topic text are returned separately, with the metadata in a
1270 TWiki::Meta object. (The topic text is, as usual, just a string.)
1271
1272 =cut
1273
1274 sub readTopic
1275 {
1276 my( $theWeb, $theTopic, $internal ) = @_;
1277
1278 my $fullText = readTopicRaw( $theWeb, $theTopic, "", $internal );
1279
1280 my ( $meta, $text ) = _extractMetaData( $theWeb, $theTopic, $fullText );
1281
1282 rizwank 1.1 return( $meta, $text );
1283 }
1284
1285 =pod
1286
1287 ---++ sub readWebTopic ( $theWeb, $theName )
1288
1289 Not yet documented.
1290
1291 =cut
1292
1293 sub readWebTopic
1294 {
1295 my( $theWeb, $theName ) = @_;
1296 my $text = &readFile( "$TWiki::dataDir/$theWeb/$theName.txt" );
1297
1298 return $text;
1299 }
1300
1301 =pod
1302
1303 rizwank 1.1 ---++ readTopicRaw( $web, $topic, $version, $internal )
1304 Return value: $topicText
1305
1306 Reads a topic; the most recent version is used unless $version is specified.
1307 If $internal is false, view access permission will be checked. If permission
1308 is not granted, then an error message will be returned in $text, and set in
1309 $TWiki::readTopicPermissionFailed.
1310
1311 =cut
1312
1313 sub readTopicRaw
1314 {
1315 my( $theWeb, $theTopic, $theVersion, $internal ) = @_;
1316
1317 #SVEN - test if theTopic contains a webName to override $theWeb
1318 ( $theWeb, $theTopic ) = normalizeWebTopicName( $theWeb, $theTopic );
1319
1320 my $text = "";
1321 if( ! $theVersion ) {
1322 $text = &readFile( "$TWiki::dataDir/$theWeb/$theTopic.txt" );
1323 } else {
1324 rizwank 1.1 $text = _readVersionNoMeta( $theWeb, $theTopic, $theVersion);
1325 }
1326
1327 my $viewAccessOK = 1;
1328 unless( $internal ) {
1329 $viewAccessOK = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName, $text, $theTopic, $theWeb );
1330 # TWiki::writeDebug( "readTopicRaw $viewAccessOK $TWiki::wikiUserName $theWeb $theTopic" );
1331 }
1332
1333 unless( $viewAccessOK ) {
1334 # FIXME: TWiki::Func::readTopicText will break if the following text changes
1335 $text = "No permission to read topic $theWeb.$theTopic - perhaps you need to log in?\n";
1336 # Could note inability to read so can divert to viewauth or similar
1337 $TWiki::readTopicPermissionFailed = "$TWiki::readTopicPermissionFailed $theWeb.$theTopic";
1338 }
1339
1340 return $text;
1341 }
1342
1343
1344 =pod
1345 rizwank 1.1
1346 ---++ sub readTemplateTopic ( $theTopicName )
1347
1348 Not yet documented.
1349
1350 =cut
1351
1352 sub readTemplateTopic
1353 {
1354 my( $theTopicName ) = @_;
1355
1356 $theTopicName =~ s/$TWiki::securityFilter//go; # zap anything suspicious
1357
1358 # try to read in current web, if not read from TWiki web
1359
1360 my $web = $TWiki::twikiWebname;
1361 if( topicExists( $TWiki::webName, $theTopicName ) ) {
1362 $web = $TWiki::webName;
1363 }
1364 return readTopic( $web, $theTopicName );
1365 }
1366 rizwank 1.1
1367 =pod
1368
1369 ---++ _readTemplateFile ( $theName, $theSkin )
1370 Return value: raw template text, or "" if read fails
1371
1372 WARNING! THIS FUNCTION DEPENDS ON GLOBAL VARIABLES
1373
1374 PRIVATE Reads a template, constructing a candidate name for the template thus: $name.$skin.tmpl,
1375 and looking for a file of that name first in templates/$web and then if that fails in templates/.
1376 If a template is not found, tries to parse $name into a web name and a topic name, and
1377 read topic $Web.${Skin}Skin${Topic}Template. If $name does not contain a web specifier,
1378 $Web defaults to TWiki::twikiWebname. If no skin is specified, topic is ${Topic}Template.
1379 If the topic exists, checks access permissions and reads the topic
1380 without meta-data. In the event that the read fails (template not found, access permissions fail)
1381 returns the empty string "". skin, web and topic names are forced to an upper-case first character
1382 when composing user topic names.
1383
1384 =cut
1385
1386 sub _readTemplateFile
1387 rizwank 1.1 {
1388 my( $theName, $theSkin, $theWeb ) = @_;
1389 $theSkin = "" unless $theSkin; # prevent 'uninitialized value' warnings
1390
1391 # CrisBailiff, PeterThoeny 13 Jun 2000: Add security
1392 $theName =~ s/$TWiki::securityFilter//go; # zap anything suspicious
1393 $theName =~ s/\.+/\./g; # Filter out ".." from filename
1394 $theSkin =~ s/$TWiki::securityFilter//go; # zap anything suspicious
1395 $theSkin =~ s/\.+/\./g; # Filter out ".." from filename
1396
1397 my $tmplFile = "";
1398
1399 # search first in twiki/templates/Web dir
1400 # for file script(.skin).tmpl
1401 my $tmplDir = "$TWiki::templateDir/$theWeb";
1402 if( opendir( DIR, $tmplDir ) ) {
1403 # for performance use readdir, not a row of ( -e file )
1404 my @filelist = grep /^$theName\..*tmpl$/, readdir DIR;
1405 closedir DIR;
1406 $tmplFile = "$theName.$theSkin.tmpl";
1407 if( ! grep { /^$tmplFile$/ } @filelist ) {
1408 rizwank 1.1 $tmplFile = "$theName.tmpl";
1409 if( ! grep { /^$tmplFile$/ } @filelist ) {
1410 $tmplFile = "";
1411 }
1412 }
1413 if( $tmplFile ) {
1414 $tmplFile = "$tmplDir/$tmplFile";
1415 }
1416 }
1417
1418 # if not found, search in twiki/templates dir
1419 $tmplDir = $TWiki::templateDir;
1420 if( ( ! $tmplFile ) && ( opendir( DIR, $tmplDir ) ) ) {
1421 my @filelist = grep /^$theName\..*tmpl$/, readdir DIR;
1422 closedir DIR;
1423 $tmplFile = "$theName.$theSkin.tmpl";
1424 if( ! grep { /^$tmplFile$/ } @filelist ) {
1425 $tmplFile = "$theName.tmpl";
1426 if( ! grep { /^$tmplFile$/ } @filelist ) {
1427 $tmplFile = "";
1428 }
1429 rizwank 1.1 }
1430 if( $tmplFile ) {
1431 $tmplFile = "$tmplDir/$tmplFile";
1432 }
1433 }
1434
1435 # See if it is a user topic. Search first in current web
1436 # twiki web. Note that neither web nor topic may be variables when used in a template.
1437 if ( ! $tmplFile ) {
1438 if ( $theSkin ne "" ) {
1439 $theSkin = ucfirst( $theSkin ) . "Skin";
1440 }
1441
1442 my $theTopic;
1443 my $theWeb;
1444
1445 if ( $theName =~ /^(\w+)\.(\w+)$/ ) {
1446 $theWeb = ucfirst( $1 );
1447 $theTopic = ucfirst( $2 );
1448 } else {
1449 $theWeb = $TWiki::webName;
1450 rizwank 1.1 $theTopic = $theSkin . ucfirst( $theName ) . "Template";
1451 if ( !TWiki::Store::topicExists( $theWeb, $theTopic )) {
1452 $theWeb = $TWiki::twikiWebname;
1453 }
1454 }
1455
1456 if ( TWiki::Store::topicExists( $theWeb, $theTopic ) &&
1457 TWiki::Access::checkAccessPermission( "view",
1458 $TWiki::wikiUserName, "", $theTopic, $theWeb )) {
1459 my ( $meta, $text ) = TWiki::Store::readTopic( $theWeb, $theTopic, 1 );
1460 return $text;
1461 }
1462 }
1463
1464 # read the template file
1465 if( -e $tmplFile ) {
1466 return &readFile( $tmplFile );
1467 }
1468 return "";
1469 }
1470
1471 rizwank 1.1 =pod
1472
1473 ---++ sub handleTmplP ( $theVar )
1474 Return value: expanded text of the named template, as found from looking in the global register of template definitions.
1475
1476 WARNING! THIS FUNCTION DEPENDS ON GLOBAL VARIABLES
1477
1478 If $theVar is the name of a previously defined template, returns the text of
1479 that template after recursive expansion of any TMPL:P tags it contains.
1480
1481 =cut
1482
1483 sub handleTmplP
1484 {
1485 # Print template variable, called by %TMPL:P{"$theVar"}%
1486 my( $theVar ) = @_;
1487
1488 my $val = "";
1489 if( ( %templateVars ) && ( exists $templateVars{ $theVar } ) ) {
1490 $val = $templateVars{ $theVar };
1491 $val =~ s/%TMPL\:P{[\s\"]*(.*?)[\"\s]*}%/&handleTmplP($1)/geo; # recursion
1492 rizwank 1.1 }
1493 if( ( $theVar eq "sep" ) && ( ! $val ) ) {
1494 # set separator explicitely if not set
1495 $val = " | ";
1496 }
1497 return $val;
1498 }
1499
1500 =pod
1501
1502 ---++ sub readTemplate ( $theName, $theSkin, $theWeb )
1503 Return value: expanded template text
1504
1505 WARNING! THIS IS A SIDE-EFFECTING FUNCTION
1506
1507 PUBLIC Reads a template, constructing a candidate name for the template as described in
1508 _readTemplateFile.
1509
1510 If template text is found, extracts include statements and fully expands them.
1511 Also extracts template definitions and adds them to the
1512 global templateVars hash, overwriting any previous definition.
1513 rizwank 1.1
1514 =cut
1515
1516 sub readTemplate
1517 {
1518 my( $theName, $theSkin, $theWeb ) = @_;
1519
1520 if( ! defined($theSkin) ) {
1521 $theSkin = &TWiki::getSkin();
1522 }
1523
1524 if( ! defined( $theWeb ) ) {
1525 $theWeb = $TWiki::webName;
1526 }
1527
1528 # recursively read template file(s)
1529 my $text = _readTemplateFile( $theName, $theSkin, $theWeb );
1530 while( $text =~ /%TMPL\:INCLUDE{[\s\"]*(.*?)[\"\s]*}%/s ) {
1531 $text =~ s/%TMPL\:INCLUDE{[\s\"]*(.*?)[\"\s]*}%/&_readTemplateFile( $1, $theSkin, $theWeb )/geo;
1532 }
1533
1534 rizwank 1.1 if( ! ( $text =~ /%TMPL\:/s ) ) {
1535 # no template processing
1536 $text =~ s|^(( {3})+)|"\t" x (length($1)/3)|geom; # leading spaces to tabs
1537 return $text;
1538 }
1539
1540 my $result = "";
1541 my $key = "";
1542 my $val = "";
1543 my $delim = "";
1544 foreach( split( /(%TMPL\:)/, $text ) ) {
1545 if( /^(%TMPL\:)$/ ) {
1546 $delim = $1;
1547 } elsif( ( /^DEF{[\s\"]*(.*?)[\"\s]*}%[\n\r]*(.*)/s ) && ( $1 ) ) {
1548 # handle %TMPL:DEF{"key"}%
1549 if( $key ) {
1550 $templateVars{ $key } = $val;
1551 }
1552 $key = $1;
1553 $val = $2 || "";
1554
1555 rizwank 1.1 } elsif( /^END%[\n\r]*(.*)/s ) {
1556 # handle %TMPL:END%
1557 $templateVars{ $key } = $val;
1558 $key = "";
1559 $val = "";
1560 $result .= $1 || "";
1561
1562 } elsif( $key ) {
1563 $val .= "$delim$_";
1564
1565 } else {
1566 $result .= "$delim$_";
1567 }
1568 }
1569
1570 # handle %TMPL:P{"..."}% recursively
1571 $result =~ s/%TMPL\:P{[\s\"]*(.*?)[\"\s]*}%/&handleTmplP($1)/geo;
1572 $result =~ s|^(( {3})+)|"\t" x (length($1)/3)|geom; # leading spaces to tabs
1573 return $result;
1574 }
1575
1576 rizwank 1.1 =pod
1577
1578 ---++ readFile( $filename )
1579 Return value: $fileContents
1580
1581 Returns the entire contents of the given file, which can be specified in any
1582 format acceptable to the Perl open() function. SECURITY NOTE: make sure
1583 any $filename coming from a user is stripped of special characters that might
1584 change Perl's open() semantics.
1585
1586 =cut
1587
1588 sub readFile
1589 {
1590 my( $name ) = @_;
1591 my $data = "";
1592 undef $/; # set to read to EOF
1593 open( IN_FILE, "<$name" ) || return "";
1594 $data = <IN_FILE>;
1595 $/ = "\n";
1596 close( IN_FILE );
1597 rizwank 1.1 $data = "" unless $data; # no undefined
1598 return $data;
1599 }
1600
1601
1602 =pod
1603
1604 ---++ sub readFileHead ( $name, $maxLines )
1605
1606 Not yet documented.
1607
1608 =cut
1609
1610 sub readFileHead
1611 {
1612 my( $name, $maxLines ) = @_;
1613 my $data = "";
1614 my $line;
1615 my $l = 0;
1616 $/ = "\n"; # read line by line
1617 open( IN_FILE, "<$name" ) || return "";
1618 rizwank 1.1 while( ( $l < $maxLines ) && ( $line = <IN_FILE> ) ) {
1619 $data .= $line;
1620 $l += 1;
1621 }
1622 close( IN_FILE );
1623 return $data;
1624 }
1625
1626
1627 #AS 5 Dec 2000 collect all Web's topic names
1628
1629 =pod
1630
1631 ---+++ getTopicNames( $web ) ==> @topics
1632
1633 | Description: | Get list of all topics in a web |
1634 | Parameter: =$web= | Web name, required, e.g. ="Sandbox"= |
1635 | Return: =@topics= | Topic list, e.g. =( "WebChanges", "WebHome", "WebIndex", "WebNotify" )= |
1636
1637 =cut
1638
1639 rizwank 1.1 sub getTopicNames {
1640 my( $web ) = @_ ;
1641
1642 if( !defined $web ) {
1643 $web="";
1644 }
1645
1646 #FIXME untaint web name?
1647
1648 # get list of all topics by scanning $dataDir
1649 opendir DIR, "$TWiki::dataDir/$web" ;
1650 my @topicList = sort grep { s/\.txt$// } readdir( DIR );
1651 closedir( DIR );
1652 return @topicList ;
1653 }
1654 #/AS
1655
1656
1657 #AS 5 Dec 2000 collect immediate subWeb names
1658
1659 =pod
1660 rizwank 1.1
1661 ---++ sub getSubWebs ( $web )
1662
1663 Not yet documented.
1664
1665 =cut
1666
1667 sub getSubWebs {
1668 my( $web ) = @_ ;
1669
1670 if( !defined $web ) {
1671 $web="";
1672 }
1673
1674 #FIXME untaint web name?
1675
1676 # get list of all subwebs by scanning $dataDir
1677 opendir DIR, "$TWiki::dataDir/$web" ;
1678 my @tmpList = readdir( DIR );
1679 closedir( DIR );
1680
1681 rizwank 1.1 # this is not magic, it just looks like it.
1682 my @webList = sort
1683 grep { s#^.+/([^/]+)$#$1# }
1684 grep { -d }
1685 map { "$TWiki::dataDir/$web/$_" }
1686 grep { ! /^\.\.?$/ } @tmpList;
1687
1688 return @webList ;
1689 }
1690 #/AS
1691
1692
1693 # =========================
1694 #AS 26 Dec 2000 recursively collects all Web names
1695 #FIXME: move var to TWiki.cfg ?
1696 use vars qw ($subWebsAllowedP);
1697
1698 $subWebsAllowedP = 0; # 1 = subwebs allowed, 0 = flat webs
1699
1700 =pod
1701
1702 rizwank 1.1 ---++ sub getAllWebs ( $web )
1703
1704 Not yet documented.
1705
1706 =cut
1707
1708 sub getAllWebs {
1709 # returns a list of subweb names
1710 my( $web ) = @_ ;
1711
1712 if( !defined $web ) {
1713 $web="";
1714 }
1715 my @webList = map { s/^\///o; $_ }
1716 map { "$web/$_" }
1717 &getSubWebs( $web );
1718 my $subWeb = "";
1719 if( $subWebsAllowedP ) {
1720 my @subWebs = @webList;
1721 foreach $subWeb ( @webList ) {
1722 push @subWebs, &getAllWebs( $subWeb );
1723 rizwank 1.1 }
1724 return @subWebs;
1725 }
1726 return @webList ;
1727 }
1728 #/AS
1729
1730 =pod
1731
1732 ---+++ setTopicRevisionTag( $web, $topic, $rev, $tag ) ==> $success
1733
1734 | Description: | sets a names tag on the specified revision |
1735 | Parameter: =$web= | webname |
1736 | Parameter: =$topic= | topic name |
1737 | Parameter: =$rev= | the revision we are taging |
1738 | Parameter: =$tag= | the string to tag with |
1739 | Return: =$success= | |
1740 | TODO: | we _need_ an error mechanism! |
1741 | Since: | TWiki:: (20 April 2004) |
1742
1743 =cut
1744 rizwank 1.1
1745 sub setTopicRevisionTag
1746 {
1747 my ( $web, $topic, $rev, $tag ) = @_;
1748
1749 my $topicHandler = _getTopicHandler( $web, $topic );
1750 # TWiki::writeDebug("Store - setTopicRevisionTag ( $web, $topic, $rev, $tag )");
1751 return $topicHandler->setTopicRevisionTag( $web, $topic, $rev, $tag );
1752 }
1753
1754
1755
1756 # =========================
1757
1758 1;
1759
1760 # EOF
|