with your question(s) and I'll provide explanations (and add comments) that answer them. Script maintained by Petko Yotov www.pmwiki.org/petko $Id$ */ error_reporting(E_ALL ^ E_NOTICE); StopWatch('PmWiki'); @ini_set('magic_quotes_runtime', 0); @ini_set('magic_quotes_sybase', 0); if (@ini_get('pcre.backtrack_limit') < 1000000) @ini_set('pcre.backtrack_limit', 1000000); if (ini_get('register_globals')) foreach($_REQUEST as $k=>$v) { if (preg_match('/^(GLOBALS|_SERVER|_GET|_POST|_COOKIE|_FILES|_ENV|_REQUEST|_SESSION|FarmD|WikiDir)$/i', $k)) exit(); ${$k}=''; unset(${$k}); } $UnsafeGlobals = array_keys($GLOBALS); $GCount=0; $FmtV=array(); $FmtV['$TokenName'] = 'pmtoken'; define('PmWiki',1); SDV($WorkDir,'wiki.d'); SDV($FarmD,dirname(__FILE__)); if (preg_match('!^phar://[^:]*$!', $FarmD)) { $IsPmArchive = 1; SDV($PmArchiveDir, substr(dirname(dirname($FarmD)), 7)); } elseif (preg_match('/\\w\\w:/', $FarmD)) exit(); @include_once("$FarmD/scripts/version.php"); $GroupPattern = '[[:upper:]][\\w]*(?:-\\w+)*'; $NamePattern = '[[:upper:]\\d][\\w]*(?:-\\w+)*'; $BlockPattern = 'form|div|table|t[rdh]|p|[uo]l|d[ltd]|h[1-6r]|pre|blockquote'; $WikiWordPattern = '[[:upper:]][[:alnum:]]*(?:[[:upper:]][[:lower:]0-9]|[[:lower:]0-9][[:upper:]])[[:alnum:]]*'; $WikiDir = new PageStore('wiki.d/{$FullName}'); $WikiLibDirs = array(&$WikiDir,new PageStore('$FarmD/wikilib.d/{$FullName}')); $PageFileEncodeFunction = 'PUE'; # only used if $WikiDir->encodefilenames is set $PageFileDecodeFunction = 'urldecode'; $LocalDir = 'local'; $InterMapFiles = array("$FarmD/scripts/intermap.txt", "$FarmD/local/farmmap.txt", '$SiteGroup.InterMap', 'local/localmap.txt'); $Newline = "\263"; # deprecated, 2.0.0 $KeepToken = "\034\034"; $Now=time(); define('READPAGE_CURRENT', $Now+604800); $TimeFmt = '%B %d, %Y, at %I:%M %p'; $TimeISOFmt = '%Y-%m-%dT%H:%M:%S'; $TimeISOZFmt = '%Y-%m-%dT%H:%M:%SZ'; $MessagesFmt = array(); $BlockMessageFmt = "
','','
',0), 'indent' => array("','','',0), 'table' => array("
'.PHSC(print_r($x, true)).''; } function PSS($x) { return str_replace('\\"','"',$x); } function PVS($x) { return preg_replace("/\n[^\\S\n]*(?=\n)/", "\n<:vspace>", $x); } function PVSE($x) { return PVS(PHSC($x, ENT_NOQUOTES)); } function PZZ($x,$y='') { return ''; } function PRR($x=NULL) { if ($x || is_null($x)) $GLOBALS['RedoMarkupLine']++; return $x; } function PUE($x) { return $x? preg_replace_callback('/[\\x80-\\xff \'"<>]/', "cb_pue", $x) : ''; } function cb_pue($m) { return '%'.dechex(ord($m[0])); } function PQA($x, $keep=true, $styletoclass=false) { global $EnableUnsafeInlineStyle; if (!@$x) return ''; # PHP 8.1 $out = ''; $s = array(); $x = MarkupRestore($x); if (preg_match_all('/([a-zA-Z][-\\w]*)\\s*=\\s*("[^"]*"|\'[^\']*\'|\\S*)/', $x, $attr, PREG_SET_ORDER)) { foreach($attr as $a) { if (preg_match('/^on/i', $a[1])) continue; $val = preg_replace('/^([\'"]?)(.*)\\1$/', '$2', $a[2]); $s[$a[1]] = $val; } foreach($s as $key=>$val) { $val = PHSC($val, ENT_QUOTES, null, false); if ($keep) $val = Keep($val); $out .= "$key='$val' "; } } return $out; } function SDV(&$v,$x) { if (!isset($v)) $v=$x; } function SDVA(&$var,$val) { foreach($val as $k=>$v) if (!isset($var[$k])) $var[$k]=$v; } function IsEnabled(&$var,$f=0) { return (isset($var)) ? $var : $f; } function SetTmplDisplay($var, $val) { NoCache(); $GLOBALS['TmplDisplay'][$var] = $val; } function NoCache($x = '') { $GLOBALS['NoHTMLCache'] |= 1; return $x; } function ParseArgs($x, $optpat = '(?>(\\w+)[:=])') { $z = array(); preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/", strval($x), $terms, PREG_SET_ORDER); foreach($terms as $t) { $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]); if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; } else { $z['#'][] = $t[1]; $z[$t[1]][] = $v; } $z['#'][] = $v; } return $z; } function PosArgs($args, $posnames) { if (is_string($args)) $args = ParseArgs($args); if (!is_array($posnames)) { $posnames = array_slice(func_get_args(), 1); } while (count($posnames) > 0 && isset($args['']) && count($args['']) > 0) { $n = array_shift($posnames); if (!isset($args[$n])) $args[$n] = array_shift($args['']); } return $args; } function PHSC($x, $flags=ENT_COMPAT, $enc=null, $dbl_enc=true) { # for PHP 5.4 if (is_null($enc)) $enc = "ISO-8859-1"; # single-byte charset if (! is_array($x)) return @htmlspecialchars($x, $flags, $enc, $dbl_enc); foreach($x as $k=>$v) $x[$k] = PHSC($v, $flags, $enc, $dbl_enc); return $x; } function PSFT($fmt, $stamp=null, $locale=null, $tz=null) { # strftime() replacement global $Now, $FTimeFmt, $TimeFmt, $EnableFTimeNew; static $cached; if (!@$cached) { SDV($FTimeFmt, $TimeFmt); $cached['dloc'] = setlocale(LC_TIME, 0); $cached['dtz'] = date_default_timezone_get(); $cached['dtzo'] = timezone_open($cached['dtz']); $cached['formats'] = array( '%d' => 'd', '%e' => ' j', # day " 1"-"31" '%u' => 'N', '%w' => 'w', '%m' => 'm', '%s' => 'U', '%n' => "\n", '%t' => "\t", '%V' => 'W', # ISO-8601 week number, starts Monday, first week with 4+ weekdays '%H' => 'H', '%I' => 'h', # hour 01-12 '%k' => ' G', # hour " 1"-"23" '%l' => ' g', # hour " 1"-"12" '%M' => 'i', '%S' => 's', '%R' => 'H:i', '%T' => 'H:i:s', '%r' => 'h:i:s A', '%D' => 'm/d/y', '%F' => 'Y-m-d', '%G' => 'o', # ISO-8601 year (like %V) '%y' => 'y', # 21 '%Y' => 'Y', # 2021 '%Z' => 'T', # tz: EST '%z' => 'O', # tz offset: -0500 ); if (extension_loaded('intl')) { $full = IntlDateFormatter::FULL; $long = IntlDateFormatter::LONG; $short = IntlDateFormatter::SHORT; $none = IntlDateFormatter::NONE; $medium = IntlDateFormatter::MEDIUM; $cached['iformats'] = array( '%a' => array($full, $full, 'EEE'), # Mon '%A' => array($full, $full, 'EEEE'), # Monday '%h' => array($full, $full, 'MMM'), # Jan '%b' => array($full, $full, 'MMM'), # Jan '%B' => array($full, $full, 'MMMM'), # January '%p' => array($full, $full, 'aa'), # AM/PM '%P' => array($full, $full, 'aa'), # am/pm (no lowercase for intl am/pm) '%c' => array($long, $short), # date time '%x' => array($short, $none), # date '%X' => array($none, $medium),# time ); } else { $cached['iformats'] = array(); $cached['formats'] += array( '%a' => 'D', # Mon '%A' => 'l', # Monday '%h' => 'M', # Jan '%b' => 'M', # Jan '%B' => 'F', # January '%p' => 'a', # AM/PM '%P' => 'A', # am/pm '%c' => 'D M j H:i:s Y', # date time '%x' => 'm/d/y', # date '%X' => 'H:i:s', # time ); } $cached['new'] = isset($EnableFTimeNew) ? $EnableFTimeNew : (PHP_VERSION_ID>=80100); } if (@$fmt == '') $fmt = $FTimeFmt; $stamp = is_numeric($stamp)? intval($stamp) : $Now; if (preg_match('/(? $tz, 'timestamp' => $timestamp, 'formats' => $cached['formats'], 'iformats' => $cached['iformats'], ); if ($locale) $vars['locale'] = substr($locale, 0, 5); $cb = new PPRC($vars); $fmt = preg_replace_callback('/(?format($formats[$fmt]); if ($fmt[0]==' ' && strlen($fmt)>2) $fmt = substr($fmt, 1); return $fmt; } if ($fmt=='%o') { # ordinal, PITS:01418 if (!@$locale) $locale = 'C'; $f = numfmt_create($locale, NumberFormatter::ORDINAL); $o = $f->format($timestamp->format('j')); return preg_replace('/\\d+/', '', $o); } if ($fmt=='%j') return sprintf('%03d',$timestamp->format('z')+1); if ($fmt=='%C') return floor($timestamp->format('Y')/100); if ($fmt=='%g') return sprintf('%02d', $timestamp->format('o') % 100); if ($fmt=='%U') return cb_PSFT_UW($timestamp, $tz, 'Sunday'); if ($fmt=='%W') return cb_PSFT_UW($timestamp, $tz, 'Monday'); if (isset($iformats[$fmt])) { $ifmt = $iformats[$fmt]; $dfmt = datefmt_create(@$locale, $ifmt[0], $ifmt[1], $tz, null, @$ifmt[2]); if ($dfmt) return $dfmt->format($timestamp); } return $fmt; } function cb_PSFT_UW($timestamp, $tz, $day) { # helper for %U %W $first = strtotime(sprintf("%d-01 $day", $timestamp->format('Y'))); $stamp1 = date_create("@$first"); date_timezone_set($stamp1, timezone_open($tz)); $days = $timestamp->format('z') - $stamp1->format('z'); return sprintf('%02d', floor($days/7)+1); } # Only called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting function PCCF($code, $template = 'default', $args = '$m') { global $CallbackFnTemplates, $CallbackFunctions, $PCCFOverrideFunction; if ($PCCFOverrideFunction && is_callable($PCCFOverrideFunction)) return $PCCFOverrideFunction($code, $template, $args); if (!isset($CallbackFnTemplates[$template])) Abort("No \$CallbackFnTemplates[$template])."); $code = sprintf($CallbackFnTemplates[$template], $code); if (!isset($CallbackFunctions[$code])) { $fn = create_function($args, $code); # called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting if ($fn) $CallbackFunctions[$code] = $fn; else StopWatch("Failed to create callback function: ".PHSC($code)); } return $CallbackFunctions[$code]; } function PPRE($pat, $rep, $x) { if (! function_exists('create_function')) return $x; $lambda = PCCF("return $rep;"); return preg_replace_callback($pat, $lambda, $x); } function PPRA($array, $x) { if ($x==='' || is_null($x)) return ''; foreach((array)$array as $pat => $rep) { # skip broken patterns rather than crash the PHP installation $oldpat = preg_match('!^/.+/[^/]*e[^/]*$!', $pat); if ($oldpat && PHP_VERSION_ID >= 50500) continue; $fmt = $x; # for $FmtP if (is_callable($rep) && $rep != '_') $x = preg_replace_callback($pat,$rep,$x); else $x = preg_replace($pat,$rep,$x);# simple text OR called by old addon|skin|recipe needing update, see pmwiki.org/Troubleshooting } return $x; } function PRCB($pat, $repl, $subj, $vars=null, $limit=-1, &$count=null, $flags=0) { if (isset($vars)) { $cb = new PPRC($vars, $repl); $repl = array($cb, 'callback'); } if (PHP_VERSION_ID >= 70400) return preg_replace_callback($pat, $repl, $subj, $limit, $count, $flags); return preg_replace_callback($pat, $repl, $subj, $limit, $count); } function PRI($glue, $array) { ## Recursive implode $out = array(); foreach($array as $v) $out[] = is_array($v)? PRI($glue, $v) : $v; return implode($glue, $out); } ## This is a replacement for json_encode+PHSC, but only for arrays that ## are used by the PmWiki core. It may or may not work in other cases. ## This may fail with international characters if UTF-8 is not enabled. function pm_json_encode($x, $encodespecial=false) { if (!isset($x) || is_null($x)) return 'null'; if (is_bool($x)) return $x? "true" : "false"; if (is_int($x) || is_float($x)) return strval($x); if (function_exists('json_encode')) $out = json_encode($x); elseif (is_string($x)) ## escape controls and specials per RFC:8259 $out = '"'.preg_replace_callback("/[\x00-\x1f\\/\\\\\"]/",'cb_rfc8259',$x).'"'; elseif (is_array($x)) { $a = array(); if (array_values($x) === $x) { # numeric sequential array foreach($x as $v) $a[] = pm_json_encode($v); $out = "[".implode(',', $a)."]"; } else { # associative array -> json object foreach($x as $k=>$v) { $jk = is_int($k)? "\"$k\"" : pm_json_encode($k); $jv = pm_json_encode($v); $a[] = "$jk:$jv"; } $out = "{".implode(',', $a)."}"; } } else return 'null'; # other types not yet supported return $encodespecial? PHSC($out, ENT_QUOTES) : $out; } function cb_rfc8259($m) { return sprintf('\\u00%02x', ord($m[0])); } ## callback functions class PPRC { # PmWiki preg replace callbacks + pass local vars var $vars; var $cb; function __construct($vars = null, $cb = null) { if (!is_null($vars)) $this->vars = $vars; if (!is_null($cb)) $this->cb = $cb; } function pagevar($m) { # called from FmtPageName $pagename = $this->vars; return PageVar($pagename, $m[1]); } function ftime($m) { # called from PSFT $vars = $this->vars; return cb_PSFT($m[0], $vars); } function callback($m) { $cb = $this->cb; return $cb($m, $this->vars); } } # restores kept/protected strings function cb_expandkpv($m) { return @$GLOBALS['KPV'][$m[1]]; } # make a string upper or lower case in various patterns function cb_toupper($m) { return strtoupper($m[1]); } function cb_tolower($m) { return strtolower($m[1]); } function pmcrypt($str, $salt=null) { global $PmCryptAlgo; SDV($PmCryptAlgo, PASSWORD_DEFAULT); if ($salt && preg_match('/^(-?@|\\*$)/', $salt)) return false; if (!is_null($salt)) { if (function_exists('password_verify')) { if (password_verify($str, $salt)) return $salt; # else retry with crypt() } return crypt($str, $salt); } if (function_exists('password_hash')) return password_hash($str, $PmCryptAlgo); return crypt($str); } # generate or check a random session token to prevent CSRF # 0=get/set token, 1=check $_POST, 2=check $_GET, # 3=check $_COOKIE, -3=set $_COOKIE function pmtoken($check = 0, $abort = 0) { global $EnablePmToken, $PmTokenFn, $FmtV, $InputValues; static $called = 0, $cookie = 0; if (IsEnabled($PmTokenFn) && function_exists($PmTokenFn)) return $PmTokenFn($token); if (! IsEnabled($EnablePmToken, 1)) return true; pm_session_start(); $key = $FmtV['$TokenName']; if (!isset($_SESSION['pmtoken'])) { $token = $_SESSION['pmtoken'] = PmNonce().PmNonce(); } else $token = $_SESSION['pmtoken']; if (! $called++) { $InputValues[$key] = $FmtV['$TokenValue'] = $token; } if ($check == -3 && !$cookie++) pmsetcookie($key, $token); if ($check <= 0) { # get the token return $token; } # else: check the token if ($check === 1 && $token === @$_POST[$key]) return true; elseif ($check === 2 && $token === @$_GET[$key]) return true; elseif ($check === 3 && $token === @$_COOKIE[$key]) return true; # fail if ($abort) Abort('? $[Token invalid or missing.]'); return false; } function PmNonce() { if (function_exists('random_bytes')) { $nonce = bin2hex(random_bytes(5)); } else $nonce = 'x'.mt_rand().mt_rand(); return $nonce; } function StopWatch($x) { global $StopWatch, $EnableStopWatch; if (!$EnableStopWatch) return; static $wstart = 0, $ustart = 0; list($usec,$sec) = explode(' ',microtime()); $wtime = ($sec+$usec); if (!$wstart) $wstart = $wtime; if ($EnableStopWatch != 2) { $StopWatch[] = sprintf("%05.2f %s", $wtime-$wstart, $x); return; } $dat = getrusage(); $utime = ($dat['ru_utime.tv_sec']+$dat['ru_utime.tv_usec']/1000000); if (!$ustart) $ustart=$utime; $StopWatch[] = sprintf("%05.2f %05.2f %s", $wtime-$wstart, $utime-$ustart, $x); } ## DRange converts a variety of string formats into date (ranges). ## It returns the start and end timestamps (+1 second) of the specified date. function DRange($when) { global $Now; ## unix/posix @timestamp dates if (preg_match('/^\\s*@(\\d+)\\s*(.*)$/', $when, $m)) { $t0 = $m[2] ? strtotime($m[2], $m[1]) : $m[1]; return array($t0, $t0+1); } ## ISO-8601 dates $dpat = '/ (?'' && @$m[5] == '') { @$n[4]++; } ## if no day given, assume 1st of month and full month range if (@$m[4] == '') { $m[4] = 1; $n[4] = 1; $n[3]++; } ## if no seconds given, assume range of 1 minute (except when full day) if (@$m[7]>'' && @$m[8] == '') { @$n[7]++; } $t0 = @mktime($m[5], $m[7], $m[8], $m[3], $m[4], $m[1]); $t1 = @mktime($n[5], $n[7], $n[8], $n[3], $n[4], $n[1]); return array($t0, $t1); } ## now, today, tomorrow, yesterday NoCache(); if ($when == 'now') return array($Now, $Now+1); $m = localtime(time()); if ($when == 'tomorrow') { $m[3]++; $when = 'today'; } if ($when == 'yesterday') { $m[3]--; $when = 'today'; } if ($when == 'today') return array(mktime(0, 0, 0, $m[4]+1, $m[3] , $m[5]+1900), mktime(0, 0, 0, $m[4]+1, $m[3]+1, $m[5]+1900)); if (preg_match('/^\\s*$/', $when)) return array(-1, -1); $t0 = strtotime($when); $t1 = strtotime("+1 day", $t0); return array($t0, $t1); } ## FmtDateTimeZ converts a GMT datetime like @2022-01-08T10:07:08Z ## into a