# Module of TWiki Collaboration Platform, http://TWiki.org/ # # Search engine of TWiki. # # Copyright (C) 2000-2003 Peter Thoeny, peter@thoeny.com # # For licensing info read license.txt file in the TWiki root. # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details, published at # http://www.gnu.org/copyleft/gpl.html # # Notes: # - Latest version at http://twiki.org/ # - Installation instructions in $dataDir/Main/TWikiDocumentation.txt # - Customize variables in TWiki.cfg when installing TWiki. # # 20000501 Kevin Kinnell : Many many many changes, best view is to # run a diff. # 20000605 Kevin Kinnell : Bug hunting. Fixed to allow web colors # spec'd as "word" instead of hex only. # Found a lovely bug that screwed up the # search limits because Perl (as we all know # but may forget) doesn't clear the $n match # params if a match fails... *^&$#!!! # PTh 03 Nov 2000: Performance improvements package TWiki::Search; use strict; # 'Use locale' for internationalisation of Perl sorting and searching - # main locale settings are done in TWiki::setupLocale BEGIN { # Do a dynamic 'use locale' for this module if( $TWiki::useLocale ) { require locale; import locale (); } } # =========================== # Normally writes no output, uncomment writeDebug line to get output of all RCS etc command to debug file sub _traceExec { my( $cmd, $result ) = @_; #TWiki::writeDebug( "Search exec: $cmd -> $result" ); } # ========================= sub searchWeb { ## 0501 kk : vvv Added params my ( $doInline, $theWebName, $theSearchVal, $theScope, $theOrder, $theRegex, $theLimit, $revSort, $caseSensitive, $noSummary, $noSearch, $noHeader, $noTotal, $doBookView, $doRenameView, $doShowLock, $noEmpty, $theTemplate, $theHeader, $theFormat, @junk ) = @_; ##TWiki::writeDebug "Search locale is $TWiki::siteLocale"; ## 0501 kk : vvv new option to limit results # process the result limit here, this is the 'global' limit for # all webs in a multi-web search ## ############# ## 0605 kk : vvv This code broke due to changes in the wiki.pm ## file; it used to rely on the value of $1 being ## a null string if there was no match. What a pity ## Perl doesn't do The Right Thing, but whatever--it's ## fixed now. if ($theLimit =~ /(^\d+$)/o) { # only digits, all else is the same as $theLimit = $1; # an empty string. "+10" won't work. } else { $theLimit = 0; # change "all" to 0, then to big number } if (! $theLimit ) { # PTh 03 Nov 2000: $theLimit = 32000; # Big number, needed for performance improvements } my $searchResult = ""; my $topic = $TWiki::mainTopicname; my @webList = (); # A value of 'all' or 'on' by itself gets all webs, # otherwise ignored (unless there is a web called "All".) my $searchAllFlag = ( $theWebName =~ /(^|[\,\s])(all|on)([\,\s]|$)/i ); # Search what webs? "" current web, list gets the list, all gets # all (unless marked in WebPrefs as NOSEARCHALL) if( $theWebName ) { foreach my $web ( split( /[\,\s]+/, $theWebName ) ) { # the web processing loop filters for valid web names, so don't do it here. if( $web =~ /^(all|on)$/i ) { # get list of all webs by scanning $dataDir opendir DIR, $TWiki::dataDir; my @tmpList = readdir(DIR); closedir(DIR); @tmpList = sort grep { s#^.+/([^/]+)$#$1# } grep { -d } map { "$TWiki::dataDir/$_" } grep { ! /^[._]/ } @tmpList; # what that does (looking from the bottom up) is take the file # list, filter out the dot directories and dot files, turn the # list into full paths instead of just file names, filter out # any non-directories, strip the path back off, and sort # whatever was left after all that (which should be merely a # list of directory's names.) foreach my $aweb ( @tmpList ) { push( @webList, $aweb ) unless( grep { /^$aweb$/ } @webList ); } } else { push( @webList, $web ) unless( grep { /^$web$/ } @webList ); } } } else { #default to current web push @webList, $TWiki::webName; } my $tempVal = ""; my $tmpl = ""; my $topicCount = 0; # JohnTalintyre my $originalSearch = $theSearchVal; my $renameTopic; my $renameWeb = ""; my $spacedTopic; $theTemplate = "searchformat" if( $theFormat ); if( $theTemplate ) { $tmpl = &TWiki::Store::readTemplate( "$theTemplate" ); # FIXME replace following with this @@@ } elsif( $doBookView ) { $tmpl = &TWiki::Store::readTemplate( "searchbookview" ); } elsif ($doRenameView ) { $tmpl = &TWiki::Store::readTemplate( "searchrenameview" ); # JohnTalintyre # Create full search string from topic name that is passed in my $renameTopic = $theSearchVal; if( $renameTopic =~ /(.*)\\\.(.*)/o ) { $renameWeb = $1; $renameTopic = $2; } $spacedTopic = spacedTopic( $renameTopic ); $spacedTopic = $renameWeb . '\.' . $spacedTopic if( $renameWeb ); # TODO: i18n fix $theSearchVal = "(^|[^A-Za-z0-9_])$theSearchVal" . '([^A-Za-z0-9_]|$)|' . '(\[\[' . $spacedTopic . '\]\])'; } else { $tmpl = &TWiki::Store::readTemplate( "search" ); } $tmpl =~ s/\%META{.*?}\%//go; # remove %META{"parent"}% my( $tmplHead, $tmplSearch, $tmplTable, $tmplNumber, $tmplTail ) = split( /%SPLIT%/, $tmpl ); $tmplHead = &TWiki::handleCommonTags( $tmplHead, $topic ); $tmplSearch = &TWiki::handleCommonTags( $tmplSearch, $topic ); $tmplNumber = &TWiki::handleCommonTags( $tmplNumber, $topic ); $tmplTail = &TWiki::handleCommonTags( $tmplTail, $topic ); if( ! $tmplTail ) { print ""; print "

