'!\.(All)?Recent(Changes|Uploads)$!',
'group' => '!\.Group(Print)?(Header|Footer|Attributes)$!',
'self' => str_replace('.', '\\.', "!^$pagename$!")));
# The list=grouphomes search pattern requires to scan
# all PageStore directories to get the pagenames.
# This takes (a tiny amoint of) time, so we only do it when needed.
function EnablePageListGroupHomes() {
global $SearchPatterns;
if (isset($SearchPatterns['grouphomes'])) return;
$groups = $homes = array();
foreach(ListPages() as $pn) {
list($g, $n) = explode(".", $pn);
@$groups[$g]++;
}
foreach($groups as $g => $cnt) {
$homes[] = MakePageName("$g.$g", "$g.");
}
$SearchPatterns['grouphomes'] = array('/^('.implode('|', $homes).')$/');
}
## $FPLFormatOpt is a list of options associated with fmt=
## values. 'default' is used for any undefined values of fmt=.
SDVA($FPLFormatOpt, array(
'default' => array('fn' => 'FPLTemplate', 'fmt' => '#default'),
'bygroup' => array('fn' => 'FPLTemplate', 'template' => '#bygroup',
'class' => 'fplbygroup'),
'simple' => array('fn' => 'FPLTemplate', 'template' => '#simple',
'class' => 'fplsimple'),
'group' => array('fn' => 'FPLTemplate', 'template' => '#group',
'class' => 'fplgroup'),
'title' => array('fn' => 'FPLTemplate', 'template' => '#title',
'class' => 'fpltitle', 'order' => 'title'),
'count' => array('fn' => 'FPLCountA'),
));
SDV($SearchResultsFmt, "
\$[SearchFor]
\$MatchList
\$[SearchFound]
");
SDV($SearchQuery, str_replace('$', '$',
PHSC(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES)));
XLSDV('en', array(
'SearchFor' => 'Results of search for $Needle:',
'SearchFound' =>
'$MatchCount pages found out of $MatchSearched pages searched.'));
SDV($PageListArgPattern, '((?:\\$:?)?\\w[-\\w]*)[:=]');
Markup('pagelist', 'directives',
'/\\(:(pagelist)(\\s+.*?)?:\\)/i', "MarkupPageList");
Markup('searchbox', 'directives',
'/\\(:(searchbox)(\\s.*?)?:\\)/', "MarkupPageList");
Markup('searchresults', 'directives',
'/\\(:(searchresults)(\\s+.*?)?:\\)/i', "MarkupPageList");
function MarkupPageList($m) {
extract($GLOBALS["MarkupToHTML"]); # get $pagename
switch ($m[1]) {
case 'pagelist':
return FmtPageList('$MatchList', $pagename, array('o' => $m[2].' '));
case 'searchbox':
return SearchBox($pagename,
ParseArgs(@$m[2], $GLOBALS['PageListArgPattern']));
case 'searchresults':
return FmtPageList($GLOBALS['SearchResultsFmt'],
$pagename, array('req' => 1, 'request'=>1, 'o' => @$m[2]));
}
}
# called from PageListIf and FPLExpandItemVars
class cb_pl_expandvars extends PPRC {
function pl_expandvars($m) {
$pn = $this->vars;
return PVSE(PageVar($pn, $m[2], $m[1]));
}
}
SDV($SaveAttrPatterns['/\\(:(searchresults|pagelist)(\\s+.*?)?:\\)/i'], ' ');
SDV($HandleActions['search'], 'HandleSearchA');
SDV($HandleAuth['search'], 'read');
SDV($ActionTitleFmt['search'], '| $[Search Results]');
SDVA($PageListFilters, array(
'PageListCache' => 80,
'PageListProtect' => 90,
'PageListSources' => 100,
'PageListPasswords' => 120,
'PageListIf' => 140,
'PageListTermsTargets' => 160,
'PageListVariables' => 180,
'PageListSort' => 900,
));
function CorePageListSorts($x, $y, $o) {
global $PCache;
if($o == 'title')
return @strcasecmp($PCache[$x]['=title'],$PCache[$y]['=title']);
return @($PCache[$x][$o]-$PCache[$y][$o]);
}
foreach(array('random', 'size', 'time', 'ctime', 'title') as $o)
SDV($PageListSortCmp[$o], 'CorePageListSorts');
define('PAGELIST_PRE' , 1);
define('PAGELIST_ITEM', 2);
define('PAGELIST_POST', 4);
## SearchBox generates the output of the (:searchbox:) markup.
## If $SearchBoxFmt is defined, that is used, otherwise a searchbox
## is generated. Options include group=, size=, label=.
function SearchBox($pagename, $opt) {
global $SearchBoxFmt, $SearchBoxInputType, $SearchBoxOpt, $SearchQuery, $EnablePathInfo;
if (isset($SearchBoxFmt)) return Keep(FmtPageName($SearchBoxFmt, $pagename));
SDVA($SearchBoxOpt, array('size' => '40',
'label' => FmtPageName('$[Search]', $pagename),
'placeholder' => FmtPageName('$[Search]', $pagename),
'value' => str_replace("'", "'", $SearchQuery)));
$opt = array_merge((array)$SearchBoxOpt, @$_GET, (array)$opt);
$opt['action'] = 'search';
$target = (@$opt['target'])
? MakePageName($pagename, $opt['target']) : $pagename;
$opt['n'] = IsEnabled($EnablePathInfo, 0) ? '' : $target;
$out = FmtPageName(" class='wikisearch' action='\$PageUrl' method='get'>",
$target);
foreach($opt as $k => $v) {
if ($v == '' || is_array($v)) continue;
$v = PHSC($v, ENT_QUOTES, null, false);
$opt[$k] = $v;
if(preg_match('/^(q|label|value|size|placeholder|aria-\\w+)$/', $k)) continue;
$k = PHSC($k, ENT_QUOTES, null, false);
$out .= "";
}
SDV($SearchBoxInputType, 'text');
$out .= "';
}
## FmtPageList combines options from markup, request form, and url,
## calls the appropriate formatting function, and returns the string.
function FmtPageList($outfmt, $pagename, $opt) {
global $GroupPattern, $FmtV, $PageListArgPattern,
$FPLFormatOpt, $FPLFunctions;
# get any form or url-submitted request
$rq = PHSC(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES);
# build the search string
$FmtV['$Needle'] = $opt['o'] . ' ' . $rq;
# Handle "group/" at the beginning of the form-submitted request
if (preg_match("!^($GroupPattern(\\|$GroupPattern)*)?/!i", $rq, $match)) {
$opt['group'] = @$match[1];
$rq = substr($rq, strlen(strval(@$match[1]))+1);
}
$opt = array_merge($opt, ParseArgs($opt['o'], $PageListArgPattern));
# merge markup options with form and url
if (@$opt['request'] && @$_REQUEST) {
$rkeys = preg_grep('/^=/', array_keys($_REQUEST), PREG_GREP_INVERT);
if ($opt['request'] != '1') {
list($incl, $excl) = GlobToPCRE($opt['request']);
if ($excl) $rkeys = array_diff($rkeys, preg_grep("/$excl/", $rkeys));
if ($incl) $rkeys = preg_grep("/$incl/", $rkeys);
}
$cleanrequest = array();
foreach($rkeys as $k) {
$cleanrequest[$k] = stripmagic($_REQUEST[$k]);
if(substr($k, 0, 4)=='ptv_') # defined separately in forms
$cleanrequest['$:'.substr($k, 4)] = stripmagic($_REQUEST[$k]);
}
$opt = array_merge($opt, ParseArgs($rq, $PageListArgPattern), $cleanrequest);
}
# non-posted blank search requests return nothing
if (@($opt['req'] && !$opt['-'] && !$opt[''] && !$opt['+'] && !$opt['q']))
return '';
# terms and group to be included and excluded
$GLOBALS['SearchIncl'] = array_merge((array)@$opt[''], (array)@$opt['+']);
$GLOBALS['SearchExcl'] = (array)@$opt['-'];
$GLOBALS['SearchGroup'] = @$opt['group'];
$fmt = @$opt['fmt']; if (!$fmt) $fmt = 'default';
$fmtopt = @$FPLFormatOpt[$fmt];
if (!is_array($fmtopt)) {
if ($fmtopt) $fmtopt = array('fn' => $fmtopt);
elseif (@$FPLFunctions[$fmt])
$fmtopt = array('fn' => $FPLFunctions[$fmt]);
else $fmtopt = $FPLFormatOpt['default'];
}
$fmtfn = @$fmtopt['fn'];
if (!is_callable($fmtfn)) $fmtfn = $FPLFormatOpt['default']['fn'];
$matches = array();
$opt = array_merge($fmtopt, $opt);
$out = $fmtfn($pagename, $matches, $opt);
$FmtV['$MatchCount'] = count($matches);
if ($outfmt != '$MatchList')
{ $FmtV['$MatchList'] = $out; $out = FmtPageName($outfmt, $pagename); }
if (@$out[0] == '<') $out = Keep($out);
return PRR($out);
}
## MakePageList generates a list of pages using the specifications given
## by $opt.
function MakePageList($pagename, $opt, $retpages = 1) {
global $MakePageListOpt, $PageListFilters, $PCache;
StopWatch('MakePageList pre');
SDVA($MakePageListOpt, array('list' => 'default'));
$opt = array_merge((array)$MakePageListOpt, (array)$opt);
if (!@$opt['order'] && !@$opt['trail']) $opt['order'] = 'name';
$opt['order'] = preg_replace('/[^-\\w:$]+/', ',', strval(@$opt['order']));
ksort($opt); $opt['=key'] = md5(serialize($opt));
$itemfilters = array(); $postfilters = array();
asort($PageListFilters);
$opt['=phase'] = PAGELIST_PRE; $list=array(); $pn=NULL; $page=NULL;
foreach($PageListFilters as $fn => $v) {
if ($v<0) continue;
$ret = $fn($list, $opt, $pagename, $page);
if ($ret & PAGELIST_ITEM) $itemfilters[] = $fn;
if ($ret & PAGELIST_POST) $postfilters[] = $fn;
}
StopWatch("MakePageList items count=".count($list).", filters=".implode(',',$itemfilters));
$opt['=phase'] = PAGELIST_ITEM;
$matches = array(); $opt['=readc'] = 0;
foreach((array)$list as $pn) {
$page = array();
foreach((array)$itemfilters as $fn)
if (!$fn($list, $opt, $pn, $page)) continue 2;
$page['pagename'] = $page['name'] = $pn;
PCache($pn, $page);
$matches[] = $pn;
}
$list = $matches;
StopWatch("MakePageList post count=".count($list).", readc={$opt['=readc']}");
$opt['=phase'] = PAGELIST_POST; $pn=NULL; $page=NULL;
foreach((array)$postfilters as $fn)
$fn($list, $opt, $pagename, $page);
if ($retpages)
for($i=0; $i '') ? PAGELIST_ITEM : 0;
$condspec = $opt['if'];
$Cursor['='] = $pn;
$varpat = '\\{([=*]|!?[-\\w.\\/\\x80-\\xff]*)(\\$:?\\w+)\\}';
while (preg_match("/$varpat/", $condspec, $match)) {
$cb = new cb_pl_expandvars($pn);
$condspec = preg_replace_callback("/$varpat/",
array($cb, 'pl_expandvars'), $condspec);
}
if (!preg_match("/^\\s*(!?)\\s*(\\S*)\\s*(.*?)\\s*$/", $condspec, $match))
return 0;
list($x, $not, $condname, $condparm) = $match;
if (!isset($Conditions[$condname])) return 1;
$tf = (int)@eval("return ({$Conditions[$condname]});");
return (boolean)($tf xor $not);
}
function PageListTermsTargets(&$list, &$opt, $pn, &$page) {
global $PageIndexTermsFunction, $FmtV, $EnableSearchAtLeastOneTerm;
static $reindex = array();
$fold = $GLOBALS['StrFoldFunction'];
switch ($opt['=phase']) {
case PAGELIST_PRE:
$FmtV['$MatchSearched'] = count($list);
$incl = $incl1 = $excl = array();
if(IsEnabled($EnableSearchAtLeastOneTerm, 0))
foreach((array)@$opt[''] as $i) { $incl1[] = $fold($i); }
else
foreach((array)@$opt[''] as $i) { $incl[] = $fold($i); }
foreach((array)@$opt['+'] as $i) { $incl[] = $fold($i); }
foreach((array)@$opt['-'] as $i) { $excl[] = $fold($i); }
$indexterms = $PageIndexTermsFunction($incl);
foreach($incl as $i) {
$delim = (!preg_match('/[^\\w\\x80-\\xff]/', $i)) ? '$' : '/';
$opt['=inclp'][] = $delim . preg_quote($i,$delim) . $delim . 'i';
}
if ($incl1)
$opt['=inclp'][] = '/'.implode('|', array_map('preg_quote',$incl1)).'/i';
if ($excl)
$opt['=exclp'][] = '$'.implode('|', array_map('preg_quote',$excl)).'$i';
$opt['=linka'] = array();
if (@$opt['links']) $opt['link'] = $opt['links'];
if (@$opt['link']) {
if (preg_match('/^\s*-|[,*?![\\]]/', $opt['link']))
$opt['=linka'] = PageListLinkPatterns($opt['link']);
else {
$link = MakePageName($pn, $opt['link']);
$opt['=linkp'] = "/(^|,)$link(,|$)/i";
$indexterms[] = " $link ";
}
}
if (@$opt['category']) {
$c = preg_replace('/(^|,)([+-]?)/', '$1$2!', $opt['category']);
$opt['=linka'] = array_merge($opt['=linka'], PageListLinkPatterns($c));
}
if (@$opt['=cached']) return 0;
if ($indexterms||@$opt['=linka']) {
StopWatch("PageListTermsTargets begin count=".count($list));
$xlist = PageIndexGrep($indexterms, true, @$opt['=linka']);
$list = array_diff($list, $xlist);
StopWatch("PageListTermsTargets end count=".count($list));
}
if (@$opt['=inclp'] || @$opt['=exclp'] || @$opt['=linkp'] || @$opt['=linka'])
return PAGELIST_ITEM|PAGELIST_POST;
return 0;
case PAGELIST_ITEM:
if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
if (!$page) return 0;
$targets = strval(@$page['targets']);
if (@$opt['=linka']) {
if (! PageListMatchTargets($targets, $opt['=linka']))
{ $reindex[] = $pn; return 0; }
}
if (@$opt['=linkp'] && !preg_match($opt['=linkp'], $targets))
{ $reindex[] = $pn; return 0; }
if (@$opt['=inclp'] || @$opt['=exclp']) {
$text = $fold($pn."\n$targets\n".@$page['text']);
foreach((array)@$opt['=exclp'] as $i)
if (preg_match($i, $text)) return 0;
foreach((array)@$opt['=inclp'] as $i)
if (!preg_match($i, $text)) {
if ($i[0] == '$') $reindex[] = $pn;
return 0;
}
}
return 1;
case PAGELIST_POST:
if ($reindex) PageIndexQueueUpdate($reindex);
$reindex = array();
return 0;
}
}
function PageListMatchTargets($targets, $links) {
$targets = preg_split('/[, ]+/', trim($targets), -1, PREG_SPLIT_NO_EMPTY);
if (@$links['none'] && MatchNames($targets, $links['none'])) return false;
if (@$links['any'] && !MatchNames($targets, $links['any'])) return false;
if (@$links['req']) foreach($links['req'] as $pat)
if (!MatchNames($targets, $pat)) return false;
return true;
}
function PageListLinkPatterns($pat) {
# custom FixGlobToPCRE
$pat = str_replace('/', '.', $pat);
$pat = preg_replace('/([\\s,][-+]?)([^\\/.\\s,!]+)(?=[\\s,])/', '$1*.$2', ",$pat,");
$pat = preg_quote($pat, '/');
$pat = str_replace(array('\\*', '\\?', '\\[', '\\]', '\\^', '\\-', '\\+', ','),
array('.*', '.', '[', ']', '^', '-', '+', ' '), $pat);
$req = array();
$patterns = array('req'=>array());
$args = ParseArgs($pat);
if (@$args['']) $patterns['any'] = '/^('.implode('|', $args['']).')$/i';
if (@$args['-']) $patterns['none'] = '/^('.implode('|', $args['-']).')$/i';
if (@$args['+']) foreach($args['+'] as $p) $patterns['req'][] = "/^$p$/i";
return $patterns;
}
function PageListVariables(&$list, &$opt, $pn, &$page) {
global $PageListVarFoldFn, $StrFoldFunction;
$fold = empty($PageListVarFoldFn)
? $StrFoldFunction : $PageListVarFoldFn;
switch ($opt['=phase']) {
case PAGELIST_PRE:
$varlist = preg_grep('/^\\$/', array_keys($opt));
if (!$varlist) return 0;
foreach($varlist as $v) {
list($inclp, $exclp) = GlobToPCRE($opt[$v]);
if ($inclp) $opt['=varinclp'][$v] = $fold("/$inclp/i");
if ($exclp) $opt['=varexclp'][$v] = $fold("/$exclp/i");
}
return PAGELIST_ITEM;
case PAGELIST_ITEM:
if (@$opt['=varinclp'])
foreach($opt['=varinclp'] as $v => $pat)
if (!preg_match($pat, $fold(PageVar($pn, $v)))) return 0;
if (@$opt['=varexclp'])
foreach($opt['=varexclp'] as $v => $pat)
if (preg_match($pat, $fold(PageVar($pn, $v)))) return 0;
return 1;
}
}
function PageListSort(&$list, &$opt, $pn, &$page) {
global $PageListSortCmp, $PCache, $PageListSortRead;
SDVA($PageListSortRead, array('name' => 0, 'group' => 0, 'random' => 0,
'title' => 0));
switch ($opt['=phase']) {
case PAGELIST_PRE:
$ret = 0;
foreach(preg_split('/[^-\\w:$]+/', @$opt['order'], -1, PREG_SPLIT_NO_EMPTY)
as $o) {
$ret |= PAGELIST_POST;
$r = '+';
if ($o[0] == '-') { $r = '-'; $o = substr($o, 1); }
$opt['=order'][$o] = $r;
if ($o[0] != '$' &&
(!isset($PageListSortRead[$o]) || $PageListSortRead[$o]))
$ret |= PAGELIST_ITEM;
}
StopWatch(@"PageListSort pre ret=$ret order={$opt['order']}");
return $ret;
case PAGELIST_ITEM:
if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
return 1;
}
## case PAGELIST_POST
StopWatch('PageListSort begin');
$order = $opt['=order'];
if (@$order['title'])
foreach($list as $pn) $PCache[$pn]['=title'] = PageVar($pn, '$Title');
if (@$order['group'])
foreach($list as $pn) $PCache[$pn]['group'] = PageVar($pn, '$Group');
if (@$order['random'])
{ NoCache(); foreach($list as $pn) $PCache[$pn]['random'] = rand(); }
foreach(preg_grep('/^\\$/', array_keys($order)) as $o)
foreach($list as $pn)
$PCache[$pn][$o] = PageVar($pn, $o);
foreach($PageListSortCmp as $o=>$f)
if(! is_callable($f)) # DEPRECATED
$PageListSortCmp[$o] = create_function( # called by old addon needing update, see pmwiki.org/CustomPagelistSortOrder
'$x,$y', "global \$PCache; return {$f};");
StopWatch('PageListSort sort');
if (count($opt['=order'])) {
$PCache['=pagelistoptorder'] = $opt['=order'];
uasort($list, 'PageListUASort');
}
StopWatch('PageListSort end');
}
function PageListUASort($x,$y) {
global $PCache, $PageListSortCmp, $PageListSortCmpFunction;
foreach($PCache['=pagelistoptorder'] as $o => $r) {
$sign = ($r == '-') ? -1 : 1;
if (@$PageListSortCmp[$o] && is_callable($PageListSortCmp[$o]))
$c = $PageListSortCmp[$o]($x, $y, $o);
else
$c = @$PageListSortCmpFunction($PCache[$x][$o],$PCache[$y][$o]);
if ($c) return $sign*$c;
}
return 0;
}
function PageListCache(&$list, &$opt, $pn, &$page) {
global $PageListCacheDir, $LastModTime;
if (@!$PageListCacheDir) return 0;
if (isset($opt['cache']) && !$opt['cache']) return 0;
$key = $opt['=key'];
$cache = "$PageListCacheDir/$key,cache";
switch ($opt['=phase']) {
case PAGELIST_PRE:
if (!file_exists($cache) || filemtime($cache) <= $LastModTime)
return PAGELIST_POST;
StopWatch("PageListCache begin load key=$key");
@list($list, $opt['=protectsafe']) =
unserialize(file_get_contents($cache));
$opt['=cached'] = 1;
if(!is_array($opt['=protectsafe'])) $opt['=protectsafe'] = array();
StopWatch("PageListCache end load");
return 0;
case PAGELIST_POST:
StopWatch("PageListCache begin save key=$key");
$fp = @fopen($cache, "w");
if ($fp) {
fputs($fp, serialize(array($list, (array)@$opt['=protectsafe'])));
fclose($fp);
}
StopWatch("PageListCache end save");
return 0;
}
return 0;
}
## HandleSearchA performs ?action=search. It's basically the same
## as ?action=browse, except it takes its contents from Site.Search.
function HandleSearchA($pagename, $level = 'read') {
global $PageSearchForm, $FmtV, $HandleSearchFmt,
$PageStartFmt, $PageEndFmt;
SDV($HandleSearchFmt,array(&$PageStartFmt, '$PageText', &$PageEndFmt));
SDV($PageSearchForm, '$[{$SiteGroup}/Search]');
$form = RetrieveAuthPage($pagename, $level, true, READPAGE_CURRENT);
if (!$form) Abort("?unable to read $pagename");
PCache($pagename, $form);
$text = preg_replace('/\\[([=@])(.*?)\\1\\]/s', ' ', strval(@$form['text']));
if (!preg_match('/\\(:searchresults(\\s.*?)?:\\)/', $text))
foreach((array)$PageSearchForm as $formfmt) {
$form = ReadPage(FmtPageName($formfmt, $pagename), READPAGE_CURRENT);
if (@$form['text']) break;
}
$text = @$form['text'];
if (!$text) $text = '(:searchresults:)';
$FmtV['$PageText'] = MarkupToHTML($pagename,$text);
PrintFmt($pagename, $HandleSearchFmt);
}
########################################################################
## The functions below provide different formatting options for
## the output list, controlled by the fmt= parameter and the
## $FPLFormatOpt hash.
########################################################################
## This helper function handles the count= parameter for extracting
## a range of pagelist in the list.
function CalcRange($range, $n) {
if ($n < 1) return array(0, 0);
if (strpos($range, '..') === false) {
$range = intval($range);
if ($range > 0) return array(1, min($range, $n));
if ($range < 0) return array(max($n + $range + 1, 1), $n);
return array(1, $n);
}
list($r0, $r1) = explode('..', $range);
$r0 = intval($r0); $r1 = intval($r1);
if ($r0 < 0) $r0 += $n + 1;
if ($r1 < 0) $r1 += $n + 1;
else if ($r1 == 0) $r1 = $n;
if ($r0 < 1 && $r1 < 1) return array($n+1, $n+1);
return array(max($r0, 1), max($r1, 1));
}
## FPLCountA handles fmt=count
function FPLCountA($pagename, &$matches, $opt) {
$matches = array_values(MakePageList($pagename, $opt, 0));
return count($matches);
}
SDVA($FPLTemplateFunctions, array(
'FPLTemplateLoad' => 100,
'FPLTemplateDefaults' => 200,
'FPLTemplatePageList' => 300,
'FPLTemplateSliceList' => 400,
'FPLTemplateFormat' => 500
));
function FPLTemplate($pagename, &$matches, $opt) {
global $FPLTemplateFunctions;
StopWatch("FPLTemplate: Chain begin");
asort($FPLTemplateFunctions, SORT_NUMERIC);
$fnlist = $FPLTemplateFunctions;
$output = '';
foreach($FPLTemplateFunctions as $fn=>$i) {
if ($i<0) continue;
StopWatch("FPLTemplate: $fn");
$fn($pagename, $matches, $opt, $tparts, $output);
}
StopWatch("FPLTemplate: Chain end");
return $output;
}
## Loads a template section
function FPLTemplateLoad($pagename, $matches, $opt, &$tparts){
global $Cursor, $FPLTemplatePageFmt, $RASPageName, $PageListArgPattern, $IncludedPages;
SDV($FPLTemplatePageFmt, array('{$FullName}',
'{$SiteGroup}.LocalTemplates', '{$SiteGroup}.PageListTemplates'));
$template = @$opt['template'];
if (!$template) $template = @$opt['fmt'];
$ttext = RetrieveAuthSection($pagename, $template, $FPLTemplatePageFmt);
$ttext = PVSE(Qualify($RASPageName, $ttext));
if ($ttext) @$IncludedPages[$RASPageName]++;
## save any escapes
$ttext = MarkupEscape($ttext);
## remove any anchor markups to avoid duplications
$ttext = preg_replace('/\\[\\[#[A-Za-z][-.:\\w]*\\]\\]/', '', $ttext);
## extract portions of template
$tparts = preg_split('/\\(:(template)\\s+([-!]?)\\s*(\\w+)\\s*(.*?):\\)/i',
$ttext, -1, PREG_SPLIT_DELIM_CAPTURE);
}
## Merge parameters from (:template default :) with those in the (:pagelist:)
function FPLTemplateDefaults($pagename, $matches, &$opt, &$tparts){
global $PageListArgPattern;
$i = 0;
while ($i < count($tparts)) {
if ($tparts[$i] != 'template') { $i++; continue; }
if ($tparts[$i+2] != 'defaults' && $tparts[$i+2] != 'default') { $i+=5; continue; }
$pvars = $GLOBALS['MarkupTable']['{$var}']; # expand {$PVars}
$ttext = preg_replace_callback($pvars['pat'], $pvars['rep'], $tparts[$i+3]);
$opt = array_merge(ParseArgs($ttext, $PageListArgPattern), $opt);
array_splice($tparts, $i, 4);
}
SDVA($opt, array('class' => 'fpltemplate', 'wrap' => 'div'));
}
## get the list of pages
function FPLTemplatePageList($pagename, &$matches, &$opt){
$matches = array_unique(array_merge((array)$matches, MakePageList($pagename, $opt, 0)));
## count matches before any slicing and save value as template var {$$PageListCount}
$opt['PageListCount'] = count($matches);
}
## extract page subset according to 'count=' parameter
function FPLTemplateSliceList($pagename, &$matches, $opt){
if (@$opt['count']) {
list($r0, $r1) = CalcRange($opt['count'], count($matches));
if ($r1 < $r0)
$matches = array_reverse(array_slice($matches, $r1-1, $r0-$r1+1));
else
$matches = array_slice($matches, $r0-1, $r1-$r0+1);
}
}
function FPLTemplateFormat($pagename, $matches, $opt, $tparts, &$output){
global $Cursor, $FPLTemplateMarkupFunction, $PCache;
SDV($FPLTemplateMarkupFunction, 'MarkupToHTML');
$savecursor = $Cursor;
$pagecount = $groupcount = $grouppagecount = $traildepth = $eachcount = 0;
$pseudovars = array('{$$PageCount}' => &$pagecount,
'{$$EachCount}' => &$eachcount,
'{$$GroupCount}' => &$groupcount,
'{$$GroupPageCount}' => &$grouppagecount,
'{$$PageTrailDepth}' => &$traildepth);
foreach(preg_grep('/^[\\w$]/', array_keys($opt)) as $k)
if (!is_array($opt[$k]))
$pseudovars["{\$\$$k}"] = PHSC($opt[$k], ENT_NOQUOTES);
$vk = array_keys($pseudovars);
$vv = array_values($pseudovars);
$lgroup = $lcontrol = ''; $out = '';
if (count($matches)==0) {
$t = 0;
while($t < count($tparts)) {
if ($tparts[$t]=='template' && $tparts[$t+2]=='none') {
$out .= MarkupRestore(FPLExpandItemVars($tparts[$t+4], $matches, 0, $pseudovars));
$t+=4;
}
$t++;
}
} # else:
foreach($matches as $i => $pn) {
$traildepth = intval(@$PCache[$pn]['depth']);
$group = PageVar($pn, '$Group');
if ($group != $lgroup) { $groupcount++; $grouppagecount = 0; $lgroup = $group; }
$grouppagecount++; $pagecount++; $eachcount++;
$t = 0;
while ($t < count($tparts)) {
if ($tparts[$t] != 'template') { $item = $tparts[$t]; $t++; }
else {
list($neg, $when, $control, $item) = array_slice($tparts, $t+1, 4); $t+=5;
if ($when=='none') continue;
if (!$control) {
if ($when == 'first' && ($neg xor ($i != 0))) continue;
if ($when == 'last' && ($neg xor ($i != count($matches) - 1))) continue;
} else {
$currcontrol = FPLExpandItemVars($control, $matches, $i, $pseudovars);
if($currcontrol != $lcontrol) { $eachcount=1; $lcontrol = $currcontrol; }
if ($when == 'first' || !isset($last[$t])) {
$curr = FPLExpandItemVars($control, $matches, $i, $pseudovars);
if ($when == 'first' && ($neg xor (($i != 0) && ($last[$t] == $curr))))
{ $last[$t] = $curr; continue; }
$last[$t] = $curr;
}
if ($when == 'last') {
$next = FPLExpandItemVars($control, $matches, $i+1, $pseudovars);
if ($neg xor ($next == $last[$t] && $i != count($matches) - 1)) continue;
$last[$t] = $next;
}
}
}
$item = FPLExpandItemVars($item, $matches, $i, $pseudovars);
$out .= MarkupRestore($item);
}
}
$class = preg_replace('/[^-a-zA-Z0-9\\x80-\\xff]/', ' ', @$opt['class']);
if ($class) $class = " class='$class'";
$wrap = @$opt['wrap'];
if ($wrap != 'inline') {
$out = $FPLTemplateMarkupFunction($pagename, $out, array('escape' => 0, 'redirect'=>1));
if ($wrap != 'none') $out = "
$out
";
}
$Cursor = $savecursor;
$output .= $out;
}
## This function moves repeated code blocks out of FPLTemplateFormat()
function FPLExpandItemVars($item, $matches, $idx, $psvars) {
global $Cursor, $EnableUndefinedTemplateVars;
$Cursor['<'] = $Cursor['<'] = (string)@$matches[$idx-1];
$Cursor['='] = $pn = (string)@$matches[$idx];
$Cursor['>'] = $Cursor['>'] = (string)@$matches[$idx+1];
$item = str_replace(array_keys($psvars), array_values($psvars), $item);
$cb = new cb_pl_expandvars($pn);
$item = preg_replace_callback('/\\{(=|&[lg]t;)(\\$:?\\w[-\\w]*)\\}/',
array($cb, 'pl_expandvars'), $item);
if (! IsEnabled($EnableUndefinedTemplateVars, 0))
$item = preg_replace("/\\{\\$\\$\\w+\\}/", '', $item);
return $item;
}
########################################################################
## The functions below optimize searches by maintaining a file of
## words and link cross references (the "page index").
########################################################################
## PageIndexTerms($terms) takes an array of strings and returns a
## normalized list of associated search terms. This reduces the
## size of the index and speeds up searches.
function PageIndexTerms($terms) {
global $PageIndexFoldFunction;
$w = array();
foreach((array)$terms as $t) {
$w = array_merge($w, preg_split('/[^\\w\\x80-\\xff]+/',
$PageIndexFoldFunction($t), -1, PREG_SPLIT_NO_EMPTY));
}
return $w;
}
## The PageIndexUpdate($pagelist) function updates the page index
## file with terms and target links for the pages in $pagelist.
## The optional $dir parameter allows this function to be called
## via register_shutdown_function (which sometimes changes directories
## on us).
function PageIndexUpdate($pagelist = NULL, $dir = '') {
global $EnableReadOnly, $PageIndexUpdateList, $PageIndexFile,
$PageIndexTermsFunction, $PageIndexTime, $Now;
if (IsEnabled($EnableReadOnly, 0)) return;
$abort = ignore_user_abort(true);
if ($dir) { flush(); chdir($dir); }
if (is_null($pagelist))
{ $pagelist = (array)$PageIndexUpdateList; $PageIndexUpdateList = array(); }
if (!$pagelist || !$PageIndexFile) return;
SDV($PageIndexTime, 10);
$c = count($pagelist); $updatecount = 0;
StopWatch("PageIndexUpdate begin ($c pages to update)");
$pagelist = (array)$pagelist;
$timeout = time() + $PageIndexTime;
$cmpfn = 'PageIndexUpdateSort';
Lock(2);
$ofp = fopen("$PageIndexFile,new", 'w');
foreach($pagelist as $pn) {
if (@$updated[$pn]) continue;
@$updated[$pn]++;
if (time() > $timeout) continue;
$page = ReadPage($pn, READPAGE_CURRENT);
if ($page) {
$targets = str_replace(',', ' ', strval(@$page['targets']));
$terms = $PageIndexTermsFunction(array(@$page['text'], $targets, $pn));
usort($terms, $cmpfn);
$x = '';
foreach($terms as $t) { if (strpos($x, $t) === false) $x .= " $t"; }
fputs($ofp, "$pn:$Now: $targets :$x\n");
}
$updatecount++;
}
$ifp = @fopen($PageIndexFile, 'r');
if ($ifp) {
while (!feof($ifp)) {
$line = fgets($ifp, 4096);
while (substr($line, -1, 1) != "\n" && !feof($ifp))
$line .= fgets($ifp, 4096);
$i = strpos($line, ':');
if ($i === false) continue;
$n = substr($line, 0, $i);
if (@$updated[$n]) continue;
fputs($ofp, $line);
}
fclose($ifp);
}
fclose($ofp);
if (file_exists($PageIndexFile)) unlink($PageIndexFile);
rename("$PageIndexFile,new", $PageIndexFile);
fixperms($PageIndexFile);
StopWatch("PageIndexUpdate end ($updatecount updated)");
ignore_user_abort($abort);
}
function PageIndexUpdateSort($a,$b) {return strlen($b)-strlen($a);}
## PageIndexQueueUpdate specifies pages to be updated in
## the index upon shutdown (via register_shutdown function).
function PageIndexQueueUpdate($pagelist) {
global $PageIndexUpdateList;
if (!@$PageIndexUpdateList)
register_shutdown_function('PageIndexUpdate', NULL, getcwd());
$PageIndexUpdateList = array_merge((array)@$PageIndexUpdateList,
(array)$pagelist);
$c1 = @count((array)$pagelist); $c2 = count($PageIndexUpdateList);
StopWatch("PageIndexQueueUpdate: queued $c1 pages ($c2 total)");
}
## PageIndexGrep returns a list of pages that match the strings
## provided. Note that some search terms may need to be normalized
## in order to get the desired results (see PageIndexTerms above).
## Also note that this just works for the index; if the index is
## incomplete, then so are the results returned by this list.
## (MakePageList above already knows how to deal with this.)
function PageIndexGrep($terms, $invert = false, $links = false) {
global $PageIndexFile;
if (!$PageIndexFile) return array();
StopWatch('PageIndexGrep begin');
$pagelist = array();
$fp = @fopen($PageIndexFile, 'r');
if ($fp) {
$terms = (array)$terms;
while (!feof($fp)) {
$line = fgets($fp, 4096);
while (substr($line, -1, 1) != "\n" && !feof($fp))
$line .= fgets($fp, 4096);
$i = strpos($line, ':');
if (!$i) continue;
$add = true;
foreach($terms as $t)
if (strpos($line, $t) === false) { $add = false; break; }
if ($add && $links) {
$parts = explode(':', $line);
if (! PageListMatchTargets($parts[2], $links)) $add = false;
}
if ($add xor $invert) $pagelist[] = substr($line, 0, $i);
}
fclose($fp);
}
StopWatch('PageIndexGrep end');
return $pagelist;
}
## PostPageIndex is inserted into $EditFunctions to update
## the linkindex whenever a page is saved.
function PostPageIndex($pagename, &$page, &$new) {
global $IsPagePosted;
if ($IsPagePosted) PageIndexQueueUpdate($pagename);
}