1 - whether to run FmtPagename() over $pn
# 'fmtval'=>1 - whether to run FmtPagename() over $val
# 'simplewrite'=>1 - use WritePage() instead of UpdatePage()
#
# logit($pagename, $pn, $text, $opt)
# Append text to the end of a page.
# $pagename - a reference page (can be the same as $pn)
# $pn - the page to which to append
# $text - the text to write
# $opt - an array containing modifying semantics
# [NO_FMTPAGENAME] - suppress running FmtPageName() over $pn
# [NO_FMTTEXT] - suppress running FmtPagename() over $text
$RecipeInfo['Toolbox']['Version'] = '2009-04-20';
define(toolbox, true);
## While these variables cannot be counted on absolutely, if you load
## toolbox.php fairly early in your config.php then you can do a comparison
## of microtime(true) against $Timeout to determine how close you are to
## timing out on the max_execution_time. It's wise to give yourself a few
## seconds of leeway by subtracting from $Timeout or adding to microtime().
## EXAMPLE: if (microtime(true)+2 > $Timeout) ...
## -don't forget to globalize $Timeout...
$StartTime = microtime(true);
$Timeout = $StartTime + ini_get('max_execution_time');
SDV($DebugLevel, 5); // Default is no debugging. Set to lower # for more detail
$pagename = ResolvePageName($pagename);
# Err()
# Right now a simple echo. Later on it might use $opt or something to get
# fancier. Possibly logging errors to a file or optionally putting them in
# (:messages:) instead or a (new) (:errmessage:). That sort of thing. That's
# why it's nice to put all errors through a single function.
function Err($pagename, $opt, $msg)
{
echo "$msg
\n";
}
# SafeFmtPagename()
$MarkupExpr["dbg"] = 'tbDbg($pagename, @$argp, @$args)';
function tbDbg($pagename, $opt, $args)
{
for ($i = 0; $i < 3; $i++)
if ($args[$i] == 'array') $args[$i] = array('a'=>1,'b'=>'abc','def');
dbg(5,"Dbg: " . $args[0], $args[1], $args[2]);
return ('');
}
# dbg()
# Printlevel indicates what level of importance this line is.
# 0=never print
# 1=very detailed debugging
# 2=a little less detailed
# 3=fairly normal debugging
# 4=high-level, print it without thinking about it
# (you can go higher, but the indentation doesn't work)
# if $printlevel is lower than $DebugLevel (global) then nothing will be
# printed. Thus you get more detailed debug by setting the $DebugLevel to
# a lower number. Typically $DebugLevel=1 is maximum detail and $DebugLevel=5
# has all debug statements turned off.
function dbg($printlevel, $text, $txt2 = '', $txt3 = '', $txt4 = '', $txt5='')
{
global $MessagesFmt, $DebugLevel;
if ($printlevel < $DebugLevel) return;
# If your cookbook is messed up enough that you can't get a page to display
# and so you can't see the (:messages:) output then uncomment the line
# below and the lines will be echoed instead (arrays are not handled).
#echo "
" . (is_array($text)?print_r($text,true):str_repeat(" ", 5-$printlevel).$text) . " (" . (microtime(true) - $StartTime) . ")
\n";
foreach (array($text, $txt2, $txt3, $txt4, $txt5) as $txt) {
if ($txt == '')
break;
elseif (is_array($txt)) {
$MessagesFmt[] = "" . print_r($txt,true) . "
";
} else {
$suffix = "";
$prefix = '';
for ($i = 4-$printlevel; $i > 0; $i--) {
$prefix .= "- ";
$suffix .= "
";
}
$prefix .= "- ";
$MessagesFmt[] = $prefix . $txt . $suffix . "\n";
}
}
}
# RunMarkupRules()
# $RuleList - an array holding indices into the $MarkupTable array - these rules
# (in this order) will be processed
# $Text - this is the text upon which the rules will be run
# RETURN: The modified text
function RunMarkupRules($pagename, $RuleList, $Text, $opt=array())
{
global $MarkupTable;
$func = 'RunMarkupRules()';
$d=0;
dbg($d*3,"$func: Entering with text=$Text");
dbg($d*3,"$func: RuleList=".print_r($RuleList, true));
foreach ((array)$RuleList as $rule) {
dbg($d*1,"rule=$rule, Text=$Text");
if ($MarkupTable[$rule]) {
$p = $MarkupTable[$rule]['pat'];
$r = $MarkupTable[$rule]['rep'];
#$Text = preg_replace($p,$r,$Text);
if ($p{0} == '/') $Text=preg_replace($p,$r,$Text);
elseif (strstr($Text,$p)!==false) $Text=eval($r);
} else {
Err($pagename, $opt, "ERROR: RunMarkupRules: Unknown markup rule $rule");
}
}
dbg($d*3,"$func: Returning text=$Text");
return($Text);
}
# od()
# This function name is taken from the shell tool "od=octal dump" and as such
# is somewhat mis-named, but it does the same sort of thing and is a nice
# short name. It takes text and makes sure that text will be represented in
# your browser without being interfered with by any rules. This means that most
# non-alpha characters will be replaced by a name for that character.
#
# For instance, displaying "My name " in HTML will always lose
# the text between angle brackets. od() will render that as
# "My_name_ OPEN-ANGLE myaddr AT foo DOT com CLOSE-ANGLE"
# The names of characters may be eclectic, but it can be helpful for debugging.
function od($text)
{
$repl = array(':' => 'COLON', ';' => 'SEMI-COLON', '/'=>'SLASH',
'(' => 'OPEN-PAREN', ' '=>' _ ', '@' => 'AT', '\\' => 'BACKSLASH',
CHR(10)=>'LF', CHR(13)=>'CR', ')'=>'CLOSE-PAREN', '.' => 'DOT',
'{'=>'OPEN-CURLY', '}'=>'CLOSE-CURLY', '='=>'EQUALS', '|' => 'VERT',
'#'=>'POUND', ','=>'COMMA', '$'=>'DOLLAR', '-'=>'DASH', '+'=>'PLUS',
'"' => 'DOUBLE-QUOTE', "'" => 'SINGLE-QUOTE', '!' => 'BANG',
'?' => 'QUESTION', '<' => 'OPEN-ANGLE', '>' => 'CLOSE-ANGLE',
'*' => 'ASTERISK', '&' => 'AMPERSAND');
$rtn = '';
for ($i = 0; $i < strlen($text); $i++)
if (in_array($text[$i], array_keys($repl))) {
#echo "$foo[$i](" . ord($foo[$i]) . ") => " . $repl[$foo[$i]] . "
\n";
$rtn .= ' ' . $repl[$text[$i]] . ' ';
}
elseif (ord($text[$i]) < 65 && (ord($text[$i])<48 || ord($text[$i])>57))
$rtn .= 'CHR(' . ord($text[$i]). ')';
else
$rtn .= $text[$i];
return($rtn);
}
# writeptv()
# This function writes a PageTextVar to $pn.
# If the PTV already exists in page=$pn then it is updated without changing the
# type of PTV. If it does not exist then it is written to (the bottom of) $pn
# in the format specified by $fmtpn.
#
# Note that PmWiki authorizations are respected in this function and history is
# maintained and etc. (RetrieveAuthPage is used instead of ReadPage and
# UpdatePage is used instead of WritePage()). If you need to bypass
# authorizations you'll need to look elsewhere.
#
# $var and $val can be either singletons or arrays. if arrays then they must
# be identical in size.
function writeptv($pagename, $pn, $var, $val, $ptvfmt='hidden', $fmtpn=true,
$fmtval=false)
{
global $WikiShWriting;
if ($WikiShWriting) return(false);
$func = 'writeptv()';
$d=1;
dbg($d*4,"$func: Entering: $pn, $var, $val, $ptvfmt");
if (!is_array($var)) $var = (array)$var;
if (!is_array($val)) $val = (array)$val;
if (@$opt['fmtpn']) $pn = FmtPageName($pn, $pagename);
$pn = MakePageName($pagename, $pn);
$page = RetrieveAuthPage($pn, 'edit', false);
if (!$page) return(false);
$opage = $page;
dbg($d*1,"$func: Before modified: >>$page[text]<<");
for (reset($var),reset($val);list($x,$vr)=each($var),list($x,$vl)=each($val);) {
if (@$opt['fmtval']) $vl = FmtPageName($vl, $pagename);
$page['text'] = ptv2text($page['text'], $vr, $vl, $ptvfmt);
}
dbg($d*1,"$func: After appended: >>$page[text]<<");
$WikiShWriting = true;
if (@$opt['simplewrite'])
$rtn = WritePage($pn, $page);
else
$rtn = UpdatePage($pn, $opage, $page);
$WikiShWriting = false;
return($rtn);
}
# ptv2text()
# Do the text manipulation to place a PTV definition within a text string
# This is a support function for writeptv(), but it is very helpful in and of
# itself if you want to handle the read/write of the page yourself.
# $var and $val must be elements, not arrays.
function ptv2text($text, $var, $val, $ptvfmt='hidden')
{
global $PageTextVarPatterns;
$func = 'ptv2text()';
$d=1;
dbg($d*3, "$func: entering. var=$var, val=$val, fmt=$ptvfmt");
switch ($ptvfmt) {
case 'text':
$newdef = "$var: $val";
break;
case 'deflist':
$newdef = ":$var: $val";
break;
case 'section':
$newdef = $val;
if (substr($val, -1) != "\n")
$newdef .= "\n";
break;
case 'hidden':
case '':
default:
$newdef = "(:$var:$val:)";
break;
}
if ($ptvfmt == 'section') {
list($a, $b, $c) = tbTextSection($text, "#$var");
dbg($d*5, "$func: a=$a, b=$b, c=$c");
if ($c) $text = $a.$newdef.$c;
else $text = $a.$newdef;
dbg($d*5, "$func: after section replace text=$text");
} else {
$set = false;
foreach ((array)$PageTextVarPatterns as $ptvpat) {
if (preg_match_all($ptvpat, $text, $match, PREG_SET_ORDER)) {
foreach ($match as $m) {
dbg($d*1,"$func: 1=$m[1], 2=$m[2], 3=$m[3], 4=$m[4]");
if ($m[2] == $var) {
dbg($d*1,"$func: found val=$m[3]");
$text = str_replace($m[0], $newdef, $text);
$set = true;
break 2;
}
}
}
}
if (!$set)
$text .= "\n$newdef";
}
dbg($d*1,"$func: After replaced: ", array($text));
return($text);
}
# logit()
# Simply append some text to the end of a page.
# PmWiki authorizations are bypassed in this function to facilitate writing to
# administrator-only pages
function logit($pagename, $pn, $text, $opt=array())
{
if (!@$opt['NO_FMTPAGENAME']) $pn = FmtPageName($pn, $pagename);
$pn = MakePageName($pagename, $pn);
$page = ReadPage($pn, READPAGE_CURRENT);
if (!@$opt['NO_FMTTEXT']) $text = FmtPageName($text, $pagename);
if (@$opt['pre'])
$page['text'] = $text . "\n" . $page['text'];
else
$page['text'] .= "\n" . $text;
WritePage($pn, $page);
}
# tbUpdateAuthPage()
#
# This function is a substitute for UpdatePage(). If called identically it
# will provide a check against pmwiki auth level 'edit' before updating the
# page. But you can also call it with sections to write to just a section,
# pass it just text as the $newpage, etc. Additionally you can check a
# SecLayer authorization level by passing the $slAuth and $slStore.
#
# This function enforces valid SecLayer authorization AND valid PmWiki
# authorization before calling UpdatePage().
#
# ARGUMENTS
# $pagename - the page to be updated
# - optional #section specification
# - optional >/pattern/mod specification
# > specification (append to page)
# << specification (prepend to page)
# $opage - the array holding old (current) page info (or false to read it)
# $npage - the array holding new page info (or just the new text)
# $pmauth - the pmwiki authorization level required
# $slAuth - the SecLayer authorization level required
# $slStore - the SecLayer authorization store
#
# Note that $opage and $npage are overloaded. They can use the standard array
# definitions or $opage can be false/null (the page will be read again to get
# the old values) and $npage just a textual value (the array will be used from
# $opage). (note the paragraph below for a restriction on $npage semantics)
#
# If you wish to use the section specification or the >/= location in order
# to put your text in a specific place on the page then $npage MUST be ONLY
# text, not an array...
#
function tbUpdateAuthPage($pagename, $opage, $npage, $pmauth='edit', $opt=array(), $slAuth=null, $slStore=null)
{
global $tbError, $PageNameChars;
$tbError = '';
SDV($PageNameChars, '-[:alnum:]');
# Isolate pagename, operators, etc.
if (!preg_match("/(?P[$PageNameChars]+)(?:(?P#[-\\w#]+$)|(?:(?P[<>=])(?P(?P[\/|#!])(?P[^(?P=delim)]+)(?P=delim)(?P[msi]*))))?/", $pagename, $m)) {
$tbError = 'Pagename ($pagesource) does not match expected pattern';
return(false);
}
$pn = MakePageName($pagename, $m['pagename']);
if ($slAuth && $slStore && is_array($slStore))
if (!slAuthorized($pn, $slStore, $slAuth)) {
$tbError = "Page=$pn does not have SecLayer auth=$slAuth";
return(false);
}
if ($opage) {
if ($pmauth && !CondAuth($pagename, $pmauth)) {
$tbError = "Page=$pn does not allow auth=$pmauth";
return(false);
}
} elseif ($pmauth) {
if (!($opage = RetrieveAuthPage($pn, $pmauth))) {
$tbError = "Page=$pn cannot be read with auth=$pmauth";
return(false);
}
} else {
if (!($opage = ReadPage($pn, READPAGE_CURRENT))) {
$tbError = "Page=$pn cannot be read even without requiring auth";
return(false);
}
}
if (!is_array($npage)) {
$x = $npage;
$npage = $opage;
$otext = $opage['text'];
# Check for section write
if ($m['section']) {
list($a,$b,$c) = tbTextSection($otext, $m['section']);
$npage = $a.$npage.$c;
}
# Check for op/pat write
elseif ($m['op']) {
if ($m['fullpat'] == '<' || ($m['op'] == '<' && !$m['fullpat'])) {
# page<< or page< - prepend text
$otext = $npage . $otext;
} elseif ($m['fullpat'] == '>' || ($m['op'] == '>' && !$m['fullpat'])) {
# page>> or page> - append text
$otext .= $npage;
} else {
}
}
$npage['text'] = $x;
}
return(UpdatePage($pagename, $opage, $npage));
}
## tbTextSection()
##
## Moved from WikiSh wshTextSection() which was in turn copied from
## pmwiki.php TextSection()
## DIFFERENCES from TextSection():
## Returns 3-element array: (1) pre-section text with starting anchor,
## (2) section text, (3) ending anchor and post-section text.
## If section definition is not valid then either all in (1) (if no
## start anchor) or all in (1) and (2) (if no end anchor)
##
## TextSection extracts a section of text delimited by page anchors.
## The $sections parameter can have the form
## #abc - [[#abc]] to next anchor
## #abc#def - [[#abc]] up to [[#def]]
## #abc#, #abc.. - [[#abc]] to end of text
## ##abc, ..#abc - beginning of text to [[#abc]]
## Returns the text unchanged if no sections are requested,
## or false if a requested beginning anchor isn't in the text.
function tbTextSection($text, $sections, $args = NULL)
{
global $WikiShVars;
$func = 'tbTextSection()';
$args = (array)$args;
$npat = '[[:alpha:]][-\\w*]*';
# Section was invalid in the call to this function - no way to append a
# section because we can't identify the section we're looking for.
if (!preg_match("/#($npat)?(\\.\\.)?(#($npat)?)?/", $sections, $match)) {
$tbError = "ERROR: $func: section definition \"$sections\" invalid";
return array($text, '', '');
}
@list($x, $aa, $dots, $b, $bb) = $match;
dbg(1,"$func: text=$text, aa=$aa, dots=$dots, b=$b, bb=$bb");
if (!$dots && !$b) $bb = $npat;
dbg(1,"$func: aa=$aa, dots=$dots, b=$b, bb=$bb");
if ($aa) {
$pos = strpos($text, "[[#$aa]]");
if ($pos === false) {
$pre = $text . "[[#$aa]]";
dbg(1,"$func: returning prematurely");
return array($pre, '', '');
}
if (@$args['anchors'])
while ($pos > 0 && $text[$pos-1] != "\n") $pos--;
else $pos += strlen("[[#$aa]]");
$pre = substr($text, 0, $pos);
$text = substr($text, $pos);
}
dbg(1,"$func: pre=$pre, text=$text");
if ($bb) {
if (preg_match("/^(.*?)(\\[\\[#$bb\\]\\].*)$/s", $text, $m)) {
$text = $m[1];
$post = $m[2];
} elseif ($b) $post = "[[$b]]";
else $post = '';
}
dbg(1,"$func: pre=$pre, text=$text, post=$post");
return array($pre, $text, $post);
}
# This markup and function are only to test tbRetrieveAuthPage()
# {(tbretrieve page#section pmauth slauth)}
$MarkupExpr["tbretrieve"] = 'tbRetrieveTest($pagename, @$argp, @$args)';
function tbRetrieveTest($pagename, $argp, $args)
{
global $tbError, $wshAuthPage;
if ($page=tbRetrieveAuthPage($pagename, $args[0], $args[1], false, 0, $wshAuthPage, $args[2], ($args[3]?array('if', 'include'):null)))
return "success: old=".str_replace('(', '[', $page['text']).", new=".str_replace('(', '[', $page['ntext']);
else
return "failure: $tbError";
}
# tbRetrieveAuthPage()
# This function acts SOMEWHAT as RetrieveAuthPage() (or RetrieveAuthSection())
# with the following differences:
# (1) The full $page array is returned with an additional $page['ntext'] which
# contains the manipulated text (sections and rules, if requested) (this
# element of the array - $page['ntext'] - should be unset() before posting
# the page using this array) ($page['text'] will hold the original page
# text)
# (2) SecLayer authorizations are enforced if $slStore and $slAuth are provided
# (3) Sections are read as expected (as specified in $pagesource)
# (4) If $RuleList is provided as an array, the list of markup rules contained
# therein will be run. (rules such as 'if', 'comment', and 'include' are
# often useful)
# (5) $pagesource is expected to be in the form page, page#section or alternates
# (6) Note that the default for $prompt is false rather than true, in keeping
# with the fact that this function will more likely be used for reading
# pages that are not being edited but are rather being manipulated
#
# If you call it exactly as RetrieveAuthPage() it still gives you the capability
# of correctly parsing sections without doing it in your recipe.
#
# Do note the need for doing an unset($page['ntext']) prior to posting the page
# or you will end up with undesired (and useless) page attribute named ntext.
#
function tbRetrieveAuthPage($pagename, $pagesource, $authlev, $prompt=false, $since=0, $slStore=null, $slAuth='', $RuleList=null)
{
global $tbError, $PageNameChars;
$func = 'tbRetrieveAuthPage()'; $d=1;
dbg($d*4,"$func: pagesource=$pagesource, authlev=$authlev, slauth=$slauth");
$tbError = '';
SDV($PageNameChars, '-[:alnum:]');
# Isolate pagename, operators, etc.
if (!preg_match("/(?P[${PageNameChars}.]+)(?:(?P#[-\\w#]+$)|(?P[<>=]))?/", $pagesource, $m)) {
$tbError = 'Pagename ($pagesource) does not match expected pattern';
return(false);
}
$pn = MakePageName($pagename, $m['pagename']);
dbg($d*1, "$func: pn=$pn");
# If $slAuth and $slStore are provided the check SecLayer authorization
dbg($d*1, "$func: SecLayer against $slAuth");
if ($slAuth && $slStore && is_array($slStore)) {
if (!slAuthorized($pn, $slStore, $slAuth)) {
$tbError = "Page=$pn does not have SecLayer auth=$slAuth";
return(false);
}
}
# Do the actual read of the page, enforcing pmwiki authorizations
$page = RetrieveAuthPage($pn, $authlev, $prompt, $since);
if (!$page) {
$tbError = "Page=$pn does not have pmwiki auth=$authlev";
return(false);
}
$text = $page['text'];
dbg($d*1, "Text=$text");
# Parse section if specified
if ($m['section'])
$text = TextSection($text, $m['section']);
dbg($d*1, "after section text=$text");
# Run markup rules if requested
if ($RuleList && is_array($RuleList))
$text = RunMarkupRules($pagename, $RuleList, $text);
dbg($d*1, "after rules text=$text");
$page['ntext'] = $text;
return($page);
}