PmWiki / (alternate) Introduction to Custom markup for Beginners

The most typical kind of "plugin" (I'll call it a "recipe" from here on out because that's how they're named in the PmWiki world) is to establish some kind of "markup rule". This means you are defining some particular "pattern" of text in your page which will cause some action and cause that particular text to be replaced with something else.

(If you are wanting to create a custom action then click here.)

The simplest possible markup would be a straight replacement. Here is a markup to replace all occurrences of the letter "a" with the letter "z":

Markup('a2z', '>{$var}', '/a/', 'z');

Then your page with this text:

The alphabet begins with "abc"

will display as this:

The zlphzbet begins with "zbc"

It's not very useful, but it gives you the most basic idea of what markup text is doing.

Creating a new markup involves calling the PmWiki Markup() function. This is usually done by editing your config.php, but you can also put it in a custom group or custom page PHP file -- you can read about those options at LocalCustomizations and GroupCustomizations.

The Markup() function takes 4 arguments:

  1. The arbitrary name you are going to give your new markup. It should be short but descriptive. Be careful you don't use the same name as another markup out there or that markup will no longer be active. (You can see the standard markup definitions in scripts/stdmarkup.php.)
  2. An indicator of WHEN you want this to occur. PmWiki has dozens of these markup rules and it makes a big difference in what order they occur.
    If one markup rules (#1) changes all occurrences of "a" into "b" and another markup (#2) changes all occurrences of "az" into "zz" it obviously makes a big difference in what order they occur.
    If #1 occurs before #2 on the text "azazaz" then you will end up with "bzbzbz". But if #2 occurs before #1 then you will end up with "zzzzzz".
    This argument is normally specified as a left-angle bracket ("before") or a right-angle bracket ("after") followed by the name of another rule. In my experience the most significant rule in terms of ordering is "{$var}" which substitutes variables -- if you say "<{$var}" then your markup will be processed before variables are substituted whereas if you say ">{$var}" then your markup will be processed after variables are substituted. But there are lots of other places in the whole order of rules -- someone else will have to go into more detail if you need it. That CustomMarkup page gives some good pointers there.

Arguments 3 and 4 are simply arguments which will be passed to preg_replace. You search for argument #3 and you replace it with argument #4.

  1. This is a regular expression. It can be as simple as "/a/" (match every occurrence of the character "a") up to very complicated and intricate patterns. Every time this pattern matches in your text it will be replaced with argument #4. Note that your pattern is always surrounded by forward slashes and there can be modifiers after the closing forward slash.
    These modifiers are single characters which you can read more about them at PCRE pattern modifiers. The key ones are "i" (ignore case), "s" (allow dot to match newlines), "m" (allow ^ and $ to match before/after newlines as well as begin/end of strings).
  2. This is the replacement text. It can be a simple string or it can include things like $1, $2, etc if you have parenthesized groups in argument #3 (you've got to be careful to put backslashes in front of the $ or else surround it in single-quotes, etc to delay the interpolation of those variables). Once you are into PHP functions then you need to read some of the many PHP tutorials on the net to see which way to go.

Having said all that, the single best way to learn how to write your own recipe or markup is to look at examples of what other people have done.

The (:comment ...:) markup rule

Here is the definition of the markup rule for the (:comment ...:) markup from scripts/stdmarkup.php:

Markup('comment', 'directives', '/\\(:comment .*?:\\)/i', '');

The purpose of the this markup is to allow you to put some kind of text in your source that is simply not displayed when browsing the page. So let's look at each argument:

The (:include ...:) markup rule

Let's look at another example from stdmarkup.php, the (:include PAGENAME:) markup rule. This rule is designed to pull the text from another page into the current page. Here's how it's defined in stdmarkup.php:

Markup('include', '>if',
  '/\\(:include\\s+(\\S.*?):\\)/ei',
  "PRR(IncludeText(\$pagename, PSS('$1')))");

Warning the /e modifier has been deprecated for years (and finally disabled with PHP7.2). For details on how to replace it check CustomMarkup#php55.

Each argument, in order:

The (:nogroupheader:) markup rule

This markup rule is for the purpose of suppressing a group header from being displayed. For this to occur the global variable $GroupHeaderFmt must be set to a blank string. Here's the markup definition:

Markup('nogroupheader', '>include',
  '/\\(:nogroupheader:\\)/ei',
  "PZZ(\$GLOBALS['GroupHeaderFmt']='')");

Custom actions

An action is executed by appending "?action=MYACTION" to the end of the address URL. The default action is always "browse" so if you don't see any action in your address bar then pmwiki will assume you meant "https://www.example.com/pmwiki/...?action=browse".

For example, if you are using CleanUrls? and you want to access the page

but you want to specify an action of "source" (this displays the page source) then you would use

For another example, if you are NOT using CleanUrls and you want to access the page

but you want to specify an action of "edit" (this allows you to edit the page) then you would use

(This is an alternate method to edit a page if there is no link to do so.)

There are several other built-in actions, but sometimes it is convenient (as a PHP developer) to add your own custom action.

If you wanted to add an action named "mynewaction" and you wanted it to call a function HandleMyNewAction() then you might put this code in your config.php or in your recipe script:

$HandleActions['mynewaction'] = 'HandleMyNewAction';

$HandleAuth['mynewaction'] = 'admin';

function HandleMyNewAction($pagename, $auth) {
   $page = RetrieveAuthPage($pagename, $auth);
   if (!$page) {
      ...
   }
   ...
}

Note that the $HandleAuth[] array just determines what default authorization is passed as the second argument to your custom function. You are responsible to make enforce that authorization (typically through CondAuth() or RetrieveAuthPage()).

Other pages of interest may be found in the PmWiki Developer and markup categories.

This page may have a more recent version on pmwiki.org: PmWiki:CustomMarkupAlt, and a talk page: PmWiki:CustomMarkupAlt-Talk.