Simple Groupware PHP Cross Reference Groupware Applications

Source: /src/lib/pmwiki/scripts/pagelist.php - 829 lines - 31461 bytes - Summary - Text - Print

   1  <?php if (!defined('PmWiki')) exit();
   2  /*  Copyright 2004-2009 Patrick R. Michaud (pmichaud@pobox.com)
   3      This file is part of PmWiki; you can redistribute it and/or modify
   4      it under the terms of the GNU General Public License as published
   5      by the Free Software Foundation; either version 2 of the License, or
   6      (at your option) any later version.  See pmwiki.php for full details.
   7  
   8      This script implements (:pagelist:) and friends -- it's one
   9      of the nastiest scripts you'll ever encounter.  Part of the reason
  10      for this is that page listings are so powerful and flexible, so
  11      that adds complexity.  They're also expensive, so we have to
  12      optimize them wherever we can.
  13  
  14      The core function is FmtPageList(), which will generate a 
  15      listing according to a wide variety of options.  FmtPageList takes 
  16      care of initial option processing, and then calls a "FPL"
  17      (format page list) function to obtain the formatted output.
  18      The FPL function is chosen by the 'fmt=' option to (:pagelist:).
  19  
  20      Each FPL function calls MakePageList() to obtain the list
  21      of pages, formats the list somehow, and returns the results
  22      to FmtPageList.  FmtPageList then returns the output to
  23      the caller, and calls Keep() (preserves HTML) or PRR() (re-evaluate
  24      as markup) as appropriate for the output being returned.
  25  */
  26  
  27  ## $PageIndexFile is the index file for term searches and link= option
  28  if (IsEnabled($EnablePageIndex, 1)) {
  29    SDV($PageIndexFile, "$WorkDir/.pageindex");
  30    $EditFunctions[] = 'PostPageIndex';
  31  }
  32  
  33  SDV($StrFoldFunction, 'strtolower');
  34  
  35  ## $SearchPatterns holds patterns for list= option
  36  SDV($SearchPatterns['all'], array());
  37  SDVA($SearchPatterns['normal'], array(
  38    'recent' => '!\.(All)?Recent(Changes|Uploads)$!',
  39    'group' => '!\.Group(Print)?(Header|Footer|Attributes)$!',
  40    'self' => str_replace('.', '\\.', "!^$pagename$!")));
  41  
  42  ## $FPLFormatOpt is a list of options associated with fmt=
  43  ## values.  'default' is used for any undefined values of fmt=.
  44  SDVA($FPLFormatOpt, array(
  45    'default' => array('fn' => 'FPLTemplate', 'fmt' => '#default'),
  46    'bygroup' => array('fn' => 'FPLTemplate', 'template' => '#bygroup',
  47                       'class' => 'fplbygroup'),
  48    'simple'  => array('fn' => 'FPLTemplate', 'template' => '#simple',
  49                       'class' => 'fplsimple'),
  50    'group'   => array('fn' => 'FPLTemplate', 'template' => '#group',
  51                       'class' => 'fplgroup'),
  52    'title'   => array('fn' => 'FPLTemplate', 'template' => '#title',
  53                       'class' => 'fpltitle', 'order' => 'title'),
  54    'count'   => array('fn' => 'FPLCountA'),
  55    ));
  56  
  57  SDV($SearchResultsFmt, "<div class='wikisearch'>\$[SearchFor]
  58    <div class='vspace'></div>\$MatchList
  59    <div class='vspace'></div>\$[SearchFound]</div>");
  60  SDV($SearchQuery, str_replace('$', '&#036;', 
  61    htmlspecialchars(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES)));
  62  XLSDV('en', array(
  63    'SearchFor' => 'Results of search for <em>$Needle</em>:',
  64    'SearchFound' => 
  65      '$MatchCount pages found out of $MatchSearched pages searched.'));
  66  
  67  SDV($PageListArgPattern, '((?:\\$:?)?\\w+)[:=]');
  68  
  69  Markup('pagelist', 'directives',
  70    '/\\(:pagelist(\\s+.*?)?:\\)/ei',
  71    "FmtPageList('\$MatchList', \$pagename, array('o' => PSS('$1 ')))");
  72  Markup('searchbox', 'directives',
  73    '/\\(:searchbox(\\s.*?)?:\\)/e',
  74    "SearchBox(\$pagename, ParseArgs(PSS('$1'), '$PageListArgPattern'))");
  75  Markup('searchresults', 'directives',
  76    '/\\(:searchresults(\\s+.*?)?:\\)/ei',
  77    "FmtPageList(\$GLOBALS['SearchResultsFmt'], \$pagename, 
  78         array('req' => 1, 'request'=>1, 'o' => PSS('$1')))");
  79  
  80  SDV($SaveAttrPatterns['/\\(:(searchresults|pagelist)(\\s+.*?)?:\\)/i'], ' ');
  81  
  82  SDV($HandleActions['search'], 'HandleSearchA');
  83  SDV($HandleAuth['search'], 'read');
  84  SDV($ActionTitleFmt['search'], '| $[Search Results]');
  85  
  86  SDVA($PageListFilters, array(
  87    'PageListCache' => 80,
  88    'PageListProtect' => 90,
  89    'PageListSources' => 100,
  90    'PageListPasswords' => 120,
  91    'PageListIf' => 140,
  92    'PageListTermsTargets' => 160,
  93    'PageListVariables' => 180,
  94    'PageListSort' => 900,
  95  ));
  96  
  97  foreach(array('random', 'size', 'time', 'ctime') as $o) 
  98    SDV($PageListSortCmp[$o], "@(\$PCache[\$x]['$o']-\$PCache[\$y]['$o'])");
  99  SDV($PageListSortCmp['title'], 
 100    '@strcasecmp($PCache[$x][\'=title\'], $PCache[$y][\'=title\'])');
 101  
 102  define('PAGELIST_PRE' , 1);
 103  define('PAGELIST_ITEM', 2);
 104  define('PAGELIST_POST', 4);
 105  
 106  ## SearchBox generates the output of the (:searchbox:) markup.
 107  ## If $SearchBoxFmt is defined, that is used, otherwise a searchbox
 108  ## is generated.  Options include group=, size=, label=.
 109  function SearchBox($pagename, $opt) {
 110    global $SearchBoxFmt, $SearchBoxOpt, $SearchQuery, $EnablePathInfo;
 111    if (isset($SearchBoxFmt)) return Keep(FmtPageName($SearchBoxFmt, $pagename));
 112    SDVA($SearchBoxOpt, array('size' => '40', 
 113      'label' => FmtPageName('$[Search]', $pagename),
 114      'value' => str_replace("'", "&#039;", $SearchQuery)));
 115    $opt = array_merge((array)$SearchBoxOpt, @$_GET, (array)$opt);
 116    $opt['action'] = 'search';
 117    $target = (@$opt['target']) 
 118              ? MakePageName($pagename, $opt['target']) : $pagename;
 119    $opt['n'] = IsEnabled($EnablePathInfo, 0) ? '' : $target;
 120    $out = FmtPageName(" class='wikisearch' action='\$PageUrl' method='get'>",
 121                       $target);
 122    foreach($opt as $k => $v) {
 123      if ($v == '' || is_array($v)) continue;
 124      $v = str_replace("'", "&#039;", $v);
 125      $opt[$k] = $v;
 126  // tb
 127      if (in_array($k,array('q','label','value','size','action','n'))) continue;
 128      $k = str_replace("'", "&#039;", $k);
 129      $out .= "<input type='hidden' name='$k' value='$v' />";
 130    }
 131    $out .= "<input type='text' name='q' value='{$opt['value']}' 
 132      class='inputbox searchbox' size='{$opt['size']}' /><input type='submit' 
 133      class='inputbutton searchbutton' value='{$opt['label']}' />";
 134    return '<form '.Keep($out).'</form>';
 135  }
 136  
 137  ## FmtPageList combines options from markup, request form, and url,
 138  ## calls the appropriate formatting function, and returns the string.
 139  function FmtPageList($outfmt, $pagename, $opt) {
 140    global $GroupPattern, $FmtV, $PageListArgPattern, 
 141      $FPLFormatOpt, $FPLFunctions;
 142    # get any form or url-submitted request
 143    $rq = htmlspecialchars(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES);
 144    # build the search string
 145    $FmtV['$Needle'] = $opt['o'] . ' ' . $rq;
 146    # Handle "group/" at the beginning of the form-submitted request
 147    if (preg_match("!^($GroupPattern(\\|$GroupPattern)*)?/!i", $rq, $match)) {
 148      $opt['group'] = @$match[1];
 149      $rq = substr($rq, strlen(@$match[1])+1);
 150    }
 151    $opt = array_merge($opt, ParseArgs($opt['o'], $PageListArgPattern));
 152    # merge markup options with form and url
 153    if (@$opt['request']) 
 154      $opt = array_merge($opt, ParseArgs($rq, $PageListArgPattern), @$_REQUEST);
 155    # non-posted blank search requests return nothing
 156    if (@($opt['req'] && !$opt['-'] && !$opt[''] && !$opt['+'] && !$opt['q']))
 157      return '';
 158    # terms and group to be included and excluded
 159    $GLOBALS['SearchIncl'] = array_merge((array)@$opt[''], (array)@$opt['+']);
 160    $GLOBALS['SearchExcl'] = (array)@$opt['-'];
 161    $GLOBALS['SearchGroup'] = @$opt['group'];
 162    $fmt = @$opt['fmt']; if (!$fmt) $fmt = 'default';
 163    $fmtopt = @$FPLFormatOpt[$fmt];
 164    if (!is_array($fmtopt)) {
 165      if ($fmtopt) $fmtopt = array('fn' => $fmtopt);
 166      elseif (@$FPLFunctions[$fmt]) 
 167        $fmtopt = array('fn' => $FPLFunctions[$fmt]);
 168      else $fmtopt = $FPLFormatOpt['default'];
 169    }
 170    $fmtfn = @$fmtopt['fn'];
 171    if (!is_callable($fmtfn)) $fmtfn = $FPLFormatOpt['default']['fn'];
 172    $matches = array();
 173    $opt = array_merge($fmtopt, $opt);
 174    $out = $fmtfn($pagename, $matches, $opt);
 175    $FmtV['$MatchCount'] = count($matches);
 176    if ($outfmt != '$MatchList') 
 177      { $FmtV['$MatchList'] = $out; $out = FmtPageName($outfmt, $pagename); }
 178    if ($out[0] == '<') $out = Keep($out);
 179    return PRR($out);
 180  }
 181  
 182  ## MakePageList generates a list of pages using the specifications given
 183  ## by $opt.
 184  function MakePageList($pagename, $opt, $retpages = 1) {
 185    global $MakePageListOpt, $PageListFilters, $PCache;
 186  
 187    StopWatch('MakePageList pre');
 188    SDVA($MakePageListOpt, array('list' => 'default'));
 189    $opt = array_merge((array)$MakePageListOpt, (array)$opt);
 190    if (!@$opt['order'] && !@$opt['trail']) $opt['order'] = 'name';
 191    $opt['order'] = preg_replace('/[^-\\w:$]+/', ',', $opt['order']);
 192  
 193    ksort($opt); $opt['=key'] = md5(serialize($opt));
 194  
 195    $itemfilters = array(); $postfilters = array();
 196    asort($PageListFilters);
 197    $opt['=phase'] = PAGELIST_PRE; $list=array(); $pn=NULL; $page=NULL;
 198    foreach($PageListFilters as $fn => $v) {
 199      if ($v<0) continue;
 200      $ret = $fn($list, $opt, $pagename, $page);
 201      if ($ret & PAGELIST_ITEM) $itemfilters[] = $fn;
 202      if ($ret & PAGELIST_POST) $postfilters[] = $fn;
 203    }
 204  
 205    StopWatch("MakePageList items count=".count($list).", filters=".implode(',',$itemfilters));
 206    $opt['=phase'] = PAGELIST_ITEM;
 207    $matches = array(); $opt['=readc'] = 0;
 208    foreach((array)$list as $pn) {
 209      $page = array();
 210      foreach((array)$itemfilters as $fn) 
 211        if (!$fn($list, $opt, $pn, $page)) continue 2;
 212      $page['pagename'] = $page['name'] = $pn;
 213      PCache($pn, $page);
 214      $matches[] = $pn;
 215    }
 216    $list = $matches;
 217    StopWatch("MakePageList post count=".count($list).", readc={$opt['=readc']}");
 218  
 219    $opt['=phase'] = PAGELIST_POST; $pn=NULL; $page=NULL;
 220    foreach((array)$postfilters as $fn) 
 221      $fn($list, $opt, $pagename, $page);
 222    
 223    if ($retpages) 
 224      for($i=0; $i<count($list); $i++)
 225        $list[$i] = &$PCache[$list[$i]];
 226    StopWatch('MakePageList end');
 227    return $list;
 228  }
 229  
 230  function PageListProtect(&$list, &$opt, $pn, &$page) {
 231    global $EnablePageListProtect;
 232  
 233    switch ($opt['=phase']) {
 234      case PAGELIST_PRE:
 235        if (!IsEnabled($EnablePageListProtect, 1) && @$opt['readf'] < 1000)
 236          return 0;
 237        StopWatch("PageListProtect enabled");
 238        $opt['=protectexclude'] = array();
 239        $opt['=protectsafe'] = (array)@$opt['=protectsafe'];
 240        return PAGELIST_ITEM|PAGELIST_POST;
 241  
 242      case PAGELIST_ITEM:
 243        if (@$opt['=protectsafe'][$pn]) return 1;
 244        $page = RetrieveAuthPage($pn, 'ALWAYS', false, READPAGE_CURRENT);
 245        $opt['=readc']++;
 246        if (!$page['=auth']['read']) $opt['=protectexclude'][$pn] = 1;
 247        if (!$page['=passwd']['read']) $opt['=protectsafe'][$pn] = 1; 
 248        else NoCache();
 249        return 1;
 250  
 251      case PAGELIST_POST:
 252        $excl = array_keys($opt['=protectexclude']);
 253        $safe = array_keys($opt['=protectsafe']);
 254        StopWatch("PageListProtect excluded=" .count($excl)
 255                  . ", safe=" . count($safe));
 256        $list = array_diff($list, $excl);
 257        return 1;
 258    }
 259  }
 260  
 261  function PageListSources(&$list, &$opt, $pn, &$page) {
 262    global $SearchPatterns;
 263  
 264    StopWatch('PageListSources begin');
 265    ## add the list= option to our list of pagename filter patterns
 266    $opt['=pnfilter'] = array_merge((array)@$opt['=pnfilter'], 
 267                                    (array)@$SearchPatterns[$opt['list']]);
 268  
 269    if (@$opt['group']) $opt['=pnfilter'][] = FixGlob($opt['group'], '$1$2.*');
 270    if (@$opt['name']) $opt['=pnfilter'][] = FixGlob($opt['name'], '$1*.$2');
 271  
 272    if (@$opt['trail']) {
 273      $trail = ReadTrail($pn, $opt['trail']);
 274      $tlist = array();
 275      foreach($trail as $tstop) {
 276        $n = $tstop['pagename'];
 277        $tlist[] = $n;
 278        $tstop['parentnames'] = array();
 279        PCache($n, $tstop);
 280      }
 281      foreach($trail as $tstop) 
 282        $PCache[$tstop['pagename']]['parentnames'][] = 
 283          @$trail[$tstop['parent']]['pagename'];
 284      if (!@$opt['=cached']) $list = MatchPageNames($tlist, $opt['=pnfilter']);
 285    } else if (!@$opt['=cached']) $list = ListPages($opt['=pnfilter']);
 286    StopWatch("PageListSources end count=".count($list));
 287    return 0;
 288  }
 289  
 290  function PageListPasswords(&$list, &$opt, $pn, &$page) {
 291    if ($opt['=phase'] == PAGELIST_PRE)
 292      return (@$opt['passwd'] > '' && !@$opt['=cached']) ? PAGELIST_ITEM : 0;
 293  
 294    if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
 295    if (!$page) return 0;
 296    return (boolean)preg_grep('/^passwd/', array_keys($page));
 297  }
 298  
 299  function PageListIf(&$list, &$opt, $pn, &$page) {
 300    global $Conditions, $Cursor;
 301  
 302    ##  See if we have any "if" processing to perform
 303    if ($opt['=phase'] == PAGELIST_PRE) 
 304      return (@$opt['if'] > '') ? PAGELIST_ITEM : 0;
 305  
 306    $condspec = $opt['if'];
 307    $Cursor['='] = $pn;
 308    $varpat = '\\{([=*]|!?[-\\w.\\/\\x80-\\xff]*)(\\$:?\\w+)\\}';
 309    while (preg_match("/$varpat/", $condspec, $match)) {
 310      $condspec = preg_replace("/$varpat/e", 
 311                      "PVSE(PageVar(\$pn, '$2', '$1'))", $condspec);
 312    }
 313    if (!preg_match("/^\\s*(!?)\\s*(\\S*)\\s*(.*?)\\s*$/", $condspec, $match)) 
 314      return 0;
 315    list($x, $not, $condname, $condparm) = $match;
 316    if (!isset($Conditions[$condname])) return 1;
 317    $tf = (int)@eval("return ({$Conditions[$condname]});");
 318    return (boolean)($tf xor $not);
 319  }
 320  
 321  function PageListTermsTargets(&$list, &$opt, $pn, &$page) {
 322    global $FmtV;
 323    static $reindex = array();
 324    $fold = $GLOBALS['StrFoldFunction'];
 325  
 326    switch ($opt['=phase']) {
 327      case PAGELIST_PRE:
 328        $FmtV['$MatchSearched'] = count($list);
 329        $incl = array(); $excl = array();
 330        foreach((array)@$opt[''] as $i) { $incl[] = $fold($i); }
 331        foreach((array)@$opt['+'] as $i) { $incl[] = $fold($i); }
 332        foreach((array)@$opt['-'] as $i) { $excl[] = $fold($i); }
 333  
 334        $indexterms = PageIndexTerms($incl);
 335        foreach($incl as $i) {
 336          $delim = (!preg_match('/[^\\w\\x80-\\xff]/', $i)) ? '$' : '/';
 337          $opt['=inclp'][] = $delim . preg_quote($i,$delim) . $delim . 'i';
 338        }
 339        if ($excl) 
 340          $opt['=exclp'][] = '$'.implode('|', array_map('preg_quote',$excl)).'$i';
 341  
 342        if (@$opt['link']) {
 343          $link = MakePageName($pn, $opt['link']);
 344          $opt['=linkp'] = "/(^|,)$link(,|$)/i";
 345          $indexterms[] = " $link ";
 346        }
 347  
 348        if (@$opt['=cached']) return 0;
 349        if ($indexterms) {
 350          StopWatch("PageListTermsTargets begin count=".count($list));
 351          $xlist = PageIndexGrep($indexterms, true);
 352          $list = array_diff($list, $xlist);
 353          StopWatch("PageListTermsTargets end count=".count($list));
 354        }
 355  
 356        if (@$opt['=inclp'] || @$opt['=exclp'] || @$opt['=linkp']) 
 357          return PAGELIST_ITEM|PAGELIST_POST; 
 358        return 0;
 359  
 360      case PAGELIST_ITEM:
 361        if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
 362        if (!$page) return 0;
 363        if (@$opt['=linkp'] && !preg_match($opt['=linkp'], @$page['targets'])) 
 364          { $reindex[] = $pn; return 0; }
 365        if (@$opt['=inclp'] || @$opt['=exclp']) {
 366          $text = $fold($pn."\n".@$page['targets']."\n".@$page['text']);
 367          foreach((array)@$opt['=exclp'] as $i) 
 368            if (preg_match($i, $text)) return 0;
 369          foreach((array)@$opt['=inclp'] as $i) 
 370            if (!preg_match($i, $text)) { 
 371              if ($i{0} == '$') $reindex[] = $pn;
 372              return 0; 
 373            }
 374        }
 375        return 1;
 376  
 377      case PAGELIST_POST:
 378        if ($reindex) PageIndexQueueUpdate($reindex);
 379        $reindex = array();
 380        return 0;
 381    }
 382  }
 383  
 384  function PageListVariables(&$list, &$opt, $pn, &$page) {
 385    switch ($opt['=phase']) {
 386      case PAGELIST_PRE:
 387        $varlist = preg_grep('/^\\$/', array_keys($opt));
 388        if (!$varlist) return 0;
 389        foreach($varlist as $v) {
 390          list($inclp, $exclp) = GlobToPCRE($opt[$v]);
 391          if ($inclp) $opt['=varinclp'][$v] = "/$inclp/i";
 392          if ($exclp) $opt['=varexclp'][$v] = "/$exclp/i";
 393        }
 394        return PAGELIST_ITEM;
 395  
 396      case PAGELIST_ITEM:
 397        if (@$opt['=varinclp'])
 398          foreach($opt['=varinclp'] as $v => $pat) 
 399            if (!preg_match($pat, PageVar($pn, $v))) return 0;
 400        if (@$opt['=varexclp'])
 401          foreach($opt['=varexclp'] as $v => $pat) 
 402             if (preg_match($pat, PageVar($pn, $v))) return 0;
 403        return 1;
 404    }
 405  }        
 406  
 407  function PageListSort(&$list, &$opt, $pn, &$page) {
 408    global $PageListSortCmp, $PCache, $PageListSortRead;
 409    SDVA($PageListSortRead, array('name' => 0, 'group' => 0, 'random' => 0,
 410      'title' => 0));
 411  
 412    switch ($opt['=phase']) {
 413      case PAGELIST_PRE:
 414        $ret = 0;
 415        foreach(preg_split('/[^-\\w:$]+/', @$opt['order'], -1, PREG_SPLIT_NO_EMPTY) 
 416                as $o) {
 417          $ret |= PAGELIST_POST;
 418          $r = '+';
 419          if ($o{0} == '-') { $r = '-'; $o = substr($o, 1); }
 420          $opt['=order'][$o] = $r;
 421          if ($o{0} != '$' && 
 422              (!isset($PageListSortRead[$o]) || $PageListSortRead[$o]))
 423            $ret |= PAGELIST_ITEM;
 424        }
 425        StopWatch(@"PageListSort pre ret=$ret order={$opt['order']}");
 426        return $ret;
 427  
 428      case PAGELIST_ITEM:
 429        if (!$page) { $page = ReadPage($pn, READPAGE_CURRENT); $opt['=readc']++; }
 430        return 1;
 431    }
 432  
 433    ## case PAGELIST_POST
 434    StopWatch('PageListSort begin');
 435    $order = $opt['=order'];
 436    if (@$order['title'])
 437      foreach($list as $pn) $PCache[$pn]['=title'] = PageVar($pn, '$Title');
 438    if (@$order['group'])
 439      foreach($list as $pn) $PCache[$pn]['group'] = PageVar($pn, '$Group');
 440    if (@$order['random']) 
 441      { NoCache(); foreach($list as $pn) $PCache[$pn]['random'] = rand(); }
 442    foreach(preg_grep('/^\\$/', array_keys($order)) as $o) 
 443      foreach($list as $pn) 
 444        $PCache[$pn][$o] = PageVar($pn, $o);
 445    $code = '';
 446    foreach($opt['=order'] as $o => $r) {
 447      if (@$PageListSortCmp[$o]) 
 448        $code .= "\$c = {$PageListSortCmp[$o]}; "; 
 449      else 
 450        $code .= "\$c = @strcasecmp(\$PCache[\$x]['$o'],\$PCache[\$y]['$o']); ";
 451      $code .= "if (\$c) return $r\$c;\n";
 452    }
 453    StopWatch('PageListSort sort');
 454    if ($code) 
 455      uasort($list,
 456             create_function('$x,$y', "global \$PCache; $code return 0;"));
 457    StopWatch('PageListSort end');
 458  }
 459  
 460  function PageListCache(&$list, &$opt, $pn, &$page) {
 461    global $PageListCacheDir, $LastModTime, $PageIndexFile;
 462  
 463    if (@!$PageListCacheDir) return 0;
 464    if (isset($opt['cache']) && !$opt['cache']) return 0;
 465   
 466    $key = $opt['=key'];
 467    $cache = "$PageListCacheDir/$key,cache"; 
 468    switch ($opt['=phase']) {
 469      case PAGELIST_PRE:
 470        if (!file_exists($cache) || filemtime($cache) <= $LastModTime)
 471          return PAGELIST_POST;
 472        StopWatch("PageListCache begin load key=$key");
 473        list($list, $opt['=protectsafe']) = 
 474          unserialize(file_get_contents($cache));
 475        $opt['=cached'] = 1;
 476        StopWatch("PageListCache end load");
 477        return 0;
 478  
 479      case PAGELIST_POST:
 480        StopWatch("PageListCache begin save key=$key");
 481        $fp = @fopen($cache, "w");
 482        if ($fp) {
 483          fputs($fp, serialize(array($list, $opt['=protectsafe'])));
 484          fclose($fp);
 485        }
 486        StopWatch("PageListCache end save");
 487        return 0;
 488    }
 489    return 0;
 490  }
 491  
 492  ## HandleSearchA performs ?action=search.  It's basically the same
 493  ## as ?action=browse, except it takes its contents from Site.Search.
 494  function HandleSearchA($pagename, $level = 'read') {
 495    global $PageSearchForm, $FmtV, $HandleSearchFmt, 
 496      $PageStartFmt, $PageEndFmt;
 497    SDV($HandleSearchFmt,array(&$PageStartFmt, '$PageText', &$PageEndFmt));
 498    SDV($PageSearchForm, '$[{$SiteGroup}/Search]');
 499    $form = RetrieveAuthPage($pagename, $level, true, READPAGE_CURRENT);
 500    if (!$form) Abort("?unable to read $pagename");
 501    PCache($pagename, $form);
 502    $text = preg_replace('/\\[([=@])(.*?)\\1\\]/s', ' ', @$form['text']);
 503    if (!preg_match('/\\(:searchresults(\\s.*?)?:\\)/', $text))
 504      foreach((array)$PageSearchForm as $formfmt) {
 505        $form = ReadPage(FmtPageName($formfmt, $pagename), READPAGE_CURRENT);
 506        if ($form['text']) break;
 507      }
 508    $text = @$form['text'];
 509    if (!$text) $text = '(:searchresults:)';
 510    $FmtV['$PageText'] = MarkupToHTML($pagename,$text);
 511    PrintFmt($pagename, $HandleSearchFmt);
 512  }
 513  
 514  ########################################################################
 515  ## The functions below provide different formatting options for
 516  ## the output list, controlled by the fmt= parameter and the
 517  ## $FPLFormatOpt hash.
 518  ########################################################################
 519  
 520  ## This helper function handles the count= parameter for extracting
 521  ## a range of pagelist in the list.
 522  function CalcRange($range, $n) {
 523    if ($n < 1) return array(0, 0);
 524    if (strpos($range, '..') === false) {
 525      if ($range > 0) return array(1, min($range, $n));
 526      if ($range < 0) return array(max($n + $range + 1, 1), $n);
 527      return array(1, $n);
 528    }
 529    list($r0, $r1) = explode('..', $range);
 530    if ($r0 < 0) $r0 += $n + 1;
 531    if ($r1 < 0) $r1 += $n + 1;
 532    else if ($r1 == 0) $r1 = $n;
 533    if ($r0 < 1 && $r1 < 1) return array($n+1, $n+1);
 534    return array(max($r0, 1), max($r1, 1));
 535  }
 536  
 537  ##  FPLCountA handles fmt=count
 538  function FPLCountA($pagename, &$matches, $opt) {
 539    $matches = array_values(MakePageList($pagename, $opt, 0));
 540    return count($matches);
 541  }
 542  
 543  SDVA($FPLTemplateFunctions, array(
 544    'FPLTemplateLoad' => 100,
 545    'FPLTemplateDefaults' => 200,
 546    'FPLTemplatePageList' => 300,
 547    'FPLTemplateSliceList' => 400,
 548    'FPLTemplateFormat' => 500
 549    ));
 550  
 551  function FPLTemplate($pagename, &$matches, $opt) {
 552    global $FPLTemplateFunctions;
 553    StopWatch("FPLTemplate: Chain begin");
 554    asort($FPLTemplateFunctions, SORT_NUMERIC);
 555    $fnlist = $FPLTemplateFunctions;
 556    $output = '';
 557    foreach($FPLTemplateFunctions as $fn=>$i) {
 558      if ($i<0) continue;
 559      StopWatch("FPLTemplate: $fn");
 560      $fn($pagename, $matches, $opt, $tparts, $output);
 561    }
 562    StopWatch("FPLTemplate: Chain end");
 563    return $output;
 564  }
 565  
 566  ## Loads a template section
 567  function FPLTemplateLoad($pagename, $matches, $opt, &$tparts){
 568    global $Cursor, $FPLTemplatePageFmt, $RASPageName, $PageListArgPattern;
 569    SDV($FPLTemplatePageFmt, array('{$FullName}',
 570      '{$SiteGroup}.LocalTemplates', '{$SiteGroup}.PageListTemplates'));
 571  
 572    $template = @$opt['template'];
 573    if (!$template) $template = @$opt['fmt'];
 574    $ttext = RetrieveAuthSection($pagename, $template, $FPLTemplatePageFmt);
 575    $ttext = PVSE(Qualify($RASPageName, $ttext));
 576  
 577    ##  save any escapes
 578    $ttext = MarkupEscape($ttext);
 579    ##  remove any anchor markups to avoid duplications
 580    $ttext = preg_replace('/\\[\\[#[A-Za-z][-.:\\w]*\\]\\]/', '', $ttext);
 581    
 582    ##  extract portions of template
 583    $tparts = preg_split('/\\(:(template)\\s+([-!]?)\\s*(\\w+)\\s*(.*?):\\)/i',
 584      $ttext, -1, PREG_SPLIT_DELIM_CAPTURE);
 585  }
 586  
 587  ## Merge parameters from (:template default :) with those in the (:pagelist:)
 588  function FPLTemplateDefaults($pagename, $matches, &$opt, &$tparts){
 589    global $PageListArgPattern;
 590    $i = 0;
 591    while ($i < count($tparts)) {
 592      if ($tparts[$i] != 'template') { $i++; continue; }
 593      if ($tparts[$i+2] != 'defaults' && $tparts[$i+2] != 'default') { $i+=5; continue; }
 594      $opt = array_merge(ParseArgs($tparts[$i+3], $PageListArgPattern), $opt);
 595      array_splice($tparts, $i, 4);
 596    }
 597    SDVA($opt, array('class' => 'fpltemplate', 'wrap' => 'div'));
 598  }
 599  
 600    ##  get the list of pages
 601  function FPLTemplatePageList($pagename, &$matches, &$opt){
 602    $matches = array_unique(array_merge((array)$matches, MakePageList($pagename, $opt, 0)));
 603    ## count matches before any slicing and save value as template var {$$PageListCount}
 604    $opt['PageListCount'] = count($matches);
 605  }
 606  
 607    ##  extract page subset according to 'count=' parameter
 608  function FPLTemplateSliceList($pagename, &$matches, $opt){
 609    if (@$opt['count']) {
 610      list($r0, $r1) = CalcRange($opt['count'], count($matches));
 611      if ($r1 < $r0) 
 612        $matches = array_reverse(array_slice($matches, $r1-1, $r0-$r1+1));
 613      else 
 614        $matches = array_slice($matches, $r0-1, $r1-$r0+1);
 615    }
 616  }
 617  
 618  function FPLTemplateFormat($pagename, $matches, $opt, $tparts, &$output){
 619    global $Cursor, $FPLTemplateMarkupFunction, $PCache;
 620    SDV($FPLTemplateMarkupFunction, 'MarkupToHTML');
 621    $savecursor = $Cursor;
 622    $pagecount = $groupcount = $grouppagecount = $traildepth = 0;
 623    $pseudovars = array('{$$PageCount}' => &$pagecount, 
 624                        '{$$GroupCount}' => &$groupcount, 
 625                        '{$$GroupPageCount}' => &$grouppagecount,
 626                        '{$$PageTrailDepth}' => &$traildepth);
 627  
 628    foreach(preg_grep('/^[\\w$]/', array_keys($opt)) as $k) 
 629      if (!is_array($opt[$k]))
 630        $pseudovars["{\$\$$k}"] = htmlspecialchars($opt[$k], ENT_NOQUOTES);
 631  
 632    $vk = array_keys($pseudovars);
 633    $vv = array_values($pseudovars);
 634  
 635    $lgroup = ''; $out = '';
 636    if(count($matches)==0 ) {
 637      $t = 0;
 638      while($t < count($tparts)) {
 639        if($tparts[$t]=='template' && $tparts[$t+2]=='none') {
 640           $out .= MarkupRestore(FPLExpandItemVars($tparts[$t+4], $matches, 0, $pseudovars));
 641           $t+=4;
 642        }
 643        $t++;
 644      }
 645    } # else:
 646    foreach($matches as $i => $pn) {
 647      $traildepth = intval(@$PCache[$pn]['depth']);
 648      $group = PageVar($pn, '$Group');
 649      if ($group != $lgroup) { $groupcount++; $grouppagecount = 0; $lgroup = $group; }
 650      $grouppagecount++; $pagecount++;
 651  
 652      $t = 0;
 653      while ($t < count($tparts)) {
 654        if ($tparts[$t] != 'template') { $item = $tparts[$t]; $t++; }
 655        else {
 656          list($neg, $when, $control, $item) = array_slice($tparts, $t+1, 4); $t+=5;
 657          if($when=='none') continue;
 658          if (!$control) {
 659            if ($when == 'first' && ($neg xor ($i != 0))) continue;
 660            if ($when == 'last' && ($neg xor ($i != count($matches) - 1))) continue;
 661          } else {
 662            if ($when == 'first' || !isset($last[$t])) {
 663              $curr = FPLExpandItemVars($control, $matches, $i, $pseudovars);
 664              if ($when == 'first' && ($neg xor (($i != 0) && ($last[$t] == $curr))))
 665                { $last[$t] = $curr; continue; }
 666              $last[$t] = $curr;
 667            }
 668            if ($when == 'last') {
 669              $next = FPLExpandItemVars($control, $matches, $i+1, $pseudovars);
 670              if ($neg xor ($next == $last[$t] && $i != count($matches) - 1)) continue;
 671              $last[$t] = $next;
 672            }
 673          }
 674        }
 675        $item = FPLExpandItemVars($item, $matches, $i, $pseudovars);
 676        $out .= MarkupRestore($item);
 677      }
 678    }
 679  
 680    $class = preg_replace('/[^-a-zA-Z0-9\\x80-\\xff]/', ' ', @$opt['class']);
 681    if ($class) $class = " class='$class'";
 682    $wrap = @$opt['wrap'];
 683    if ($wrap != 'inline') {
 684      $out = $FPLTemplateMarkupFunction($pagename, $out, array('escape' => 0, 'redirect'=>1));
 685      if ($wrap != 'none') $out = "<div$class>$out</div>";
 686    }
 687    $Cursor = $savecursor;
 688    $output .= $out;
 689  }
 690  ## This function moves repeated code blocks out of FPLTemplateFormat()
 691  function FPLExpandItemVars($item, $matches, $idx, $psvars) {
 692    global $Cursor, $EnableUndefinedTemplateVars;
 693    $Cursor['<'] = $Cursor['&lt;'] = (string)@$matches[$idx-1];
 694    $Cursor['='] = (string)@$matches[$idx];
 695    $Cursor['>'] = $Cursor['&gt;'] = (string)@$matches[$idx+1];
 696    $item = str_replace(array_keys($psvars), array_values($psvars), $item);
 697    $item = preg_replace('/\\{(=|&[lg]t;)(\\$:?\\w+)\\}/e',
 698                "PVSE(PageVar(\$pn, '$2', '$1'))", $item);
 699    if(! IsEnabled($EnableUndefinedTemplateVars, 0))
 700      $item = preg_replace("/\\{\\$\\$\\w+\\}/", '', $item);
 701    return $item;
 702  }
 703  
 704  ########################################################################
 705  ## The functions below optimize searches by maintaining a file of
 706  ## words and link cross references (the "page index").
 707  ########################################################################
 708  
 709  ## PageIndexTerms($terms) takes an array of strings and returns a
 710  ## normalized list of associated search terms.  This reduces the
 711  ## size of the index and speeds up searches.
 712  function PageIndexTerms($terms) {
 713    global $StrFoldFunction;
 714    $w = array();
 715    foreach((array)$terms as $t) {
 716      $w = array_merge($w, preg_split('/[^\\w\\x80-\\xff]+/', 
 717             $StrFoldFunction($t), -1, PREG_SPLIT_NO_EMPTY));
 718    }
 719   return $w;
 720  }
 721  
 722  ## The PageIndexUpdate($pagelist) function updates the page index
 723  ## file with terms and target links for the pages in $pagelist.
 724  ## The optional $dir parameter allows this function to be called
 725  ## via register_shutdown_function (which sometimes changes directories
 726  ## on us).
 727  function PageIndexUpdate($pagelist = NULL, $dir = '') {
 728    global $EnableReadOnly, $PageIndexUpdateList, $PageIndexFile, 
 729      $PageIndexTime, $Now;
 730    if (IsEnabled($EnableReadOnly, 0)) return;
 731    $abort = ignore_user_abort(true);
 732    if ($dir) { flush(); chdir($dir); }
 733    if (is_null($pagelist)) 
 734      { $pagelist = (array)$PageIndexUpdateList; $PageIndexUpdateList = array(); }
 735    if (!$pagelist || !$PageIndexFile) return;
 736    SDV($PageIndexTime, 10);
 737    $c = count($pagelist); $updatecount = 0;
 738    StopWatch("PageIndexUpdate begin ($c pages to update)");
 739    $pagelist = (array)$pagelist;
 740    $timeout = time() + $PageIndexTime;
 741    $cmpfn = create_function('$a,$b', 'return strlen($b)-strlen($a);');
 742    Lock(2);
 743    $ofp = fopen("$PageIndexFile,new", 'w');
 744    foreach($pagelist as $pn) {
 745      if (@$updated[$pn]) continue;
 746      @$updated[$pn]++;
 747      if (time() > $timeout) continue;
 748      $page = ReadPage($pn, READPAGE_CURRENT);
 749      if ($page) {
 750        $targets = str_replace(',', ' ', @$page['targets']);
 751        $terms = PageIndexTerms(array(@$page['text'], $targets, $pn));
 752        usort($terms, $cmpfn);
 753        $x = '';
 754        foreach($terms as $t) { if (strpos($x, $t) === false) $x .= " $t"; }
 755        fputs($ofp, "$pn:$Now: $targets :$x\n");
 756      }
 757      $updatecount++;
 758    }
 759    $ifp = @fopen($PageIndexFile, 'r');
 760    if ($ifp) {
 761      while (!feof($ifp)) {
 762        $line = fgets($ifp, 4096);
 763        while (substr($line, -1, 1) != "\n" && !feof($ifp)) 
 764          $line .= fgets($ifp, 4096);
 765        $i = strpos($line, ':');
 766        if ($i === false) continue;
 767        $n = substr($line, 0, $i);
 768        if (@$updated[$n]) continue;
 769        fputs($ofp, $line);
 770      }
 771      fclose($ifp);
 772    }
 773    fclose($ofp);
 774    if (file_exists($PageIndexFile)) unlink($PageIndexFile); 
 775    rename("$PageIndexFile,new", $PageIndexFile);
 776    fixperms($PageIndexFile);
 777    StopWatch("PageIndexUpdate end ($updatecount updated)");
 778    ignore_user_abort($abort);
 779  }
 780  
 781  ## PageIndexQueueUpdate specifies pages to be updated in
 782  ## the index upon shutdown (via register_shutdown function).
 783  function PageIndexQueueUpdate($pagelist) {
 784    global $PageIndexUpdateList;
 785    if (!@$PageIndexUpdateList) 
 786      register_shutdown_function('PageIndexUpdate', NULL, getcwd());
 787    $PageIndexUpdateList = array_merge((array)@$PageIndexUpdateList,
 788                                       (array)$pagelist);
 789    $c1 = count($pagelist); $c2 = count($PageIndexUpdateList);
 790    StopWatch("PageIndexQueueUpdate: queued $c1 pages ($c2 total)");
 791  }
 792  
 793  ## PageIndexGrep returns a list of pages that match the strings
 794  ## provided.  Note that some search terms may need to be normalized
 795  ## in order to get the desired results (see PageIndexTerms above).
 796  ## Also note that this just works for the index; if the index is
 797  ## incomplete, then so are the results returned by this list.
 798  ## (MakePageList above already knows how to deal with this.)
 799  function PageIndexGrep($terms, $invert = false) {
 800    global $PageIndexFile;
 801    if (!$PageIndexFile) return array();
 802    StopWatch('PageIndexGrep begin');
 803    $pagelist = array();
 804    $fp = @fopen($PageIndexFile, 'r');
 805    if ($fp) {
 806      $terms = (array)$terms;
 807      while (!feof($fp)) {
 808        $line = fgets($fp, 4096);
 809        while (substr($line, -1, 1) != "\n" && !feof($fp))
 810          $line .= fgets($fp, 4096);
 811        $i = strpos($line, ':');
 812        if (!$i) continue;
 813        $add = true;
 814        foreach($terms as $t) 
 815          if (strpos($line, $t) === false) { $add = false; break; }
 816        if ($add xor $invert) $pagelist[] = substr($line, 0, $i);
 817      }
 818      fclose($fp);
 819    }
 820    StopWatch('PageIndexGrep end');
 821    return $pagelist;
 822  }
 823    
 824  ## PostPageIndex is inserted into $EditFunctions to update
 825  ## the linkindex whenever a page is saved.
 826  function PostPageIndex($pagename, &$page, &$new) {
 827    global $IsPagePosted;
 828    if ($IsPagePosted) PageIndexQueueUpdate($pagename);
 829  }

title

Description

title

Description

title

Description

title

title

Body