admins (advanced)
This page describes some of the internal workings of PmWiki by explaining how some of the functions in pmwiki.php
work. For a more brief list/overview on functions useful to for instance cookbook writers, see Cookbook:Functions.
To use this functions you have to make sure that all relevant internal variables have been initialized correctly. See Custom Markup and Custom Actions for more information on how these functions are typically called via Markup()
or $HandleActions[]
.
PSFT($format, $timestamp=null, $locale=null, $tz=null)
The PSFT()
function (PmWiki String Format Time, added in 2.3.0) is intended to be a safe replacement for the very widely used PHP functions strftime()
and gmtstrftime()
which became deprecated in PHP 8.1.
Unlike strftime()
, PSFT()
accepts 2 additional arguments $locale
and $tz
, which allow to set a different language or timezone.
PmWiki 2.3.0 uses this function in all cases where it previously used strftime()
, including the {(ftime)}
Markup Expression. If your local customizations and recipes use strftime()
, you should be able to safely replace all calls to strftime()
with PSFT()
, without changing the arguments. Any calls to gmtstrftime($fmt, $stamp)
can be replaced with PSFT($fmt, $stamp, null, 'GMT')
.
The old functions were deprecated by the PHP developers because they behaved inconsistently on different platforms, and were dependent on installed system locales (i.e. a language would only work if the system had its locale installed). The new function uses the PHP class IntlDateFormatter and should be better. Unfortunately, it is not always enabled by hosting providers.
So depending on your installation, a specific language may be available with strftime()
and/or with IntlDateFormatter
.
In addition, some rarely used shortcut percent-formats %c, %x, %X
also behave inconsistently on different platforms, and the new formatter may show a slightly different output. You can always replace these shortcut formats with the full formats you require.
For these reasons, PSFT()
is currently a compromise, by default reusing strftime()
for PHP 8.0 or earlier. Updating your calls from strftime()
shouldn't cause any changes in your outputs.
You can set in config.php
the variable
for $EnableFTimeNew
= 1;PSFT()
to try using IntlDateFormatter
before PHP 8.1. If IntlDateFormatter
is not available, it will show the month and day names in English. Check what works.
Since the strftime()
function is deprecated, it is unlikely for it to add new formats. We have added 2 custom formats:
%o
for the "ordinal suffix" of the date, as "st" in "January 1st". If IntlDateFormatter
is not available, it will show the suffix in English
%L
for a human-readable timestamp of the format @2022-09-25T11:49:08Z
which will be displayed formatted either as $TimeFmt
or in the local time zone of the visitor, see $EnableLocalTimes
.
A difference between strftime($format, $stamp)
and PSFT($format, $stamp)
is how they interpret a false, empty or non-numeric $stamp
argument.
$stamp argument | strftime($format, $stamp) | PSFT($format, $stamp) |
---|---|---|
numeric | the stamp | the stamp |
missing or null | current time | current time |
false | 1970-01-01 | current time |
"" (empty string) other non-numeric | 1970-01-01 or false (older PHP versions) Warning: TypeError (PHP 7.4+) | current time |
For PmWiki, it seemed reasonable to make empty strings and other non-numeric values default to the current time. If your $stamp
variable may be empty or false, and your recipes rely on strftime()
returning "1970-01-01", you can cast the stamp to integer:
PSFT($format, intval($stamp));
pmtoken($check = 0, $abort = false)
The pmtoken()
function sets or checks a unique session identifier to be used in input forms, with the goal of preventing cross-site request forgeries (CSRF).
Calling pmtoken()
or pmtoken(0)
will create a token if it doesn't exist, store it in the $_SESSION
array, and return it. It will also set the variables $FmtV['$TokenValue']
which can be used in HTML templates and $InputValues['pmtoken']
to be used in markup forms, although it may be simpler to use (:input pmtoken:)
instead.
The name of the input element, by default 'pmtoken', can be changed by setting for example $FmtV['$TokenName'] = 'CSRFtoken';
.
Calling pmtoken(1)
will check the $_POST['pmtoken']
value and will return true if it is valid.
Calling pmtoken(2)
will check the $_GET['pmtoken']
value and will return true if it is valid.
Calling pmtoken(1, true)
or pmtoken(2, true)
with a second truthy argument, and the token is invalid, will directly call Abort()
and exit.
pmcrypt($password, $salt = null)
The pmcrypt()
function is intended to be a safe replacement for the PHP 5.6+ crypt() function without providing a $salt, which would raise a notice. If a salt is provided, crypt() is called to check an existing password. If a salt is not provided, password_hash() will be called to create a cryptographically strong password hash.
pmsetcookie($name, $val="", $exp=0, $path="", $dom="", $secure=null, $httponly=null)
This function is intended as a replacement for setcookie(). It will automatically set the $secure and $httponly arguments if they are not set by the caller function and if $EnableCookieSecure
and $EnableCookieHTTPOnly
are enabled.
PCCF($php_code, $callback_template='default', $callback_arguments = '$m')
The PCCF()
function (PmWiki Create Callback Function) can be used to create callback functions used with preg_replace_callback. It is required for PHP 5.5, but will also work with earlier PHP versions.
The first argument is the PHP code to be evaluated.
The second argument (optional) is the callback template, a key from the global $CallbackFnTemplates
array. There are two templates that can be used by recipe authors:
$php_code
as a function code
$php_code
like "return $php_code;
" (since PmWiki 2.2.62)
The third argument (optional) is the argument of the callback function. Note that PmWiki uses the '$m' argument to pass the matches of a regular expression search, but your function can use other argument(s).
PCCF()
will create an anonymous (lambda) callback function containing the supplied code, and will cache it. On subsequent calls with the same $php_code
, PCCF()
will return the cached function name.
See the PHP create function.
PHP 7.2 deprecates create_function()
and future versions will remove it. If you need to migrate older code that used PCCF()
, you can usually write regular functions and pass the function name where you previously passed the result of PCCF(). For example, suppose you had a pattern like this:
'/(?<=^| )([a-z])/' => PCCF("return strtoupper(\$m[1]);"),
For PHP 7.2 compatibility, you can write a callback function:
function
my_callback($m) { return strtoupper($m[1]); }
then change the pattern to look like this:
'/(?<=^| )([a-z])/' => '
my_callback',
See also: the recipe PccfToPcfOverride allows existing recipes to run on PHP 7 without causing deprecated create_function()
messages.
PPRA($array_search_replace, $string)
The PPRA()
function (PmWiki Preg Replace Array) can be used to perform a regular expression replacement with or without evaluation, for PHP 5.5 compatibility.
Since PmWiki 2.2.56, PmWiki uses this function to process the following arrays: $MakePageNamePatterns
, $FmtP
, $QualifyPatterns
, $ROEPatterns
, $ROSPatterns
, $SaveAttrPatterns, $MakeUploadNamePatterns
. Any custom settings should continue to work for PHP 5.4 and earlier, but wikis running on PHP 5.5 may need to make a few changes.
The first argument contains the 'search'=>'replace' pairs, the second is the "haystack" string to be manipulated.
The 'replace' parts of the array can be strings or function names. If the 'replace' part is a callable function name, it will be called with the array of matches as a first argument via preg_replace_callback()
. If not a callable function, a simple preg_replace()
will be performed.
Previously, PmWiki used such constructs:
It is now possible to use simply this:
$fmt = PPRA($FmtP
, $fmt);
Note that since PHP 5.5, the search patterns cannot have an /e evaluation flag. When creating the $array_search_replace array, before PHP 5.5 we could use something like (eg. for $MakePageNamePatterns
):
'/(?<=^| )([a-z])/e' => "strtoupper('$1')",
Since PHP 5.5, we should use this (will also work in PHP 5.4 and earlier):
'/(?<=^| )([a-z])/' => PCCF("return strtoupper(\$m[1]);"),
Note that the /e
flag should be now omitted, instead of '$0', '$1', '$2',
we should use $m[0], $m[1], $m[2],
etc. in the replacement code, and there is no need to call PSS()
in the replacement code, as backslashes are not automatically added.
For PHP 7.2 and newer, instead of using PCCF()
to create anonymous functions, we add a real function in our add-on, and then pass the function name as the pattern replacement (see example at PCCF, which will also work on PHP 4 and 5):
'/(?<=^| )([a-z])/' => '
my_callback',
PPRE($search_pattern, $replacement_code, $string)
The PPRE()
function (PmWiki Preg Replace Evaluate) can be used to perform a regular expression replacement with evaluation.
Since PHP 5.5, the preg_replace()
function has deprecated the /e evaluation flag, and displays warnings when the flag is used. The PPRE()
function automatically creates a callback function with the replacement code and calls it.
Before PHP 5.5, it was possible to use such calls:
$fmt = preg_replace('/\\$([A-Z]\\w*Fmt)\\b/e','$GLOBALS["$1"]',$fmt);
Since PHP 5.5, it is possible to replace the previous snippet with the following (also works before PHP 5.5):
$fmt = PPRE('
/\\$([A-Z]\\w*Fmt)\\b/','$GLOBALS[$m[1]]',$fmt);
Note that the /e
flag should be now omitted, instead of '$0', '$1', '$2',
we should use $m[0], $m[1], $m[2],
etc. in the replacement code, and there is no need to call PSS()
in the replacement code, as backslashes are not automatically added.
For PHP 7.2 and newer, calling this function will raise "deprecated" notices. You need to rewrite your code to use preg_replace_callback, by moving the code into real functions:
$fmt = preg_replace_callback('
/\\$([A-Z]\\w*Fmt)\\b/', 'my_global_var_callback',$fmt);
function my_global_var_callback($m) { return $GLOBALS[$m[1]]; }
instead of using PCCF()
to create anonymous functions, we add a real function in our add-on, and then pass the function name as the pattern replacement (see example at PCCF, which will also work on PHP 4 and 5):
=> '/(?<=^| )([a-z])/' => '
my_callback',
Qualify($pagename, $text)
Qualify()
applies $QualifyPatterns
to convert relative links and references into absolute equivalents.
This function is called by usual wiki markups that include text from other pages.
It will rewrite links like [[Page]]
into [[Group/Page]]
, and page (text) variables like {$Title}
into {Group.Page$Title}
so that they work the same way in the source page and in the including page.
See also $QualifyPatterns
and RetrieveAuthSection()
.
PHSC($string_or_array, $flags=ENT_COMPAT, $encoding=null, $double_encode=true)
The PHSC()
function (PmWiki HTML Special Characters) is a replacement for the PHP function htmlspecialchars.
The htmlspecialchars()
function was modified since PHP 5.4 in two ways: it now requires a valid string for the supplied encoding, and it changes the default encoding to UTF-8. This can cause sections of the page to become blank/empty on many sites using the ISO-8859-1 encoding without having set the third argument ($encoding) when calling htmlspecialchars()
.
The PHSC()
function calls htmlspecialchars()
with an 8-bit encoding as third argument, whatever the encoding of the wiki (unless you supply an encoding). This way the string never contains invalid characters.
It should be safe for recipe developers to replace all calls to htmlspecialchars()
with calls to PHSC()
. Only the first argument is required when calling PHSC()
, although authors may wish to call PHSC($string_or_array, ENT_QUOTES)
.
Unlike htmlspecialchars()
, the PHSC()
function can process arrays recursively (only the values are converted, not the keys of the array).
PSS($string)
The PSS()
function (PmWiki Strip Slashes) removes the backslashes that are automatically inserted in front of quotation marks by the /e option of PHP's preg_replace function. PSS()
is
most commonly used in replacement arguments to Markup()
, when the pattern specifies /e and one or more of the parenthesized subpatterns could contain a quote or backslash.
("PSS" stands for "PmWiki Strip Slashes".)
PSS()
to always occur inside of double-quoted strings and to contain single quoted strings internally. The reason for this is that we don't want the $1
or $2
to accidentally contain characters that would then be interpreted inside of the double-quoted string when the PSS is evaluated.
Markup('foo', 'inline', '/(something)/e', 'Foo(PSS("$1"))'); # wrong
Markup('foo', 'inline', '/(something)/e', "Foo(PSS('$1'))"); # right
Note, the extra slashes are only added by preg_replace()
with an /e
modifier. The markup definitions with Markup_e()
do NOT need to use PSS()
in the replacement strings. The new-type markup definitions with Markup()
and a simple function name as a replacement do NOT need to use PSS()
inside the replacement function. If you migrate old markup rules to the new format, delete the PSS()
calls.
This is a fictitious example where PSS()
should be used.
Let us assume that you wish to define a directive (:example:)
such that (:example "A horse":)
results in the HTML
<div>"A horse"</div>
.
Here is how the markup rule can be created:
Markup('example', 'directives', '/\\(:example\\s(.*?):\\)/e', "Keep('<div>'.PSS('$1').'</div>')");
We need to use PSS()
around the '$1
' because the matched text could contain quotation marks, and the /e
will add backslashes in front of them.
stripmagic($string)
This function should be used when processing the contents of $_POST
or $_GET
variables when they could contain quotes or backslashes. It verifies get_magic_quotes()
, if true, strips the automatically inserted escapes from the string.
The function can process arrays recursively (only the values are processed).
FmtPageName($fmt, $pagename)
Returns $fmt
, with $variable
and $[internationalisation] substitutions performed, under the assumption that the current page is pagename
. See PmWiki.Variables for an (incomplete) list of available variables, PmWiki.Internationalizations for internationalisation. Security: not to be run on user-supplied data.
This is one of the major functions in PmWiki, see PmWiki.FmtPageName for lots of details.
Markup($name, $when, $pattern, $replace)
Adds a new markup to the conversion table. Described in greater detail at PmWiki.CustomMarkup.
This function is used to insert translation rules into the PmWiki's translation engine. The arguments to Markup()
are all strings, where:
$name
$when
<xyz
" says to apply this rule prior to the rule named "xyz", while ">xyz
" says to apply this rule after the rule "xyz". See CustomMarkup for more details on the order of rules.
$pattern
$replace
Also see: PmWiki.CustomMarkup and Cookbook:Functions#Markup
MarkupToHTML($pagename, $str)
Converts the string $str
containing PmWiki markup into the corresponding HTML code, assuming the current page is $pagename
.
MarkupToHTML()
replaces \n\n
sequences by <:vspace>
first thing when text is passed to it. Subsequently <:vspace>
is processed by the markup rules:
!vspace
' removes <:vspace>
after headings.
<vspace><p>
' replaces <:vspace><p>
with <p class='vspace'>
<vspace>
' replaces <:vspace> with <div class='vspace'>
^<:
' removes any remaining <:vspace>
, mostly from restored [=escaped text=].
Also see: Cookbook:Functions#MarkupToHTML
mkdirp($dir)
The function mkdirp($dir)
creates a directory, $dir
, if it doesn't already exist, including any parent directories that might be needed. For each directory created, it checks that the permissions on the directory are sufficient to allow PmWiki scripts to read and write files in that directory. This includes checking for restrictions imposed by PHP's safe_mode setting. If mkdirp()
is unable to successfully create a read/write directory, mkdirp()
aborts with an error message telling the administrator the steps to take to either create $dir
manually or give PmWiki sufficient permissions to be able to do it.
Lock(0)
This function is used to make sure only one instance of PmWiki is running when files are being written. It does not "lock pages" for editing.
From a recipe, use:
Lock(2);
to acquire an exclusive lock, so that no other PHP processes can modify files. This can be used when your function is writing files on the server.
Lock(1);
to acquire a shared lock. This may be used when your function is reading files from the server, in case another process is writing them at the same time.
Lock(0);
to release a previous exclusive or shared lock. Use this immediately after your function finishes reading or writing the files.
If you don't release an acquired lock, it should be automatically released at the end of the processing.
MakeLink($pagename, $target, $txt, $suffix, $fmt)
The function MakeLink($pagename, $target, $txt, $suffix, $fmt)
returns an html-formatted anchor link. Its arguments are as follows:
$pagename
is the source page
$target is where the link should go
$txt is the value to use for '$LinkText' in the output
$suffix is any suffix string to be added to $txt
$fmt is a format string to use
If $txt is NULL or not specified, then it is automatically computed from $target.
If $fmt is NULL or not specified, then MakeLink uses the default
format as specified by the type of link. For page links this
means the $LinkPageExistsFmt
and $LinkPageCreateFmt
variables,
for intermap-style links it comes from either the $IMapLinkFmt
array or from $UrlLinkFmt
. Inside of the formatting strings,
$LinkUrl is replaced by the resolved url for the link, $LinkText
is replaced with the appropriate text, and $LinkAlt is replaced
by any "title" (alternate text) information associated with the
link.
Also see: PmWiki:MakeLink and Cookbook:Functions#MakeLink
MakeUploadName(
$pagename
, $x)
MakeUploadName()
simply takes a string $x
(representing an attachment's
name) and converts it to a valid name by removing any unwanted characters.
It also requires the name to begin and end with an alphanumeric character,
and as of 2.0.beta28 it forces any file extensions to lowercase.
This function is defined in scripts/upload.php
and only used when uploads
are enabled.
DownloadUrl(
$pagename
, $path)
This function returns the public URL of an attached file. The arguments are as follow:
$pagename
- the currently processed page
$path
- the file path, as in file.ext
or OtherPage/file.ext
or Group/OtherPage/file.ext
If the file doesn't exist, the function returns false. The global variable
contains the URL to the upload form, for a file with such a name to be attached. The global variable $FmtV
['$LinkUpload']
contains the URL to the file, as if it were uploaded to the wiki.
$FmtV
['$LinkDownload']
The function calls MakeUploadName()
on the $path
argument so you don't need to do it before calling it.
The returned URL respects $UploadPrefixFmt
and $EnableDirectDownload
of the wiki.
SessionAuth($pagename, $auth=NULL)
SessionAuth()
manages keeping authentication via cookie-sessions. Session contains every password or validated id and associated groups from previous calls. It adds elements passed by $auth
to session. It also writes every element saved in session to
and $AuthPw
(passwords)$AuthList(ids and groups)
.
IsAuthorized($chal, $source, &$from)
IsAuthorized()
takes a pageattributesstring (e. g. "id:user1 $1$Ff3w34HASH...") in $chal
.
$source
is simply returned and used for building the authcascade (pageattributes - groupattributes - $DefaultPassword
).
$from
will be returned if $chal
is empty, because it is not checked before calling IsAuthorized()
, this is needed for the authcascade.
IsAuthorized()
returns an array with three values: $auth
1
- authenticated, 0
- not authenticated, -1
- refused; $passwd
; $source
from the parameter list.
CondAuth($pagename, 'auth level')
CondAuth()
implements the ConditionalMarkup for (:if auth level:)
. For instance CondAuth($pagename,'edit')
is true if authorization level is 'edit'. Use inside local configuration files to build conditionals with a check of authorization level, similar to using (:if auth level:)
on a wiki page.
Note that CondAuth()
should be called after all authorization levels and passwords have been defined. For example, if you use it with Drafts, you should include the draft.php
script before calling CondAuth()
:
$EnableDrafts = 1; $DefaultPasswords['publish'] = pmcrypt('secret'); include_once("$FarmD/scripts/draft.php"); if (! CondAuth($pagename, 'edit')) { /* whatever */ }
Best is to use CondAuth()
near the bottom of your config.php
script.
RetrieveAuthPage(
$pagename
, $level, $authprompt=true, $since=0)
Pm words as said in https://www.pmwiki.org/pipermail/pmwiki-users/2005-April/012804.html where:
$pagename
- name of page to be read
$level - authorization level required (read/edit/auth/upload)
$authprompt - true if user should be prompted for a password if needed
$since - how much of the page history to read
0 == read entire page including all of history
READPAGE_CURRENT == read page without loading history
timestamp == read history only back through timestamp
The $since
parameter allows PmWiki to stop reading from a page file
as soon as it has whatever information is needed -- i.e., if an operation
such as browsing isn't going to need the page's history, then specifying
READPAGE_CURRENT can result in a much faster loading time. (This can be
especially important for things such as searching and page listings.)
However, if combined with UpdatePage()
, the updated page will have no history.
Use e.g. $page = @RetrieveAuthPage('Main.MyPage', 'read')
to obtain a page object that contains all the information of the correspondent file in separate keys, e.g. $page['text']
will contain a string with the current wiki markup of Main.MyPage. Use this generally in preference to the alternative function ReadPage($pagename, $since=0)
since it respects the authorisation of the user, i.e. it checks the authorisation level before loading the page, or it can be set to do so. ReadPage()
reads a page regardless of permission.
Passing 'ALWAYS' as the authorization level (instead of 'read', 'edit', etc.) will cause RetrieveAuthPage()
to always read and return the page, even if it happens to be protected by a read password.
RetrieveAuthSection(
$pagename
, $pagesection, $list=NULL, $auth='read')
RetrieveAuthSection()
extracts a section of text from a page. If $pagesection
starts with anything other than '#
', the text before the first '#
' (or all of it, if there is no '#
') identifies the page to extract text from. Otherwise RetrieveAuthSection()
looks in the pages given by $list
(should be an array), or in
if $pagename
$list
is not specified.
$RASPageName
variable.
Qualify()
as needed, i.e. if you need to control how unqualified page and variable names shall be resolved.
Qualify()
resolve them relative to the source page.
Qualify()
them. If you output them into wikitext, you'll probably need to Keep()
the text (in case of HTML, XML, RSS or similar output, PHSC()
first!), to prevent later stages of processing from interpreting the apparent wiki markups in context of the target page.
$pagename
argument for Qualify()
.
Provides a way to limit the array that is returned by ReadPage, so that it only pulls the content up to a specific section marker. For example, pulling from start of page to '##blogend':
function FeedText($pagename, &$page, $tag) { $text = RetrieveAuthSection($pagename, '##blogend'); $content = MarkupToHTML($pagename, $text); return "<$tag><![CDATA[$content]]></$tag>"; }
The '##blogend' argument says to read from the beginning of the page to just before the line containing the marker. See IncludeOtherPages for more information about the section specifications.
This version won't read text from pages that are read-protected; if you want to get text even from read-protected pages, then
$text = RetrieveAuthSection($pagename, '##blogend', NULL, 'ALWAYS');
UpdatePage(
$pagename
, $old (page object), $new (page object));
UpdatePage()
allows cookbook recipes to mimic the behavior of editing wiki pages via the browser. Internally, PmWiki does several housekeeping tasks which are accessible via this function (preserving history/diff information, updating page revision numbers, updating RecentChanges pages, sending email notifications, etc._
RetrieveAuthPage($pagename, $level, $authprompt=true, $since=0);
(preferred), or ReadPage($pagename);
(disregards page security). Note that $new['text']
should contain all page data for the new version of the page.
UpdatePage()
will attempt to create it.
$old
(e.g. UpdatePage($pagename, '', $new);
) will erase all historical page data---a tabula rasa.
$old
using RetrieveAuthPage($pagename
,$auth,$prompt,READPAGE_CURRENT)
and set $new=$old
, then UpdatePage()
will also erase all historical data
UpdatePage()
cannot be called directly from config.php
because there are necessary initializations which occur later in pmwiki.php
. It is not enough to just load stdconfig.php
. If you want to use UpdatePage()
you will need to do it within a custom markup, a custom markup expression, or a custom action.
InsertEditFunction($newfn, $where='<PostPage')
This function makes it easy for recipe authors to insert a custom function in a specific position of the processing, see $EditFunctions
and UpdatePage().
The first argument is the name of the new function.
The second argument is the position where to place the new function, related to other edit functions? It can be:
"<"
at the start, before the first edit function
">"
at the end, after the last edit function
"<ExistingEditFunction"
before a specified edit function
">ExistingEditFunction"
after a specified edit function
The function will return true on success, and false on failure (when the specified position or existing function was not recognized).
DisableSkinParts('parts to disable');
This function allows easy disabling of the skin sections header, footer, title, actions, and sidebars, like the corresponding directives (:notitle:) (:noleft:)
etc. In your function, use something like:
DisableSkinParts('Left Header Footer Action Title');
See Cookbook:ParseArgs.
AsSpaced()
is declared in pmwiki.php
AsSpaced()
converts a string with WikiWords into a spaced version of that string.
It can be overridden via $AsSpacedFunction
, as it is in AsSpacedUTF8()
. AsSpacedUTF8()
is declared in the xlpage-utf-8.php
script.
Categories: PmWiki Developer
This page may have a more recent version on pmwiki.org: PmWiki:Functions, and a talk page: PmWiki:Functions-Talk.