Manual:Tag extensions

From MediaWiki.org

Jump to: navigation, search
Tag Extensions Parser Functions Hooks Special Pages Skins Magic Words

Individual projects will often find it useful to extend the built-in wiki markup with additional capabilities, whether simple string processing, or full-blown information retrieval. Tag Extensions allow users to create new custom tags that do just that. For example, one might use a tag extension to introduce a simple <donation /> tag, which injects a donation form into the page. Extensions, along with Parser Functions and Hooks are the most effective way to change or enhance the functionality of MediaWiki. To see existing extensions built by other MediaWiki users see: Extension Matrix. You should always check the matrix before you start work on an extension to make sure someone else hasn't done exactly what you are trying to do.

A simple tag extension consists of a callback function, which is hooked to the parser so that, when the parser runs, it will find and replace all instances of a specific tag, calling the corresponding callback function to render the actual HTML.

Contents

[edit] Example

<?php
$wgExtensionFunctions[] = 'efSampleSetup';
 
function efSampleSetup() {
    global $wgParser;
    $wgParser->setHook( 'sample', 'efSampleRender' );
}
 
function efSampleRender( $input, $args, $parser ) {
    // Nothing exciting here, just escape the user-provided
    // input and throw it back out again
    return htmlspecialchars( $input );
}

This example registers a callback function for the <sample> tag. When a user adds this tag to a page like this: <sample arg1="xxx" arg2="xxx">...input...</sample>, the parser will call the efSampleRender() function, passing in three arguments:

$input 
Input between the <sample> and </sample> tags, or null if the tag is "closed", i.e. <sample />
$args 
Tag arguments, which are entered like HTML tag attributes; this is an associative array indexed by attribute name
$parser 
The parent parser; more advanced extensions use this to obtain the contextual Title, parse wiki text, expand braces, register link relationships and dependencies, etc.

[edit] Attributes

Let's look at another example:

<?php
$wgExtensionFunctions[] = 'efSampleSetup';
 
function efSampleSetup() {
    global $wgParser;
    $wgParser->setHook( 'sample', 'efSampleRender' );
}
 
function efSampleRender( $input, $args, $parser ) {
    $attr = array();    
    // This time, make a list of attributes and their values,
    // and dump them, along with the user input
    foreach( $args as $name => $value )
        $attr[] = '<strong>' . htmlspecialchars( $name ) . '</strong> = ' . htmlspecialchars( $value );
    return implode( '<br />', $attr ) . "\n\n" . htmlspecialchars( $input );
}

This example dumps the attributes passed to the tag, along with their values. It's quite evident that this allows for flexible specification of new, custom tags. You might, for example, define a tag extension which allows a user to inject a contact form on their user page, using something like <emailform to="User" email="user@foo.com" />.

There is a veritable plethora of tag extensions available for MediaWiki, some of which are listed on this site; others can be found via a quick web search. While a number of these are quite specialised for their use case, there are a great deal of well-loved and well-used extensions providing varying degrees of functionality.

[edit] Conventions

While an extension can be a single file, it is recommended that for each extension a separate subdirectory extension_name of the extensions directory is created with three files:

  • a small setup file, extension_name.setup.php
  • an internationalisation file, extension_name.i18n.php
  • a extension_name.body.php file with the bulk of the code.

The setup file adds an element to the $wgAutoloadClasses array, which specifies which files are to be loaded:

$wgAutoloadClasses[extension_name] = dirname(__FILE__) . '/extension_name.body.php';

[edit] Publishing your extensions

  1. Create a new page on this wiki named Extension:<extension_name> with information on your extension, how to install it, and screenshots of it in use. A convenient template has been created to hold this information called Template:Extension. See the template page for more information. You should also add as much detail as possible to the body of the page, and it is wise to check back fairly regularly to respond to user questions on the associated talk page. Also, make sure the page belongs to Category:Extensions.

    NOTE: If you already have a page for your extension on meta.wikimedia.org then please do not start a new page here. Your page will be moved by an admin, with complete revision history - in the meantime, continue to edit at meta.

  2. Extensions that create new hooks within the extension code should register them on extension hook registry.
  3. Notify the mediawiki-l mailing list.

[edit] FAQ

[edit] Security Concerns

You'll notice above that the input in the examples above is escaped using htmlspecialchars() before being returned. It is vital that all user input is treated in this manner before echoing it back to the clients, to avoid introducing vectors for arbitrary HTML injection, which can lead to cross-site scripting vulnerabilities.

