1 rizwank 1.1 # Module of TWiki Collaboration Platform, http://TWiki.org/
2 #
3 # Search engine of TWiki.
4 #
5 # Copyright (C) 2000-2004 Peter Thoeny, peter@thoeny.com
6 #
7 # For licensing info read license.txt file in the TWiki root.
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 2
11 # of the License, or (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details, published at
17 # http://www.gnu.org/copyleft/gpl.html
18 #
19 # Notes:
20 # - Latest version at http://twiki.org/
21 # - Installation instructions in $dataDir/Main/TWikiDocumentation.txt
22 rizwank 1.1 # - Customize variables in TWiki.cfg when installing TWiki.
23 #
24 # 20000501 Kevin Kinnell : Many many many changes, best view is to
25 # run a diff.
26 # 20000605 Kevin Kinnell : Bug hunting. Fixed to allow web colors
27 # spec'd as "word" instead of hex only.
28 # Found a lovely bug that screwed up the
29 # search limits because Perl (as we all know
30 # but may forget) doesn't clear the $n match
31 # params if a match fails... *^&$#!!!
32 # PTh 03 Nov 2000: Performance improvements
33
34 =begin twiki
35
36 ---+ TWiki::Search Module
37
38 This module implements all the search functionality.
39
40 =cut
41
42 package TWiki::Search;
43 rizwank 1.1 use strict;
44
45 use vars qw(
46 $cacheRev1webTopic $cacheRev1date $cacheRev1user
47 );
48
49
50 # 'Use locale' for internationalisation of Perl sorting and searching -
51 # main locale settings are done in TWiki::setupLocale
52 BEGIN {
53 # Do a dynamic 'use locale' for this module
54 if( $TWiki::useLocale ) {
55 require locale;
56 import locale ();
57 }
58 $cacheRev1webTopic = "";
59 }
60
61 # ===========================
62 # Normally writes no output, uncomment writeDebug line to get output of all RCS etc command to debug file
63 =pod
64 rizwank 1.1
65 ---++ sub _traceExec ( $cmd, $result )
66
67 Not yet documented.
68
69 =cut
70
71 sub _traceExec
72 {
73 my( $cmd, $result ) = @_;
74
75 #TWiki::writeDebug( "Search exec: $cmd -> $result" );
76 }
77
78 # ===========================
79 =pod
80
81 ---++ sub _translateSpace ( $theText )
82
83 Not yet documented.
84
85 rizwank 1.1 =cut
86
87 sub _translateSpace
88 {
89 my( $theText ) = @_;
90 $theText =~ s/\s+/$TWiki::TranslationToken/go;
91 return $theText;
92 }
93
94 # ===========================
95 =pod
96
97 ---++ sub _tokensFromSearchString ( $theSearchVal, $theType )
98
99 Not yet documented.
100
101 =cut
102
103 sub _tokensFromSearchString
104 {
105 my( $theSearchVal, $theType ) = @_;
106 rizwank 1.1
107 my @tokens = ();
108 if( $theType eq "regex" ) {
109 # regular expression search Example: soap;wsdl;web service;!shampoo
110 @tokens = split( /;/, $theSearchVal );
111
112 } elsif( $theType eq "literal" ) {
113 # literal search
114 $tokens[0] = $theSearchVal;
115
116 } else {
117 # keyword search. Example: soap +wsdl +"web service" -shampoo
118 $theSearchVal =~ s/(\".*?)\"/&_translateSpace($1)/geo; # hide spaces in "literal text"
119 $theSearchVal =~ s/[\+\-]\s+//go;
120
121 # build pattern of stop words
122 my $stopWords = &TWiki::Prefs::getPreferencesValue( "SEARCHSTOPWORDS" ) || "";
123 $stopWords =~ s/[\s\,]+/\|/go;
124 $stopWords =~ s/[\(\)]//go;
125
126 # read from bottom to up:
127 rizwank 1.1 @tokens =
128 map { s/^\+//o; s/^\-/\!/o; s/^\"//o; $_ } # remove +, change - to !, remove "
129 grep { ! /^($stopWords)$/i } # remove stopwords
130 map { s/$TWiki::TranslationToken/ /go; $_ } # restore space
131 split( /[\s]+/, $theSearchVal ); # split on spaces
132 }
133
134 return @tokens;
135 }
136
137 # =========================
138 =pod
139
140 ---++ sub _searchTopicsInWeb ( $theWeb, $theTopic, $theScope, $theType, $caseSensitive, @theTokens )
141
142 Not yet documented.
143
144 =cut
145
146 sub _searchTopicsInWeb
147 {
148 rizwank 1.1 my( $theWeb, $theTopic, $theScope, $theType, $caseSensitive, @theTokens ) = @_;
149
150 my @topicList = ();
151 return @topicList unless( @theTokens ); # bail out if no search string
152
153 if( $theTopic ) { # limit search to topic list
154 if( $theTopic =~ /^\^\([$TWiki::regex{mixedAlphaNum}\|]+\)\$$/ ) { # topic list without wildcards
155 my $topics = $theTopic; # for speed, do not get all topics in web
156 $topics =~ s/^\^\(//o; # but convert topic pattern into topic list
157 $topics =~ s/\)\$//o; #
158 @topicList = split( /\|/, $topics ); # build list from topic pattern
159 } else { # topic list with wildcards
160 @topicList = _getTopicList( $theWeb ); # get all topics in web
161 if( $caseSensitive ) {
162 @topicList = grep( /$theTopic/, @topicList ); # limit by topic name,
163 } else { # Codev.SearchTopicNameAndTopicText
164 @topicList = grep( /$theTopic/i, @topicList );
165 }
166 }
167 } else {
168 @topicList = _getTopicList( $theWeb ); # get all topics in web
169 rizwank 1.1 }
170
171 my $sDir = "$TWiki::dataDir/$theWeb";
172 $theScope = "text" unless( $theScope =~ /^(topic|all)$/ ); # default scope is "text"
173
174 foreach my $token ( @theTokens ) { # search each token
175 my $invertSearch = ( $token =~ s/^\!//o ); # flag for AND NOT search
176 my @scopeTextList = ();
177 my @scopeTopicList = ();
178 return @topicList unless( @topicList ); # bail out if no topics left
179
180 # scope can be "topic" (default), "text" or "all"
181 # scope="text", e.g. Perl search on topic name:
182 unless( $theScope eq "text" ) {
183 my $qtoken = $token;
184 $qtoken = quotemeta( $qtoken ) if( $theType ne "regex" ); # FIXME I18N
185 if( $caseSensitive ) { # fix for Codev.SearchWithNoPipe
186 @scopeTopicList = grep( /$qtoken/, @topicList );
187 } else {
188 @scopeTopicList = grep( /$qtoken/i, @topicList );
189 }
190 rizwank 1.1 }
191
192 # scope="text", e.g. grep search on topic text:
193 unless( $theScope eq "topic" ) {
194 # Construct command line with 'grep'. I18N: 'grep' must use locales if needed,
195 # for case-insensitive searching. See TWiki::setupLocale.
196 my $cmd = "";
197 if( $theType eq "regex" ) {
198 $cmd .= $TWiki::egrepCmd;
199 } else {
200 $cmd .= $TWiki::fgrepCmd;
201 }
202 $cmd .= " -i" unless( $caseSensitive );
203 $cmd .= " -l -- $TWiki::cmdQuote%TOKEN%$TWiki::cmdQuote %FILES%";
204
205 my $result = "";
206 if( $sDir ) {
207 chdir( "$sDir" );
208 _traceExec( "chdir to $sDir", "" );
209 $sDir = ""; # chdir only once
210 }
211 rizwank 1.1
212 # process topics in sets, fix for Codev.ArgumentListIsTooLongForSearch
213 my $maxTopicsInSet = 512; # max number of topics for a grep call
214 my @take = @topicList;
215 my @set = splice( @take, 0, $maxTopicsInSet );
216 while( @set ) {
217 @set = map { "$_.txt" } @set; # add ".txt" extension to topic names
218 my $acmd = $cmd;
219 $acmd =~ s/%TOKEN%/$token/o;
220 $acmd =~ s/%FILES%/@set/o;
221 $acmd =~ /(.*)/;
222 $acmd = "$1"; # untaint variable (FIXME: Needs a better check!)
223 $result = `$acmd`;
224 _traceExec( $acmd, $result );
225 @set = split( /\n/, $result );
226 @set = map { /(.*)\.txt$/; $_ = $1; } @set; # cut ".txt" extension
227 my %seen = ();
228 foreach my $topic ( @set ) {
229 $seen{$topic}++; # make topics unique
230 }
231 push( @scopeTextList, sort keys %seen ); # add hits to found list
232 rizwank 1.1 @set = splice( @take, 0, $maxTopicsInSet );
233 }
234 }
235
236 if( @scopeTextList && @scopeTopicList ) {
237 push( @scopeTextList, @scopeTopicList ); # join "topic" and "text" lists
238 my %seen = ();
239 @scopeTextList = sort grep { ! $seen{$_} ++ } @scopeTextList; # make topics unique
240 } elsif( @scopeTopicList ) {
241 @scopeTextList = @scopeTopicList;
242 }
243
244 if( $invertSearch ) { # do AND NOT search
245 my %seen = ();
246 foreach my $topic ( @scopeTextList ) {
247 $seen{$topic} = 1;
248 }
249 @scopeTextList = ();
250 foreach my $topic ( @topicList ) {
251 push( @scopeTextList, $topic ) unless( $seen{$topic} );
252 }
253 rizwank 1.1 }
254 @topicList = @scopeTextList; # reduced topic list for next token
255 }
256 return @topicList;
257 }
258
259 # =========================
260 =pod
261
262 ---++ sub _getTopicList ( $web )
263
264 Not yet documented.
265
266 =cut
267
268 sub _getTopicList
269 {
270 my( $web ) = @_ ;
271 opendir DIR, "$TWiki::dataDir/$web" ;
272 my @topicList = sort map { s/\.txt$//o; $_ } grep { /\.txt$/ } readdir( DIR );
273 closedir( DIR );
274 rizwank 1.1 return @topicList;
275 }
276
277 # =========================
278 =pod
279
280 ---++ sub _makeTopicPattern ( $theTopic )
281
282 Not yet documented.
283
284 =cut
285
286 sub _makeTopicPattern
287 {
288 my( $theTopic ) = @_ ;
289 return "" unless( $theTopic );
290 # "Web*, FooBar" ==> ( "Web*", "FooBar" ) ==> ( "Web.*", "FooBar" )
291 my @arr = map { s/[^\*\_$TWiki::regex{mixedAlphaNum}]//go; s/\*/\.\*/go; $_ }
292 split( /,\s*/, $theTopic );
293 return "" unless( @arr );
294 # ( "Web.*", "FooBar" ) ==> "^(Web.*|FooBar)$"
295 rizwank 1.1 return '^(' . join( "|", @arr ) . ')$';
296 }
297
298 # =========================
299 =pod
300
301 ---++ sub revDate2ISO ()
302
303 Not yet documented.
304
305 =cut
306
307 sub revDate2ISO
308 {
309 my $epochSec = &TWiki::revDate2EpSecs( $_[0] );
310 return &TWiki::formatTime( $epochSec, "\$iso", "gmtime");
311 }
312
313 # =========================
314 =pod
315
316 rizwank 1.1 ---++ sub searchWeb ()
317
318 Not yet documented.
319
320 =cut
321
322 sub searchWeb
323 {
324 my %params = @_;
325 my $doInline = $params{"inline"} || 0;
326 my $baseWeb = $params{"baseweb"} || $TWiki::webName;
327 my $baseTopic = $params{"basetopic"} || $TWiki::topicName;
328 my $emptySearch = "something.Very/unLikelyTo+search-for;-)";
329 my $theSearchVal = $params{"search"} || $emptySearch;
330 my $theWebName = $params{"web"} || "";
331 my $theTopic = $params{"topic"} || "";
332 my $theExclude = $params{"excludetopic"} || "";
333 my $theScope = $params{"scope"} || "";
334 my $theOrder = $params{"order"} || "";
335 my $theType = $params{"type"} || "";
336 my $theRegex = $params{"regex"} || "";
337 rizwank 1.1 my $theLimit = $params{"limit"} || "";
338 my $revSort = $params{"reverse"} || "";
339 my $caseSensitive = $params{"casesensitive"} || "";
340 my $noSummary = $params{"nosummary"} || "";
341 my $noSearch = $params{"nosearch"} || "";
342 my $noHeader = $params{"noheader"} || "";
343 my $noTotal = $params{"nototal"} || "";
344 my $doBookView = $params{"bookview"} || "";
345 my $doRenameView = $params{"renameview"} || "";
346 my $doShowLock = $params{"showlock"} || "";
347 my $doExpandVars = $params{"expandvariables"} || "";
348 my $noEmpty = $params{"noempty"} || "";
349 my $theTemplate = $params{"template"} || "";
350 my $theHeader = $params{"header"} || "";
351 my $theFormat = $params{"format"} || "";
352 my $doMultiple = $params{"multiple"} || "";
353 my $theSeparator = $params{"separator"} || "";
354 my $newLine = $params{"newline"} || "";
355
356 ##TWiki::writeDebug "Search locale is $TWiki::siteLocale";
357
358 rizwank 1.1 ## 0501 kk : vvv new option to limit results
359 # process the result limit here, this is the 'global' limit for
360 # all webs in a multi-web search
361
362 ## #############
363 ## 0605 kk : vvv This code broke due to changes in the wiki.pm
364 ## file; it used to rely on the value of $1 being
365 ## a null string if there was no match. What a pity
366 ## Perl doesn't do The Right Thing, but whatever--it's
367 ## fixed now.
368 if ($theLimit =~ /(^\d+$)/o) { # only digits, all else is the same as
369 $theLimit = $1; # an empty string. "+10" won't work.
370 } else {
371 $theLimit = 0; # change "all" to 0, then to big number
372 }
373 if (! $theLimit ) { # PTh 03 Nov 2000:
374 $theLimit = 32000; # Big number, needed for performance improvements
375 }
376
377 $theType = "regex" if( $theRegex );
378
379 rizwank 1.1 # I18N fix
380 my $mixedAlpha = $TWiki::regex{mixedAlpha};
381
382 if( $theSeparator ) {
383 $theSeparator =~ s/\$n\(\)/\n/gos; # expand "$n()" to new line
384 $theSeparator =~ s/\$n([^$mixedAlpha]|$)/\n$1/gos;
385 }
386 if( $newLine ) {
387 $newLine =~ s/\$n\(\)/\n/gos; # expand "$n()" to new line
388 $newLine =~ s/\$n([^$mixedAlpha]|$)/\n$1/gos;
389 }
390
391 my $searchResult = "";
392 my $topic = $TWiki::mainTopicname;
393
394 my @webList = ();
395
396 # A value of 'all' or 'on' by itself gets all webs,
397 # otherwise ignored (unless there is a web called "All".)
398 my $searchAllFlag = ( $theWebName =~ /(^|[\,\s])(all|on)([\,\s]|$)/i );
399
400 rizwank 1.1 # Search what webs? "" current web, list gets the list, all gets
401 # all (unless marked in WebPrefs as NOSEARCHALL)
402
403 if( $theWebName ) {
404 foreach my $web ( split( /[\,\s]+/, $theWebName ) ) {
405 # the web processing loop filters for valid web names, so don't do it here.
406
407 if( $web =~ /^(all|on)$/i ) {
408 # get list of all webs by scanning $dataDir
409 opendir DIR, $TWiki::dataDir;
410 my @tmpList = readdir(DIR);
411 closedir(DIR);
412 @tmpList = sort
413 grep { s#^.+/([^/]+)$#$1# }
414 grep { -d }
415 map { "$TWiki::dataDir/$_" }
416 grep { ! /^[._]/ } @tmpList;
417
418 # what that does (looking from the bottom up) is take the file
419 # list, filter out the dot directories and dot files, turn the
420 # list into full paths instead of just file names, filter out
421 rizwank 1.1 # any non-directories, strip the path back off, and sort
422 # whatever was left after all that (which should be merely a
423 # list of directory's names.)
424
425 foreach my $aweb ( @tmpList ) {
426 push( @webList, $aweb ) unless( grep { /^$aweb$/ } @webList );
427 }
428
429 } else {
430 push( @webList, $web ) unless( grep { /^$web$/ } @webList );
431 }
432 }
433
434 } else {
435 #default to current web
436 push @webList, $TWiki::webName;
437 }
438
439 $theTopic = _makeTopicPattern( $theTopic ); # E.g. "Bug*, *Patch" ==> "^(Bug.*|.*Patch)$"
440 $theExclude = _makeTopicPattern( $theExclude ); # E.g. "Web*, FooBar" ==> "^(Web.*|FooBar)$"
441
442 rizwank 1.1 my $tempVal = "";
443 my $tmpl = "";
444 my $topicCount = 0; # JohnTalintyre
445 my $originalSearch = $theSearchVal;
446 my $renameTopic;
447 my $renameWeb = "";
448 my $spacedTopic;
449 $theTemplate = "searchformat" if( $theFormat );
450
451 if( $theTemplate ) {
452 $tmpl = &TWiki::Store::readTemplate( "$theTemplate" );
453 # FIXME replace following with this @@@
454 } elsif( $doBookView ) {
455 $tmpl = &TWiki::Store::readTemplate( "searchbookview" );
456 } elsif ($doRenameView ) {
457 # Rename view, showing where topics refer to topic being renamed.
458 $tmpl = &TWiki::Store::readTemplate( "searchrenameview" ); # JohnTalintyre
459
460 # Create full search string from topic name that is passed in
461 $renameTopic = $theSearchVal;
462 if( $renameTopic =~ /(.*)\\\.(.*)/o ) {
463 rizwank 1.1 $renameWeb = $1;
464 $renameTopic = $2;
465 }
466 $spacedTopic = spacedTopic( $renameTopic );
467 $spacedTopic = $renameWeb . '\.' . $spacedTopic if( $renameWeb );
468
469 # I18N: match non-alpha before and after topic name in renameview searches
470 # This regex must work under grep, i.e. if using Perl 5.6 or higher
471 # the POSIX character classes will be used in grep as well.
472 my $alphaNum = $TWiki::regex{mixedAlphaNum};
473 $theSearchVal = "(^|[^${alphaNum}_])$theSearchVal" .
474 "([^${alphaNum}_]" . '|$)|' .
475 '(\[\[' . $spacedTopic . '\]\])';
476 } else {
477 $tmpl = &TWiki::Store::readTemplate( "search" );
478 }
479
480 $tmpl =~ s/\%META{.*?}\%//go; # remove %META{"parent"}%
481
482 my( $tmplHead, $tmplSearch,
483 $tmplTable, $tmplNumber, $tmplTail ) = split( /%SPLIT%/, $tmpl );
484 rizwank 1.1 $tmplHead = &TWiki::handleCommonTags( $tmplHead, $topic );
485 $tmplSearch = &TWiki::handleCommonTags( $tmplSearch, $topic );
486 $tmplNumber = &TWiki::handleCommonTags( $tmplNumber, $topic );
487 $tmplTail = &TWiki::handleCommonTags( $tmplTail, $topic );
488
489 if( ! $tmplTail ) {
490 print "<html><body>";
491 print "<h1>TWiki Installation Error</h1>";
492 # Might not be search.tmpl FIXME
493 print "Incorrect format of search.tmpl (missing %SPLIT% parts)";
494 print "</body></html>";
495 return;
496 }
497
498 if( ! $doInline ) {
499 # print first part of full HTML page
500 $tmplHead = &TWiki::Render::getRenderedVersion( $tmplHead );
501 $tmplHead =~ s|</*nop/*>||goi; # remove <nop> tags (PTh 06 Nov 2000)
502 print $tmplHead;
503 }
504
505 rizwank 1.1 if( ! $noSearch ) {
506 # print "Search:" part
507 my $searchStr = $theSearchVal;
508 $searchStr = "" if( $theSearchVal eq $emptySearch );
509 $searchStr =~ s/&/&/go;
510 $searchStr =~ s/</</go;
511 $searchStr =~ s/>/>/go;
512 $searchStr =~ s/^\.\*$/Index/go;
513 $tmplSearch =~ s/%SEARCHSTRING%/$searchStr/go;
514 if( $doInline ) {
515 $searchResult .= $tmplSearch;
516 } else {
517 $tmplSearch = &TWiki::Render::getRenderedVersion( $tmplSearch );
518 $tmplSearch =~ s|</*nop/*>||goi; # remove <nop> tag
519 print $tmplSearch;
520 }
521 }
522
523 my @tokens = &_tokensFromSearchString( $theSearchVal, $theType );
524
525 # write log entry
526 rizwank 1.1 # FIXME: Move log entry further down to log actual webs searched
527 if( ( $TWiki::doLogTopicSearch ) && ( ! $doInline ) ) {
528 # 0501 kk : vvv Moved from search
529 # PTh 17 May 2000: reverted to old behaviour,
530 # e.g. do not log inline search
531 # PTh 03 Nov 2000: Moved out of the 'foreach $thisWebName' loop
532 $tempVal = join( ' ', @webList );
533 &TWiki::Store::writeLog( "search", $tempVal, $theSearchVal );
534 }
535
536 # loop through webs
537 foreach my $thisWebName ( @webList ) {
538
539 # PTh 03 Nov 2000: Add security check
540 $thisWebName =~ s/$TWiki::securityFilter//go;
541 $thisWebName =~ /(.*)/;
542 $thisWebName = $1; # untaint variable
543
544 next unless &TWiki::Store::webExists( $thisWebName ); # can't process what ain't thar
545
546 my $thisWebBGColor = &TWiki::Prefs::getPreferencesValue( "WEBBGCOLOR", $thisWebName ) || "\#FF00FF";
547 rizwank 1.1 my $thisWebNoSearchAll = &TWiki::Prefs::getPreferencesValue( "NOSEARCHALL", $thisWebName );
548
549 # make sure we can report this web on an 'all' search
550 # DON'T filter out unless it's part of an 'all' search.
551 # PTh 18 Aug 2000: Need to include if it is the current web
552 next if ( ( $searchAllFlag )
553 && ( ( $thisWebNoSearchAll =~ /on/i ) || ( $thisWebName =~ /^[\.\_]/ ) )
554 && ( $thisWebName ne $TWiki::webName ) );
555
556 # search topics in this web
557 my @topicList = _searchTopicsInWeb( $thisWebName, $theTopic, $theScope, $theType, $caseSensitive, @tokens );
558
559 # exclude topics, Codev.ExcludeWebTopicsFromSearch
560 if( $caseSensitive ) {
561 @topicList = grep( !/$theExclude/, @topicList ) if( $theExclude );
562 } else {
563 @topicList = grep( !/$theExclude/i, @topicList ) if( $theExclude );
564 }
565 next if( $noEmpty && ! @topicList ); # Nothing to show for this web
566
567 # use hash tables for date, author, rev number and view permission
568 rizwank 1.1 my %topicRevDate = ();
569 my %topicRevUser = ();
570 my %topicRevNum = ();
571 my %topicAllowView = ();
572
573 # sort the topic list by date, author or topic name
574 if( $theOrder eq "modified" ) {
575 # PTh 03 Nov 2000: Performance improvement
576 # Dates are tricky. For performance we do not read, say,
577 # 2000 records of author/date, sort and then use only 50.
578 # Rather we
579 # * sort by file timestamp (to get a rough list)
580 # * shorten list to the limit + some slack
581 # * sort by rev date on shortened list to get the acurate list
582
583 # Do performance exercise only if it pays off
584 if( $theLimit + 20 < scalar(@topicList) ) {
585 # sort by file timestamp, Schwartzian Transform
586 my @tmpList = ();
587 if( $revSort ) {
588 @tmpList = map { $_->[1] }
589 rizwank 1.1 sort {$b->[0] <=> $a->[0] }
590 map { [ (stat "$TWiki::dataDir\/$thisWebName\/$_.txt")[9], $_ ] }
591 @topicList;
592 } else {
593 @tmpList = map { $_->[1] }
594 sort {$a->[0] <=> $b->[0] }
595 map { [ (stat "$TWiki::dataDir\/$thisWebName\/$_.txt")[9], $_ ] }
596 @topicList;
597 }
598
599 # then shorten list and build the hashes for date and author
600 my $idx = $theLimit + 10; # slack on limit
601 @topicList = ();
602 foreach( @tmpList ) {
603 push( @topicList, $_ );
604 $idx -= 1;
605 last if $idx <= 0;
606 }
607 }
608
609 # build the hashes for date and author
610 rizwank 1.1 foreach( @topicList ) {
611 $tempVal = $_;
612 # Permission check done below, so force this read to succeed with "internal" parameter
613 my( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $tempVal, "", "internal" );
614 my ( $revdate, $revuser, $revnum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $tempVal, $meta );
615 $topicRevUser{ $tempVal } = &TWiki::userToWikiName( $revuser );
616 $topicRevDate{ $tempVal } = $revdate; # keep epoc sec for sorting
617 $topicRevNum{ $tempVal } = $revnum;
618 $topicAllowView{ $tempVal } = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName,
619 $text, $tempVal, $thisWebName );
620 }
621
622 # sort by date (second time if exercise), Schwartzian Transform
623 my $dt = "";
624 if( $revSort ) {
625 @topicList = map { $_->[1] }
626 sort {$b->[0] <=> $a->[0] }
627 map { $dt = $topicRevDate{$_}; $topicRevDate{$_} = TWiki::formatTime( $dt ); [ $dt, $_ ] }
628 @topicList;
629 } else {
630 @topicList = map { $_->[1] }
631 rizwank 1.1 sort {$a->[0] <=> $b->[0] }
632 map { $dt = $topicRevDate{$_}; $topicRevDate{$_} = TWiki::formatTime( $dt ); [ $dt, $_ ] }
633 @topicList;
634 }
635
636 } elsif( $theOrder =~ /^creat/ ) {
637 # sort by topic creation time
638
639 # first we need to build the hashes for modified date, author, creation time
640 my %topicCreated = (); # keep only temporarily for sort
641 foreach( @topicList ) {
642 $tempVal = $_;
643 # Permission check done below, so force this read to succeed with "internal" parameter
644 my( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $tempVal, "", "internal" );
645 my( $revdate, $revuser, $revnum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $tempVal, $meta );
646 $topicRevUser{ $tempVal } = &TWiki::userToWikiName( $revuser );
647 $topicRevDate{ $tempVal } = &TWiki::formatTime( $revdate );
648 $topicRevNum{ $tempVal } = $revnum;
649 $topicAllowView{ $tempVal } = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName,
650 $text, $tempVal, $thisWebName );
651 my ( $createdate ) = &TWiki::Store::getRevisionInfo( $thisWebName, $tempVal, "1.1" );
652 rizwank 1.1 $topicCreated{ $tempVal } = $createdate; # Sortable epoc second format
653 }
654
655 # sort by creation time, Schwartzian Transform
656 if( $revSort ) {
657 @topicList = map { $_->[1] }
658 sort {$b->[0] <=> $a->[0] }
659 map { [ $topicCreated{$_}, $_ ] }
660 @topicList;
661 } else {
662 @topicList = map { $_->[1] }
663 sort {$a->[0] <=> $b->[0] }
664 map { [ $topicCreated{$_}, $_ ] }
665 @topicList;
666 }
667
668 } elsif( $theOrder eq "editby" ) {
669 # sort by author
670
671 # first we need to build the hashes for date and author
672 foreach( @topicList ) {
673 rizwank 1.1 $tempVal = $_;
674 # Permission check done below, so force this read to succeed with "internal" parameter
675 my( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $tempVal, "", "internal" );
676 my( $revdate, $revuser, $revnum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $tempVal, $meta );
677 $topicRevUser{ $tempVal } = &TWiki::userToWikiName( $revuser );
678 $topicRevDate{ $tempVal } = &TWiki::formatTime( $revdate );
679 $topicRevNum{ $tempVal } = $revnum;
680 $topicAllowView{ $tempVal } = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName,
681 $text, $tempVal, $thisWebName );
682 }
683
684 # sort by author, Schwartzian Transform
685 if( $revSort ) {
686 @topicList = map { $_->[1] }
687 sort {$b->[0] cmp $a->[0] }
688 map { [ $topicRevUser{$_}, $_ ] }
689 @topicList;
690 } else {
691 @topicList = map { $_->[1] }
692 sort {$a->[0] cmp $b->[0] }
693 map { [ $topicRevUser{$_}, $_ ] }
694 rizwank 1.1 @topicList;
695 }
696
697 } elsif( $theOrder =~ m/^formfield\((.*)\)$/ ) {
698 # sort by TWikiForm field
699 my $sortfield = $1;
700 my %fieldVals= ();
701 # first we need to build the hashes for fields
702 foreach( @topicList ) {
703 $tempVal = $_;
704 # Permission check done below, so force this read to succeed with "internal" parameter
705 my( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $tempVal, "", "internal" );
706 my( $revdate, $revuser, $revnum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $tempVal, $meta );
707 $topicRevUser{ $tempVal } = &TWiki::userToWikiName( $revuser );
708 $topicRevDate{ $tempVal } = &TWiki::formatTime( $revdate );
709 $topicRevNum{ $tempVal } = $revnum;
710 $topicAllowView{ $tempVal } = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName,
711 $text, $tempVal, $thisWebName );
712 $fieldVals{ $tempVal } = getMetaFormField( $meta, $sortfield );
713 }
714
715 rizwank 1.1 # sort by field, Schwartzian Transform
716 if( $revSort ) {
717 @topicList = map { $_->[1] }
718 sort {$b->[0] cmp $a->[0] }
719 map { [ $fieldVals{$_}, $_ ] }
720 @topicList;
721 } else {
722 @topicList = map { $_->[1] }
723 sort {$a->[0] cmp $b->[0] }
724 map { [ $fieldVals{$_}, $_ ] }
725 @topicList;
726 }
727
728 } else {
729 # simple sort, suggested by RaymondLutz in Codev.SchwartzianTransformMisused
730 ##TWiki::writeDebug "Topic list before sort = @topicList";
731 if( $revSort ) {
732 @topicList = sort {$b cmp $a} @topicList;
733 } else {
734 @topicList = sort {$a cmp $b} @topicList;
735 }
736 rizwank 1.1 ##TWiki::writeDebug "Topic list after sort = @topicList";
737 }
738
739 # header and footer of $thisWebName
740 my( $beforeText, $repeatText, $afterText ) = split( /%REPEAT%/, $tmplTable );
741 if( $theHeader ) {
742 $theHeader =~ s/\$n\(\)/\n/gos; # expand "$n()" to new line
743 $theHeader =~ s/\$n([^$mixedAlpha]|$)/\n$1/gos; # expand "$n" to new line
744 $beforeText = $theHeader;
745 $beforeText =~ s/\$web/$thisWebName/gos;
746 if( $theSeparator ) {
747 $beforeText .= $theSeparator;
748 } else {
749 $beforeText =~ s/([^\n])$/$1\n/os; # add new line at end if needed
750 }
751 }
752
753 # output the list of topics in $thisWebName
754 my $ntopics = 0;
755 my $headerDone = 0;
756 my $topic = "";
757 rizwank 1.1 my $head = "";
758 my $revDate = "";
759 my $revUser = "";
760 my $revNum = "";
761 my $revNumText = "";
762 my $allowView = "";
763 my $locked = "";
764 foreach( @topicList ) {
765 $topic = $_;
766
767 my $meta = "";
768 my $text = "";
769 my $forceRendering = 0;
770
771 # make sure we have date and author
772 if( exists( $topicRevUser{$topic} ) ) {
773 $revDate = $topicRevDate{$topic};
774 $revUser = $topicRevUser{$topic};
775 $revNum = $topicRevNum{$topic};
776 $allowView = $topicAllowView{$topic};
777 } else {
778 rizwank 1.1 # lazy query, need to do it at last
779 ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic );
780 $text =~ s/%WEB%/$thisWebName/gos;
781 $text =~ s/%TOPIC%/$topic/gos;
782 $allowView = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName, $text, $topic, $thisWebName );
783 ( $revDate, $revUser, $revNum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $topic, $meta );
784 $revDate = &TWiki::formatTime( $revDate );
785 $revUser = &TWiki::userToWikiName( $revUser );
786 }
787
788 $locked = "";
789 if( $doShowLock ) {
790 ( $tempVal ) = &TWiki::Store::topicIsLockedBy( $thisWebName, $topic );
791 if( $tempVal ) {
792 $revUser = &TWiki::userToWikiName( $tempVal );
793 $locked = "(LOCKED)";
794 }
795 }
796
797 # Check security
798 # FIXME - how do we deal with user login not being available if
799 rizwank 1.1 # coming from search script?
800 if( ! $allowView ) {
801 next;
802 }
803
804 # Special handling for format="..."
805 if( $theFormat ) {
806 unless( $text ) {
807 ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic );
808 $text =~ s/%WEB%/$thisWebName/gos;
809 $text =~ s/%TOPIC%/$topic/gos;
810 }
811 if( $doExpandVars ) {
812 if( "$thisWebName.$topic" eq "$baseWeb.$baseTopic" ) {
813 # primitive way to prevent recursion
814 $text =~ s/%SEARCH/%<nop>SEARCH/g;
815 }
816 $text = &TWiki::handleCommonTags( $text, $topic, $thisWebName );
817 }
818 }
819
820 rizwank 1.1 my @multipleHitLines = ();
821 if( $doMultiple ) {
822 my $pattern = $tokens[$#tokens]; # last token in an AND search
823 $pattern = quotemeta( $pattern ) if( $theType ne "regex" );
824 ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic ) unless $text;
825 if( $caseSensitive ) {
826 @multipleHitLines = reverse grep { /$pattern/ } split( /[\n\r]+/, $text );
827 } else {
828 @multipleHitLines = reverse grep { /$pattern/i } split( /[\n\r]+/, $text );
829 }
830 }
831
832 do { # multiple=on loop
833
834 $text = pop( @multipleHitLines ) if( scalar( @multipleHitLines ) );
835
836 if( $theFormat ) {
837 $tempVal = $theFormat;
838 $tempVal =~ s/\$web/$thisWebName/gos;
839 $tempVal =~ s/\$topic\(([^\)]*)\)/breakName( $topic, $1 )/geos;
840 $tempVal =~ s/\$topic/$topic/gos;
841 rizwank 1.1 $tempVal =~ s/\$locked/$locked/gos;
842 $tempVal =~ s/\$date/$revDate/gos;
843 $tempVal =~ s/\$isodate/&revDate2ISO($revDate)/geos;
844 $tempVal =~ s/\$rev/1.$revNum/gos;
845 $tempVal =~ s/\$wikiusername/$revUser/gos;
846 $tempVal =~ s/\$wikiname/wikiName($revUser)/geos;
847 $tempVal =~ s/\$username/&TWiki::wikiToUserName($revUser)/geos;
848 $tempVal =~ s/\$createdate/_getRev1Info( $thisWebName, $topic, "date" )/geos;
849 $tempVal =~ s/\$createusername/_getRev1Info( $thisWebName, $topic, "username" )/geos;
850 $tempVal =~ s/\$createwikiname/_getRev1Info( $thisWebName, $topic, "wikiname" )/geos;
851 $tempVal =~ s/\$createwikiusername/_getRev1Info( $thisWebName, $topic, "wikiusername" )/geos;
852 if( $tempVal =~ m/\$text/ ) {
853 # expand topic text
854 ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic ) unless $text;
855 if( $topic eq $TWiki::topicName ) {
856 # defuse SEARCH in current topic to prevent loop
857 $text =~ s/%SEARCH{.*?}%/SEARCH{...}/go;
858 }
859 $tempVal =~ s/\$text/$text/gos;
860 $forceRendering = 1 unless( $doMultiple );
861 }
862 rizwank 1.1 } else {
863 $tempVal = $repeatText;
864 }
865 $tempVal =~ s/%WEB%/$thisWebName/go;
866 $tempVal =~ s/%TOPICNAME%/$topic/go;
867 $tempVal =~ s/%LOCKED%/$locked/o;
868 $tempVal =~ s/%TIME%/$revDate/o;
869 if( $revNum > 1 ) {
870 $revNumText = "r1.$revNum";
871 } else {
872 $revNumText = "<span class=\"twikiNew\"><b>NEW</b></span>";
873 }
874 $tempVal =~ s/%REVISION%/$revNumText/o;
875 $tempVal =~ s/%AUTHOR%/$revUser/o;
876
877 if( ( $doInline || $theFormat ) && ( ! ( $forceRendering ) ) ) {
878 # print at the end if formatted search because of table rendering
879 # do nothing
880 } else {
881 $tempVal = &TWiki::handleCommonTags( $tempVal, $topic );
882 $tempVal = &TWiki::Render::getRenderedVersion( $tempVal );
883 rizwank 1.1 }
884
885 if( $doRenameView ) { # added JET 19 Feb 2000
886 # Permission check done below, so force this read to succeed with "internal" parameter
887 my $rawText = &TWiki::Store::readTopicRaw( $thisWebName, $topic, "", "internal" );
888 my $changeable = "";
889 my $changeAccessOK = &TWiki::Access::checkAccessPermission( "change", $TWiki::wikiUserName, $text, $topic, $thisWebName );
890 if( ! $changeAccessOK ) {
891 $changeable = "(NO CHANGE PERMISSION)";
892 $tempVal =~ s/%SELECTION%.*%SELECTION%//o;
893 } else {
894 $tempVal =~ s/%SELECTION%//go;
895 }
896 $tempVal =~ s/%CHANGEABLE%/$changeable/o;
897
898 $tempVal =~ s/%LABEL%/$doRenameView/go;
899 my $reducedOutput = "";
900
901 # Remove lines that don't contain the topic and highlight matched string
902 my $insidePRE = 0;
903 my $insideVERBATIM = 0;
904 rizwank 1.1 my $noAutoLink = 0;
905
906 foreach( split( /\n/, $rawText ) ) {
907
908 next if( /^%META:TOPIC(INFO|MOVED)/ );
909 s/</</go;
910 s/>/>/go;
911
912 # This code is in far too many places
913 m|<pre>|i && ( $insidePRE = 1 );
914 m|</pre>|i && ( $insidePRE = 0 );
915 if( m|<verbatim>|i ) {
916 $insideVERBATIM = 1;
917 }
918 if( m|</verbatim>|i ) {
919 $insideVERBATIM = 0;
920 }
921 m|<noautolink>|i && ( $noAutoLink = 1 );
922 m|</noautolink>|i && ( $noAutoLink = 0 );
923
924 if( ! ( $insidePRE || $insideVERBATIM || $noAutoLink ) ) {
925 rizwank 1.1 # Case insensitive option is required to get [[spaced Word]] to match
926 # I18N: match non-alpha before and after topic name in renameview searches
927 my $alphaNum = $TWiki::regex{mixedAlphaNum};
928 my $match = "(^|[^${alphaNum}_.])($originalSearch)(?=[^${alphaNum}]|\$)";
929 # NOTE: Must *not* use /o here, since $match is based on
930 # search string that will vary during lifetime of
931 # compiled code with mod_perl.
932 my $subs = s|$match|$1<font color="red"><span class="twikiAlert">$2</span></font> |g;
933 $match = '(\[\[)' . "($spacedTopic)" . '(?=\]\])';
934 $subs += s|$match|$1<font color="red"><span class="twikiAlert">$2</span></font> |gi;
935 if( $subs ) {
936 $topicCount++ if( ! $reducedOutput );
937 $reducedOutput .= "$_<br />\n" if( $subs );
938 }
939 }
940 }
941 $tempVal =~ s/%TOPIC_NUMBER%/$topicCount/go;
942 $tempVal =~ s/%TEXTHEAD%/$reducedOutput/go;
943 next if ( ! $reducedOutput );
944
945 } elsif( $doBookView ) {
946 rizwank 1.1 # BookView, added PTh 20 Jul 2000
947 if( ! $text ) {
948 ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic );
949 }
950 if( "$thisWebName.$topic" eq "$baseWeb.$baseTopic" ) {
951 # primitive way to prevent recursion
952 $text =~ s/%SEARCH/%<nop>SEARCH/g;
953 }
954 $text = &TWiki::handleCommonTags( $text, $topic, $thisWebName );
955 $text = &TWiki::Render::getRenderedVersion( $text, $thisWebName );
956 # FIXME: What about meta data rendering?
957 $tempVal =~ s/%TEXTHEAD%/$text/go;
958
959 } elsif( $theFormat ) {
960 # free format, added PTh 10 Oct 2001
961 $tempVal =~ s/\$summary\(([^\)]*)\)/&TWiki::makeTopicSummary( $text, $topic, $thisWebName, $1 )/geos;
962 $tempVal =~ s/\$summary/&TWiki::makeTopicSummary( $text, $topic, $thisWebName )/geos;
963 $tempVal =~ s/\$parent\(([^\)]*)\)/breakName( getMetaParent( $meta ), $1 )/geos;
964 $tempVal =~ s/\$parent/getMetaParent( $meta )/geos;
965 $tempVal =~ s/\$formfield\(\s*([^\)]*)\s*\)/getMetaFormField( $meta, $1 )/geos;
966 $tempVal =~ s/\$formname/_getMetaFormName( $meta )/geos;
967 rizwank 1.1 $tempVal =~ s/\$pattern\((.*?\s*\.\*)\)/getTextPattern( $text, $1 )/geos;
968 $tempVal =~ s/\r?\n/$newLine/gos if( $newLine );
969 if( $theSeparator ) {
970 $tempVal .= $theSeparator;
971 } else {
972 $tempVal =~ s/([^\n])$/$1\n/os; # add new line at end if needed
973 }
974 $tempVal =~ s/\$n\(\)/\n/gos; # expand "$n()" to new line
975 $tempVal =~ s/\$n([^$mixedAlpha]|$)/\n$1/gos; # expand "$n" to new line
976 $tempVal =~ s/\$nop(\(\))?//gos; # remove filler, useful for nested search
977 $tempVal =~ s/\$quot(\(\))?/\"/gos; # expand double quote
978 $tempVal =~ s/\$percnt(\(\))?/\%/gos; # expand percent
979 $tempVal =~ s/\$dollar(\(\))?/\$/gos; # expand dollar
980
981 } elsif( $noSummary ) {
982 $tempVal =~ s/%TEXTHEAD%//go;
983 $tempVal =~ s/ //go;
984
985 } else {
986 # regular search view
987 if( $text ) {
988 rizwank 1.1 $head = $text;
989 } else {
990 $head = &TWiki::Store::readFileHead( "$TWiki::dataDir\/$thisWebName\/$topic.txt", 16 );
991 }
992 $head = &TWiki::makeTopicSummary( $head, $topic, $thisWebName );
993 $tempVal =~ s/%TEXTHEAD%/$head/go;
994 }
995
996 # lazy output of header (only if needed for the first time)
997 unless( $headerDone || $noHeader ) {
998 $headerDone = 1;
999 $beforeText =~ s/%WEBBGCOLOR%/$thisWebBGColor/go;
1000 $beforeText =~ s/%WEB%/$thisWebName/go;
1001 $beforeText = &TWiki::handleCommonTags( $beforeText, $topic );
1002 if( $doInline || $theFormat ) {
1003 # print at the end if formatted search because of table rendering
1004 $searchResult .= $beforeText;
1005 } else {
1006 $beforeText = &TWiki::Render::getRenderedVersion( $beforeText, $thisWebName );
1007 $beforeText =~ s|</*nop/*>||goi; # remove <nop> tag
1008 print $beforeText;
1009 rizwank 1.1 }
1010 }
1011
1012 # output topic (or line if multiple=on)
1013 if( $doInline || $theFormat ) {
1014 # print at the end if formatted search because of table rendering
1015 $searchResult .= $tempVal;
1016 } else {
1017 $tempVal = &TWiki::Render::getRenderedVersion( $tempVal, $thisWebName );
1018 $tempVal =~ s|</*nop/*>||goi; # remove <nop> tag
1019 print $tempVal;
1020 }
1021
1022 } while( @multipleHitLines ); # multiple=on loop
1023
1024 $ntopics += 1;
1025 last if( $ntopics >= $theLimit );
1026 } # end topic loop in a web
1027
1028 # output footer only if hits in web
1029 if( $ntopics ) {
1030 rizwank 1.1 # output footer of $thisWebName
1031 $afterText = &TWiki::handleCommonTags( $afterText, $topic );
1032 if( $doInline || $theFormat ) {
1033 # print at the end if formatted search because of table rendering
1034 $afterText =~ s/\n$//os; # remove trailing new line
1035 $searchResult .= $afterText;
1036 } else {
1037 $afterText = &TWiki::Render::getRenderedVersion( $afterText, $thisWebName );
1038 $afterText =~ s|</*nop/*>||goi; # remove <nop> tag
1039 print $afterText;
1040 }
1041 }
1042
1043 # output number of topics (only if hits in web or if search only one web)
1044 if( $ntopics || @webList < 2 ) {
1045 unless( $noTotal ) {
1046 my $thisNumber = $tmplNumber;
1047 $thisNumber =~ s/%NTOPICS%/$ntopics/go;
1048 if( $doInline || $theFormat ) {
1049 # print at the end if formatted search because of table rendering
1050 $searchResult .= $thisNumber;
1051 rizwank 1.1 } else {
1052 $thisNumber = &TWiki::Render::getRenderedVersion( $thisNumber, $thisWebName );
1053 $thisNumber =~ s|</*nop/*>||goi; # remove <nop> tag
1054 print $thisNumber;
1055 }
1056 }
1057 }
1058 }
1059
1060 if( $theFormat ) {
1061 if( $theSeparator ) {
1062 $searchResult =~ s/$theSeparator$//s; # remove separator at end
1063 } else {
1064 $searchResult =~ s/\n$//os; # remove trailing new line
1065 }
1066 }
1067 if( $doInline ) {
1068 # return formatted search result
1069 return $searchResult;
1070
1071 } else {
1072 rizwank 1.1 if( $theFormat ) {
1073 # finally print $searchResult which got delayed because of formatted search
1074 $tmplTail = "$searchResult$tmplTail";
1075 }
1076
1077 # print last part of full HTML page
1078 $tmplTail = &TWiki::Render::getRenderedVersion( $tmplTail );
1079 $tmplTail =~ s|</*nop/*>||goi; # remove <nop> tag
1080 print $tmplTail;
1081 }
1082 return $searchResult;
1083 }
1084
1085 #=========================
1086 =pod
1087
1088 ---++ sub _getRev1Info( $theWeb, $theTopic, $theAttr )
1089
1090 Returns the topic revision info of version 1.1, attributes are "date", "username", "wikiname",
1091 "wikiusername". Revision info is cached for speed
1092
1093 rizwank 1.1 =cut
1094
1095 sub _getRev1Info
1096 {
1097 my( $theWeb, $theTopic, $theAttr ) = @_;
1098
1099 unless( $cacheRev1webTopic eq "$theWeb.$theTopic" ) {
1100 # refresh cache
1101 $cacheRev1webTopic = "$theWeb.$theTopic";
1102 ( $cacheRev1date, $cacheRev1user ) = &TWiki::Store::getRevisionInfo( $theWeb, $theTopic, "1.1" );
1103 }
1104 if( $theAttr eq "username" ) {
1105 return $cacheRev1user;
1106 }
1107 if( $theAttr eq "wikiname" ) {
1108 return &TWiki::userToWikiName( $cacheRev1user, 1 );
1109 }
1110 if( $theAttr eq "wikiusername" ) {
1111 return &TWiki::userToWikiName( $cacheRev1user );
1112 }
1113 if( $theAttr eq "date" ) {
1114 rizwank 1.1 return &TWiki::formatTime( $cacheRev1date );
1115 }
1116 # else assume attr "key"
1117 return "1.1";
1118 }
1119
1120 #=========================
1121 =pod
1122
1123 ---++ sub getMetaParent( $theMeta )
1124
1125 Not yet documented.
1126
1127 =cut
1128
1129 sub getMetaParent
1130 {
1131 my( $theMeta ) = @_;
1132
1133 my $value = "";
1134 my %parent = $theMeta->findOne( "TOPICPARENT" );
1135 rizwank 1.1 $value = $parent{"name"} if( %parent );
1136 return $value;
1137 }
1138
1139 #=========================
1140 =pod
1141
1142 ---++ sub getMetaFormField ( $theMeta, $theParams )
1143
1144 Not yet documented.
1145
1146 =cut
1147
1148 sub getMetaFormField
1149 {
1150 my( $theMeta, $theParams ) = @_;
1151
1152 my $name = $theParams;
1153 my $break = "";
1154 my @params = split( /\,\s*/, $theParams, 2 );
1155 if( @params > 1 ) {
1156 rizwank 1.1 $name = $params[0] || "";
1157 $break = $params[1] || 1;
1158 }
1159 my $value = "";
1160 my @fields = $theMeta->find( "FIELD" );
1161 foreach my $field ( @fields ) {
1162 $value = $field->{"value"};
1163 $value =~ s/^\s*(.*?)\s*$/$1/go;
1164 if( $name =~ /^($field->{"name"}|$field->{"title"})$/ ) {
1165 $value = breakName( $value, $break );
1166 return $value;
1167 }
1168 }
1169 return "";
1170 }
1171
1172 #=========================
1173 =pod
1174
1175 ---++ sub _getMetaFormName ( $theMeta )
1176
1177 rizwank 1.1 Returns the name of the form attached to the topic
1178
1179 =cut
1180
1181 sub _getMetaFormName
1182 {
1183 my( $theMeta ) = @_;
1184
1185 my %aForm = $theMeta->findOne( "FORM" );
1186 if( %aForm ) {
1187 return $aForm{"name"};
1188 }
1189 return "";
1190 }
1191
1192 #=========================
1193 =pod
1194
1195 ---++ sub getTextPattern ( $theText, $thePattern )
1196
1197 Not yet documented.
1198 rizwank 1.1
1199 =cut
1200
1201 sub getTextPattern
1202 {
1203 my( $theText, $thePattern ) = @_;
1204
1205 $thePattern =~ s/([^\\])([\$\@\%\&\#\'\`\/])/$1\\$2/go; # escape some special chars
1206 $thePattern =~ /(.*)/; # untaint
1207 $thePattern = $1;
1208 my $OK = 0;
1209 eval {
1210 $OK = ( $theText =~ s/$thePattern/$1/is );
1211 };
1212 $theText = "" unless( $OK );
1213
1214 return $theText;
1215 }
1216
1217 #=========================
1218 =pod
1219 rizwank 1.1
1220 ---++ sub wikiName ( $theWikiUserName )
1221
1222 Not yet documented.
1223
1224 =cut
1225
1226 sub wikiName
1227 {
1228 my( $theWikiUserName ) = @_;
1229
1230 $theWikiUserName =~ s/^.*\.//o;
1231 return $theWikiUserName;
1232 }
1233
1234 #=========================
1235 =pod
1236
1237 ---++ sub breakName ( $theText, $theParams )
1238
1239 Not yet documented.
1240 rizwank 1.1
1241 =cut
1242
1243 sub breakName
1244 {
1245 my( $theText, $theParams ) = @_;
1246
1247 my @params = split( /[\,\s]+/, $theParams, 2 );
1248 if( @params ) {
1249 my $len = $params[0] || 1;
1250 $len = 1 if( $len < 1 );
1251 my $sep = "- ";
1252 $sep = $params[1] if( @params > 1 );
1253 if( $sep =~ /^\.\.\./i ) {
1254 # make name shorter like "ThisIsALongTop..."
1255 $theText =~ s/(.{$len})(.+)/$1.../s;
1256
1257 } else {
1258 # split and hyphenate the topic like "ThisIsALo- ngTopic"
1259 $theText =~ s/(.{$len})/$1$sep/gs;
1260 $theText =~ s/$sep$//;
1261 rizwank 1.1 }
1262 }
1263 return $theText;
1264 }
1265
1266 #=========================
1267 # Turn a topic into a spaced-out topic, with space before each part of
1268 # the WikiWord.
1269 =pod
1270
1271 ---++ sub spacedTopic ( $topic )
1272
1273 Not yet documented.
1274
1275 =cut
1276
1277 sub spacedTopic
1278 {
1279 my( $topic ) = @_;
1280 # FindMe -> Find\s*Me
1281 # I18N fix
1282 rizwank 1.1 my $upperAlpha = $TWiki::regex{singleUpperAlphaRegex};
1283 my $lowerAlpha = $TWiki::regex{singleLowerAlphaRegex};
1284 $topic =~ s/($lowerAlpha)($upperAlpha)/$1 *$2/go;
1285 return $topic;
1286 }
1287
1288 #=========================
1289
1290 1;
1291
1292 # EOF
|