pagelist.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <?php if (!defined('PmWiki')) exit();
  2. /* Copyright 2004-2006 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. This script implements (:pagelist:) and friends -- it's one
  8. of the nastiest scripts you'll ever encounter. Part of the reason
  9. for this is that page listings are so powerful and flexible, so
  10. that adds complexity. They're also expensive, so we have to
  11. optimize them wherever we can.
  12. The core function is FmtPageList(), which will generate a
  13. listing according to a wide variety of options. FmtPageList takes
  14. care of initial option processing, and then calls a "FPL"
  15. (format page list) function to obtain the formatted output.
  16. The FPL function is chosen by the 'fmt=' option to (:pagelist:).
  17. Each FPL function calls MakePageList() to obtain the list
  18. of pages, formats the list somehow, and returns the results
  19. to FmtPageList. FmtPageList then returns the output to
  20. the caller, and calls Keep() (preserves HTML) or PRR() (re-evaluate
  21. as markup) as appropriate for the output being returned.
  22. */
  23. ## $PageIndexFile is the index file for term searches and link= option
  24. if (IsEnabled($EnablePageIndex, 1)) {
  25. SDV($PageIndexFile, "$WorkDir/.pageindex");
  26. $EditFunctions[] = 'PostPageIndex';
  27. }
  28. ## $SearchPatterns holds patterns for list= option
  29. SDVA($SearchPatterns['all'], array());
  30. $SearchPatterns['normal'][] = '!\.(All)?Recent(Changes|Uploads)$!';
  31. $SearchPatterns['normal'][] = '!\.Group(Print)?(Header|Footer|Attributes)$!';
  32. $SearchPatterns['normal'][] = str_replace('.', '\\.', "!^$pagename$!");
  33. ## $FPLFormatOpt is a list of options associated with fmt=
  34. ## values. 'default' is used for any undefined values of fmt=.
  35. SDVA($FPLFormatOpt, array(
  36. 'default' => array('fn' => 'FPLTemplate', 'fmt' => '#default',
  37. 'class' => 'fpltemplate'),
  38. 'bygroup' => array('fn' => 'FPLTemplate', 'template' => '#bygroup',
  39. 'class' => 'fplbygroup'),
  40. 'simple' => array('fn' => 'FPLTemplate', 'template' => '#simple',
  41. 'class' => 'fplsimple'),
  42. 'group' => array('fn' => 'FPLTemplate', 'template' => '#group',
  43. 'class' => 'fplgroup'),
  44. 'title' => array('fn' => 'FPLTemplate', 'template' => '#title',
  45. 'class' => 'fpltitle', 'order' => 'title'),
  46. ));
  47. SDV($SearchResultsFmt, "<div class='wikisearch'>\$[SearchFor]
  48. $HTMLVSpace\$MatchList
  49. $HTMLVSpace\$[SearchFound]$HTMLVSpace</div>");
  50. SDV($SearchQuery, str_replace('$', '&#036;',
  51. htmlspecialchars(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES)));
  52. XLSDV('en', array(
  53. 'SearchFor' => 'Results of search for <em>$Needle</em>:',
  54. 'SearchFound' =>
  55. '$MatchCount pages found out of $MatchSearched pages searched.'));
  56. Markup('pagelist', 'directives',
  57. '/\\(:pagelist(\\s+.*?)?:\\)/ei',
  58. "FmtPageList('\$MatchList', \$pagename, array('o' => PSS('$1 ')))");
  59. Markup('searchbox', 'directives',
  60. '/\\(:searchbox(\\s.*?)?:\\)/e',
  61. "SearchBox(\$pagename, ParseArgs(PSS('$1')))");
  62. Markup('searchresults', 'directives',
  63. '/\\(:searchresults(\\s+.*?)?:\\)/ei',
  64. "FmtPageList(\$GLOBALS['SearchResultsFmt'], \$pagename,
  65. array('req' => 1, 'o' => PSS('$1')))");
  66. SDV($SaveAttrPatterns['/\\(:(searchresults|pagelist)(\\s+.*?)?:\\)/i'], ' ');
  67. SDV($HandleActions['search'], 'HandleSearchA');
  68. SDV($HandleAuth['search'], 'read');
  69. SDV($ActionTitleFmt['search'], '| $[Search Results]');
  70. ## SearchBox generates the output of the (:searchbox:) markup.
  71. ## If $SearchBoxFmt is defined, that is used, otherwise a searchbox
  72. ## is generated. Options include group=, size=, label=.
  73. function SearchBox($pagename, $opt) {
  74. global $SearchBoxFmt, $SearchBoxOpt, $SearchQuery, $EnablePathInfo;
  75. if (isset($SearchBoxFmt)) return Keep(FmtPageName($SearchBoxFmt, $pagename));
  76. SDVA($SearchBoxOpt, array('size' => '40',
  77. 'label' => FmtPageName('$[Search]', $pagename),
  78. 'value' => str_replace("'", "&#039;", $SearchQuery)));
  79. $opt = array_merge((array)$SearchBoxOpt, @$_GET, (array)$opt);
  80. $opt['action'] = 'search';
  81. $target = ($opt['target'])
  82. ? MakePageName($pagename, $opt['target']) : $pagename;
  83. $out = FmtPageName(" class='wikisearch' action='\$PageUrl' method='get'>",
  84. $target);
  85. $opt['n'] = IsEnabled($EnablePathInfo, 0) ? '' : $target;
  86. $out .= "<input type='text' name='q' value='{$opt['value']}'
  87. class='inputbox searchbox' size='{$opt['size']}' /><input type='submit'
  88. class='inputbutton searchbutton' value='{$opt['label']}' />";
  89. foreach($opt as $k => $v) {
  90. if ($v == '') continue;
  91. if ($k == 'q' || $k == 'label' || $k == 'value' || $k == 'size') continue;
  92. $k = str_replace("'", "&#039;", $k);
  93. $v = str_replace("'", "&#039;", $v);
  94. $out .= "<input type='hidden' name='$k' value='$v' />";
  95. }
  96. return '<form '.Keep($out).'</form>';
  97. }
  98. ## FmtPageList combines options from markup, request form, and url,
  99. ## calls the appropriate formatting function, and returns the string.
  100. function FmtPageList($outfmt, $pagename, $opt) {
  101. global $GroupPattern, $FmtV, $FPLFormatOpt, $FPLFunctions;
  102. # get any form or url-submitted request
  103. $rq = htmlspecialchars(stripmagic(@$_REQUEST['q']), ENT_NOQUOTES);
  104. # build the search string
  105. $FmtV['$Needle'] = $opt['o'] . ' ' . $rq;
  106. # Handle "group/" at the beginning of the form-submitted request
  107. if (preg_match("!^($GroupPattern(\\|$GroupPattern)*)?/!i", $rq, $match)) {
  108. $opt['group'] = @$match[1];
  109. $rq = substr($rq, strlen(@$match[1])+1);
  110. }
  111. # merge markup options with form and url
  112. $opt = array_merge($opt, ParseArgs($opt['o'] . ' ' . $rq), @$_REQUEST);
  113. # non-posted blank search requests return nothing
  114. if (@($opt['req'] && !$opt['-'] && !$opt[''] && !$opt['+'] && !$opt['q']))
  115. return '';
  116. # terms and group to be included and excluded
  117. $GLOBALS['SearchIncl'] = array_merge((array)@$opt[''], (array)@$opt['+']);
  118. $GLOBALS['SearchExcl'] = (array)@$opt['-'];
  119. $GLOBALS['SearchGroup'] = @$opt['group'];
  120. $fmt = @$opt['fmt']; if (!$fmt) $fmt = 'default';
  121. $fmtopt = @$FPLFormatOpt[$fmt];
  122. if (!is_array($fmtopt)) {
  123. if ($fmtopt) $fmtopt = array('fn' => $fmtopt);
  124. elseif (@$FPLFunctions[$fmt])
  125. $fmtopt = array('fn' => $FPLFunctions[$fmt]);
  126. else $fmtopt = $FPLFormatOpt['default'];
  127. }
  128. $fmtfn = @$fmtopt['fn'];
  129. if (!is_callable($fmtfn)) $fmtfn = $FPLFormatOpt['default']['fn'];
  130. $matches = array();
  131. $opt = array_merge($fmtopt, $opt);
  132. $out = $fmtfn($pagename, $matches, $opt);
  133. $FmtV['$MatchCount'] = count($matches);
  134. if ($outfmt != '$MatchList')
  135. { $FmtV['$MatchList'] = $out; $out = FmtPageName($outfmt, $pagename); }
  136. $out = preg_replace('/^(<[^>]+>)(.*)/esm', "PSS('$1').Keep(PSS('$2'))", $out);
  137. return PRR($out);
  138. }
  139. ## MakePageList generates a list of pages using the specifications given
  140. ## by $opt.
  141. function MakePageList($pagename, $opt, $retpages = 1) {
  142. global $MakePageListOpt, $SearchPatterns, $EnablePageListProtect, $PCache,
  143. $FmtV;
  144. StopWatch('MakePageList begin');
  145. SDVA($MakePageListOpt, array('list' => 'default'));
  146. $opt = array_merge((array)$MakePageListOpt, $opt);
  147. $readf = @$opt['readf'];
  148. # we have to read the page if order= is anything but name
  149. $order = @$opt['order'];
  150. $readf |= $order && ($order!='name') && ($order!='-name');
  151. $pats = @(array)$SearchPatterns[$opt['list']];
  152. if (@$opt['group']) $pats[] = FixGlob($opt['group'], '$1$2.*');
  153. if (@$opt['name']) $pats[] = FixGlob($opt['name'], '$1*.$2');
  154. # inclp/exclp contain words to be included/excluded.
  155. $incl = array(); $inclp = array(); $inclx = false;
  156. $excl = array(); $exclp = '';
  157. foreach((array)@$opt[''] as $i) { $incl[] = $i; }
  158. foreach((array)@$opt['+'] as $i) { $incl[] = $i; }
  159. foreach((array)@$opt['-'] as $i) { $excl[] = $i; }
  160. foreach($incl as $i) {
  161. $inclp[] = '$'.preg_quote($i).'$i';
  162. $inclx |= preg_match('[^\\w\\x80-\\xff]', $i);
  163. }
  164. if ($excl) $exclp = '$'.implode('|', array_map('preg_quote', $excl)).'$i';
  165. $searchterms = count($incl) + count($excl);
  166. $readf += $searchterms; # forced read if incl/excl
  167. if (@$opt['trail']) {
  168. $trail = ReadTrail($pagename, $opt['trail']);
  169. $list = array();
  170. foreach($trail as $tstop) {
  171. $pn = $tstop['pagename'];
  172. $list[] = $pn;
  173. $tstop['parentnames'] = array();
  174. PCache($pn, $tstop);
  175. }
  176. foreach($trail as $tstop)
  177. $PCache[$tstop['pagename']]['parentnames'][] =
  178. @$trail[$tstop['parent']]['pagename'];
  179. } else $list = ListPages($pats);
  180. if (IsEnabled($EnablePageListProtect, 1)) $readf = 1000;
  181. $matches = array();
  182. $FmtV['$MatchSearched'] = count($list);
  183. $terms = ($incl) ? PageIndexTerms($incl) : array();
  184. if (@$opt['link']) {
  185. $link = MakePageName($pagename, $opt['link']);
  186. $linkp = "/(^|,)$link(,|$)/i";
  187. $terms[] = " $link ";
  188. $readf++;
  189. }
  190. if ($terms) {
  191. $xlist = PageIndexGrep($terms, true);
  192. $a = count($list);
  193. $list = array_diff($list, $xlist);
  194. $a -= count($list);
  195. StopWatch("MakePageList: PageIndex filtered $a pages");
  196. }
  197. $xlist = array();
  198. StopWatch('MakePageList scanning '.count($list)." pages, readf=$readf");
  199. foreach((array)$list as $pn) {
  200. if ($readf) {
  201. $page = ($readf >= 1000)
  202. ? RetrieveAuthPage($pn, 'read', false, READPAGE_CURRENT)
  203. : ReadPage($pn, READPAGE_CURRENT);
  204. if (!$page) continue;
  205. if (@$linkp && !preg_match($linkp, @$page['targets']))
  206. { $xlist[] = $pn; continue; }
  207. if ($searchterms) {
  208. $text = $pn."\n".@$page['targets']."\n".@$page['text'];
  209. if ($exclp && preg_match($exclp, $text)) continue;
  210. foreach($inclp as $i)
  211. if (!preg_match($i, $text))
  212. { if (!$inclx) $xlist[] = $pn; continue 2; }
  213. }
  214. $page['size'] = strlen(@$page['text']);
  215. } else $page = array();
  216. $page['pagename'] = $page['name'] = $pn;
  217. PCache($pn, $page);
  218. $matches[] = $pn;
  219. }
  220. StopWatch('MakePageList sort');
  221. if ($order) SortPageList($matches, $order);
  222. if ($xlist) {
  223. register_shutdown_function('flush');
  224. register_shutdown_function('PageIndexUpdate', $xlist, getcwd());
  225. }
  226. StopWatch('MakePageList end');
  227. if ($retpages)
  228. for($i=0; $i<count($matches); $i++)
  229. $matches[$i] = &$PCache[$matches[$i]];
  230. return $matches;
  231. }
  232. function SortPageList(&$matches, $order) {
  233. global $PCache;
  234. $code = '';
  235. foreach(preg_split("/[\\s,|]+/", $order, -1, PREG_SPLIT_NO_EMPTY) as $o) {
  236. if ($o{0}=='-') { $r = '-'; $o = substr($o, 1); }
  237. else $r = '';
  238. switch ($o) {
  239. case 'random':
  240. foreach($matches as $pn) $PCache[$pn]['random'] = rand();
  241. /* fall through */
  242. case 'size':
  243. case 'time':
  244. case 'ctime':
  245. $code .= "\$c = @(\$PCache[\$x]['$o']-\$PCache[\$y]['$o']); ";
  246. break;
  247. default:
  248. if ($o == 'title')
  249. foreach($matches as $pn)
  250. if (!isset($PCache[$pn]['title']))
  251. $PCache[$pn]['title'] = PageVar($pn, '$Title');
  252. if ($o == 'group')
  253. foreach($matches as $pn)
  254. $PCache[$pn]['group'] = PageVar($pn, '$Group');
  255. $code .= "\$c = @strcasecmp(\$PCache[\$x]['$o'],\$PCache[\$y]['$o']); ";
  256. break;
  257. }
  258. $code .= "if (\$c) return $r\$c;\n";
  259. }
  260. if ($code)
  261. uasort($matches,
  262. create_function('$x,$y', "global \$PCache; $code return 0;"));
  263. }
  264. ## HandleSearchA performs ?action=search. It's basically the same
  265. ## as ?action=browse, except it takes its contents from Site.Search.
  266. function HandleSearchA($pagename, $level = 'read') {
  267. global $PageSearchForm, $FmtV, $HandleSearchFmt,
  268. $PageStartFmt, $PageEndFmt;
  269. SDV($HandleSearchFmt,array(&$PageStartFmt, '$PageText', &$PageEndFmt));
  270. SDV($PageSearchForm, '$[{$SiteGroup}/Search]');
  271. $form = RetrieveAuthPage($pagename, $level, true, READPAGE_CURRENT);
  272. if (!$form) Abort("?unable to read $pagename");
  273. PCache($pagename, $form);
  274. $text = preg_replace('/\\[([=@])(.*?)\\1\\]/s', ' ', $form['text']);
  275. if (!preg_match('/\\(:searchresults(\\s.*?)?:\\)/', $text))
  276. foreach((array)$PageSearchForm as $formfmt) {
  277. $form = ReadPage(FmtPageName($formfmt, $pagename), READPAGE_CURRENT);
  278. if ($form['text']) break;
  279. }
  280. $text = @$form['text'];
  281. if (!$text) $text = '(:searchresults:)';
  282. $FmtV['$PageText'] = MarkupToHTML($pagename,$text);
  283. PrintFmt($pagename, $HandleSearchFmt);
  284. }
  285. ########################################################################
  286. ## The functions below provide different formatting options for
  287. ## the output list, controlled by the fmt= parameter and the
  288. ## $FPLFormatOpt hash.
  289. ########################################################################
  290. function FPLTemplate($pagename, &$matches, $opt) {
  291. global $Cursor, $FPLFormatOpt, $FPLTemplatePageFmt;
  292. SDV($FPLTemplatePageFmt, array('{$FullName}',
  293. '{$SiteGroup}.LocalTemplates','{$SiteGroup}.PageListTemplates'));
  294. $template = @$opt['template'];
  295. if (!$template) $template = @$opt['fmt'];
  296. list($tname, $qf) = explode('#', $template, 2);
  297. if ($tname) $tname = array(MakePageName($pagename, $tname));
  298. else $tname = (array)$FPLTemplatePageFmt;
  299. foreach ($tname as $t) {
  300. $t = FmtPageName($t, $pagename);
  301. if (!PageExists($t)) continue;
  302. if ($qf) $t .= "#$qf";
  303. $ttext = IncludeText($pagename, $t, true);
  304. if (!$qf || strpos($ttext, "[[#$qf]]") !== false) break;
  305. }
  306. ## remove any anchor markups to avoid duplications
  307. $ttext = preg_replace('/\\[\\[#[A-Za-z][-.:\\w]*\\]\\]/', '', $ttext);
  308. if (!@$opt['order'] && !@$opt['trail']) $opt['order'] = 'name';
  309. $matches = array_values(MakePageList($pagename, $opt, 0));
  310. if (@$opt['count']) array_splice($matches, $opt['count']);
  311. $savecursor = $Cursor;
  312. $pagecount = 0; $groupcount = 0; $grouppagecount = 0;
  313. $vk = array('{$PageCount}', '{$GroupCount}', '{$GroupPageCount}');
  314. $vv = array(&$pagecount, &$groupcount, &$grouppagecount);
  315. $lgroup = ''; $out = '';
  316. foreach($matches as $i => $pn) {
  317. $prev = (string)@$matches[$i-1];
  318. $next = (string)@$matches[$i+1];
  319. $Cursor['<'] = $Cursor['&lt;'] = $prev;
  320. $Cursor['='] = $pn;
  321. $Cursor['>'] = $Cursor['&gt;'] = $next;
  322. $group = PageVar($pn, '$Group');
  323. if ($group != $lgroup) { $groupcount++; $grouppagecount = 0; }
  324. $grouppagecount++; $pagecount++;
  325. $item = str_replace($vk, $vv, $ttext);
  326. $item = preg_replace('/\\{(=|&[lg]t;)(\\$:?\\w+)\\}/e',
  327. "PageVar(\$pn, '$2', '$1')", $item);
  328. $out .= $item;
  329. $lgroup = $group;
  330. }
  331. $class = preg_replace('/[^-a-zA-Z0-9\\x80-\\xff]/', ' ', @$opt['class']);
  332. $div = ($class) ? "<div class='$class'>" : '<div>';
  333. return $div.MarkupToHTML($pagename, $out, array('escape' => 0)).'</div>';
  334. }
  335. ########################################################################
  336. ## The functions below optimize searches by maintaining a file of
  337. ## words and link cross references (the "page index").
  338. ########################################################################
  339. ## PageIndexTerms($terms) takes an array of strings and returns a
  340. ## normalized list of associated search terms. This reduces the
  341. ## size of the index and speeds up searches.
  342. function PageIndexTerms($terms) {
  343. $w = array();
  344. foreach((array)$terms as $t) {
  345. $w = array_merge($w, preg_split('/[^\\w\\x80-\\xff]+/',
  346. strtolower($t), -1, PREG_SPLIT_NO_EMPTY));
  347. }
  348. return $w;
  349. }
  350. ## The PageIndexUpdate($pagelist) function updates the page index
  351. ## file with terms and target links for the pages in $pagelist.
  352. ## The optional $dir parameter allows this function to be called
  353. ## via register_shutdown_function (which sometimes changes directories
  354. ## on us).
  355. function PageIndexUpdate($pagelist, $dir = '') {
  356. global $PageIndexFile, $PageIndexTime, $Now;
  357. $abort = ignore_user_abort(true);
  358. if ($dir) chdir($dir);
  359. SDV($PageIndexTime, 10);
  360. if (!$pagelist || !$PageIndexFile) return;
  361. $c = count($pagelist);
  362. StopWatch("PageIndexUpdate begin ($c pages to update)");
  363. $pagelist = (array)$pagelist;
  364. $timeout = time() + $PageIndexTime;
  365. $cmpfn = create_function('$a,$b', 'return strlen($b)-strlen($a);');
  366. Lock(2);
  367. $ofp = fopen("$PageIndexFile,new", 'w');
  368. foreach($pagelist as $pn) {
  369. if (time() > $timeout) break;
  370. $page = ReadPage($pn, READPAGE_CURRENT);
  371. if ($page) {
  372. $targets = str_replace(',', ' ', @$page['targets']);
  373. $terms = PageIndexTerms(array(@$page['text'], $targets, $pn));
  374. usort($terms, $cmpfn);
  375. $x = '';
  376. foreach($terms as $t) { if (strpos($x, $t) === false) $x .= " $t"; }
  377. fputs($ofp, "$pn:$Now: $targets :$x\n");
  378. }
  379. $updated[$pn]++;
  380. }
  381. $ifp = @fopen($PageIndexFile, 'r');
  382. if ($ifp) {
  383. while (!feof($ifp)) {
  384. $line = fgets($ifp, 4096);
  385. while (substr($line, -1, 1) != "\n" && !feof($ifp))
  386. $line .= fgets($ifp, 4096);
  387. $i = strpos($line, ':');
  388. if ($i === false) continue;
  389. $n = substr($line, 0, $i);
  390. if (@$updated[$n]) continue;
  391. fputs($ofp, $line);
  392. }
  393. fclose($ifp);
  394. }
  395. fclose($ofp);
  396. if (file_exists($PageIndexFile)) unlink($PageIndexFile);
  397. rename("$PageIndexFile,new", $PageIndexFile);
  398. fixperms($PageIndexFile);
  399. $c = count($updated);
  400. StopWatch("PageIndexUpdate end ($c updated)");
  401. ignore_user_abort($abort);
  402. }
  403. ## PageIndexGrep returns a list of pages that match the strings
  404. ## provided. Note that some search terms may need to be normalized
  405. ## in order to get the desired results (see PageIndexTerms above).
  406. ## Also note that this just works for the index; if the index is
  407. ## incomplete, then so are the results returned by this list.
  408. ## (MakePageList above already knows how to deal with this.)
  409. function PageIndexGrep($terms, $invert = false) {
  410. global $PageIndexFile;
  411. if (!$PageIndexFile) return array();
  412. StopWatch('PageIndexGrep begin');
  413. $pagelist = array();
  414. $fp = @fopen($PageIndexFile, 'r');
  415. if ($fp) {
  416. $terms = (array)$terms;
  417. while (!feof($fp)) {
  418. $line = fgets($fp, 4096);
  419. while (substr($line, -1, 1) != "\n" && !feof($fp))
  420. $line .= fgets($fp, 4096);
  421. $i = strpos($line, ':');
  422. if (!$i) continue;
  423. $add = true;
  424. foreach($terms as $t)
  425. if (strpos($line, $t) === false) { $add = false; break; }
  426. if ($add xor $invert) $pagelist[] = substr($line, 0, $i);
  427. }
  428. fclose($fp);
  429. }
  430. StopWatch('PageIndexGrep end');
  431. return $pagelist;
  432. }
  433. ## PostPageIndex is inserted into $EditFunctions to update
  434. ## the linkindex whenever a page is saved.
  435. function PostPageIndex($pagename, &$page, &$new) {
  436. global $IsPagePosted;
  437. if ($IsPagePosted) PageIndexUpdate($pagename);
  438. }