[edit] Timing and extensions

If you change the code for an extension, all pages that use the extension will, theoretically, immediately reflect the results of new code. Technically speaking, this means your code is executed each and every time a page containing the extension is rendered.

In practice, this is often not the case, due to page caching - either by the MediaWiki software, the browser or by an intermediary proxy or firewall.

To bypass MediaWiki's parser cache and ensure a new version of the page is generated, click on edit, replace "action=edit" in the URL shown in the address bar of your browser by "action=purge" and submit the new URL. The page and all templates it references will be regenerated, ignoring all cached data. The purge action is needed if the main page itself is not modified, but the way it must be rendered has changed (the extension was modified, or only a referenced template was modified).

If this is not sufficient to get you a fresh copy of the page, you can normally bypass intermediary caches by adding '&rand=somerandomtext' to the end of the above URL. Make sure 'somerandomtext' is different every time.

[edit] How do I disable caching for pages using my extension?

Since MediaWiki 1.5, the parser is passed as the third parameter to an extension. This parser can be used to invalidate the cache like this:

function efSomeHookFunction( $text, $params, &$parser ) {
     $parser->disableCache();
     ...
 }

To disable all server side caching on the site, put the following code in LocalSettings.php:

/** Allow client-side caching of pages */
$wgCachePages       = false;
 
/**
 * Set this to current time to invalidate all prior cached pages. Affects both
 * client- and server-side caching.
 * You can get the current date on your server by using the command:
 *   date +%Y%m%d%H%M%S
 */
$wgCacheEpoch = 'date +%Y%m%d%H%M%S';

[edit] How do I render wikitext in my extension?

[edit] Since version 1.8

Parser hook functions are passed a reference to the parser object and should use this to parse wikitext.

function efWonderfulHook( $text, $args, &$parser ) {
   $output = $parser->recursiveTagParse( $text );
   return '<div class="wonderful">' . $output . '</div>';
}

Parser::recursiveTagParse() has been around since version 1.8. Its advantages include simplicity (it takes just one argument and returns a string) and the fact that it parses extension tags in $text, so you can nest extension tags.

[edit] Before version 1.8

Before version 1.8, the following, less pretty, approach must be used:

function efWonderfulHook( $text, $args, &$parser ) {
   $output = $parser->parse( $text, $parser->mTitle, $parser->mOptions, true, false );
   return '<div class="wonderful">' . $output->getText() . '</div>';
}

The fourth and fifth parameters to Parser::parse() are $lineStart and $clearState; these dictate how the parser behaves. $lineStart, when true, indicates that the wikitext is to be treated as starting on a new line (use this when dealing with blocks, or handling lists, etc.). $clearState, when true, indicates that the parser should clear internal state information; in most cases, this should be false.

Note that Parser::parse() returns a ParserOutput object, not raw HTML, as in the example above.

In cases where this does not work (often due to order of operations, nesting or other hook confusion), use a cloned copy of the parser object:

function efWonderfulHook( $text, $args, &$parser ) {
   $lparse = clone $parser;
   $output = $lparse->parse( $text, $parser->mTitle, $parser->mOptions, true, false );
   return '<div class="wonderful">' . $output->getText() . '</div>';
}

[edit] Extensions and Templates

If template parameters are used inside extension tags (e.g. <myext>{{{1}}}</myext>), the "{{{1}}}" is passed directly to the extension and not transformed into the correct value specified when the template was called. This is because extensions are parsed before templates in the parser.

One possible workaround is to use #tag: to invoke a tag as if it were a parser function; see Extension:TagParser for details. As of MediaWiki 1.12 (Jan 2008) the #tag: function is not an extension but part of the core MediaWiki code.

For instance:

"<code>{{{1}}}</code>"

could be rewritten as:

"{{#tag:code | {{{1}}} }}"

This would permit the template variable(s) to be substituted correctly.

See bug 2257 for more info and possible patches.

Another workaround for this issue is to create a new parser function which expands template variables and then creates the extension code. For example to call the <myext> extension with template parameters, you would create a new parser function like this:

<?php
$wgExtensionFunctions[] = 'efExtensionWrapperParserFunction_Setup';
$wgHooks['LanguageGetMagic'][]       = 'efExtensionWrapperParserFunction_Magic';
 