TWiki Installation Error

"; # Might not be search.tmpl FIXME print "Incorrect format of search.tmpl (missing %SPLIT% parts)"; print ""; return; } if( ! $doInline ) { # print first part of full HTML page $tmplHead = &TWiki::getRenderedVersion( $tmplHead ); $tmplHead =~ s|||goi; # remove tags (PTh 06 Nov 2000) print $tmplHead; } if( ! $noSearch ) { # print "Search:" part #FIXME: The following regex changes the actual search string! $theSearchVal =~ s/&/&/go; $theSearchVal =~ s//>/go; $theSearchVal =~ s/^\.\*$/Index/go; $tmplSearch =~ s/%SEARCHSTRING%/$theSearchVal/go; if( $doInline ) { $searchResult .= $tmplSearch; } else { $tmplSearch = &TWiki::getRenderedVersion( $tmplSearch ); $tmplSearch =~ s|||goi; # remove tag print $tmplSearch; } } if( $caseSensitive ) { $tempVal = ""; } else { $tempVal = "-i"; } # Construct command line for grep. # 'grep' must use locales if needed, for case-insensitive searching. my $cmd = "%GREP% $tempVal -l -- $TWiki::cmdQuote%TOKEN%$TWiki::cmdQuote %FILES%"; my @tokens; if( $theRegex ) { $tempVal = $TWiki::egrepCmd; @tokens = split( /;/, $theSearchVal ); if( $theScope eq "topic" ) { # Fix for Codev.CantAnchorSearchREToEnd @tokens = map { s/\$$/\\\.txt\$/o; $_ } @tokens; } } else { $tempVal = $TWiki::fgrepCmd; @tokens = $theSearchVal; } $cmd =~ s/%GREP%/$tempVal/go; # write log entry if( ( $TWiki::doLogTopicSearch ) && ( ! $doInline ) ) { # 0501 kk : vvv Moved from search # PTh 17 May 2000: reverted to old behaviour, # e.g. do not log inline search # PTh 03 Nov 2000: Moved out of the 'foreach $thisWebName' loop my $tempVal = join( ' ', @webList ); &TWiki::Store::writeLog( "search", $tempVal, $theSearchVal ); } ## ############# ## 0501 kk : vvv New web processing loop, does what the old straight ## code did for each web the user requested. Note that ## '$theWebName' is mostly replaced by '$thisWebName' foreach my $thisWebName (@webList) { # PTh 03 Nov 2000: Add security check $thisWebName =~ s/$TWiki::securityFilter//go; $thisWebName =~ /(.*)/; $thisWebName = $1; # untaint variable next unless &TWiki::Store::webExists( $thisWebName ); # can't process what ain't thar my $thisWebBGColor = &TWiki::Prefs::getPreferencesValue( "WEBBGCOLOR", $thisWebName ) || "\#FF00FF"; my $thisWebNoSearchAll = &TWiki::Prefs::getPreferencesValue( "NOSEARCHALL", $thisWebName ); # make sure we can report this web on an 'all' search # DON'T filter out unless it's part of an 'all' search. # PTh 18 Aug 2000: Need to include if it is the current web next if ( ( $searchAllFlag ) && ( ( $thisWebNoSearchAll =~ /on/i ) || ( $thisWebName =~ /^[\.\_]/ ) ) && ( $thisWebName ne $TWiki::webName ) ); (my $baz = "foo") =~ s/foo//; # reset search vars. defensive coding # 0501 kjk : vvv New var for accessing web dirs. my $sDir = "$TWiki::dataDir/$thisWebName"; my @topicList = ""; if( $theSearchVal ) { # do grep search chdir( "$sDir" ); _traceExec( "chdir to $sDir", "" ); if( $theScope eq "topic" ) { @topicList = undef; foreach my $token ( @tokens ) { opendir( DIR, "."); my @tmpList = map { s/\$$/\\\.txt\$/o; $_ } readdir DIR; closedir DIR; if( $caseSensitive ) { @tmpList = grep /$token/, @tmpList } else { @tmpList = grep /$token/i, @tmpList } @topicList = (@topicList, @tmpList); } } else { # Scope is TEXT --> grep in TOPICs @topicList = ( "*.txt" ); # Search in RCS files too ? if ( $theScope eq "history" ) { push (@topicList, "*.txt,v") ; } foreach my $token ( @tokens ) { my $acmd = $cmd; $acmd =~ s/%TOKEN%/$token/o; $acmd =~ s/%FILES%/@topicList/; $acmd =~ /(.*)/; $acmd = "$1"; # untaint variable (NOTE: Needs a better check!) $tempVal = `$acmd`; _traceExec( $acmd, $tempVal ); @topicList = split( /\n/, $tempVal ); last if( ! @topicList ); } } # cut .txt extension and make Topics uniques my %tmpList; if ( $theScope eq "history" ) { $tempVal="txt|txt,v"; # Topic file extension + RCS file extension } else { $tempVal="txt"; # Topic file extension } foreach (@topicList) { if (/(.*)\.($tempVal)$/) { $tmpList{"$1"}=1; } } @topicList = keys(%tmpList); } next if ( $noEmpty && ! @topicList ); # Nothing to show for this topic # use hash tables for date, author, rev number and view permission my %topicRevDate = (); my %topicRevUser = (); my %topicRevNum = (); my %topicAllowView = (); # sort the topic list by date, author or topic name if( $theOrder eq "modified" ) { # PTh 03 Nov 2000: Performance improvement # Dates are tricky. For performance we do not read, say, # 2000 records of author/date, sort and then use only 50. # Rather we # * sort by file timestamp (to get a rough list) # * shorten list to the limit + some slack # * sort by rev date on shortened list to get the acurate list # Do performance exercise only if it pays off if( $theLimit + 20 < scalar(@topicList) ) { # sort by file timestamp, Schwartzian Transform my @tmpList = (); if( $revSort ) { @tmpList = map { $_->[1] } sort {$b->[0] <=> $a->[0] } map { [ (stat "$TWiki::dataDir\/$thisWebName\/$_.txt")[9], $_ ] } @topicList; } else { @tmpList = map { $_->[1] } sort {$a->[0] <=> $b->[0] } map { [ (stat "$TWiki::dataDir\/$thisWebName\/$_.txt")[9], $_ ] } @topicList; } # then shorten list and build the hashes for date and author my $idx = $theLimit + 10; # slack on limit @topicList = (); foreach( @tmpList ) { push( @topicList, $_ ); $idx -= 1; last if $idx <= 0; } } # build the hashes for date and author foreach( @topicList ) { my $tempVal = $_; # FIXME should be able to get data from topic my( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $tempVal ); my ( $revdate, $revuser, $revnum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $tempVal, $meta, 1 ); $topicRevUser{ $tempVal } = &TWiki::userToWikiName( $revuser ); $topicRevDate{ $tempVal } = $revdate; $topicRevNum{ $tempVal } = $revnum; $topicAllowView{ $tempVal } = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName, $text, $tempVal, $thisWebName ); } # sort by date (second time if exercise), Schwartzian Transform if( $revSort ) { @topicList = map { $_->[1] } sort {$b->[0] <=> $a->[0] } map { [ &TWiki::revDate2EpSecs( $topicRevDate{$_} ), $_ ] } @topicList; } else { @topicList = map { $_->[1] } sort {$a->[0] <=> $b->[0] } map { [ &TWiki::revDate2EpSecs( $topicRevDate{$_} ), $_ ] } @topicList; } } elsif( $theOrder eq "editby" ) { # sort by author # first we need to build the hashes for date and author foreach( @topicList ) { $tempVal = $_; my( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $tempVal ); my( $revdate, $revuser, $revnum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $tempVal, $meta, 1 ); $topicRevUser{ $tempVal } = &TWiki::userToWikiName( $revuser ); $topicRevDate{ $tempVal } = $revdate; $topicRevNum{ $tempVal } = $revnum; $topicAllowView{ $tempVal } = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName, $text, $tempVal, $thisWebName ); } # sort by author, Schwartzian Transform if( $revSort ) { @topicList = map { $_->[1] } sort {$b->[0] cmp $a->[0] } map { [ $topicRevUser{$_}, $_ ] } @topicList; } else { @topicList = map { $_->[1] } sort {$a->[0] cmp $b->[0] } map { [ $topicRevUser{$_}, $_ ] } @topicList; } } elsif( $theOrder =~ m/^formfield\((.*)\)$/ ) { # sort by TWikiForm field my $sortfield = $1; my %fieldVals= (); # first we need to build the hashes for fields foreach( @topicList ) { $tempVal = $_; my( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $tempVal ); my( $revdate, $revuser, $revnum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $tempVal, $meta, 1 ); $topicRevUser{ $tempVal } = &TWiki::userToWikiName( $revuser ); $topicRevDate{ $tempVal } = $revdate; $topicRevNum{ $tempVal } = $revnum; $topicAllowView{ $tempVal } = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName, $text, $tempVal, $thisWebName ); $fieldVals{ $tempVal } = getMetaFormField( $meta, $sortfield ); } # sort by field, Schwartzian Transform if( $revSort ) { @topicList = map { $_->[1] } sort {$b->[0] cmp $a->[0] } map { [ $fieldVals{$_}, $_ ] } @topicList; } else { @topicList = map { $_->[1] } sort {$a->[0] cmp $b->[0] } map { [ $fieldVals{$_}, $_ ] } @topicList; } } else { # sort by filename, Schwartzian Transform ##TWiki::writeDebug "Topic list before sort = @topicList"; if( $revSort ) { @topicList = map { $_->[1] } sort {$b->[0] cmp $a->[0] } map { [ $_, $_ ] } @topicList; } else { @topicList = map { $_->[1] } sort {$a->[0] cmp $b->[0] } map { [ $_, $_ ] } @topicList; } ##TWiki::writeDebug "Topic list after sort = @topicList"; } # output header of $thisWebName my( $beforeText, $repeatText, $afterText ) = split( /%REPEAT%/, $tmplTable ); if( $theHeader ) { $theHeader =~ s/\$n\(\)/\n/gos; # expand "$n()" to new line # TODO: i18n fix $theHeader =~ s/\$n([^a-zA-Z])/\n$1/gos; # expand "$n" to new line $theHeader =~ s/([^\n])$/$1\n/gos; $beforeText = $theHeader; $beforeText =~ s/\$web/$thisWebName/gos; } $beforeText =~ s/%WEBBGCOLOR%/$thisWebBGColor/go; $beforeText =~ s/%WEB%/$thisWebName/go; $beforeText = &TWiki::handleCommonTags( $beforeText, $topic ); $afterText = &TWiki::handleCommonTags( $afterText, $topic ); if( ! $noHeader ) { if( $doInline || $theFormat ) { # print at the end if formatted search because of table rendering $searchResult .= $beforeText; } else { $beforeText = &TWiki::getRenderedVersion( $beforeText, $thisWebName ); $beforeText =~ s|||goi; # remove tag print $beforeText; } } # output the list of topics in $thisWebName my $ntopics = 0; my $topic = ""; my $head = ""; my $revDate = ""; my $revUser = ""; my $revNum = ""; my $allowView = ""; my $locked = ""; foreach( @topicList ) { $topic = $_; my $meta = ""; my $text = ""; my $forceRendering = 0; # make sure we have date and author if( exists( $topicRevUser{$topic} ) ) { $revDate = $topicRevDate{$topic}; $revUser = $topicRevUser{$topic}; $revNum = $topicRevNum{$topic}; $allowView = $topicAllowView{$topic}; } else { # lazy query, need to do it at last ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic ); $text =~ s/%WEB%/$thisWebName/gos; $text =~ s/%TOPIC%/$topic/gos; $allowView = &TWiki::Access::checkAccessPermission( "view", $TWiki::wikiUserName, $text, $topic, $thisWebName ); ( $revDate, $revUser, $revNum ) = &TWiki::Store::getRevisionInfoFromMeta( $thisWebName, $topic, $meta, 1 ); $revUser = &TWiki::userToWikiName( $revUser ); } $locked = ""; if( $doShowLock ) { ( $tempVal ) = &TWiki::Store::topicIsLockedBy( $thisWebName, $topic ); if( $tempVal ) { $revUser = &TWiki::userToWikiName( $tempVal ); $locked = "(LOCKED)"; } } # Check security # FIXME - how deal with user login not available if coming from search script? if( ! $allowView ) { next; } if( $theFormat ) { $tempVal = $theFormat; $tempVal =~ s/([^\n])$/$1\n/gos; # cut last trailing new line $tempVal =~ s/\$n\(\)/\n/gos; # expand "$n()" to new line # TODO: i18n fix $tempVal =~ s/\$n([^a-zA-Z])/\n$1/gos; # expand "$n" to new line $tempVal =~ s/\$web/$thisWebName/gos; $tempVal =~ s/\$topic\(([^\)]*)\)/breakName( $topic, $1 )/geos; $tempVal =~ s/\$topic/$topic/gos; $tempVal =~ s/\$locked/$locked/gos; $tempVal =~ s/\$date/$revDate/gos; $tempVal =~ s/\$isodate/&TWiki::revDate2ISO($revDate)/geos; $tempVal =~ s/\$rev/1.$revNum/gos; $tempVal =~ s/\$wikiusername/$revUser/gos; $tempVal =~ s/\$username/&TWiki::wikiToUserName($revUser)/geos; if( $tempVal =~ m/\$text/ ) { # expand topic text ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic ) unless $text; if( $topic eq $TWiki::topicName ) { # defuse SEARCH in current topic to prevent loop $text =~ s/%SEARCH{.*?}%/SEARCH{...}/go; } $tempVal =~ s/\$text/$text/gos; $forceRendering = 1; } } else { $tempVal = $repeatText; } $tempVal =~ s/%WEB%/$thisWebName/go; $tempVal =~ s/%TOPICNAME%/$topic/go; $tempVal =~ s/%LOCKED%/$locked/o; $tempVal =~ s/%TIME%/$revDate/o; if( $revNum > 1 ) { $revNum = "r1.$revNum"; } else { $revNum = "NEW"; } $tempVal =~ s/%REVISION%/$revNum/o; $tempVal =~ s/%AUTHOR%/$revUser/o; if( ( $doInline || $theFormat ) && ( ! ( $forceRendering ) ) ) { # print at the end if formatted search because of table rendering # do nothing } else { $tempVal = &TWiki::handleCommonTags( $tempVal, $topic ); $tempVal = &TWiki::getRenderedVersion( $tempVal ); } if( $doRenameView ) { # added JET 19 Feb 2000 my $rawText = &TWiki::Store::readTopicRaw( $thisWebName, $topic ); my $changeable = ""; my $changeAccessOK = &TWiki::Access::checkAccessPermission( "change", $TWiki::wikiUserName, $text, $topic, $thisWebName ); if( ! $changeAccessOK ) { $changeable = "(NO CHANGE PERMISSION)"; $tempVal =~ s/%SELECTION%.*%SELECTION%//o; } else { $tempVal =~ s/%SELECTION%//go; } $tempVal =~ s/%CHANGEABLE%/$changeable/o; $tempVal =~ s/%LABEL%/$doRenameView/go; my $reducedOutput = ""; # Remove lines that don't contain the topic and highlight matched string my $insidePRE = 0; my $insideVERBATIM = 0; my $noAutoLink = 0; foreach( split( /\n/, $rawText ) ) { next if( /^%META:TOPIC(INFO|MOVED)/ ); s//>/go; # This code is in far too many places m|
|i  && ( $insidePRE = 1 );
                   m|
