See https://www.pmwiki.org/wiki/Cookbook/SimpleRecipeTemplate and https://kiwiwiki.nz/pmwiki/pmwiki.php/Cookbook/SimpleRecipeTemplate + Copyright 2022-present Simon Davis This software is written for PmWiki; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. See pmwiki.php for full details and lack of warranty. The recipe implements a PmWiki directive whose parameters are hex=# len=# + Revision History # 2022-04-24 Add more outputs # 2022-01-22 Initial version Search and replace this template for "SimpleRecipe" with "YourRecipeName" */ //================================================================================================= /* This recipe illustrates some simple features of PmWiki recipes. * \Markup (): directive markup (:directive parm=value:) text (:directiveend:) * \ParseArgs (): PmWiki function to create key value array from directive parameters * \Keep (): PmWiki function to return HTML rather than wiki markup for page * \SDV (): override default values from a configuration file * $MessageFmt: log messages to the default PmWiki logging mechanism * $HTMLStylesFmt: insert CSS for the recipe into the page * $HTMLHeaderFmt: insert into HTML metadata element * $HTMLFooterFmt: insert text just before tag * dmsg: a way to debug a recipe * $RecipeInfo: publish recipe version for PmWiki Site Analyzer and Recipe Check * $FmtPV: create a page text variable enabling version display on a page */ //================================================================================================= \SDV($SimpleRecipeInitialvalue, 'GetAnInitialValue'); # get an initial value from a configuration file # insert into HTML metadata element // $HTMLHeaderFmt [SIMPLERECIPEID] = "\n"; # set default formatting for recipe classes \SDV($HTMLStylesFmt[SIMPLERECIPEID], '.simplerecipe {display: inline-block; font-family: monospace; white-space: pre-wrap;}' . NL . '.simplerecipedebug {font-size: smaller; font-family: monospace;}' . NL . '.verysimple {font-size: smaller; font-family: monospace;}' . NL); # set debug flag \SDV($SimpleRecipeDebug, false); # set default debug setting if not defined in an configuration file $SimpleRecipe_debugon = boolval ($SimpleRecipeDebug); # if on writes input and output to web page # Version date $RecipeInfo[SIMPLERECIPENAME]['Version'] = '2022-04-24' . SIMPLERECIPENEW; # PmWiki version numbering is done by date # recipe version page variable $FmtPV['$SimpleRecipeVersion'] = "'" . SIMPLERECIPENAME . ' version ' . $RecipeInfo[SIMPLERECIPENAME]['Version'] . "'"; // return version as a custom page variable if ($SimpleRecipe_debugon) dmsg(__FILE__, $RecipeInfo[SIMPLERECIPENAME]['Version']); # declare $SimpleRecipe for (:if enabled SimpleRecipe:) recipe installation check $SimpleRecipe = true; # enabled # // Initialise constants DEFINE ('NL', "\n"); DEFINE ('BR', '
' . NL); $Transmacrons = array ('ā' => 'ā', 'ē' => 'ē', 'ī' => 'ī', 'ō' => 'ō', 'ū' => 'ū', 'Ā' => 'Ā', 'Ē' => 'Ē', 'Ī' => 'Ī', 'Ō' => 'Ō', 'Ū' => 'Ū', '’' => '’'); ## Add a PmWiki custom markup # (:simplerecipe hex= len= :) (:simplerecipeend:) ## the following builds the regex to parse the SimpleRecipe PmWiki directive input parameters # directive arguments are # type= $phex = 'hex=' . qte ('on|off|only', true); $plen = 'len=' . qte ('(?:100|[1-9][0-9]|9|8)', true); ## $qparm = '(' . $phex . ')|(' . $plen . ')'; $qpattern = '(?:(?:' . $qparm . ')\s*){0,2}'; if ($SimpleRecipe_debugon) dmsg ('qpattern', mb_strtolower(SIMPLERECIPENAME) . ": " . $qpattern); $markup_pattern = '/\\(:' . mb_strtolower(SIMPLERECIPENAME) . '\s*(?:' . $qpattern . ')\s*:\\)' . '(.*?)' . '\\(:' . mb_strtolower(SIMPLERECIPENAME) . 'end\s*' . ':\\)/sim'; ## # Markup is an internal PmWiki function that defines the custom markup for the wiki (see https://www.pmwiki.org/wiki/PmWiki/CustomMarkup) # (:simplerecipe optional by syntacically defined parameters:) arbitrary text (:simplerecipeend:) \Markup(SIMPLERECIPEID, #name '[=', # when, e.g. fulltext, directives $markup_pattern, # pattern __NAMESPACE__ . '\SimpleRecipe_Parse' ); if ($SimpleRecipe_debugon) dmsg('markup',$markup_pattern); # s = dot matches all chars including newline # i = case insensitive # m = multiline # uses lazy evaluation, preserves leading and trailing white space # Keep prevents PmWiki markup being applied // # another example of a directive # (:verysimplerecipe Optional parameters:) $very_simple_markup_pattern = '/\\(:' . mb_strtolower(VERYSIMPLERECIPENAME) . '(.*)?:\\)/i'; # when has to be at least fulltext \Markup(VERYSIMPLERECIPENAME . SIMPLERECIPENEW, #name 'fulltext', # when, e.g. fulltext, directives $very_simple_markup_pattern, # pattern __NAMESPACE__ . '\VerySimpleRecipe_Parse' ); if ($SimpleRecipe_debugon) dmsg('very simple markup',$very_simple_markup_pattern); // return; # completed simple recipe setup /*-----------------------------------------------------------------------------------------------------------*/ # /** Main SimpleRecipe parser * /param arguments as documented above * /return The HTML-formatted information wrapped in a
of class "simplerecipe". */ function SimpleRecipe_Parse(array $m):string { # global $SimpleRecipe_debugon; # import variables // Initialise variables $retval = '<:block>
'; # return value # extract by line the text between the start and end directive $recipetext = explode("\n", $m[array_key_last ($m)]); # direct text is always last # see PmWiki documentation https://www.pmwiki.org/wiki/Cookbook/ParseArgs $args = \ParseArgs(implode (' ', array_slice ($m, 1))); # skip full text and directive text, see pmWiki documentation https://www.pmwiki.org/wiki/Cookbook/ParseArgs // if($SimpleRecipe_debugon) { # display inputs and outputs to wiki page dmsg ("
\n", SIMPLERECIPEID); dmsg ('m', $m); dmsg ('args', $args); dmsg ('recipetext', $recipetext); } # end SimpleRecipe_debugon // $float = array_key_exists ('float', $args) ? 'float:' . $args['float'] . ';' : ''; # article parameter $clear = array_key_exists ('clear', $args) ? 'clear:' . $args['clear'] . ';' : ''; # article parameter $particle = (array_key_exists ('float', $args) || array_key_exists ('clear', $args)) ? ' style="' . $clear . $float . '"' : ''; # article positioning ## query string parameters $bhex = array_key_exists ('hex', $args); $blen = array_key_exists ('len', $args); ## extract parameters $display_hex = boolval ($args['hex']); $display_len = ($blen) ? intval ($args['len']) : 16; $totlines = count($recipetext); $nrwidth = max(2, intdiv($totlines, 10)); # calculate digits for line numbering if ($SimpleRecipe_debugon) dmsg ('totlines', $totlines . ' ' . $nrwidth . ' #' . count ($m) . ' ^' . array_key_last ($m)); $linecount = 0; # count lines for line numbering foreach($recipetext as $txtval) { if ($txtval == '<:vspace>') $txtval = ''; # remove PmWiki empty line marker $retval .= dline ($txtval, ++$linecount, $nrwidth, $display_len); } $retval .= "
\n"; # returns html return \Keep($retval); # Keep prevents PmWiki markup being applied } # end SimpleRecipe_Parse ## /** Very Simple Recipe parser * /param arguments as documented above * /return The PmWiki-formatted information. */ function VerySimpleRecipe_Parse (array $m):string { # global $SimpleRecipe_debugon; # import variables global $RecipeInfo, $HandleActions, $FmtV, $CustomSyntax, $MarkupExpr; global $HTMLStylesFmt, $HTMLHeaderFmt, $HTMLFooterFmt, $UploadExts, $UploadExtSize, $WikiStyle; global $ThumbList, $_SERVER; $pagename = $GLOBALS['MarkupToHTML']['pagename']; $aladmin = false; $aledit = false; $alread = false; switch (PmWikiAuthLevel($pagename)) { case 'admin' : $aladmin = true; case 'edit' : $aledit = true; case 'read' : $alread = true; default: break; } $UploadInfo = []; $args = \ParseArgs($m[1]); # contains all text within directive // if ($SimpleRecipe_debugon) { # display inputs and outputs to wiki page dmsg ("
\n", 'Very' . SIMPLERECIPEID); dmsg ('m[]', $m); dmsg ('args[]', $args); } # end SimpleRecipe_debugon $displayopts = array_key_exists ('display', $args) ? explode(',', $args ['display']) : ['']; # defaults to none $debugopt = $args['debug'] === 'true'; if ($debugopt) $SimpleRecipe_debugon = true; # set on $retval = ''; foreach ($displayopts as $argval) { switch (true) { case ($argval == ''): # none specified $retval .= '!! Very Simple Recipe parameters' . NL . 'display= all, recipeinfo, customsyntax, defined, fmtv, handleactions, htmlstylesfmt, htmlheaderfmt, htmlfooterfmt, ' . 'markupexpr, server, phpinfo, thumblist, uploadexts, uploadextsize, vars, wikistyle, debug' . BR . 'debug= false, true' . BR; break; case ($argval == 'all' || $argval == 'recipeinfo'): $retval .= '!! RecipeInfo' . NL; $retval .= SRDisplayInfo ($RecipeInfo); if ($argval == 'recipeinfo') break; case ($argval == 'all' || $argval == 'handleactions'): $retval .= '!! HandleActions' . NL; $retval .= SRDisplayInfo ($HandleActions); if ($argval == 'handleactions') break; case ($argval == 'all' || $argval == 'fmtv'): $retval .= '!! FmtV' . NL; $retval .= SRDisplayInfo ($FmtV); if ($argval == 'fmtv') break; case ($argval == 'all' || $argval == 'customsyntax'): $retval .= '!! CustomSyntax' . NL; $retval .= SRDisplayInfo ($CustomSyntax); if ($argval == 'customsyntax') break; case ($argval == 'all' || $argval == 'markupexpr'): $retval .= '!! MarkupExpr' . NL; $retval .= SRDisplayInfo ($MarkupExpr); if ($argval == 'markupexpr') break; case ($argval == 'all' || $argval == 'htmlstylesfmt'): $retval .= '!! HTMLStylesFmt' . NL; $retval .= SRDisplayInfo ($HTMLStylesFmt); if ($argval == 'htmlstylesfmt') break; case ($argval == 'all' || $argval == 'htmlheaderfmt'): $retval .= '!! HTMLHeaderFmt' . NL; $retval .= SRDisplayInfo ($HTMLHeaderFmt); if ($argval == 'htmlheaderfmt') break; case ($argval == 'all' || $argval == 'htmlheaderfmt'): $retval .= '!! HTMLHeaderFmt' . NL; $retval .= SRDisplayInfo ($HTMLHeaderFmt); if ($argval == 'htmlheaderfmt') break; case ($argval == 'all' || $argval == 'htmlfooterfmt'): $retval .= '!! HTMLFooterFmt' . NL; $retval .= SRDisplayInfo ($HTMLFooterFmt); if ($argval == 'htmlfooterfmt') break; case ($argval == 'phpinfo'): # not included in 'all' $retval .= '!! PhpInfo' . NL; if ($aladmin) { $retval .= '(:details summary="PhpInfo":)' .NL; foreach (get_phpinfo() as $info) { $retval .= '!!! ' . key ($info) . NL; $retval .= SRDisplayInfo ($info); } $retval .= '(:detailsend:)' . NL; } else {$retval .= 'Please login as "admin".' . BR;} if ($argval == 'phpinfo') break; case ($argval == 'all' || $argval == 'vars'): $retval .= '!! Variables' . NL; $retval .= SRDisplayInfo (SRVars ()); if ($argval == 'vars') break; case ($argval == 'all' || $argval == 'uploadexts' || $argval == 'uploadextsize'): foreach ($UploadExts as $kext => $kval) $UploadInfo [$kext] ['ext'] = $kval; foreach ($UploadExtSize as $kext => $kval) $UploadInfo [$kext] ['size'] = $kval; $retval .= '!! $UploadExts' . NL; $retval .= SRDisplayInfo ($UploadInfo, true); if ($argval == 'uploadexts' || $argval == 'uploadextsize') break; case ($argval == 'all' || $argval == 'wikistyle'): $retval .= '!! WikiStyle' . NL; $retval .= SRDisplayInfo ($WikiStyle, true); if ($argval == 'wikistyle') break; case ($argval == 'all' || $argval == 'server'): $retval .= '!! Server' . NL; if ($aladmin) { $retval .= SRDisplayInfo ($_SERVER, true); } else {$retval .= 'Please login as "admin".' . BR;} if ($argval == 'server') break; case ($argval == 'all' || $argval == 'thumblist'): $retval .= '!! ThumbList' . NL; $retval .= SRDisplayInfo ($ThumbList); if ($argval == 'thumblist') break; case ( $argval == 'defined'): # not included in 'all' $retval .= '!! Defined' . NL; if ($aladmin) { $retval .= '(:details summary="Defined constants":)' .NL; $retval .= SRDisplayInfo (get_defined_constants()); $retval .= '(:detailsend:)' . NL . '(:details summary="Defined vars":)' . NL; //$retval .= SRDisplayInfo (array_slice (get_defined_vars(), 0, 100), true); $retval .= 'Too large to display' . NL; $retval .= '(:detailsend:)' . NL . '(:details summary="Defined functions":)' . NL; $retval .= SRDisplayInfo (get_defined_functions()); $retval .= '(:detailsend:)' . NL; } else {$retval .= 'Please login as "admin".' . BR;} if ($argval == 'defined') break; ## case ($argval == 'all' || $argval == 'debug'): $retval .= '!! Debug information' . NL; $retval .= SRDebugInfo ($m, $args); if ($argval == 'debug') break; ## /* case ($argval == 'all' || $argval == 'entities'): $Entities = get_html_translation_table(HTML_ENTITIES); $retval .= '!! HTML entities' . NL; $retval .= SRDisplayInfo (array_values ($Entities)); if ($argval == 'entities') break;*/ case ($argval == 'all'): break; default: $retval .= 'Unknown display option: "' . $argval . '"' . BR; } } # returns wiki markup return $retval; } # end VerySimpleRecipe_Parse ## // these are functional parts of the recipe # display the information for the array supplied function SRDisplayInfo ($srinfo, bool $details = false): string { global $SimpleRecipe_debugon; # import variables # sort keys (i.e. recipe name) ascending, case insensitive if (empty ($srinfo)) return 'No data' . BR; if (!ksort ($srinfo, SORT_FLAG_CASE | SORT_STRING)) return 'Sort failed' . BR; $retval = ''; if ($details) $retval .= '(:details:)' . NL; $retval .= NL. '(:table:)'; # can't be simple table as some strings have newlines in them foreach ($srinfo as $key => $value) { $retval .= NL . '(:cellnr:)' . NL . \Keep (htmlentities (strval ($key), ENT_NOQUOTES)) . NL . '(:cell:)='; if (is_array ($value)) { $retval .= NL . '(:cell:)' . NL; foreach ($value as $rcpkey => $rcpval) { $retval .= \Keep (htmlentities (strval ($rcpkey))) . ': '; if (is_array ($rcpval)) { $retval .= \Keep (htmlentities (print_r($rcpval, true), ENT_NOQUOTES)); } else { $retval .= \Keep (htmlentities (strval ($rcpval), ENT_NOQUOTES)); } $retval .= BR; } } else { $retval .= NL . '(:cell:)' . NL . \Keep (htmlentities (strval ($value), ENT_NOQUOTES)); } } $retval .= NL . '(:tableend:)' . NL; if ($details) $retval .= '(:detailsend:)' . NL; return $retval; } # end SRDisplayInfo ## function PmWikiAuthLevel(string $pagename): string { global $AuthLevels; \SDV($AuthLevels, array('admin', 'edit', 'read')); foreach($AuthLevels as $level) if (RetrieveAuthPage($pagename, $level, false, READPAGE_CURRENT)) return $level; return '(none)'; } // $FmtPV['$AuthLevel'] = 'PmWikiAuthLevel($pn)'; #3 function SRVars (): array { global $Author, $AuthorGroup, $CategoryGroup, $CookiePrefix, $DefaultGroup, $DefaultName, $DefaultPage, $RecipeInfo; global $SiteAdminGroup, $SiteGroup, $Skin, $Version; return array ( 'Author' => $Author, 'AuthorGroup' => $AuthorGroup, 'CategoryGroup' => $CategoryGroup, 'CookiePrefix' => $CookiePrefix, 'DefaultGroup' => $DefaultGroup, 'DefaultName' => $DefaultName, 'DefaultPage' => $DefaultPage, 'Skin' => $Skin, 'SiteAdminGroup' => $SiteAdminGroup, 'SiteGroup' => $SiteGroup, 'Version' => $Version, 'RecipeInfo' => $RecipeInfo[SIMPLERECIPENAME]['Version'] ); } ## function SRDebugInfo (array $srmarkup, array $srargs): string { $retval = '(:div class="verysimple":)m[]: '; $retval .= SRDispArray ($srmarkup); $retval .= 'args[]: '; unset ($srargs ['#']); # don't display this $retval .= SRDispArray ($srargs); return $retval . BR . '(:divend:)' . BR; } # end SRDebugInfo ## function SRDispArray (array $ArrayVal):string { array_walk_recursive ($ArrayVal, __NAMESPACE__ . '\dsanitise'); $keeptxt = ''; foreach ($ArrayVal as $key => $value) { $keeptxt .= '[' . $key . '] => '; if (is_array ($value)) { foreach ($value as $k2 => $v2) $keeptxt .= $v2 . '; '; } else { $keeptxt .= $value; } $keeptxt .= BR; } # use \Keep as markup contains PmWiki markup return htmlspecialchars (\Keep($keeptxt)); } ## // get php information function get_phpinfo (): array { ob_start (); // Capturing null, 0, PHP_OUTPUT_HANDLER_CLEANABLE ^ PHP_OUTPUT_HANDLER_REMOVABLE phpinfo (); // phpinfo () $html = trim (ob_get_clean ()); // output and clean if (isset($_SERVER['AUTH_USER'])) $html = str_replace($_SERVER['AUTH_USER'], '#####', $html); if (isset($_SERVER['AUTH_PASSWORD'])) $html = str_replace($_SERVER['AUTH_PASSWORD'], '#####', $html); if (isset($_SERVER['PHP_AUTH_USER'])) $html = str_replace($_SERVER['PHP_AUTH_USER'], '*****', $html); if (isset($_SERVER['PHP_AUTH_PW'])) $html = str_replace($_SERVER['PHP_AUTH_PW'], '*****', $html); $strt = strpos($html, '') + strlen(''); # Only works when has no attributes $fnsh = ''; $html = trim(substr($html, $strt, strpos($html, $fnsh) - $strt)); $phpinfo = explode (NL, strip_tags($html, '

')); $retval = array(); $cat = 'General'; foreach($phpinfo as $line) { // new cat? preg_match('~

(.*)

~', $line, $title) ? $cat = $title[1] : null; if(preg_match('~]+>([^<]*)]+>([^<]*)~', $line, $val)) { $retval[$cat][$val[1]] = $val[2]; } elseif(preg_match('~]+>([^<]*)]+>([^<]*)]+>([^<]*)~', $line, $val)) { $retval[$cat][$val[1]] = array('local' => $val[2], 'master' => $val[3]); } } return $retval; } ## // generate regex for quoted parameters function qte (string $parm, bool $unqt = false): string { # use a named regex capture group to allow a parameter to be single or double quoted, static $namenr = 0; # to generate unique names # named capture group is (?['"]), named back reference is \k) $retval = '(?\\\'|\"'; if ($unqt) $retval .= '|'; # allow unquoted strings return $retval . ')(?:' . $parm . ')\k'; # the only drawback in the the quote (or null) is in a captured group and is returned by the regex } # end qte ## // Display one line of text from screen function dline (string $txtvalin, int $linecount, int $nrwidth, int $dsplnlen=16): string { global $SimpleRecipe_debugon; # import variables $txtval = htmlspecialchars_decode ($txtvalin); # remove quoted entities inserted by Pmwiki $llength = strlen ($txtval); # length of text in the line $dispnr = intdiv($llength, $dsplnlen); # number of display blocks of text $retval = ''; $lnrval = str_pad(strval($linecount), $nrwidth, '0', STR_PAD_LEFT); $hexpad = intval($dsplnlen * 2.5); # hex doubles characters plus space per 4 hex chars for ($counter=0; $counter<=$dispnr; $counter++) { # display chunk of line in hex, with spacing, and line padded $retval .= $lnrval . ': | '; $subtxt = substr ($txtval, $counter * $dsplnlen, $dsplnlen); $lentxt = strlen ($subtxt); $retval .= str_pad (dhexline ($subtxt), $hexpad, ' '); $retval .= '| '; # from https://stackoverflow.com/questions/1176904/how-to-remove-all-non-printable-characters-in-a-string # /u modifier pattern and subject strings are treated as UTF-8. $valtmp = preg_replace ('/[\x00-\x1F\x20\x7F-\xA0\xAD]/u', '·', htmlspecialchars ($subtxt)); $retval .= $valtmp . str_pad ('', $dsplnlen - $lentxt, ' '); $retval .= ' |'. NL; if ($SimpleRecipe_debugon) dmsg ('dline ' . $lnrval, $lentxt . '="' . $subtxt . '", "' . $valtmp . '"'); } return $retval; } # end dline // Space line into blocks of characters function dhexline (string $valtxt, int $blen=4): string { global $SimpleRecipe_debugon; # import variables $hexval = bin2hex ($valtxt); # convert to hex $hexlen = strlen ($hexval); # length of hex string $blocknr = intdiv($hexlen, $blen); # calculate number of hex text blocks if ($blocknr == 0) return $hexval; for ($counter=$blocknr; $counter>0; $counter--) { $hexval = substr_replace($hexval, ' ', $counter * $blen, 0); } return $hexval; } # end dhexline ## // Debug function function dmsg(string $dmsgprefix, $dmsgdata) { # see https://www.pmwiki.org/wiki/Cookbook/DebuggingForCookbookAuthors # add debug text to message array global $Transmacrons, $MessagesFmt, $SimpleRecipe_debugon; $MessageFmt = '' . $dmsgprefix . ': '; switch (true) { case (is_null($dmsgdata)) : $MessageFmt .= 'null'; break; case (is_bool($dmsgdata)) : $MessageFmt .= var_export($dmsgdata,true); break; case (is_array($dmsgdata)) : array_walk_recursive ($dmsgdata, __NAMESPACE__ . '\dsanitise'); $MessageFmt .= '
' . print_r($dmsgdata, true) . '
' . NL; break; case (is_string ($dmsgdata)): $MessageFmt .= "'" . strtr (htmlspecialchars ($dmsgdata), (array) $Transmacrons) . "'"; break; default: $MessageFmt .= '"' . htmlspecialchars (strval ($dmsgdata)) . '"'; break; } $MessageFmt .= '
' . BR; $MessagesFmt [SIMPLERECIPENAME] .= $MessageFmt; return; } # end dmsg ## function dsanitise(&$item, $itemkey) { # escape html special characters global $Transmacrons; if (is_string($item)) { $item = strtr (htmlspecialchars ($item, ENT_NOQUOTES), $Transmacrons); } } # end dsanitise