Simple Groupware PHP Cross Reference Groupware Applications

Source: /src/lib/pmwiki/pmwiki.php - 2072 lines - 80094 bytes - Summary - Text - Print

   1  <?php
   2  /*
   3      PmWiki
   4      Copyright 2001-2009 Patrick R. Michaud
   5      pmichaud@pobox.com
   6      http://www.pmichaud.com/
   7  
   8      This program is free software; you can redistribute it and/or modify
   9      it under the terms of the GNU General Public License as published by
  10      the Free Software Foundation; either version 2 of the License, or
  11      (at your option) any later version.
  12  
  13      This program is distributed in the hope that it will be useful,
  14      but WITHOUT ANY WARRANTY; without even the implied warranty of
  15      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16      GNU General Public License for more details.
  17  
  18      You should have received a copy of the GNU General Public License
  19      along with this program; if not, write to the Free Software
  20      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21  
  22      ----
  23      Note from Pm:  Trying to understand the PmWiki code?  Wish it had 
  24      more comments?  If you want help with any of the code here,
  25      write me at <pmichaud@pobox.com> with your question(s) and I'll
  26      provide explanations (and add comments) that answer them.
  27  */
  28  
  29  pmwiki_init();
  30  define('PmWiki',1);
  31  require_once('lib/pmwiki/scripts/xlpage-utf-8.php');
  32  require_once(sys_custom('lib/pmwiki/config.php'));
  33  require_once('lib/pmwiki/scripts/stdconfig.php');
  34  
  35  // tb
  36  function pmwiki_readpage_external($file) {
  37    global $UploadUrlFmt, $WikiLibDirs, $FmtPV;
  38  
  39    $dir = dirname($file);
  40    $UploadUrlFmt = $dir."/uploads";
  41    $WikiLibDirs = array(new PageStore($dir."/{\$FullName}"));
  42    $FmtPV['$PageUrl'] = 'PUE("index.php?item[]='.$dir.'/$group.$name")';
  43    
  44    $page = ReadPage(basename($file), time());
  45    if (!empty($page["text"]) and !modify::detect_utf($page["text"])) $page["text"] = utf8_encode($page["text"]);
  46    return @$page["text"];
  47  }
  48  
  49  // tb
  50  function pmwiki_render($pagename,$data,$table,$cache=false,$lastmodified=0) {
  51    PageDbStore::$table = $table;
  52    if (!$cache) return MarkupToHTML($pagename,$data);
  53    $cid = "cms_".md5(MAIN_SCRIPT.$pagename.$table.$lastmodified);
  54    if ($output = sys_cache_get($cid)) return $output;
  55    $output = MarkupToHTML($pagename,$data);
  56    sys_cache_set($cid,$output,CMS_CACHE);
  57    return $output;
  58  }
  59  
  60  // tb
  61  function pmwiki_recent_pages($limit=20,$where="") {
  62    $pagename = "c.pagename";
  63    if (CMS_REAL_URL!="") $pagename = "replace(c.pagename,'.','/') as pagename";
  64  
  65    $query = "select ".$pagename.",c.title,c.lastmodified,c.change_summary,c.description from ".PageDbStore::$table." c, simple_sys_tree t where
  66                c.folder = t.id and c.activated=1 and
  67                ".str_replace("r@right@","t.rread",$_SESSION["permission_sql"])." and
  68                ".str_replace("r@right@","c.rread",$_SESSION["permission_sql"])."
  69                ".$where."
  70                order by c.lastmodified desc
  71                limit ".(int)$limit;
  72    return db_fetch($query);
  73  }
  74  
  75  // tb
  76  function pmwiki_url($url) {
  77    $url = str_replace(array("?page=Main.","?page=Index.Php"),array("?page=","index.php?"),$url);
  78    if (MAIN_SCRIPT!="cms.php") { // backend
  79      if (!strpos($url,"&file=")) $url = str_replace("?page=","index.php?find=".PageDbStore::$table."|pagename=",$url);
  80      if ($url[0]=="?") $url = "cms.php".$url;
  81    } else if (CMS_REAL_URL) { // frontend real-url
  82      $url = preg_replace("/(\?page=[^{&\"']+)/e","str_replace('.','/','\\1')",$url); // Group.Name => Group/Name
  83      $url = preg_replace("|(\?page=[^\"']+)&|","\\1?",$url); // ?page=&file => ?page=?file
  84      $replace = array(
  85        "ext/cms/",
  86        "?page=",
  87        "preview.php?filename=",
  88        "?file=",
  89        '="?"',
  90        '="?',
  91      );
  92      $with = array(
  93        CMS_REAL_URL."ext/",
  94        CMS_REAL_URL,
  95        CMS_REAL_URL."thumbs/",
  96        "/file/",
  97        '="'.CMS_REAL_URL.'"',
  98        '="'.CMS_REAL_URL.'?',
  99      );
 100      $url = str_replace($replace,$with,$url);
 101    } else {} // frontend
 102    return $url;
 103  }
 104  
 105  // tb
 106  function pmwiki_graphviz($data) {
 107    global $KPV, $KeepToken;
 108    $data = preg_replace( "/$KeepToken(\\d+?)$KeepToken/e", '$KPV[$1]', $data); // Unescape: [==]
 109    $data = str_replace( array("&gt;","&lt;","<:vspace>"), array(">","<",""), $data);
 110    $url = pmwiki_url("preview.php?filename=".basename(type_graphviz::render_png($data)));
 111    return "<img border='0' src='".$url."' />";
 112  }
 113  
 114  // tb
 115  function pmwiki_include_page($url, $height, $style) {
 116    if (!is_numeric($height) or $height < 20) $height = "200";
 117    return '
 118      <iframe src="'.modify::htmlquote($url).'" frameborder="0" style="width:100%; height:'.(int)$height.'px; '.modify::htmlquote($style).'"></iframe>
 119    ';
 120  }
 121  
 122  // tb
 123  function pmwiki_get_content($url, $regexp="", $regexp_format="", $xpath="", $time=1800, $timeout=10) {
 124    if (empty($url)) return "";
 125    $cid = md5($url.$regexp.$regexp_format.$xpath);
 126    if (DEBUG or $time==0 or !$data=sys_cache_get($cid)) {
 127      $context = stream_context_create(array(
 128        "http" => array("timeout"=>$timeout), "https" => array("timeout"=>$timeout)
 129      ));     
 130      $data = file_get_contents($url,0,$context);
 131      if ($data=="") {
 132        sys_log_message_log("cms-fail",$url);
 133        return "Error";
 134      }
 135      if (!modify::detect_utf($data)) $data = utf8_encode($data);
 136      $is_xml = sys_strbegins($data, "<?xml");
 137      if ($regexp_format!="") {
 138        preg_match_all($regexp, $data, $matches, PREG_SET_ORDER);
 139        if (isset($matches[0][1])) {
 140          $data = "";
 141          foreach ($matches as $match) $data .= vsprintf($regexp_format, array_slice($match, 1));
 142          if ($is_xml) $data = modify::htmlunquote($data);
 143        }
 144      } else if ($regexp!="") {
 145        preg_match($regexp, $data, $match);
 146        if (isset($match[1])) $data = $match[1];
 147      }
 148      if ($xpath!="") {
 149        $xml = new SimpleXMLElement($data);
 150        $data = $xml->xpath($xpath);
 151      }
 152      if ($time!=0) sys_cache_set($cid,$data,$time);
 153    }
 154    return $data;
 155  }
 156  
 157  // func a=b c=d + array(a,c) => func(b,d)
 158  // P2V = params and markup to vars
 159  function P2V($params,$markup) {
 160    $markup = htmlspecialchars_decode($markup,ENT_QUOTES);
 161    preg_match_all("/(\w+)=(\"[^\"]*\"|\S+)/",$markup,$matches,PREG_SET_ORDER);
 162    foreach ($matches as $match) {
 163      if (isset($params[$match[1]])) $params[$match[1]] = trim($match[2],"\"");
 164    }
 165    return $params;
 166  }
 167  
 168  // tb
 169  class PageDbStore {
 170    static $show_deactivated = false;
 171    static $table = "simple_cms";
 172    
 173    static function read($pagename) {
 174      static $cache = array();
 175      if (CMS_REAL_URL!="") $pagename = str_replace("/",".",$pagename);
 176      if (substr($pagename,0,5)=="Main.") $pagename = substr($pagename,5);
 177      if (!isset($cache[$pagename])) {
 178        $vars = array("pagename"=>$pagename);
 179        $query = "select c.*,c.data as text from ".self::$table." c, simple_sys_tree t where
 180                    c.pagename=@pagename@ and c.folder = t.id and
 181                  ".str_replace("r@right@","t.rread",$_SESSION["permission_sql"])." and
 182                  ".str_replace("r@right@","c.rread",$_SESSION["permission_sql"]);
 183        if (!self::$show_deactivated) $query .= " and c.activated=1";
 184        $rows = db_fetch(sql_limit($query,0,1), $vars);
 185        $result = array();
 186        if (!empty($rows[0])) {
 187          $result = $rows[0];
 188          if ($pos = strpos($result["pagename"],".")) {
 189            $result["group"] = substr($result["pagename"],0,$pos);
 190            $result["name"] = substr($result["pagename"],$pos+1);
 191          } else {
 192            $result["group"] = "";
 193            $result["name"] = $result["pagename"];
 194          }
 195        }
 196        $cache[$pagename] = $result;
 197      }
 198      return $cache[$pagename];
 199    }
 200    function write($pagename,$page) {
 201      debug_html("not implemented");
 202    }
 203    static function exists($pagename) {
 204      if (!$pagename) return false;
 205      static $cache = array();
 206      if (substr($pagename,0,5)=="Main.") $pagename = substr($pagename,5);
 207      if (!isset($cache[$pagename])) {
 208        $cache[$pagename] = (bool)db_count(self::$table,array("pagename=@pagename@"),array("pagename"=>$pagename));
 209      }
 210      return $cache[$pagename];
 211    }
 212    function delete($pagename) {
 213      debug_html("not implemented");
 214    }
 215    function ls($pats=NULL) {
 216      global $GroupPattern, $NamePattern;
 217      $query = "select c.pagename from ".self::$table." c, simple_sys_tree t where
 218                  c.folder = t.id and c.activated=1 and
 219                ".str_replace("r@right@","t.rread",$_SESSION["permission_sql"])." and
 220                ".str_replace("r@right@","c.rread",$_SESSION["permission_sql"]);
 221      $result = array();
 222      $rows = db_fetch($query);
 223      if (is_array($rows) and count($rows)>0) {
 224        foreach ($rows as $row) $result[] = $row["pagename"];
 225      }
 226      $pats=(array)$pats;
 227      array_push($pats, "/^(?:$GroupPattern\.)?$NamePattern$/");
 228      return MatchPageNames($result,$pats);
 229    }
 230  }
 231  
 232  function pmwiki_init() {
 233    StopWatch('PmWiki');
 234    @ini_set('magic_quotes_runtime', 0);
 235    if (@ini_get('pcre.backtrack_limit') < 1000000) @ini_set('pcre.backtrack_limit', 1000000);
 236    if (ini_get('register_globals')) {
 237      foreach($_REQUEST as $k=>$v) { 
 238        if (preg_match('/^(GLOBALS|_SERVER|_GET|_POST|_COOKIE|_FILES|_ENV|_REQUEST|_SESSION|FarmD|WikiDir)$/i', $k)) exit();
 239        $GLOBALS[$k]=''; unset(${$k}); 
 240      }
 241    }
 242    $q = preg_replace('/(\\?|%3f)([-\\w]+=)/', '&$2', @$_SERVER['QUERY_STRING']);
 243    if ($q != @$_SERVER['QUERY_STRING']) {
 244      unset($_GET);
 245      parse_str($q, $_GET);
 246      $_REQUEST = array_merge($_REQUEST, $_GET, $_POST);
 247    }
 248    $GLOBALS["UnsafeGlobals"] = array_keys($GLOBALS);
 249    $GLOBALS["GCount"]=0;
 250    $GLOBALS["FmtV"]=array();
 251    $GLOBALS["action"]='browse';
 252    $GLOBALS["EnableRobotControl"]=0;
 253  }
 254  
 255  function CompareArgs($arg) {
 256    $arg = ParseArgs($arg);
 257    return strcmp(@$arg[''][0], @$arg[''][1]);
 258  }
 259  
 260  function CondAuth($pagename, $condparm) {
 261    global $HandleAuth;
 262    @list($level, $pn) = explode(' ', $condparm, 2);
 263    $pn = ($pn > '') ? MakePageName($pagename, $pn) : $pagename;
 264    if (@$HandleAuth[$level]>'') $level = $HandleAuth[$level];
 265    return (boolean)RetrieveAuthPage($pn, $level, false, READPAGE_CURRENT);
 266  }
 267  
 268  ## CondExpr handles complex conditions (expressions)
 269  ## Portions Copyright 2005 by D. Faure (dfaure@cpan.org)
 270  function CondExpr($pagename, $condname, $condparm) {
 271    global $CondExprOps;
 272    SDV($CondExprOps, 'and|x?or|&&|\\|\\||[!()]');
 273    if ($condname == '(' || $condname == '[')
 274      $condparm = preg_replace('/[\\]\\)]\\s*$/', '', $condparm);
 275    $condparm = str_replace('&amp;&amp;', '&&', $condparm);
 276    $terms = preg_split("/(?<!\\S)($CondExprOps)(?!\\S)/i", $condparm, -1,
 277                        PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
 278    foreach($terms as $i => $t) {
 279      $t = trim($t);
 280      if (preg_match("/^($CondExprOps)$/i", $t)) continue;
 281      if ($t) $terms[$i] = CondText($pagename, "if $t", 'TRUE') ? '1' : '0';
 282    }
 283    return @eval('return(' . implode(' ', $terms) . ');');
 284  }
 285  if (function_exists("date_default_timezone_get") ) { # fix PHP5.3 warnings
 286    @date_default_timezone_set(@date_default_timezone_get());
 287  }
 288  
 289  ##  HandleDispatch() is used to dispatch control to the appropriate
 290  ##  action handler with the appropriate permissions.
 291  ##  If a message is supplied, it is added to $MessagesFmt.
 292  function HandleDispatch($pagename, $action, $msg=NULL) {
 293    global $MessagesFmt, $HandleActions, $HandleAuth;
 294    if ($msg) $MessagesFmt[] = "<div class='wikimessage'>$msg</div>";
 295    $fn = $HandleActions[$action];
 296    $auth = $HandleAuth[$action];
 297    if (!$auth) $auth = 'read';
 298    return $fn($pagename, $auth);
 299  }
 300  
 301  ## helper functions
 302  function stripmagic($x) 
 303    { return (ini_get("magic_quotes_gpc")!==false and get_magic_quotes_gpc()) ? stripslashes($x) : $x; }
 304  function pre_r(&$x)
 305    { return '<pre>'.htmlspecialchars(print_r($x, true)).'</pre>'; }
 306  function PSS($x) 
 307    { return str_replace('\\"','"',$x); }
 308  function PVS($x) 
 309    { return preg_replace("/\n[^\\S\n]*(?=\n)/", "\n<:vspace>", $x); }
 310  function PVSE($x) { return PVS(htmlspecialchars($x, ENT_NOQUOTES)); }
 311  function PZZ($x,$y='') { return ''; }
 312  function PRR($x=NULL) 
 313    { if ($x || is_null($x)) $GLOBALS['RedoMarkupLine']++; return $x; }
 314  function PUE($x)
 315    { return preg_replace('/[\\x80-\\xff \'"<>]/e', "'%'.dechex(ord('$0'))", $x); }
 316  function PQA($x) { 
 317    $out = '';
 318    if (preg_match_all('/([a-zA-Z]+)\\s*=\\s*("[^"]*"|\'[^\']*\'|\\S*)/',
 319                       $x, $attr, PREG_SET_ORDER)) {
 320      foreach($attr as $a) {
 321        if (preg_match('/^on/i', $a[1])) continue;
 322        $out .= $a[1] . '=' 
 323                . preg_replace( '/^([\'"]?)(.*)\\1$/e', 
 324                    "\"'\".str_replace(\"'\", '&#39;', PSS('$2')).\"'\"", $a[2])
 325                . ' ';
 326      }
 327    }
 328    return $out;
 329  }
 330  function SDV(&$v,$x) { if (!isset($v)) $v=$x; }
 331  function SDVA(&$var,$val) 
 332    { foreach($val as $k=>$v) if (!isset($var[$k])) $var[$k]=$v; }
 333  function IsEnabled(&$var,$f=0)
 334    { return (isset($var)) ? $var : $f; }
 335  function SetTmplDisplay($var, $val) 
 336    { NoCache(); $GLOBALS['TmplDisplay'][$var] = $val; }
 337  function NoCache($x = '') { $GLOBALS['NoHTMLCache'] |= 1; return $x; }
 338  function ParseArgs($x, $optpat = '(?>(\\w+)[:=])') {
 339    $z = array();
 340    preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/",
 341      $x, $terms, PREG_SET_ORDER);
 342    foreach($terms as $t) {
 343      $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]);
 344      if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; }
 345      else { $z['#'][] = $t[1]; $z[$t[1]][] = $v; }
 346      $z['#'][] = $v;
 347    }
 348    return $z;
 349  }
 350  function StopWatch($x) { 
 351    global $StopWatch, $EnableStopWatch;
 352    if (!$EnableStopWatch) return;
 353    static $wstart = 0, $ustart = 0;
 354    list($usec,$sec) = explode(' ',microtime());
 355    $wtime = ($sec+$usec); 
 356    if (!$wstart) $wstart = $wtime;
 357    if ($EnableStopWatch != 2) 
 358      { $StopWatch[] = sprintf("%05.2f %s", $wtime-$wstart, $x); return; }
 359    $dat = getrusage();
 360    $utime = ($dat['ru_utime.tv_sec']+$dat['ru_utime.tv_usec']/1000000);
 361    if (!$ustart) $ustart=$utime;
 362    $StopWatch[] = 
 363      sprintf("%05.2f %05.2f %s", $wtime-$wstart, $utime-$ustart, $x);
 364  }
 365  
 366  ## DRange converts a variety of string formats into date (ranges).
 367  ## It returns the start and end timestamps (+1 second) of the specified date.
 368  function DRange($when) {
 369    global $Now;
 370    ##  unix/posix @timestamp dates
 371    if (preg_match('/^\\s*@(\\d+)\\s*(.*)$/', $when, $m)) {
 372      $t0 = $m[2] ? strtotime($m[2], $m[1]) : $m[1];
 373      return array($t0, $t0+1);
 374    }
 375    ##  ISO-8601 dates
 376    $dpat = '/
 377      (?<!\\d)                 # non-digit
 378      (19\\d\\d|20[0-3]\\d)    # year ($1)
 379      ([-.\\/]?)               # date separator ($2)
 380      (0\\d|1[0-2])            # month ($3)
 381      (?: \\2                  # repeat date separator
 382        ([0-2]\\d|3[0-1])      # day ($4)
 383        (?: T                  # time marker
 384          ([01]\\d|2[0-4])     # hour ($5)
 385          ([.:]?)              # time separator ($6)
 386          ([0-5]\\d)           # minute ($7)
 387          (?: \\6              # repeat time separator
 388            ([0-5]\\d|60)      # seconds ($8)
 389          )?                   # optional :ss
 390        )?                     # optional Thh:mm:ss
 391      )?                       # optional -ddThh:mm:ss
 392      (?!\d)                   # non-digit
 393      /x';
 394    if (preg_match($dpat, $when, $m)) {
 395      $n = $m;
 396      ##  if no time given, assume range of 1 day (except when full month)
 397      if (@$m[4]>'' && @$m[5] == '') { @$n[4]++; }
 398      ##  if no day given, assume 1st of month and full month range
 399      if (@$m[4] == '') { $m[4] = 1; $n[4] = 1; $n[3]++; }
 400      ##  if no seconds given, assume range of 1 minute (except when full day)
 401      if (@$m[7]>'' && @$m[8] == '') { @$n[7]++; }
 402      $t0 = @mktime($m[5], $m[7], $m[8], $m[3], $m[4], $m[1]);
 403      $t1 = @mktime($n[5], $n[7], $n[8], $n[3], $n[4], $n[1]);
 404      return array($t0, $t1);
 405    }
 406    ##  now, today, tomorrow, yesterday
 407    NoCache();
 408    if ($when == 'now') return array($Now, $Now+1);
 409    $m = localtime(time());
 410    if ($when == 'tomorrow') { $m[3]++; $when = 'today'; }
 411    if ($when == 'yesterday') { $m[3]--; $when = 'today'; }
 412    if ($when == 'today') 
 413      return array(mktime(0, 0, 0, $m[4]+1, $m[3]  , $m[5]+1900),
 414                   mktime(0, 0, 0, $m[4]+1, $m[3]+1, $m[5]+1900));
 415    if (preg_match('/^\\s*$/', $when)) return array(-1, -1);
 416    $t0 = strtotime($when);
 417    $t1 = strtotime("+1 day", $t0);
 418    return array($t0, $t1);
 419  }
 420  
 421  ## AsSpaced converts a string with WikiWords into a spaced version
 422  ## of that string.  (It can be overridden via $AsSpacedFunction.)
 423  function AsSpaced($text) {
 424    $text = preg_replace("/([[:lower:]\\d])([[:upper:]])/", '$1 $2', $text);
 425    $text = preg_replace('/([^-\\d])(\\d[-\\d]*( |$))/','$1 $2',$text);
 426    return preg_replace("/([[:upper:]])([[:upper:]][[:lower:]\\d])/",
 427      '$1 $2', $text);
 428  }
 429  
 430  ## Lock is used to make sure only one instance of PmWiki is running when
 431  ## files are being written.  It does not "lock pages" for editing.
 432  function Lock($op) { 
 433    global $WorkDir, $LockFile, $EnableReadOnly;
 434    if ($op > 0 && IsEnabled($EnableReadOnly, 0))
 435      Abort('Cannot modify site -- $EnableReadOnly is set', 'readonly');
 436    SDV($LockFile, "$WorkDir/.flock");
 437    mkdirp(dirname($LockFile));
 438    static $lockfp,$curop;
 439    if (!$lockfp) $lockfp = @fopen($LockFile, "w");
 440    if (!$lockfp) { 
 441      if ($op <= 0) return;
 442      @unlink($LockFile); 
 443      $lockfp = fopen($LockFile,"w") or
 444        Abort('Cannot acquire lockfile', 'flock');
 445      fixperms($LockFile);
 446    }
 447    if ($op<0) { flock($lockfp,LOCK_UN); fclose($lockfp); $lockfp=0; $curop=0; }
 448    elseif ($op==0) { flock($lockfp,LOCK_UN); $curop=0; }
 449    elseif ($op==1 && $curop<1) 
 450      { session_write_close(); flock($lockfp,LOCK_SH); $curop=1; }
 451    elseif ($op==2 && $curop<2) 
 452      { session_write_close(); flock($lockfp,LOCK_EX); $curop=2; }
 453  }
 454  
 455  ## mkdirp creates a directory and its parents as needed, and sets
 456  ## permissions accordingly.
 457  function mkdirp($dir) {
 458    global $ScriptUrl;
 459    if (file_exists($dir)) return;
 460    if (!file_exists(dirname($dir))) mkdirp(dirname($dir));
 461    if (mkdir($dir, 0777)) {
 462      fixperms($dir);
 463      if (@touch("$dir/xxx")) { unlink("$dir/xxx"); return; }
 464      rmdir($dir);
 465    }
 466    $parent = realpath(dirname($dir)); 
 467    $bdir = basename($dir);
 468    $perms = decoct(fileperms($parent) & 03777);
 469    $msg = "PmWiki needs to have a writable <tt>$dir/</tt> directory 
 470      before it can continue.  You can create the directory manually 
 471      by executing the following commands on your server:
 472      <pre>    mkdir $parent/$bdir\n    chmod 777 $parent/$bdir</pre>
 473      Then, <a href='{$ScriptUrl}'>reload this page</a>.";
 474    $safemode = ini_get('safe_mode');
 475    if (!$safemode) $msg .= "<br /><br />Or, for a slightly more 
 476      secure installation, try executing <pre>    chmod 2777 $parent</pre> 
 477      on your server and following <a target='_blank' href='$ScriptUrl'>
 478      this link</a>.  Afterwards you can restore the permissions to 
 479      their current setting by executing <pre>    chmod $perms $parent</pre>.";
 480    Abort($msg);
 481  }
 482  
 483  ## fixperms attempts to correct permissions on a file or directory
 484  ## so that both PmWiki and the account (current dir) owner can manipulate it
 485  function fixperms($fname, $add = 0) {
 486    clearstatcache();
 487    if (!file_exists($fname)) Abort('?no such file');
 488    $bp = 0;
 489    if (fileowner($fname)!=@fileowner('.')) $bp = (is_dir($fname)) ? 007 : 006;
 490    if (filegroup($fname)==@filegroup('.')) $bp <<= 3;
 491    $bp |= $add;
 492    if ($bp && (fileperms($fname) & $bp) != $bp)
 493      @chmod($fname,fileperms($fname)|$bp);
 494  }
 495  
 496  ## GlobToPCRE converts wildcard patterns into pcre patterns for
 497  ## inclusion and exclusion.  Wildcards beginning with '-' or '!'
 498  ## are treated as things to be excluded.
 499  function GlobToPCRE($pat) {
 500    $pat = preg_quote($pat, '/');
 501    $pat = str_replace(array('\\*', '\\?', '\\[', '\\]', '\\^', '\\-', '\\!'),
 502                       array('.*',  '.',   '[',   ']',   '^', '-', '!'), $pat);
 503    $excl = array(); $incl = array();
 504    foreach(preg_split('/,+\s?/', $pat, -1, PREG_SPLIT_NO_EMPTY) as $p) {
 505      if ($p{0} == '-' || $p{0} == '!') $excl[] = '^'.substr($p, 1).'$';
 506      else $incl[] = "^$p$";
 507    }
 508    return array(implode('|', $incl), implode('|', $excl));
 509  }
 510  
 511  ## FixGlob changes wildcard patterns without '.' to things like
 512  ## '*.foo' (name matches) or 'foo.*' (group matches).
 513  function FixGlob($x, $rep = '$1*.$2') {
 514    return preg_replace('/([\\s,][-!]?)([^\\/.\\s,]+)(?=[\\s,])/', $rep, ",$x,");
 515  }
 516  
 517  ## MatchPageNames reduces $pagelist to those pages with names
 518  ## matching the pattern(s) in $pat.  Patterns can be either
 519  ## regexes to include ('/'), regexes to exclude ('!'), or
 520  ## wildcard patterns (all others).
 521  function MatchPageNames($pagelist, $pat) {
 522    global $Charset, $EnableRangeMatchUTF8;
 523    # allow range matches in utf8; doesn't work on pmwiki.org and possibly elsewhere
 524    $pcre8 = (IsEnabled($EnableRangeMatchUTF8,0) && $Charset=='UTF-8')? 'u' : '';
 525    $pagelist = (array)$pagelist;
 526    foreach((array)$pat as $p) {
 527      if (count($pagelist) < 1) break;
 528      if (!$p) continue;
 529      switch ($p{0}) {
 530        case '/': 
 531          $pagelist = preg_grep($p, $pagelist); 
 532          continue;
 533        case '!':
 534          $pagelist = array_diff($pagelist, preg_grep($p, $pagelist)); 
 535          continue;
 536        default:
 537          list($inclp, $exclp) = GlobToPCRE(str_replace('/', '.', $p));
 538          if ($exclp) 
 539            $pagelist = array_diff($pagelist, preg_grep("/$exclp/i$pcre8", $pagelist));
 540          if ($inclp)
 541            $pagelist = preg_grep("/$inclp/i$pcre8", $pagelist);
 542      }
 543    }
 544    return $pagelist;
 545  }
 546    
 547  ## ResolvePageName "normalizes" a pagename based on the current
 548  ## settings of $DefaultPage and $PagePathFmt.  It's normally used
 549  ## during initialization to fix up any missing or partial pagenames.
 550  function ResolvePageName($pagename) {
 551    global $DefaultPage, $DefaultGroup, $DefaultName,
 552      $GroupPattern, $NamePattern, $EnableFixedUrlRedirect;
 553    SDV($DefaultPage, "$DefaultGroup.$DefaultName");
 554    $pagename = preg_replace('!([./][^./]+)\\.html$!', '$1', $pagename);
 555    if ($pagename == '') return $DefaultPage;
 556    $p = MakePageName($DefaultPage, $pagename);
 557    if (!preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $p)) {
 558      header('HTTP/1.1 404 Not Found');
 559      Abort('$[?invalid page name]');
 560    }
 561    if (preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $pagename))
 562      return $p;
 563    if (IsEnabled($EnableFixedUrlRedirect, 1)
 564        && $p && (PageExists($p) || preg_match('/[\\/.]/', $pagename)))
 565      { Redirect($p); exit(); }
 566    return MakePageName($DefaultPage, "$pagename.$pagename");
 567  }
 568  
 569  ## MakePageName is used to convert a string $str into a fully-qualified
 570  ## pagename.  If $str doesn't contain a group qualifier, then 
 571  ## MakePageName uses $basepage and $PagePathFmt to determine the 
 572  ## group of the returned pagename.
 573  function MakePageName($basepage, $str) {
 574    global $MakePageNameFunction, $PageNameChars, $PagePathFmt,
 575      $MakePageNamePatterns;
 576    if (@$MakePageNameFunction) return $MakePageNameFunction($basepage, $str);
 577    SDV($PageNameChars,'-[:alnum:]');
 578    SDV($MakePageNamePatterns, array(
 579      "/'/" => '',               # strip single-quotes
 580      "/[^$PageNameChars]+/" => ' ',         # convert everything else to space
 581      '/((^|[^-\\w])\\w)/e' => "strtoupper('$1')",
 582      '/ /' => ''));
 583    $str = preg_replace('/[#?].*$/', '', $str);
 584    $m = preg_split('/[.\\/]/', $str);
 585    if (count($m)<1 || count($m)>2 || $m[0]=='') return '';
 586    ##  handle "Group.Name" conversions
 587    if (@$m[1] > '') {
 588      $group = preg_replace(array_keys($MakePageNamePatterns),
 589                 array_values($MakePageNamePatterns), $m[0]);
 590      $name = preg_replace(array_keys($MakePageNamePatterns),
 591                array_values($MakePageNamePatterns), $m[1]);
 592      return "$group.$name";
 593    }
 594    $name = preg_replace(array_keys($MakePageNamePatterns),
 595              array_values($MakePageNamePatterns), $m[0]);
 596    $isgrouphome = count($m) > 1;
 597    foreach((array)$PagePathFmt as $pg) {
 598      if ($isgrouphome && strncmp($pg, '$1.', 3) !== 0) continue;
 599      $pn = FmtPageName(str_replace('$1', $name, $pg), $basepage);
 600      if (PageExists($pn)) return $pn;
 601    }
 602    if ($isgrouphome) {
 603      foreach((array)$PagePathFmt as $pg) 
 604        if (strncmp($pg, '$1.', 3) == 0)
 605          return FmtPageName(str_replace('$1', $name, $pg), $basepage);
 606      return "$name.$name";
 607    }
 608    return preg_replace('/[^\\/.]+$/', $name, $basepage);
 609  }
 610  
 611  ## MakeBaseName uses $BaseNamePatterns to return the "base" form
 612  ## of a given pagename -- i.e., stripping any recipe-defined
 613  ## prefixes or suffixes from the page.
 614  function MakeBaseName($pagename, $patlist = NULL) {
 615    global $BaseNamePatterns;
 616    if (is_null($patlist)) $patlist = (array)@$BaseNamePatterns;
 617    foreach($patlist as $pat => $rep) 
 618      $pagename = preg_replace($pat, $rep, $pagename);
 619    return $pagename;
 620  }
 621  
 622  ## PCache caches basic information about a page and its attributes--
 623  ## usually everything except page text and page history.  This makes
 624  ## for quicker access to certain values in PageVar below.
 625  function PCache($pagename, $page) {
 626    global $PCache;
 627    foreach($page as $k=>$v) 
 628      if ($k!='text' && strpos($k,':')===false) $PCache[$pagename][$k]=$v;
 629  }
 630  
 631  ## SetProperty saves a page property into $PCache.  For convenience
 632  ## it returns the $value of the property just set.  If $sep is supplied,
 633  ## then $value is appended to the current property (with $sep as
 634  ## as separator) instead of replacing it. If $keep is suplied and the 
 635  ## property already exists, then $value will be ignored.
 636  function SetProperty($pagename, $prop, $value, $sep=NULL, $keep=NULL) {
 637    global $PCache, $KeepToken;
 638    NoCache();
 639    $prop = "=p_$prop";
 640    $value = preg_replace("/$KeepToken(\\d.*?)$KeepToken/e", 
 641                          "\$GLOBALS['KPV']['$1']", $value);
 642    if (!is_null($sep) && isset($PCache[$pagename][$prop]))
 643      $value = $PCache[$pagename][$prop] . $sep . $value;
 644    if (is_null($keep) || !isset($PCache[$pagename][$prop]))
 645    $PCache[$pagename][$prop] = $value;
 646    return $PCache[$pagename][$prop];
 647  }
 648  
 649  ## PageTextVar loads a page's text variables (defined by
 650  ## $PageTextVarPatterns) into a page's $PCache entry, and returns
 651  ## the property associated with $var.
 652  function PageTextVar($pagename, $var) {
 653    global $PCache, $PageTextVarPatterns, $MaxPageTextVars;
 654    SDV($MaxPageTextVars, 500);
 655    static $status;
 656    if(@$status["$pagename:$var"]++ > $MaxPageTextVars) return '';
 657    if (!@$PCache[$pagename]['=pagetextvars']) {
 658      $pc = &$PCache[$pagename];
 659      $pc['=pagetextvars'] = 1;
 660      $page = RetrieveAuthPage($pagename, 'read', false, READPAGE_CURRENT);
 661      if ($page) {
 662        foreach((array)$PageTextVarPatterns as $pat) 
 663          if (preg_match_all($pat, IsEnabled($PCache[$pagename]['=preview'],@$page['text']), 
 664            $match, PREG_SET_ORDER))
 665            foreach($match as $m) {
 666              $t = preg_replace("/\\{\\$:{$m[2]}\\}/", '', $m[3]);
 667              $pc["=p_{$m[2]}"] = Qualify($pagename, $t);
 668            }
 669      }
 670    }
 671    return @$PCache[$pagename]["=p_$var"];
 672  }
 673  
 674  function PageVar($pagename, $var, $pn = '') {
 675    global $Cursor, $PCache, $FmtPV, $AsSpacedFunction, $ScriptUrl,
 676      $EnablePathInfo, $DefaultGroup;
 677    if ($var == '$ScriptUrl') return PUE($ScriptUrl);
 678    if ($pn) {
 679      $pn = isset($Cursor[$pn]) ? $Cursor[$pn] : MakePageName($pagename, $pn);
 680    } else $pn = $pagename;
 681    if ($pn) {
 682      // tb
 683      if (!strpos($pn,".")) $pn = "$DefaultGroup.$pn";
 684      if (preg_match('/^(.+)[.\\/]([^.\\/]+)$/', $pn, $match)
 685          && !isset($PCache[$pn]['time']) 
 686          && (!@$FmtPV[$var] || strpos($FmtPV[$var], '$page') !== false)) {
 687        $page = ReadPage($pn, READPAGE_CURRENT);
 688        PCache($pn, $page);
 689        }
 690      @list($d, $group, $name) = $match;
 691      $page = &$PCache[$pn];
 692      if(strpos($FmtPV[$var], '$authpage') !== false) {
 693        if(!isset($page['=auth']['read'])) {
 694          $x = RetrieveAuthPage($pn, 'read', false, READPAGE_CURRENT);
 695          if($x) PCache($pn, $x);
 696        }
 697        if(@$page['=auth']['read']) $authpage = &$page;
 698      }
 699    } else { $group = ''; $name = ''; }
 700    if (@$FmtPV[$var]) return eval("return ({$FmtPV[$var]});");
 701    if (strncmp($var, '$:', 2)==0) return PageTextVar($pn, substr($var, 2));
 702    return '';
 703  }
 704  
 705    
 706  ## FmtPageName handles $[internationalization] and $Variable 
 707  ## substitutions in strings based on the $pagename argument.
 708  function FmtPageName($fmt, $pagename) {
 709    # Perform $-substitutions on $fmt relative to page given by $pagename
 710    global $GroupPattern, $NamePattern, $EnablePathInfo, $ScriptUrl,
 711      $GCount, $UnsafeGlobals, $FmtV, $FmtP, $FmtPV, $PCache, $AsSpacedFunction;
 712    if (strpos($fmt,'$')===false) return $fmt;
 713    $fmt = preg_replace('/\\$([A-Z]\\w*Fmt)\\b/e','$GLOBALS[\'$1\']',$fmt);
 714    $fmt = preg_replace('/\\$\\[(?>([^\\]]+))\\]/e',"XL(PSS('$1'))",$fmt);
 715    $fmt = str_replace('{$ScriptUrl}', '$ScriptUrl', $fmt);
 716    $fmt = 
 717      preg_replace('/\\{(\\$[A-Z]\\w+)\\}/e', "PageVar(\$pagename, '$1')", $fmt);
 718    if (strpos($fmt,'$')===false) return $fmt;
 719    if ($FmtP) $fmt = preg_replace(array_keys($FmtP), array_values($FmtP), $fmt);
 720    static $pv, $pvpat;
 721    if ($pv != count($FmtPV)) {
 722      $pvpat = str_replace('$', '\\$', implode('|', array_keys($FmtPV)));
 723      $pv = count($FmtPV);
 724    }
 725    $fmt = preg_replace("/(?:$pvpat)\\b/e", "PageVar(\$pagename, '$0')", $fmt);
 726    $fmt = preg_replace('!\\$ScriptUrl/([^?#\'"\\s<>]+)!e', 
 727      (@$EnablePathInfo) ? "'$ScriptUrl/'.PUE('$1')" :
 728          "'$ScriptUrl?n='.str_replace('/','.',PUE('$1'))",
 729      $fmt);
 730    if (strpos($fmt,'$')===false) return $fmt;
 731    static $g;
 732    if ($GCount != count($GLOBALS)+count($FmtV)) {
 733      $g = array();
 734      foreach($GLOBALS as $n=>$v) {
 735        if (is_array($v) || is_object($v) ||
 736           isset($FmtV["\$$n"]) || in_array($n,$UnsafeGlobals)) continue;
 737        $g["\$$n"] = $v;
 738      }
 739      $GCount = count($GLOBALS)+count($FmtV);
 740      krsort($g); reset($g);
 741    }
 742    $fmt = str_replace(array_keys($g),array_values($g),$fmt);
 743    $fmt = preg_replace('/(?>(\\$[[:alpha:]]\\w+))/e', 
 744            "isset(\$FmtV['$1']) ? \$FmtV['$1'] : '$1'", $fmt); 
 745    return $fmt;
 746  }
 747  
 748  ## FmtPageTitle returns the page title, or the page name
 749  ## It localizes standard technical pages (RecentChanges...)
 750  function FmtPageTitle($title, $name, $spaced=0) {
 751    if($title>'') return str_replace("$", "&#036;", $title);
 752    global $SpaceWikiWords, $AsSpacedFunction;
 753    if(preg_match("/^(Site(Admin)?
 754      |(All)?(Site|Group)(Header|Footer|Attributes)
 755      |(Side|Left|Right)Bar
 756      |(Wiki)?Sand[Bb]ox
 757      |(All)?Recent(Changes|Uploads)|(Auth|Edit)Form
 758      |InterMap|PageActions|\\w+QuickReference|\\w+Templates
 759      |NotifyList|AuthUser|ApprovedUrls|(Block|Auth)List
 760      )$/x", $name) && $name != XL($name))
 761        return XL($name);
 762    return ($spaced || $SpaceWikiWords) ? $AsSpacedFunction($name) : $name;
 763  }
 764  
 765  ##  FmtTemplateVars uses $vars to replace all occurrences of 
 766  ##  {$$key} in $text with $vars['key'].
 767  function FmtTemplateVars($text, $vars, $pagename = NULL) {
 768    global $FmtPV, $EnableUndefinedTemplateVars;
 769    if ($pagename) {
 770      $pat = implode('|', array_map('preg_quote', array_keys($FmtPV)));
 771      $text = preg_replace("/\\{\\$($pat)\\}/e", 
 772                           "PageVar('$pagename', '$1')", $text);
 773    }
 774    foreach(preg_grep('/^[\\w$]/', array_keys($vars)) as $k)
 775      if (!is_array($vars[$k]))
 776        $text = str_replace("{\$\$$k}", $vars[$k], $text);
 777    if(! IsEnabled($EnableUndefinedTemplateVars, 0))
 778      $text = preg_replace("/\\{\\$\\$\\w+\\}/", '', $text);
 779    return $text;
 780  }
 781  
 782  ## The XL functions provide translation tables for $[i18n] strings
 783  ## in FmtPageName().
 784  function XL($key) {
 785    global $XL,$XLLangs;
 786    foreach($XLLangs as $l) if (isset($XL[$l][$key])) return $XL[$l][$key];
 787    return $key;
 788  }
 789  function XLSDV($lang,$a) {
 790    global $XL;
 791    foreach($a as $k=>$v) { if (!isset($XL[$lang][$k])) $XL[$lang][$k]=$v; }
 792  }
 793  function XLPage($lang,$p,$nohtml=false) {
 794    global $TimeFmt,$XLLangs,$FarmD, $EnableXLPageScriptLoad;
 795    $page = ReadPage($p, READPAGE_CURRENT);
 796    if (!$page) return;
 797    $text = preg_replace("/=>\\s*\n/",'=> ',@$page['text']);
 798    foreach(explode("\n",$text) as $l)
 799      if (preg_match('/^\\s*[\'"](.+?)[\'"]\\s*=>\\s*[\'"](.+)[\'"]/',$l,$match))
 800        $xl[stripslashes($match[1])] = stripslashes($match[2]);
 801    if (isset($xl)) {
 802      if (IsEnabled($EnableXLPageScriptLoad, 0) && @$xl['xlpage-i18n']) {
 803        $i18n = preg_replace('/[^-\\w]/','',$xl['xlpage-i18n']);
 804        include_once("$FarmD/scripts/xlpage-$i18n.php");
 805      }
 806      if (@$xl['Locale']) setlocale(LC_ALL,$xl['Locale']);
 807      if (@$xl['TimeFmt']) $TimeFmt=$xl['TimeFmt'];
 808      if (!in_array($lang, $XLLangs)) array_unshift($XLLangs, $lang);
 809      XLSDV($lang,$xl);
 810    }
 811  }
 812  
 813  ## CmpPageAttr is used with uksort to order a page's elements with
 814  ## the latest items first.  This can make some operations more efficient.
 815  function CmpPageAttr($a, $b) {
 816    @list($x, $agmt) = explode(':', $a);
 817    @list($x, $bgmt) = explode(':', $b);
 818    if ($agmt != $bgmt) 
 819      return ($agmt==0 || $bgmt==0) ? $agmt - $bgmt : $bgmt - $agmt;
 820    return strcmp($a, $b);
 821  }
 822  
 823  ## class PageStore holds objects that store pages via the native
 824  ## filesystem.
 825  class PageStore {
 826    var $dirfmt;
 827    var $iswrite;
 828    var $attr;
 829    function PageStore($d='$WorkDir/$FullName', $w=0, $a=NULL) { 
 830      $this->dirfmt = $d; $this->iswrite = $w; $this->attr = (array)$a;
 831      $GLOBALS['PageExistsCache'] = array();
 832    }
 833    function pagefile($pagename) {
 834      global $FarmD;
 835      $dfmt = $this->dirfmt;
 836      if ($pagename > '') {
 837        $pagename = str_replace('/', '.', $pagename);
 838        if ($dfmt == 'wiki.d/{$FullName}')               # optimizations for
 839          return "wiki.d/$pagename";                     # standard locations
 840        if ($dfmt == '$FarmD/wikilib.d/{$FullName}')     # 
 841          return "$FarmD/wikilib.d/$pagename";           #
 842        if ($dfmt == 'wiki.d/{$Group}/{$FullName}')
 843          return preg_replace('/([^.]+).*/', 'wiki.d/$1/$0', $pagename);
 844      }
 845      return FmtPageName($dfmt, $pagename);
 846    }
 847    function read($pagename, $since=0) {
 848      // tb
 849      $pagename = str_replace("_","",$pagename);
 850      $newline = '';
 851      $urlencoded = false;
 852      $pagefile = $this->pagefile($pagename);
 853      // tb
 854      if ($text = sys_allowedpath(dirname($pagefile))) return array("text"=>$text);
 855      if ($pagefile && ($fp=@fopen($pagefile, "r"))) {
 856        $page = $this->attr;
 857        while (!feof($fp)) {
 858          $line = fgets($fp, 4096);
 859          while (substr($line, -1, 1) != "\n" && !feof($fp)) 
 860            { $line .= fgets($fp, 4096); }
 861          $line = rtrim($line);
 862          if ($urlencoded) $line = urldecode(str_replace('+', '%2b', $line));
 863          @list($k,$v) = explode('=', $line, 2);
 864          if (!$k) continue;
 865          if ($k == 'version') { 
 866            $ordered = (strpos($v, 'ordered=1') !== false); 
 867            $urlencoded = (strpos($v, 'urlencoded=1') !== false); 
 868            if (strpos($v, 'pmwiki-0.')!==false) $newline="\262";
 869          }
 870          if ($k == 'newline') { $newline = $v; continue; }
 871          if ($since > 0 && preg_match('/:(\\d+)/', $k, $m) && $m[1] < $since) {
 872            if ($ordered) break;
 873            continue;
 874          }
 875          if ($newline) $v = str_replace($newline, "\n", $v);
 876          $page[$k] = $v;
 877        }
 878        fclose($fp);
 879      }
 880      return $this->recode($pagename, @$page);
 881    }
 882    function write($pagename,$page) {
 883      global $Now, $Version, $Charset;
 884      $page['charset'] = $Charset;
 885      $page['name'] = $pagename;
 886      $page['time'] = $Now;
 887      $page['host'] = $_SERVER['REMOTE_ADDR'];
 888      $page['agent'] = @$_SERVER['HTTP_USER_AGENT'];
 889      $page['rev'] = @$page['rev']+1;
 890      unset($page['version']); unset($page['newline']);
 891      uksort($page, 'CmpPageAttr');
 892      $s = false;
 893      $pagefile = $this->pagefile($pagename);
 894      // tb
 895      if (sys_allowedpath(dirname($pagefile))!="") return false;
 896      $dir = dirname($pagefile); mkdirp($dir);
 897      if (!file_exists("$dir/.htaccess") && $fp = @fopen("$dir/.htaccess", "w")) 
 898        { fwrite($fp, "Order Deny,Allow\nDeny from all\n"); fclose($fp); }
 899      if ($pagefile && ($fp=fopen("$pagefile,new","w"))) {
 900        $r0 = array('%', "\n", '<');
 901        $r1 = array('%25', '%0a', '%3c');
 902        $x = "version=$Version ordered=1 urlencoded=1\n";
 903        $s = true && fputs($fp, $x); $sz = strlen($x);
 904        foreach($page as $k=>$v) 
 905          if ($k > '' && $k{0} != '=') {
 906            $x = str_replace($r0, $r1, "$k=$v") . "\n";
 907            $s = $s && fputs($fp, $x); $sz += strlen($x);
 908          }
 909        $s = fclose($fp) && $s;
 910        $s = $s && (filesize("$pagefile,new") > $sz * 0.95);
 911        if (file_exists($pagefile)) $s = $s && unlink($pagefile);
 912        $s = $s && rename("$pagefile,new", $pagefile);
 913      }
 914      $s && fixperms($pagefile);
 915      if (!$s)
 916        Abort("Cannot write page to $pagename ($pagefile)...changes not saved");
 917      PCache($pagename, $page);
 918    }
 919    function exists($pagename) {
 920      if (!$pagename) return false;
 921      $pagefile = $this->pagefile($pagename);
 922      return ($pagefile && file_exists($pagefile));
 923    }
 924    function delete($pagename) {
 925      global $Now;
 926      $pagefile = $this->pagefile($pagename);
 927      @rename($pagefile,"$pagefile,del-$Now");
 928    }
 929    function ls($pats=NULL) {
 930      global $GroupPattern, $NamePattern;
 931      StopWatch("PageStore::ls begin {$this->dirfmt}");
 932      $pats=(array)$pats; 
 933      array_push($pats, "/^$GroupPattern\.$NamePattern$/");
 934      $dir = $this->pagefile('$Group.$Name');
 935      $maxslash = substr_count($dir, '/');
 936      $dirlist = array(preg_replace('!/*[^/]*\\$.*$!','',$dir));
 937      $out = array();
 938      while (count($dirlist)>0) {
 939        $dir = array_shift($dirlist);
 940        $dfp = @opendir($dir); if (!$dfp) { continue; }
 941        $dirslash = substr_count($dir, '/') + 1;
 942        $o = array();
 943        while ( ($pagefile = readdir($dfp)) !== false) {
 944          if ($pagefile{0} == '.') continue;
 945          if ($dirslash < $maxslash && is_dir("$dir/$pagefile"))
 946            { array_push($dirlist,"$dir/$pagefile"); continue; }
 947          if ($dirslash == $maxslash) $o[] = $pagefile;
 948        }
 949        closedir($dfp);
 950        StopWatch("PageStore::ls merge {$this->dirfmt}");
 951        $out = array_merge($out, MatchPageNames($o, $pats));
 952      }
 953      StopWatch("PageStore::ls end {$this->dirfmt}");
 954      return $out;
 955    }
 956    function recode($pagename, $a) {
 957      if(!$a) return false;
 958      global $Charset, $PageRecodeFunction, $DefaultPageCharset;
 959      if (function_exists($PageRecodeFunction)) return $PageRecodeFunction($a);
 960      SDVA($DefaultPageCharset, array(''=>$Charset)); # pre-2.2.31 RecentChanges
 961      if (@$DefaultPageCharset[$a['charset']]>'')  # wrong pre-2.2.30 encs. *-2, *-9, *-13
 962        $a['charset'] = $DefaultPageCharset[$a['charset']];
 963      if (!$a['charset'] || $Charset==$a['charset']) return $a;
 964      if ($Charset=='ISO-8859-1' && $a['charset']=='UTF-8') $F = 'utf8_decode'; # 2.2.31+ documentation
 965      elseif ($Charset=='UTF-8' && $a['charset']=='ISO-8859-1') $F = 'utf8_encode'; # utf8 wiki & pre-2.2.30 doc
 966      elseif (function_exists('iconv'))
 967        $F = create_function('$s', "return iconv('{$a['charset']}', '$Charset//IGNORE', \$s);");
 968      elseif (function_exists('mb_convert_encoding'))
 969        $F = create_function('$s', "return mb_convert_encoding(\$s, '$Charset', '{$a['charset']}');");
 970      else return $a;
 971      foreach($a as $k=>$v) $a[$k] = $F($v);
 972      $a['charset'] = $Charset;
 973      return $a;
 974    }
 975  }
 976  
 977  function ReadPage($pagename, $since=0) {
 978    # read a page from the appropriate directories given by $WikiReadDirsFmt.
 979    global $WikiLibDirs,$Now;
 980    foreach ($WikiLibDirs as $dir) {
 981      $page = $dir->read($pagename, $since);
 982      if ($page) break;
 983    }
 984    if (@!$page) $page['ctime'] = $Now;
 985    if (@!$page['time']) $page['time'] = $Now;
 986    return $page;
 987  }
 988  
 989  function WritePage($pagename,$page) {
 990    global $WikiLibDirs,$WikiDir,$LastModFile;
 991    $WikiDir->iswrite = 1;
 992    for($i=0; $i<count($WikiLibDirs); $i++) {
 993      $wd = &$WikiLibDirs[$i];
 994      if ($wd->iswrite && $wd->exists($pagename)) break;
 995    }
 996    if ($i >= count($WikiLibDirs)) $wd = &$WikiDir;
 997    $wd->write($pagename,$page);
 998    if ($LastModFile && !@touch($LastModFile)) 
 999      { unlink($LastModFile); touch($LastModFile); fixperms($LastModFile); }
1000  }
1001  
1002  function PageExists($pagename) {
1003    ##  note:  $PageExistsCache might change or disappear someday
1004    global $WikiLibDirs, $PageExistsCache;
1005    if (!isset($PageExistsCache[$pagename])) {
1006      foreach((array)$WikiLibDirs as $dir)
1007        if ($PageExistsCache[$pagename] = $dir->exists($pagename)) break;
1008    }
1009    return $PageExistsCache[$pagename];
1010  }
1011  
1012  function ListPages($pat=NULL) {
1013    global $WikiLibDirs;
1014    foreach((array)$WikiLibDirs as $dir) 
1015      $out = array_unique(array_merge($dir->ls($pat),(array)@$out));
1016    return $out;
1017  }
1018  
1019  function RetrieveAuthPage($pagename, $level, $authprompt=true, $since=0) {
1020    global $AuthFunction;
1021    SDV($AuthFunction,'PmWikiAuth');
1022    if (!function_exists($AuthFunction)) return ReadPage($pagename, $since);
1023    return $AuthFunction($pagename, $level, $authprompt, $since);
1024  }
1025  
1026  function Abort($msg, $info='') {
1027    # exit pmwiki with an abort message
1028    global $ScriptUrl, $Charset;
1029    if ($info) 
1030      $info = "<p class='vspace'><a target='_blank' rel='nofollow' href='http://www.pmwiki.org/pmwiki/info/$info'>$[More information]</a></p>";
1031    $msg = "<h3>$[PmWiki can't process your request]</h3>
1032      <p class='vspace'>$msg</p>
1033      <p class='vspace'>$[We are sorry for any inconvenience].</p>
1034      $info
1035      <p class='vspace'><a href='$ScriptUrl'>$[Return to] $ScriptUrl</a></p>";
1036    @header("Content-type: text/html; charset=$Charset");
1037    echo preg_replace('/\\$\\[([^\\]]+)\\]/e', "XL(PSS('$1'))", $msg);
1038    exit;
1039  }
1040  
1041  function Redirect($pagename, $urlfmt='$PageUrl') {
1042    # redirect the browser to $pagename
1043    global $EnableRedirect, $RedirectDelay, $EnableStopWatch;
1044    SDV($RedirectDelay, 0);
1045    clearstatcache();
1046    $pageurl = FmtPageName($urlfmt,$pagename);
1047    if (IsEnabled($EnableRedirect,1) && 
1048        (!isset($_REQUEST['redirect']) || $_REQUEST['redirect'])) {
1049      header("Location: $pageurl");
1050      header("Content-type: text/html");
1051      echo "<html><head>
1052        <meta http-equiv='Refresh' Content='$RedirectDelay; URL=$pageurl' />
1053       <title>Redirect</title></head><body></body></html>";
1054       exit;
1055    }
1056    echo "<a href='$pageurl'>Redirect to $pageurl</a>";
1057    if (@$EnableStopWatch && function_exists('StopWatchHTML'))
1058      StopWatchHTML($pagename, 1);
1059    exit;
1060  }
1061  
1062  function PrintFmt($pagename,$fmt) {
1063    global $HTTPHeaders,$FmtV;
1064    if (is_array($fmt)) 
1065      { foreach($fmt as $f) PrintFmt($pagename,$f); return; }
1066    if ($fmt == 'headers:') {
1067      foreach($HTTPHeaders as $h) (@$sent++) ? @header($h) : header($h);
1068      return;
1069    }
1070    $x = FmtPageName($fmt,$pagename);
1071    if (strncmp($fmt, 'function:', 9) == 0 &&
1072        preg_match('/^function:(\S+)\s*(.*)$/s', $x, $match) &&
1073        function_exists($match[1]))
1074      { $match[1]($pagename,$match[2]); return; }
1075    if (strncmp($fmt, 'file:', 5) == 0 && preg_match("/^file:(.+)/s",$x,$match)) {
1076      $filelist = preg_split('/[\\s]+/',$match[1],-1,PREG_SPLIT_NO_EMPTY);
1077      foreach($filelist as $f) {
1078        if (file_exists($f)) { include($f); return; }
1079      }
1080      return;
1081    }
1082    if (substr($x, 0, 7) == 'markup:')
1083      { print MarkupToHTML($pagename, substr($x, 7)); return; }
1084    if (substr($x, 0, 5) == 'wiki:')
1085      { PrintWikiPage($pagename, substr($x, 5), 'read'); return; }
1086    if (substr($x, 0, 5) == 'page:')
1087      { PrintWikiPage($pagename, substr($x, 5), ''); return; }
1088    echo $x;
1089  }
1090  
1091  function PrintWikiPage($pagename, $wikilist=NULL, $auth='read') {
1092    if (is_null($wikilist)) $wikilist=$pagename;
1093    $pagelist = preg_split('/\s+/',$wikilist,-1,PREG_SPLIT_NO_EMPTY);
1094    foreach($pagelist as $p) {
1095      if (PageExists($p)) {
1096        $page = ($auth) ? RetrieveAuthPage($p, $auth, false, READPAGE_CURRENT)
1097                : ReadPage($p, READPAGE_CURRENT);
1098        if ($page['text']) 
1099          echo MarkupToHTML($pagename,Qualify($p, $page['text']));
1100        return;
1101      }
1102    }
1103  }
1104  
1105  function Keep($x, $pool=NULL) {
1106    # Keep preserves a string from being processed by wiki markups
1107    global $BlockPattern, $KeepToken, $KPV, $KPCount;
1108    $x = preg_replace("/$KeepToken(\\d.*?)$KeepToken/e", "\$KPV['\$1']", $x);
1109    if (is_null($pool) && preg_match("!</?($BlockPattern)\\b!", $x)) $pool = 'B';
1110    $KPCount++; $KPV[$KPCount.$pool]=$x;
1111    return $KeepToken.$KPCount.$pool.$KeepToken;
1112  }
1113  
1114  ##  MarkupEscape examines markup source and escapes any [@...@]
1115  ##  and [=...=] sequences using Keep().  MarkupRestore undoes the
1116  ##  effect of any MarkupEscape().
1117  function MarkupEscape($text) {
1118    global $EscapePattern;
1119    SDV($EscapePattern, '\\[([=@]).*?\\1\\]');
1120    return preg_replace("/$EscapePattern/es", "Keep(PSS('$0'))", $text);
1121  }
1122  function MarkupRestore($text) {
1123    global $KeepToken, $KPV;
1124    return preg_replace("/$KeepToken(\\d.*?)$KeepToken/e", "\$KPV['$1']", $text);
1125  }
1126  
1127  ##  Qualify() applies $QualifyPatterns to convert relative links
1128  ##  and references into absolute equivalents.
1129  function Qualify($pagename, $text) {
1130    global $QualifyPatterns, $KeepToken, $KPV;
1131    if (!@$QualifyPatterns) return $text;
1132    $text = MarkupEscape($text);
1133    $group = PageVar($pagename, '$Group');
1134    $name = PageVar($pagename, '$Name');
1135    foreach((array)$QualifyPatterns as $pat => $rep) 
1136      $text = preg_replace($pat, $rep, $text);
1137    return MarkupRestore($text);
1138  }
1139  
1140  function CondText($pagename,$condspec,$condtext) {
1141    global $Conditions;
1142    if (!preg_match("/^(\\S+)\\s*(!?)\\s*(\\S+)?\\s*(.*?)\\s*$/",
1143      $condspec,$match)) return '';
1144    @list($condstr,$condtype,$not,$condname,$condparm) = $match;
1145    if (isset($Conditions[$condname])) {
1146      $tf = @eval("return (".$Conditions[$condname].");");
1147      if (!$tf xor $not) $condtext='';
1148    }
1149    return $condtext;
1150  }
1151  
1152  ##  TextSection extracts a section of text delimited by page anchors.
1153  ##  The $sections parameter can have the form
1154  ##    #abc           - [[#abc]] to next anchor
1155  ##    #abc#def       - [[#abc]] up to [[#def]]
1156  ##    #abc#, #abc..  - [[#abc]] to end of text
1157  ##    ##abc, #..#abc - beginning of text to [[#abc]]
1158  ##  Returns the text unchanged if no sections are requested,
1159  ##  or false if a requested beginning anchor isn't in the text.
1160  function TextSection($text, $sections, $args = NULL) {
1161    $args = (array)$args;
1162    $npat = '[[:alpha:]][-\\w*]*';
1163    if (!preg_match("/#($npat)?(\\.\\.)?(#($npat)?)?/", $sections, $match))
1164      return $text;
1165    @list($x, $aa, $dots, $b, $bb) = $match;
1166    if (!$dots && !$b) $bb = $npat;
1167    if ($aa) {
1168      $pos = strpos($text, "[[#$aa]]");  if ($pos === false) return false;
1169      if (@$args['anchors']) 
1170        while ($pos > 0 && $text[$pos-1] != "\n") $pos--;
1171      else $pos += strlen("[[#$aa]]");
1172      $text = substr($text, $pos);
1173    }
1174    if ($bb)
1175      $text = preg_replace("/(\n)[^\n]*\\[\\[#$bb\\]\\].*$/s", '$1', $text, 1);
1176    return $text;
1177  }
1178  
1179  ##  RetrieveAuthSection extracts a section of text from a page.
1180  ##  If $pagesection starts with anything other than '#', it identifies
1181  ##  the page to extract text from.  Otherwise RetrieveAuthSection looks
1182  ##  in the pages given by $list, or in $pagename if $list is not specified.
1183  ##  The selected page is placed in the global $RASPageName variable.
1184  ##  The caller is responsible for calling Qualify() as needed.
1185  function RetrieveAuthSection($pagename, $pagesection, $list=NULL, $auth='read') {
1186    global $RASPageName, $PCache;
1187    if ($pagesection{0} != '#')
1188      $list = array(MakePageName($pagename, $pagesection));
1189    else if (is_null($list)) $list = array($pagename);
1190    foreach((array)$list as $t) {
1191      $t = FmtPageName($t, $pagename);
1192      if (!PageExists($t)) continue;
1193      $tpage = RetrieveAuthPage($t, $auth, false, READPAGE_CURRENT);
1194      if (!$tpage) continue;
1195      $text = TextSection(IsEnabled($PCache[$t]['=preview'],$tpage['text']),$pagesection);
1196      if ($text !== false) { $RASPageName = $t; return $text; }
1197    }
1198    $RASPageName = '';
1199    return false;
1200  }
1201  
1202  function IncludeText($pagename, $inclspec) {
1203    global $MaxIncludes, $IncludeOpt, $InclCount, $PCache;
1204    SDV($MaxIncludes,50);
1205    SDVA($IncludeOpt, array('self'=>1));
1206    $npat = '[[:alpha:]][-\\w]*';
1207    if ($InclCount++>=$MaxIncludes) return Keep($inclspec);
1208    $args = array_merge($IncludeOpt, ParseArgs($inclspec));
1209    while (count($args['#'])>0) {
1210      $k = array_shift($args['#']); $v = array_shift($args['#']);
1211      if ($k=='') {
1212        if ($v{0} != '#') {
1213          if (isset($itext)) continue;
1214          $iname = MakePageName($pagename, $v);
1215          if (!$args['self'] && $iname == $pagename) continue;
1216          $ipage = RetrieveAuthPage($iname, 'read', false, READPAGE_CURRENT);
1217          $itext = IsEnabled($PCache[$iname]['=preview'], @$ipage['text']);
1218        }
1219        $itext = TextSection($itext, $v, array('anchors' => 1));
1220        continue;
1221      }
1222      if (preg_match('/^(?:line|para)s?$/', $k)) {
1223        preg_match('/^(\\d*)(\\.\\.(\\d*))?$/', $v, $match);
1224        @list($x, $a, $dots, $b) = $match;
1225        $upat = ($k{0} == 'p') ? ".*?(\n\\s*\n|$)" : "[^\n]*(?:\n|$)";
1226        if (!$dots) { $b=$a; $a=0; }
1227        if ($a>0) $a--;
1228        $itext=preg_replace("/^(($upat){0,$b}).*$/s",'$1',$itext,1);
1229        $itext=preg_replace("/^($upat){0,$a}/s",'',$itext,1);
1230        continue;
1231      }
1232    }
1233    $basepage = isset($args['basepage']) 
1234                ? MakePageName($pagename, $args['basepage'])
1235                : $iname;
1236    if ($basepage) $itext = Qualify(@$basepage, @$itext);
1237    return FmtTemplateVars(PVSE($itext), $args);
1238  }
1239  
1240  function RedirectMarkup($pagename, $opt) {
1241    $k = Keep("(:redirect $opt:)");
1242    global $MarkupFrame, $EnableRedirectQuiet;
1243    if (!@$MarkupFrame[0]['redirect']) return $k;
1244    $opt = ParseArgs($opt);
1245    $to = @$opt['to']; if (!$to) $to = @$opt[''][0];
1246    if (!$to) return $k;
1247    if (preg_match('/^([^#]+)(#[A-Za-z][-\\w]*)$/', $to, $match)) 
1248      { $to = $match[1]; $anchor = @$match[2]; }
1249    $to = MakePageName($pagename, $to);
1250    if (!PageExists($to)) return $k;
1251    if ($to == $pagename) return '';
1252    if (@$opt['from'] 
1253        && !MatchPageNames($pagename, FixGlob($opt['from'], '$1*.$2')))
1254      return '';
1255    if (preg_match('/^30[1237]$/', @$opt['status'])) 
1256       header("HTTP/1.1 {$opt['status']}");
1257    Redirect($to, "{\$PageUrl}"
1258      . (IsEnabled($EnableRedirectQuiet, 0) && IsEnabled($opt['quiet'], 0)
1259        ? '' : "?from=$pagename")
1260      . $anchor);
1261    exit();
1262  }
1263     
1264  
1265  function Block($b) {
1266    global $BlockMarkups,$HTMLVSpace,$HTMLPNewline,$MarkupFrame;
1267    $mf = &$MarkupFrame[0]; $cs = &$mf['cs']; $vspaces = &$mf['vs'];
1268    $out = '';
1269    if ($b == 'vspace') {
1270      $vspaces .= "\n";
1271      while (count($cs)>0 && @end($cs)!='pre' && @$BlockMarkups[@end($cs)][3]==0) 
1272        { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
1273      return $out;
1274    }
1275    @list($code, $depth, $icol) = explode(',', $b);
1276    if (!$code) $depth = 1;
1277    if ($depth == 0) $depth = strlen($depth);
1278    if ($icol == 0) $icol = strlen($icol);
1279    if ($depth > 0) $depth += @$mf['idep'];
1280    if ($icol > 0) $mf['is'][$depth] = $icol + @$mf['icol'];
1281    @$mf['idep'] = @$mf['icol'] = 0;
1282    while (count($cs)>$depth) 
1283      { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
1284    if (!$code) {
1285      if (@end($cs) == 'p') { $out .= $HTMLPNewline; $code = 'p'; }
1286      else if ($depth < 2) { $code = 'p'; $mf['is'][$depth] = 0; }
1287      else { $out .= $HTMLPNewline; $code = 'block'; }
1288    }
1289    if ($depth>0 && $depth==count($cs) && $cs[$depth-1]!=$code)
1290      { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
1291    while (count($cs)>0 && @end($cs)!=$code &&
1292        @$BlockMarkups[@end($cs)][3]==0)
1293      { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
1294    if ($vspaces) { 
1295      $out .= (@end($cs) == 'pre') ? $vspaces : $HTMLVSpace; 
1296      $vspaces=''; 
1297    }
1298    if ($depth==0) { return $out; }
1299    if ($depth==count($cs)) { return $out.$BlockMarkups[$code][1]; }
1300    while (count($cs)<$depth-1) { 
1301      array_push($cs, 'dl'); $mf['is'][count($cs)] = 0;
1302      $out .= $BlockMarkups['dl'][0].'<dd>'; 
1303    }
1304    if (count($cs)<$depth) {
1305      array_push($cs,$code);
1306      $out .= $BlockMarkups[$code][0];
1307    }
1308    return $out;
1309  }
1310  
1311  function MarkupClose($key = '') {
1312    global $MarkupFrame;
1313    $cf = & $MarkupFrame[0]['closeall'];
1314    $out = '';
1315    if ($key == '' || isset($cf[$key])) {
1316      $k = array_keys((array)$cf);
1317      while ($k) { 
1318        $x = array_pop($k); $out .= $cf[$x]; unset($cf[$x]);
1319        if ($x == $key) break;
1320      }
1321    }
1322    return $out;
1323  }
1324  
1325  function FormatTableRow($x, $sep = '\\|\\|') {
1326    global $Block, $TableCellAttrFmt, $MarkupFrame, $TableRowAttrFmt, 
1327      $TableRowIndexMax, $FmtV;
1328    static $rowcount;
1329    $x = preg_replace("/$sep\\s*$/",'',$x);
1330    $td = preg_split("/$sep/", $x); $y = '';
1331    for($i=0;$i<count($td);$i++) {
1332      if ($td[$i]=='') continue;
1333      $FmtV['$TableCellCount'] = $i;
1334      $attr = FmtPageName(@$TableCellAttrFmt, '');
1335      $td[$i] = preg_replace('/^(!?)\\s+$/', '$1&nbsp;', $td[$i]);
1336      if (preg_match('/^!(.*?)!$/',$td[$i],$match))
1337        { $td[$i]=$match[1]; $t='caption'; $attr=''; }
1338      elseif (preg_match('/^!(.*)$/',$td[$i],$match)) 
1339        { $td[$i]=$match[1]; $t='th'; }
1340      else $t='td';
1341      if (preg_match('/^\\s.*\\s$/',$td[$i])) { if ($t!='caption') $attr .= " align='center'"; }
1342      elseif (preg_match('/^\\s/',$td[$i])) { $attr .= " align='right'"; }
1343      elseif (preg_match('/\\s$/',$td[$i])) { $attr .= " align='left'"; }
1344      for ($colspan=1;$i+$colspan<count($td);$colspan++) 
1345        if ($td[$colspan+$i]!='') break;
1346      if ($colspan>1) { $attr .= " colspan='$colspan'"; }
1347      $y .= "<$t $attr>".trim($td[$i])."</$t>";
1348    }
1349    if ($t=='caption') return "<:table,1>$y";
1350    if (@$MarkupFrame[0]['cs'][0] != 'table') $rowcount = 0; else $rowcount++;
1351    $FmtV['$TableRowCount'] = $rowcount + 1;
1352    $FmtV['$TableRowIndex'] = ($rowcount % $TableRowIndexMax) + 1;
1353    $trattr = FmtPageName(@$TableRowAttrFmt, '');
1354    return "<:table,1><tr $trattr>$y</tr>";
1355  }
1356  
1357  function LinkIMap($pagename,$imap,$path,$alt,$txt,$fmt=NULL) {
1358    global $FmtV, $IMap, $IMapLinkFmt, $UrlLinkFmt, $IMapLocalPath;
1359    SDVA($IMapLocalPath, array('Path:'=>1));
1360    if(@$IMapLocalPath[$imap]) {
1361      $path = preg_replace('/^\\w+:/e', "urlencode('$0')", $path);
1362    }
1363    $FmtV['$LinkUrl'] = PUE(str_replace('$1',$path,$IMap[$imap]));
1364    $FmtV['$LinkText'] = $txt;
1365    $FmtV['$LinkAlt'] = str_replace(array('"',"'"),array('&#34;','&#39;'),$alt);
1366    if (!$fmt) 
1367      $fmt = (isset($IMapLinkFmt[$imap])) ? $IMapLinkFmt[$imap] : $UrlLinkFmt;
1368    return str_replace(array_keys($FmtV),array_values($FmtV),$fmt);
1369  }
1370  
1371  function LinkPage($pagename,$imap,$path,$alt,$txt,$fmt=NULL) {
1372    global $QueryFragPattern, $LinkPageExistsFmt, $LinkPageSelfFmt,
1373      $LinkPageCreateSpaceFmt, $LinkPageCreateFmt, $LinkTargets,
1374      $EnableLinkPageRelative;
1375    if (!$fmt && $path{0} == '#') {
1376      $path = preg_replace("/[^-.:\\w]/", '', $path);
1377      return ($path) ? "<a href='#$path'>$txt</a>" : '';
1378    }
1379    if (!preg_match("/^\\s*([^#?]+)($QueryFragPattern)?$/",$path,$match))
1380      return '';
1381    $tgtname = MakePageName($pagename, $match[1]); 
1382    if (!$tgtname) return '';
1383    $qf = @$match[2];
1384    // tb
1385    $qf = str_replace('?','&',$qf);
1386    @$LinkTargets[$tgtname]++;
1387    if (!$fmt) {
1388      // tb
1389      if (false && !PageExists($tgtname) && !preg_match('/[&?]action=/', $qf))
1390        $fmt = preg_match('/\\s/', $txt) 
1391               ? $LinkPageCreateSpaceFmt : $LinkPageCreateFmt;
1392      else
1393        $fmt = ($tgtname == $pagename && $qf == '') 
1394               ? $LinkPageSelfFmt : $LinkPageExistsFmt;
1395    }
1396    $url = PageVar($tgtname, '$PageUrl');
1397    if (trim($txt) == '+') $txt = PageVar($tgtname, '$Title');
1398    $txt = str_replace("$", "&#036;", $txt);
1399    $alt = str_replace(array('"',"'"),array('&#34;','&#39;'),$alt);
1400    if (@$EnableLinkPageRelative) 
1401      $url = preg_replace('!^[a-z]+://[^/]*!i', '', $url);
1402    $fmt = str_replace(array('$LinkUrl', '$LinkText', '$LinkAlt'),
1403                       array($url.PUE($qf), $txt, $alt), $fmt);
1404    return FmtPageName($fmt,$tgtname);
1405  }
1406  
1407  function MakeLink($pagename,$tgt,$txt=NULL,$suffix=NULL,$fmt=NULL) {
1408    global $LinkPattern,$LinkFunctions,$UrlExcludeChars,$ImgExtPattern,$ImgTagFmt,
1409      $LinkTitleFunction;
1410    $t = preg_replace('/[()]/','',trim($tgt));
1411    $t = preg_replace('/<[^>]*>/','',$t);
1412    preg_match("/^($LinkPattern)?(.+?)(\"(.*)\")?$/",$t,$m);
1413    if (!$m[1]) $m[1]='<:page>';
1414    if (preg_match("/\"(.*)\"$/",trim($tgt),$x)) $m[4]=$x[1];
1415    if (preg_match("/(($LinkPattern)([^$UrlExcludeChars]+$ImgExtPattern))(\"(.*)\")?$/",$txt,$tm)) 
1416      $txt = $LinkFunctions[$tm[2]]($pagename,$tm[2],$tm[3],@$tm[5],
1417        $tm[1],$ImgTagFmt);
1418    else {
1419      if (is_null($txt)) {
1420        $txt = preg_replace('/\\([^)]*\\)/','',$tgt);
1421        if (@$m[3]) $txt = preg_replace('/"(.*)"(\\s*)$/','$2',$txt);
1422        if ($m[1]=='<:page>') {
1423          $txt = preg_replace('!/\\s*$!', '', $txt);
1424          $txt = preg_replace('!^.*[^<]/!', '', $txt);
1425        }
1426      }
1427      $txt .= $suffix;
1428    }
1429    if (@$LinkTitleFunction) $m[4] = $LinkTitleFunction($pagename,$m,$txt);
1430    $out = $LinkFunctions[$m[1]]($pagename,$m[1],$m[2],@$m[4],$txt,$fmt);
1431    return preg_replace('/(<[^>]+) title=(""|\'\')/', '$1', $out);
1432  }
1433  
1434  function Markup($id, $when, $pat=NULL, $rep=NULL) {
1435    global $MarkupTable;
1436    unset($GLOBALS['MarkupRules']);
1437    if (preg_match('/^([<>])?(.+)$/', $when, $m)) {
1438      $MarkupTable[$id]['cmd'] = $when;
1439      $MarkupTable[$m[2]]['dep'][$id] = $m[1];
1440      if (!$m[1]) $m[1] = '=';
1441      if (@$MarkupTable[$m[2]]['seq']) {
1442        $MarkupTable[$id]['seq'] = $MarkupTable[$m[2]]['seq'].$m[1];
1443        foreach((array)@$MarkupTable[$id]['dep'] as $i=>$m)
1444          Markup($i,"$m$id");
1445        unset($GLOBALS['MarkupTable'][$id]['dep']);
1446      }
1447    }
1448    if ($pat && !isset($MarkupTable[$id]['pat'])) {
1449      $MarkupTable[$id]['pat'] = $pat;
1450      $MarkupTable[$id]['rep'] = $rep;
1451    }
1452  }
1453  
1454  function DisableMarkup() {
1455    global $MarkupTable;
1456    $idlist = func_get_args();
1457    unset($GLOBALS['MarkupRules']);
1458    while (count($idlist)>0) {
1459      $id = array_shift($idlist);
1460      if (is_array($id)) { $idlist = array_merge($idlist, $id); continue; }
1461      $MarkupTable[$id] = array('cmd' => 'none', 'pat'=>'');
1462    }
1463  }
1464      
1465  function mpcmp($a,$b) { return @strcmp($a['seq'].'=',$b['seq'].'='); }
1466  function BuildMarkupRules() {
1467    global $MarkupTable,$MarkupRules,$LinkPattern;
1468    if (!$MarkupRules) {
1469      uasort($MarkupTable,'mpcmp');
1470      foreach($MarkupTable as $id=>$m) 
1471        if (@$m['pat'] && @$m['seq']) 
1472          $MarkupRules[str_replace('\\L',$LinkPattern,$m['pat'])]=$m['rep'];
1473    }
1474    return $MarkupRules;
1475  }
1476  
1477  function MarkupToHTML($pagename, $text, $opt = NULL) {
1478    # convert wiki markup text to HTML output
1479    global $MarkupRules, $MarkupFrame, $MarkupFrameBase, $WikiWordCount,
1480      $K0, $K1, $RedoMarkupLine;
1481  
1482    StopWatch('MarkupToHTML begin');
1483    array_unshift($MarkupFrame, array_merge($MarkupFrameBase, (array)$opt));
1484    $MarkupFrame[0]['wwcount'] = $WikiWordCount;
1485    foreach((array)$text as $l) 
1486      $lines[] = $MarkupFrame[0]['escape'] ? PVSE($l) : $l;
1487    $lines[] = '(:closeall:)';
1488    $out = '';
1489    while (count($lines)>0) {
1490      $x = array_shift($lines);
1491      $RedoMarkupLine=0;
1492      $markrules = BuildMarkupRules();
1493      foreach($markrules as $p=>$r) {
1494        if ($p{0} == '/') $x=preg_replace($p,$r,$x); 
1495        elseif (strstr($x,$p)!==false) $x=eval($r);
1496        if (isset($php_errormsg)) 
1497          { echo "ERROR: pat=$p $php_errormsg"; unset($php_errormsg); }
1498        if ($RedoMarkupLine) { $lines=array_merge((array)$x,$lines); continue 2; }
1499      }
1500      if ($x>'') $out .= "$x\n";
1501    }
1502    foreach((array)(@$MarkupFrame[0]['posteval']) as $v) eval($v);
1503    array_shift($MarkupFrame);
1504    StopWatch('MarkupToHTML end');
1505    return $out;
1506  }
1507     
1508  function HandleBrowse($pagename, $auth = 'read') {
1509    # handle display of a page
1510    global $DefaultPageTextFmt, $PageNotFoundHeaderFmt, $HTTPHeaders,
1511      $EnableHTMLCache, $NoHTMLCache, $PageCacheFile, $LastModTime, $IsHTMLCached,
1512      $FmtV, $HandleBrowseFmt, $PageStartFmt, $PageEndFmt, $PageRedirectFmt;
1513    $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
1514    if (!$page) Abort("?cannot read $pagename");
1515    PCache($pagename,$page);
1516    if (PageExists($pagename)) $text = @$page['text'];
1517    else {
1518      SDV($DefaultPageTextFmt,'(:include $[{$SiteGroup}.PageNotFound]:)');
1519      $text = FmtPageName($DefaultPageTextFmt, $pagename);
1520      SDV($PageNotFoundHeaderFmt, 'HTTP/1.1 404 Not Found');
1521      SDV($HTTPHeaders['status'], $PageNotFoundHeaderFmt);
1522    }
1523    $opt = array();
1524    SDV($PageRedirectFmt,"<p><i>($[redirected from] <a rel='nofollow' 
1525      href='{\$PageUrl}?action=edit'>{\$FullName}</a>)</i></p>\n");
1526    if (@!$_GET['from']) { $opt['redirect'] = 1; $PageRedirectFmt = ''; }
1527    else {
1528      $frompage = MakePageName($pagename, $_GET['from']);
1529      $PageRedirectFmt = (!$frompage) ? ''
1530        : FmtPageName($PageRedirectFmt, $frompage);
1531  }
1532    if (@$EnableHTMLCache && !$NoHTMLCache && $PageCacheFile && 
1533        @filemtime($PageCacheFile) > $LastModTime) {
1534      list($ctext) = unserialize(file_get_contents($PageCacheFile));
1535      $FmtV['$PageText'] = "<!--cached-->$ctext";
1536      $IsHTMLCached = 1;
1537      StopWatch("HandleBrowse: using cached copy");
1538    } else {
1539      $IsHTMLCached = 0;
1540      $text = '(:groupheader:)'.@$text.'(:groupfooter:)';
1541      $t1 = time();
1542      $FmtV['$PageText'] = MarkupToHTML($pagename, $text, $opt);
1543      if (@$EnableHTMLCache > 0 && !$NoHTMLCache && $PageCacheFile
1544          && (time() - $t1 + 1) >= $EnableHTMLCache) {
1545        $fp = @fopen("$PageCacheFile,new", "x");
1546        if ($fp) { 
1547          StopWatch("HandleBrowse: caching page");
1548          fwrite($fp, serialize(array($FmtV['$PageText']))); fclose($fp);
1549          rename("$PageCacheFile,new", $PageCacheFile);
1550        }
1551      }
1552    }
1553    SDV($HandleBrowseFmt,array(&$PageStartFmt, &$PageRedirectFmt, '$PageText',
1554      &$PageEndFmt));
1555    PrintFmt($pagename,$HandleBrowseFmt);
1556  }
1557  
1558  ## UpdatePage goes through all of the steps needed to update a page,
1559  ## preserving page history, computing link targets, page titles, 
1560  ## and other page attributes.  It does this by calling each entry
1561  ## in $EditFunctions.  $pagename is the name of the page to be updated,
1562  ## $page is the old version of the page (used for page history),
1563  ## $new is the new version of the page to be saved, and $fnlist is
1564  ## an optional list of functions to use instead of $EditFunctions.
1565  function UpdatePage(&$pagename, &$page, &$new, $fnlist = NULL) {
1566    global $EditFunctions, $IsPagePosted;
1567    StopWatch("UpdatePage: begin $pagename");
1568    if (is_null($fnlist)) $fnlist = $EditFunctions;
1569    $IsPagePosted = false;
1570    foreach((array)$fnlist as $fn) {
1571      StopWatch("UpdatePage: $fn ($pagename)");
1572      $fn($pagename, $page, $new);
1573    }
1574    StopWatch("UpdatePage: end $pagename");
1575    return $IsPagePosted;
1576  }
1577  
1578  # EditTemplate allows a site administrator to pre-populate new pages
1579  # with the contents of another page.
1580  function EditTemplate($pagename, &$page, &$new) {
1581    global $EditTemplatesFmt;
1582    if (@$new['text'] > '') return;
1583    if (@$_REQUEST['template'] && PageExists($_REQUEST['template'])) {
1584      $p = RetrieveAuthPage($_REQUEST['template'], 'read', false,
1585               READPAGE_CURRENT);
1586      if ($p['text'] > '') $new['text'] = $p['text']; 
1587      return;
1588    }
1589    foreach((array)$EditTemplatesFmt as $t) {
1590      $p = RetrieveAuthPage(FmtPageName($t,$pagename), 'read', false,
1591               READPAGE_CURRENT);
1592      if (@$p['text'] > '') { $new['text'] = $p['text']; return; }
1593    }
1594  }
1595  
1596  # RestorePage handles returning to the version of text as of
1597  # the version given by $restore or $_REQUEST['restore'].
1598  function RestorePage($pagename,&$page,&$new,$restore=NULL) {
1599    if (is_null($restore)) $restore=@$_REQUEST['restore'];
1600    if (!$restore) return;
1601    $t = $page['text'];
1602    $nl = (substr($t,-1)=="\n");
1603    $t = explode("\n",$t);
1604    if ($nl) array_pop($t);
1605    krsort($page); reset($page);
1606    foreach($page as $k=>$v) {
1607      if ($k<$restore) break;
1608      if (strncmp($k, 'diff:', 5) != 0) continue;
1609      foreach(explode("\n",$v) as $x) {
1610        if (preg_match('/^(\\d+)(,(\\d+))?([adc])(\\d+)/',$x,$match)) {
1611          $a1 = $a2 = $match[1];
1612          if ($match[3]) $a2=$match[3];
1613          $b1 = $match[5];
1614          if ($match[4]=='d') array_splice($t,$b1,$a2-$a1+1);
1615          if ($match[4]=='c') array_splice($t,$b1-1,$a2-$a1+1);
1616          continue;
1617        }
1618        if (strncmp($x,'< ',2) == 0) { $nlflag=true; continue; }
1619        if (preg_match('/^> (.*)$/',$x,$match)) {
1620          $nlflag=false;
1621          array_splice($t,$b1-1,0,$match[1]); $b1++;
1622        }
1623        if ($x=='\\ No newline at end of file') $nl=$nlflag;
1624      }
1625    }
1626    if ($nl) $t[]='';
1627    $new['text']=implode("\n",$t);
1628    $new['=preview'] = $new['text'];
1629    PCache($pagename, $new);
1630    return $new['text'];
1631  }
1632  
1633  ## ReplaceOnSave performs text replacements on the text being posted.
1634  ## Patterns held in $ROEPatterns are replaced on every edit request,
1635  ## patterns held in $ROSPatterns are replaced only when the page
1636  ## is being posted (as signaled by $EnablePost).
1637  function ReplaceOnSave($pagename,&$page,&$new) {
1638    global $EnablePost, $ROSPatterns, $ROEPatterns;
1639    foreach ((array)@$ROEPatterns as $pat => $rep)
1640      $new['text'] = preg_replace($pat, $rep, $new['text']);
1641    if ($EnablePost) {
1642    foreach((array)@$ROSPatterns as $pat=>$rep) 
1643      $new['text'] = preg_replace($pat, $rep, $new['text']);
1644  }
1645    $new['=preview'] = $new['text'];
1646    PCache($pagename, $new);
1647  }
1648  
1649  function SaveAttributes($pagename,&$page,&$new) {
1650    global $EnablePost, $LinkTargets, $SaveAttrPatterns, $PCache,
1651      $SaveProperties;
1652    if (!$EnablePost) return;
1653    $text = preg_replace(array_keys($SaveAttrPatterns), 
1654                         array_values($SaveAttrPatterns), $new['text']);
1655    $LinkTargets = array();
1656    $html = MarkupToHTML($pagename,$text);
1657    $new['targets'] = implode(',',array_keys((array)$LinkTargets));
1658    $p = & $PCache[$pagename];
1659    foreach((array)$SaveProperties as $k) {
1660      if (@$p["=p_$k"]) $new[$k] = $p["=p_$k"];
1661      else unset($new[$k]);
1662    }
1663    unset($new['excerpt']);
1664  }
1665  
1666  function PostPage($pagename, &$page, &$new) {
1667    global $DiffKeepDays, $DiffFunction, $DeleteKeyPattern, $EnablePost,
1668      $Now, $Charset, $Author, $WikiDir, $IsPagePosted, $DiffKeepNum;
1669    SDV($DiffKeepDays,3650);
1670    SDV($DiffKeepNum,20);
1671    SDV($DeleteKeyPattern,"^\\s*delete\\s*$");
1672    $IsPagePosted = false;
1673    if ($EnablePost) {
1674      $new['charset'] = $Charset; # kept for now, may be needed if custom PageStore
1675      $new['author'] = @$Author;
1676      $new["author:$Now"] = @$Author;
1677      $new["host:$Now"] = $_SERVER['REMOTE_ADDR'];
1678      $diffclass = preg_replace('/\\W/','',@$_POST['diffclass']);
1679      if ($page['time']>0 && function_exists(@$DiffFunction)) 
1680        $new["diff:$Now:{$page['time']}:$diffclass"] =
1681          $DiffFunction($new['text'],@$page['text']);
1682      $keepgmt = $Now-$DiffKeepDays * 86400;
1683      $keepnum = array(); 
1684      $keys = array_keys($new);
1685      foreach($keys as $k)
1686        if (preg_match("/^\\w+:(\\d+)/",$k,$match)) {
1687          $keepnum[$match[1]] = 1;
1688          if(count($keepnum)>$DiffKeepNum && $match[1]<$keepgmt) 
1689          unset($new[$k]);
1690        }
1691      if (preg_match("/$DeleteKeyPattern/",$new['text'])){
1692        if(@$new['passwdattr']>'' && !CondAuth($pagename, 'attr'))
1693          Abort('$[The page has an "attr" attribute and cannot be deleted.]');
1694        else  $WikiDir->delete($pagename);
1695      }
1696      else WritePage($pagename,$new);
1697      $IsPagePosted = true;
1698    }
1699  }
1700  
1701  function PostRecentChanges($pagename,$page,$new,$Fmt=null) {
1702    global $IsPagePosted, $RecentChangesFmt, $RCDelimPattern, $RCLinesMax;
1703    if (!$IsPagePosted && $Fmt==null) return;
1704    if ($Fmt==null) $Fmt = $RecentChangesFmt;
1705    foreach($Fmt as $rcfmt=>$pgfmt) {
1706      $rcname = FmtPageName($rcfmt,$pagename);  if (!$rcname) continue;
1707      $pgtext = FmtPageName($pgfmt,$pagename);  if (!$pgtext) continue;
1708      if (@$seen[$rcname]++) continue;
1709      $rcpage = ReadPage($rcname);
1710      $rcelim = preg_quote(preg_replace("/$RCDelimPattern.*$/",' ',$pgtext),'/');
1711      $rcpage['text'] = preg_replace("/^.*$rcelim.*\n/m", '', @$rcpage['text']);
1712      if (!preg_match("/$RCDelimPattern/",$rcpage['text'])) 
1713        $rcpage['text'] .= "$pgtext\n";
1714      else
1715        $rcpage['text'] = preg_replace("/([^\n]*$RCDelimPattern.*\n)/",
1716          str_replace("$", "\\$", $pgtext) . "\n$1", $rcpage['text'], 1);
1717      if (@$RCLinesMax > 0) 
1718        $rcpage['text'] = implode("\n", array_slice(
1719            explode("\n", $rcpage['text'], $RCLinesMax + 1), 0, $RCLinesMax));
1720      WritePage($rcname, $rcpage);
1721    }
1722  }
1723  
1724  function AutoCreateTargets($pagename, &$page, &$new) {
1725    global $IsPagePosted, $AutoCreate, $LinkTargets;
1726    if (!$IsPagePosted) return;
1727    foreach((array)@$AutoCreate as $pat => $init) {
1728      if (is_null($init)) continue;
1729      foreach(preg_grep($pat, array_keys((array)@$LinkTargets)) as $aname) {
1730        if (PageExists($aname)) continue;
1731        $x = RetrieveAuthPage($aname, 'edit', false, READPAGE_CURRENT);
1732        if (!$x) continue;
1733        WritePage($aname, $init);
1734      }
1735    }
1736  }
1737      
1738  function PreviewPage($pagename,&$page,&$new) {
1739    global $IsPageSaved, $FmtV;
1740    if (@$_REQUEST['preview']) {
1741      $text = '(:groupheader:)'.$new['text'].'(:groupfooter:)';
1742      $FmtV['$PreviewText'] = MarkupToHTML($pagename,$text);
1743    } 
1744  }
1745    
1746  function HandleEdit($pagename, $auth = 'edit') {
1747    global $IsPagePosted, $EditFields, $ChangeSummary, $EditFunctions, 
1748      $EnablePost, $FmtV, $Now, $EditRedirectFmt, 
1749      $PageEditForm, $HandleEditFmt, $PageStartFmt, $PageEditFmt, $PageEndFmt;
1750    SDV($EditRedirectFmt, '$FullName');
1751    if (@$_POST['cancel']) 
1752      { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; }
1753    Lock(2);
1754    $page = RetrieveAuthPage($pagename, $auth, true);
1755    if (!$page) Abort("?cannot edit $pagename"); 
1756    $new = $page;
1757    foreach((array)$EditFields as $k) 
1758      if (isset($_POST[$k])) $new[$k]=str_replace("\r",'',stripmagic($_POST[$k]));
1759    $new['csum'] = $ChangeSummary;
1760    if ($ChangeSummary) $new["csum:$Now"] = $ChangeSummary;
1761    $EnablePost &= preg_grep('/^post/', array_keys(@$_POST));
1762    $new['=preview'] = $new['text'];
1763    PCache($pagename, $new);
1764    UpdatePage($pagename, $page, $new);
1765    Lock(0);
1766    if ($IsPagePosted && !@$_POST['postedit']) 
1767      { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; }
1768    $FmtV['$DiffClassMinor'] = 
1769      (@$_POST['diffclass']=='minor') ?  "checked='checked'" : '';
1770    $FmtV['$EditText'] = 
1771      str_replace('$','&#036;',htmlspecialchars(@$new['text'],ENT_NOQUOTES));
1772    $FmtV['$EditBaseTime'] = $Now;
1773    if (@$PageEditForm) {
1774      $efpage = FmtPageName($PageEditForm, $pagename);
1775      $form = RetrieveAuthPage($efpage, 'read', false, READPAGE_CURRENT);
1776      if (!$form || !@$form['text']) 
1777        Abort("?unable to retrieve edit form $efpage", 'editform');
1778      $FmtV['$EditForm'] = MarkupToHTML($pagename, $form['text']);
1779    }
1780    SDV($PageEditFmt, "<div id='wikiedit'>
1781      <h2 class='wikiaction'>$[Editing {\$FullName}]</h2>
1782      <form method='post' rel='nofollow' action='\$PageUrl?action=edit'>
1783      <input type='hidden' name='action' value='edit' />
1784      <input type='hidden' name='n' value='\$FullName' />
1785      <input type='hidden' name='basetime' value='\$EditBaseTime' />
1786      \$EditMessageFmt
1787      <textarea id='text' name='text' rows='25' cols='60'
1788        onkeydown='if (event.keyCode==27) event.returnValue=false;'
1789        >\$EditText</textarea><br />
1790      <input type='submit' name='post' value=' $[Save] ' />");
1791    SDV($HandleEditFmt, array(&$PageStartFmt, &$PageEditFmt, &$PageEndFmt));
1792    PrintFmt($pagename, $HandleEditFmt);
1793  }
1794  
1795  function HandleSource($pagename, $auth = 'read') {
1796    global $HTTPHeaders;
1797    $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
1798    if (!$page) Abort("?cannot source $pagename");
1799    foreach ($HTTPHeaders as $h) {
1800      $h = preg_replace('!^Content-type:\\s+text/html!i',
1801               'Content-type: text/plain', $h);
1802      header($h);
1803    }
1804    echo @$page['text'];
1805  }
1806  
1807  ## PmWikiAuth provides password-protection of pages using PHP sessions.
1808  ## It is normally called from RetrieveAuthPage.  Since RetrieveAuthPage
1809  ## can be called a lot within a single page execution (i.e., for every
1810  ## page accessed), we cache the results of site passwords and 
1811  ## GroupAttribute pages to be able to speed up subsequent calls.
1812  function PmWikiAuth($pagename, $level, $authprompt=true, $since=0) {
1813    global $DefaultPasswords, $GroupAttributesFmt, $AllowPassword,
1814      $AuthCascade, $FmtV, $AuthPromptFmt, $PageStartFmt, $PageEndFmt, 
1815      $AuthId, $AuthList, $NoHTMLCache;
1816    static $acache;
1817    SDV($GroupAttributesFmt,'$Group/GroupAttributes');
1818    SDV($AllowPassword,'nopass');
1819    
1820    $page = ReadPage($pagename, $since);
1821    if (!$page) { return false; }
1822    if (!isset($acache)) 
1823      SessionAuth($pagename, (@$_POST['authpw']) 
1824                             ? array('authpw' => array($_POST['authpw'] => 1))
1825                             : '');
1826    if (@$AuthId) {
1827      $AuthList["id:$AuthId"] = 1;
1828      $AuthList["id:-$AuthId"] = -1;
1829      $AuthList["id:*"] = 1;
1830    }
1831    ## To allow @_site_edit in GroupAttributes, we cache it first
1832    if (!isset($acache['@site'])) {
1833      foreach($DefaultPasswords as $k => $v) {
1834        $x = array(2, array(), '');
1835        $acache['@site'][$k] = IsAuthorized($v, 'site', $x);
1836        $AuthList["@_site_$k"] = $acache['@site'][$k][0] ? 1 : 0;
1837      }
1838    }
1839    $gn = FmtPageName($GroupAttributesFmt, $pagename);
1840    if (!isset($acache[$gn])) {
1841      $gp = ReadPage($gn, READPAGE_CURRENT);
1842      foreach($DefaultPasswords as $k => $v) {
1843        $acache[$gn][$k] = IsAuthorized(@$gp["passwd$k"], 'group', 
1844                                        $acache['@site'][$k]);
1845      }
1846    }
1847    foreach($DefaultPasswords as $k => $v) 
1848      list($page['=auth'][$k], $page['=passwd'][$k], $page['=pwsource'][$k]) =
1849        IsAuthorized(@$page["passwd$k"], 'page', $acache[$gn][$k]);
1850    foreach($AuthCascade as $k => $t) {
1851      if ($page['=auth'][$k]+0 == 2) {
1852        $page['=auth'][$k] = $page['=auth'][$t];
1853        if ($page['=passwd'][$k] = $page['=passwd'][$t])         # assign
1854          $page['=pwsource'][$k] = "cascade:$t";
1855      }
1856    }
1857    if (@$page['=auth']['admin']) 
1858      foreach($page['=auth'] as $lv=>$a) @$page['=auth'][$lv] = 3;
1859    if (@$page['=passwd']['read']) $NoHTMLCache |= 2;
1860    if ($level=='ALWAYS' || @$page['=auth'][$level]) return $page;
1861    if (!$authprompt) return false;
1862    $GLOBALS['AuthNeeded'] = (@$_POST['authpw']) 
1863      ? $page['=pwsource'][$level] . ' ' . $level : '';
1864    PCache($pagename, $page);
1865    $postvars = '';
1866    foreach($_POST as $k=>$v) {
1867      if ($k == 'authpw' || $k == 'authid') continue;
1868      $k = htmlspecialchars(stripmagic($k), ENT_QUOTES);
1869      $v = str_replace('$', '&#036;', 
1870               htmlspecialchars(stripmagic($v), ENT_COMPAT));
1871      $postvars .= "<input type='hidden' name='$k' value=\"$v\" />\n";
1872    }
1873    $FmtV['$PostVars'] = $postvars;
1874    $r = str_replace("'", '%37', stripmagic($_SERVER['REQUEST_URI']));
1875    SDV($AuthPromptFmt,array(&$PageStartFmt,
1876      "<p><b>$[Password required]</b></p>
1877        <form name='authform' action='$r' method='post'>
1878          $[Password]: <input tabindex='1' type='password' name='authpw' 
1879            value='' />
1880          <input type='submit' value='OK' />\$PostVars</form>
1881          <script language='javascript' type='text/javascript'><!--
1882            document.authform.authpw.focus() //--></script>", &$PageEndFmt));
1883    PrintFmt($pagename,$AuthPromptFmt);
1884    exit;
1885  }
1886  
1887  function IsAuthorized($chal, $source, &$from) {
1888    global $AuthList, $AuthPw, $AllowPassword;
1889    if (!$chal) return $from;
1890    $auth = 0; 
1891    $passwd = array();
1892    foreach((array)$chal as $c) {
1893      $x = '';
1894      $pwchal = preg_split('/([, ]|\\w+:)/', $c, -1, PREG_SPLIT_DELIM_CAPTURE);
1895      foreach($pwchal as $pw) {
1896        if ($pw == ',' || $pw == '') continue;
1897        else if ($pw == ' ') { $x = ''; continue; }
1898        else if (substr($pw, -1, 1) == ':') { $x = $pw; continue; }
1899        else if ($pw{0} != '@' && $x > '') $pw = $x . $pw;
1900        if (!$pw) continue;
1901        $passwd[] = $pw;
1902        if ($auth < 0) continue;
1903        if ($x || $pw{0} == '@') {
1904          if (@$AuthList[$pw]) $auth = $AuthList[$pw];
1905          continue;
1906        }
1907        if (crypt($AllowPassword, $pw) == $pw)           # nopass
1908          { $auth=1; continue; }
1909        foreach((array)$AuthPw as $pwresp)                       # password
1910          if (crypt($pwresp, $pw) == $pw) { $auth=1; continue; }
1911      }
1912    }
1913    if (!$passwd) return $from;
1914    if ($auth < 0) $auth = 0;
1915    return array($auth, $passwd, $source);
1916  }
1917  
1918  ## SessionAuth works with PmWikiAuth to manage authorizations
1919  ## as stored in sessions.  First, it can be used to set session
1920  ## variables by calling it with an $auth argument.  It then
1921  ## uses the authid, authpw, and authlist session variables
1922  ## to set the corresponding values of $AuthId, $AuthPw, and $AuthList
1923  ## as needed.
1924  function SessionAuth($pagename, $auth = NULL) {
1925    // tb
1926    return;
1927  
1928    global $AuthId, $AuthList, $AuthPw, $SessionEncode, $SessionDecode,
1929      $EnableSessionPasswords;
1930    static $called;
1931  
1932    @$called++;
1933    $sn = session_name(); # in PHP5.3, $_REQUEST doesn't contain $_COOKIE
1934    if (!$auth && ($called > 1 || (!@$_REQUEST[$sn] && !@$_COOKIE[$sn]))) return;
1935  
1936    $sid = session_id();
1937    @session_start();
1938    foreach((array)$auth as $k => $v) {
1939      if ($k == 'authpw') {
1940        foreach((array)$v as $pw => $pv) {
1941          if ($SessionEncode) $pw = $SessionEncode($pw);
1942          $_SESSION[$k][$pw] = $pv;
1943        }
1944      } 
1945      else if ($k) $_SESSION[$k] = (array)$v + (array)@$_SESSION[$k];
1946    }
1947  
1948    if (!isset($AuthId)) $AuthId = @end($_SESSION['authid']);
1949    $AuthPw = array_map($SessionDecode, array_keys((array)@$_SESSION['authpw']));
1950    if (!IsEnabled($EnableSessionPasswords, 1)) $_SESSION['authpw'] = array();
1951    $AuthList = array_merge($AuthList, (array)@$_SESSION['authlist']);
1952    
1953    if (!$sid) session_write_close();
1954  }
1955  
1956  function PasswdVar($pagename, $level) {
1957    global $PCache, $PasswdVarAuth, $FmtV;
1958    $page = $PCache[$pagename];
1959    if (!isset($page['=passwd'][$level])) {
1960      $page = RetrieveAuthPage($pagename, 'ALWAYS', false, READPAGE_CURRENT);
1961      if ($page) PCache($pagename, $page);
1962    }
1963    SDV($PasswdVarAuth, 'attr');
1964    if ($PasswdVarAuth && !@$page['=auth'][$PasswdVarAuth]) return XL('(protected)');
1965    $pwsource = $page['=pwsource'][$level];
1966    if (strncmp($pwsource, 'cascade:', 8) == 0) {
1967      $FmtV['$PWCascade'] = substr($pwsource, 8);
1968      return FmtPageName('$[(using $PWCascade password)]', $pagename);
1969    }
1970    $setting = htmlspecialchars(implode(' ', preg_replace('/^(?!@|\\w+:).+$/', '****',
1971                                         (array)$page['=passwd'][$level])));
1972    if ($pwsource == 'group' || $pwsource == 'site') {
1973      $FmtV['$PWSource'] = $pwsource;
1974      $setting = FmtPageName('$[(set by $PWSource)] ', $pagename)
1975         . htmlspecialchars($setting);
1976    }
1977    return $setting;
1978  }
1979  
1980  function PrintAttrForm($pagename) {
1981    global $PageAttributes, $PCache, $FmtV;
1982    echo FmtPageName("<form action='\$PageUrl' method='post'>
1983      <input type='hidden' name='action' value='postattr' />
1984      <input type='hidden' name='n' value='\$FullName' />
1985      <table>",$pagename);
1986    $page = $PCache[$pagename];
1987    foreach($PageAttributes as $attr=>$p) {
1988      if (!$p) continue;
1989      if (strncmp($attr, 'passwd', 6) == 0) {
1990        $setting = PageVar($pagename, '$Passwd'.ucfirst(substr($attr, 6)));
1991        $value = '';
1992      } else { $setting = $value = htmlspecialchars(@$page[$attr]); }
1993      $prompt = FmtPageName($p,$pagename);
1994      echo "<tr><td>$prompt</td>
1995        <td><input type='text' name='$attr' value='$value' /></td>
1996        <td>$setting</td></tr>";
1997    }
1998    echo FmtPageName("</table><input type='submit' value='$[Save]' /></form>",
1999           $pagename);
2000  }
2001  
2002  function HandleAttr($pagename, $auth = 'attr') {
2003    global $HandleAttrFmt,$PageAttrFmt,$PageStartFmt,$PageEndFmt;
2004    $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
2005    if (!$page) { Abort("?unable to read $pagename"); }
2006    PCache($pagename,$page);
2007    XLSDV('en', array('EnterAttributes' =>
2008      "Enter new attributes for this page below.  Leaving a field blank
2009      will leave the attribute unchanged.  To clear an attribute, enter
2010      'clear'."));
2011    SDV($PageAttrFmt,"<div class='wikiattr'>
2012      <h2 class='wikiaction'>$[{\$FullName} Attributes]</h2>
2013      <p>$[EnterAttributes]</p></div>");
2014    SDV($HandleAttrFmt,array(&$PageStartFmt,&$PageAttrFmt,
2015      'function:PrintAttrForm',&$PageEndFmt));
2016    PrintFmt($pagename,$HandleAttrFmt);
2017  }
2018  
2019  function HandlePostAttr($pagename, $auth = 'attr') {
2020    global $PageAttributes, $EnablePostAttrClearSession;
2021    Lock(2);
2022    $page = RetrieveAuthPage($pagename, $auth, true);
2023    if (!$page) { Abort("?unable to read $pagename"); }
2024    foreach($PageAttributes as $attr=>$p) {
2025      $v = stripmagic(@$_POST[$attr]);
2026      if ($v == '') continue;
2027      if ($v=='clear') unset($page[$attr]);
2028      else if (strncmp($attr, 'passwd', 6) != 0) $page[$attr] = $v;
2029      else {
2030        $a = array();
2031        preg_match_all('/"[^"]*"|\'[^\']*\'|\\S+/', $v, $match);
2032        foreach($match[0] as $pw) 
2033          $a[] = preg_match('/^(@|\\w+:)/', $pw) ? $pw 
2034                     : crypt(preg_replace('/^([\'"])(.*)\\1$/', '$2', $pw));
2035        if ($a) $page[$attr] = implode(' ',$a);
2036      }
2037    }
2038    WritePage($pagename,$page);
2039    Lock(0);
2040    if (IsEnabled($EnablePostAttrClearSession, 1)) {
2041      @session_start();
2042      unset($_SESSION['authid']);
2043      unset($_SESSION['authlist']);
2044      $_SESSION['authpw'] = array();
2045    }
2046    Redirect($pagename);
2047    exit;
2048  } 
2049  
2050  function HandleLogoutA($pagename, $auth = 'read') {
2051    global $LogoutRedirectFmt, $LogoutCookies;
2052    SDV($LogoutRedirectFmt, '$FullName');
2053    SDV($LogoutCookies, array());
2054    @session_start();
2055    $_SESSION = array();
2056    if ( session_id() != '' || isset($_COOKIE[session_name()]) )
2057      setcookie(session_name(), '', time()-43200, '/');
2058    foreach ($LogoutCookies as $c)
2059      if (isset($_COOKIE[$c])) setcookie($c, '', time()-43200, '/');
2060    session_destroy();
2061    Redirect(FmtPageName($LogoutRedirectFmt, $pagename));
2062  }
2063  
2064  function HandleLoginA($pagename, $auth = 'login') {
2065    global $AuthId, $DefaultPasswords;
2066    unset($DefaultPasswords['admin']);
2067    $prompt = @(!$_POST['authpw'] || ($AuthId != $_POST['authid']));
2068    $page = RetrieveAuthPage($pagename, $auth, $prompt, READPAGE_CURRENT);
2069    Redirect($pagename);
2070  }
2071  
2072  ?>

title

Description

title

Description

title

Description

title

title

Body