|i && ( $insidePRE = 0 ); if( m||i ) { $insideVERBATIM = 1; } if( m||i ) { $insideVERBATIM = 0; } m||i && ( $noAutoLink = 1 ); m||i && ( $noAutoLink = 0 ); if( ! ( $insidePRE || $insideVERBATIM || $noAutoLink ) ) { # Case insensitive option is required to get [[spaced Word]] to match # TODO: i18n fix my $match = "(^|[^A-Za-z0-9_.])($originalSearch)(?=[^A-Za-z0-9_]|\$)"; # FIXME: Should use /o here since $match is based on # search string. my $subs = s|$match|$1$2 |g; $match = '(\[\[)' . "($spacedTopic)" . '(?=\]\])'; $subs += s|$match|$1$2 |gi; if( $subs ) { $topicCount++ if( ! $reducedOutput ); $reducedOutput .= "$_
\n" if( $subs ); } } } $tempVal =~ s/%TOPIC_NUMBER%/$topicCount/go; $tempVal =~ s/%TEXTHEAD%/$reducedOutput/go; next if ( ! $reducedOutput ); } elsif( $doBookView ) { # BookView, added PTh 20 Jul 2000 if( ! $text ) { ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic ); } $text = &TWiki::handleCommonTags( $text, $topic, $thisWebName ); $text = &TWiki::getRenderedVersion( $text, $thisWebName ); # FIXME: What about meta data rendering? $tempVal =~ s/%TEXTHEAD%/$text/go; } elsif( $theFormat ) { # free format, added PTh 10 Oct 2001 if( ! $text ) { ( $meta, $text ) = &TWiki::Store::readTopic( $thisWebName, $topic ); $text =~ s/%WEB%/$thisWebName/gos; $text =~ s/%TOPIC%/$topic/gos; } $tempVal =~ s/\$summary/&TWiki::makeTopicSummary( $text, $topic, $thisWebName )/geos; $tempVal =~ s/\$formfield\(\s*([^\)]*)\s*\)/getMetaFormField( $meta, $1 )/geos; $tempVal =~ s/\$pattern\(\s*(.*?\s*\.\*)\)/getTextPattern( $text, $1 )/geos; $tempVal =~ s/\$nop(\(\))?//gos; # remove filler, useful for nested search $tempVal =~ s/\$quot(\(\))?/\"/gos; # expand double quote $tempVal =~ s/\$percnt(\(\))?/\%/gos; # expand percent $tempVal =~ s/\$dollar(\(\))?/\$/gos; # expand dollar } elsif( $noSummary ) { $tempVal =~ s/%TEXTHEAD%//go; $tempVal =~ s/ //go; } else { # regular search view if( $text ) { $head = $text; } else { $head = &TWiki::Store::readFileHead( "$TWiki::dataDir\/$thisWebName\/$topic.txt", 16 ); } $head = &TWiki::makeTopicSummary( $head, $topic, $thisWebName ); $tempVal =~ s/%TEXTHEAD%/$head/go; } if( $doInline || $theFormat ) { # print at the end if formatted search because of table rendering $searchResult .= $tempVal; } else { $tempVal = &TWiki::getRenderedVersion( $tempVal, $thisWebName ); $tempVal =~ s|||goi; # remove tag print $tempVal; } $ntopics += 1; last if $ntopics >= $theLimit; } # output footer of $thisWebName if( $doInline || $theFormat ) { # print at the end if formatted search because of table rendering $afterText =~ s/\n$//gos; # remove trailing new line $searchResult .= $afterText; } else { $afterText = &TWiki::getRenderedVersion( $afterText, $thisWebName ); $afterText =~ s|||goi; # remove tag print $afterText; } if( ! $noTotal ) { # print "Number of topics:" part my $thisNumber = $tmplNumber; $thisNumber =~ s/%NTOPICS%/$ntopics/go; if( $doInline || $theFormat ) { # print at the end if formatted search because of table rendering $searchResult .= $thisNumber; } else { $thisNumber = &TWiki::getRenderedVersion( $thisNumber, $thisWebName ); $thisNumber =~ s|||goi; # remove tag print $thisNumber; } } } if( $theFormat ) { $searchResult =~ s/\n$//gos; # remove trailing new line } if( $doInline ) { # return formatted search result return $searchResult; } else { if( $theFormat ) { # finally print $searchResult which got delayed because of formatted search $tmplTail = "$searchResult$tmplTail"; } # print last part of full HTML page $tmplTail = &TWiki::getRenderedVersion( $tmplTail ); $tmplTail =~ s|||goi; # remove tag print $tmplTail; } return $searchResult; } #========================= sub getMetaFormField { my( $theMeta, $theParams ) = @_; my $name = $theParams; my $break = ""; my @params = split( /\,\s*/, $theParams, 2 ); if( @params > 1 ) { $name = $params[0] || ""; $break = $params[1] || 1; } my $title = ""; my $value = ""; my @fields = $theMeta->find( "FIELD" ); foreach my $field ( @fields ) { $title = $field->{"title"}; $value = $field->{"value"}; $value =~ s/^\s*(.*?)\s*$/$1/go; if( $title eq $name ) { $value = breakName( $value, $break ); return $value; } } return ""; } #========================= sub getTextPattern { my( $theText, $thePattern ) = @_; $thePattern =~ s/([^\\])([\$\@\%\&\#\'\`\/])/$1\\$2/go; # escape some special chars $thePattern =~ /(.*)/; # untaint $thePattern = $1; $theText = "" unless( $theText =~ s/$thePattern/$1/is ); return $theText; } #========================= sub breakName { my( $theText, $theParams ) = @_; my @params = split( /[\,\s]+/, $theParams, 2 ); if( @params ) { my $len = $params[0] || 1; $len = 1 if( $len < 1 ); my $sep = "- "; $sep = $params[1] if( @params > 1 ); if( $sep =~ /^\.\.\./i ) { # make name shorter like "ThisIsALongTop..." $theText =~ s/(.{$len})(.+)/$1.../; } else { # split and hyphenate the topic like "ThisIsALo- ngTopic" $theText =~ s/(.{$len})/$1$sep/g; $theText =~ s/$sep$//; } } return $theText; } #========================= sub spacedTopic { my( $topic ) = @_; # FindMe -> Find\s*Me # TODO: i18n fix $topic =~ s/([a-z])([A-Z])/$1 *$2/go; return $topic; } #========================= 1; # EOF