1<?php 2/* 3 PmWiki 4 Copyright 2001-2021 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 Script maintained by Petko YOTOV www.pmwiki.org/petko 29 $Id: pmwiki.php 3711 2021-04-09 05:31:52Z petko $ 30*/ 31error_reporting(E_ALL ^ E_NOTICE); 32StopWatch('PmWiki'); 33@ini_set('magic_quotes_runtime', 0); 34@ini_set('magic_quotes_sybase', 0); 35if (@ini_get('pcre.backtrack_limit') < 1000000) 36 @ini_set('pcre.backtrack_limit', 1000000); 37if (ini_get('register_globals')) 38 foreach($_REQUEST as $k=>$v) { 39 if (preg_match('/^(GLOBALS|_SERVER|_GET|_POST|_COOKIE|_FILES|_ENV|_REQUEST|_SESSION|FarmD|WikiDir)$/i', $k)) exit(); 40 ${$k}=''; unset(${$k}); 41 } 42$UnsafeGlobals = array_keys($GLOBALS); $GCount=0; $FmtV=array(); 43$FmtV['$TokenName'] = 'pmtoken'; 44SDV($FarmD,dirname(__FILE__)); 45SDV($WorkDir,'wiki.d'); 46define('PmWiki',1); 47if (preg_match('/\\w\\w:/', $FarmD)) exit(); 48@include_once("$FarmD/scripts/version.php"); 49$GroupPattern = '[[:upper:]][\\w]*(?:-\\w+)*'; 50$NamePattern = '[[:upper:]\\d][\\w]*(?:-\\w+)*'; 51$BlockPattern = 'form|div|table|t[rdh]|p|[uo]l|d[ltd]|h[1-6r]|pre|blockquote'; 52$WikiWordPattern = '[[:upper:]][[:alnum:]]*(?:[[:upper:]][[:lower:]0-9]|[[:lower:]0-9][[:upper:]])[[:alnum:]]*'; 53$WikiDir = new PageStore('wiki.d/{$FullName}'); 54$WikiLibDirs = array(&$WikiDir,new PageStore('$FarmD/wikilib.d/{$FullName}')); 55$PageFileEncodeFunction = 'PUE'; # only used if $WikiDir->encodefilenames is set 56$PageFileDecodeFunction = 'urldecode'; 57$LocalDir = 'local'; 58$InterMapFiles = array("$FarmD/scripts/intermap.txt", 59 "$FarmD/local/farmmap.txt", '$SiteGroup.InterMap', 'local/localmap.txt'); 60$Newline = "\263"; # deprecated, 2.0.0 61$KeepToken = "\034\034"; 62$Now=time(); 63define('READPAGE_CURRENT', $Now+604800); 64$TimeFmt = '%B %d, %Y, at %I:%M %p'; 65$TimeISOFmt = '%Y-%m-%dT%H:%M:%S'; 66$TimeISOZFmt = '%Y-%m-%dT%H:%M:%SZ'; 67$MessagesFmt = array(); 68$BlockMessageFmt = "<h3 class='wikimessage'>$[This post has been blocked by the administrator]</h3>"; 69$EditFields = array('text'); 70$EditFunctions = array('AutoCheckToken', 'EditTemplate', 'RestorePage', 'ReplaceOnSave', 71 'SaveAttributes', 'PostPage', 'PostRecentChanges', 'AutoCreateTargets', 72 'PreviewPage'); 73$EnablePost = 1; 74$ChangeSummary = substr(preg_replace('/[\\x00-\\x1f]|=\\]/', '', 75 stripmagic(@$_REQUEST['csum'])), 0, 100); 76$AsSpacedFunction = 'AsSpaced'; 77$SpaceWikiWords = 0; 78$RCDelimPattern = ' '; 79$RecentChangesFmt = array( 80 '$SiteGroup.AllRecentChanges' => 81 '* [[{$Group}.{$Name}]] . . . $CurrentTime $[by] $AuthorLink: [=$ChangeSummary=]', 82 '$Group.RecentChanges' => 83 '* [[{$Group}/{$Name}]] . . . $CurrentTime $[by] $AuthorLink: [=$ChangeSummary=]'); 84$UrlScheme = (@$_SERVER['HTTPS']=='on' || @$_SERVER['SERVER_PORT']==443) 85 ? 'https' : 'http'; 86$ScriptUrl = $UrlScheme.'://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']; 87$PubDirUrl = preg_replace('#/[^/]*$#', '/pub', $ScriptUrl, 1); 88$HTMLVSpace = "<vspace>"; 89$HTMLPNewline = ''; 90$MarkupFrame = array(); 91$MarkupFrameBase = array('cs' => array(), 'vs' => '', 'ref' => 0, 92 'closeall' => array(), 'is' => array(), 93 'escape' => 1); 94$WikiWordCountMax = 1000000; 95$WikiWordCount['PmWiki'] = 1; 96$TableRowIndexMax = 1; 97$UrlExcludeChars = '<>"{}|\\\\^`()[\\]\''; 98$QueryFragPattern = "[?#][^\\s$UrlExcludeChars]*"; 99$SuffixPattern = '(?:-?[[:alnum:]]+)*'; 100$LinkPageSelfFmt = "<a class='selflink' href='\$LinkUrl' title='\$LinkAlt'>\$LinkText</a>"; 101$LinkPageExistsFmt = "<a class='wikilink' href='\$LinkUrl' title='\$LinkAlt'>\$LinkText</a>"; 102$LinkPageCreateFmt = 103 "<a class='createlinktext' rel='nofollow' title='\$LinkAlt' 104 href='{\$PageUrl}?action=edit'>\$LinkText</a><a rel='nofollow' 105 class='createlink' href='{\$PageUrl}?action=edit'>?</a>"; 106$UrlLinkFmt = 107 "<a class='urllink' href='\$LinkUrl' title='\$LinkAlt' rel='nofollow'>\$LinkText</a>"; 108umask(002); 109$CookiePrefix = ''; 110$SiteGroup = 'Site'; 111$SiteAdminGroup = 'SiteAdmin'; 112$DefaultGroup = 'Main'; 113$DefaultName = 'HomePage'; 114$GroupHeaderFmt = '(:include {$Group}.GroupHeader self=0 basepage={*$FullName}:)(:nl:)'; 115$GroupFooterFmt = '(:nl:)(:include {$Group}.GroupFooter self=0 basepage={*$FullName}:)'; 116$PagePathFmt = array('{$Group}.$1','$1.$1','$1.{$DefaultName}'); 117$PageAttributes = array( 118 'passwdread' => '$[Set new read password:]', 119 'passwdedit' => '$[Set new edit password:]', 120 'passwdattr' => '$[Set new attribute password:]'); 121$XLLangs = array('en'); 122if (preg_match('/^C$|\.UTF-?8/i',setlocale(LC_ALL,0))) 123 setlocale(LC_ALL,'en_US'); 124$FmtP = array(); 125$FmtPV = array( 126 # '$ScriptUrl' => 'PUE($ScriptUrl)', ## $ScriptUrl is special 127 '$PageUrl' => 128 'PUE(($EnablePathInfo) 129 ? "$ScriptUrl/$group/$name" 130 : "$ScriptUrl?n=$group.$name")', 131 '$FullName' => '"$group.$name"', 132 '$Groupspaced' => '$AsSpacedFunction($group)', 133 '$Namespaced' => '$AsSpacedFunction($name)', 134 '$Group' => '$group', 135 '$Name' => '$name', 136 '$Titlespaced' => 'FmtPageTitle(@$page["title"], $name, 1)', 137 '$Title' => 'FmtPageTitle(@$page["title"], $name, 0)', 138 '$LastModifiedBy' => '@$page["author"]', 139 '$LastModifiedHost' => '@$page["host"]', 140 '$LastModified' => 'strftime($GLOBALS["TimeFmt"], $page["time"])', 141 '$LastModifiedSummary' => '@$page["csum"]', 142 '$LastModifiedTime' => '$page["time"]', 143 '$Description' => '@$page["description"]', 144 '$SiteGroup' => '$GLOBALS["SiteGroup"]', 145 '$SiteAdminGroup' => '$GLOBALS["SiteAdminGroup"]', 146 '$VersionNum' => '$GLOBALS["VersionNum"]', 147 '$Version' => '$GLOBALS["Version"]', 148 '$WikiTitle' => '$GLOBALS["WikiTitle"]', 149 '$Author' => 'NoCache($GLOBALS["Author"])', 150 '$AuthId' => 'NoCache($GLOBALS["AuthId"])', 151 '$DefaultGroup' => '$GLOBALS["DefaultGroup"]', 152 '$DefaultName' => '$GLOBALS["DefaultName"]', 153 '$BaseName' => 'MakeBaseName($pn)', 154 '$Action' => '$GLOBALS["action"]', 155 '$PasswdRead' => 'PasswdVar($pn, "read")', 156 '$PasswdEdit' => 'PasswdVar($pn, "edit")', 157 '$PasswdAttr' => 'PasswdVar($pn, "attr")', 158 ); 159$SaveProperties = array('title', 'description', 'keywords'); 160$PageTextVarPatterns = array( 161 'var:' => '/^(:*[ \\t]*(\\w[-\\w]*)[ \\t]*:[ \\t]?)(.*)($)/m', 162 '(:var:...:)' => '/(\\(: *(\\w[-\\w]*) *:(?!\\))\\s?)(.*?)(:\\))/s' 163 ); 164 165 166$WikiTitle = 'PmWiki'; 167$Charset = 'ISO-8859-1'; 168$HTTPHeaders = array( 169 "Expires: Tue, 01 Jan 2002 00:00:00 GMT", 170 "Cache-Control: no-store, no-cache, must-revalidate", 171 "Content-type: text/html; charset=ISO-8859-1;"); 172$CacheActions = array('browse','diff','print'); 173$EnableHTMLCache = 0; 174$NoHTMLCache = 0; 175$HTMLTagAttr = ''; 176$HTMLDoctypeFmt = 177 "<!DOCTYPE html 178 PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" 179 \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> 180 <html xmlns='http://www.w3.org/1999/xhtml' \$HTMLTagAttr><head>\n"; 181$HTMLStylesFmt['pmwiki'] = " 182 ul, ol, pre, dl, p { margin-top:0px; margin-bottom:0px; } 183 code.escaped { white-space: pre; } 184 .vspace { margin-top:1.33em; } 185 .indent { margin-left:40px; } 186 .outdent { margin-left:40px; text-indent:-40px; } 187 a.createlinktext { text-decoration:none; border-bottom:1px dotted gray; } 188 a.createlink { text-decoration:none; position:relative; top:-0.5em; 189 font-weight:bold; font-size:smaller; border-bottom:none; } 190 img { border:0px; } 191 "; 192$HTMLHeaderFmt['styles'] = array( 193 "<style type='text/css'><!--",&$HTMLStylesFmt,"\n--></style>"); 194$HTMLBodyFmt = "</head>\n<body>"; 195$HTMLStartFmt = array('headers:',&$HTMLDoctypeFmt,&$HTMLHeaderFmt, 196 &$HTMLBodyFmt); 197$HTMLEndFmt = "\n</body>\n</html>"; 198$PageStartFmt = array(&$HTMLStartFmt,"\n<div id='contents'>\n"); 199$PageEndFmt = array('</div>',&$HTMLEndFmt); 200 201$HandleActions = array( 202 'browse' => 'HandleBrowse', 'print' => 'HandleBrowse', 203 'edit' => 'HandleEdit', 'source' => 'HandleSource', 204 'attr' => 'HandleAttr', 'postattr' => 'HandlePostAttr', 205 'logout' => 'HandleLogoutA', 'login' => 'HandleLoginA'); 206$HandleAuth = array( 207 'browse' => 'read', 'source' => 'read', 'print' => 'read', 208 'edit' => 'edit', 'attr' => 'attr', 'postattr' => 'attr', 209 'logout' => 'read', 'login' => 'login'); 210$ActionTitleFmt = array( 211 'edit' => '| $[Edit]', 212 'attr' => '| $[Attributes]', 213 'login' => '| $[Login]'); 214$DefaultPasswords = array('admin'=>'@lock','read'=>'','edit'=>'','attr'=>''); 215$AuthCascade = array('edit'=>'read', 'attr'=>'edit'); 216$AuthList = array('' => 1, 'nopass:' => 1, '@nopass' => 1); 217$SessionEncode = 'base64_encode'; 218$SessionDecode = 'base64_decode'; 219 220$CallbackFnTemplates = array( 221 'default' => '%s', 222 'return' => 'return %s;', 223 'markup_e' => 'extract($GLOBALS["MarkupToHTML"]); return %s;', 224 'qualify' => 'extract($GLOBALS["tmp_qualify"]); return %s;', 225); 226 227$Conditions['enabled'] = '(boolean)@$GLOBALS[$condparm]'; 228$Conditions['false'] = 'false'; 229$Conditions['true'] = 'true'; 230$Conditions['group'] = 231 "(boolean)MatchPageNames(\$pagename, FixGlob(\$condparm, '$1$2.*'))"; 232$Conditions['name'] = 233 "(boolean)MatchPageNames(\$pagename, FixGlob(\$condparm, '$1*.$2'))"; 234$Conditions['match'] = 'preg_match("!$condparm!",$pagename)'; 235$Conditions['authid'] = 'NoCache(@$GLOBALS["AuthId"] > "")'; 236$Conditions['exists'] = "(boolean)ListPages(FixGlob( 237 str_replace(array('[[',']]'), array('', ''), \$condparm) , '$1*.$2'))"; 238$Conditions['equal'] = 'CompareArgs($condparm) == 0'; 239function CompareArgs($arg) 240 { $arg = ParseArgs($arg); return strcmp(@$arg[''][0], @$arg[''][1]); } 241 242$Conditions['auth'] = 'NoCache(CondAuth($pagename, $condparm))'; 243function CondAuth($pagename, $condparm) { 244 global $HandleAuth; 245 @list($level, $pn) = explode(' ', $condparm, 2); 246 $pn = ($pn > '') ? MakePageName($pagename, $pn) : $pagename; 247 if (@$HandleAuth[$level]>'') $level = $HandleAuth[$level]; 248 return (boolean)RetrieveAuthPage($pn, $level, false, READPAGE_CURRENT); 249} 250 251## CondExpr handles complex conditions (expressions) 252## Portions Copyright 2005 by D. Faure (dfaure@cpan.org) 253function CondExpr($pagename, $condname, $condparm) { 254 global $CondExprOps; 255 SDV($CondExprOps, 'and|x?or|&&|\\|\\||[!()]'); 256 if ($condname == '(' || $condname == '[') 257 $condparm = preg_replace('/[\\]\\)]\\s*$/', '', $condparm); 258 $condparm = str_replace('&&', '&&', $condparm); 259 $terms = preg_split("/(?<!\\S)($CondExprOps)(?!\\S)/i", $condparm, -1, 260 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 261 foreach($terms as $i => $t) { 262 $t = trim($t); 263 if (preg_match("/^($CondExprOps)$/i", $t)) continue; 264 if ($t) $terms[$i] = CondText($pagename, "if $t", 'TRUE') ? '1' : '0'; 265 } 266 return @eval('return(' . implode(' ', $terms) . ');'); 267} 268$Conditions['expr'] = 'CondExpr($pagename, $condname, $condparm)'; 269$Conditions['('] = 'CondExpr($pagename, $condname, $condparm)'; 270$Conditions['['] = 'CondExpr($pagename, $condname, $condparm)'; 271 272$MarkupTable['_begin']['seq'] = 'B'; 273$MarkupTable['_end']['seq'] = 'E'; 274Markup('fulltext','>_begin'); 275Markup('split','>fulltext',"\n", 276 '$RedoMarkupLine=1; return explode("\n",$x);'); 277Markup('directives','>split'); 278Markup('inline','>directives'); 279Markup('links','>inline'); 280Markup('block','>links'); 281Markup('style','>block'); 282Markup('closeall', '_begin', 283 '/^\\(:closeall:\\)$/', "MarkupMarkupClose"); 284function MarkupMarkupClose() { return '<:block>' . MarkupClose(); } 285 286$ImgExtPattern="\\.(?:gif|jpg|jpeg|a?png|svgz?|GIF|JPG|JPEG|A?PNG|SVGZ?|webp|WEBP)"; 287$ImgTagFmt="<img src='\$LinkUrl' alt='\$LinkAlt' title='\$LinkAlt' />"; 288 289$BlockMarkups = array( 290 'block' => array('','','',0), 291 'ul' => array('<ul><li>','</li><li>','</li></ul>',1), 292 'dl' => array('<dl>','</dd>','</dd></dl>',1), 293 'ol' => array('<ol><li>','</li><li>','</li></ol>',1), 294 'p' => array('<p>','','</p>',0), 295 'indent' => 296 array("<div class='indent'>","</div><div class='indent'>",'</div>',1), 297 'outdent' => 298 array("<div class='outdent'>","</div><div class='outdent'>",'</div>',1), 299 'pre' => array('<pre>','','</pre>',0), 300 'table' => array("<table width='100%'>",'','</table>',0)); 301 302foreach(array('http:','https:','mailto:','ftp:','news:','gopher:','nap:', 303 'file:', 'tel:', 'geo:') as $m) 304 { $LinkFunctions[$m] = 'LinkIMap'; $IMap[$m]="$m$1"; } 305$LinkFunctions['<:page>'] = 'LinkPage'; 306 307$q = preg_replace('/(\\?|%3f)([-\\w]+=)/i', '&$2', @$_SERVER['QUERY_STRING']); 308if ($q != @$_SERVER['QUERY_STRING']) { 309 unset($_GET); 310 parse_str($q, $_GET); 311 $_REQUEST = array_merge($_REQUEST, $_GET, $_POST); 312} 313 314if (isset($_GET['action'])) $action = $_GET['action']; 315elseif (isset($_POST['action'])) $action = $_POST['action']; 316else $action = 'browse'; 317 318$pagename = @$_REQUEST['n']; 319if (!$pagename) $pagename = @$_REQUEST['pagename']; 320if (!$pagename && 321 preg_match('!^'.preg_quote($_SERVER['SCRIPT_NAME'],'!').'/?([^?]*)!', 322 $_SERVER['REQUEST_URI'],$match)) 323 $pagename = urldecode($match[1]); 324if (preg_match('/[\\x80-\\xbf]/',$pagename)) 325 $pagename=utf8_decode($pagename); 326$pagename = preg_replace('![^[:alnum:]\\x80-\\xff]+$!','',$pagename); 327$pagename_unfiltered = $pagename; 328$pagename = preg_replace('![${}\'"\\\\]+!', '', $pagename); 329$FmtPV['$RequestedPage'] = 'PHSC($GLOBALS["pagename_unfiltered"], ENT_QUOTES)'; 330$Cursor['*'] = &$pagename; 331if (function_exists("date_default_timezone_get") ) { # fix PHP5.3 warnings 332 @date_default_timezone_set(@date_default_timezone_get()); 333} 334 335$DenyHtaccessContent = <<<EOF 336<IfModule !mod_authz_host.c> 337 Order Deny,Allow 338 Deny from all 339</IfModule> 340 341<IfModule mod_authz_host.c> 342 Require all denied 343</IfModule> 344 345EOF; 346 347if (file_exists("$FarmD/local/farmconfig.php")) 348 include_once("$FarmD/local/farmconfig.php"); 349if (IsEnabled($EnableLocalConfig,1)) { 350 if (file_exists("$LocalDir/config.php")) 351 include_once("$LocalDir/config.php"); 352 elseif (file_exists('config.php')) 353 include_once('config.php'); 354} 355 356SDV($CurrentTime, strftime($TimeFmt, $Now)); 357SDV($CurrentTimeISO, strftime($TimeISOFmt, $Now)); 358 359if (IsEnabled($EnableStdConfig,1)) 360 include_once("$FarmD/scripts/stdconfig.php"); 361 362if (isset($PostConfig) && is_array($PostConfig)) { 363 asort($PostConfig, SORT_NUMERIC); 364 foreach ($PostConfig as $k=>$v) { 365 if (!$k || !$v || $v<50) continue; 366 if (function_exists($k)) $k($pagename); 367 elseif (file_exists($k)) include_once($k); 368 } 369} 370 371function pmsetcookie($name, $val="", $exp=0, $path="", $dom="", $secure=null, $httponly=null) { 372 global $EnableCookieSecure, $EnableCookieHTTPOnly, $SetCookieFunction; 373 if(IsEnabled($SetCookieFunction)) 374 return $SetCookieFunction($name, $val, $exp, $path, $dom, $secure, $httponly); 375 if (is_null($secure)) $secure = IsEnabled($EnableCookieSecure, false); 376 if (is_null($httponly)) $httponly = IsEnabled($EnableCookieHTTPOnly, false); 377 setcookie($name, $val, $exp, $path, $dom, $secure, $httponly); 378} 379if (IsEnabled($EnableCookieSecure, false)) 380 @ini_set('session.cookie_secure', $EnableCookieSecure); 381if (IsEnabled($EnableCookieHTTPOnly, false)) 382 @ini_set('session.cookie_httponly', $EnableCookieHTTPOnly); 383 384foreach((array)$InterMapFiles as $f) { 385 $f = FmtPageName($f, $pagename); 386 if (($v = @file($f))) 387 $v = preg_replace('/^\\s*(?>\\w[-\\w]*)(?!:)/m', '$0:', implode('', $v)); 388 else if (@PageExists($f)) { 389 $p = ReadPage($f, READPAGE_CURRENT); 390 $v = @$p['text']; 391 } else continue; 392 if (!preg_match_all("/^\\s*(\\w[-\\w]*:)[^\\S\n]+(\\S*)/m", $v, 393 $match, PREG_SET_ORDER)) continue; 394 foreach($match as $m) { 395 if (strpos($m[2], '$1') === false) $m[2] .= '$1'; 396 $LinkFunctions[$m[1]] = 'LinkIMap'; 397 $IMap[$m[1]] = FmtPageName($m[2], $pagename); 398 } 399} 400 401$keys = array_keys($AuthCascade); 402while ($keys) { 403 $k = array_shift($keys); $t = $AuthCascade[$k]; 404 if (in_array($t, $keys)) 405 { unset($AuthCascade[$k]); $AuthCascade[$k] = $t; array_push($keys, $k); } 406} 407 408$LinkPattern = implode('|',array_keys($LinkFunctions)); # after InterMaps 409SDV($LinkPageCreateSpaceFmt,$LinkPageCreateFmt); 410$ActionTitle = FmtPageName(@$ActionTitleFmt[$action], $pagename); 411 412 413if (!@$HandleActions[$action] || !function_exists($HandleActions[$action])) 414 $action='browse'; 415if (IsEnabled($EnableActions, 1)) HandleDispatch($pagename, $action); 416Lock(0); 417return; 418 419## HandleDispatch() is used to dispatch control to the appropriate 420## action handler with the appropriate permissions. 421## If a message is supplied, it is added to $MessagesFmt. 422function HandleDispatch($pagename, $action, $msg=NULL) { 423 global $MessagesFmt, $HandleActions, $HandleAuth; 424 if ($msg) $MessagesFmt[] = "<div class='wikimessage'>$msg</div>"; 425 $fn = $HandleActions[$action]; 426 $auth = @$HandleAuth[$action]; 427 if (!$auth) $auth = 'read'; 428 return $fn($pagename, $auth); 429} 430 431## helper functions 432function stripmagic($x) { 433 $fn = 'get_magic_quotes_gpc'; 434 if (!function_exists($fn)) return $x; 435 if (is_array($x)) { 436 foreach($x as $k=>$v) $x[$k] = stripmagic($v); 437 return $x; 438 } 439 return @$fn() ? stripslashes($x) : $x; 440} 441function pre_r(&$x) 442 { return '<pre>'.PHSC(print_r($x, true)).'</pre>'; } 443function PSS($x) 444 { return str_replace('\\"','"',$x); } 445function PVS($x) 446 { return preg_replace("/\n[^\\S\n]*(?=\n)/", "\n<:vspace>", $x); } 447function PVSE($x) { return PVS(PHSC($x, ENT_NOQUOTES)); } 448function PZZ($x,$y='') { return ''; } 449function PRR($x=NULL) 450 { if ($x || is_null($x)) $GLOBALS['RedoMarkupLine']++; return $x; } 451function PUE($x) 452 { return preg_replace_callback('/[\\x80-\\xff \'"<>]/', "cb_pue", $x); } 453function cb_pue($m) { return '%'.dechex(ord($m[0])); } 454function PQA($x, $keep=true) { 455 $out = ''; 456 if (preg_match_all('/([a-zA-Z][-\\w]*)\\s*=\\s*("[^"]*"|\'[^\']*\'|\\S*)/', 457 $x, $attr, PREG_SET_ORDER)) { 458 foreach($attr as $a) { 459 if (preg_match('/^on/i', $a[1])) continue; 460 $val = preg_replace('/^([\'"]?)(.*)\\1$/', '$2', $a[2]); 461 if ($keep) $val = Keep(PHSC($val, ENT_QUOTES, null, false)); 462 else $val = str_replace("'", ''', $val); 463 464 $out .= "{$a[1]}='$val' "; 465 } 466 } 467 return $out; 468} 469function SDV(&$v,$x) { if (!isset($v)) $v=$x; } 470function SDVA(&$var,$val) 471 { foreach($val as $k=>$v) if (!isset($var[$k])) $var[$k]=$v; } 472function IsEnabled(&$var,$f=0) 473 { return (isset($var)) ? $var : $f; } 474function SetTmplDisplay($var, $val) 475 { NoCache(); $GLOBALS['TmplDisplay'][$var] = $val; } 476function NoCache($x = '') { $GLOBALS['NoHTMLCache'] |= 1; return $x; } 477function ParseArgs($x, $optpat = '(?>(\\w+)[:=])') { 478 $z = array(); 479 preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/", 480 $x, $terms, PREG_SET_ORDER); 481 foreach($terms as $t) { 482 $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]); 483 if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; } 484 else { $z['#'][] = $t[1]; $z[$t[1]][] = $v; } 485 $z['#'][] = $v; 486 } 487 return $z; 488} 489function PHSC($x, $flags=ENT_COMPAT, $enc=null, $dbl_enc=true) { # for PHP 5.4 490 if (is_null($enc)) $enc = "ISO-8859-1"; # single-byte charset 491 if (! is_array($x)) return @htmlspecialchars($x, $flags, $enc, $dbl_enc); 492 foreach($x as $k=>$v) $x[$k] = PHSC($v, $flags, $enc, $dbl_enc); 493 return $x; 494} 495function PCCF($code, $template = 'default', $args = '$m') { 496 global $CallbackFnTemplates, $CallbackFunctions, $PCCFOverrideFunction; 497 if ($PCCFOverrideFunction && is_callable($PCCFOverrideFunction)) 498 return $PCCFOverrideFunction($code, $template, $args); 499 500 if (!isset($CallbackFnTemplates[$template])) 501 Abort("No \$CallbackFnTemplates[$template])."); 502 $code = sprintf($CallbackFnTemplates[$template], $code); 503 if (!isset($CallbackFunctions[$code])) { 504 $fn = create_function($args, $code); # called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting 505 if ($fn) $CallbackFunctions[$code] = $fn; 506 else StopWatch("Failed to create callback function: ".PHSC($code)); 507 } 508 return $CallbackFunctions[$code]; 509} 510function PPRE($pat, $rep, $x) { 511 $lambda = PCCF("return $rep;"); 512 return preg_replace_callback($pat, $lambda, $x); 513} 514function PPRA($array, $x) { 515 foreach((array)$array as $pat => $rep) { 516 $fmt = $x; # for $FmtP 517 if (is_callable($rep) && $rep != '_') $x = preg_replace_callback($pat,$rep,$x); 518 else $x = preg_replace($pat,$rep,$x);# simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting 519 } 520 return $x; 521} 522## callback functions 523class PPRC { # PmWiki preg replace callbacks + pass local vars 524 var $vars; 525 function __construct($vars = false) { 526 if ($vars && !is_null($vars)) $this->vars = $vars; 527 } 528 function pagevar($m) { # called from FmtPageName 529 $pagename = $this->vars; 530 return PageVar($pagename, $m[1]); 531 } 532} 533 534# restores kept/protected strings 535function cb_expandkpv($m) { return $GLOBALS['KPV'][$m[1]]; } 536 537# make a string upper or lower case in various patterns 538function cb_toupper($m) { return strtoupper($m[1]); } 539function cb_tolower($m) { return strtolower($m[1]); } 540 541function pmcrypt($str, $salt=null) { 542 if ($salt && preg_match('/^(-?@|\\*$)/', $salt)) return false; 543 if (!is_null($salt)) return crypt($str, $salt); 544 545 if (function_exists('password_hash')) 546 return password_hash($str, PASSWORD_DEFAULT); 547 return crypt($str); 548} 549 550# generate or check a random one-time token to prevent CSRF 551function pmtoken($token = null) { 552 global $SessionMaxTokens, $PmTokenFn; 553 if(IsEnabled($PmTokenFn) && function_exists($PmTokenFn)) 554 return $PmTokenFn($token); 555 @session_start(); 556 if(!isset($_SESSION['pmtokens'])) $_SESSION['pmtokens'] = array(); 557 if(is_null($token)) { # create a one-time token 558 $len = mt_rand(20,30); 559 $token = ""; 560 while(strlen($token)<$len) { 561 $token .= chr(mt_rand(32,126)); 562 } 563 if(count($_SESSION['pmtokens'])) 564 $id = max(array_keys($_SESSION['pmtokens']))+1; 565 else $id = 0; 566 $_SESSION['pmtokens'][$id] = $token; 567 if(IsEnabled($SessionMaxTokens, 0)) { 568 $max = $SessionMaxTokens; 569 $_SESSION['pmtokens'] = array_slice($_SESSION['pmtokens'], -$max, $max, true); 570 } 571 return "$id:" . md5($token); 572 } 573 # else: check a token, if correct, delete it 574 @list($id, $hash) = explode(':', $token); 575 $id = intval($id); 576 if(isset($_SESSION['pmtokens'][$id]) && $hash == md5($_SESSION['pmtokens'][$id])) { 577 unset($_SESSION['pmtokens'][$id]); 578 return true; 579 } 580 return false; 581} 582 583function StopWatch($x) { 584 global $StopWatch, $EnableStopWatch; 585 if (!$EnableStopWatch) return; 586 static $wstart = 0, $ustart = 0; 587 list($usec,$sec) = explode(' ',microtime()); 588 $wtime = ($sec+$usec); 589 if (!$wstart) $wstart = $wtime; 590 if ($EnableStopWatch != 2) 591 { $StopWatch[] = sprintf("%05.2f %s", $wtime-$wstart, $x); return; } 592 $dat = getrusage(); 593 $utime = ($dat['ru_utime.tv_sec']+$dat['ru_utime.tv_usec']/1000000); 594 if (!$ustart) $ustart=$utime; 595 $StopWatch[] = 596 sprintf("%05.2f %05.2f %s", $wtime-$wstart, $utime-$ustart, $x); 597} 598 599 600## DRange converts a variety of string formats into date (ranges). 601## It returns the start and end timestamps (+1 second) of the specified date. 602function DRange($when) { 603 global $Now; 604 ## unix/posix @timestamp dates 605 if (preg_match('/^\\s*@(\\d+)\\s*(.*)$/', $when, $m)) { 606 $t0 = $m[2] ? strtotime($m[2], $m[1]) : $m[1]; 607 return array($t0, $t0+1); 608 } 609 ## ISO-8601 dates 610 $dpat = '/ 611 (?<!\\d) # non-digit 612 (19\\d\\d|20[0-3]\\d) # year ($1) 613 ([-.\\/]?) # date separator ($2) 614 (0\\d|1[0-2]) # month ($3) 615 (?: \\2 # repeat date separator 616 ([0-2]\\d|3[0-1]) # day ($4) 617 (?: T # time marker 618 ([01]\\d|2[0-4]) # hour ($5) 619 ([.:]?) # time separator ($6) 620 ([0-5]\\d) # minute ($7) 621 (?: \\6 # repeat time separator 622 ([0-5]\\d|60) # seconds ($8) 623 )? # optional :ss 624 )? # optional Thh:mm:ss 625 )? # optional -ddThh:mm:ss 626 (?!\d) # non-digit 627 /x'; 628 if (preg_match($dpat, $when, $m) && 629 !preg_match('/[+-]\\s*\\d+\\s*(sec(ond)?|min(ute)?|forth?night|day|week(day)?|month|year)s?/i', $when)) { 630 $n = $m; 631 ## if no time given, assume range of 1 day (except when full month) 632 if (@$m[4]>'' && @$m[5] == '') { @$n[4]++; } 633 ## if no day given, assume 1st of month and full month range 634 if (@$m[4] == '') { $m[4] = 1; $n[4] = 1; $n[3]++; } 635 ## if no seconds given, assume range of 1 minute (except when full day) 636 if (@$m[7]>'' && @$m[8] == '') { @$n[7]++; } 637 $t0 = @mktime($m[5], $m[7], $m[8], $m[3], $m[4], $m[1]); 638 $t1 = @mktime($n[5], $n[7], $n[8], $n[3], $n[4], $n[1]); 639 return array($t0, $t1); 640 } 641 ## now, today, tomorrow, yesterday 642 NoCache(); 643 if ($when == 'now') return array($Now, $Now+1); 644 $m = localtime(time()); 645 if ($when == 'tomorrow') { $m[3]++; $when = 'today'; } 646 if ($when == 'yesterday') { $m[3]--; $when = 'today'; } 647 if ($when == 'today') 648 return array(mktime(0, 0, 0, $m[4]+1, $m[3] , $m[5]+1900), 649 mktime(0, 0, 0, $m[4]+1, $m[3]+1, $m[5]+1900)); 650 if (preg_match('/^\\s*$/', $when)) return array(-1, -1); 651 $t0 = strtotime($when); 652 $t1 = strtotime("+1 day", $t0); 653 return array($t0, $t1); 654} 655 656## DiffTimeCompact subtracts 2 timestamps and outputs a compact 657## human-readable delay in hours, days, weeks, months or years 658function DiffTimeCompact($time, $time2=null, $precision=1) { 659 if(is_null($time2)) $time2 = $GLOBALS['Now']; 660 $suffix = explode(',', XL('h,d,w,m,y')); 661 $x = $hours = abs($time2 - $time)/3600; 662 if($x<24) return round($x,$precision).$suffix[0]; 663 $x /= 24; if($x<14) return round($x,$precision).$suffix[1]; 664 $x /= 7; if($x< 9) return round($x,$precision).$suffix[2]; 665 $x = $hours/2/365.2425; if($x<24) return round($x,$precision).$suffix[3]; 666 return round($hours/24/365.2425,$precision).$suffix[4]; 667} 668 669## FileSizeCompact outputs a human readable file size 670## with an appropriate suffix. 671## Note: unreliable filemtime()/stat() over 2GB @ 32bit 672function FileSizeCompact($n, $precision=1) { 673 if(!(float)$n) return 0; 674 $units = 'bkMGTPEZY'; 675 $b = log((float)$n, 1024); 676 $fb = floor($b); 677 return round(pow(1024,$b-$fb),$precision).@$units[$fb]; 678} 679 680## AsSpaced converts a string with WikiWords into a spaced version 681## of that string. (It can be overridden via $AsSpacedFunction.) 682function AsSpaced($text) { 683 $text = preg_replace("/([[:lower:]\\d])([[:upper:]])/", '$1 $2', $text); 684 $text = preg_replace('/([^-\\d])(\\d[-\\d]*( |$))/','$1 $2',$text); 685 return preg_replace("/([[:upper:]])([[:upper:]][[:lower:]\\d])/", 686 '$1 $2', $text); 687} 688 689## Lock is used to make sure only one instance of PmWiki is running when 690## files are being written. It does not "lock pages" for editing. 691function Lock($op) { 692 global $WorkDir, $LockFile, $EnableReadOnly; 693 if ($op > 0 && IsEnabled($EnableReadOnly, 0)) 694 Abort('Cannot modify site -- $EnableReadOnly is set', 'readonly'); 695 SDV($LockFile, "$WorkDir/.flock"); 696 mkdirp(dirname($LockFile)); 697 static $lockfp,$curop; 698 if (!$lockfp) $lockfp = @fopen($LockFile, "w"); 699 if (!$lockfp) { 700 if ($op <= 0) return; 701 @unlink($LockFile); 702 $lockfp = fopen($LockFile,"w") or 703 Abort('Cannot acquire lockfile', 'flock'); 704 fixperms($LockFile); 705 } 706 if ($op<0) { flock($lockfp,LOCK_UN); fclose($lockfp); $lockfp=0; $curop=0; } 707 elseif ($op==0) { flock($lockfp,LOCK_UN); $curop=0; } 708 elseif ($op==1 && $curop<1) 709 { session_write_close(); flock($lockfp,LOCK_SH); $curop=1; } 710 elseif ($op==2 && $curop<2) 711 { session_write_close(); flock($lockfp,LOCK_EX); $curop=2; } 712} 713 714## mkdirp creates a directory and its parents as needed, and sets 715## permissions accordingly. 716function mkdirp($dir) { 717 global $ScriptUrl; 718 if (file_exists($dir)) return; 719 if (!file_exists(dirname($dir))) mkdirp(dirname($dir)); 720 if (mkdir($dir, 0777)) { 721 fixperms($dir); 722 if (@touch("$dir/xxx")) { unlink("$dir/xxx"); return; } 723 rmdir($dir); 724 } 725 $parent = realpath(dirname($dir)); 726 $bdir = basename($dir); 727 $perms = decoct(fileperms($parent) & 03777); 728 $msg = "PmWiki needs to have a writable <tt>$dir/</tt> directory 729 before it can continue. You can create the directory manually 730 by executing the following commands on your server: 731 <pre> mkdir $parent/$bdir\n chmod 777 $parent/$bdir</pre> 732 Then, <a href='{$ScriptUrl}'>reload this page</a>."; 733 $safemode = ini_get('safe_mode'); 734 if (!$safemode) $msg .= "<br /><br />Or, for a slightly more 735 secure installation, try executing <pre> chmod 2777 $parent</pre> 736 on your server and following <a target='_blank' href='$ScriptUrl'> 737 this link</a>. Afterwards you can restore the permissions to 738 their current setting by executing <pre> chmod $perms $parent</pre>."; 739 Abort($msg); 740} 741 742## fixperms attempts to correct permissions on a file or directory 743## so that both PmWiki and the account (current dir) owner can manipulate it 744function fixperms($fname, $add = 0, $set = 0) { 745 clearstatcache(); 746 if (!file_exists($fname)) Abort('?no such file'); 747 if ($set) { # advanced admins, $UploadPermSet 748 if (fileperms($fname) != $set) @chmod($fname,$set); 749 } 750 else { 751 $bp = 0; 752 if (fileowner($fname)!=@fileowner('.') && @fileowner('.')!==0) 753 $bp = (is_dir($fname)) ? 007 : 006; 754 if (filegroup($fname)==@filegroup('.')) $bp <<= 3; 755 $bp |= $add; 756 if ($bp && (fileperms($fname) & $bp) != $bp) 757 @chmod($fname,fileperms($fname)|$bp); 758 } 759} 760 761## GlobToPCRE converts wildcard patterns into pcre patterns for 762## inclusion and exclusion. Wildcards beginning with '-' or '!' 763## are treated as things to be excluded. 764function GlobToPCRE($pat) { 765 $pat = preg_quote($pat, '/'); 766 $pat = str_replace(array('\\*', '\\?', '\\[', '\\]', '\\^', '\\-', '\\!'), 767 array('.*', '.', '[', ']', '^', '-', '!'), $pat); 768 $excl = array(); $incl = array(); 769 foreach(preg_split('/,+\s?/', $pat, -1, PREG_SPLIT_NO_EMPTY) as $p) { 770 if ($p[0] == '-' || $p[0] == '!') $excl[] = '^'.substr($p, 1).'$'; 771 else $incl[] = "^$p$"; 772 } 773 return array(implode('|', $incl), implode('|', $excl)); 774} 775 776## FixGlob changes wildcard patterns without '.' to things like 777## '*.foo' (name matches) or 'foo.*' (group matches). 778function FixGlob($x, $rep = '$1*.$2') { 779 return preg_replace('/([\\s,][-!]?)([^\\/.\\s,]+)(?=[\\s,])/', $rep, ",$x,"); 780} 781 782## MatchPageNames reduces $pagelist to those pages with names 783## matching the pattern(s) in $pat. Patterns can be either 784## regexes to include ('/'), regexes to exclude ('!'), or 785## wildcard patterns (all others). 786function MatchPageNames($pagelist, $pat) { 787 # Note: MatchNames() is the generic function matching patterns, 788 # works for attachments and other arrays. We can commit to 789 # keep it generic, even if we someday change MatchPageNames(). 790 return MatchNames($pagelist, $pat); 791} 792function MatchNames($list, $pat) { 793 global $Charset, $EnableRangeMatchUTF8; 794 # allow range matches in utf8; doesn't work on pmwiki.org and possibly elsewhere 795 $pcre8 = (IsEnabled($EnableRangeMatchUTF8,0) && $Charset=='UTF-8')? 'u' : ''; 796 $list = (array)$list; 797 foreach((array)$pat as $p) { 798 if (count($list) < 1) break; 799 if (!$p) continue; 800 switch ($p[0]) { 801 case '/': 802 $list = preg_grep($p, $list); 803 break; 804 case '!': 805 $list = array_diff($list, preg_grep($p, $list)); 806 break; 807 default: 808 list($inclp, $exclp) = GlobToPCRE(str_replace('/', '.', $p)); 809 if ($exclp) 810 $list = array_diff($list, preg_grep("/$exclp/i$pcre8", $list)); 811 if ($inclp) 812 $list = preg_grep("/$inclp/i$pcre8", $list); 813 } 814 } 815 return $list; 816} 817 818## ResolvePageName "normalizes" a pagename based on the current 819## settings of $DefaultPage and $PagePathFmt. It's normally used 820## during initialization to fix up any missing or partial pagenames. 821function ResolvePageName($pagename) { 822 global $DefaultPage, $DefaultGroup, $DefaultName, 823 $GroupPattern, $NamePattern, $EnableFixedUrlRedirect; 824 SDV($DefaultPage, "$DefaultGroup.$DefaultName"); 825 $pagename = preg_replace('!([./][^./]+)\\.html?$!', '$1', $pagename); 826 if ($pagename == '') return $DefaultPage; 827 $p = MakePageName($DefaultPage, $pagename); 828 if (!preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $p)) { 829 header('HTTP/1.1 404 Not Found'); 830 Abort("\$[?invalid page name] \"$p\""); 831 } 832 if (preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $pagename)) 833 return $p; 834 if (IsEnabled($EnableFixedUrlRedirect, 1) 835 && $p && (PageExists($p) || preg_match('/[\\/.]/', $pagename))) 836 { Redirect($p); exit(); } 837 return MakePageName($DefaultPage, "$pagename.$pagename"); 838} 839 840## MakePageName is used to convert a string $str into a fully-qualified 841## pagename. If $str doesn't contain a group qualifier, then 842## MakePageName uses $basepage and $PagePathFmt to determine the 843## group of the returned pagename. 844function MakePageName($basepage, $str) { 845 global $MakePageNameFunction, $PageNameChars, $PagePathFmt, 846 $MakePageNamePatterns, $MakePageNameSplitPattern; 847 if (@$MakePageNameFunction) return $MakePageNameFunction($basepage, $str); 848 SDV($PageNameChars,'-[:alnum:]'); 849 SDV($MakePageNamePatterns, array( 850 "/'/" => '', # strip single-quotes 851 "/[^$PageNameChars]+/" => ' ', # convert everything else to space 852 '/((^|[^-\\w])\\w)/' => 'cb_toupper', # CamelCase 853 '/ /' => '')); 854 SDV($MakePageNameSplitPattern, '/[.\\/]/'); 855 $str = preg_replace('/[#?].*$/', '', $str); 856 $m = preg_split($MakePageNameSplitPattern, $str); 857 if (count($m)<1 || count($m)>2 || $m[0]=='') return ''; 858 ## handle "Group.Name" conversions 859 if (@$m[1] > '') { 860 $group = PPRA($MakePageNamePatterns, $m[0]); 861 $name = PPRA($MakePageNamePatterns, $m[1]); 862 return "$group.$name"; 863 } 864 $name = PPRA($MakePageNamePatterns, $m[0]); 865 $isgrouphome = count($m) > 1; 866 foreach((array)$PagePathFmt as $pg) { 867 if ($isgrouphome && strncmp($pg, '$1.', 3) !== 0) continue; 868 $pn = FmtPageName(str_replace('$1', $name, $pg), $basepage); 869 if (PageExists($pn)) return $pn; 870 } 871 if ($isgrouphome) { 872 foreach((array)$PagePathFmt as $pg) 873 if (strncmp($pg, '$1.', 3) == 0) 874 return FmtPageName(str_replace('$1', $name, $pg), $basepage); 875 return "$name.$name"; 876 } 877 return preg_replace('/[^\\/.]+$/', $name, $basepage); 878} 879 880 881## MakeBaseName uses $BaseNamePatterns to return the "base" form 882## of a given pagename -- i.e., stripping any recipe-defined 883## prefixes or suffixes from the page. 884function MakeBaseName($pagename, $patlist = NULL) { 885 global $BaseNamePatterns; 886 if (is_null($patlist)) $patlist = (array)@$BaseNamePatterns; 887 foreach($patlist as $pat => $rep) 888 $pagename = preg_replace($pat, $rep, $pagename); # TODO 889 return $pagename; 890} 891 892 893## PCache caches basic information about a page and its attributes-- 894## usually everything except page text and page history. This makes 895## for quicker access to certain values in PageVar below. 896function PCache($pagename, $page) { 897 global $PCache; 898 foreach($page as $k=>$v) 899 if ($k!='text' && strpos($k,':')===false) $PCache[$pagename][$k]=$v; 900} 901 902## SetProperty saves a page property into $PCache. For convenience 903## it returns the $value of the property just set. If $sep is supplied, 904## then $value is appended to the current property (with $sep as 905## as separator) instead of replacing it. If $keep is suplied and the 906## property already exists, then $value will be ignored. 907function SetProperty($pagename, $prop, $value, $sep=NULL, $keep=NULL) { 908 global $PCache, $KeepToken; 909 NoCache(); 910 $prop = "=p_$prop"; 911 $value = preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/", 912 "cb_expandkpv", $value); 913 if (!is_null($sep) && isset($PCache[$pagename][$prop])) 914 $value = $PCache[$pagename][$prop] . $sep . $value; 915 if (is_null($keep) || !isset($PCache[$pagename][$prop])) 916 $PCache[$pagename][$prop] = $value; 917 return $PCache[$pagename][$prop]; 918} 919 920 921## PageTextVar loads a page's text variables (defined by 922## $PageTextVarPatterns) into a page's $PCache entry, and returns 923## the property associated with $var. 924function PageTextVar($pagename, $var) { 925 global $PCache, $PageTextVarPatterns, $MaxPageTextVars, $DefaultUnsetPageTextVars, $DefaultEmptyPageTextVars; 926 SDV($MaxPageTextVars, 500); 927 static $status; 928 if (@$status["$pagename:$var"]++ > $MaxPageTextVars) return ''; 929 if (!@$PCache[$pagename]['=pagetextvars']) { 930 $pc = &$PCache[$pagename]; 931 $pc['=pagetextvars'] = 1; 932 $page = RetrieveAuthPage($pagename, 'read', false, READPAGE_CURRENT); 933 if ($page) { 934 foreach((array)$PageTextVarPatterns as $pat) 935 if (preg_match_all($pat, IsEnabled($pc['=preview'],@$page['text']), 936 $match, PREG_SET_ORDER)) 937 foreach($match as $m) { 938 $t = preg_replace("/\\{\\$:{$m[2]}\\}/", '', $m[3]); 939 $pc["=p_{$m[2]}"] = Qualify($pagename, $t); 940 } 941 } 942 } 943 if (! isset($PCache[$pagename]["=p_$var"]) && is_array($DefaultUnsetPageTextVars)) { 944 foreach($DefaultUnsetPageTextVars as $k=>$v) { 945 if (count(MatchNames($var, $k))) { 946 $PCache[$pagename]["=p_$var"] = $v; 947 break; 948 } 949 } 950 SDV($PCache[$pagename]["=p_$var"], ''); # to avoid re-loop 951 } 952 elseif (@$PCache[$pagename]["=p_$var"] == '' && is_array($DefaultEmptyPageTextVars)) { 953 foreach($DefaultEmptyPageTextVars as $k=>$v) { 954 if (count(MatchNames($var, $k))) { 955 $PCache[$pagename]["=p_$var"] = $v; 956 break; 957 } 958 } 959 SDV($PCache[$pagename]["=p_$var"], ''); # to avoid re-loop 960 } 961 return @$PCache[$pagename]["=p_$var"]; 962} 963 964 965function PageVar($pagename, $var, $pn = '') { 966 global $Cursor, $PCache, $FmtPV, $AsSpacedFunction, $ScriptUrl, 967 $EnablePathInfo; 968 if ($var == '$ScriptUrl') return PUE($ScriptUrl); 969 if ($pn) { 970 $pn = isset($Cursor[$pn]) ? $Cursor[$pn] : MakePageName($pagename, $pn); 971 } else $pn = $pagename; 972 if ($pn) { 973 if (preg_match('/^(.+)[.\\/]([^.\\/]+)$/', $pn, $match) 974 && !isset($PCache[$pn]['time']) 975 && (!@$FmtPV[$var] || strpos($FmtPV[$var], '$page') !== false)) { 976 $page = ReadPage($pn, READPAGE_CURRENT); 977 PCache($pn, $page); 978 } 979 @list($d, $group, $name) = $match; 980 $page = &$PCache[$pn]; 981 if (strpos(@$FmtPV[$var], '$authpage') !== false) { 982 if (!isset($page['=auth']['read'])) { 983 $x = RetrieveAuthPage($pn, 'read', false, READPAGE_CURRENT); 984 if ($x) PCache($pn, $x); 985 } 986 if (@$page['=auth']['read']) $authpage = &$page; 987 } 988 } else { $group = ''; $name = ''; } 989 if (@$FmtPV[$var]) return eval("return ({$FmtPV[$var]});"); 990 if (strncmp($var, '$:', 2)==0) return PageTextVar($pn, substr($var, 2)); 991 return ''; 992} 993 994## FmtPageName handles $[internationalization] and $Variable 995## substitutions in strings based on the $pagename argument. 996function FmtPageName($fmt, $pagename) { 997 # Perform $-substitutions on $fmt relative to page given by $pagename 998 global $GroupPattern, $NamePattern, $EnablePathInfo, $ScriptUrl, 999 $GCount, $UnsafeGlobals, $FmtV, $FmtP, $FmtPV, $PCache, $AsSpacedFunction; 1000 if (strpos($fmt,'$')===false) return $fmt; 1001 $fmt = preg_replace_callback('/\\$([A-Z]\\w*Fmt)\\b/','cb_expandglobal',$fmt); 1002 $fmt = preg_replace_callback('/\\$\\[(?>([^\\]]+))\\]/',"cb_expandxlang",$fmt); 1003 $fmt = str_replace('{$ScriptUrl}', '$ScriptUrl', $fmt); 1004 $pprc = new PPRC($pagename); 1005 $fmt = preg_replace_callback('/\\{\\*?(\\$[A-Z]\\w+)\\}/', 1006 array($pprc, 'pagevar'), $fmt); 1007 if (strpos($fmt,'$')===false) return $fmt; 1008 if ($FmtP) $fmt = PPRA($FmtP, $fmt); # FIXME 1009 static $pv, $pvpat; 1010 if ($pv != count($FmtPV)) { 1011 $pvpat = str_replace('$', '\\$', implode('|', array_keys($FmtPV))); 1012 $pv = count($FmtPV); 1013 } 1014 $fmt = preg_replace_callback("/($pvpat)\\b/", array($pprc, 'pagevar'), $fmt); 1015 $fmt = preg_replace_callback('!\\$ScriptUrl/([^?#\'"\\s<>]+)!', 1016 'cb_expandscripturl', $fmt); 1017 if (strpos($fmt,'$')===false) return $fmt; 1018 static $g; 1019 if ($GCount != count($GLOBALS)+count($FmtV)) { 1020 $g = array(); 1021 foreach($GLOBALS as $n=>$v) { 1022 if (is_array($v) || is_object($v) || 1023 isset($FmtV["\$$n"]) || in_array($n,$UnsafeGlobals)) continue; 1024 $g["\$$n"] = $v; 1025 } 1026 $GCount = count($GLOBALS)+count($FmtV); 1027 krsort($g); reset($g); 1028 } 1029 $fmt = str_replace(array_keys($g),array_values($g),$fmt); 1030 $fmt = preg_replace_callback('/(?>(\\$[[:alpha:]]\\w+))/', 1031 "cb_expandfmtv", $fmt); 1032 return $fmt; 1033} 1034function cb_expandglobal($m){ return @$GLOBALS[$m[1]]; } 1035function cb_expandxlang ($m){ return NoCache(XL($m[1])); } 1036function cb_expandfmtv ($m){ 1037 return isset($GLOBALS['FmtV'][$m[1]]) ? $GLOBALS['FmtV'][$m[1]] : $m[1]; 1038} 1039function cb_expandscripturl($m) { 1040 global $EnablePathInfo, $ScriptUrl; 1041 return (@$EnablePathInfo) ? "$ScriptUrl/" . PUE($m[1]) 1042 : "$ScriptUrl?n=".str_replace('/','.',PUE($m[1])); 1043} 1044 1045 1046## FmtPageTitle returns the page title, or the page name. 1047## It localizes standard technical pages (RecentChanges...) 1048function FmtPageTitle($title, $name, $spaced=0) { 1049 if ($title>'') return str_replace("$", "$", $title); 1050 global $SpaceWikiWords, $AsSpacedFunction; 1051 if (preg_match("/^(Site(Admin)? 1052 |(All)?(Site|Group)(Header|Footer|Attributes) 1053 |(Side|Left|Right)Bar 1054 |(Wiki)?Sand[Bb]ox 1055 |(All)?Recent(Changes|Uploads)|(Auth|Edit)Form 1056 |InterMap|PageActions|\\w+QuickReference|\\w+Templates 1057 |NotifyList|AuthUser|ApprovedUrls|(Block|Auth)List 1058 )$/x", $name) && $name != XL($name)) 1059 return XL($name); 1060 return ($spaced || $SpaceWikiWords) ? $AsSpacedFunction($name) : $name; 1061} 1062 1063## FmtTemplateVars uses $vars to replace all occurrences of 1064## {$$key} in $text with $vars['key']. 1065function FmtTemplateVars($text, $vars, $pagename = NULL) { 1066 global $FmtPV, $EnableUndefinedTemplateVars; 1067 if ($pagename) { 1068 $pat = implode('|', array_map('preg_quote', array_keys($FmtPV))); 1069 $pprc = new PPRC($pagename); 1070 $text = preg_replace_callback("/\\{\\$($pat)\\}/", 1071 array($pprc, 'pagevar'), $text); 1072 } 1073 foreach(preg_grep('/^[\\w$]/', array_keys($vars)) as $k) 1074 if (!is_array($vars[$k])) 1075 $text = str_replace("{\$\$$k}", @$vars[$k], $text); 1076 if (! IsEnabled($EnableUndefinedTemplateVars, 0)) 1077 $text = preg_replace("/\\{\\$\\$\\w+\\}/", '', $text); 1078 return $text; 1079} 1080 1081## The XL functions provide translation tables for $[i18n] strings 1082## in FmtPageName(). 1083function XL($key) { 1084 global $XL,$XLLangs; 1085 foreach($XLLangs as $l) if (isset($XL[$l][$key])) return $XL[$l][$key]; 1086 return $key; 1087} 1088function XLSDV($lang,$a) { 1089 global $XL; 1090 foreach($a as $k=>$v) { 1091 if (!isset($XL[$lang][$k])) { 1092 if (preg_match('/^e_(rows|cols)$/', $k)) $v = intval($v); 1093 elseif (preg_match('/^ak_/', $k)) $v = @$v[0]; 1094 $XL[$lang][$k]=$v; 1095 } 1096 } 1097} 1098function XLPage($lang,$p,$nohtml=false) { 1099 global $TimeFmt,$XLLangs,$FarmD, $EnableXLPageScriptLoad; 1100 $page = ReadPage($p, READPAGE_CURRENT); 1101 if (!$page) return; 1102 $text = preg_replace("/=>\\s*\n/",'=> ',@$page['text']); 1103 foreach(explode("\n",$text) as $l) 1104 if (preg_match('/^\\s*[\'"](.+?)[\'"]\\s*=>\\s*[\'"](.+)[\'"]/',$l,$m)) 1105 $xl[stripslashes($m[1])] = stripslashes($nohtml? PHSC($m[2]): $m[2]); 1106 if (isset($xl)) { 1107 if (IsEnabled($EnableXLPageScriptLoad, 0) && @$xl['xlpage-i18n']) { 1108 $i18n = preg_replace('/[^-\\w]/','',$xl['xlpage-i18n']); 1109 include_once("$FarmD/scripts/xlpage-$i18n.php"); 1110 } 1111 if (@$xl['Locale']) setlocale(LC_ALL,$xl['Locale']); 1112 if (@$xl['TimeFmt']) $TimeFmt=$xl['TimeFmt']; 1113 if (!in_array($lang, $XLLangs)) array_unshift($XLLangs, $lang); 1114 XLSDV($lang,$xl); 1115 } 1116} 1117 1118## CmpPageAttr is used with uksort to order a page's elements with 1119## the latest items first. This can make some operations more efficient. 1120function CmpPageAttr($a, $b) { 1121 @list($x, $agmt) = explode(':', $a); 1122 @list($x, $bgmt) = explode(':', $b); 1123 if ($agmt != $bgmt) 1124 return ($agmt==0 || $bgmt==0) ? $agmt - $bgmt : $bgmt - $agmt; 1125 return strcmp($a, $b); 1126} 1127 1128## class PageStore holds objects that store pages via the native 1129## filesystem. 1130class PageStore { 1131 var $dirfmt; 1132 var $iswrite; 1133 var $encodefilenames; 1134 var $attr; 1135 function __construct($d='$WorkDir/$FullName', $w=0, $a=NULL) { 1136 $this->dirfmt = $d; $this->iswrite = $w; $this->attr = (array)$a; 1137 $GLOBALS['PageExistsCache'] = array(); 1138 } 1139 function recodefn($s,$from,$to) { 1140 static $able; 1141 if(is_null($able)) { 1142 # can we rely on iconv() or on mb_convert_encoding() ? 1143 if (function_exists('iconv') && @iconv("UTF-8", "WINDOWS-1252//IGNORE", "te\xd0\xafst")=='test' ) 1144 $able = 'iconv'; 1145 elseif (function_exists('mb_convert_encoding') && @mb_convert_encoding("te\xd0\xafst", "WINDOWS-1252", "UTF-8")=="te?st") 1146 $able = 'mb'; 1147 } 1148 switch ($able) { 1149 case "iconv": 1150 return @iconv($from,"$to//IGNORE",$s); 1151 case "mb": 1152 return @mb_convert_encoding($s,$to,$from); 1153 } 1154 if ($to=='UTF-8' && $from=='WINDOWS-1252') return utf8_decode($s); 1155 if ($from=='UTF-8' && $to=='WINDOWS-1252') return utf8_encode($s); 1156 return $s; 1157 } 1158 function pagefile($pagename) { 1159 global $FarmD; 1160 $dfmt = $this->dirfmt; 1161 if ($pagename > '') { 1162 $pagename = str_replace('/', '.', $pagename); 1163 if ($dfmt == 'wiki.d/{$FullName}') # optimizations for 1164 return $this->PFE("wiki.d/$pagename"); # standard locations 1165 if ($dfmt == '$FarmD/wikilib.d/{$FullName}') # 1166 return $this->PFE("$FarmD/wikilib.d/$pagename"); 1167 if ($dfmt == 'wiki.d/{$Group}/{$FullName}') 1168 return $this->PFE(preg_replace('/([^.]+).*/', 'wiki.d/$1/$0', $pagename)); 1169 } 1170 return $this->PFE(FmtPageName($dfmt, $pagename)); 1171 } 1172 function PFE($f) { # pagefile_encode 1173 if (!$this->encodefilenames) return $f; 1174 global $PageFileEncodeFunction; 1175 return $PageFileEncodeFunction($f); 1176 } 1177 function PFD($f) { # pagefile_decode 1178 if (!$this->encodefilenames) return $f; 1179 global $PageFileDecodeFunction; 1180 return $PageFileDecodeFunction($f); 1181 } 1182 function read($pagename, $since=0) { 1183 $newline = ''; 1184 $urlencoded = false; 1185 $pagefile = $this->pagefile($pagename); 1186 if ($pagefile && ($fp=@fopen($pagefile, "r"))) { 1187 $page = $this->attr; 1188 while (!feof($fp)) { 1189 $line = @fgets($fp, 4096); 1190 while (substr($line, -1, 1) != "\n" && !feof($fp)) 1191 { $line .= fgets($fp, 4096); } 1192 $line = rtrim($line); 1193 if ($urlencoded) $line = urldecode(str_replace('+', '%2b', $line)); 1194 @list($k,$v) = explode('=', $line, 2); 1195 if (!$k) continue; 1196 if ($k == 'version') { 1197 $ordered = (strpos($v, 'ordered=1') !== false); 1198 $urlencoded = (strpos($v, 'urlencoded=1') !== false); 1199 if (strpos($v, 'pmwiki-0.')!==false) $newline="\262"; 1200 } 1201 if ($k == 'newline') { $newline = $v; continue; } 1202 if ($since > 0 && preg_match('/:(\\d+)/', $k, $m) && $m[1] < $since) { 1203 if ($ordered) break; 1204 continue; 1205 } 1206 if ($newline) $v = str_replace($newline, "\n", $v); 1207 $page[$k] = $v; 1208 } 1209 fclose($fp); 1210 } 1211 return $this->recode($pagename, @$page); 1212 } 1213 function write($pagename,$page) { 1214 global $Now, $Version, $Charset, $EnableRevUserAgent, $PageExistsCache, $DenyHtaccessContent; 1215 $page['charset'] = $Charset; 1216 $page['name'] = $pagename; 1217 $page['time'] = $Now; 1218 $page['host'] = $_SERVER['REMOTE_ADDR']; 1219 $page['agent'] = @$_SERVER['HTTP_USER_AGENT']; 1220 if(IsEnabled($EnableRevUserAgent, 0)) $page["agent:$Now"] = $page['agent']; 1221 $page['rev'] = @$page['rev']+1; 1222 unset($page['version']); unset($page['newline']); 1223 uksort($page, 'CmpPageAttr'); 1224 $s = false; 1225 $pagefile = $this->pagefile($pagename); 1226 $dir = dirname($pagefile); mkdirp($dir); 1227 if (!file_exists("$dir/.htaccess") && $fp = @fopen("$dir/.htaccess", "w")) 1228 { fwrite($fp, $DenyHtaccessContent); fclose($fp); } 1229 if ($pagefile && ($fp=fopen("$pagefile,new","w"))) { 1230 $r0 = array('%', "\n", '<'); 1231 $r1 = array('%25', '%0a', '%3c'); 1232 $x = "version=$Version ordered=1 urlencoded=1\n"; 1233 $s = true && fputs($fp, $x); $sz = strlen($x); 1234 foreach($page as $k=>$v) 1235 if ($k > '' && $k[0] != '=') { 1236 $x = str_replace($r0, $r1, "$k=$v") . "\n"; 1237 $s = $s && fputs($fp, $x); $sz += strlen($x); 1238 } 1239 $s = fclose($fp) && $s; 1240 $s = $s && (filesize("$pagefile,new") > $sz * 0.95); 1241 if (file_exists($pagefile)) $s = $s && unlink($pagefile); 1242 $s = $s && rename("$pagefile,new", $pagefile); 1243 } 1244 $s && fixperms($pagefile); 1245 if (!$s) 1246 Abort("Cannot write page to $pagename ($pagefile)...changes not saved"); 1247 PCache($pagename, $page); 1248 unset($PageExistsCache[$pagename]); # PITS:01401 1249 } 1250 function exists($pagename) { 1251 if (!$pagename) return false; 1252 $pagefile = $this->pagefile($pagename); 1253 return ($pagefile && file_exists($pagefile)); 1254 } 1255 function delete($pagename) { 1256 global $Now, $PageExistsCache; 1257 $pagefile = $this->pagefile($pagename); 1258 @rename($pagefile,"$pagefile,del-$Now"); 1259 unset($PageExistsCache[$pagename]); # PITS:01401 1260 } 1261 function ls($pats=NULL) { 1262 global $GroupPattern, $NamePattern; 1263 StopWatch("PageStore::ls begin {$this->dirfmt}"); 1264 $pats=(array)$pats; 1265 array_push($pats, "/^$GroupPattern\.$NamePattern$/"); 1266 $dir = $this->pagefile('$Group.$Name'); 1267 $maxslash = substr_count($dir, '/'); 1268 $dirlist = array(preg_replace('!/*[^/]*\\$.*$!','',$dir)); 1269 $out = array(); 1270 while (count($dirlist)>0) { 1271 $dir = array_shift($dirlist); 1272 $dfp = @opendir($dir); if (!$dfp) { continue; } 1273 $dirslash = substr_count($dir, '/') + 1; 1274 $o = array(); 1275 while ( ($pagefile = readdir($dfp)) !== false) { 1276 if ($pagefile[0] == '.') continue; 1277 if ($dirslash < $maxslash && is_dir("$dir/$pagefile")) 1278 { array_push($dirlist,"$dir/$pagefile"); continue; } 1279 if ($dirslash == $maxslash) $o[] = $this->PFD($pagefile); 1280 } 1281 closedir($dfp); 1282 StopWatch("PageStore::ls merge {$this->dirfmt}"); 1283 $out = array_merge($out, MatchPageNames($o, $pats)); 1284 } 1285 StopWatch("PageStore::ls end {$this->dirfmt}"); 1286 return $out; 1287 } 1288 function recode($pagename, $a) { 1289 if (!$a) return false; 1290 global $Charset, $PageRecodeFunction, $DefaultPageCharset, $EnableOldCharset; 1291 if (function_exists($PageRecodeFunction)) return $PageRecodeFunction($a); 1292 if (IsEnabled($EnableOldCharset)) $a['=oldcharset'] = @$a['charset']; 1293 SDVA($DefaultPageCharset, array(''=>@$Charset)); # pre-2.2.31 RecentChanges 1294 if (@$DefaultPageCharset[$a['charset']]>'') # wrong pre-2.2.30 encs. *-2, *-9, *-13 1295 $a['charset'] = $DefaultPageCharset[@$a['charset']]; 1296 if (!$a['charset'] || $Charset==$a['charset']) return $a; 1297 $from = ($a['charset']=='ISO-8859-1') ? 'WINDOWS-1252' : $a['charset']; 1298 $to = ($Charset=='ISO-8859-1') ? 'WINDOWS-1252' : $Charset; 1299 if($from != $to) { 1300 foreach($a as $k=>$v) $a[$k] = $this->recodefn($v,$from,$to); 1301 } 1302 $a['charset'] = $Charset; 1303 return $a; 1304 } 1305} 1306 1307function ReadPage($pagename, $since=0) { 1308 # read a page from the appropriate directories given by $WikiReadDirsFmt. 1309 global $WikiLibDirs,$Now; 1310 foreach ($WikiLibDirs as $dir) { 1311 $page = $dir->read($pagename, $since); 1312 if ($page) break; 1313 } 1314 if (@!$page) $page['ctime'] = $Now; 1315 if (@!$page['time']) $page['time'] = $Now; 1316 return $page; 1317} 1318 1319function WritePage($pagename,$page) { 1320 global $WikiLibDirs,$WikiDir,$LastModFile; 1321 $WikiDir->iswrite = 1; 1322 for($i=0; $i<count($WikiLibDirs); $i++) { 1323 $wd = &$WikiLibDirs[$i]; 1324 if ($wd->iswrite && $wd->exists($pagename)) break; 1325 } 1326 if ($i >= count($WikiLibDirs)) $wd = &$WikiDir; 1327 $wd->write($pagename,$page); 1328 if ($LastModFile && !@touch($LastModFile)) 1329 { unlink($LastModFile); touch($LastModFile); fixperms($LastModFile); } 1330} 1331 1332function PageExists($pagename) { 1333 ## note: $PageExistsCache might change or disappear someday 1334 global $WikiLibDirs, $PageExistsCache; 1335 if (!isset($PageExistsCache[$pagename])) { 1336 foreach((array)$WikiLibDirs as $dir) 1337 if ($PageExistsCache[$pagename] = $dir->exists($pagename)) break; 1338 } 1339 return $PageExistsCache[$pagename]; 1340} 1341 1342function ListPages($pat=NULL) { 1343 global $WikiLibDirs; 1344 foreach((array)$WikiLibDirs as $dir) 1345 $out = array_unique(array_merge($dir->ls($pat),(array)@$out)); 1346 return $out; 1347} 1348 1349function RetrieveAuthPage($pagename, $level, $authprompt=true, $since=0) { 1350 global $AuthFunction; 1351 SDV($AuthFunction,'PmWikiAuth'); 1352 if (!function_exists($AuthFunction)) return ReadPage($pagename, $since); 1353 return $AuthFunction($pagename, $level, $authprompt, $since); 1354} 1355 1356function Abort($msg, $info='') { 1357 # exit pmwiki with an abort message 1358 global $ScriptUrl, $Charset, $AbortFunction; 1359 if (@$AbortFunction) return $AbortFunction($msg, $info); 1360 if ($info) 1361 $info = "<p class='vspace'><a target='_blank' rel='nofollow' href='http://www.pmwiki.org/pmwiki/info/$info'>$[More information]</a></p>"; 1362 $msg = "<h3>$[PmWiki can't process your request]</h3> 1363 <p class='vspace'>$msg</p> 1364 <p class='vspace'>$[We are sorry for any inconvenience].</p> 1365 $info 1366 <p class='vspace'><a href='$ScriptUrl'>$[Return to] $ScriptUrl</a></p>"; 1367 @header("Content-type: text/html; charset=$Charset"); 1368 echo preg_replace_callback('/\\$\\[([^\\]]+)\\]/', "cb_expandxlang", $msg); 1369 exit; 1370} 1371 1372function Redirect($pagename, $urlfmt='$PageUrl', $redirecturl=null) { 1373 # redirect the browser to $pagename 1374 global $EnableRedirect, $RedirectDelay, $EnableStopWatch; 1375 SDV($RedirectDelay, 0); 1376 clearstatcache(); 1377 if (is_null($redirecturl)) $redirecturl = FmtPageName($urlfmt,$pagename); 1378 if (IsEnabled($EnableRedirect,1) && 1379 (!isset($_REQUEST['redirect']) || $_REQUEST['redirect'])) { 1380 header("Location: $redirecturl"); 1381 header("Content-type: text/html"); 1382 echo "<html><head> 1383 <meta http-equiv='Refresh' Content='$RedirectDelay; URL=$redirecturl' /> 1384 <title>Redirect</title></head><body></body></html>"; 1385 exit; 1386 } 1387 echo "<a href='$redirecturl'>Redirect to $redirecturl</a>"; 1388 if (@$EnableStopWatch && function_exists('StopWatchHTML')) 1389 StopWatchHTML($pagename, 1); 1390 exit; 1391} 1392 1393function PrintFmt($pagename,$fmt) { 1394 global $HTTPHeaders,$FmtV; 1395 if (is_array($fmt)) 1396 { foreach($fmt as $f) PrintFmt($pagename,$f); return; } 1397 if ($fmt == 'headers:') { 1398 foreach($HTTPHeaders as $h) (@$sent++) ? @header($h) : header($h); 1399 return; 1400 } 1401 $x = FmtPageName($fmt,$pagename); 1402 if (strncmp($fmt, 'function:', 9) == 0 && 1403 preg_match('/^function:(\S+)\s*(.*)$/s', $x, $match) && 1404 function_exists($match[1])) 1405 { $match[1]($pagename,$match[2]); return; } 1406 if (strncmp($fmt, 'file:', 5) == 0 && preg_match("/^file:(.+)/s",$x,$match)) { 1407 $filelist = preg_split('/[\\s]+/',$match[1],-1,PREG_SPLIT_NO_EMPTY); 1408 foreach($filelist as $f) { 1409 if (file_exists($f)) { include($f); return; } 1410 } 1411 return; 1412 } 1413 if (substr($x, 0, 7) == 'markup:') 1414 { print MarkupToHTML($pagename, substr($x, 7)); return; } 1415 if (substr($x, 0, 5) == 'wiki:') 1416 { PrintWikiPage($pagename, substr($x, 5), 'read'); return; } 1417 if (substr($x, 0, 5) == 'page:') 1418 { PrintWikiPage($pagename, substr($x, 5), ''); return; } 1419 echo $x; 1420} 1421 1422function PrintWikiPage($pagename, $wikilist=NULL, $auth='read') { 1423 if (is_null($wikilist)) $wikilist=$pagename; 1424 $pagelist = preg_split('/\s+/',$wikilist,-1,PREG_SPLIT_NO_EMPTY); 1425 foreach($pagelist as $p) { 1426 if (PageExists($p)) { 1427 $page = ($auth) ? RetrieveAuthPage($p, $auth, false, READPAGE_CURRENT) 1428 : ReadPage($p, READPAGE_CURRENT); 1429 if ($page['text']) 1430 echo MarkupToHTML($pagename,Qualify($p, $page['text'])); 1431 return; 1432 } 1433 } 1434} 1435 1436function Keep($x, $pool=NULL) { 1437 if(is_array($x)) $x = $x[0]; # used in many callbacks 1438 # Keep preserves a string from being processed by wiki markups 1439 global $BlockPattern, $KeepToken, $KPV, $KPCount; 1440 $x = preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/", 'cb_expandkpv', $x); 1441 if (is_null($pool) && preg_match("!</?($BlockPattern)\\b!", $x)) $pool = 'B'; 1442 $KPCount++; $KPV[$KPCount.$pool]=$x; 1443 return $KeepToken.$KPCount.$pool.$KeepToken; 1444} 1445 1446 1447## MarkupEscape examines markup source and escapes any [@...@] 1448## and [=...=] sequences using Keep(). MarkupRestore undoes the 1449## effect of any MarkupEscape(). 1450function MarkupEscape($text) { 1451 global $EscapePattern; 1452 SDV($EscapePattern, '\\[([=@]).*?\\1\\]'); 1453 return preg_replace_callback("/$EscapePattern/s", "Keep", $text); 1454} 1455function MarkupRestore($text) { 1456 global $KeepToken, $KPV; 1457 return preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/", 'cb_expandkpv', $text); 1458} 1459 1460 1461## Qualify() applies $QualifyPatterns to convert relative links 1462## and references into absolute equivalents. 1463function Qualify($pagename, $text) { 1464 global $QualifyPatterns, $KeepToken, $KPV, $tmp_qualify; 1465 if (!@$QualifyPatterns) return $text; 1466 $text = MarkupEscape($text); 1467 $group = $tmp_qualify['group'] = PageVar($pagename, '$Group'); 1468 $name = $tmp_qualify['name'] = PageVar($pagename, '$Name'); 1469 $tmp_qualify['pagename'] = $pagename; 1470 $text = PPRA((array)$QualifyPatterns, $text); 1471 return MarkupRestore($text); 1472} 1473 1474 1475function CondText($pagename,$condspec,$condtext) { 1476 global $Conditions; 1477 if (!preg_match("/^(\\S+)\\s*(!?)\\s*(\\S+)?\\s*(.*?)\\s*$/", 1478 $condspec,$match)) return ''; 1479 @list($condstr,$condtype,$not,$condname,$condparm) = $match; 1480 if (isset($Conditions[$condname])) { 1481 $tf = @eval("return (".$Conditions[$condname].");"); 1482 if (!$tf xor $not) $condtext=''; 1483 } 1484 return $condtext; 1485} 1486 1487 1488## TextSection extracts a section of text delimited by page anchors. 1489## The $sections parameter can have the form 1490## #abc - [[#abc]] to next anchor 1491## #abc#def - [[#abc]] up to [[#def]] 1492## #abc#, #abc.. - [[#abc]] to end of text 1493## ##abc, #..#abc - beginning of text to [[#abc]] 1494## Returns the text unchanged if no sections are requested, 1495## or false if a requested beginning anchor isn't in the text. 1496function TextSection($text, $sections, $args = NULL) { 1497 $args = (array)$args; 1498 $npat = '[[:alpha:]][-\\w.]*'; 1499 if (!preg_match("/#($npat)?(\\.\\.)?(#($npat)?)?/", $sections, $match)) 1500 return $text; 1501 @list($x, $aa, $dots, $b, $bb) = $match; 1502 if (!$dots && !$b) $bb = $npat; 1503 if ($aa) { 1504 $aa = preg_replace('/\\.\\.$/', '', $aa); 1505 $pos = strpos($text, "[[#$aa]]"); if ($pos === false) return false; 1506 if (@$args['anchors']) 1507 while ($pos > 0 && $text[$pos-1] != "\n") $pos--; 1508 else $pos += strlen("[[#$aa]]"); 1509 $text = substr($text, $pos); 1510 } 1511 if ($bb) 1512 $text = preg_replace("/(\n)[^\n]*\\[\\[#$bb\\]\\].*$/s", '$1', $text, 1); 1513 return $text; 1514} 1515 1516 1517## RetrieveAuthSection extracts a section of text from a page. 1518## If $pagesection starts with anything other than '#', it identifies 1519## the page to extract text from. Otherwise RetrieveAuthSection looks 1520## in the pages given by $list, or in $pagename if $list is not specified. 1521## The selected page is placed in the global $RASPageName variable. 1522## The caller is responsible for calling Qualify() as needed. 1523function RetrieveAuthSection($pagename, $pagesection, $list=NULL, $auth='read') { 1524 global $RASPageName, $PCache; 1525 if ($pagesection[0] != '#') 1526 $list = array(MakePageName($pagename, $pagesection)); 1527 else if (is_null($list)) $list = array($pagename); 1528 foreach((array)$list as $t) { 1529 $t = FmtPageName($t, $pagename); 1530 if (!PageExists($t)) continue; 1531 $tpage = RetrieveAuthPage($t, $auth, false, READPAGE_CURRENT); 1532 if (!$tpage) continue; 1533 $text = TextSection(IsEnabled($PCache[$t]['=preview'],$tpage['text']),$pagesection); 1534 if ($text !== false) { $RASPageName = $t; return $text; } 1535 } 1536 $RASPageName = ''; 1537 return false; 1538} 1539 1540function IncludeText($pagename, $inclspec) { 1541 global $MaxIncludes, $IncludeOpt, $InclCount, $PCache; 1542 SDV($MaxIncludes,50); 1543 SDVA($IncludeOpt, array('self'=>1)); 1544 if (@$InclCount[$pagename]++>=$MaxIncludes) return Keep($inclspec); 1545 $args = array_merge($IncludeOpt, ParseArgs($inclspec)); 1546 while (count($args['#'])>0) { 1547 $k = array_shift($args['#']); $v = array_shift($args['#']); 1548 if ($k=='') { 1549 if ($v[0] != '#') { 1550 if (isset($itext)) continue; 1551 $iname = MakePageName($pagename, $v); 1552 if (!$args['self'] && $iname == $pagename) continue; 1553 $ipage = RetrieveAuthPage($iname, 'read', false, READPAGE_CURRENT); 1554 $itext = IsEnabled($PCache[$iname]['=preview'], @$ipage['text']); 1555 } 1556 $itext = TextSection(@$itext, $v, array('anchors' => 1)); 1557 continue; 1558 } 1559 if (preg_match('/^(?:line|para)s?$/', $k)) { 1560 preg_match('/^(\\d*)(\\.\\.(\\d*))?$/', $v, $match); 1561 @list($x, $a, $dots, $b) = $match; 1562 $upat = ($k[0] == 'p') ? ".*?(\n\\s*\n|$)" : "[^\n]*(?:\n|$)"; 1563 if (!$dots) { $b=$a; $a=0; } 1564 if ($a>0) $a--; 1565 $itext=preg_replace("/^(($upat){0,$b}).*$/s",'$1',$itext,1); 1566 $itext=preg_replace("/^($upat){0,$a}/s",'',$itext,1); 1567 continue; 1568 } 1569 } 1570 $basepage = isset($args['basepage']) 1571 ? MakePageName($pagename, $args['basepage']) 1572 : @$iname; 1573 if ($basepage) $itext = Qualify(@$basepage, @$itext); 1574 return FmtTemplateVars(PVSE(@$itext), $args); 1575} 1576 1577 1578function RedirectMarkup($pagename, $opt) { 1579 $k = Keep("(:redirect $opt:)"); 1580 global $MarkupFrame, $EnableRedirectQuiet; 1581 if (!@$MarkupFrame[0]['redirect']) return $k; 1582 $opt = ParseArgs($opt); 1583 $to = @$opt['to']; if (!$to) $to = @$opt[''][0]; 1584 if (!$to) return $k; 1585 if (preg_match('/^([^#]+)(#[A-Za-z][-\\w]*)$/', $to, $match)) 1586 { $to = $match[1]; $anchor = @$match[2]; } 1587 $to = MakePageName($pagename, $to); 1588 if (!PageExists($to)) return $k; 1589 if ($to == $pagename) return ''; 1590 if (@$opt['from'] 1591 && !MatchPageNames($pagename, FixGlob($opt['from'], '$1*.$2'))) 1592 return ''; 1593 if (preg_match('/^30[1237]$/', @$opt['status'])) 1594 header("HTTP/1.1 {$opt['status']}"); 1595 Redirect($to, "{\$PageUrl}" 1596 . (IsEnabled($EnableRedirectQuiet, 0) && IsEnabled($opt['quiet'], 0) 1597 ? '' : "?from=$pagename") 1598 . $anchor); 1599 exit(); 1600} 1601 1602 1603function Block($b) { 1604 global $BlockMarkups,$HTMLVSpace,$HTMLPNewline,$MarkupFrame; 1605 $mf = &$MarkupFrame[0]; $cs = &$mf['cs']; $vspaces = &$mf['vs']; 1606 $out = ''; 1607 if ($b == 'vspace') { 1608 $vspaces .= "\n"; 1609 while (count($cs)>0 && @end($cs)!='pre' && @$BlockMarkups[@end($cs)][3]==0) 1610 { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; } 1611 return $out; 1612 } 1613 @list($code, $depth, $icol) = explode(',', $b); 1614 if (!$code) $depth = 1; 1615 if (!is_numeric($depth)) $depth = strlen($depth); # PHP8 1616 if (!is_numeric($icol)) $icol = strlen($icol); # PHP8 1617 if ($depth > 0) $depth += @$mf['idep']; 1618 if ($icol > 0) $mf['is'][$depth] = $icol + @$mf['icol']; 1619 @$mf['idep'] = @$mf['icol'] = 0; 1620 while (count($cs)>$depth) 1621 { $c = array_pop($cs); $out .= @$BlockMarkups[$c][2]; } 1622 if (!$code) { 1623 if (@end($cs) == 'p') { $out .= $HTMLPNewline; $code = 'p'; } 1624 else if ($depth < 2) { $code = 'p'; $mf['is'][$depth] = 0; } 1625 else { $out .= $HTMLPNewline; $code = 'block'; } 1626 } 1627 if ($depth>0 && $depth==count($cs) && $cs[$depth-1]!=$code) 1628 { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; } 1629 while (count($cs)>0 && @end($cs)!=$code && 1630 @$BlockMarkups[@end($cs)][3]==0) 1631 { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; } 1632 if ($vspaces) { 1633 $out .= (@end($cs) == 'pre') ? $vspaces : $HTMLVSpace; 1634 $vspaces=''; 1635 } 1636 if ($depth==0) { return $out; } 1637 if ($depth==count($cs)) { return $out.$BlockMarkups[$code][1]; } 1638 while (count($cs)<$depth-1) { 1639 array_push($cs, 'dl'); $mf['is'][count($cs)] = 0; 1640 $out .= $BlockMarkups['dl'][0].'<dd>'; 1641 } 1642 if (count($cs)<$depth) { 1643 array_push($cs,$code); 1644 $out .= $BlockMarkups[$code][0]; 1645 } 1646 return $out; 1647} 1648 1649 1650function MarkupClose($key = '') { 1651 global $MarkupFrame; 1652 $cf = & $MarkupFrame[0]['closeall']; 1653 $out = ''; 1654 if ($key == '' || isset($cf[$key])) { 1655 $k = array_keys((array)$cf); 1656 while ($k) { 1657 $x = array_pop($k); $out .= $cf[$x]; unset($cf[$x]); 1658 if ($x == $key) break; 1659 } 1660 } 1661 return $out; 1662} 1663 1664 1665function FormatTableRow($x, $sep = '\\|\\|') { 1666 global $TableCellAttrFmt, $TableCellAlignFmt, $TableRowAttrFmt, 1667 $TableRowIndexMax, $MarkupFrame, $FmtV, $EnableSimpleTableRowspan; 1668 static $rowcount; 1669 SDV($TableCellAlignFmt, " align='%s'"); 1670 1671 if (IsEnabled($EnableSimpleTableRowspan, 0)) { 1672 $x = preg_replace("/\\|\\|__+(?=\\|\\|)/", '||', $x); 1673 $x = preg_replace("/\\|\\|\\^\\^+(?=\\|\\|)/", '', $x); 1674 } 1675 $x = preg_replace("/$sep\\s*$/",'',$x); 1676 $td = preg_split("/$sep/", $x); $y = ''; 1677 for($i=0;$i<count($td);$i++) { 1678 if ($td[$i]=='') continue; 1679 $FmtV['$TableCellCount'] = $i; 1680 $attr = FmtPageName(@$TableCellAttrFmt, ''); 1681 if (IsEnabled($EnableSimpleTableRowspan, 0)) { 1682 if (preg_match('/(\\+\\++)\\s*$/', $td[$i], $rspn)) { 1683 $td[$i] = preg_replace('/\\+\\++(\\s*)$/', '$1', $td[$i]); 1684 $attr .= " rowspan='".strlen($rspn[1])."'"; 1685 } 1686 } 1687 $td[$i] = preg_replace('/^(!?)\\s+$/', '$1 ', $td[$i]); 1688 if (preg_match('/^!(.*?)!$/',$td[$i],$match)) 1689 { $td[$i]=$match[1]; $t='caption'; $attr=''; } 1690 elseif (preg_match('/^!(.*)$/',$td[$i],$match)) 1691 { $td[$i]=$match[1]; $t='th'; } 1692 else $t='td'; 1693 if (preg_match('/^\\s.*\\s$/',$td[$i])) { 1694 if ($t!='caption') $attr .= sprintf($TableCellAlignFmt, 'center'); 1695 } 1696 elseif (preg_match('/^\\s/',$td[$i])) { $attr .= sprintf($TableCellAlignFmt, 'right'); } 1697 elseif (preg_match('/\\s$/',$td[$i])) { $attr .= sprintf($TableCellAlignFmt, 'left'); } 1698 for ($colspan=1;$i+$colspan<count($td);$colspan++) 1699 if ($td[$colspan+$i]!='') break; 1700 if ($colspan>1) { $attr .= " colspan='$colspan'"; } 1701 $y .= "<$t $attr>".trim($td[$i])."</$t>"; 1702 } 1703 if ($t=='caption') return "<:table,1>$y"; 1704 if (@$MarkupFrame[0]['cs'][0] != 'table') $rowcount = 0; else $rowcount++; 1705 $FmtV['$TableRowCount'] = $rowcount + 1; 1706 $FmtV['$TableRowIndex'] = ($rowcount % $TableRowIndexMax) + 1; 1707 $trattr = FmtPageName(@$TableRowAttrFmt, ''); 1708 return "<:table,1><tr $trattr>$y</tr>"; 1709} 1710 1711function LinkIMap($pagename,$imap,$path,$alt,$txt,$fmt=NULL) { 1712 global $FmtV, $IMap, $IMapLinkFmt, $UrlLinkFmt, $IMapLocalPath, $ScriptUrl, $AddLinkCSS; 1713 SDVA($IMapLocalPath, array('Path:'=>1)); 1714 if (@$IMapLocalPath[$imap]) { 1715 $path = preg_replace('/^(\\w+):/', "$1%3a", $path); # PITS:01260 1716 } 1717 $FmtV['$LinkUrl'] = PUE(str_replace('$1',$path,$IMap[$imap])); 1718 $FmtV['$LinkText'] = $txt; 1719 $FmtV['$LinkAlt'] = Keep(str_replace(array('"',"'"),array('"','''),$alt)); 1720 if (!$fmt) 1721 $fmt = (isset($IMapLinkFmt[$imap])) ? $IMapLinkFmt[$imap] : $UrlLinkFmt; 1722 if(IsEnabled($AddLinkCSS['samedomain'])) { 1723 $parsed_url = parse_url($FmtV['$LinkUrl']); 1724 $parsed_wiki = parse_url($ScriptUrl); 1725 if(! @$parsed_url['host'] || $parsed_url['host'] == $parsed_wiki['host']) { 1726 $fmt = preg_replace('/(<a[^>]*class=["\'])/', "$1{$AddLinkCSS['samedomain']} ", $fmt); 1727 } 1728 } 1729 # remove unused title attributes 1730 if(!$alt) $fmt = preg_replace('/\\stitle=([\'"])\\$LinkAlt\\1/', '', $fmt); 1731 return str_replace(array_keys($FmtV),array_values($FmtV),$fmt); 1732} 1733 1734## These 2 functions hide e-mail addresses from spam bot harvesters 1735## recover them for most users with a javascript utility, 1736## while keeping them readable for users with JS disabled. 1737## Based on Cookbook:DeObMail by Petko Yotov 1738## To enable, set $LinkFunctions['mailto:'] = 'ObfuscateLinkIMap'; 1739function ObfuscateLinkIMap($pagename,$imap,$path,$title,$txt,$fmt=NULL) { 1740 global $FmtV, $IMap, $IMapLinkFmt; 1741 SDVA($IMapLinkFmt, array('obfuscate-mailto:' => 1742 "<span class='_pmXmail' title=\"\$LinkAlt\"><span class='_t'>\$LinkText</span><span class='_m'>\$LinkUrl</span></span>")); 1743 $FmtV['$LinkUrl'] = cb_obfuscate_mail(str_replace('$1',$path,$IMap[$imap])); 1744 $FmtV['$LinkText'] = cb_obfuscate_mail(preg_replace('/^mailto:/i', '', $txt)); 1745 if($FmtV['$LinkText'] == preg_replace('/^mailto:/i', '', $FmtV['$LinkUrl'])) $FmtV['$LinkUrl'] = ''; 1746 else $FmtV['$LinkUrl'] = " -> ".$FmtV['$LinkUrl']; 1747 $FmtV['$LinkAlt'] = str_replace(array('"',"'"),array('"','''),cb_obfuscate_mail($title, 0)); 1748 return str_replace(array_keys($FmtV),array_values($FmtV), $IMapLinkFmt['obfuscate-mailto:']); 1749} 1750 1751function cb_obfuscate_mail($x, $wrap=1) { 1752 $classes = array('.' => '_d', '@' => '_a'); 1753 $texts = array( '.' => XL(' [period] '), '@' => XL(' [snail] ')); 1754 foreach($classes as $k=>$v) 1755 $x = preg_replace("/(\\w)".preg_quote($k)."(\\w)/", 1756 ($wrap? 1757 "$1<span class='$v'>{$texts[$k]}</span>$2" 1758 : "$1{$texts[$k]}$2") 1759 , $x); 1760 return $x; 1761} 1762 1763function LinkPage($pagename,$imap,$path,$alt,$txt,$fmt=NULL) { 1764 global $QueryFragPattern, $LinkPageExistsFmt, $LinkPageSelfFmt, 1765 $LinkPageCreateSpaceFmt, $LinkPageCreateFmt, $LinkTargets, 1766 $EnableLinkPageRelative, $EnableLinkPlusTitlespaced, $AddLinkCSS; 1767 $alt = str_replace(array('"',"'"),array('"','''),$alt); 1768 $path = preg_replace('/(#[-.:\\w]*)#.*$/', '$1', $path); # PITS:01388 1769 if (is_array($txt)) { # PITS:01392 1770 $suffix = $txt[1]; 1771 $txt = $txt[0]; 1772 } 1773 if (!$fmt && @$path[0] == '#') { 1774 $path = preg_replace("/[^-.:\\w]/", '', $path); 1775 if (trim($txt) == '+') $txt = PageVar($pagename, '$Title') . @$suffix; 1776 if ($alt) $alt = " title='$alt'"; 1777 return ($path) ? "<a href='#$path'$alt>".str_replace("$", "$", $txt)."</a>" : ''; 1778 } 1779 if (!preg_match("/^\\s*([^#?]+)($QueryFragPattern)?$/",$path,$match)) 1780 return ''; 1781 $tgtname = MakePageName($pagename, $match[1]); 1782 if (!$tgtname) return ''; 1783 $qf = @$match[2]; 1784 @$LinkTargets[$tgtname]++; 1785 if (!$fmt) { 1786 if (!PageExists($tgtname) && !preg_match('/[&?]action=/', $qf)) 1787 $fmt = preg_match('/\\s/', $txt) 1788 ? $LinkPageCreateSpaceFmt : $LinkPageCreateFmt; 1789 else 1790 $fmt = ($tgtname == $pagename && $qf == '') 1791 ? $LinkPageSelfFmt : $LinkPageExistsFmt; 1792 } 1793 $url = PageVar($tgtname, '$PageUrl'); 1794 if (trim($txt) == '+') $txt = PageVar($tgtname, 1795 IsEnabled($EnableLinkPlusTitlespaced, 0) ? '$Titlespaced' : '$Title') . @$suffix; 1796 $txt = str_replace("$", "$", $txt); 1797 if (@$EnableLinkPageRelative) 1798 $url = preg_replace('!^[a-z]+://[^/]*!i', '', $url); 1799 # remove unused title attributes 1800 if(!$alt) $fmt = preg_replace('/\\stitle=([\'"])\\$LinkAlt\\1/', '', $fmt); 1801 $fmt = str_replace(array('$LinkUrl', '$LinkText', '$LinkAlt'), 1802 array($url.PUE($qf), $txt, Keep($alt)), $fmt); 1803 if(IsEnabled($AddLinkCSS['othergroup'])) { 1804 list($cgroup, ) = explode('.', $pagename); 1805 list($tgroup, ) = explode('.', $tgtname); 1806 if($cgroup != $tgroup) 1807 $fmt = preg_replace('/(<a[^>]*class=["\'])/', "$1{$AddLinkCSS['othergroup']} ", $fmt); 1808 } 1809 return FmtPageName($fmt,$tgtname); 1810} 1811 1812function MakeLink($pagename,$tgt,$txt=NULL,$suffix=NULL,$fmt=NULL) { 1813 global $LinkPattern,$LinkFunctions,$UrlExcludeChars,$ImgExtPattern,$ImgTagFmt, 1814 $LinkTitleFunction; 1815 if(preg_match("/^(.*)(?:\"(.*)\")\\s*$/",$tgt,$x)) list(,$tgt,$title) = $x; 1816 $t = preg_replace('/[()]/','',trim($tgt)); 1817 $t = preg_replace('/<[^>]*>/','',$t); 1818 $t = trim(MarkupRestore($t)); 1819 $txtr = trim(MarkupRestore($txt)); 1820 1821 preg_match("/^($LinkPattern)?(.+)$/",$t,$m); 1822 if (!@$m[1]) $m[1]='<:page>'; 1823 if (preg_match("/(($LinkPattern)([^$UrlExcludeChars]+$ImgExtPattern))(\"(.*)\")?$/",$txtr,$tm)) 1824 $txt = $LinkFunctions[$tm[2]]($pagename,$tm[2],$tm[3],@$tm[5], 1825 $tm[1],$ImgTagFmt); 1826 else { 1827 if (is_null($txt)) { 1828 $txt = preg_replace('/\\([^)]*\\)/','',$tgt); 1829 if ($m[1]=='<:page>') { 1830 $txt = preg_replace('!/\\s*$!', '', $txt); 1831 $txt = preg_replace('!^.*[^<]/!', '', $txt); 1832 } 1833 } 1834 if ($m[1]=='<:page>' && trim($txt) == '+' && $suffix>'') { # PITS:01392 1835 $txt = array(trim($txt), $suffix); 1836 } 1837 else $txt .= $suffix; 1838 } 1839 if (@$LinkTitleFunction) $title = $LinkTitleFunction($pagename,$m,$txt); 1840 else $title = PHSC(MarkupRestore(@$title), ENT_QUOTES); 1841 $out = $LinkFunctions[$m[1]]($pagename,$m[1],@$m[2],@$title,$txt,$fmt); 1842 return preg_replace('/(<[^>]+)\\stitle=(""|\'\')/', '$1', $out); 1843} 1844 1845function Markup($id, $when, $pat=NULL, $rep=NULL, $tracelev=0) { 1846 global $MarkupTable, $EnableMarkupDiag; 1847 unset($GLOBALS['MarkupRules']); 1848 if (preg_match('/^([<>])?(.+)$/', $when, $m)) { 1849 $MarkupTable[$id]['cmd'] = $when; 1850 $MarkupTable[$m[2]]['dep'][$id] = $m[1]; 1851 if (!$m[1]) $m[1] = '='; 1852 if (@$MarkupTable[$m[2]]['seq']) { 1853 $MarkupTable[$id]['seq'] = $MarkupTable[$m[2]]['seq'].$m[1]; 1854 foreach((array)@$MarkupTable[$id]['dep'] as $i=>$m) 1855 Markup($i,"$m$id"); 1856 unset($GLOBALS['MarkupTable'][$id]['dep']); 1857 } 1858 } 1859 if ($pat && !isset($MarkupTable[$id]['pat'])) { 1860 $MarkupTable[$id]['pat'] = $pat; 1861 $MarkupTable[$id]['rep'] = $rep; 1862 1863 $oldpat = preg_match('!/[^/]*e[^/]*$!', $pat); 1864 if (IsEnabled($EnableMarkupDiag, 0) || $oldpat) { 1865 $exmark = $oldpat ? '!' : ' '; 1866 if (function_exists('debug_backtrace')) { 1867 $dbg = debug_backtrace(); 1868 $dbginfo = $dbg[$tracelev]; 1869 $MarkupTable[$id]['dbg'] = "$exmark file: {$dbginfo['file']}, " 1870 . "line: {$dbginfo['line']}, pat: {$dbginfo['args'][2]}"; 1871 } 1872 else 1873 $MarkupTable[$id]['dbg'] = "$exmark id: '$id', pat: '$pat'"; 1874 } 1875 } 1876} 1877 1878function Markup_e($id, $when, $pat, $rep, $template = 'markup_e') { 1879 if (!is_callable($rep)) $rep = PCCF($rep, $template); 1880 Markup($id, $when, $pat, $rep, 1); 1881} 1882 1883function DisableMarkup() { 1884 global $MarkupTable; 1885 $idlist = func_get_args(); 1886 unset($GLOBALS['MarkupRules']); 1887 while (count($idlist)>0) { 1888 $id = array_shift($idlist); 1889 if (is_array($id)) { $idlist = array_merge($idlist, $id); continue; } 1890 $MarkupTable[$id] = array('cmd' => 'none', 'pat'=>''); 1891 } 1892} 1893 1894function mpcmp($a,$b) { return @strcmp($a['seq'].'=',$b['seq'].'='); } 1895function BuildMarkupRules() { 1896 global $MarkupTable,$MarkupRules,$LinkPattern; 1897 if (!$MarkupRules) { 1898 uasort($MarkupTable,'mpcmp'); 1899 foreach($MarkupTable as $id=>$m) 1900 if (@$m['pat'] && @$m['seq']) { 1901 $MarkupRules[str_replace('\\L',$LinkPattern,$m['pat'])] 1902 = array($m['rep'], $id); 1903 } 1904 } 1905 return $MarkupRules; 1906} 1907 1908 1909function MarkupToHTML($pagename, $text, $opt = NULL) { 1910 # convert wiki markup text to HTML output 1911 global $MarkupRules, $MarkupFrame, $MarkupFrameBase, $WikiWordCount, 1912 $K0, $K1, $RedoMarkupLine, $MarkupToHTML; 1913 $MarkupToHTML['pagename'] = $pagename; 1914 1915 StopWatch('MarkupToHTML begin'); 1916 array_unshift($MarkupFrame, array_merge($MarkupFrameBase, (array)$opt)); 1917 $MarkupFrame[0]['wwcount'] = $WikiWordCount; 1918 foreach((array)$text as $l) 1919 $lines[] = $MarkupFrame[0]['escape'] ? PVSE($l) : $l; 1920 $lines[] = '(:closeall:)'; 1921 $out = ''; 1922 while (count($lines)>0) { 1923 $x = array_shift($lines); 1924 $RedoMarkupLine=0; 1925 $markrules = BuildMarkupRules(); 1926 foreach($markrules as $p=>$r) { 1927 list($r, $id) = (array)$r; 1928 $MarkupToHTML['markupid'] = $id; 1929 if ($p[0] == '/') { 1930 if (is_callable($r)) $x = preg_replace_callback($p,$r,$x); 1931 else $x=preg_replace($p,$r,$x); # simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting 1932 } 1933 elseif (strstr($x,$p)!==false) $x=eval($r); 1934 if (isset($php_errormsg)) ### TODO: $php_errormsg removed since PHP 8 1935 { echo "ERROR: pat=$p $php_errormsg"; unset($php_errormsg); } 1936 if ($RedoMarkupLine) { $lines=array_merge((array)$x,$lines); continue 2; } 1937 } 1938 if ($x>'') $out .= "$x\n"; 1939 } 1940 foreach((array)(@$MarkupFrame[0]['posteval']) as $v) eval($v); 1941 array_shift($MarkupFrame); 1942 StopWatch('MarkupToHTML end'); 1943 return $out; 1944} 1945 1946function HandleBrowse($pagename, $auth = 'read') { 1947 # handle display of a page 1948 global $DefaultPageTextFmt, $PageNotFoundHeaderFmt, $HTTPHeaders, 1949 $EnableHTMLCache, $NoHTMLCache, $PageCacheFile, $LastModTime, $IsHTMLCached, 1950 $FmtV, $HandleBrowseFmt, $PageStartFmt, $PageEndFmt, $PageRedirectFmt; 1951 $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT); 1952 if (!$page) Abort("?cannot read $pagename"); 1953 PCache($pagename,$page); 1954 if (PageExists($pagename)) $text = @$page['text']; 1955 else { 1956 SDV($DefaultPageTextFmt,'(:include $[{$SiteGroup}.PageNotFound]:)'); 1957 $text = FmtPageName($DefaultPageTextFmt, $pagename); 1958 SDV($PageNotFoundHeaderFmt, 'HTTP/1.1 404 Not Found'); 1959 SDV($HTTPHeaders['status'], $PageNotFoundHeaderFmt); 1960 } 1961 $opt = array(); 1962 SDV($PageRedirectFmt,"<p><i>($[redirected from] <a rel='nofollow' 1963 href='{\$PageUrl}?action=edit'>{\$FullName}</a>)</i></p>\n"); 1964 if (@!$_GET['from']) { $opt['redirect'] = 1; $PageRedirectFmt = ''; } 1965 else { 1966 $frompage = MakePageName($pagename, $_GET['from']); 1967 $PageRedirectFmt = (!$frompage) ? '' 1968 : FmtPageName($PageRedirectFmt, $frompage); 1969 } 1970 if (@$EnableHTMLCache && !$NoHTMLCache && $PageCacheFile && 1971 @filemtime($PageCacheFile) > $LastModTime) { 1972 list($ctext) = unserialize(file_get_contents($PageCacheFile)); 1973 $FmtV['$PageText'] = "<!--cached-->$ctext"; 1974 $IsHTMLCached = 1; 1975 StopWatch("HandleBrowse: using cached copy"); 1976 } else { 1977 $IsHTMLCached = 0; 1978 $text = '(:groupheader:)'.@$text.'(:groupfooter:)'; 1979 $t1 = time(); 1980 $FmtV['$PageText'] = MarkupToHTML($pagename, $text, $opt); 1981 if (@$EnableHTMLCache > 0 && !$NoHTMLCache && $PageCacheFile 1982 && (time() - $t1 + 1) >= $EnableHTMLCache) { 1983 $fp = @fopen("$PageCacheFile,new", "x"); 1984 if ($fp) { 1985 StopWatch("HandleBrowse: caching page"); 1986 fwrite($fp, serialize(array($FmtV['$PageText']))); fclose($fp); 1987 rename("$PageCacheFile,new", $PageCacheFile); 1988 } 1989 } 1990 } 1991 SDV($HandleBrowseFmt,array(&$PageStartFmt, &$PageRedirectFmt, '$PageText', 1992 &$PageEndFmt)); 1993 PrintFmt($pagename,$HandleBrowseFmt); 1994} 1995 1996 1997## UpdatePage goes through all of the steps needed to update a page, 1998## preserving page history, computing link targets, page titles, 1999## and other page attributes. It does this by calling each entry 2000## in $EditFunctions. $pagename is the name of the page to be updated, 2001## $page is the old version of the page (used for page history), 2002## $new is the new version of the page to be saved, and $fnlist is 2003## an optional list of functions to use instead of $EditFunctions. 2004function UpdatePage(&$pagename, &$page, &$new, $fnlist = NULL) { 2005 global $EditFunctions, $IsPagePosted; 2006 StopWatch("UpdatePage: begin $pagename"); 2007 if (is_null($fnlist)) $fnlist = $EditFunctions; 2008 $IsPagePosted = false; 2009 foreach((array)$fnlist as $fn) { 2010 StopWatch("UpdatePage: $fn ($pagename)"); 2011 $fn($pagename, $page, $new); 2012 } 2013 StopWatch("UpdatePage: end $pagename"); 2014 return $IsPagePosted; 2015} 2016 2017# AutoCheckToken verifies if the posted content was sent 2018# from the website forms, to prevent CSRF 2019function AutoCheckToken() { 2020 # TODO: Work in progress (Jan 2021), releasing for 2021 return true; 2022 2023 global $EnablePost, $AutoCheckTokenActions, $EnablePmToken, 2024 $FmtV, $action, $BlockMessageFmt, $MessagesFmt; 2025 2026 # a quick way to disable tokens 2027 if(! IsEnabled($EnablePmToken, 1)) return true; 2028 2029 SDVA($AutoCheckTokenActions, array( # 1=POST, 2=GET, 0=disabled 2030 'edit' => 1, 2031 'postattr' => 1, 2032 'postupload' => 1, 2033 'approvesites' => 2, 2034 'approveurls' => 2, 2035 )); 2036 $tname = $FmtV['$TokenName']; 2037 $x = @$AutoCheckTokenActions[$action]; 2038 if (!$x) return true; 2039 elseif ($x==1) { 2040 if ( count($_POST) < 1 || pmtoken(''.@$_POST[$tname]) ) return true; 2041 } 2042 elseif($x==2 && pmtoken(''.@$_GET[$tname])) return true; 2043 2044 $EnablePost = 0; 2045 $MessagesFmt[] = $BlockMessageFmt; 2046 $MessagesFmt[] = XL('Token invalid or missing.'); 2047 return false; 2048} 2049 2050# EditTemplate allows a site administrator to pre-populate new pages 2051# with the contents of another page. 2052function EditTemplate($pagename, &$page, &$new) { 2053 global $EditTemplatesFmt; 2054 if (@$new['text'] > '') return; 2055 if (@$_REQUEST['template'] && PageExists($_REQUEST['template'])) { 2056 $p = RetrieveAuthPage($_REQUEST['template'], 'read', false, 2057 READPAGE_CURRENT); 2058 if ($p['text'] > '') $new['text'] = $p['text']; 2059 return; 2060 } 2061 foreach((array)$EditTemplatesFmt as $t) { 2062 $p = RetrieveAuthPage(FmtPageName($t,$pagename), 'read', false, 2063 READPAGE_CURRENT); 2064 if (@$p['text'] > '') { $new['text'] = $p['text']; return; } 2065 } 2066} 2067 2068# RestorePage handles returning to the version of text as of 2069# the version given by $restore or $_REQUEST['restore']. 2070function RestorePage($pagename,&$page,&$new,$restore=NULL) { 2071 if (is_null($restore)) $restore=@$_REQUEST['restore']; 2072 if (!$restore) return; 2073 $t = $page['text']; 2074 $nl = (substr($t,-1)=="\n"); 2075 $t = explode("\n",$t); 2076 if ($nl) array_pop($t); 2077 krsort($page); reset($page); 2078 foreach($page as $k=>$v) { 2079 if ($k<$restore) break; 2080 if (strncmp($k, 'diff:', 5) != 0) continue; 2081 foreach(explode("\n",$v) as $x) { 2082 if (preg_match('/^(\\d+)(,(\\d+))?([adc])(\\d+)/',$x,$match)) { 2083 $a1 = $a2 = $match[1]; 2084 if ($match[3]) $a2=$match[3]; 2085 $b1 = $match[5]; 2086 if ($match[4]=='d') array_splice($t,$b1,$a2-$a1+1); 2087 if ($match[4]=='c') array_splice($t,$b1-1,$a2-$a1+1); 2088 continue; 2089 } 2090 if (strncmp($x,'< ',2) == 0) { $nlflag=true; continue; } 2091 if (preg_match('/^> (.*)$/',$x,$match)) { 2092 $nlflag=false; 2093 array_splice($t,$b1-1,0,$match[1]); $b1++; 2094 } 2095 if ($x=='\\ No newline at end of file') $nl=$nlflag; 2096 } 2097 } 2098 if ($nl) $t[]=''; 2099 $new['text']=implode("\n",$t); 2100 $new['=preview'] = $new['text']; 2101 PCache($pagename, $new); 2102 return $new['text']; 2103} 2104 2105## ReplaceOnSave performs text replacements on the text being posted. 2106## Patterns held in $ROEPatterns are replaced on every edit request, 2107## patterns held in $ROSPatterns are replaced only when the page 2108## is being posted (as signaled by $EnablePost). 2109function ReplaceOnSave($pagename,&$page,&$new) { 2110 global $EnablePost, $ROSPatterns, $ROEPatterns; 2111 $new['text'] = ProcessROESPatterns(@$new['text'], $ROEPatterns); 2112 if ($EnablePost) { 2113 $new['text'] = ProcessROESPatterns($new['text'], $ROSPatterns); 2114 } 2115 $new['=preview'] = $new['text']; 2116 PCache($pagename, $new); 2117} 2118function ProcessROESPatterns($text, $patterns) { 2119 global $EnableROSEscape; 2120 if (IsEnabled($EnableROSEscape, 0)) $text = MarkupEscape($text); 2121 $text = PPRA((array)@$patterns, $text); 2122 if (IsEnabled($EnableROSEscape, 0)) $text = MarkupRestore($text); 2123 return $text; 2124} 2125 2126function SaveAttributes($pagename,&$page,&$new) { 2127 global $EnablePost, $LinkTargets, $SaveAttrPatterns, $PCache, 2128 $SaveProperties; 2129 if (!$EnablePost) return; 2130 $text = PPRA($SaveAttrPatterns, $new['text']); 2131 $LinkTargets = array(); 2132 $new['=html'] = MarkupToHTML($pagename,$text); 2133 $new['targets'] = implode(',',array_keys((array)$LinkTargets)); 2134 $p = & $PCache[$pagename]; 2135 foreach((array)$SaveProperties as $k) { 2136 if (@$p["=p_$k"]) $new[$k] = $p["=p_$k"]; 2137 else unset($new[$k]); 2138 } 2139 unset($new['excerpt']); 2140} 2141 2142function PostPage($pagename, &$page, &$new) { 2143 global $DiffKeepDays, $DiffFunction, $DeleteKeyPattern, $EnablePost, 2144 $Now, $Charset, $Author, $WikiDir, $IsPagePosted, $DiffKeepNum; 2145 SDV($DiffKeepDays,3650); 2146 SDV($DiffKeepNum,20); 2147 SDV($DeleteKeyPattern,"^\\s*delete\\s*$"); 2148 $IsPagePosted = false; 2149 if ($EnablePost) { 2150 $new['charset'] = $Charset; # kept for now, may be needed if custom PageStore 2151 $new['author'] = @$Author; 2152 $new["author:$Now"] = @$Author; 2153 $new["host:$Now"] = $_SERVER['REMOTE_ADDR']; 2154 $diffclass = preg_replace('/\\W/','',@$_POST['diffclass']); 2155 if ($page['time']>0 && function_exists(@$DiffFunction)) 2156 $new["diff:$Now:{$page['time']}:$diffclass"] = 2157 $DiffFunction($new['text'],@$page['text']); 2158 $keepgmt = $Now-$DiffKeepDays * 86400; 2159 $keepnum = array(); 2160 $keys = array_keys($new); 2161 foreach($keys as $k) 2162 if (preg_match("/^\\w+:(\\d+)/",$k,$match)) { 2163 $keepnum[$match[1]] = 1; 2164 if (count($keepnum)>$DiffKeepNum && $match[1]<$keepgmt) 2165 unset($new[$k]); 2166 } 2167 if (preg_match("/$DeleteKeyPattern/",$new['text'])){ 2168 if (@$new['passwdattr']>'' && !CondAuth($pagename, 'attr')) 2169 Abort('$[The page has an "attr" attribute and cannot be deleted.]'); 2170 else $WikiDir->delete($pagename); 2171 } 2172 else WritePage($pagename,$new); 2173 $IsPagePosted = true; 2174 } 2175} 2176 2177function PostRecentChanges($pagename,$page,$new,$Fmt=null) { 2178 global $IsPagePosted, $RecentChangesFmt, $RCDelimPattern, $RCLinesMax, 2179 $EnableRCDiffBytes; 2180 if (!$IsPagePosted && $Fmt==null) return; 2181 if ($Fmt==null) $Fmt = $RecentChangesFmt; 2182 foreach($Fmt as $rcfmt=>$pgfmt) { 2183 $rcname = FmtPageName($rcfmt,$pagename); if (!$rcname) continue; 2184 $pgtext = FmtPageName($pgfmt,$pagename); if (!$pgtext) continue; 2185 if (@$seen[$rcname]++) continue; 2186 2187 if (IsEnabled($EnableRCDiffBytes, 0)) { 2188 $pgtext = PPRA(array( 2189 '/\\(([+-])(\\d+)\\)(\\s*=\\]\\s*)$/'=>'$3%diffmarkup%{$1($1$2)$1}%%', 2190 '/\\(\\+(0\\)\\+\\}%%)$/'=>'(±$1'), $pgtext); 2191 } 2192 $rcpage = ReadPage($rcname); 2193 $rcelim = preg_quote(preg_replace("/$RCDelimPattern.*$/",' ',$pgtext),'/'); 2194 $rcpage['text'] = preg_replace("/^.*$rcelim.*\n/m", '', @$rcpage['text']); 2195 if (!preg_match("/$RCDelimPattern/",$rcpage['text'])) 2196 $rcpage['text'] .= "$pgtext\n"; 2197 else 2198 $rcpage['text'] = preg_replace("/([^\n]*$RCDelimPattern.*\n)/", 2199 str_replace("$", "\\$", $pgtext) . "\n$1", $rcpage['text'], 1); 2200 if (@$RCLinesMax > 0) 2201 $rcpage['text'] = implode("\n", array_slice( 2202 explode("\n", $rcpage['text'], $RCLinesMax + 1), 0, $RCLinesMax)); 2203 WritePage($rcname, $rcpage); 2204 } 2205} 2206 2207function AutoCreateTargets($pagename, &$page, &$new) { 2208 global $IsPagePosted, $AutoCreate, $LinkTargets; 2209 if (!$IsPagePosted) return; 2210 foreach((array)@$AutoCreate as $pat => $init) { 2211 if (is_null($init)) continue; 2212 foreach(preg_grep($pat, array_keys((array)@$LinkTargets)) as $aname) { 2213 if (PageExists($aname)) continue; 2214 $x = RetrieveAuthPage($aname, 'edit', false, READPAGE_CURRENT); 2215 if (!$x) continue; 2216 WritePage($aname, $init); 2217 } 2218 } 2219} 2220 2221function PreviewPage($pagename,&$page,&$new) { 2222 global $IsPageSaved, $FmtV, $ROSPatterns; 2223 if (@$_REQUEST['preview']) { 2224 $text = ProcessROESPatterns($new['text'], $ROSPatterns); 2225 $text = '(:groupheader:)'.$text.'(:groupfooter:)'; 2226 $FmtV['$PreviewText'] = MarkupToHTML($pagename,$text); 2227 } 2228} 2229 2230function HandleEdit($pagename, $auth = 'edit') { 2231 global $IsPagePosted, $EditFields, $ChangeSummary, $EditFunctions, 2232 $EnablePost, $FmtV, $Now, $EditRedirectFmt, $EnableRCDiffBytes, 2233 $PageEditForm, $HandleEditFmt, $PageStartFmt, $PageEditFmt, $PageEndFmt; 2234 SDV($EditRedirectFmt, '$FullName'); 2235 if (@$_POST['cancel']) 2236 { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; } 2237 Lock(2); 2238 $page = RetrieveAuthPage($pagename, $auth, true); 2239 if (!$page) Abort("?cannot edit $pagename"); 2240 $new = $page; 2241 foreach((array)$EditFields as $k) 2242 if (isset($_POST[$k])) $new[$k]=str_replace("\r",'',stripmagic($_POST[$k])); 2243 2244 if (IsEnabled($EnableRCDiffBytes, 0) && isset($new['text'])) { 2245 $bytes = strlen($new['text']) - strlen(@$page['text']); 2246 if ($bytes>=0) $bytes = "+$bytes"; 2247 $ChangeSummary = rtrim($ChangeSummary) . " ($bytes)"; 2248 } 2249 $new['csum'] = $ChangeSummary; 2250 if ($ChangeSummary) $new["csum:$Now"] = $ChangeSummary; 2251 $EnablePost &= (bool)preg_grep('/^post/', array_keys(@$_POST)); 2252 $new['=preview'] = @$new['text']; 2253 PCache($pagename, $new); 2254 UpdatePage($pagename, $page, $new); 2255 Lock(0); 2256 if ($IsPagePosted && !@$_POST['postedit']) 2257 { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; } 2258 $FmtV['$DiffClassMinor'] = 2259 (@$_POST['diffclass']=='minor') ? "checked='checked'" : ''; 2260 $FmtV['$EditText'] = 2261 str_replace('$','$',PHSC(@$new['text'],ENT_NOQUOTES)); 2262 $FmtV['$EditBaseTime'] = $Now; 2263 $FmtV['$TokenValue'] = pmtoken(); 2264 if (@$PageEditForm) { 2265 $efpage = FmtPageName($PageEditForm, $pagename); 2266 $form = RetrieveAuthPage($efpage, 'read', false, READPAGE_CURRENT); 2267 if (!$form || !@$form['text']) 2268 Abort("?unable to retrieve edit form $efpage", 'editform'); 2269 $FmtV['$EditForm'] = MarkupToHTML($pagename, $form['text']); 2270 } 2271 SDV($PageEditFmt, "<div id='wikiedit'> 2272 <h2 class='wikiaction'>$[Editing {\$FullName}]</h2> 2273 <form method='post' rel='nofollow' action='\$PageUrl?action=edit'> 2274 <input type='hidden' name='action' value='edit' /> 2275 <input type='hidden' name='n' value='\$FullName' /> 2276 <input type='hidden' name='basetime' value='\$EditBaseTime' /> 2277 <input type='hidden' name='\$TokenName' value='\$TokenValue' /> 2278 \$EditMessageFmt 2279 <textarea id='text' name='text' rows='25' cols='60' 2280 onkeydown='if (event.keyCode==27) event.returnValue=false;' 2281 >\$EditText</textarea><br /> 2282 <input type='submit' name='post' value=' $[Save] ' />"); 2283 SDV($HandleEditFmt, array(&$PageStartFmt, &$PageEditFmt, &$PageEndFmt)); 2284 PrintFmt($pagename, $HandleEditFmt); 2285} 2286 2287function HandleSource($pagename, $auth = 'read') { 2288 global $HTTPHeaders; 2289 $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT); 2290 if (!$page) Abort("?cannot source $pagename"); 2291 foreach ($HTTPHeaders as $h) { 2292 $h = preg_replace('!^Content-type:\\s+text/html!i', 2293 'Content-type: text/plain', $h); 2294 header($h); 2295 } 2296 echo @$page['text']; 2297} 2298 2299## PmWikiAuth provides password-protection of pages using PHP sessions. 2300## It is normally called from RetrieveAuthPage. Since RetrieveAuthPage 2301## can be called a lot within a single page execution (i.e., for every 2302## page accessed), we cache the results of site passwords and 2303## GroupAttribute pages to be able to speed up subsequent calls. 2304function PmWikiAuth($pagename, $level, $authprompt=true, $since=0) { 2305 global $DefaultPasswords, $GroupAttributesFmt, $AllowPassword, 2306 $AuthCascade, $FmtV, $AuthPromptFmt, $PageStartFmt, $PageEndFmt, 2307 $AuthId, $AuthList, $NoHTMLCache; 2308 static $acache; 2309 SDV($GroupAttributesFmt,'$Group/GroupAttributes'); 2310 SDV($AllowPassword,'nopass'); 2311 $page = ReadPage($pagename, $since); 2312 if (!$page) { return false; } 2313 if (!isset($acache)) 2314 SessionAuth($pagename, (@$_POST['authpw']) 2315 ? array('authpw' => array($_POST['authpw'] => 1)) 2316 : ''); 2317 if (@$AuthId) { 2318 $AuthList["id:$AuthId"] = 1; 2319 $AuthList["id:-$AuthId"] = -1; 2320 $AuthList["id:*"] = 1; 2321 } 2322 ## To allow @_site_edit in GroupAttributes, we cache it first 2323 if (!isset($acache['@site'])) { 2324 foreach($DefaultPasswords as $k => $v) { 2325 $x = array(2, array(), ''); 2326 $acache['@site'][$k] = IsAuthorized($v, 'site', $x); 2327 $AuthList["@_site_$k"] = $acache['@site'][$k][0] ? 1 : 0; 2328 } 2329 } 2330 $gn = FmtPageName($GroupAttributesFmt, $pagename); 2331 if (!isset($acache[$gn])) { 2332 $gp = ReadPage($gn, READPAGE_CURRENT); 2333 foreach($DefaultPasswords as $k => $v) { 2334 $acache[$gn][$k] = IsAuthorized(@$gp["passwd$k"], 'group', 2335 $acache['@site'][$k]); 2336 } 2337 } 2338 foreach($DefaultPasswords as $k => $v) 2339 list($page['=auth'][$k], $page['=passwd'][$k], $page['=pwsource'][$k]) = 2340 IsAuthorized(@$page["passwd$k"], 'page', $acache[$gn][$k]); 2341 foreach($AuthCascade as $k => $t) { 2342 if ($page['=auth'][$k]+0 == 2) { 2343 $page['=auth'][$k] = $page['=auth'][$t]; 2344 if ($page['=passwd'][$k] = $page['=passwd'][$t]) # assign 2345 $page['=pwsource'][$k] = "cascade:$t"; 2346 } 2347 } 2348 if (@$page['=auth']['admin']) 2349 foreach($page['=auth'] as $lv=>$a) @$page['=auth'][$lv] = 3; 2350 if (@$page['=passwd']['read']) $NoHTMLCache |= 2; 2351 if ($level=='ALWAYS' || @$page['=auth'][$level]) return $page; 2352 if (!$authprompt) return false; 2353 $GLOBALS['AuthNeeded'] = (@$_POST['authpw']) 2354 ? $page['=pwsource'][$level] . ' ' . $level : ''; 2355 PCache($pagename, $page); 2356 $postvars = ''; 2357 foreach($_POST as $k=>$v) { 2358 if ($k == 'authpw' || $k == 'authid') continue; 2359 $k = PHSC(stripmagic($k), ENT_QUOTES); 2360 if (is_array($v)) { 2361 foreach($v as $vk=>$vv) { 2362 $vk = PHSC(stripmagic($vk), ENT_QUOTES); 2363 $vv = str_replace('$', '$', 2364 PHSC(stripmagic($vv), ENT_COMPAT)); 2365 $postvars .= "<input type='hidden' name='{$k}[{$vk}]' value=\"$vv\" />\n"; 2366 } 2367 } 2368 else { 2369 $v = str_replace('$', '$', 2370 PHSC(stripmagic($v), ENT_COMPAT)); 2371 $postvars .= "<input type='hidden' name='$k' value=\"$v\" />\n"; 2372 } 2373 } 2374 $FmtV['$PostVars'] = $postvars; 2375 $r = str_replace("'", '%37', stripmagic($_SERVER['REQUEST_URI'])); 2376 SDV($AuthPromptFmt,array(&$PageStartFmt, 2377 "<p><b>$[Password required]</b></p> 2378 <form name='authform' action='$r' method='post'> 2379 $[Password]: <input tabindex='1' type='password' name='authpw' 2380 value='' /> 2381 <input type='submit' value='$[OK]' />\$PostVars</form> 2382 <script language='javascript' type='text/javascript'><!-- 2383 document.authform.authpw.focus() //--></script>", &$PageEndFmt)); 2384 PrintFmt($pagename,$AuthPromptFmt); 2385 exit; 2386} 2387 2388function IsAuthorized($chal, $source, &$from) { 2389 global $AuthList, $AuthPw, $AllowPassword; 2390 if (!$chal) return $from; 2391 $auth = 0; 2392 $passwd = array(); 2393 foreach((array)$chal as $c) { 2394 $x = ''; 2395 $pwchal = preg_split('/([, ]|\\w+:)/', $c, -1, PREG_SPLIT_DELIM_CAPTURE); 2396 foreach($pwchal as $pw) { 2397 if ($pw == ',' || $pw == '') continue; 2398 else if ($pw == ' ') { $x = ''; continue; } 2399 else if (substr($pw, -1, 1) == ':') { $x = $pw; continue; } 2400 else if ($pw[0] != '@' && $x > '') $pw = $x . $pw; 2401 if (!$pw) continue; 2402 $passwd[] = $pw; 2403 if ($auth < 0) continue; 2404 if ($x || $pw[0] == '@') { 2405 if (@$AuthList[$pw]) $auth = $AuthList[$pw]; 2406 continue; 2407 } 2408 if ($AllowPassword && pmcrypt($AllowPassword, $pw) == $pw) # nopass 2409 { $auth=1; continue; } 2410 foreach((array)$AuthPw as $pwresp) # password 2411 if (pmcrypt($pwresp, $pw) == $pw) { $auth=1; continue; } 2412 } 2413 } 2414 if (!$passwd) return $from; 2415 if ($auth < 0) $auth = 0; 2416 return array($auth, $passwd, $source); 2417} 2418 2419 2420## SessionAuth works with PmWikiAuth to manage authorizations 2421## as stored in sessions. First, it can be used to set session 2422## variables by calling it with an $auth argument. It then 2423## uses the authid, authpw, and authlist session variables 2424## to set the corresponding values of $AuthId, $AuthPw, and $AuthList 2425## as needed. 2426function SessionAuth($pagename, $auth = NULL) { 2427 global $AuthId, $AuthList, $AuthPw, $SessionEncode, $SessionDecode, 2428 $EnableSessionPasswords, $EnableAuthPostRegenerateSID; 2429 static $called; 2430 2431 @$called++; 2432 $sn = session_name(); # in PHP5.3, $_REQUEST doesn't contain $_COOKIE 2433 if (!$auth && ($called > 1 || (!@$_REQUEST[$sn] && !@$_COOKIE[$sn]))) return; 2434 2435 $sid = session_id(); 2436 @session_start(); 2437 if($called == 1 && isset($_POST['authpw']) && $_POST['authpw'] 2438 && IsEnabled($EnableAuthPostRegenerateSID, true)) { 2439 session_regenerate_id(); 2440 } 2441 2442 foreach((array)$auth as $k => $v) { 2443 if ($k == 'authpw') { 2444 foreach((array)$v as $pw => $pv) { 2445 if ($SessionEncode) $pw = $SessionEncode($pw); 2446 $_SESSION[$k][$pw] = $pv; 2447 } 2448 } 2449 else if ($k) $_SESSION[$k] = (array)$v + (array)@$_SESSION[$k]; 2450 } 2451 2452 if (!isset($AuthId)) $AuthId = @$_SESSION['authid'] ? @end($_SESSION['authid']) : ''; 2453 $AuthPw = array_map($SessionDecode, array_keys((array)@$_SESSION['authpw'])); 2454 if (!IsEnabled($EnableSessionPasswords, 1)) $_SESSION['authpw'] = array(); 2455 $AuthList = array_merge($AuthList, (array)@$_SESSION['authlist']); 2456 2457 if (!$sid) @session_write_close(); 2458} 2459 2460 2461function PasswdVar($pagename, $level) { 2462 global $PCache, $PasswdVarAuth, $FmtV; 2463 $page = $PCache[$pagename]; 2464 if (!isset($page['=passwd'][$level])) { 2465 $page = RetrieveAuthPage($pagename, 'ALWAYS', false, READPAGE_CURRENT); 2466 if ($page) PCache($pagename, $page); 2467 } 2468 SDV($PasswdVarAuth, 'attr'); 2469 if ($PasswdVarAuth && !@$page['=auth'][$PasswdVarAuth]) return XL('(protected)'); 2470 $pwsource = $page['=pwsource'][$level]; 2471 if (strncmp($pwsource, 'cascade:', 8) == 0) { 2472 $FmtV['$PWCascade'] = substr($pwsource, 8); 2473 return FmtPageName('$[(using $PWCascade password)]', $pagename); 2474 } 2475 $setting = PHSC(implode(' ', preg_replace('/^(?!@|\\w+:).+$/', '****', 2476 (array)$page['=passwd'][$level]))); 2477 if ($pwsource == 'group' || $pwsource == 'site') { 2478 $FmtV['$PWSource'] = $pwsource; 2479 $setting = FmtPageName('$[(set by $PWSource)] ', $pagename) 2480 . PHSC($setting); 2481 } 2482 return $setting; 2483} 2484 2485 2486function PrintAttrForm($pagename) { 2487 global $PageAttributes, $PCache, $FmtV; 2488 $FmtV['$TokenValue'] = pmtoken(); 2489 echo FmtPageName("<form action='\$PageUrl' method='post'> 2490 <input type='hidden' name='action' value='postattr' /> 2491 <input type='hidden' name='\$TokenName' value='\$TokenValue' /> 2492 <input type='hidden' name='n' value='\$FullName' /> 2493 <table>",$pagename); 2494 $page = $PCache[$pagename]; 2495 foreach($PageAttributes as $attr=>$p) { 2496 if (!$p) continue; 2497 if (strncmp($attr, 'passwd', 6) == 0) { 2498 $setting = PageVar($pagename, '$Passwd'.ucfirst(substr($attr, 6))); 2499 $value = ''; 2500 } else { $setting = $value = PHSC(@$page[$attr]); } 2501 $prompt = FmtPageName($p,$pagename); 2502 echo "<tr><td>$prompt</td> 2503 <td><input type='text' name='$attr' value='$value' /></td> 2504 <td>$setting</td></tr>"; 2505 } 2506 echo FmtPageName("</table><input type='submit' value='$[Save]' /></form>", 2507 $pagename); 2508} 2509 2510function HandleAttr($pagename, $auth = 'attr') { 2511 global $HandleAttrFmt,$PageAttrFmt,$PageStartFmt,$PageEndFmt; 2512 $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT); 2513 if (!$page) { Abort("?unable to read $pagename"); } 2514 PCache($pagename,$page); 2515 XLSDV('en', array('EnterAttributes' => 2516 "Enter new attributes for this page below. Leaving a field blank 2517 will leave the attribute unchanged. To clear an attribute, enter 2518 'clear'.")); 2519 SDV($PageAttrFmt,"<div class='wikiattr'> 2520 <h2 class='wikiaction'>$[{\$FullName} Attributes]</h2> 2521 <p>$[EnterAttributes]</p></div>"); 2522 SDV($HandleAttrFmt,array(&$PageStartFmt,&$PageAttrFmt, 2523 'function:PrintAttrForm',&$PageEndFmt)); 2524 PrintFmt($pagename,$HandleAttrFmt); 2525} 2526 2527function HandlePostAttr($pagename, $auth = 'attr') { 2528 global $PageAttributes, $EnablePostAttrClearSession; 2529 if(! AutoCheckToken()) { 2530 Abort('? $[Token invalid or missing.]'); 2531 } 2532 Lock(2); 2533 $page = RetrieveAuthPage($pagename, $auth, true); 2534 if (!$page) { Abort("?unable to read $pagename"); } 2535 foreach($PageAttributes as $attr=>$p) { 2536 $v = stripmagic(@$_POST[$attr]); 2537 if ($v == '') continue; 2538 if ($v=='clear') unset($page[$attr]); 2539 else if (strncmp($attr, 'passwd', 6) != 0) $page[$attr] = $v; 2540 else { 2541 $a = array(); 2542 preg_match_all('/"[^"]*"|\'[^\']*\'|\\S+/', $v, $match); 2543 foreach($match[0] as $pw) 2544 $a[] = preg_match('/^(@|\\w+:)/', $pw) ? $pw 2545 : pmcrypt(preg_replace('/^([\'"])(.*)\\1$/', '$2', $pw)); 2546 if ($a) $page[$attr] = implode(' ',$a); 2547 } 2548 } 2549 WritePage($pagename,$page); 2550 Lock(0); 2551 if (IsEnabled($EnablePostAttrClearSession, 1)) { 2552 @session_start(); 2553 unset($_SESSION['authid']); 2554 unset($_SESSION['authlist']); 2555 $_SESSION['authpw'] = array(); 2556 } 2557 Redirect($pagename); 2558 exit; 2559} 2560 2561 2562function HandleLogoutA($pagename, $auth = 'read') { 2563 global $LogoutRedirectFmt, $LogoutCookies; 2564 SDV($LogoutRedirectFmt, '$FullName'); 2565 SDV($LogoutCookies, array()); 2566 @session_start(); 2567 $_SESSION = array(); 2568 if ( session_id() != '' || isset($_COOKIE[session_name()]) ) 2569 pmsetcookie(session_name(), '', time()-43200, '/'); 2570 foreach ($LogoutCookies as $c) 2571 if (isset($_COOKIE[$c])) pmsetcookie($c, '', time()-43200, '/'); 2572 session_destroy(); 2573 Redirect(FmtPageName($LogoutRedirectFmt, $pagename)); 2574} 2575 2576 2577function HandleLoginA($pagename, $auth = 'login') { 2578 global $AuthId, $DefaultPasswords; 2579 unset($DefaultPasswords['admin']); 2580 $prompt = @(!$_POST['authpw'] || ($AuthId != $_POST['authid'])); 2581 $page = RetrieveAuthPage($pagename, $auth, $prompt, READPAGE_CURRENT); 2582 Redirect($pagename); 2583} 2584 2585