function efExtensionWrapperParserFunction_Setup() {
    global $wgParser;
    $wgParser->setFunctionHook( 'myext', 'efExtensionWrapperParserFunction_Render' );
}
 
function efExtensionWrapperParserFunction_Magic( &$magicWords, $langCode ) {
    $magicWords['myext'] = array( 0, 'myext' );
    return true;
}
 
function efExtensionWrapperParserFunction_Render( &$parser, $param1 = '' ) {
    # The input parameters are wikitext with templates expanded.  The output should be wikitext too
    return "<myext>{$param1}</myext>";
}

Then you would call the function on a page like this: {{#myext: param1}}

[edit] How can I pass XML-style parameters in my extension tag?

[edit] Since version 1.5

Since MediaWiki 1.5, XML-style parameters (tag attributes) are supported. The parameters are passed as the second parameter to the hook function, as an associative array. The value strings have already had HTML character entities decoded for you, so if you emit them back to HTML, don't forget to use htmlspecialchars(), to avoid the risk of HTML injection.

[edit] Before version 1.5

Before MediaWiki 1.5, you have to pass any parameters in between the extension tags. That is, you have to invent a special syntax for "options", and then analyse the hook's input text for them. One possible syntax would be:

<mytag>
@option1=foo
@option2=bar

...content to be processed by the extension....
</mytag>

In the extension function, you would then look for lines looking like @option1=foo before performing the normal processing. You are of course free to choose some other syntax.

[edit] How can I avoid modification of my extension's HTML output?

The current extension code assumes parser hook extensions will produce inline material and they are inserted before the final block-level rendering stages (see Bugzilla:8997).

One way to work around this is to make your extension's parser hook function output a marker, instead of the actual result, and then replace that marker with the actual result in a handler function registered for the ParserAfterTidy hook.

$wgHooks['ParserAfterTidy'][] = 'myextParserAfterTidy';
$wgExtensionFunctions[] = "myextSetup";
 
function myextSetup {
    global $wgParser;
    $wgParser->setHook( "mytag", "myextParserHook" );
}
 
$markerList = array();
function myextParserHook($input, $argv, &$parser) {
    global $markerList;
    $output = "result that you do not want to be modified by the parser";
    $makercount =  count($markerList);
    $marker = "xx-marker".$makercount."-xx";
    $markerList[$makercount] = $output;
    return $marker;
}
 
function myextParserAfterTidy(&$parser, &$text) {
    // find markers in $text
    // replace markers with actual output
    global $markerList;
    for ($i=0;$i<count($markerList);$i++)
      $text = preg_replace('/xx-marker'.$i.'-xx/',$markerList[$i],$text);
    return true;
}

Alternatively, instead of outputting a marker, it would also be possible to output the result in an armored form (e.g. Base64-Encoded), and unarmor (decode) it in the ParserAfterTidy handler.

[edit] How do I get my extension to show up on Special:Version?

In order for your extension to be displayed on the MediaWiki Special:Version page, you must assign extension credits within the PHP code.

To do this, add a $wgExtensionCredits variable as the first executable line of code before the hook line or function definition.

An example extension credit is:

<?php
/**
 * Example.php
 * This extension does...
 * written by John Doe
 * http://www.johndoe.com/
 * To activate the functionality of this extension include the following in your
 * LocalSettings.php file:
 * require_once('$IP/extensions/Example.php');
 */
if(! defined( 'MEDIAWIKI' ) ) {
   echo( "This is an extension to the MediaWiki package and cannot be run standalone.\n" );
   die( -1 );
}
 
define('EXTENSION_VERSION','R16385');
 
$wgExtensionCredits['validextensionclass'][] = array(
  'name'         => 'Example',
  'author'       =>'John Doe', 
  'url'          => 'http://www.mediawiki.org/wiki/User:JDoe',
  'description'  => 'This extension is an example and performs no discernable function',
  'version'      => EXTENSION_VERSION
);
..
 
function fnExample(){
..
 
}

Replace validextensionclass with one of the following (unless your extension falls under multiple classes--then create a credit for each class):

  • 'specialpage' -- reserved for additions to Mediawiki Special Pages;
  • 'parserhook' -- used if your extension modifies, complements, or replaces the parser functions in MediaWiki;
  • 'variable' -- extension that add multiple functionality to MediaWiki;
  • 'other' -- all other extensions.

[edit] See also

Personal tools