Blob


1 <?php
2 /*
3 PmWiki
4 Copyright 2001-2023 Patrick R. Michaud
5 pmichaud@pobox.com
6 http://www.pmichaud.com/
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.
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.
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
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.
28 Script maintained by Petko YOTOV www.pmwiki.org/petko
29 $Id: pmwiki.php 4370 2023-02-12 08:26:08Z petko $
30 */
31 error_reporting(E_ALL ^ E_NOTICE);
32 StopWatch('PmWiki');
33 @ini_set('magic_quotes_runtime', 0);
34 @ini_set('magic_quotes_sybase', 0);
35 if (@ini_get('pcre.backtrack_limit') < 1000000)
36 @ini_set('pcre.backtrack_limit', 1000000);
37 if (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';
44 define('PmWiki',1);
45 SDV($WorkDir,'wiki.d');
46 SDV($FarmD,dirname(__FILE__));
47 if (strpos($FarmD, 'phar://')===0) $IsFarmArchive = 1;
48 elseif (preg_match('/\\w\\w:/', $FarmD)) exit();
49 @include_once("$FarmD/scripts/version.php");
50 $GroupPattern = '[[:upper:]][\\w]*(?:-\\w+)*';
51 $NamePattern = '[[:upper:]\\d][\\w]*(?:-\\w+)*';
52 $BlockPattern = 'form|div|table|t[rdh]|p|[uo]l|d[ltd]|h[1-6r]|pre|blockquote';
53 $WikiWordPattern = '[[:upper:]][[:alnum:]]*(?:[[:upper:]][[:lower:]0-9]|[[:lower:]0-9][[:upper:]])[[:alnum:]]*';
54 $WikiDir = new PageStore('wiki.d/{$FullName}');
55 $WikiLibDirs = array(&$WikiDir,new PageStore('$FarmD/wikilib.d/{$FullName}'));
56 $PageFileEncodeFunction = 'PUE'; # only used if $WikiDir->encodefilenames is set
57 $PageFileDecodeFunction = 'urldecode';
58 $LocalDir = 'local';
59 $InterMapFiles = array("$FarmD/scripts/intermap.txt",
60 "$FarmD/local/farmmap.txt", '$SiteGroup.InterMap', 'local/localmap.txt');
61 $Newline = "\263"; # deprecated, 2.0.0
62 $KeepToken = "\034\034";
63 $Now=time();
64 define('READPAGE_CURRENT', $Now+604800);
65 $TimeFmt = '%B %d, %Y, at %I:%M %p';
66 $TimeISOFmt = '%Y-%m-%dT%H:%M:%S';
67 $TimeISOZFmt = '%Y-%m-%dT%H:%M:%SZ';
68 $MessagesFmt = array();
69 $BlockMessageFmt = "<h3 class='wikimessage'>$[This post has been blocked by the administrator]</h3>";
70 $EditFields = array('text');
71 $EditFunctions = array('AutoCheckToken', 'EditTemplate', 'RestorePage', 'ReplaceOnSave',
72 'SaveAttributes', 'PostPage', 'PostRecentChanges', 'AutoCreateTargets',
73 'PreviewPage');
74 $EnablePost = 1;
75 $ChangeSummary = substr(preg_replace('/[\\x00-\\x1f]|=\\]/', '',
76 stripmagic(@$_REQUEST['csum'])), 0, 100);
77 $AsSpacedFunction = 'AsSpaced';
78 $SpaceWikiWords = 0;
79 $RCDelimPattern = ' ';
80 $RecentChangesFmt = array(
81 '$SiteGroup.AllRecentChanges' =>
82 '* [[{$Group}.{$Name}]] . . . $CurrentLocalTime $[by] $AuthorLink: [=$ChangeSummary=]',
83 '$Group.RecentChanges' =>
84 '* [[{$Group}/{$Name}]] . . . $CurrentLocalTime $[by] $AuthorLink: [=$ChangeSummary=]');
85 $UrlScheme = (@$_SERVER['HTTPS']=='on' || @$_SERVER['SERVER_PORT']==443)
86 ? 'https' : 'http';
87 $ScriptUrl = $UrlScheme.'://'.strval(@$_SERVER['HTTP_HOST']).strval(@$_SERVER['SCRIPT_NAME']);
88 $PubDirUrl = preg_replace('#/[^/]*$#', '/pub', $ScriptUrl, 1);
89 SDV($FarmPubDirPrefix, 'PmFarmPubDirUrl');
90 if (@$IsFarmArchive) SDV($FarmPubDirUrl, "$ScriptUrl/$FarmPubDirPrefix");
91 $HTMLVSpace = "<vspace>";
92 $HTMLPNewline = '';
93 $MarkupFrame = array();
94 $MarkupFrameBase = array('cs' => array(), 'vs' => '', 'ref' => 0,
95 'closeall' => array(), 'is' => array(),
96 'escape' => 1);
97 $WikiWordCountMax = 1000000;
98 $WikiWordCount['PmWiki'] = 1;
99 $TableRowIndexMax = 1;
100 $UrlExcludeChars = '<>"{}|\\\\^`()[\\]\'';
101 $QueryFragPattern = "[?#][^\\s$UrlExcludeChars]*";
102 $SuffixPattern = '(?:-?[[:alnum:]]+)*';
103 $LinkPageSelfFmt = "<a class='selflink' href='\$LinkUrl' title='\$LinkAlt'>\$LinkText</a>";
104 $LinkPageExistsFmt = "<a class='wikilink' href='\$LinkUrl' title='\$LinkAlt'>\$LinkText</a>";
105 $LinkPageCreateFmt =
106 "<a class='createlinktext' rel='nofollow' title='\$LinkAlt'
107 href='{\$PageUrl}?action=edit'>\$LinkText</a><a rel='nofollow'
108 class='createlink' href='{\$PageUrl}?action=edit'>?</a>";
109 $UrlLinkFmt =
110 "<a class='urllink' href='\$LinkUrl' title='\$LinkAlt' rel='nofollow'>\$LinkText</a>";
111 umask(002);
112 $CookiePrefix = '';
113 $SiteGroup = 'Site';
114 $SiteAdminGroup = 'SiteAdmin';
115 $DefaultGroup = 'Main';
116 $DefaultName = 'HomePage';
117 $GroupHeaderFmt = '(:include {$Group}.GroupHeader self=0 basepage={*$FullName}:)(:nl:)';
118 $GroupFooterFmt = '(:nl:)(:include {$Group}.GroupFooter self=0 basepage={*$FullName}:)';
119 $PagePathFmt = array('{$Group}.$1','$1.$1','$1.{$DefaultName}');
120 $PageAttributes = array(
121 'passwdread' => '$[Set new read password:]',
122 'passwdedit' => '$[Set new edit password:]',
123 'passwdattr' => '$[Set new attribute password:]');
124 $XLLangs = array('en');
125 if (preg_match('/^C$|\.UTF-?8/i',setlocale(LC_ALL,0)))
126 setlocale(LC_ALL,'en_US');
127 $FmtP = array();
128 $FmtPV = array(
129 # '$ScriptUrl' => 'PUE($ScriptUrl)', ## $ScriptUrl is special
130 '$PageUrl' =>
131 'PUE(($EnablePathInfo)
132 ? "$ScriptUrl/$group/$name"
133 : "$ScriptUrl?n=$group.$name")',
134 '$FullName' => '"$group.$name"',
135 '$Groupspaced' => '$AsSpacedFunction($group)',
136 '$Namespaced' => '$AsSpacedFunction($name)',
137 '$Group' => '$group',
138 '$Name' => '$name',
139 '$Titlespaced' => 'FmtPageTitle(@$page["title"], $name, 1)',
140 '$Title' => 'FmtPageTitle(@$page["title"], $name, 0)',
141 '$LastModifiedBy' => '@$page["author"]',
142 '$LastModifiedHost' => '@$page["host"]',
143 '$LastModified' => 'PSFT($GLOBALS["TimeFmt"], $page["time"])',
144 '$LastModifiedSummary' => '@$page["csum"]',
145 '$LastModifiedTime' => '$page["time"]',
146 '$Description' => '@$page["description"]',
147 '$SiteGroup' => '$GLOBALS["SiteGroup"]',
148 '$SiteAdminGroup' => '$GLOBALS["SiteAdminGroup"]',
149 '$VersionNum' => '$GLOBALS["VersionNum"]',
150 '$Version' => '$GLOBALS["Version"]',
151 '$WikiTitle' => '$GLOBALS["WikiTitle"]',
152 '$PageLogoUrl' => 'strval(@$GLOBALS["PageLogoUrl"])',
153 '$Author' => 'NoCache($GLOBALS["Author"])',
154 '$AuthId' => 'NoCache($GLOBALS["AuthId"])',
155 '$DefaultGroup' => '$GLOBALS["DefaultGroup"]',
156 '$DefaultName' => '$GLOBALS["DefaultName"]',
157 '$BaseName' => 'MakeBaseName($pn)',
158 '$Action' => '$GLOBALS["action"]',
159 '$PasswdRead' => 'PasswdVar($pn, "read")',
160 '$PasswdEdit' => 'PasswdVar($pn, "edit")',
161 '$PasswdAttr' => 'PasswdVar($pn, "attr")',
162 '$EnabledIMap' => 'implode("|", array_keys($GLOBALS["IMap"]))', # PmSyntax
163 '$GroupHomePage' => 'FmtGroupHome($pn,$group,$var)',
164 '$GroupHomePageName' => 'FmtGroupHome($pn,$group,$var)',
165 '$GroupHomePageTitle' => 'FmtGroupHome($pn,$group,$var)',
166 '$GroupHomePageTitlespaced' => 'FmtGroupHome($pn,$group,$var)',
167 );
168 $SaveProperties = array('title', 'description', 'keywords');
169 $PageTextVarPatterns = array(
170 'var:' => '/^(:*[ \\t]*(\\w[-\\w]*)[ \\t]*:[ \\t]?)(.*)($)/m',
171 '(:var:...:)' => '/(\\(: *(\\w[-\\w]*) *:(?!\\))\\s?)(.*?)(:\\))/s'
172 );
174 $WikiTitle = 'PmWiki';
175 $Charset = 'ISO-8859-1';
176 $HTTPHeaders = array(
177 "Expires: Tue, 01 Jan 2002 00:00:00 GMT",
178 "Cache-Control: no-store, no-cache, must-revalidate",
179 "Content-type: text/html; charset=ISO-8859-1;");
180 $HTTPHeaders['XFO'] = 'X-Frame-Options: SAMEORIGIN';
181 $HTTPHeaders['CSP'] = "Content-Security-Policy: frame-ancestors 'self'; base-uri 'self'; object-src 'none';";
182 $HTTPHeaders['XSSP'] = 'X-XSS-Protection: 1; mode=block';
184 $CacheActions = array('browse','diff','print');
185 $EnableHTMLCache = 0;
186 $NoHTMLCache = 0;
187 $HTMLTagAttr = '';
188 $HTMLDoctypeFmt =
189 "<!DOCTYPE html
190 PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
191 \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
192 <html xmlns='http://www.w3.org/1999/xhtml' \$HTMLTagAttr><head>\n";
193 $HTMLStylesFmt['pmwiki'] = "
194 ul, ol, pre, dl, p { margin-top:0px; margin-bottom:0px; }
195 code.escaped { white-space: pre; }
196 .vspace { margin-top:1.33em; }
197 .indent { margin-left:40px; }
198 .outdent { margin-left:40px; text-indent:-40px; }
199 a.createlinktext { text-decoration:none; border-bottom:1px dotted gray; }
200 a.createlink { text-decoration:none; position:relative; top:-0.5em;
201 font-weight:bold; font-size:smaller; border-bottom:none; }
202 img { border:0px; }
203 ";
204 $HTMLHeaderFmt['styles'] = array(
205 "<style type='text/css'><!--",&$HTMLStylesFmt,"\n--></style>");
206 $HTMLBodyFmt = "</head>\n<body>";
207 $HTMLStartFmt = array('headers:',&$HTMLDoctypeFmt,&$HTMLHeaderFmt,
208 &$HTMLBodyFmt);
209 $HTMLEndFmt = "\n</body>\n</html>";
210 $PageStartFmt = array(&$HTMLStartFmt,"\n<div id='contents'>\n");
211 $PageEndFmt = array('</div>',&$HTMLEndFmt);
213 $HandleActions = array(
214 'browse' => 'HandleBrowse', 'print' => 'HandleBrowse',
215 'edit' => 'HandleEdit', 'source' => 'HandleSource',
216 'attr' => 'HandleAttr', 'postattr' => 'HandlePostAttr',
217 'logout' => 'HandleLogoutA', 'login' => 'HandleLoginA');
218 $HandleAuth = array(
219 'browse' => 'read', 'source' => 'read', 'print' => 'read',
220 'edit' => 'edit', 'attr' => 'attr', 'postattr' => 'attr',
221 'logout' => 'read', 'login' => 'login');
222 $ActionTitleFmt = array(
223 'edit' => '| $[Edit]',
224 'attr' => '| $[Attributes]',
225 'login' => '| $[Login]');
226 $DefaultPasswords = array('admin'=>'@lock','read'=>'','edit'=>'','attr'=>'');
227 $AuthCascade = array('edit'=>'read', 'attr'=>'edit');
228 $AuthList = array('' => 1, 'nopass:' => 1, '@nopass' => 1);
229 $SessionEncode = 'base64_encode';
230 $SessionDecode = 'base64_decode';
232 $CallbackFnTemplates = array(
233 'default' => '%s',
234 'return' => 'return %s;',
235 'markup_e' => 'extract($GLOBALS["MarkupToHTML"]); return %s;',
236 'qualify' => 'extract($GLOBALS["tmp_qualify"]); return %s;',
237 );
239 $Conditions['enabled'] = '(boolean)@$GLOBALS[$condparm]';
240 $Conditions['false'] = 'false';
241 $Conditions['true'] = 'true';
242 $Conditions['group'] =
243 "(boolean)MatchPageNames(\$pagename, FixGlob(\$condparm, '$1$2.*'))";
244 $Conditions['name'] =
245 "(boolean)MatchPageNames(\$pagename, FixGlob(\$condparm, '$1*.$2'))";
246 $Conditions['match'] = 'preg_match("!$condparm!",$pagename)';
247 $Conditions['authid'] = 'NoCache(@$GLOBALS["AuthId"] > "")';
248 $Conditions['equal'] = 'CompareArgs($condparm) == 0';
249 function CompareArgs($arg)
250 { $arg = ParseArgs($arg); return strcmp(@$arg[''][0], @$arg[''][1]); }
252 $Conditions['auth'] = 'NoCache(CondAuth($pagename, $condparm))';
253 function CondAuth($pagename, $condparm) {
254 global $AuthList, $HandleAuth;
255 @list($level, $pn) = explode(' ', $condparm, 2);
256 if (@$level && $level[0] == '@') { # user belongs to @group1,@group2
257 $keys = MatchNames(array_keys((array)@$AuthList), $level, true);
258 foreach($keys as $k) {
259 if (@$AuthList[$k] == 1 && $AuthList["-$k"] != 1) return true;
261 return false;
263 $pn = ($pn > '') ? MakePageName($pagename, $pn) : $pagename;
264 if (@$HandleAuth[$level]>'') $level = $HandleAuth[$level];
265 return (boolean)RetrieveAuthPage($pn, $level, false, READPAGE_CURRENT);
267 $Conditions['exists'] = 'CondExists($condparm)';
268 ## This is an optimized version of the earlier conditional
269 ## especially for pagelists
270 function CondExists($condparm, $caseinsensitive = true) {
271 static $ls = false;
272 if (!$ls) $ls = ListPages();
273 $condparm = str_replace(array('[[',']]'), array('', ''), $condparm);
274 $glob = FixGlob($condparm, '$1*.$2');
275 return (boolean)MatchPageNames($ls, $glob, $caseinsensitive);
278 ## CondExpr handles complex conditions (expressions)
279 ## Portions Copyright 2005 by D. Faure (dfaure@cpan.org)
280 function CondExpr($pagename, $condname, $condparm) {
281 global $CondExprOps;
282 SDV($CondExprOps, 'and|x?or|&&|\\|\\||[!()]');
283 if ($condname == '(' || $condname == '[')
284 $condparm = preg_replace('/[\\]\\)]\\s*$/', '', $condparm);
285 $condparm = str_replace('&amp;&amp;', '&&', $condparm);
286 $terms = preg_split("/(?<!\\S)($CondExprOps)(?!\\S)/i", $condparm, -1,
287 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
288 foreach($terms as $i => $t) {
289 $t = trim($t);
290 if (preg_match("/^($CondExprOps)$/i", $t)) continue;
291 if ($t) $terms[$i] = CondText($pagename, "if $t", 'TRUE') ? '1' : '0';
294 ## PITS:01480, (:if [ {$EmptyPV} and ... ]:)
295 $code = preg_replace('/(^\\s*|(and|x?or|&&|\\|\\||!)\\s+)(?=and|x?or|&&|\\|\\|)/',
296 '$1 0 ', trim(implode(' ', $terms)));
298 return @eval("return( $code );");
300 $Conditions['expr'] = 'CondExpr($pagename, $condname, $condparm)';
301 $Conditions['('] = 'CondExpr($pagename, $condname, $condparm)';
302 $Conditions['['] = 'CondExpr($pagename, $condname, $condparm)';
304 $MarkupTable['_begin']['seq'] = 'B';
305 $MarkupTable['_end']['seq'] = 'E';
306 Markup('fulltext','>_begin');
307 Markup('split','>fulltext',"\n",
308 '$RedoMarkupLine=1; return explode("\n",$x);');
309 Markup('directives','>split');
310 Markup('inline','>directives');
311 Markup('links','>inline');
312 Markup('block','>links');
313 Markup('style','>block');
314 Markup('closeall', '_begin',
315 '/^\\(:closeall:\\)$/', "MarkupMarkupClose");
316 function MarkupMarkupClose() { return '<:block>' . MarkupClose(); }
318 $ImgExtPattern="\\.(?:gif|jpg|jpeg|a?png|svgz?|GIF|JPG|JPEG|A?PNG|SVGZ?|webp|WEBP)";
319 $ImgTagFmt="<img src='\$LinkUrl' alt='\$LinkAlt' title='\$LinkAlt' />";
321 $BlockMarkups = array(
322 'block' => array('','','',0),
323 'ul' => array('<ul><li>','</li><li>','</li></ul>',1),
324 'dl' => array('<dl>','</dd>','</dd></dl>',1),
325 'ol' => array('<ol><li>','</li><li>','</li></ol>',1),
326 'p' => array('<p>','','</p>',0),
327 'indent' =>
328 array("<div class='indent'>","</div><div class='indent'>",'</div>',1),
329 'outdent' =>
330 array("<div class='outdent'>","</div><div class='outdent'>",'</div>',1),
331 'pre' => array('<pre>','','</pre>',0),
332 'table' => array("<table width='100%'>",'','</table>',0));
334 foreach(array('http:','https:','mailto:','ftp:','news:','gopher:','nap:',
335 'file:', 'tel:', 'geo:') as $m)
336 { $LinkFunctions[$m] = 'LinkIMap'; $IMap[$m]="$m$1"; }
337 $LinkFunctions['<:page>'] = 'LinkPage';
339 $q = preg_replace('/(\\?|%3f)([-\\w]+=)/i', '&$2', strval(@$_SERVER['QUERY_STRING']));
340 if ($q != @$_SERVER['QUERY_STRING']) {
341 unset($_GET);
342 parse_str($q, $_GET);
343 $_REQUEST = array_merge($_REQUEST, $_GET, $_POST);
346 if (isset($_GET['action'])) $action = $_GET['action'];
347 elseif (isset($_POST['action'])) $action = $_POST['action'];
348 else $action = 'browse';
350 $pagename = strval(@$_REQUEST['n']);
351 if (!$pagename) $pagename = strval(@$_REQUEST['pagename']);
352 if (!$pagename &&
353 preg_match('!^'.preg_quote(strval(@$_SERVER['SCRIPT_NAME']),'!').'/?([^?]*)!',
354 strval(@$_SERVER['REQUEST_URI']),$match))
355 $pagename = urldecode($match[1]);
356 if (preg_match('/[\\x80-\\xbf]/',$pagename))
357 $pagename=pm_recode($pagename, 'UTF-8', 'WINDOWS-1252');
358 $pagename = preg_replace('![^[:alnum:]\\x80-\\xff]+$!','',$pagename);
359 $pagename_unfiltered = $pagename;
360 $pagename = preg_replace('![${}\'"\\\\]+!', '', $pagename);
361 $FmtPV['$RequestedPage'] = 'PHSC($GLOBALS["pagename_unfiltered"], ENT_QUOTES)';
362 $Cursor['*'] = &$pagename;
363 if (function_exists("date_default_timezone_get") ) { # fix PHP5.3 warnings
364 @date_default_timezone_set(@date_default_timezone_get());
367 $DenyHtaccessContent = <<<EOF
368 <IfModule !mod_authz_host.c>
369 Order Deny,Allow
370 Deny from all
371 </IfModule>
373 <IfModule mod_authz_host.c>
374 Require all denied
375 </IfModule>
377 EOF;
379 SDVA($ServeFileExts, array(
380 'gif' => 'image/gif', 'png' => 'image/png', 'svg' => 'image/svg+xml',
381 'README' => 'text/plain', 'txt' => 'text/plain',
382 'css' => 'text/css', 'js' => 'application/javascript',
383 ));
384 function pm_servefile($basedir, $path, $cachecontrol='no-cache') {
385 global $ServeFileExts;
386 header("X-Sent-Via: pm_servefile");
387 $ext = preg_replace('!^.*[./]!', '', $path);
388 if (!isset($ServeFileExts[$ext]) || preg_match('/[?#${}]|\\.\\./', $path)) {
389 http_response_code(403);
390 die('Forbidden');
392 $filepath = "$basedir/$path";
393 if(!file_exists($filepath)) {
394 http_response_code(404);
395 die('File not found');
398 header("Cache-Control: $cachecontrol");
399 header('Expires: ');
400 $filelastmod = gmdate('D, d M Y H:i:s \G\M\T', filemtime($filepath));
401 if (@$_SERVER['HTTP_IF_MODIFIED_SINCE'] == $filelastmod)
402 { http_response_code(304); exit(); }
403 header("Last-Modified: $filelastmod");
404 header("Content-Type: {$ServeFileExts[$ext]}");
405 $length = filesize($filepath);
406 header("Content-Length: $length");
407 readfile($filepath);
408 exit;
411 if (strpos($pagename, "$FarmPubDirPrefix/")===0) {
412 $path = substr($pagename, strlen("$FarmPubDirPrefix/"));
413 pm_servefile("$FarmD/pub", $path);
414 exit;
417 if (file_exists("$FarmD/local/farmconfig.php"))
418 include_once("$FarmD/local/farmconfig.php");
419 if (IsEnabled($EnableLocalConfig,1)) {
420 if (file_exists("$LocalDir/config.php"))
421 include_once("$LocalDir/config.php");
422 elseif (file_exists('config.php'))
423 include_once('config.php');
426 SDV($CurrentTime, PSFT($TimeFmt, $Now));
427 SDV($CurrentLocalTime, PSFT("@$TimeISOZFmt", $Now, null, 'GMT'));
428 SDV($CurrentTimeISO, PSFT($TimeISOFmt, $Now));
430 if (IsEnabled($EnableStdConfig,1))
431 include_once("$FarmD/scripts/stdconfig.php");
433 if (isset($PostConfig) && is_array($PostConfig)) {
434 asort($PostConfig, SORT_NUMERIC);
435 foreach ($PostConfig as $k=>$v) {
436 if (!$k || !$v || $v<50) continue;
437 if (function_exists($k)) $k($pagename);
438 elseif (file_exists($k)) include_once($k);
442 function pmsetcookie($name, $val="", $exp=0, $path="", $dom="", $secure=null, $httponly=null, $samesite=null) {
443 global $EnableCookieSecure, $EnableCookieHTTPOnly, $SetCookieFunction, $CookieSameSite;
444 if (IsEnabled($SetCookieFunction))
445 return $SetCookieFunction($name, $val, $exp, $path, $dom, $secure, $httponly, $samesite);
446 if (is_null($secure)) $secure = IsEnabled($EnableCookieSecure, false);
447 if (is_null($httponly)) $httponly = IsEnabled($EnableCookieHTTPOnly, false);
448 if (is_null($samesite)) $samesite = IsEnabled($CookieSameSite, 'Lax');
450 if (PHP_VERSION_ID>=70300) {
451 return setcookie($name, $val, array(
452 'expires' => $exp,
453 'path' => $path,
454 'domain' => $dom,
455 'secure' => $secure,
456 'httponly' => $httponly,
457 'samesite' => $samesite
458 ));
460 if (!$path) $path = '/';
461 setcookie($name, $val, $exp, "$path; SameSite=$samesite", $dom, $secure, $httponly);
463 function pm_session_start($a = array()) {
464 global $EnableCookieSecure, $EnableCookieHTTPOnly, $CookieSameSite;
465 if (function_exists('session_status')) {
466 if (session_status() === PHP_SESSION_ACTIVE) return true;
469 if (!headers_sent()){
470 $params = session_get_cookie_params();
471 if (isset($EnableCookieSecure) && !isset($a['secure']))
472 $a['secure'] = $EnableCookieSecure;
473 if (isset($EnableCookieHTTPOnly) && !isset($a['httponly']))
474 $a['httponly'] = $EnableCookieHTTPOnly;
475 if (!isset($a['samesite'])) $a['samesite'] = IsEnabled($CookieSameSite, 'Lax');
476 SDVA($a, $params);
478 if (PHP_VERSION_ID < 70300) {
479 if (!$a['path']) $a['path'] = '/';
480 $a['path'] .= "; SameSite={$a['samesite']}";
481 session_set_cookie_params(
482 $a['lifetime'],
483 $a['path'],
484 $a['domain'],
485 $a['secure'],
486 $a['httponly']
487 );
489 else {
490 session_set_cookie_params($a);
493 return @session_start();
497 foreach((array)$InterMapFiles as $f) {
498 $f = FmtPageName($f, $pagename);
499 if (($v = @file($f)))
500 $v = preg_replace('/^\\s*(?>\\w[-\\w]*)(?!:)/m', '$0:', implode('', $v));
501 else if (@PageExists($f)) {
502 $p = ReadPage($f, READPAGE_CURRENT);
503 $v = @$p['text'];
504 } else continue;
505 if (!preg_match_all("/^\\s*(\\w[-\\w]*:)[^\\S\n]+(\\S*)/m", $v,
506 $match, PREG_SET_ORDER)) continue;
507 foreach($match as $m) {
508 if (strpos($m[2], '$1') === false) $m[2] .= '$1';
509 $LinkFunctions[$m[1]] = 'LinkIMap';
510 $IMap[$m[1]] = FmtPageName($m[2], $pagename);
514 $keys = array_keys($AuthCascade);
515 while ($keys) {
516 $k = array_shift($keys); $t = $AuthCascade[$k];
517 if (in_array($t, $keys))
518 { unset($AuthCascade[$k]); $AuthCascade[$k] = $t; array_push($keys, $k); }
521 $LinkPattern = implode('|',array_keys($LinkFunctions)); # after InterMaps
522 SDV($LinkPageCreateSpaceFmt,$LinkPageCreateFmt);
523 $ActionTitle = FmtPageName(@$ActionTitleFmt[$action], $pagename);
526 if (!@$HandleActions[$action] || !function_exists($HandleActions[$action]))
527 $action='browse';
528 if (IsEnabled($EnableActions, 1)) HandleDispatch($pagename, $action);
529 Lock(0);
530 return;
532 ## HandleDispatch() is used to dispatch control to the appropriate
533 ## action handler with the appropriate permissions.
534 ## If a message is supplied, it is added to $MessagesFmt.
535 function HandleDispatch($pagename, $action, $msg=NULL) {
536 global $MessagesFmt, $HandleActions, $HandleAuth;
537 if ($msg) $MessagesFmt[] = "<div class='wikimessage'>$msg</div>";
538 $fn = $HandleActions[$action];
539 $auth = @$HandleAuth[$action];
540 if (!$auth) $auth = 'read';
541 return $fn($pagename, $auth);
544 ## helper functions
545 function stripmagic($x) {
546 if(is_null($x)) return '';
547 $fn = 'get_magic_quotes_gpc';
548 if (!function_exists($fn)) return $x;
549 if (is_array($x)) {
550 foreach($x as $k=>$v) $x[$k] = stripmagic($v);
551 return $x;
553 $x = strval($x);
554 return @$fn() ? stripslashes($x) : $x;
556 function pre_r(&$x)
557 { return '<pre>'.PHSC(print_r($x, true)).'</pre>'; }
558 function PSS($x)
559 { return str_replace('\\"','"',$x); }
560 function PVS($x)
561 { return preg_replace("/\n[^\\S\n]*(?=\n)/", "\n<:vspace>", $x); }
562 function PVSE($x) { return PVS(PHSC($x, ENT_NOQUOTES)); }
563 function PZZ($x,$y='') { return ''; }
564 function PRR($x=NULL)
565 { if ($x || is_null($x)) $GLOBALS['RedoMarkupLine']++; return $x; }
566 function PUE($x)
567 { return $x? preg_replace_callback('/[\\x80-\\xff \'"<>]/', "cb_pue", $x) : ''; }
568 function cb_pue($m) { return '%'.dechex(ord($m[0])); }
569 function PQA($x, $keep=true, $styletoclass=false) {
570 global $EnableUnsafeInlineStyle;
571 if (!@$x) return ''; # PHP 8.1
572 $out = '';
573 $s = array();
574 $x = MarkupRestore($x);
575 if (preg_match_all('/([a-zA-Z][-\\w]*)\\s*=\\s*("[^"]*"|\'[^\']*\'|\\S*)/',
576 $x, $attr, PREG_SET_ORDER)) {
577 foreach($attr as $a) {
578 if (preg_match('/^on/i', $a[1])) continue;
579 $val = preg_replace('/^([\'"]?)(.*)\\1$/', '$2', $a[2]);
580 $s[$a[1]] = $val;
582 foreach($s as $key=>$val) {
583 $val = PHSC($val, ENT_QUOTES, null, false);
584 if ($keep) $val = Keep($val);
585 $out .= "$key='$val' ";
588 return $out;
590 function SDV(&$v,$x) { if (!isset($v)) $v=$x; }
591 function SDVA(&$var,$val)
592 { foreach($val as $k=>$v) if (!isset($var[$k])) $var[$k]=$v; }
593 function IsEnabled(&$var,$f=0)
594 { return (isset($var)) ? $var : $f; }
595 function SetTmplDisplay($var, $val)
596 { NoCache(); $GLOBALS['TmplDisplay'][$var] = $val; }
597 function NoCache($x = '') { $GLOBALS['NoHTMLCache'] |= 1; return $x; }
598 function ParseArgs($x, $optpat = '(?>(\\w+)[:=])') {
599 $z = array();
600 preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/",
601 strval($x), $terms, PREG_SET_ORDER);
602 foreach($terms as $t) {
603 $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]);
604 if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; }
605 else { $z['#'][] = $t[1]; $z[$t[1]][] = $v; }
606 $z['#'][] = $v;
608 return $z;
610 function PHSC($x, $flags=ENT_COMPAT, $enc=null, $dbl_enc=true) { # for PHP 5.4
611 if (is_null($enc)) $enc = "ISO-8859-1"; # single-byte charset
612 if (! is_array($x)) return @htmlspecialchars($x, $flags, $enc, $dbl_enc);
613 foreach($x as $k=>$v) $x[$k] = PHSC($v, $flags, $enc, $dbl_enc);
614 return $x;
616 function PSFT($fmt, $stamp=null, $locale=null, $tz=null) { # strftime() replacement
617 global $Now, $FTimeFmt, $TimeFmt, $EnableFTimeNew;
618 static $cached;
619 if (!@$cached) {
620 SDV($FTimeFmt, $TimeFmt);
621 $cached['dloc'] = setlocale(LC_TIME, 0);
622 $cached['dtz'] = date_default_timezone_get();
623 $cached['dtzo'] = timezone_open($cached['dtz']);
624 $cached['formats'] = array(
625 '%d' => 'd',
626 '%e' => ' j', # day " 1"-"31"
627 '%u' => 'N',
628 '%w' => 'w',
629 '%m' => 'm',
630 '%s' => 'U',
631 '%n' => "\n",
632 '%t' => "\t",
633 '%V' => 'W', # ISO-8601 week number, starts Monday, first week with 4+ weekdays
635 '%H' => 'H',
636 '%I' => 'h', # hour 01-12
637 '%k' => ' G', # hour " 1"-"23"
638 '%l' => ' g', # hour " 1"-"12"
639 '%M' => 'i',
640 '%S' => 's',
641 '%R' => 'H:i',
642 '%T' => 'H:i:s',
643 '%r' => 'h:i:s A',
644 '%D' => 'm/d/y',
645 '%F' => 'Y-m-d',
647 '%G' => 'o', # ISO-8601 year (like %V)
648 '%y' => 'y', # 21
649 '%Y' => 'Y', # 2021
651 '%Z' => 'T', # tz: EST
652 '%z' => 'O', # tz offset: -0500
653 );
654 if (extension_loaded('intl')) {
655 $full = IntlDateFormatter::FULL;
656 $long = IntlDateFormatter::LONG;
657 $short = IntlDateFormatter::SHORT;
658 $none = IntlDateFormatter::NONE;
659 $medium = IntlDateFormatter::MEDIUM;
661 $cached['iformats'] = array(
662 '%a' => array($full, $full, 'EEE'), # Mon
663 '%A' => array($full, $full, 'EEEE'), # Monday
664 '%h' => array($full, $full, 'MMM'), # Jan
665 '%b' => array($full, $full, 'MMM'), # Jan
666 '%B' => array($full, $full, 'MMMM'), # January
667 '%p' => array($full, $full, 'aa'), # AM/PM
668 '%P' => array($full, $full, 'aa'), # am/pm (no lowercase for intl am/pm)
669 '%c' => array($long, $short), # date time
670 '%x' => array($short, $none), # date
671 '%X' => array($none, $medium),# time
672 );
674 else {
675 $cached['iformats'] = array();
676 $cached['formats'] += array(
677 '%a' => 'D', # Mon
678 '%A' => 'l', # Monday
679 '%h' => 'M', # Jan
680 '%b' => 'M', # Jan
681 '%B' => 'F', # January
682 '%p' => 'a', # AM/PM
683 '%P' => 'A', # am/pm
684 '%c' => 'D M j H:i:s Y', # date time
685 '%x' => 'm/d/y', # date
686 '%X' => 'H:i:s', # time
687 );
689 $cached['new'] = isset($EnableFTimeNew)
690 ? $EnableFTimeNew
691 : (PHP_VERSION_ID>=80100);
694 if (@$fmt == '') $fmt = $FTimeFmt;
695 $stamp = is_numeric($stamp)? intval($stamp) : $Now;
697 if(preg_match('/(?<!%)(%L)/', $fmt)) {
698 $gmt = PSFT('@%Y-%m-%dT%H:%M:%SZ', $stamp, null, 'GMT');
699 $fmt = preg_replace('/(?<!%)(%L)/', $gmt, $fmt);
702 if (! $cached['new']) {
703 if (@$locale)
704 setlocale(LC_TIME, preg_split('/[, ]+/', $locale, -1, PREG_SPLIT_NO_EMPTY));
705 if (@$tz) @date_default_timezone_set($tz);
706 $fmt = str_replace(array('%F', '%s', '%o'),
707 array('%Y-%m-%d', $stamp, date('S', $stamp)),
708 $fmt);
709 $ret = strftime($fmt, $stamp);
710 if ($tz) date_default_timezone_set($cached['dtz']);
711 if (@$locale) setlocale(LC_TIME, $cached['dloc']);
712 return $ret;
715 $timestamp = date_create("@$stamp");
716 if ($tz) {
717 $tzo = @timezone_open($tz);
718 if (!@$tzo) unset($tz);
720 if (!@$tz) { # need to set it otherwise resets to GMT
721 $tz = $cached['dtz'];
722 $tzo = $cached['dtzo'];
724 date_timezone_set($timestamp, $tzo);
726 $vars = array(
727 'tz' => $tz,
728 'timestamp' => $timestamp,
729 'formats' => $cached['formats'],
730 'iformats' => $cached['iformats'],
731 );
732 if ($locale) $vars['locale'] = substr($locale, 0, 5);
734 $cb = new PPRC($vars);
735 $fmt = preg_replace_callback('/(?<!%)(%[a-zA-Z])/', array($cb, 'ftime'), $fmt);
736 return str_replace('%%', '%', $fmt);
738 function cb_PSFT($fmt, $vars) {
739 extract($vars); # $timestamp, $tz, $locale, $formats, $iformats
741 if (isset($formats[$fmt])) {
742 $fmt = $timestamp->format($formats[$fmt]);
743 if ($fmt[0]==' ' && strlen($fmt)>2) $fmt = substr($fmt, 1);
744 return $fmt;
746 if ($fmt=='%o') { # ordinal, PITS:01418
747 if (!@$locale) $locale = 'C';
748 $f = numfmt_create($locale, NumberFormatter::ORDINAL);
749 $o = $f->format($timestamp->format('j'));
750 return preg_replace('/\\d+/', '', $o);
752 if ($fmt=='%j') return sprintf('%03d',$timestamp->format('z')+1);
753 if ($fmt=='%C') return floor($timestamp->format('Y')/100);
754 if ($fmt=='%g') return sprintf('%02d', $timestamp->format('o') % 100);
755 if ($fmt=='%U') return cb_PSFT_UW($timestamp, $tz, 'Sunday');
756 if ($fmt=='%W') return cb_PSFT_UW($timestamp, $tz, 'Monday');
758 if (isset($iformats[$fmt])) {
759 $ifmt = $iformats[$fmt];
760 return datefmt_create(@$locale, $ifmt[0], $ifmt[1], $tz, null, @$ifmt[2])->format($timestamp);
762 return $fmt;
764 function cb_PSFT_UW($timestamp, $tz, $day) { # helper for %U %W
765 $first = strtotime(sprintf("%d-01 $day", $timestamp->format('Y')));
766 $stamp1 = date_create("@$first");
767 date_timezone_set($stamp1, timezone_open($tz));
768 $days = $timestamp->format('z') - $stamp1->format('z');
769 return sprintf('%02d', floor($days/7)+1);
772 # Only called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting
773 function PCCF($code, $template = 'default', $args = '$m') {
774 global $CallbackFnTemplates, $CallbackFunctions, $PCCFOverrideFunction;
775 if ($PCCFOverrideFunction && is_callable($PCCFOverrideFunction))
776 return $PCCFOverrideFunction($code, $template, $args);
778 if (!isset($CallbackFnTemplates[$template]))
779 Abort("No \$CallbackFnTemplates[$template]).");
780 $code = sprintf($CallbackFnTemplates[$template], $code);
781 if (!isset($CallbackFunctions[$code])) {
782 $fn = create_function($args, $code); # called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting
783 if ($fn) $CallbackFunctions[$code] = $fn;
784 else StopWatch("Failed to create callback function: ".PHSC($code));
786 return $CallbackFunctions[$code];
788 function PPRE($pat, $rep, $x) {
789 if (! function_exists('create_function')) return $x;
790 $lambda = PCCF("return $rep;");
791 return preg_replace_callback($pat, $lambda, $x);
793 function PPRA($array, $x) {
794 if ($x==='' || is_null($x)) return '';
795 foreach((array)$array as $pat => $rep) {
796 # skip broken patterns rather than crash the PHP installation
797 $oldpat = preg_match('!^/.+/[^/]*e[^/]*$!', $pat);
798 if ($oldpat && PHP_VERSION_ID >= 50500) continue;
800 $fmt = $x; # for $FmtP
801 if (is_callable($rep) && $rep != '_')
802 $x = preg_replace_callback($pat,$rep,$x);
803 else
804 $x = preg_replace($pat,$rep,$x);# simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting
806 return $x;
808 function PRCB($pat, $repl, $subj, $vars=null, $limit=-1, &$count=null, $flags=0) {
809 if (isset($vars)) {
810 $cb = new PPRC($vars, $repl);
811 $repl = array($cb, 'callback');
813 return preg_replace_callback($pat, $repl, $subj, $limit, $count, $flags);
815 ## callback functions
816 class PPRC { # PmWiki preg replace callbacks + pass local vars
817 var $vars;
818 var $cb;
819 function __construct($vars = null, $cb = null) {
820 if (!is_null($vars)) $this->vars = $vars;
821 if (!is_null($cb)) $this->cb = $cb;
824 function pagevar($m) { # called from FmtPageName
825 $pagename = $this->vars;
826 return PageVar($pagename, $m[1]);
828 function ftime($m) { # called from PSFT
829 $vars = $this->vars;
830 return cb_PSFT($m[0], $vars);
832 function callback($m) {
833 $cb = $this->cb;
834 return $cb($m, $this->vars);
838 # restores kept/protected strings
839 function cb_expandkpv($m) { return @$GLOBALS['KPV'][$m[1]]; }
841 # make a string upper or lower case in various patterns
842 function cb_toupper($m) { return strtoupper($m[1]); }
843 function cb_tolower($m) { return strtolower($m[1]); }
845 function pmcrypt($str, $salt=null) {
846 if ($salt && preg_match('/^(-?@|\\*$)/', $salt)) return false;
847 if (!is_null($salt)) return crypt($str, $salt);
849 if (function_exists('password_hash'))
850 return password_hash($str, PASSWORD_DEFAULT);
851 return crypt($str);
854 # generate or check a random one-time token to prevent CSRF
855 function pmtoken($token = null) {
856 global $SessionMaxTokens, $PmTokenFn;
857 if (IsEnabled($PmTokenFn) && function_exists($PmTokenFn))
858 return $PmTokenFn($token);
859 pm_session_start();
860 if (!isset($_SESSION['pmtokens'])) $_SESSION['pmtokens'] = array();
861 if (is_null($token)) { # create a one-time token
862 $len = mt_rand(20,30);
863 $token = "";
864 while(strlen($token)<$len) {
865 $token .= chr(mt_rand(32,126));
867 if (count($_SESSION['pmtokens']))
868 $id = max(array_keys($_SESSION['pmtokens']))+1;
869 else $id = 0;
870 $_SESSION['pmtokens'][$id] = $token;
871 if (IsEnabled($SessionMaxTokens, 0)) {
872 $max = $SessionMaxTokens;
873 $_SESSION['pmtokens'] = array_slice($_SESSION['pmtokens'], -$max, $max, true);
875 return "$id:" . md5($token);
877 # else: check a token, if correct, delete it
878 @list($id, $hash) = explode(':', $token);
879 $id = intval($id);
880 if (isset($_SESSION['pmtokens'][$id]) && $hash == md5($_SESSION['pmtokens'][$id])) {
881 unset($_SESSION['pmtokens'][$id]);
882 return true;
884 return false;
887 function PmNonce() {
888 if (function_exists('random_bytes')) {
889 $nonce = bin2hex(random_bytes(5));
891 else $nonce = mt_rand();
892 return $nonce;
897 function StopWatch($x) {
898 global $StopWatch, $EnableStopWatch;
899 if (!$EnableStopWatch) return;
900 static $wstart = 0, $ustart = 0;
901 list($usec,$sec) = explode(' ',microtime());
902 $wtime = ($sec+$usec);
903 if (!$wstart) $wstart = $wtime;
904 if ($EnableStopWatch != 2)
905 { $StopWatch[] = sprintf("%05.2f %s", $wtime-$wstart, $x); return; }
906 $dat = getrusage();
907 $utime = ($dat['ru_utime.tv_sec']+$dat['ru_utime.tv_usec']/1000000);
908 if (!$ustart) $ustart=$utime;
909 $StopWatch[] =
910 sprintf("%05.2f %05.2f %s", $wtime-$wstart, $utime-$ustart, $x);
914 ## DRange converts a variety of string formats into date (ranges).
915 ## It returns the start and end timestamps (+1 second) of the specified date.
916 function DRange($when) {
917 global $Now;
918 ## unix/posix @timestamp dates
919 if (preg_match('/^\\s*@(\\d+)\\s*(.*)$/', $when, $m)) {
920 $t0 = $m[2] ? strtotime($m[2], $m[1]) : $m[1];
921 return array($t0, $t0+1);
923 ## ISO-8601 dates
924 $dpat = '/
925 (?<!\\d) # non-digit
926 (19\\d\\d|20[0-3]\\d) # year ($1)
927 ([-.\\/]?) # date separator ($2)
928 (0\\d|1[0-2]) # month ($3)
929 (?: \\2 # repeat date separator
930 ([0-2]\\d|3[0-1]) # day ($4)
931 (?: T # time marker
932 ([01]\\d|2[0-4]) # hour ($5)
933 ([.:]?) # time separator ($6)
934 ([0-5]\\d) # minute ($7)
935 (?: \\6 # repeat time separator
936 ([0-5]\\d|60) # seconds ($8)
937 )? # optional :ss
938 )? # optional Thh:mm:ss
939 )? # optional -ddThh:mm:ss
940 (?!\d) # non-digit
941 /x';
942 if (preg_match($dpat, $when, $m) &&
943 !preg_match('/[+-]\\s*\\d+\\s*(sec(ond)?|min(ute)?|forth?night|day|week(day)?|month|year)s?/i', $when)) {
944 $n = $m;
945 ## if no time given, assume range of 1 day (except when full month)
946 if (@$m[4]>'' && @$m[5] == '') { @$n[4]++; }
947 ## if no day given, assume 1st of month and full month range
948 if (@$m[4] == '') { $m[4] = 1; $n[4] = 1; $n[3]++; }
949 ## if no seconds given, assume range of 1 minute (except when full day)
950 if (@$m[7]>'' && @$m[8] == '') { @$n[7]++; }
951 $t0 = @mktime($m[5], $m[7], $m[8], $m[3], $m[4], $m[1]);
952 $t1 = @mktime($n[5], $n[7], $n[8], $n[3], $n[4], $n[1]);
953 return array($t0, $t1);
955 ## now, today, tomorrow, yesterday
956 NoCache();
957 if ($when == 'now') return array($Now, $Now+1);
958 $m = localtime(time());
959 if ($when == 'tomorrow') { $m[3]++; $when = 'today'; }
960 if ($when == 'yesterday') { $m[3]--; $when = 'today'; }
961 if ($when == 'today')
962 return array(mktime(0, 0, 0, $m[4]+1, $m[3] , $m[5]+1900),
963 mktime(0, 0, 0, $m[4]+1, $m[3]+1, $m[5]+1900));
964 if (preg_match('/^\\s*$/', $when)) return array(-1, -1);
965 $t0 = strtotime($when);
966 $t1 = strtotime("+1 day", $t0);
967 return array($t0, $t1);
970 ## FmtDateTimeZ converts a GMT datetime like @2022-01-08T10:07:08Z
971 ## into a <time> element formatted according to $TimeFmt
972 function FmtDateTimeZ($m) {
973 global $TimeFmt, $TimeISOZFmt;
974 $time = strtotime(substr($m[0], 1)); # "@"
975 $iso = PSFT($TimeISOZFmt, $time, null, 'GMT');
976 $text = PSFT($TimeFmt, $time);
977 return "<time datetime='$iso'>$text</time>";
980 ## DiffTimeCompact subtracts 2 timestamps and outputs a compact
981 ## human-readable delay in hours, days, weeks, months or years
982 function DiffTimeCompact($time, $time2=null, $precision=1) {
983 if (is_null($time2)) $time2 = $GLOBALS['Now'];
984 $suffix = explode(',', XL('h,d,w,m,y'));
985 $x = $hours = abs($time2 - $time)/3600;
986 if ($x<24) return round($x,$precision).$suffix[0];
987 $x /= 24; if ($x<14) return round($x,$precision).$suffix[1];
988 $x /= 7; if ($x< 9) return round($x,$precision).$suffix[2];
989 $x = $hours/2/365.2425; if ($x<24) return round($x,$precision).$suffix[3];
990 return round($hours/24/365.2425,$precision).$suffix[4];
993 ## FileSizeCompact outputs a human readable file size
994 ## with an appropriate suffix.
995 ## Note: unreliable filemtime()/stat() over 2GB @ 32bit
996 function FileSizeCompact($n, $precision=1) {
997 if (!(float)$n) return 0;
998 $units = 'bkMGTPEZY';
999 $b = log((float)$n, 1024);
1000 $fb = floor($b);
1001 return round(pow(1024,$b-$fb),$precision).@$units[$fb];
1004 ## AsSpaced converts a string with WikiWords into a spaced version
1005 ## of that string. (It can be overridden via $AsSpacedFunction.)
1006 function AsSpaced($text) {
1007 $text = preg_replace("/([[:lower:]\\d])([[:upper:]])/", '$1 $2', $text);
1008 $text = preg_replace('/([^-\\d])(\\d[-\\d]*( |$))/','$1 $2',$text);
1009 return preg_replace("/([[:upper:]])([[:upper:]][[:lower:]\\d])/",
1010 '$1 $2', $text);
1013 ## Lock is used to make sure only one instance of PmWiki is running when
1014 ## files are being written. It does not "lock pages" for editing.
1015 function Lock($op) {
1016 global $WorkDir, $LockFile, $EnableReadOnly;
1017 if ($op > 0 && IsEnabled($EnableReadOnly, 0))
1018 Abort('Cannot modify site -- $EnableReadOnly is set', 'readonly');
1019 SDV($LockFile, "$WorkDir/.flock");
1020 mkdirp(dirname($LockFile));
1021 static $lockfp,$curop;
1022 if (!$lockfp) $lockfp = @fopen($LockFile, "w");
1023 if (!$lockfp) {
1024 if ($op <= 0) return;
1025 @unlink($LockFile);
1026 $lockfp = fopen($LockFile,"w") or
1027 Abort('Cannot acquire lockfile', 'flock');
1028 fixperms($LockFile);
1030 if ($op<0) { flock($lockfp,LOCK_UN); fclose($lockfp); $lockfp=0; $curop=0; }
1031 elseif ($op==0) { flock($lockfp,LOCK_UN); $curop=0; }
1032 elseif ($op==1 && $curop<1)
1033 { session_write_close(); flock($lockfp,LOCK_SH); $curop=1; }
1034 elseif ($op==2 && $curop<2)
1035 { session_write_close(); flock($lockfp,LOCK_EX); $curop=2; }
1038 ## mkdirp creates a directory and its parents as needed, and sets
1039 ## permissions accordingly.
1040 function mkdirp($dir) {
1041 global $ScriptUrl;
1042 if (file_exists($dir)) return;
1043 if (!file_exists(dirname($dir))) mkdirp(dirname($dir));
1044 if (mkdir($dir, 0777)) {
1045 fixperms($dir);
1046 if (@touch("$dir/xxx")) { unlink("$dir/xxx"); return; }
1047 rmdir($dir);
1049 $parent = realpath(dirname($dir));
1050 $bdir = basename($dir);
1051 $perms = decoct(fileperms($parent) & 03777);
1052 $msg = "PmWiki needs to have a writable <tt>$dir/</tt> directory
1053 before it can continue. You can create the directory manually
1054 by executing the following commands on your server:
1055 <pre> mkdir $parent/$bdir\n chmod 777 $parent/$bdir</pre>
1056 Then, <a href='{$ScriptUrl}'>reload this page</a>.";
1057 $safemode = ini_get('safe_mode');
1058 if (!$safemode) $msg .= "<br /><br />Or, for a slightly more
1059 secure installation, try executing <pre> chmod 2777 $parent</pre>
1060 on your server and following <a target='_blank' href='$ScriptUrl'>
1061 this link</a>. Afterwards you can restore the permissions to
1062 their current setting by executing <pre> chmod $perms $parent</pre>.";
1063 Abort($msg);
1066 ## fixperms attempts to correct permissions on a file or directory
1067 ## so that both PmWiki and the account (current dir) owner can manipulate it
1068 function fixperms($fname, $add = 0, $set = 0) {
1069 clearstatcache();
1070 if (!file_exists($fname)) Abort('?no such file');
1071 if ($set) { # advanced admins, $UploadPermSet
1072 if (fileperms($fname) != $set) @chmod($fname,$set);
1074 else {
1075 $bp = 0;
1076 if (fileowner($fname)!=@fileowner('.') && @fileowner('.')!==0)
1077 $bp = (is_dir($fname)) ? 007 : 006;
1078 if (filegroup($fname)==@filegroup('.')) $bp <<= 3;
1079 $bp |= $add;
1080 if ($bp && (fileperms($fname) & $bp) != $bp)
1081 @chmod($fname,fileperms($fname)|$bp);
1085 ## GlobToPCRE converts wildcard patterns into pcre patterns for
1086 ## inclusion and exclusion. Wildcards beginning with '-' or '!'
1087 ## are treated as things to be excluded.
1088 function GlobToPCRE($pat) {
1089 $pat = preg_quote($pat, '/');
1090 $pat = str_replace(array('\\*', '\\?', '\\[', '\\]', '\\^', '\\-', '\\!'),
1091 array('.*', '.', '[', ']', '^', '-', '!'), $pat);
1092 $excl = array(); $incl = array();
1093 foreach(preg_split('/,+\s?/', $pat, -1, PREG_SPLIT_NO_EMPTY) as $p) {
1094 if ($p[0] == '-' || $p[0] == '!') $excl[] = '^'.substr($p, 1).'$';
1095 else $incl[] = "^$p$";
1097 return array(implode('|', $incl), implode('|', $excl));
1100 ## FixGlob changes wildcard patterns without '.' to things like
1101 ## '*.foo' (name matches) or 'foo.*' (group matches).
1102 function FixGlob($x, $rep = '$1*.$2') {
1103 return preg_replace('/([\\s,][-!]?)([^\\/.\\s,]+)(?=[\\s,])/', $rep, ",$x,");
1106 ## MatchPageNames reduces $pagelist to those pages with names
1107 ## matching the pattern(s) in $pat. Patterns can be either
1108 ## regexes to include ('/'), regexes to exclude ('!'), or
1109 ## wildcard patterns (all others).
1110 function MatchPageNames($pagelist, $pat, $caseinsensitive = true) {
1111 # Note: MatchNames() is the generic function matching patterns,
1112 # works for attachments and other arrays. We can commit to
1113 # keep it generic, even if we someday change MatchPageNames().
1114 return MatchNames($pagelist, $pat, $caseinsensitive);
1116 function MatchNames($list, $pat, $caseinsensitive = true) {
1117 global $Charset, $EnableRangeMatchUTF8;
1118 # allow range matches in utf8; doesn't work on pmwiki.org and possibly elsewhere
1119 $pcre8 = (IsEnabled($EnableRangeMatchUTF8,0) && $Charset=='UTF-8')? 'u' : '';
1120 $insensitive = $caseinsensitive ? 'i' : '';
1121 $list = (array)$list;
1122 foreach((array)$pat as $p) {
1123 if (count($list) < 1) break;
1124 if (!$p) continue;
1125 switch ($p[0]) {
1126 case '/':
1127 $list = preg_grep($p, $list);
1128 break;
1129 case '!':
1130 $list = array_diff($list, preg_grep($p, $list));
1131 break;
1132 default:
1133 list($inclp, $exclp) = GlobToPCRE(str_replace('/', '.', $p));
1134 if ($exclp)
1135 $list = array_diff($list, preg_grep("/$exclp/$insensitive$pcre8", $list));
1136 if ($inclp)
1137 $list = preg_grep("/$inclp/$insensitive$pcre8", $list);
1140 return $list;
1143 ## ResolvePageName "normalizes" a pagename based on the current
1144 ## settings of $DefaultPage and $PagePathFmt. It's normally used
1145 ## during initialization to fix up any missing or partial pagenames.
1146 function ResolvePageName($pagename) {
1147 global $DefaultPage, $DefaultGroup, $DefaultName,
1148 $GroupPattern, $NamePattern, $EnableFixedUrlRedirect;
1149 SDV($DefaultPage, "$DefaultGroup.$DefaultName");
1150 $pagename = preg_replace('!([./][^./]+)\\.html?$!', '$1', $pagename);
1151 if ($pagename == '') return $DefaultPage;
1152 $p = MakePageName($DefaultPage, $pagename);
1153 if (!preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $p)) {
1154 header('HTTP/1.1 404 Not Found');
1155 Abort("\$[?invalid page name] \"$p\"");
1157 if (preg_match("/^($GroupPattern)[.\\/]($NamePattern)$/i", $pagename))
1158 return $p;
1159 if (IsEnabled($EnableFixedUrlRedirect, 1)
1160 && $p && (PageExists($p) || preg_match('/[\\/.]/', $pagename)))
1161 { Redirect($p); exit(); }
1162 return MakePageName($DefaultPage, "$pagename.$pagename");
1165 ## MakePageName is used to convert a string $str into a fully-qualified
1166 ## pagename. If $str doesn't contain a group qualifier, then
1167 ## MakePageName uses $basepage and $PagePathFmt to determine the
1168 ## group of the returned pagename.
1169 function MakePageName($basepage, $str) {
1170 global $MakePageNameFunction, $PageNameChars, $PagePathFmt,
1171 $MakePageNamePatterns, $MakePageNameSplitPattern;
1172 if (@$MakePageNameFunction) return $MakePageNameFunction($basepage, $str);
1174 SDV($PageNameChars,'-[:alnum:]');
1175 SDV($MakePageNamePatterns, array(
1176 "/'/" => '', # strip single-quotes
1177 "/[^$PageNameChars]+/" => ' ', # convert everything else to space
1178 '/((^|[^-\\w])\\w)/' => 'cb_toupper', # CamelCase
1179 '/ /' => ''));
1180 SDV($MakePageNameSplitPattern, '/[.\\/]/');
1181 $str = preg_replace('/[#?].*$/', '', $str);
1182 $m = preg_split($MakePageNameSplitPattern, $str);
1183 if (count($m)<1 || count($m)>2 || $m[0]=='') return '';
1184 ## handle "Group.Name" conversions
1185 if (@$m[1] > '') {
1186 $group = PPRA($MakePageNamePatterns, $m[0]);
1187 $name = PPRA($MakePageNamePatterns, $m[1]);
1188 return "$group.$name";
1190 $name = PPRA($MakePageNamePatterns, $m[0]);
1191 $isgrouphome = count($m) > 1;
1192 foreach((array)$PagePathFmt as $pg) {
1193 if ($isgrouphome && strncmp($pg, '$1.', 3) !== 0) continue;
1194 $pn = FmtPageName(str_replace('$1', $name, $pg), $basepage);
1195 if (PageExists($pn)) return $pn;
1197 if ($isgrouphome) {
1198 foreach((array)$PagePathFmt as $pg)
1199 if (strncmp($pg, '$1.', 3) == 0)
1200 return FmtPageName(str_replace('$1', $name, $pg), $basepage);
1201 return "$name.$name";
1203 return preg_replace('/[^\\/.]+$/', $name, $basepage);
1207 ## MakeBaseName uses $BaseNamePatterns to return the "base" form
1208 ## of a given pagename -- i.e., stripping any recipe-defined
1209 ## prefixes or suffixes from the page.
1210 function MakeBaseName($pagename, $patlist = NULL) {
1211 global $BaseNamePatterns;
1212 if (is_null($patlist)) $patlist = (array)@$BaseNamePatterns;
1213 foreach($patlist as $pat => $rep)
1214 $pagename = preg_replace($pat, $rep, $pagename); # TODO
1215 return $pagename;
1219 ## PCache caches basic information about a page and its attributes--
1220 ## usually everything except page text and page history. This makes
1221 ## for quicker access to certain values in PageVar below.
1222 function PCache($pagename, $page) {
1223 global $PCache;
1224 foreach($page as $k=>$v)
1225 if ($k!='text' && strpos($k,':')===false) $PCache[$pagename][$k]=$v;
1228 ## SetProperty saves a page property into $PCache. For convenience
1229 ## it returns the $value of the property just set. If $sep is supplied,
1230 ## then $value is appended to the current property (with $sep as
1231 ## as separator) instead of replacing it. If $keep is suplied and the
1232 ## property already exists, then $value will be ignored.
1233 function SetProperty($pagename, $prop, $value, $sep=NULL, $keep=NULL) {
1234 global $PCache, $KeepToken;
1235 NoCache();
1236 $prop = "=p_$prop";
1237 $value = preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/",
1238 "cb_expandkpv", $value);
1239 if (!is_null($sep) && isset($PCache[$pagename][$prop]))
1240 $value = $PCache[$pagename][$prop] . $sep . $value;
1241 if (is_null($keep) || !isset($PCache[$pagename][$prop]))
1242 $PCache[$pagename][$prop] = $value;
1243 return $PCache[$pagename][$prop];
1247 ## PageTextVar loads a page's text variables (defined by
1248 ## $PageTextVarPatterns) into a page's $PCache entry, and returns
1249 ## the property associated with $var.
1250 function PageTextVar($pagename, $var) {
1251 global $PCache, $PageTextVarPatterns, $MaxPageTextVars,
1252 $DefaultUnsetPageTextVars, $DefaultEmptyPageTextVars;
1253 SDV($MaxPageTextVars, 500);
1254 static $status;
1255 if (@$status["$pagename:$var"]++ > $MaxPageTextVars) return '';
1256 if (!@$PCache[$pagename]['=pagetextvars']) {
1257 $pc = &$PCache[$pagename];
1258 $pc['=pagetextvars'] = 1;
1259 $page = RetrieveAuthPage($pagename, 'read', false, READPAGE_CURRENT);
1260 if ($page) {
1261 foreach((array)$PageTextVarPatterns as $pat)
1262 if (preg_match_all($pat, IsEnabled($pc['=preview'],strval(@$page['text'])),
1263 $match, PREG_SET_ORDER)) {
1264 foreach($match as $m) {
1265 $t = preg_replace("/\\{\\$:{$m[2]}\\}/", '', $m[3]);
1267 $pc["=p_{$m[2]}"] = Qualify($pagename, $t);
1272 if (! isset($PCache[$pagename]["=p_$var"]) && is_array($DefaultUnsetPageTextVars)) {
1273 foreach($DefaultUnsetPageTextVars as $k=>$v) {
1274 if (count(MatchNames($var, $k))) {
1275 $PCache[$pagename]["=p_$var"] = $v;
1276 break;
1279 SDV($PCache[$pagename]["=p_$var"], ''); # to avoid re-loop
1281 elseif (@$PCache[$pagename]["=p_$var"] === '' && is_array($DefaultEmptyPageTextVars)) {
1282 foreach($DefaultEmptyPageTextVars as $k=>$v) {
1283 if (count(MatchNames($var, $k))) {
1284 $PCache[$pagename]["=p_$var"] = $v;
1285 break;
1288 SDV($PCache[$pagename]["=p_$var"], ''); # to avoid re-loop
1290 return strval(@$PCache[$pagename]["=p_$var"]);
1294 function PageVar($pagename, $var, $pn = '') {
1295 global $Cursor, $PCache, $FmtPV, $AsSpacedFunction, $ScriptUrl,
1296 $EnablePathInfo;
1297 if ($var == '$ScriptUrl') return PUE($ScriptUrl);
1298 if ($pn) {
1299 $pn = isset($Cursor[$pn]) ? $Cursor[$pn] : MakePageName($pagename, $pn);
1300 } else $pn = $pagename;
1301 if ($pn) {
1302 if (preg_match('/^(.+)[.\\/]([^.\\/]+)$/', $pn, $match)
1303 && !isset($PCache[$pn]['time'])
1304 && (!@$FmtPV[$var] || strpos($FmtPV[$var], '$page') !== false)) {
1305 $page = ReadPage($pn, READPAGE_CURRENT);
1306 PCache($pn, $page);
1308 @list($d, $group, $name) = $match;
1309 $page = &$PCache[$pn];
1310 if (@$FmtPV[$var] && strpos(@$FmtPV[$var], '$authpage') !== false) {
1311 if (!isset($page['=auth']['read'])) {
1312 $x = RetrieveAuthPage($pn, 'read', false, READPAGE_CURRENT);
1313 if ($x) PCache($pn, $x);
1315 if (@$page['=auth']['read']) $authpage = &$page;
1317 } else { $group = ''; $name = ''; }
1318 if (isset($FmtPV[$var])) return eval("return ({$FmtPV[$var]});");
1319 if (strncmp($var, '$:', 2)==0) return PageTextVar($pn, substr($var, 2));
1320 return '';
1323 ## FmtPageName handles $[internationalization] and $Variable
1324 ## substitutions in strings based on the $pagename argument.
1325 function FmtPageName($fmt, $pagename) {
1326 # Perform $-substitutions on $fmt relative to page given by $pagename
1327 global $GroupPattern, $NamePattern, $EnablePathInfo, $ScriptUrl,
1328 $GCount, $UnsafeGlobals, $FmtV, $FmtP, $FmtPV, $PCache, $AsSpacedFunction;
1329 if (! @$fmt) { return ''; }
1330 if (strpos($fmt,'$')===false) return $fmt;
1331 $fmt = preg_replace_callback('/\\$([A-Z]\\w*Fmt)\\b/','cb_expandglobal',$fmt);
1332 $fmt = preg_replace_callback('/\\$\\[(?>([^\\]]+))\\]/',"cb_expandxlang",$fmt);
1333 $fmt = str_replace('{$ScriptUrl}', '$ScriptUrl', $fmt);
1334 $pprc = new PPRC($pagename);
1335 $fmt = preg_replace_callback('/\\{\\*?(\\$[A-Z]\\w+)\\}/',
1336 array($pprc, 'pagevar'), $fmt);
1337 if (strpos($fmt,'$')===false) return $fmt;
1338 if ($FmtP) $fmt = PPRA($FmtP, $fmt); # FIXME
1339 static $pv, $pvpat;
1340 if ($pv != count($FmtPV)) {
1341 $pvpat = str_replace('$', '\\$', implode('|', array_keys($FmtPV)));
1342 $pv = count($FmtPV);
1344 $fmt = preg_replace_callback("/($pvpat)\\b/", array($pprc, 'pagevar'), $fmt);
1345 $fmt = preg_replace_callback('!\\$ScriptUrl/([^?#\'"\\s<>]+)!',
1346 'cb_expandscripturl', $fmt);
1347 if (strpos($fmt,'$')===false) return $fmt;
1348 static $g;
1349 if ($GCount != count($GLOBALS)+count($FmtV)) {
1350 $g = array();
1351 foreach($GLOBALS as $n=>$v) {
1352 if (is_array($v) || is_object($v) ||
1353 isset($FmtV["\$$n"]) || in_array($n,$UnsafeGlobals)) continue;
1354 $g["\$$n"] = $v;
1356 $GCount = count($GLOBALS)+count($FmtV);
1357 krsort($g); reset($g);
1359 $fmt = str_replace(array_keys($g),array_values($g),$fmt);
1360 $fmt = preg_replace_callback('/(?>(\\$[[:alpha:]]\\w+))/',
1361 "cb_expandfmtv", $fmt);
1362 return $fmt;
1364 function cb_expandglobal($m){ return @$GLOBALS[$m[1]]; }
1365 function cb_expandxlang ($m){ return NoCache(XL($m[1])); }
1366 function cb_expandfmtv ($m){
1367 return isset($GLOBALS['FmtV'][$m[1]]) ? $GLOBALS['FmtV'][$m[1]] : $m[1];
1369 function cb_expandscripturl($m) {
1370 global $EnablePathInfo, $ScriptUrl;
1371 return (@$EnablePathInfo) ? "$ScriptUrl/" . PUE($m[1])
1372 : "$ScriptUrl?n=".str_replace('/','.',PUE($m[1]));
1376 ## FmtPageTitle returns the page title, or the page name.
1377 ## It localizes standard technical pages (RecentChanges...)
1378 function FmtPageTitle($title, $name, $spaced=0) {
1379 if ($title>'') return str_replace("$", "&#036;", $title);
1380 global $SpaceWikiWords, $AsSpacedFunction;
1381 if (preg_match("/^(Site(Admin)?
1382 |(All)?(Site|Group)(Header|Footer|Attributes)
1383 |(Side|Left|Right)Bar
1384 |(Wiki)?Sand[Bb]ox
1385 |(All)?Recent(Changes|Uploads)|(Auth|Edit)Form
1386 |InterMap|PageActions|\\w+QuickReference|\\w+Templates
1387 |NotifyList|AuthUser|ApprovedUrls|(Block|Auth)List
1388 )$/x", $name) && $name != XL($name))
1389 return XL($name);
1390 return ($spaced || $SpaceWikiWords) ? $AsSpacedFunction($name) : $name;
1393 ## FmtGroupHome returns the homepage of any page ($Group or $DefaultName)
1394 function FmtGroupHome($pn,$group,$var) {
1395 $gpn = MakePageName($pn, "$group.");
1396 $pv = substr($var, 14);
1397 if (!$pv) return $gpn;
1398 return PageVar($gpn, "\$$pv");
1401 ## FmtTemplateVars uses $vars to replace all occurrences of
1402 ## {$$key} in $text with $vars['key'].
1403 function FmtTemplateVars($text, $vars, $pagename = NULL) {
1404 global $FmtPV, $EnableUndefinedTemplateVars;
1405 if ($pagename) {
1406 $pat = implode('|', array_map('preg_quote', array_keys($FmtPV)));
1407 $pprc = new PPRC($pagename);
1408 $text = preg_replace_callback("/\\{\\$($pat)\\}/",
1409 array($pprc, 'pagevar'), $text);
1411 foreach(preg_grep('/^[\\w$]/', array_keys($vars)) as $k)
1412 if (!is_array($vars[$k]))
1413 $text = str_replace("{\$\$$k}", @$vars[$k], $text);
1414 if (! IsEnabled($EnableUndefinedTemplateVars, 0))
1415 $text = preg_replace("/\\{\\$\\$\\w+\\}/", '', $text);
1416 return $text;
1419 ## The XL functions provide translation tables for $[i18n] strings
1420 ## in FmtPageName().
1421 function XL($key) {
1422 global $XL,$XLLangs;
1423 foreach($XLLangs as $l) if (isset($XL[$l][$key])) return $XL[$l][$key];
1424 return $key;
1426 function XLSDV($lang,$a) {
1427 global $XL;
1428 foreach($a as $k=>$v) {
1429 if (!isset($XL[$lang][$k])) {
1430 if (preg_match('/^e_(rows|cols)$/', $k)) $v = intval($v);
1431 elseif (preg_match('/^ak_/', $k)) $v = @$v[0];
1432 $XL[$lang][$k]=$v;
1436 function XLPage($lang,$p,$nohtml=false) {
1437 global $TimeFmt,$XLLangs,$FarmD, $EnableXLPageScriptLoad;
1438 $page = ReadPage($p, READPAGE_CURRENT);
1439 if (!$page) return;
1440 $text = preg_replace("/=>\\s*\n/",'=> ',@$page['text']);
1441 foreach(explode("\n",$text) as $l)
1442 if (preg_match('/^\\s*[\'"](.+?)[\'"]\\s*=>\\s*[\'"](.+)[\'"]/',$l,$m))
1443 $xl[stripslashes($m[1])] = stripslashes($nohtml? PHSC($m[2]): $m[2]);
1444 if (isset($xl)) {
1445 if (IsEnabled($EnableXLPageScriptLoad, 0) && @$xl['xlpage-i18n']) {
1446 $i18n = preg_replace('/[^-\\w]/','',$xl['xlpage-i18n']);
1447 include_once("$FarmD/scripts/xlpage-$i18n.php");
1449 if (@$xl['Locale']) setlocale(LC_ALL,$xl['Locale']);
1450 if (@$xl['TimeFmt']) $TimeFmt=$xl['TimeFmt'];
1451 if (!in_array($lang, $XLLangs)) array_unshift($XLLangs, $lang);
1452 XLSDV($lang,$xl);
1456 ## CmpPageAttr is used with uksort to order a page's elements with
1457 ## the latest items first. This can make some operations more efficient.
1458 function CmpPageAttr($a, $b) {
1459 @list($x, $agmt) = explode(':', $a);
1460 @list($x, $bgmt) = explode(':', $b);
1461 $nagmt = intval($agmt);
1462 $nbgmt = intval($bgmt);
1463 if ($agmt != $bgmt)
1464 return ($nagmt==0 || $nbgmt==0) ? $nagmt - $nbgmt : $nbgmt - $nagmt;
1465 return strcmp($a, $b);
1468 function pm_recode($s,$from,$to) {
1469 static $able;
1470 if (is_null($able)) {
1471 # can we rely on iconv() or on mb_convert_encoding() ?
1472 if (function_exists('iconv') && @iconv("UTF-8", "WINDOWS-1252//IGNORE", "te\xd0\xafst")=='test' )
1473 $able = 'iconv';
1474 elseif (function_exists('mb_convert_encoding') && @mb_convert_encoding("te\xd0\xafst", "WINDOWS-1252", "UTF-8")=="te?st")
1475 $able = 'mb';
1476 elseif (class_exists('UConverter')) $able = 'uc';
1477 else $able = false;
1479 $to = strtoupper($to);
1480 $from = strtoupper($from);
1481 switch ($able) {
1482 case "iconv":
1483 return @iconv($from,"$to//IGNORE",$s);
1484 case "mb":
1485 return @mb_convert_encoding($s,$to,$from);
1486 case "uc":
1487 if ($to == 'WINDOWS-1252') $to = 'ISO-8859-1';
1488 if ($from == 'WINDOWS-1252') $from = 'ISO-8859-1';
1489 return @UConverter::transcode($s,$to,$from);
1491 if (function_exists('utf8_decode')) {
1492 if ($to=='UTF-8' && $from=='WINDOWS-1252') return @utf8_decode($s);
1493 if ($from=='UTF-8' && $to=='WINDOWS-1252') return @utf8_encode($s);
1495 return $s;
1498 ## class PageStore holds objects that store pages via the native
1499 ## filesystem.
1500 class PageStore {
1501 var $dirfmt;
1502 var $iswrite;
1503 var $encodefilenames;
1504 var $attr;
1505 function __construct($d='$WorkDir/$FullName', $w=0, $a=NULL) {
1506 $this->dirfmt = $d; $this->iswrite = $w; $this->attr = (array)$a;
1507 $GLOBALS['PageExistsCache'] = array();
1509 function recodefn($s,$from,$to) {
1510 return pm_recode($s,$from,$to);
1512 function pagefile($pagename) {
1513 global $FarmD;
1514 $dfmt = $this->dirfmt;
1515 if ($pagename > '') {
1516 $pagename = str_replace('/', '.', $pagename);
1517 if ($dfmt == 'wiki.d/{$FullName}') # optimizations for
1518 return $this->PFE("wiki.d/$pagename"); # standard locations
1519 if ($dfmt == '$FarmD/wikilib.d/{$FullName}') #
1520 return $this->PFE("$FarmD/wikilib.d/$pagename");
1521 if ($dfmt == 'wiki.d/{$Group}/{$FullName}')
1522 return $this->PFE(preg_replace('/([^.]+).*/', 'wiki.d/$1/$0', $pagename));
1524 return $this->PFE(FmtPageName($dfmt, $pagename));
1526 function PFE($f) { # pagefile_encode
1527 if (!$this->encodefilenames) return $f;
1528 global $PageFileEncodeFunction;
1529 return $PageFileEncodeFunction($f);
1531 function PFD($f) { # pagefile_decode
1532 if (!$this->encodefilenames) return $f;
1533 global $PageFileDecodeFunction;
1534 return $PageFileDecodeFunction($f);
1536 function read($pagename, $since=0) {
1537 $newline = '';
1538 $urlencoded = false;
1539 $pagefile = $this->pagefile($pagename);
1540 if ($pagefile && ($fp=@fopen($pagefile, "r"))) {
1541 $page = $this->attr;
1542 while (!feof($fp)) {
1543 $line = @fgets($fp, 4096);
1544 while (substr($line, -1, 1) != "\n" && !feof($fp))
1545 { $line .= fgets($fp, 4096); }
1546 $line = rtrim($line);
1547 if ($urlencoded) $line = urldecode(str_replace('+', '%2b', $line));
1548 @list($k,$v) = explode('=', $line, 2);
1549 if (!$k) continue;
1550 if ($k == 'version') {
1551 $ordered = (strpos($v, 'ordered=1') !== false);
1552 $urlencoded = (strpos($v, 'urlencoded=1') !== false);
1553 if (strpos($v, 'pmwiki-0.')!==false) $newline="\262";
1555 if ($k == 'newline') { $newline = $v; continue; }
1556 if ($since > 0 && preg_match('/:(\\d+)/', $k, $m) && $m[1] < $since) {
1557 if (@$ordered) break;
1558 continue;
1560 if ($newline) $v = str_replace($newline, "\n", $v);
1561 $page[$k] = $v;
1563 fclose($fp);
1565 return $this->recode($pagename, @$page);
1567 function write($pagename,$page) {
1568 global $Now, $Version, $Charset, $EnableRevUserAgent, $PageExistsCache, $DenyHtaccessContent;
1569 $page['charset'] = $Charset;
1570 $page['name'] = $pagename;
1571 $page['time'] = $Now;
1572 $page['host'] = strval(@$_SERVER['REMOTE_ADDR']);
1573 $page['agent'] = strval(@$_SERVER['HTTP_USER_AGENT']);
1574 if (IsEnabled($EnableRevUserAgent, 0)) $page["agent:$Now"] = $page['agent'];
1575 $page['rev'] = intval(@$page['rev'])+1;
1576 unset($page['version']); unset($page['newline']);
1577 uksort($page, 'CmpPageAttr');
1578 $s = false;
1579 $pagefile = $this->pagefile($pagename);
1580 $dir = dirname($pagefile); mkdirp($dir);
1581 if (!file_exists("$dir/.htaccess") && $fp = @fopen("$dir/.htaccess", "w"))
1582 { fwrite($fp, $DenyHtaccessContent); fclose($fp); }
1583 if ($pagefile && ($fp=fopen("$pagefile,new","w"))) {
1584 $r0 = array('%', "\n", '<');
1585 $r1 = array('%25', '%0a', '%3c');
1586 $x = "version=$Version ordered=1 urlencoded=1\n";
1587 $s = true && fputs($fp, $x); $sz = strlen($x);
1588 foreach($page as $k=>$v)
1589 if ($k > '' && $k[0] != '=') {
1590 $x = str_replace($r0, $r1, "$k=$v") . "\n";
1591 $s = $s && fputs($fp, $x); $sz += strlen($x);
1593 $s = fclose($fp) && $s;
1594 $s = $s && (filesize("$pagefile,new") > $sz * 0.95);
1595 if (file_exists($pagefile)) $s = $s && unlink($pagefile);
1596 $s = $s && rename("$pagefile,new", $pagefile);
1598 $s && fixperms($pagefile);
1599 if (!$s)
1600 Abort("Cannot write page to $pagename ($pagefile)...changes not saved");
1601 PCache($pagename, $page);
1602 unset($PageExistsCache[$pagename]); # PITS:01401
1604 function exists($pagename) {
1605 if (!$pagename) return false;
1606 $pagefile = $this->pagefile($pagename);
1607 return ($pagefile && file_exists($pagefile));
1609 function delete($pagename) {
1610 global $Now, $PageExistsCache;
1611 $pagefile = $this->pagefile($pagename);
1612 @rename($pagefile,"$pagefile,del-$Now");
1613 unset($PageExistsCache[$pagename]); # PITS:01401
1615 function ls($pats=NULL) {
1616 global $GroupPattern, $NamePattern;
1617 StopWatch("PageStore::ls begin {$this->dirfmt}");
1618 $pats=(array)$pats;
1619 array_push($pats, "/^$GroupPattern\.$NamePattern$/");
1620 $dir = $this->pagefile('$Group.$Name');
1621 $maxslash = substr_count($dir, '/');
1622 $dirlist = array(preg_replace('!/*[^/]*\\$.*$!','',$dir));
1623 $out = array();
1624 while (count($dirlist)>0) {
1625 $dir = array_shift($dirlist);
1626 $dfp = @opendir($dir); if (!$dfp) { continue; }
1627 $dirslash = substr_count($dir, '/') + 1;
1628 $o = array();
1629 while ( ($pagefile = readdir($dfp)) !== false) {
1630 if ($pagefile[0] == '.') continue;
1631 if ($dirslash < $maxslash && is_dir("$dir/$pagefile"))
1632 { array_push($dirlist,"$dir/$pagefile"); continue; }
1633 if ($dirslash == $maxslash) $o[] = $this->PFD($pagefile);
1635 closedir($dfp);
1636 StopWatch("PageStore::ls merge {$this->dirfmt}");
1637 $out = array_merge($out, MatchPageNames($o, $pats));
1639 StopWatch("PageStore::ls end {$this->dirfmt}");
1640 return $out;
1642 function recode($pagename, $a) {
1643 if (!$a) return false;
1644 global $Charset, $PageRecodeFunction, $DefaultPageCharset, $EnableOldCharset;
1645 if (@$PageRecodeFunction && function_exists($PageRecodeFunction))
1646 return $PageRecodeFunction($a);
1647 if (IsEnabled($EnableOldCharset)) $a['=oldcharset'] = @$a['charset'];
1648 SDVA($DefaultPageCharset, array(''=>@$Charset)); # pre-2.2.31 RecentChanges
1649 if (@$DefaultPageCharset[$a['charset']]>'') # wrong pre-2.2.30 encs. *-2, *-9, *-13
1650 $a['charset'] = $DefaultPageCharset[@$a['charset']];
1651 if (!$a['charset'] || $Charset==$a['charset']) return $a;
1652 $from = ($a['charset']=='ISO-8859-1') ? 'WINDOWS-1252' : $a['charset'];
1653 $to = ($Charset=='ISO-8859-1') ? 'WINDOWS-1252' : $Charset;
1654 if ($from != $to) {
1655 foreach($a as $k=>$v) $a[$k] = $this->recodefn($v,$from,$to);
1657 $a['charset'] = $Charset;
1658 return $a;
1662 function ReadPage($pagename, $since=0) {
1663 # read a page from the appropriate directories given by $WikiReadDirsFmt.
1664 global $WikiLibDirs,$Now;
1665 foreach ($WikiLibDirs as $dir) {
1666 $page = $dir->read($pagename, $since);
1667 if ($page) break;
1669 if (@!$page) $page = array('ctime' => $Now);
1670 if (@!$page['time']) $page['time'] = $Now;
1671 return $page;
1674 function WritePage($pagename,$page) {
1675 global $WikiLibDirs,$WikiDir,$LastModFile;
1676 $WikiDir->iswrite = 1;
1677 for($i=0; $i<count($WikiLibDirs); $i++) {
1678 $wd = &$WikiLibDirs[$i];
1679 if ($wd->iswrite && $wd->exists($pagename)) break;
1681 if ($i >= count($WikiLibDirs)) $wd = &$WikiDir;
1682 $wd->write($pagename,$page);
1683 if ($LastModFile && !@touch($LastModFile))
1684 { unlink($LastModFile); touch($LastModFile); fixperms($LastModFile); }
1687 function PageExists($pagename) {
1688 ## note: $PageExistsCache might change or disappear someday
1689 global $WikiLibDirs, $PageExistsCache;
1690 if (!isset($PageExistsCache[$pagename])) {
1691 foreach((array)$WikiLibDirs as $dir)
1692 if ($PageExistsCache[$pagename] = $dir->exists($pagename)) break;
1694 return $PageExistsCache[$pagename];
1697 function ListPages($pat=NULL) {
1698 global $WikiLibDirs;
1699 foreach((array)$WikiLibDirs as $dir)
1700 $out = array_unique(array_merge($dir->ls($pat),(array)@$out));
1701 return $out;
1704 function RetrieveAuthPage($pagename, $level, $authprompt=true, $since=0) {
1705 global $AuthFunction;
1706 SDV($AuthFunction,'PmWikiAuth');
1707 if (!function_exists($AuthFunction)) return ReadPage($pagename, $since);
1708 return $AuthFunction($pagename, $level, $authprompt, $since);
1711 function Abort($msg, $info='') {
1712 # exit pmwiki with an abort message
1713 global $ScriptUrl, $Charset, $AbortFunction;
1714 if (@$AbortFunction) return $AbortFunction($msg, $info);
1715 if ($info)
1716 $info = "<p class='vspace'><a target='_blank' rel='nofollow' href='http://www.pmwiki.org/pmwiki/info/$info'>$[More information]</a></p>";
1717 $msg = "<h3>$[PmWiki can't process your request]</h3>
1718 <p class='vspace'>$msg</p>
1719 <p class='vspace'>$[We are sorry for any inconvenience].</p>
1720 $info
1721 <p class='vspace'><a href='$ScriptUrl'>$[Return to] $ScriptUrl</a></p>";
1722 @header("Content-type: text/html; charset=$Charset");
1723 echo preg_replace_callback('/\\$\\[([^\\]]+)\\]/', "cb_expandxlang", $msg);
1724 exit;
1727 function Redirect($pagename, $urlfmt='$PageUrl', $redirecturl=null) {
1728 # redirect the browser to $pagename
1729 global $EnableRedirect, $RedirectDelay, $EnableStopWatch;
1730 SDV($RedirectDelay, 0);
1731 clearstatcache();
1732 if (is_null($redirecturl)) $redirecturl = FmtPageName($urlfmt,$pagename);
1733 if (IsEnabled($EnableRedirect,1) &&
1734 (!isset($_REQUEST['redirect']) || $_REQUEST['redirect'])) {
1735 header("Location: $redirecturl");
1736 header("Content-type: text/html");
1737 echo "<html><head>
1738 <meta http-equiv='Refresh' Content='$RedirectDelay; URL=$redirecturl' />
1739 <title>Redirect</title></head><body></body></html>";
1740 exit;
1742 echo "<a href='$redirecturl'>Redirect to $redirecturl</a>";
1743 if (@$EnableStopWatch && function_exists('StopWatchHTML'))
1744 StopWatchHTML($pagename, 1);
1745 exit;
1748 function PrintFmt($pagename,$fmt) {
1749 global $HTTPHeaders,$FmtV;
1750 if (is_array($fmt)) {
1751 foreach($fmt as $f) PrintFmt($pagename,$f);
1752 return;
1754 if ($fmt == 'headers:') {
1755 foreach($HTTPHeaders as $h) (@$sent++) ? @header($h) : header($h);
1756 return;
1758 $fmt = strval($fmt);
1759 $x = FmtPageName($fmt,$pagename);
1760 if (strncmp($fmt, 'function:', 9) == 0 &&
1761 preg_match('/^function:(\S+)\s*(.*)$/s', $x, $match) &&
1762 function_exists($match[1]))
1763 { $match[1]($pagename,$match[2]); return; }
1764 if (strncmp($fmt, 'file:', 5) == 0 && preg_match("/^file:(.+)/s",$x,$match)) {
1765 $filelist = preg_split('/[\\s]+/',$match[1],-1,PREG_SPLIT_NO_EMPTY);
1766 foreach($filelist as $f) {
1767 if (file_exists($f)) { include($f); return; }
1769 return;
1771 if (substr($x, 0, 7) == 'markup:')
1772 { print MarkupToHTML($pagename, substr($x, 7)); return; }
1773 if (substr($x, 0, 5) == 'wiki:')
1774 { PrintWikiPage($pagename, substr($x, 5), 'read'); return; }
1775 if (substr($x, 0, 5) == 'page:')
1776 { PrintWikiPage($pagename, substr($x, 5), ''); return; }
1777 echo $x;
1780 function PrintWikiPage($pagename, $wikilist=NULL, $auth='read', $return=false) {
1781 if (is_null($wikilist)) $wikilist=$pagename;
1782 $pagelist = preg_split('/\s+/',$wikilist,-1,PREG_SPLIT_NO_EMPTY);
1783 foreach($pagelist as $p) {
1784 if (PageExists($p)) {
1785 $page = ($auth) ? RetrieveAuthPage($p, $auth, false, READPAGE_CURRENT)
1786 : ReadPage($p, READPAGE_CURRENT);
1787 if (@$page['text']) {
1788 $html = MarkupToHTML($pagename,Qualify($p, $page['text']));
1789 if ($return) return $html;
1790 echo $html;
1792 return '';
1795 return '';
1798 function Keep($x, $pool=NULL) {
1799 if (is_array($x)) $x = $x[0]; # used in many callbacks
1800 # Keep preserves a string from being processed by wiki markups
1801 global $BlockPattern, $KeepToken, $KPV, $KPCount;
1802 $x = preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/", 'cb_expandkpv', $x);
1803 if (is_null($pool) && preg_match("!</?($BlockPattern)\\b!", $x)) $pool = 'B';
1804 $KPCount++; $KPV[$KPCount.$pool]=$x;
1805 return $KeepToken.$KPCount.$pool.$KeepToken;
1808 function KeepBlock($x, $pool=NULL) {
1809 return '<:block>' . Keep($x, $pool);
1812 ## MarkupEscape examines markup source and escapes any [@...@]
1813 ## and [=...=] sequences using Keep(). MarkupRestore undoes the
1814 ## effect of any MarkupEscape().
1815 function MarkupEscape($text) {
1816 global $EscapePattern;
1817 SDV($EscapePattern, '\\[([=@]).*?\\1\\]');
1818 return preg_replace_callback("/$EscapePattern/s", "Keep", strval($text));
1820 function MarkupRestore($text) {
1821 global $KeepToken, $KPV;
1822 if (is_null($text)) return '';
1823 if (!$text) return $text;
1824 return preg_replace_callback("/$KeepToken(\\d.*?)$KeepToken/", 'cb_expandkpv', $text);
1828 ## Qualify() applies $QualifyPatterns to convert relative links
1829 ## and references into absolute equivalents.
1830 function Qualify($pagename, $text) {
1831 global $QualifyPatterns, $KeepToken, $KPV, $tmp_qualify;
1832 if (!@$QualifyPatterns) return $text;
1833 $text = MarkupEscape($text);
1834 $group = $tmp_qualify['group'] = PageVar($pagename, '$Group');
1835 $name = $tmp_qualify['name'] = PageVar($pagename, '$Name');
1836 $tmp_qualify['pagename'] = $pagename;
1837 $text = PPRA((array)$QualifyPatterns, $text);
1838 return MarkupRestore($text);
1842 function CondText($pagename,$condspec,$condtext) {
1843 global $Conditions;
1844 if (!preg_match("/^(\\S+)\\s*(!?)\\s*(\\S+)?\\s*(.*?)\\s*$/",
1845 $condspec,$match)) return '';
1846 @list($condstr,$condtype,$not,$condname,$condparm) = $match;
1847 if (isset($Conditions[$condname])) {
1848 $tf = @eval("return (".$Conditions[$condname].");");
1849 if (!$tf xor $not) $condtext='';
1851 return $condtext;
1855 ## TextSection extracts a section of text delimited by page anchors.
1856 ## The $sections parameter can have the form
1857 ## #abc - [[#abc]] to next anchor
1858 ## #abc#def - [[#abc]] up to [[#def]]
1859 ## #abc#, #abc.. - [[#abc]] to end of text
1860 ## ##abc, #..#abc - beginning of text to [[#abc]]
1861 ## Returns the text unchanged if no sections are requested,
1862 ## or false if a requested beginning anchor isn't in the text.
1863 function TextSection($text, $sections, $args = NULL) {
1864 if (!$text) return false; # PHP 8.1
1865 $args = (array)$args;
1866 $npat = '[[:alpha:]][-\\w.]*';
1867 if (!preg_match("/#($npat)?(\\.\\.)?(#($npat)?)?/", $sections, $match))
1868 return $text;
1869 @list($x, $aa, $dots, $b, $bb) = $match;
1870 if (!$dots && !$b) $bb = $npat;
1871 if ($aa) {
1872 $aa = preg_replace('/\\.\\.$/', '', $aa);
1873 $pos = strpos($text, "[[#$aa]]"); if ($pos === false) return false;
1874 if (@$args['anchors'])
1875 while ($pos > 0 && $text[$pos-1] != "\n") $pos--;
1876 else $pos += strlen("[[#$aa]]");
1877 $text = substr($text, $pos);
1879 if ($bb)
1880 $text = preg_replace("/(\n)[^\n]*\\[\\[#$bb\\]\\].*$/s", '$1', $text, 1);
1881 return $text;
1885 ## RetrieveAuthSection extracts a section of text from a page.
1886 ## If $pagesection starts with anything other than '#', it identifies
1887 ## the page to extract text from. Otherwise RetrieveAuthSection looks
1888 ## in the pages given by $list, or in $pagename if $list is not specified.
1889 ## The selected page is placed in the global $RASPageName variable.
1890 ## The caller is responsible for calling Qualify() as needed.
1891 function RetrieveAuthSection($pagename, $pagesection, $list=NULL, $auth='read') {
1892 global $RASPageName, $PCache;
1893 if ($pagesection[0] != '#')
1894 $list = array(MakePageName($pagename, $pagesection));
1895 elseif (is_null($list)) $list = array($pagename);
1896 foreach((array)$list as $t) {
1897 $t = FmtPageName($t, $pagename);
1898 if (!PageExists($t)) continue;
1899 $tpage = RetrieveAuthPage($t, $auth, false, READPAGE_CURRENT);
1900 if (!$tpage) continue;
1901 $text = TextSection(IsEnabled($PCache[$t]['=preview'],strval(@$tpage['text'])),$pagesection);
1902 if ($text !== false) { $RASPageName = $t; return $text; }
1904 $RASPageName = '';
1905 return false;
1908 function IncludeText($pagename, $inclspec) {
1909 global $MaxIncludes, $IncludeOpt, $InclCount, $PCache, $IncludedPages;
1910 SDV($MaxIncludes,50);
1911 SDVA($IncludeOpt, array('self'=>1));
1912 if (@$InclCount[$pagename]++>=$MaxIncludes) return Keep($inclspec);
1913 $args = array_merge($IncludeOpt, ParseArgs($inclspec));
1914 while (count($args['#'])>0) {
1915 $k = array_shift($args['#']); $v = array_shift($args['#']);
1916 if ($k=='') {
1917 if ($v[0] != '#') {
1918 if (isset($itext)) continue;
1919 $iname = MakePageName($pagename, $v);
1920 if (!$args['self'] && $iname == $pagename) continue;
1921 $ipage = RetrieveAuthPage($iname, 'read', false, READPAGE_CURRENT);
1922 if (isset($PCache[$iname]['=preview'])) $itext = $PCache[$iname]['=preview'];
1923 elseif (isset($ipage['text'])) $itext = $ipage['text'];
1925 if (isset($itext))
1926 $itext = TextSection($itext, $v, array('anchors' => 1));
1927 continue;
1929 $itext = strval(@$itext);
1930 if (preg_match('/^(?:line|para)s?$/', $k)) {
1931 preg_match('/^(\\d*)(\\.\\.(\\d*))?$/', $v, $match);
1932 @list($x, $a, $dots, $b) = $match;
1933 $upat = ($k[0] == 'p') ? ".*?(\n\\s*\n|$)" : "[^\n]*(?:\n|$)";
1934 if (!$dots) { $b=$a; $a=0; }
1935 if ($a>0) $a--;
1936 $itext=preg_replace("/^(($upat){0,$b}).*$/s",'$1',$itext,1);
1937 $itext=preg_replace("/^($upat){0,$a}/s",'',$itext,1);
1938 continue;
1941 $basepage = isset($args['basepage'])
1942 ? MakePageName($pagename, $args['basepage'])
1943 : @$iname;
1944 if ($basepage) $itext = Qualify(@$basepage, @$itext);
1945 if (@$itext) @$IncludedPages[$iname]++;
1946 return FmtTemplateVars(PVSE(@$itext), $args);
1950 function RedirectMarkup($pagename, $opt) {
1951 $k = Keep("(:redirect $opt:)");
1952 global $MarkupFrame, $EnableRedirectQuiet;
1953 if (!@$MarkupFrame[0]['redirect']) return $k;
1954 $opt = ParseArgs($opt);
1955 $to = @$opt['to']; if (!$to) $to = @$opt[''][0];
1956 if (!$to) return $k;
1957 if (preg_match('/^([^#]+)(#[A-Za-z][-\\w]*)$/', $to, $match))
1958 { $to = $match[1]; $anchor = @$match[2]; }
1959 $to = MakePageName($pagename, $to);
1960 if (!PageExists($to)) return $k;
1961 if ($to == $pagename) return '';
1962 if (@$opt['from']
1963 && !MatchPageNames($pagename, FixGlob($opt['from'], '$1*.$2')))
1964 return '';
1965 if (preg_match('/^30[1237]$/', @$opt['status']))
1966 header("HTTP/1.1 {$opt['status']}");
1967 Redirect($to, "{\$PageUrl}"
1968 . (IsEnabled($EnableRedirectQuiet, 0) && IsEnabled($opt['quiet'], 0)
1969 ? '' : "?from=$pagename")
1970 . $anchor);
1971 exit();
1975 function Block($b) {
1976 global $BlockMarkups,$HTMLVSpace,$HTMLPNewline,$MarkupFrame;
1977 $mf = &$MarkupFrame[0]; $cs = &$mf['cs']; $vspaces = &$mf['vs'];
1978 $out = '';
1979 if ($b == 'vspace') {
1980 $vspaces .= "\n";
1981 while (count($cs)>0 && @end($cs)!='pre' && @$BlockMarkups[@end($cs)][3]==0)
1982 { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
1983 return $out;
1985 @list($code, $depth, $icol) = explode(',', $b);
1986 if (!$code) $depth = 1;
1987 if (!is_numeric($depth)) $depth = @$depth? strlen($depth) : 0; # PHP8.1
1988 if (!is_numeric($icol)) $icol = @$icol? strlen($icol) : 0; # PHP8.1
1989 if ($depth > 0) $depth += @$mf['idep'];
1990 if ($icol > 0) $mf['is'][$depth] = $icol + @$mf['icol'];
1991 @$mf['idep'] = @$mf['icol'] = 0;
1992 while (count($cs)>$depth)
1993 { $c = array_pop($cs); $out .= @$BlockMarkups[$c][2]; }
1994 if (!$code) {
1995 if (@end($cs) == 'p') { $out .= $HTMLPNewline; $code = 'p'; }
1996 else if ($depth < 2) { $code = 'p'; $mf['is'][$depth] = 0; }
1997 else { $out .= $HTMLPNewline; $code = 'block'; }
1999 if ($depth>0 && $depth==count($cs) && $cs[$depth-1]!=$code)
2000 { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
2001 while (count($cs)>0 && @end($cs)!=$code &&
2002 @$BlockMarkups[@end($cs)][3]==0)
2003 { $c = array_pop($cs); $out .= $BlockMarkups[$c][2]; }
2004 if ($vspaces) {
2005 $out .= (@end($cs) == 'pre') ? $vspaces : $HTMLVSpace;
2006 $vspaces='';
2008 if ($depth==0) { return $out; }
2009 if ($depth==count($cs)) { return $out.$BlockMarkups[$code][1]; }
2010 while (count($cs)<$depth-1) {
2011 array_push($cs, 'dl'); $mf['is'][count($cs)] = 0;
2012 $out .= $BlockMarkups['dl'][0].'<dd>';
2014 if (count($cs)<$depth) {
2015 array_push($cs,$code);
2016 $out .= $BlockMarkups[$code][0];
2018 return $out;
2022 function MarkupClose($key = '') {
2023 global $MarkupFrame;
2024 $cf = & $MarkupFrame[0]['closeall'];
2025 $out = '';
2026 if ($key == '' || isset($cf[$key])) {
2027 $k = array_keys((array)$cf);
2028 while ($k) {
2029 $x = array_pop($k); $out .= $cf[$x]; unset($cf[$x]);
2030 if ($x == $key) break;
2033 return $out;
2037 function FormatTableRow($x, $sep = '\\|\\|') {
2038 global $TableCellAttrFmt, $TableCellAlignFmt, $TableRowAttrFmt,
2039 $TableRowIndexMax, $MarkupFrame, $FmtV, $EnableSimpleTableRowspan;
2040 static $rowcount;
2041 SDV($TableCellAlignFmt, " align='%s'");
2043 if (IsEnabled($EnableSimpleTableRowspan, 0)) {
2044 $x = preg_replace("/\\|\\|__+(?=\\|\\|)/", '||', $x);
2045 $x = preg_replace("/\\|\\|\\^\\^+(?=\\|\\|)/", '', $x);
2047 $x = preg_replace("/$sep\\s*$/",'',$x);
2048 $td = preg_split("/$sep/", $x); $y = '';
2049 for($i=0;$i<count($td);$i++) {
2050 if ($td[$i]=='') continue;
2051 $FmtV['$TableCellCount'] = $i;
2052 $attr = FmtPageName(@$TableCellAttrFmt, '');
2053 if (IsEnabled($EnableSimpleTableRowspan, 0)) {
2054 if (preg_match('/(\\+\\++)\\s*$/', $td[$i], $rspn)) {
2055 $td[$i] = preg_replace('/\\+\\++(\\s*)$/', '$1', $td[$i]);
2056 $attr .= " rowspan='".strlen($rspn[1])."'";
2059 $td[$i] = preg_replace('/^(!?)\\s+$/', '$1&nbsp;', $td[$i]);
2060 if (preg_match('/^!(.*?)!$/',$td[$i],$match))
2061 { $td[$i]=$match[1]; $t='caption'; $attr=''; }
2062 elseif (preg_match('/^!(.*)$/',$td[$i],$match))
2063 { $td[$i]=$match[1]; $t='th'; }
2064 else $t='td';
2065 if (preg_match('/^\\s.*\\s$/',$td[$i])) {
2066 if ($t!='caption') $attr .= sprintf($TableCellAlignFmt, 'center');
2068 elseif (preg_match('/^\\s/',$td[$i])) { $attr .= sprintf($TableCellAlignFmt, 'right'); }
2069 elseif (preg_match('/\\s$/',$td[$i])) { $attr .= sprintf($TableCellAlignFmt, 'left'); }
2070 for ($colspan=1;$i+$colspan<count($td);$colspan++)
2071 if ($td[$colspan+$i]!='') break;
2072 if ($colspan>1) { $attr .= " colspan='$colspan'"; }
2073 $y .= "<$t $attr>".trim($td[$i])."</$t>";
2075 if ($t=='caption') return "<:table,1>$y";
2076 if (@$MarkupFrame[0]['cs'][0] != 'table') $rowcount = 0; else $rowcount++;
2077 $FmtV['$TableRowCount'] = $rowcount + 1;
2078 $FmtV['$TableRowIndex'] = ($rowcount % $TableRowIndexMax) + 1;
2079 $trattr = FmtPageName(@$TableRowAttrFmt, '');
2080 return "<:table,1><tr $trattr>$y</tr>";
2083 function LinkIMap($pagename,$imap,$path,$alt,$txt,$fmt=NULL) {
2084 global $FmtV, $IMap, $IMapLinkFmt, $UrlLinkFmt, $IMapLocalPath, $ScriptUrl, $AddLinkCSS;
2085 SDVA($IMapLocalPath, array('Path:'=>1));
2086 if (@$IMapLocalPath[$imap]) {
2087 $path = preg_replace('/^(\\w+):/', "$1%3a", $path); # PITS:01260
2089 $FmtV['$LinkUrl'] = PUE(str_replace('$1',$path,$IMap[$imap]));
2090 $FmtV['$LinkText'] = $txt;
2091 if (@$alt) $FmtV['$LinkAlt'] = Keep(str_replace(array('"',"'"),array('&#34;','&#39;'),$alt));
2092 else $FmtV['$LinkAlt'] = '';
2093 if (!$fmt)
2094 $fmt = (isset($IMapLinkFmt[$imap])) ? $IMapLinkFmt[$imap] : $UrlLinkFmt;
2095 if (IsEnabled($AddLinkCSS['samedomain'])) {
2096 $parsed_url = parse_url($FmtV['$LinkUrl']);
2097 $parsed_wiki = parse_url($ScriptUrl);
2098 if (! @$parsed_url['host'] || $parsed_url['host'] == $parsed_wiki['host']) {
2099 $fmt = preg_replace('/(<a[^>]*class=["\'])/', "$1{$AddLinkCSS['samedomain']} ", $fmt);
2102 # remove unused title attributes
2103 if (!$alt) $fmt = preg_replace('/\\stitle=([\'"])\\$LinkAlt\\1/', '', $fmt);
2104 return str_replace(array_keys($FmtV),array_values($FmtV),$fmt);
2107 ## These 2 functions hide e-mail addresses from spam bot harvesters
2108 ## recover them for most users with a javascript utility,
2109 ## while keeping them readable for users with JS disabled.
2110 ## Based on Cookbook:DeObMail by Petko Yotov
2111 ## To enable, set $LinkFunctions['mailto:'] = 'ObfuscateLinkIMap';
2112 function ObfuscateLinkIMap($pagename,$imap,$path,$title,$txt,$fmt=NULL) {
2113 global $FmtV, $IMap, $IMapLinkFmt;
2114 SDVA($IMapLinkFmt, array('obfuscate-mailto:' =>
2115 "<span class='_pmXmail' title=\"\$LinkAlt\"><span class='_t'>\$LinkText</span><span class='_m'>\$LinkUrl</span></span>"));
2116 $FmtV['$LinkUrl'] = cb_obfuscate_mail(str_replace('$1',$path,$IMap[$imap]));
2117 $FmtV['$LinkText'] = cb_obfuscate_mail(preg_replace('/^mailto:/i', '', $txt));
2118 if ($FmtV['$LinkText'] == preg_replace('/^mailto:/i', '', $FmtV['$LinkUrl'])) $FmtV['$LinkUrl'] = '';
2119 else $FmtV['$LinkUrl'] = " -&gt; ".$FmtV['$LinkUrl'];
2120 $FmtV['$LinkAlt'] = str_replace(array('"',"'"),array('&#34;','&#39;'),cb_obfuscate_mail($title, 0));
2121 return str_replace(array_keys($FmtV),array_values($FmtV), $IMapLinkFmt['obfuscate-mailto:']);
2124 function cb_obfuscate_mail($x, $wrap=1) {
2125 $classes = array('.' => '_d', '@' => '_a');
2126 $texts = array( '.' => XL(' [period] '), '@' => XL(' [snail] '));
2127 foreach($classes as $k=>$v)
2128 $x = preg_replace("/(\\w)".preg_quote($k)."(\\w)/",
2129 ($wrap?
2130 "$1<span class='$v'>{$texts[$k]}</span>$2"
2131 : "$1{$texts[$k]}$2")
2132 , $x);
2133 return $x;
2136 function LinkPage($pagename,$imap,$path,$alt,$txt,$fmt=NULL) {
2137 global $QueryFragPattern, $LinkPageExistsFmt, $LinkPageSelfFmt,
2138 $LinkPageCreateSpaceFmt, $LinkPageCreateFmt, $LinkTargets,
2139 $EnableLinkPageRelative, $EnableLinkPlusTitlespaced, $AddLinkCSS,
2140 $CategoryGroup, $LinkCategoryFmt;
2141 if ($alt) $alt = str_replace(array('"',"'"),array('&#34;','&#39;'),$alt);
2142 $path = preg_replace('/(#[-.:\\w]*)#.*$/', '$1', strval($path)); # PITS:01388
2143 if (is_array($txt)) { # PITS:01392
2144 $suffix = $txt[1];
2145 $txt = $txt[0];
2147 if (!$fmt && @$path[0] == '#') {
2148 $path = preg_replace("/[^-.:\\w]/", '', $path);
2149 if (trim($txt) == '+') $txt = PageVar($pagename, '$Title') . @$suffix;
2150 if ($alt) $alt = " title='$alt'";
2151 return ($path) ? "<a href='#$path'$alt>".str_replace("$", "&#036;", $txt)."</a>" : '';
2153 if (preg_match('/^\\s*!/', $path)) {
2154 $is_cat = true;
2155 $path = preg_replace('/^\\s*!/', "$CategoryGroup/", $path);
2156 $txt = preg_replace('/^\\s*!/', '', $txt);
2158 if (!preg_match("/^\\s*([^#?]+)($QueryFragPattern)?$/",$path,$match))
2159 return '';
2160 $tgtname = MakePageName($pagename, $match[1]);
2161 if (!$tgtname) return '';
2162 $qf = @$match[2]? $match[2] : '';
2163 @$LinkTargets[$tgtname]++;
2164 if (@$is_cat) {
2165 $fmt = $LinkCategoryFmt;
2166 $c = preg_replace('/^.*\\./', '!', $tgtname);
2167 @$LinkTargets[$c]++;
2169 if (!$fmt) {
2170 if (!PageExists($tgtname) && !preg_match('/[&?]action=/', $qf))
2171 $fmt = preg_match('/\\s/', $txt)
2172 ? $LinkPageCreateSpaceFmt : $LinkPageCreateFmt;
2173 else
2174 $fmt = ($tgtname == $pagename && $qf == '')
2175 ? $LinkPageSelfFmt : $LinkPageExistsFmt;
2177 $url = PageVar($tgtname, '$PageUrl');
2178 if (trim($txt) == '+') $txt = PageVar($tgtname,
2179 IsEnabled($EnableLinkPlusTitlespaced, 0) ? '$Titlespaced' : '$Title') . @$suffix;
2180 $txt = str_replace("$", "&#036;", $txt);
2181 if (@$EnableLinkPageRelative)
2182 $url = preg_replace('!^[a-z]+://[^/]*!i', '', $url);
2183 # remove unused title attributes
2184 if (!$alt) $fmt = preg_replace('/\\stitle=([\'"])\\$LinkAlt\\1/', '', $fmt);
2185 $fmt = str_replace(array('$LinkUrl', '$LinkText', '$LinkAlt'),
2186 array($url.PUE($qf), $txt, Keep($alt)), $fmt);
2187 if (IsEnabled($AddLinkCSS['othergroup'])) {
2188 list($cgroup, ) = explode('.', $pagename);
2189 list($tgroup, ) = explode('.', $tgtname);
2190 if ($cgroup != $tgroup)
2191 $fmt = preg_replace('/(<a[^>]*class=["\'])/', "$1{$AddLinkCSS['othergroup']} ", $fmt);
2193 return FmtPageName($fmt,$tgtname);
2196 function MakeLink($pagename,$tgt,$txt=NULL,$suffix=NULL,$fmt=NULL) {
2197 global $LinkPattern,$LinkFunctions,$UrlExcludeChars,$ImgExtPattern,$ImgTagFmt,
2198 $LinkTitleFunction;
2199 if (preg_match("/^(.*)(?:\"(.*)\")\\s*$/",$tgt,$x)) list(,$tgt,$title) = $x;
2200 $t = preg_replace('/[()]/','',trim($tgt));
2201 $t = preg_replace('/<[^>]*>/','',$t);
2202 $t = trim(MarkupRestore($t));
2203 $txtr = trim(MarkupRestore(strval($txt)));
2205 preg_match("/^($LinkPattern)?(.+)$/",$t,$m);
2206 if (!@$m[1]) $m[1]='<:page>';
2207 if (preg_match("/(($LinkPattern)([^$UrlExcludeChars]+$ImgExtPattern))(\"(.*)\")?$/",$txtr,$tm))
2208 $txt = $LinkFunctions[$tm[2]]($pagename,$tm[2],$tm[3],@$tm[5],
2209 $tm[1],$ImgTagFmt);
2210 else {
2211 if (is_null($txt)) {
2212 $txt = preg_replace('/\\([^)]*\\)/','',$tgt);
2213 if ($m[1]=='<:page>') {
2214 $txt = preg_replace('!/\\s*$!', '', $txt);
2215 $txt = preg_replace('!^.*[^<]/!', '', $txt);
2218 if ($m[1]=='<:page>' && trim($txt) == '+' && $suffix>'') { # PITS:01392
2219 $txt = array(trim($txt), $suffix);
2221 else $txt .= $suffix;
2223 if (@$LinkTitleFunction) $title = $LinkTitleFunction($pagename,$m,$txt);
2224 else $title = PHSC(MarkupRestore(@$title), ENT_QUOTES);
2225 $out = $LinkFunctions[$m[1]]($pagename,$m[1],@$m[2],@$title,$txt,$fmt);
2226 return preg_replace('/(<[^>]+)\\stitle=(""|\'\')/', '$1', $out);
2229 function Markup($id, $when, $pat=NULL, $rep=NULL) {
2230 global $MarkupTable, $EnableMarkupDiag, $ObsoleteMarkups;
2231 unset($GLOBALS['MarkupRules']);
2232 if (preg_match('/^([<>])?(.+)$/', $when, $m)) {
2233 $MarkupTable[$id]['cmd'] = $when;
2234 $MarkupTable[$m[2]]['dep'][$id] = $m[1];
2235 if (!$m[1]) $m[1] = '=';
2236 if (@$MarkupTable[$m[2]]['seq']) {
2237 $MarkupTable[$id]['seq'] = $MarkupTable[$m[2]]['seq'].$m[1];
2238 foreach((array)@$MarkupTable[$id]['dep'] as $i=>$m)
2239 Markup($i,"$m$id");
2240 unset($GLOBALS['MarkupTable'][$id]['dep']);
2243 if ($pat && !isset($MarkupTable[$id]['pat'])) {
2244 $oldpat = preg_match('!(^/.+/[^/]*)e([^/]*)$!', $pat, $mm);
2245 if ($oldpat && PHP_VERSION_ID >= 50500) {
2246 # disable old markup for recent PHP versions
2247 $trace = TraceMarkup($id, true);
2248 $rep = 'ObsoleteMarkup';
2249 $pat = $mm[1].$mm[2];
2251 $MarkupTable[$id]['pat'] = $pat;
2252 $MarkupTable[$id]['rep'] = $rep;
2254 if (IsEnabled($EnableMarkupDiag, 0) || isset($ObsoleteMarkups[$id])) {
2255 $MarkupTable[$id]['dbg'] = isset($ObsoleteMarkups[$id])
2256 ? $ObsoleteMarkups[$id]
2257 : TraceMarkup($id, false);
2262 function Markup_e($id, $when, $pat, $rep, $template = 'markup_e') {
2263 if (!is_callable($rep)) {
2264 if (PHP_VERSION_ID < 70200 || isset($GLOBALS['PCCFOverrideFunction']))
2265 $rep = PCCF($rep, $template);
2266 else {
2267 TraceMarkup($id, true);
2268 $rep = 'ObsoleteMarkup';
2271 Markup($id, $when, $pat, $rep, 1);
2274 function TraceMarkup($id, $obsolete = false) {
2275 global $ObsoleteMarkups;
2276 $trace = debug_backtrace();
2277 foreach($trace as $t) {
2278 if (! preg_match('/^Markup(_e)?$/i', $t['function'])) continue;
2279 if ($t['args'][0] !== $id) continue;
2280 $excl = $obsolete? "!" : " ";
2281 $msg = "$excl File: {$t['file']}, line: {$t['line']}"
2282 . ", pat: {$t['args'][2]}, rep: {$t['args'][3]}.";
2283 if ($obsolete) $ObsoleteMarkups[$id] = $msg;
2284 return $msg;
2288 function ObsoleteMarkup($m) {
2289 extract($GLOBALS['MarkupToHTML']);
2290 global $ObsoleteMarkups;
2291 $id = PHSC($markupid, ENT_QUOTES, null, false);
2292 $txt = PHSC($m[0], ENT_QUOTES, null, false);
2293 if (isset($ObsoleteMarkups[$markupid])) {
2294 $dbg = PHSC($ObsoleteMarkups[$markupid], ENT_QUOTES, null, false);
2296 else $dbg = '';
2297 return Keep("<code title='Markup rule &quot;$id&quot; is obsolete and has been disabled. $dbg See pmwiki.org/Troubleshooting'
2298 class='obsolete-markup frame'>&#9888; $txt</code>");
2301 function DisableMarkup() {
2302 global $MarkupTable;
2303 $idlist = func_get_args();
2304 unset($GLOBALS['MarkupRules']);
2305 while (count($idlist)>0) {
2306 $id = array_shift($idlist);
2307 if (is_array($id)) { $idlist = array_merge($idlist, $id); continue; }
2308 $MarkupTable[$id] = array('cmd' => 'none', 'pat'=>'');
2312 function mpcmp($a,$b) { return @strcmp($a['seq'].'=',$b['seq'].'='); }
2313 function BuildMarkupRules() {
2314 global $MarkupTable,$MarkupRules,$LinkPattern;
2315 if (!$MarkupRules) {
2316 uasort($MarkupTable,'mpcmp');
2317 foreach($MarkupTable as $id=>$m)
2318 if (@$m['pat'] && @$m['seq']) {
2319 $MarkupRules[str_replace('\\L',strval(@$LinkPattern),$m['pat'])]
2320 = array($m['rep'], $id);
2323 return $MarkupRules;
2327 function MarkupToHTML($pagename, $text, $opt = NULL) {
2328 # convert wiki markup text to HTML output
2329 global $MarkupRules, $MarkupFrame, $MarkupFrameBase, $WikiWordCount,
2330 $K0, $K1, $RedoMarkupLine, $MarkupToHTML;
2331 $MarkupToHTML['pagename'] = $pagename;
2333 StopWatch('MarkupToHTML begin');
2334 array_unshift($MarkupFrame, array_merge($MarkupFrameBase, (array)$opt));
2335 $MarkupFrame[0]['wwcount'] = $WikiWordCount;
2336 foreach((array)$text as $l)
2337 $lines[] = $MarkupFrame[0]['escape'] ? PVSE($l) : $l;
2338 $lines[] = '(:closeall:)';
2339 $out = '';
2340 while (count($lines)>0) {
2341 $x = array_shift($lines);
2342 $RedoMarkupLine=0;
2343 $markrules = BuildMarkupRules();
2344 foreach($markrules as $p=>$r) {
2345 list($r, $id) = (array)$r;
2346 $MarkupToHTML['markupid'] = $id;
2347 if ($p[0] == '/') {
2348 if (is_callable($r)) $x = preg_replace_callback($p,$r,$x);
2349 else $x=preg_replace($p,$r,$x); # simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting
2351 elseif (strstr($x,$p)!==false) $x=eval($r);
2352 if (isset($php_errormsg)) ### TODO: $php_errormsg removed since PHP 8
2353 { echo "ERROR: pat=$p $php_errormsg"; unset($php_errormsg); }
2354 if ($RedoMarkupLine) { $lines=array_merge((array)$x,$lines); continue 2; }
2356 if ($x>'') $out .= "$x\n";
2358 foreach((array)(@$MarkupFrame[0]['posteval']) as $v) eval($v);
2359 array_shift($MarkupFrame);
2360 StopWatch('MarkupToHTML end');
2361 return $out;
2364 function HandleBrowse($pagename, $auth = 'read') {
2365 # handle display of a page
2366 global $DefaultPageTextFmt, $PageNotFoundHeaderFmt, $HTTPHeaders,
2367 $EnableHTMLCache, $NoHTMLCache, $PageCacheFile, $LastModTime, $IsHTMLCached,
2368 $FmtV, $HandleBrowseFmt, $PageStartFmt, $PageEndFmt, $PageRedirectFmt;
2369 $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
2370 if (!$page) Abort("?cannot read $pagename");
2371 PCache($pagename,$page);
2372 if (PageExists($pagename)) $text = $FmtV['$PageSourceText'] = @$page['text'];
2373 else {
2374 SDV($DefaultPageTextFmt,'(:include $[{$SiteGroup}.PageNotFound]:)');
2375 $text = FmtPageName($DefaultPageTextFmt, $pagename);
2376 SDV($PageNotFoundHeaderFmt, 'HTTP/1.1 404 Not Found');
2377 SDV($HTTPHeaders['status'], $PageNotFoundHeaderFmt);
2379 $opt = array();
2380 SDV($PageRedirectFmt,"<p><i>($[redirected from] <a rel='nofollow'
2381 href='{\$PageUrl}?action=edit'>{\$FullName}</a>)</i></p>\n");
2382 if (@!$_GET['from']) { $opt['redirect'] = 1; $PageRedirectFmt = ''; }
2383 else {
2384 $frompage = MakePageName($pagename, $_GET['from']);
2385 $PageRedirectFmt = (!$frompage) ? ''
2386 : FmtPageName($PageRedirectFmt, $frompage);
2388 if (@$EnableHTMLCache && !$NoHTMLCache && $PageCacheFile &&
2389 @filemtime($PageCacheFile) > $LastModTime) {
2390 list($ctext) = unserialize(file_get_contents($PageCacheFile));
2391 $FmtV['$PageText'] = "<!--cached-->$ctext";
2392 $IsHTMLCached = 1;
2393 StopWatch("HandleBrowse: using cached copy");
2394 } else {
2395 $IsHTMLCached = 0;
2396 $text = '(:groupheader:)'.@$text.'(:groupfooter:)';
2397 $t1 = time();
2398 $FmtV['$PageText'] = MarkupToHTML($pagename, $text, $opt);
2399 if (@$EnableHTMLCache > 0 && !$NoHTMLCache && $PageCacheFile
2400 && (time() - $t1 + 1) >= $EnableHTMLCache) {
2401 $fp = @fopen("$PageCacheFile,new", "x");
2402 if ($fp) {
2403 StopWatch("HandleBrowse: caching page");
2404 fwrite($fp, serialize(array($FmtV['$PageText']))); fclose($fp);
2405 rename("$PageCacheFile,new", $PageCacheFile);
2409 SDV($HandleBrowseFmt,array(&$PageStartFmt, &$PageRedirectFmt, '$PageText',
2410 &$PageEndFmt));
2411 PrintFmt($pagename,$HandleBrowseFmt);
2415 ## UpdatePage goes through all of the steps needed to update a page,
2416 ## preserving page history, computing link targets, page titles,
2417 ## and other page attributes. It does this by calling each entry
2418 ## in $EditFunctions. $pagename is the name of the page to be updated,
2419 ## $page is the old version of the page (used for page history),
2420 ## $new is the new version of the page to be saved, and $fnlist is
2421 ## an optional list of functions to use instead of $EditFunctions.
2422 function UpdatePage(&$pagename, &$page, &$new, $fnlist = NULL) {
2423 global $EditFunctions, $IsPagePosted;
2424 StopWatch("UpdatePage: begin $pagename");
2425 if (is_null($fnlist)) $fnlist = $EditFunctions;
2426 $IsPagePosted = false;
2427 foreach((array)$fnlist as $fn) {
2428 StopWatch("UpdatePage: $fn ($pagename)");
2429 $fn($pagename, $page, $new);
2431 StopWatch("UpdatePage: end $pagename");
2432 return $IsPagePosted;
2435 # AutoCheckToken verifies if the posted content was sent
2436 # from the website forms, to prevent CSRF
2437 function AutoCheckToken() {
2438 # TODO: Work in progress (Jan 2021), releasing for
2439 return true;
2441 global $EnablePost, $AutoCheckTokenActions, $EnablePmToken,
2442 $FmtV, $action, $BlockMessageFmt, $MessagesFmt;
2444 # a quick way to disable tokens
2445 if (! IsEnabled($EnablePmToken, 1)) return true;
2447 SDVA($AutoCheckTokenActions, array( # 1=POST, 2=GET, 0=disabled
2448 'edit' => 1,
2449 'postattr' => 1,
2450 'postupload' => 1,
2451 'approvesites' => 2,
2452 'approveurls' => 2,
2453 ));
2454 $tname = $FmtV['$TokenName'];
2455 $x = @$AutoCheckTokenActions[$action];
2456 if (!$x) return true;
2457 elseif ($x==1) {
2458 if ( count($_POST) < 1 || pmtoken(''.@$_POST[$tname]) ) return true;
2460 elseif ($x==2 && pmtoken(''.@$_GET[$tname])) return true;
2462 $EnablePost = 0;
2463 $MessagesFmt[] = $BlockMessageFmt;
2464 $MessagesFmt[] = XL('Token invalid or missing.');
2465 return false;
2468 # EditTemplate allows a site administrator to pre-populate new pages
2469 # with the contents of another page.
2470 function EditTemplate($pagename, &$page, &$new) {
2471 global $EditTemplatesFmt;
2472 if (@$new['text'] > '') return;
2473 if (@$_REQUEST['template'] && PageExists($_REQUEST['template'])) {
2474 $p = RetrieveAuthPage($_REQUEST['template'], 'read', false,
2475 READPAGE_CURRENT);
2476 if ($p['text'] > '') $new['text'] = $p['text'];
2477 return;
2479 foreach((array)$EditTemplatesFmt as $t) {
2480 $p = RetrieveAuthPage(FmtPageName($t,$pagename), 'read', false,
2481 READPAGE_CURRENT);
2482 if (@$p['text'] > '') { $new['text'] = $p['text']; return; }
2486 # RestorePage handles returning to the version of text as of
2487 # the version given by $restore or $_REQUEST['restore'].
2488 function RestorePage($pagename,&$page,&$new,$restore=NULL) {
2489 if (is_null($restore)) $restore=@$_REQUEST['restore'];
2490 if (!$restore) return;
2491 $t = $page['text'];
2492 $nl = (substr($t,-1)=="\n");
2493 $t = explode("\n",$t);
2494 if ($nl) array_pop($t);
2495 krsort($page); reset($page);
2496 foreach($page as $k=>$v) {
2497 if ($k<$restore) break;
2498 if (strncmp($k, 'diff:', 5) != 0) continue;
2499 foreach(explode("\n",$v) as $x) {
2500 if (preg_match('/^(\\d+)(,(\\d+))?([adc])(\\d+)/',$x,$match)) {
2501 $a1 = $a2 = $match[1];
2502 if ($match[3]) $a2=$match[3];
2503 $b1 = $match[5];
2504 if ($match[4]=='d') array_splice($t,$b1,$a2-$a1+1);
2505 if ($match[4]=='c') array_splice($t,$b1-1,$a2-$a1+1);
2506 continue;
2508 if (strncmp($x,'< ',2) == 0) { $nlflag=true; continue; }
2509 if (preg_match('/^> (.*)$/',$x,$match)) {
2510 $nlflag=false;
2511 array_splice($t,$b1-1,0,$match[1]); $b1++;
2513 if ($x=='\\ No newline at end of file') $nl=$nlflag;
2516 if ($nl) $t[]='';
2517 $new['text']=implode("\n",$t);
2518 $new['=preview'] = $new['text'];
2519 PCache($pagename, $new);
2520 return $new['text'];
2523 ## ReplaceOnSave performs text replacements on the text being posted.
2524 ## Patterns held in $ROEPatterns are replaced on every edit request,
2525 ## patterns held in $ROSPatterns are replaced only when the page
2526 ## is being posted (as signaled by $EnablePost).
2527 function ReplaceOnSave($pagename,&$page,&$new) {
2528 global $EnablePost, $ROSPatterns, $ROEPatterns;
2529 $new['text'] = ProcessROESPatterns(@$new['text'], $ROEPatterns);
2530 if ($EnablePost) {
2531 $new['text'] = ProcessROESPatterns($new['text'], $ROSPatterns);
2533 $new['=preview'] = $new['text'];
2534 PCache($pagename, $new);
2536 function ProcessROESPatterns($text, $patterns) {
2537 global $EnableROSEscape;
2538 if (IsEnabled($EnableROSEscape, 0)) $text = MarkupEscape($text);
2539 $text = PPRA((array)@$patterns, $text);
2540 if (IsEnabled($EnableROSEscape, 0)) $text = MarkupRestore($text);
2541 return $text;
2544 function SaveAttributes($pagename,&$page,&$new) {
2545 global $EnablePost, $LinkTargets, $SaveAttrPatterns, $PCache,
2546 $SaveProperties;
2547 if (!$EnablePost) return;
2548 $text = PPRA($SaveAttrPatterns, $new['text']);
2549 $LinkTargets = array();
2550 $new['=html'] = MarkupToHTML($pagename,$text);
2551 $new['targets'] = implode(',',array_keys((array)$LinkTargets));
2552 $p = & $PCache[$pagename];
2553 foreach((array)$SaveProperties as $k) {
2554 if (@$p["=p_$k"]) $new[$k] = $p["=p_$k"];
2555 else unset($new[$k]);
2557 unset($new['excerpt']);
2560 function PostPage($pagename, &$page, &$new) {
2561 global $DiffKeepDays, $DiffFunction, $DeleteKeyPattern, $EnablePost,
2562 $Now, $Charset, $Author, $WikiDir, $IsPagePosted, $DiffKeepNum;
2563 SDV($DiffKeepDays,3650);
2564 SDV($DiffKeepNum,20);
2565 SDV($DeleteKeyPattern,"^\\s*delete\\s*$");
2566 $IsPagePosted = false;
2567 if (!$EnablePost) return;
2568 if (preg_match("/$DeleteKeyPattern/",$new['text'])) {
2569 if (@$new['passwdattr']>'' && !CondAuth($pagename, 'attr'))
2570 Abort('$[The page has an "attr" attribute and cannot be deleted.]');
2571 else $WikiDir->delete($pagename);
2572 $IsPagePosted = true;
2573 return;
2575 $new['charset'] = $Charset; # kept for now, may be needed if custom PageStore
2576 $new['author'] = @$Author;
2577 $new["author:$Now"] = @$Author;
2578 $new["host:$Now"] = strval(@$_SERVER['REMOTE_ADDR']);
2579 $diffclass = preg_replace('/\\W/','',strval(@$_POST['diffclass']));
2580 if ($page['time']>0 && function_exists(@$DiffFunction))
2581 $new["diff:$Now:{$page['time']}:$diffclass"] =
2582 $DiffFunction($new['text'],@$page['text']);
2583 $keepgmt = $Now-$DiffKeepDays * 86400;
2584 $keepnum = array();
2585 $keys = array_keys($new);
2586 foreach($keys as $k)
2587 if (preg_match("/^\\w+:(\\d+)/",$k,$match)) {
2588 $keepnum[$match[1]] = 1;
2589 if (count($keepnum)>$DiffKeepNum && $match[1]<$keepgmt)
2590 unset($new[$k]);
2592 WritePage($pagename,$new);
2593 $IsPagePosted = true;
2596 function PostRecentChanges($pagename,$page,$new,$Fmt=null) {
2597 global $IsPagePosted, $RecentChangesFmt, $RCDelimPattern, $RCLinesMax,
2598 $EnableRCDiffBytes, $Now, $EnableLocalTimes;
2599 if (!$IsPagePosted && $Fmt==null) return;
2600 if (is_null($Fmt)) $Fmt = $RecentChangesFmt;
2601 foreach($Fmt as $rcfmt=>$pgfmt) {
2602 $rcname = FmtPageName($rcfmt,$pagename); if (!$rcname) continue;
2603 $pgtext = FmtPageName($pgfmt,$pagename); if (!$pgtext) continue;
2604 if (@$seen[$rcname]++) continue;
2606 if (IsEnabled($EnableRCDiffBytes, 0)) {
2607 $pgtext = PPRA(array(
2608 '/\\(([+-])(\\d+)\\)(\\s*=\\]\\s*)$/'=>'$3%diffmarkup%{$1($1$2)$1}%%',
2609 '/\\(\\+(0\\)\\+\\}%%)$/'=>'(&#177;$1'), $pgtext);
2611 $rcpage = ReadPage($rcname);
2612 $rcelim = preg_quote(preg_replace("/$RCDelimPattern.*$/",' ',$pgtext),'/');
2613 $rcpage['text'] = preg_replace("/^.*$rcelim.*\n/m", '', @$rcpage['text']);
2614 if (!preg_match("/$RCDelimPattern/",$rcpage['text']))
2615 $rcpage['text'] .= "$pgtext\n";
2616 else
2617 $rcpage['text'] = preg_replace("/([^\n]*$RCDelimPattern.*\n)/",
2618 str_replace("$", "\\$", $pgtext) . "\n$1", $rcpage['text'], 1);
2619 if (@$RCLinesMax > 0)
2620 $rcpage['text'] = implode("\n", array_slice(
2621 explode("\n", $rcpage['text'], $RCLinesMax + 1), 0, $RCLinesMax));
2622 WritePage($rcname, $rcpage);
2626 function AutoCreateTargets($pagename, &$page, &$new) {
2627 global $IsPagePosted, $AutoCreate, $LinkTargets;
2628 if (!$IsPagePosted) return;
2629 foreach((array)@$AutoCreate as $pat => $init) {
2630 if (is_null($init)) continue;
2631 foreach(preg_grep($pat, array_keys((array)@$LinkTargets)) as $aname) {
2632 if (PageExists($aname)) continue;
2633 $x = RetrieveAuthPage($aname, 'edit', false, READPAGE_CURRENT);
2634 if (!$x) continue;
2635 WritePage($aname, $init);
2640 function PreviewPage($pagename,&$page,&$new) {
2641 global $IsPageSaved, $FmtV, $ROSPatterns, $PCache,
2642 $HTMLStylesFmt, $IncludedPages, $EnableListIncludedPages;
2643 $text = ProcessROESPatterns($new['text'], $ROSPatterns);
2644 $text = '(:groupheader:)'.$text.'(:groupfooter:)';
2645 $IncludedPages = array();
2646 $preview = MarkupToHTML($pagename,$text);
2647 $incp = array_diff(array_keys($IncludedPages), array($pagename));
2648 $cnt = count($incp);
2649 if (IsEnabled($EnableListIncludedPages,0) && $cnt) {
2650 $label = XL('Text or data included from other pages');
2651 $edit = XL('Edit');
2652 SDV($HTMLStylesFmt['inclpages'], 'details.inclpages {display: inline-block;}');
2653 $out = "<br/><details class='inclpages'>"
2654 . "<summary>($cnt) $label</summary><ul>\n";
2655 sort($incp);
2656 foreach($incp as $pn) {
2657 $url = PageVar($pn, '$PageUrl');
2658 $out .= "<li> <a class='wikilink' href='$url'>$pn</a>
2659 (<a class='wikilink' href='$url?action=edit'>$edit</a>)</li>\n";
2661 $out .= "</ul></details>";
2662 $FmtV['$IncludedPages'] = $out;
2664 else $FmtV['$IncludedPages'] = '';
2665 if (@$_REQUEST['preview']) {
2666 $FmtV['$PreviewText'] = $preview;
2670 function HandleEdit($pagename, $auth = 'edit') {
2671 global $IsPagePosted, $EditFields, $ChangeSummary, $EditFunctions,
2672 $EnablePost, $FmtV, $Now, $EditRedirectFmt, $EnableRCDiffBytes,
2673 $PageEditForm, $HandleEditFmt, $PageStartFmt, $PageEditFmt, $PageEndFmt;
2674 SDV($EditRedirectFmt, '$FullName');
2675 if (@$_POST['cancel'])
2676 { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; }
2677 Lock(2);
2678 $page = RetrieveAuthPage($pagename, $auth, true);
2679 if (!$page) Abort("?cannot edit $pagename");
2680 $new = $page;
2681 foreach((array)$EditFields as $k)
2682 if (isset($_POST[$k])) $new[$k]=str_replace("\r",'',stripmagic($_POST[$k]));
2684 if (IsEnabled($EnableRCDiffBytes, 0) && isset($new['text'])) {
2685 $bytes = strlen($new['text']) - strlen(strval(@$page['text']));
2686 if ($bytes>=0) $bytes = "+$bytes";
2687 $ChangeSummary = rtrim($ChangeSummary) . " ($bytes)";
2689 $new['csum'] = $ChangeSummary;
2690 if ($ChangeSummary) $new["csum:$Now"] = $ChangeSummary;
2691 $EnablePost &= (bool)preg_grep('/^post/', array_keys(@$_POST));
2692 $new['=preview'] = @$new['text'];
2693 PCache($pagename, $new);
2694 UpdatePage($pagename, $page, $new);
2695 Lock(0);
2696 if ($IsPagePosted && !@$_POST['postedit'])
2697 { Redirect(FmtPageName($EditRedirectFmt, $pagename)); return; }
2698 $FmtV['$DiffClassMinor'] =
2699 (@$_POST['diffclass']=='minor') ? "checked='checked'" : '';
2700 $FmtV['$EditText'] =
2701 str_replace('$','&#036;',PHSC(@$new['text'],ENT_NOQUOTES));
2702 $FmtV['$EditBaseTime'] = $Now;
2703 $FmtV['$TokenValue'] = pmtoken();
2704 if (@$PageEditForm) {
2705 $efpage = FmtPageName($PageEditForm, $pagename);
2706 $form = RetrieveAuthPage($efpage, 'read', false, READPAGE_CURRENT);
2707 if (!$form || !@$form['text'])
2708 Abort("?unable to retrieve edit form $efpage", 'editform');
2709 $FmtV['$EditForm'] = MarkupToHTML($pagename, $form['text']);
2711 SDV($PageEditFmt, "<div id='wikiedit'>
2712 <h2 class='wikiaction'>$[Editing {\$FullName}]</h2>
2713 <form method='post' rel='nofollow' action='\$PageUrl?action=edit'>
2714 <input type='hidden' name='action' value='edit' />
2715 <input type='hidden' name='n' value='\$FullName' />
2716 <input type='hidden' name='basetime' value='\$EditBaseTime' />
2717 <input type='hidden' name='\$TokenName' value='\$TokenValue' />
2718 \$EditMessageFmt
2719 <textarea id='text' name='text' rows='25' cols='60'
2720 >\$EditText</textarea><br />
2721 <input type='submit' name='post' value=' $[Save] ' />");
2722 SDV($HandleEditFmt, array(&$PageStartFmt, &$PageEditFmt, &$PageEndFmt));
2723 PrintFmt($pagename, $HandleEditFmt);
2726 function HandleSource($pagename, $auth = 'read') {
2727 global $HTTPHeaders;
2728 $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
2729 if (!$page) Abort("?cannot source $pagename");
2730 foreach ($HTTPHeaders as $h) {
2731 $h = preg_replace('!^Content-type:\\s+text/html!i',
2732 'Content-type: text/plain', $h);
2733 header($h);
2735 echo @$page['text'];
2738 ## PmWikiAuth provides password-protection of pages using PHP sessions.
2739 ## It is normally called from RetrieveAuthPage. Since RetrieveAuthPage
2740 ## can be called a lot within a single page execution (i.e., for every
2741 ## page accessed), we cache the results of site passwords and
2742 ## GroupAttribute pages to be able to speed up subsequent calls.
2743 function PmWikiAuth($pagename, $level, $authprompt=true, $since=0) {
2744 global $DefaultPasswords, $GroupAttributesFmt, $AllowPassword,
2745 $AuthCascade, $AuthId, $AuthList, $NoHTMLCache;
2746 static $acache;
2747 SDV($GroupAttributesFmt,'$Group/GroupAttributes');
2748 SDV($AllowPassword,'nopass');
2749 $page = ReadPage($pagename, $since);
2750 if (!$page) { return false; }
2751 if (!isset($acache))
2752 SessionAuth($pagename, (@$_POST['authpw'])
2753 ? array('authpw' => array($_POST['authpw'] => 1))
2754 : '');
2755 if (@$AuthId) {
2756 $AuthList["id:$AuthId"] = 1;
2757 $AuthList["id:-$AuthId"] = -1;
2758 $AuthList["id:*"] = 1;
2760 ## To allow @_site_edit in GroupAttributes, we cache it first
2761 if (!isset($acache['@site'])) {
2762 foreach($DefaultPasswords as $k => $v) {
2763 $x = array(2, array(), '');
2764 $acache['@site'][$k] = IsAuthorized($v, 'site', $x);
2765 $AuthList["@_site_$k"] = $acache['@site'][$k][0] ? 1 : 0;
2768 $gn = FmtPageName($GroupAttributesFmt, $pagename);
2769 if (!isset($acache[$gn])) {
2770 $gp = ReadPage($gn, READPAGE_CURRENT);
2771 foreach($DefaultPasswords as $k => $v) {
2772 $acache[$gn][$k] = IsAuthorized(@$gp["passwd$k"], 'group',
2773 $acache['@site'][$k]);
2776 foreach($DefaultPasswords as $k => $v)
2777 list($page['=auth'][$k], $page['=passwd'][$k], $page['=pwsource'][$k]) =
2778 IsAuthorized(@$page["passwd$k"], 'page', $acache[$gn][$k]);
2779 foreach($AuthCascade as $k => $t) {
2780 if ($page['=auth'][$k]+0 == 2) {
2781 $page['=auth'][$k] = $page['=auth'][$t];
2782 if ($page['=passwd'][$k] = $page['=passwd'][$t]) # assign
2783 $page['=pwsource'][$k] = "cascade:$t";
2786 if (@$page['=auth']['admin'])
2787 foreach($page['=auth'] as $lv=>$a) @$page['=auth'][$lv] = 3;
2788 if (@$page['=passwd']['read']) $NoHTMLCache |= 2;
2789 if ($level=='ALWAYS' || @$page['=auth'][$level]) return $page;
2790 if (!$authprompt) return false;
2791 $GLOBALS['AuthNeeded'] = (@$_POST['authpw'])
2792 ? $page['=pwsource'][$level] . ' ' . $level : '';
2793 PCache($pagename, $page);
2794 PrintAuthForm($pagename);
2795 exit;
2798 ## Split from PmWikiAuth to allow for recipes to call it
2799 function PrintAuthForm($pagename) {
2800 global $FmtV, $AuthPromptFmt, $PageStartFmt, $PageEndFmt;
2801 $postvars = '';
2802 foreach($_POST as $k=>$v) {
2803 if ($k == 'authpw' || $k == 'authid') continue;
2804 $k = PHSC(stripmagic($k), ENT_QUOTES);
2805 if (is_array($v)) {
2806 foreach($v as $vk=>$vv) {
2807 $vk = PHSC(stripmagic($vk), ENT_QUOTES);
2808 $vv = str_replace('$', '&#036;',
2809 PHSC(stripmagic($vv), ENT_COMPAT));
2810 $postvars .= "<input type='hidden' name='{$k}[{$vk}]' value=\"$vv\" />\n";
2813 else {
2814 $v = str_replace('$', '&#036;',
2815 PHSC(stripmagic($v), ENT_COMPAT));
2816 $postvars .= "<input type='hidden' name='$k' value=\"$v\" />\n";
2819 $FmtV['$PostVars'] = $postvars;
2820 $r = str_replace("'", '%37', stripmagic(strval(@$_SERVER['REQUEST_URI'])));
2821 SDV($AuthPromptFmt,array(&$PageStartFmt,
2822 "<p><b>$[Password required]</b></p>
2823 <form name='authform' action='$r' method='post'>
2824 $[Password]: <input tabindex='1' type='password' name='authpw'
2825 value='' autofocus='autofocus' />
2826 <input type='submit' value='$[OK]' />\$PostVars</form>", &$PageEndFmt));
2827 PrintFmt($pagename,$AuthPromptFmt);
2828 exit;
2832 function IsAuthorized($chal, $source, &$from) {
2833 global $AuthList, $AuthPw, $AllowPassword;
2834 if (!$chal) return $from;
2835 $auth = 0;
2836 $passwd = array();
2837 foreach((array)$chal as $c) {
2838 $x = '';
2839 $pwchal = preg_split('/([, ]|\\w+:)/', $c, -1, PREG_SPLIT_DELIM_CAPTURE);
2840 foreach($pwchal as $pw) {
2841 if ($pw == ',' || $pw == '') continue;
2842 else if ($pw == ' ') { $x = ''; continue; }
2843 else if (substr($pw, -1, 1) == ':') { $x = $pw; continue; }
2844 else if ($pw[0] != '@' && $x > '') $pw = $x . $pw;
2845 if (!$pw) continue;
2846 $passwd[] = $pw;
2847 if ($auth < 0) continue;
2848 if ($x || $pw[0] == '@') {
2849 if (@$AuthList[$pw]) $auth = $AuthList[$pw];
2850 continue;
2852 if ($AllowPassword && pmcrypt($AllowPassword, $pw) == $pw) # nopass
2853 { $auth=1; continue; }
2854 foreach((array)$AuthPw as $pwresp) # password
2855 if (pmcrypt($pwresp, $pw) == $pw) { $auth=1; continue; }
2858 if (!$passwd) return $from;
2859 if ($auth < 0) $auth = 0;
2860 return array($auth, $passwd, $source);
2864 ## SessionAuth works with PmWikiAuth to manage authorizations
2865 ## as stored in sessions. First, it can be used to set session
2866 ## variables by calling it with an $auth argument. It then
2867 ## uses the authid, authpw, and authlist session variables
2868 ## to set the corresponding values of $AuthId, $AuthPw, and $AuthList
2869 ## as needed.
2870 function SessionAuth($pagename, $auth = NULL) {
2871 global $AuthId, $AuthList, $AuthPw, $SessionEncode, $SessionDecode,
2872 $EnableSessionPasswords, $EnableAuthPostRegenerateSID;
2873 static $called;
2875 @$called++;
2876 $sn = session_name(); # in PHP5.3, $_REQUEST doesn't contain $_COOKIE
2877 if (!$auth && ($called > 1 || (!@$_REQUEST[$sn] && !@$_COOKIE[$sn]))) return;
2879 $sid = session_id();
2880 pm_session_start();
2881 if ($called == 1 && isset($_POST['authpw']) && $_POST['authpw']
2882 && IsEnabled($EnableAuthPostRegenerateSID, true) && $sid) {
2883 @session_regenerate_id();
2886 foreach((array)$auth as $k => $v) {
2887 if ($k == 'authpw') {
2888 foreach((array)$v as $pw => $pv) {
2889 if ($SessionEncode) $pw = $SessionEncode($pw);
2890 $_SESSION[$k][$pw] = $pv;
2893 else if ($k) $_SESSION[$k] = (array)$v + (array)@$_SESSION[$k];
2896 if (!isset($AuthId)) $AuthId = @$_SESSION['authid'] ? @end($_SESSION['authid']) : '';
2897 $AuthPw = array_map($SessionDecode, array_keys((array)@$_SESSION['authpw']));
2898 if (!IsEnabled($EnableSessionPasswords, 1)) $_SESSION['authpw'] = array();
2899 $AuthList = array_merge($AuthList, (array)@$_SESSION['authlist']);
2901 if (!$sid) @session_write_close();
2905 function PasswdVar($pagename, $level) {
2906 global $PCache, $PasswdVarAuth, $FmtV;
2907 $page = $PCache[$pagename];
2908 if (!isset($page['=passwd'][$level])) {
2909 $page = RetrieveAuthPage($pagename, 'ALWAYS', false, READPAGE_CURRENT);
2910 if ($page) PCache($pagename, $page);
2912 SDV($PasswdVarAuth, 'attr');
2913 if ($PasswdVarAuth && !@$page['=auth'][$PasswdVarAuth]) return XL('(protected)');
2914 $pwsource = $page['=pwsource'][$level];
2915 if (strncmp($pwsource, 'cascade:', 8) == 0) {
2916 $FmtV['$PWCascade'] = substr($pwsource, 8);
2917 return FmtPageName('$[(using $PWCascade password)]', $pagename);
2919 $setting = PHSC(implode(' ', preg_replace('/^(?!@|\\w+:).+$/', '****',
2920 (array)$page['=passwd'][$level])));
2921 if ($pwsource == 'group' || $pwsource == 'site') {
2922 $FmtV['$PWSource'] = $pwsource;
2923 $setting = FmtPageName('$[(set by $PWSource)] ', $pagename)
2924 . PHSC($setting);
2926 return $setting;
2930 function PrintAttrForm($pagename) {
2931 global $PageAttributes, $PCache, $FmtV;
2932 $FmtV['$TokenValue'] = pmtoken();
2933 echo FmtPageName("<form action='\$PageUrl' method='post'>
2934 <input type='hidden' name='action' value='postattr' />
2935 <input type='hidden' name='\$TokenName' value='\$TokenValue' />
2936 <input type='hidden' name='n' value='\$FullName' />
2937 <table>",$pagename);
2938 $page = $PCache[$pagename];
2939 foreach($PageAttributes as $attr=>$p) {
2940 if (!$p) continue;
2941 if (strncmp($attr, 'passwd', 6) == 0) {
2942 $setting = PageVar($pagename, '$Passwd'.ucfirst(substr($attr, 6)));
2943 $value = '';
2944 } else { $setting = $value = PHSC(@$page[$attr]); }
2945 $prompt = FmtPageName($p,$pagename);
2946 echo "<tr><td>$prompt</td>
2947 <td><input type='text' name='$attr' value='$value' /></td>
2948 <td>$setting</td></tr>";
2950 echo FmtPageName("</table><input type='submit' value='$[Save]' /></form>",
2951 $pagename);
2954 function HandleAttr($pagename, $auth = 'attr') {
2955 global $HandleAttrFmt,$PageAttrFmt,$PageStartFmt,$PageEndFmt;
2956 $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
2957 if (!$page) { Abort("?unable to read $pagename"); }
2958 PCache($pagename,$page);
2959 XLSDV('en', array('EnterAttributes' =>
2960 "Enter new attributes for this page below. Leaving a field blank
2961 will leave the attribute unchanged. To clear an attribute, enter
2962 'clear'."));
2963 SDV($PageAttrFmt,"<div class='wikiattr'>
2964 <h2 class='wikiaction'>$[{\$FullName} Attributes]</h2>
2965 <p>$[EnterAttributes]</p></div>");
2966 SDV($HandleAttrFmt,array(&$PageStartFmt,&$PageAttrFmt,
2967 'function:PrintAttrForm',&$PageEndFmt));
2968 PrintFmt($pagename,$HandleAttrFmt);
2971 function HandlePostAttr($pagename, $auth = 'attr') {
2972 global $PageAttributes, $EnablePostAttrClearSession;
2973 if (! AutoCheckToken()) {
2974 Abort('? $[Token invalid or missing.]');
2976 Lock(2);
2977 $page = RetrieveAuthPage($pagename, $auth, true);
2978 if (!$page) { Abort("?unable to read $pagename"); }
2979 foreach($PageAttributes as $attr=>$p) {
2980 $v = stripmagic(@$_POST[$attr]);
2981 if ($v == '') continue;
2982 if ($v=='clear') unset($page[$attr]);
2983 else if (strncmp($attr, 'passwd', 6) != 0) $page[$attr] = $v;
2984 else {
2985 $a = array();
2986 preg_match_all('/"[^"]*"|\'[^\']*\'|\\S+/', $v, $match);
2987 foreach($match[0] as $pw)
2988 $a[] = preg_match('/^(@|\\w+:)/', $pw) ? $pw
2989 : pmcrypt(preg_replace('/^([\'"])(.*)\\1$/', '$2', $pw));
2990 if ($a) $page[$attr] = implode(' ',$a);
2993 WritePage($pagename,$page);
2994 Lock(0);
2995 if (IsEnabled($EnablePostAttrClearSession, 1)) {
2996 pm_session_start();
2997 unset($_SESSION['authid']);
2998 unset($_SESSION['authlist']);
2999 $_SESSION['authpw'] = array();
3001 Redirect($pagename);
3002 exit;
3006 function HandleLogoutA($pagename, $auth = 'read') {
3007 global $LogoutRedirectFmt, $LogoutCookies;
3008 SDV($LogoutRedirectFmt, '$FullName');
3009 SDV($LogoutCookies, array());
3010 pm_session_start();
3011 $_SESSION = array();
3012 if ( session_id() != '' || isset($_COOKIE[session_name()]) )
3013 pmsetcookie(session_name(), '', time()-43200, '/');
3014 foreach ($LogoutCookies as $c)
3015 if (isset($_COOKIE[$c])) pmsetcookie($c, '', time()-43200, '/');
3016 session_destroy();
3017 Redirect(FmtPageName($LogoutRedirectFmt, $pagename));
3021 function HandleLoginA($pagename, $auth = 'login') {
3022 global $AuthId, $DefaultPasswords;
3023 unset($DefaultPasswords['admin']);
3024 $prompt = @(!$_POST['authpw'] || ($AuthId != $_POST['authid']));
3025 $page = RetrieveAuthPage($pagename, $auth, $prompt, READPAGE_CURRENT);
3026 Redirect($pagename);