Chapter 9. Extensions

By now, you are familiar with magic words and how to use them with wikitext. In this chapter, you will learn how to create your very own magic words and special pages. Of necessity, this means diving a little deeper into the inner workings of MediaWiki, and it requires familiarity with PHP. If you are completely unfamiliar with PHP and computer programming, you can find several good books that can help you.

See especially Beginning PHP, Apache, MySQL Web Development, by Michael K. Glass et al. (Wrox, 2004).

Otherwise, read on and learn about MediaWiki's extension mechanism.

Extensions enable you to customize MediaWiki to your individual needs. The MediaWiki community has made a fairly large number of extensions available, which are a good starting place to learn how to write them. Extensions are surprisingly easy to write in MediaWiki. They are made possible by a rather large collection of hooks throughout the application that you can register callback functions with, which are then called at opportune times.

XML tag extensions and parser functions are callback functions that enable you to extend wikitext. There are a large number of other hooks, though, that give you the opportunity to modify MediaWiki behavior at all stages of the page delivery process.

In addition to learning how to use hooks in this chapter, you will also learn how to create your own special pages.

MediaWiki Hooks

A hook is an array of functions (if any functions have been registered) that are carried out every time a given section of code is executed. XML tag extensions and parser functions are two special cases of hooks that are used to extend wikitext. Many more, however, are available to you. The list of available hooks is long and growing, and not all of them are documented. You can find the latest documentation at www.mediawiki.org/wiki/Manual:MediaWiki_hooks. In addition, you can search for undocumented hooks by running the following scripts from the command line: maintenance/findhooks.php.

In order to understand how hooks work, and decide which hooks are good candidates to accomplish whatever it is you want to accomplish with your extension, it is necessary to have a basic understanding of the sequence of events that takes place when a page is requested in MediaWiki. The next section reviews that process in some detail, after which we will delve into the specifics of creating your own extensions. Examples are provided to get you started.

The Parsing Process

Every request begins with a call to index.php, the PHP script that serves as an entry point to the MediaWiki application. In most typical uses of PHP, PHP code is embedded into HTML pages. MediaWiki does things the other way around. The PHP files that comprise MediaWiki are all PHP code. If you are familiar with computer programming, but perhaps not familiar with PHP, you need to understand how code is executed in PHP because it will help you understand the following sections.

A PHP script executes either when it is requested by the HTTP server or when it is included in another PHP page. Whenever a PHP script is included in another script, the scope of the included script's execution is limited to the current insertion. The only exception to this is PHP scripts that define functions or classes. The code in a function isn't executed until the function is called by a statement in the script. Classes aren't executed, they are instantiated, so they must be instantiated in code.

Look at the first line of code in index.php, and you'll see the following line:

require_once( './includes/WebStart.php' );

require_once is one of the ways that PHP includes one PHP script in another script. The once refers to the fact that this script should only be included one time. The PHP parser will then avoid loading it again, even if it encounters the same line of code later in the script.

When a page is first requested, the WebStart.php script is executed, so in order to follow the logic of the application, you need to open WebStart.php and start at the top. It is this functionality that often makes PHP code so difficult to decipher—the code frequently jumps from page to page and it can be tedious to follow. Fortunately for us, the developers of MediaWiki have made it possible to extend and customize MediaWiki without needing to modify the base code. It is helpful, however, to understand the basic mechanics of how a request in MediaWiki is processed.

The WebStart.php script then makes some security checks and loads the Defines.php file, which initializes a list of constants used by the application, followed by the familiar LocalSettings.php (which in turn loads and thereby executes DefaultSettings.php). Finally, Setup.php is executed, which sets up a host of global variables and includes still more PHP scripts used by MediaWiki. Setup.php also instantiates some very important objects that you will need to know about when writing extensions: $wgUser, $wgOut, $wgParser, $wgTitle, and $wgArticle, which are discussed in more detail momentarily.

All of this happens when index.php includes WebStart.php at the very beginning of a page request. The next two lines of code in index.php are as follows:

require_once( "includes/Wiki.php" );
$mediaWiki = new MediaWiki();

The MediaWiki class is defined in Wiki.php, and a new $mediaWiki object is instantiated. The MediaWiki class is intended to be the base class for the MediaWiki application. It's intended because the developers are still migrating MediaWiki into a more object-oriented architecture. While good headway is being made, not all of the functionality is encapsulated in objects, and a lot of global variables and global functions are floating around. Nevertheless, the request is processed by the MediaWiki object. The following snippet of code from the index.php script shows the stages of the request cycle in MediaWiki. Additional comments in the code provide some explanation for each step:

# Generate a title object
$wgTitle = $mediaWiki->checkInitialQueries(
   $title,$action,$wgOut, $wgRequest, $wgContLang );

# Some debugging and error checking code has been deleted for clarity
# Set global variables in mediaWiki, based in some instances on values
# set in LocalSettings.php
$mediaWiki->setVal( 'Server', $wgServer );
$mediaWiki->setVal( 'DisableInternalSearch', $wgDisableInternalSearch );
$mediaWiki->setVal( 'action', $action );
$mediaWiki->setVal( 'SquidMaxage', $wgSquidMaxage );
$mediaWiki->setVal( 'EnableDublinCoreRdf', $wgEnableDublinCoreRdf );
$mediaWiki->setVal( 'EnableCreativeCommonsRdf', $wgEnableCreativeCommonsRdf );
$mediaWiki->setVal( 'CommandLineMode', $wgCommandLineMode );
$mediaWiki->setVal( 'UseExternalEditor', $wgUseExternalEditor );
$mediaWiki->setVal( 'DisabledActions', $wgDisabledActions );

# Initialize the Article object, which is responsible for building the page
# In the mediaWiki->initialize method, the article object is instantiated,
# and then mediaWiki->performAction is called, which will cause the appropriate
# page to be displayed, depending on the requested action.
$wgArticle = $mediaWiki->initialize ( $wgTitle, $wgOut, $wgUser, $wgRequest );

# The following methods perform some cleanup tasks, and are
# positioned here after the article has been created for performance
# reasons.
$mediaWiki->finalCleanup ( $wgDeferredUpdateList, $wgLoadBalancer, $wgOut );
  wfDebug("PROF: Do updates
");
$mediaWiki->doUpdates( $wgPostCommitUpdateList );

$mediaWiki->restInPeace( $wgLoadBalancer );

That concludes the high-level overview of how a request is processed, but it leaves out a fair amount of detail. A lot is going on behind the scenes. Before a page is delivered, permissions need to be checked, wikitext needs to be converted into HTML, and the overall page needs to be displayed and configured appropriately for the user requesting the page. This brings us to the $wgUser, $wgOut, and $wgParser objects.

The user object represents the user making the request, whether it's an anonymous user or a sysop, and MediaWiki uses this object to determine whether the page can be delivered, and what options to display on the page according to the permissions granted to the user.

The $wgOut object is the output page, which results in the HTML that is sent back to the requesting browser. $wgParser is the parser object, responsible for parsing wikitext and turning it into HTML. The following sections walk through the parsing process. Mind you, this is not for your moral edification; there are very practical applications for this knowledge. When you are writing extensions for MediaWiki, such as XML-type wikitext extensions or parser functions, your code will be executed during the parsing process and you will need to be familiar with what happens.

Step 1: Start with Raw Wikitext

When a page is first requested, the page content, stored as wikitext, is retrieved and converted into HTML by the parser. This, of course, is a simplified view, ignoring things like caching, but that's basically what happens. The process of parsing the wikitext into HTML is handled by code in includes/Parser.php, which defines the Parser, ParserOptions and ParserOutput classes, all of which laboriously, with much Sturm und Drang and sound and fury, convert humble wikitext into magnificent HTML.

The parser doesn't just scan through the wikitext once and then spit out some HTML. That would be too easy. This parser isn't happy until it has chewed through the wikitext nine or ten times. Needless to say, the parser is not a model of efficiency and grace (which is why you learn all about caching in Chapter 11). In all fairness, it is a powerful parser that does a lot of cool tricks. The reason for so much of the code is partly because of the demands of parsing wikitext, but also because the developers have provided many hooks that enable others to extend MediaWiki so easily.

The simplest way to see what is happening is to follow wikitext through the process. The following wikitext is fresh from the database, waiting to be parsed:

==Sample Wikitext==

I want to use '''a few''' different wikitext features.

<nowiki>
===This won't be parsed===
</nowiki>

This links to the [[Main Page]] and this links to [[User:WikiSysop]].

<pre>
when does this return?
</pre>

===Sample tag hook===

<mytaghook arg1="Red" arg2="Blue">My content</mytaghook>

===Sample parser function===

{{example: Red | Blue}}

Step 2: Remove Text That Shouldn't Be Parsed

The first thing the parser does is strip out <nowiki>, <pre>, and <gallery> tags (and the content they contain) and replace them with a unique identifier (so they can be unstripped later). <nowiki> and <pre> are stripped out because, by definition, they aren't parsed. The reason why <gallery> is stripped out has to do with a bug in the parser, so it is handled separately. Most important, XML tag extensions are stripped out as well, which keeps them from being parsed as raw wikitext, which takes place in the step that follows this one.

The following code contains comments to make the code easier to read, but they are not produced during the conversion process. You can see the unique tokens that now reside in the place where the <nowiki> and <pre> tags once resided, as well as our customized XML extension <mytaghook arg1="Red" arg2="Blue" >My content</mytaghook>:

==Sample Wikitext==

I want to use '''a few''' different wikitext features.

 <!-- Former "nowiki" tag -->
 UNIQ767b803742958244-nowiki-00000001-QINU

This links to the [[Main Page]] and this links to [[User:WikiSysop]].

 <!-- Former "pre" tag -->
 UNIQ767b803742958244-pre-00000002-QINU

===Sample tag hook===

 <!-- Former "mytaghook" tag -->
 UNIQ767b803742958244-mytaghook-00000003-QINU

===Sample parser function===

{{example: Red | Blue}}

Step 3: Generate Some (But Not All) of the Wikitext

The next step occurs when the $wgParser->internalParse() method is called. It does two things. First, it converts some of the wikitext, such as headings and inline styles, to HTML, and adds the edit links where appropriate. It also strips out all the wikilinks and replaces them with tokens that identify them so that they, too, can be reinserted at a later time, using a format like the following:

<!--LINK 0-->

Now the example parser function has been executed and its output inserted into the text:

<p><a name="Sample_Wikitext" id="Sample_Wikitext"></a></p>

<h2><span class="editsection">[<a
    href="/mysql/index.php?title=MediaWiki_Extensions&amp;action=edit&amp;section=1"
    title="Edit section: Sample Wikitext">edit</a>]</span> <span
    class="mw-headline">Sample Wikitext</span></h2>

<p>I want to use <b>a few</b> different wikitext features.
    UNIQ767b803742958244-nowiki-00000001-QINU This links to the <!--LINK 0-->
    and this links to <!--LINK 1-->. UNIQ767b803742958244-pre-00000002-QINU <a
    name="Sample_tag_hook" id="Sample_tag_hook"></a></p>

<h3><span class="editsection">[<a
    href="/mysql/index.php?title=MediaWiki_Extensions&amp;action=edit&amp;section=2"
    title="Edit section: Sample tag hook">edit</a>]</span> <span
class="mw-headline">Sample tag hook</span></h3>

<p>UNIQ767b803742958244-mytaghook-00000003-QINU <a
    name="Sample_parser_function" id="Sample_parser_function"></a></p>

<h3><span class="editsection">[<a
    href="/mysql/index.php?title=MediaWiki_Extensions&amp;action=edit&amp;section=3"
    title="Edit section: Sample parser function">edit</a>]</span> <span
    class="mw-headline">Sample parser function</span></h3>

<!-- Parser functions are executed -->
<p>Function: example<br>
param1 value is: Red<br>
param2 value is: Blue<br>
<a href="http://127.0.0.1//mysql/index.php?title=MediaWiki_Extensions&amp;
    action=edit" class="external free" title="http://127.0.0.1//mysql/index.php?
    title=MediaWiki_Extensions&amp;action=edit" rel="nofollow">http://127.0.0.1//
    mysql/index.php?title=MediaWiki_Extensions&amp;action=edit</a></p>

Step 4: Unstrip Everything That You Stripped (Except <nowiki> Text)

In this step, all the things you stripped in Step 2 should be reinserted into the wikitext, except for the content nested in <nowiki> tags and content for links:

<p><a name="Sample_Wikitext" id="Sample_Wikitext"></a></p>

<h2><span class="editsection">[<a
    href="/mysql/index.php?title=MediaWiki_Extensions&amp;action=edit&amp;section=1"
    title="Edit section: Sample Wikitext">edit</a>]</span> <span
    class="mw-headline">Sample Wikitext</span></h2>

<p>I want to use <b>a few</b> different wikitext features.
    UNIQ767b803742958244-nowiki-00000001-QINU This links to the <!--LINK 0-->
    and this links to <!--LINK 1-->.</p>

<!-- "pre" text returned -->
<pre>
when does this return?
</pre>

<p><a name="Sample_tag_hook" id="Sample_tag_hook"></a></p>

<h3><span class="editsection">[<a
    href="/mysql/index.php?title=MediaWiki_Extensions&amp;action=edit&amp;section=2"
    title="Edit section: Sample tag hook">edit</a>]</span> <span
    class="mw-headline">Sample tag hook</span></h3>

<!-- "mytaghook" results are inserted into the text -->
<p>Input: My content<br>
Arg1 value is: Red<br>
Arg2 value is: Blue<br>
<a name="Sample_parser_function" id="Sample_parser_function"></a></p>
<h3><span class="editsection">[<a
    href="/mysql/index.php?title=MediaWiki_Extensions&amp;action=edit&amp;section=3"
    title="Edit section: Sample parser function">edit</a>]</span> <span
    class="mw-headline">Sample parser function</span></h3>

<p>Function: example<br>
param1 value is: Red<br>
param2 value is: Blue<br>
<a href="http://127.0.0.1//mysql/index.php?title=MediaWiki_Extensions&amp;
    action=edit" class="external free" title="http://127.0.0.1//mysql/index.php?
    title=MediaWiki_Extensions&amp;action=edit" rel="nofollow">http://127.0.0.1//
    mysql/index.php?title=MediaWiki_Extensions&amp;action=edit</a></p>

Step 5: Fix Common Errors

Nothing changes in our sample wikitext because I didn't make any of the errors that are routinely fixed. Basically, the code looks for some common mistakes that are made when people type wikitext and tries to fix them before proceeding.

Step 6: Generate Block-level HTML

Next, the block-level HTML that didn't get produced in Step 3 is now generated. In most cases, the block-level elements are already in place, but in places where <nowiki> text has been temporarily removed, the HTML needs to be fixed. Here is the original paragraph before the block-level HTML is generated:

<p>I want to use <b>a few</b> different wikitext features.
    UNIQ767b803742958244-nowiki-00000001-QINU This links to the <!--LINK 0-->
    and this links to <!--LINK 1-->.</p>

The following output shows the same paragraph after the block-level HTML is generated. You will notice that <p> tags have been added around the placeholder for <wikitext> and what was one paragraph before is now three:

<p>I want to use <b>a few</b> different wikitext features.</p>

<p>UNIQ767b803742958244-nowiki-00000001-QINU</p>

<p>This links to the <!--LINK 0--> and this links to <!--LINK 1-->.</p>

Step 7: Put Links Back in

Finally, the wikilinks are put back into the page. The following code snippet shows the links before they are returned:

<p>This links to the <!--LINK 0--> and this links to <!--LINK 1-->.</p>

This code shows the content with the links back in the page:

<p>This links to the <a href="/mysql/index.php/Main_Page"
   title="Main Page"> Main Page</a> and this links to <a
   href="/mysql/index.php?title=User:WikiSysop&amp;action=edit" class="new"
   title="User:WikiSysop">User:WikiSysop</a>.</p>

Step 8: Do Something Obscure with Chinese Text

This step translates from one form of Chinese text to another. Needless to say, nothing happens to our example wikitext because it's not in Chinese.

Step 9: Unstrip <nowiki> Elements

The <nowiki> tags have not been forgotten. They are now added back into the text, which means that we're done converting wikitext.

The original <nowiki> tag looked like this:

I want to use '''a few''' different wikitext features.

<nowiki>
===This won't be parsed===
</nowiki>

This links to the [[Main Page]] and this links to [[User:WikiSysop]].

Then, the tag was stripped and replaced with a placeholder:

<p>I want to use <b>a few</b> different wikitext features.</p>

<p>UNIQ767b803742958244-nowiki-00000001-QINU</p>

<p>This links to the <a href="/mysql/index.php/Main_Page"
   title="Main Page"> Main Page</a> and this links to <a
   href="/mysql/index.php?title=User:WikiSysop&amp;action=edit" class="new"
   title="User:WikiSysop">User:WikiSysop</a>.</p>

Finally, the raw content nested by the <nowiki> tag is returned, wrapped in <p> tags:

<p>I want to use <b>a few</b> different wikitext features.</p>

<p>===This won't be parsed===</p>

<p>This links to the <a href="/mysql/index.php/Main_Page"
   title="Main Page"> Main Page</a> and this links to <a
   href="/mysql/index.php?title=User:WikiSysop&amp;action=edit" class="new"
   title="User:WikiSysop">User:WikiSysop</a>.</p>

Step 10: Tidy Up the HTML

That leaves one last step, which is to tidy up the HTML with Tidy, if you have opted for that configuration in LocalSettings.php. In this case, there wasn't much to tidy up, so the content remains the same.

XML Tag Extensions

XML wikitext extensions take the form of XML tags that can optionally include attributes. The previous examples used to illustrate the stages of the parsing process included an XML extension function called mytaghook. In this section, you will learn how to create that extension, so that the user can enter the following XML:

<mytaghook arg1="Red" arg2="Blue">My content</mytaghook>

The output of the preceding XML will be as follows:

<p>Input: My content<br>
Arg1 value is: Red<br>
Arg2 value is: Blue<br></p>

Creating an XML tag extension is a three-part process:

  1. The first step is to create a MyTagHook.php file in the extensions directory.

  2. Define two functions in the MyTagHook.php file: wfMyTagHook_Setup and wfMyTagHook_Render.

  3. Insert the following at the end of LocalSettings.php: include("extensions/MyTagHook.php");

The Setup Function

Two functions need to be written. The first function will be used to register the second function with the parser. In the following example, I have created a function wfMyTagHook_Setup that is added to the $wgExtensionFunctions array. All functions appended in this way will be executed when the parser is instantiated, in order to register the callback function to be used by the parser, which is called wfMyTagHook_Render:

$wgExtensionFunctions[] = "wfMyTagHook_Setup";


# This function is called in Setup.php and it registers the name of the
# tag with the parser, as well as the callback function that renders the
# actual HTML output.
function wfMyTagHook_Setup() {
    global $wgParser;
    # If this were a parser function instead of an extension tag,
  # the $wgParser->setFunctionHook method would be called.
  # The renderMyTagHook function will be called in Parser->strip.
    $wgParser->setHook( "mytaghook", "wfMyTagHook_Render" );
}

The Render Function

In this example, the wfMyTagHook_Render function does not do anything particularly useful. It just returns information about the arguments that were passed to the function. The function receives three arguments:

  • $input: a string representing the text between the opening and closing XML tags

  • $argv: an associative array containing any attributes used in the XML tag

  • &$parser: a reference to the parser object

function wfMyTagHook_Render( $input, $argv, &$parser ) {
  # This keeps the parser from caching output, which is especially
# useful when debugging.
$parser->disableCache();

  # This tag extension simply returns information about the request,
 # such as the value for $input, and the arguments.
 # The $output variable is a string, not on OutputPage object.
  $output = "Input: " . $input . "<br/>";
 $output .="Arg1 value is: " .$argv["arg1"] . "<br/>";
 $output .="Arg2 value is: " .$argv["arg2"] . "<br/>";

The Complete Extension

The complete extension script follows. In addition to the functions already discussed, some additional code has been added that keeps the code from being run outside of MediaWiki, and that generates credits for the author of the extension, which is displayed on the special page Special:Version:

<?php
# This is an example extension to wikitext, that adds additional
# XML tags to MediaWiki to be parsed as wikitext. The tags are
# structured like normal XML elements, such as:
#    <example arg1="some value">My input text/example>
# The function registered by this extension gets passed to the text between the
# tags as $input as well as an arbitrary number of arguments passed
# in the $argv array. These tag extensions are expected to return HTML.
# If wikitext is returned instead, it will not be parsed.
# To activate the extension, include it from your LocalSettings.php
# with: include("extensions/MyTagHook.php");

# This code keeps this PHP file from being run on the commandline;
# It can only be called from within MediaWiki.
if(! defined( 'MEDIAWIKI' ) ) {
   echo( "This is an extension to the MediaWiki package and cannot be run
   standalone.
" );
   die( −1 );
} else {

# Give yourself credit. This will appear in the Special:Version page.
$wgExtensionCredits['parser'][] = array(
       'name' => 'My Tag Hook',
       'author' =>'Mark Choate',
       'url' => 'http://choate.info/',
       'description' => 'A simple example.'
       );
}


# Register the extension function. During the setup phase in Setup.php,
# all the extensions that have been registered (or appended) to the
  $wgExtensionFunctions
# array are executed. This is not the function that
  generates the content, but it is the
# function that registers the extension with the parser.
$wgExtensionFunctions[] = "wfMyTagHook_Setup";


# This function is called in Setup.php and it registers the name of the
# tag with the parser, as well as the callback function that renders the
# actual HTML output.
function wfMyTagHook_Setup() {
    global $wgParser;
    # If this were a parser function instead of an extension tag,
 # the $wgParser->setFunctionHook method would be called.
 # The renderMyTagHook function will be called in Parser->strip.
    $wgParser->setHook( "mytaghook", "wfMyTagHook_Render" );
}

# This is the callback function for converting the input text to HTML output.
# $input is the text that is nested in the XML tag. For example,
# in <example>My Text</example>, the value held in the $input variable
# will be "My Text". $argv is an array that contains the values of
# the XML element's attributes, such as <example arg1="Some data" arg2="More data">.
function wfMyTagHook_Render( $input, $argv, &$parser ) {
  # This keeps the parser from caching output, which is especially
# useful when debugging.
$parser->disableCache();

  # This tag extension simply returns information about the request,
  # such as the value for $input, and the arguments.
  # The $output variable is a string, not on OutputPage object.
  $output = "Input: " . $input . "<br/>";
  $output .="Arg1 value is: " .$argv["arg1"] . "<br/>";
  $output .="Arg2 value is: " .$argv["arg2"] . "<br/>";

    return $output;
}
?>

The extension should then be included in the LocalSettings.php file, so that MediaWiki is aware of it:

include("extensions/MyTagHook.php");

The renderTagHook function is called Step 2 of the parsing process, in the strip function. The results of the function are not inserted into the original text, however, until Step 4, skipping Step 3, where the main wikitext is converted into HTML. As a consequence, XML tag extensions skip the conversion process, so that any wikitext returned by the function will not be converted into HTML.

Following is the source tag:

<mytaghook arg1="Red" arg2="Blue">My content</mytaghook>

Here are the tag results:

<!-- "mytaghook" results are inserted into the text -->
<p>Input: My content<br>
Arg1 value is: Red<br>
Arg2 value is: Blue<br></p>

Parser Functions

The first parser function demonstrated is called (imaginatively) Example, and it can be referenced in wikitext in the following way:

{{example: Red | Blue}}

This parser function will generate the following HTML:

<p>Function: example<br>
param1 value is: Red<br>
param2 value is: Blue<br>
</p>

Parser functions are very similar to XML tag extensions, with the primary difference being that you have to register the function as a magic word. The steps to creating a function are as follows:

  1. Create an ExampleParserFunction.php file in the extensions directory.

  2. Define three functions in the ExampleParserFunction.php file: wfExampleParserFunction_Setup, wfExampleParserFunction_Render, and wfExampleParserFunction_Magic.

  3. Insert the following at the end of LocalSettings.php: include("extensions/ExampleParserFunction.php");

Unlike XML tag extensions, parser functions are processed earlier enough that their output can be wikitext, which is converted to HTML. The parser functions are called in Step 3, during the process in which wikitext is converted to HTML.

This example makes use of magic words. In this case, the magic words provide localization for the name of the parser function itself; you have to register a magic word for the parser function to work. In this example, only one word is mapped to the key, but it is followed by an example with far more extensive customization.

The Setup Function

The setup function is very similar to the XML tag extension setup function, except that it registers the parser function using the $wgParser->setFunctionHook function, rather than $wgParser->setHook:

<?php

# Define a setup function
$wgExtensionFunctions[] = 'wfExampleParserFunction_Setup';
function wfExampleParserFunction_Setup() {
        global $wgParser;

       # Set a function hook associating the "example" magic word with our function
        # Setting the third argument to "1" will enable you to create parser functions
        # that do not need to be preceeded with a "#" character. The primary benefit of
       # using "#" is that it avoids namespace collisions and other confusion.
        $wgParser->setFunctionHook( 'example', 'wfExampleParserFunction_Render',  1);
}

The Render Function

The render function generates the output based on the values passed in the parameters:

function wfExampleParserFunction_Render( &$parser, $param1
    = 'default1', $param2 = 'default2' ) {
        # The parser function itself
        # The input parameters are wikitext with templates expanded
        # The output should be wikitext too.

        $output = "Function: example<br/>";
        $output .="param1 value is: " . $param1 . "<br/>";
        $output .="param2 value is: " . $param2 . "<br/>";

        return $output;
}

?>

The Magic Function

The magic function is used to add new messages to the MessageCache. In this example, not much happens, but later you will see a more detailed explanation of what is happening here, in the section "Parser Functions with Messages."

# Add a hook to initialise the magic word
$wgHooks['LanguageGetMagic'][]       = 'wfExampleParserFunction_Magic';

function wfExampleParserFunction_Magic( &$magicWords, $langCode ) {
        # Parser functions are "magic words", which means that you can configure or
        # localize the word used to refer to the function.
        # All remaining elements are synonyms for our parser function.
        # This is a simple case, and uses the same word regardless of language.
        # The "0" value in the first element of the array signifies that this word
        # is not case sensitive.
        # This function is called by the LanguageGetMagic hook.
        $magicWords['example'] = array( 0, 'example' );

        # Return true so that the other functions will be loaded.
        return true;
}

The Complete Extension

Altogether, the different elements combine to create the complete extension:

<?php

# Define a setup function
$wgExtensionFunctions[] = 'wfExampleParserFunction_Setup';

# Add a hook to initialise the magic word
$wgHooks['LanguageGetMagic'][]       = 'wfExampleParserFunction_Magic';

function wfExampleParserFunction_Setup() {
        global $wgParser;

       # Set a function hook associating the "example" magic word with our function
        # Setting the third argument to "1" will enable you to create parser functions
        # that do not need to be preceeded with a "#" character. The primary benefit of
       # using "#" is that it avoids namespace collisions and other confusion.
        $wgParser->setFunctionHook( 'example', 'wfExampleParserFunction_Render',  1);
}

function wfExampleParserFunction_Magic( &$magicWords, $langCode ) {
        # Parser functions are "magic words", which means that you can configure or
        # localize the word used to refer to the function.
        # All remaining elements are synonyms for our parser function.
        # This is a simple case, and uses the same word regardless of language.
        # The "0" value in the first element of the array signifies that this word
        # is not case sensitive.
        # This function is called by the LanguageGetMagic hook.
        $magicWords['example'] = array( 0, 'example' );

        # Return true so that the other functions will be loaded.
        return true;
}

function wfExampleParserFunction_Render( &$parser, $param1
  = 'default1', $param2 = 'default2' ) {
        # The parser function itself
        # The input parameters are wikitext with templates expanded
        # The output should be wikitext too.

        # Global variables must be declared locally in order to be accessed.
        global $wgServer;

        $output = "Function: example<br/>";
        $output .="param1 value is: " . $param1 . "<br/>";
        $output .="param2 value is: " . $param2 . "<br/>";

        return $output;
}
?>

Default Values

Parser functions also support default values, and this function was declared with default values set for both param1 and param2:

function wfExampleParserFunction_Render( &$parser, $param1
  = 'default1', $param2 = 'default2' )

This means that you can also call the function with only one parameter, as in the following:

{{example: Red}}

The result of this function would be as follows:

<p>Function: example<br>
param1 value is: Red<br>
param2 value is: default2<br>
</p>

Return Values

Parser functions can return a string with the resulting text, or an array of values. The first element of the array is the resulting text, and the rest are flags, which can be set to true or false.

The return values include the following:

  • found: Stop processing the template.

  • nowiki: Do not process the wikitext.

  • noparse: Do not remove unsafe HTML tags.

  • noargs: If this is used in a template, do not replace the triple-brace arguments ({{{) in the return value.

  • isHTML: The text is HTML and should not be parsed as wikitext.

Using Global Objects

Several global objects can be accessed when writing parser functions. The following function is a rewritten version of the earlier function, this time making use of global objects. This function gets a reference to the Title object from the parser and uses it to construct a URL to itself:

function wfExampleParserFunction_Render( &$parser, $param1 =
  'default1', $param2 = 'default2' ) {

 # Global variables must be declared locally in order to be accessed.
         global $wgServer;

         # You can access global variables through the parser object:
         $title = $parser->getTitle();
$output = $wgServer ."/" . $title->getEditURL();


         return $output;
}

The output of this page is as follows:

<p><a
    href="http://127.0.0.1//mysql/index.php?title=MediaWiki_
    Extensions&amp;action=edit" class="external free"
    title="http://127.0.0.1//mysql/index.php?title=MediaWiki_
    Extensions&amp;action=edit"
    rel="nofollow">http://127.0.0.1//mysql/index.php?title=MediaWiki_
    Extensions&amp;action=edit</a> Function:
    example</p>

Parser Functions with Messages

The following example illustrates a more complete use of the MessageCache object to translate terms for the parser function. This example uses a real MediaWiki extension as the starting point, but it has been simplified to make it easy to follow the logic of the code. The original extension is called ParserFunctions (as opposed to the built-in parser functions reviewed in the previous chapter), and is very popular. It was written by Tim Starling, and can be found at http://meta.wikimedia.org/wiki/ParserFunctions.

This simplified version is called ParserFunctionsLite, and in order to implement it, you need to do the following:

  1. Create a ParserFunctionsLite.php file in the extensions/ParserFunctionsLite directory, which you will have to create, as well as a ParserFunctionsLite.i18n.php file.

  2. Define the ParserFunctionsLite class.

  3. Define two functions in the ParserFunctionsLite.php file: wfParserFunctionsLite_Setup and wfParserFunctionsLite_Magic.

  4. Insert the following at the end of LocalSettings.php: include("extensions/ParserFunctionsLite/ParserFunctionsLite.php");

The Extension Object

In this example, a class is defined instead of a function. The class has two member functions that will be registered as callback functions for two different parser functions: ifhook and ifeq:

# In this example, a class is used rather than simply a function.
class ExtParserFunctionsLite {


 function clearState() {
         return true;
}
function ifHook( &$parser, $test = ", $then = ", $else = " ) {
         if ( $test !== " ) {
                return $then;
         } else {
                return $else;
         }
}

 function ifeq( &$parser, $left = ", $right = ", $then = ", $else = " ) {
         if ( $left == $right ) {
                return $then;
         } else {
                return $else;
         }
 }
}

The Setup Function

Because this extension is based on a class, the setup function takes a slightly different structure than the previous example. Before setting the hooks, the ExtParserFunctionsLite class needs to be instantiated. Then, when the setFunctionHook method is called, a reference to the object, and the function, is passed:

$wgExtensionFunctions[] = 'wfParserFunctionsLite_Setup';

function wfParserFunctionsLite_Setup() {
 global $wgParser, $wgMessageCache, $wgExtParserFunctionsLite,
   $wgMessageCache, $wgHooks;

  # Instantiate the object
 $wgExtParserFunctionsLite = new ExtParserFunctionsLite;

# Since a class is being used, the function to call needs to be passed to
# the setFunctionHook method uding the setup phase.
 $wgParser->setFunctionHook( 'if', array( &$wgExtParserFunctionsLite, 'ifHook' ) );
 $wgParser->setFunctionHook( 'ifeq', array( &$wgExtParserFunctionsLite, 'ifeq' ) );
 $wgHooks['ParserClearState'][] = array( &$wgExtParserFunctionsLite, 'clearState' );
}

The Magic Function

In the previous example, the magic function didn't have messages in different languages, so the function call was simple. In this example, translations are available, so the magic function includes the ParserFunctionsLite.i18n.php file, which includes the translations, and then loops through the array of translations looking for the ones that are intended for the current language, as referenced in $langCode:

$wgHooks['LanguageGetMagic'][]       = 'wfParserFunctionsLite_Magic';

function wfParserFunctionsLite_Magic( &$magicWords, $langCode ) {
 require_once( dirname( __FILE__ ) . '/ParserFunctionsLite.i18n.php' );
foreach( efParserFunctionsLite_Words( $langCode ) as $word => $trans )
  $magicWords[$word] = $trans;
 return true;
}

ParserFunctionsLite.i18.php

The translations are defined as follows:

<?php

/**
 * Get translated magic words, if available
 *
 * @param string $lang Language code
 * @return array
 */
function efParserFunctionsLite_Words( $lang ) {
 $words = array();

 /**
  * English
  */
 $words['en'] = array(

         'if'           => array( 0, 'if' ),
         'ifeq'         => array( 0, 'ifeq' ),
 );

 /**
  * Farsi-Persian
  */
 $words['fa'] = array(
         'if'          => array( 0, 
ParserFunctionsLite.i18.php
'if' ), 'ifeq' => array( 0,
ParserFunctionsLite.i18.php
'ifeq' ), ); /** * Hebrew */ $words['he'] = array( 'if' => array( 0,
ParserFunctionsLite.i18.php
'if' ), 'ifeq' => array( 0,
ParserFunctionsLite.i18.php
'ifeq' ), ); /** * Indonesian */ $words['id'] = array( 'if' => array( 0, 'jika', 'if' ), 'ifeq' => array( 0, 'jikasama', 'ifeq' ), ); # English is used as a fallback, and the English synonyms are # used if a translation has not been provided for a given word
return ( $lang == 'en' || !isset( $words[$lang] ) )
         ? $words['en']
         : array_merge( $words['en'], $words[$lang] );
}

Messages

The translation process requires some explanation. System messages are strings that are localized for the language that is set as the default language of the site, or the selected language of the user. There is a global object, $wgMessageCache, that contains all the messages available for the current language. It is an instance of the MessageCache class, and when it is instantiated it loads the messages that are defined in the message files in the /languages/messages/ directory. The messages themselves are loaded into an associative array called $magicWords, which maps string keys to their translated value.

MediaWiki provides developers with a way to add messages to the $wgMessageCache object without having to modify the underlying message files, and this is what is happening in the previous function.

In this example, the words that are added to the $wgMessageCache object depend upon the value passed in $langCode. The ParserFunctionsLite.php file contains messages in four languages: English, Farsi-Persian, Hebrew, and Indonesian. If the current language is English, then the messages for English are added to the $magicWords array. Otherwise, if it is Farsi-Persian, then the Farsi-Persian words are added, and so on. If no word is defined, or if the language is not defined, then the default fallback is English.

The $magicWords Array

Once the words are in the array, they are accessed by referencing the key. The most common way to retrieve a value is with the wfMsg global function:

function wfMsg( $key )

To use this method, you pass it the string key for the message, which is usually a lowercase version of the English translation. To illustrate, suppose the following magic word has been added to the $magicWords array:

$magicWords['example'] = array( 0, 'example' );

In this case, the key is 'example', and it is associated with the array(0, 'example'). The first digit indicates whether the word is case sensitive or not (in this case, it isn't), and the next element in the array is the translated word. In this example, the translated word is "example" too. This means that calling the function wfMsg('example') will return the string 'example'. Even though with English words it seems redundant to jump through this hoop, it is important conceptually to remember that you are getting a translation of the word associated with the key.

The Complete Extension

The complete extension is as follows:

<?php

if ( !defined( 'MEDIAWIKI' ) ) {
 die( 'This file is a MediaWiki extension, it is not a valid entry point' );
}
$wgExtensionFunctions[] = 'wfParserFunctionsLite_Setup';

$wgExtensionCredits['parserhook'][] = array(
 'name' => 'ParserFunctionsLite',
 'url' => 'Original code http://meta.wikimedia.org/wiki/ParserFunctions',
 'author' => 'Based on ParserFunctions.php by Tim Starling',
 'description' => 'A scaled down version of ParserFunctions.php',
);

$wgHooks['LanguageGetMagic'][]       = 'wfParserFunctionsLite_Magic';

class ExtParserFunctions {
 var $mExprParser;
 var $mTimeCache = array();
 var $mTimeChars = 0;
 var $mMaxTimeChars = 6000; # ~10 seconds

 function clearState() {
         $this->mTimeChars = 0;
         return true;
}


 function ifHook( &$parser, $test = ", $then = ", $else = " ) {
         if ( $test !== " ) {
                return $then;
         } else {
                return $else;
         }
}

 function ifeq( &$parser, $left = ", $right = ", $then = ", $else = " ) {
         if ( $left == $right ) {
                return $then;
         } else {
                return $else;
        }
 }
}

function wfParserFunctions_Setup() {
 global $wgParser, $wgMessageCache, $wgExtParserFunctions,
 $wgMessageCache, $wgHooks;

 $wgExtParserFunctionsLite = new ExtParserFunctionsLite;

 $wgParser->setFunctionHook( 'if', array( &$wgExtParserFunctionsLite, 'ifHook' ) );
 $wgParser->setFunctionHook( 'ifeq', array( &$wgExtParserFunctionsLite, 'ifeq' ) );
 $wgHooks['ParserClearState'][] = array( &$wgExtParserFunctionsLite, 'clearState' );
}

function wfParserFunctionsLite_Magic( &$magicWords, $langCode ) {
 require_once( dirname( __FILE__ ) . '/ParserFunctionsLite.i18n.php' );
foreach( efParserFunctionsWords( $langCode ) as $word => $trans )
         $magicWords[$word] = $trans;
 return true;
}

Hook Extensions

All of the previous examples showed you how to use hooks to add functionality to wikitext, but that is not the only use of hooks when writing extensions. You can also use hooks to customize the user interface, change the way the site functions, and even write your own actions. There are too many possible hooks to demonstrate all of them, but this section shows you how to use two different hooks in order to give you an idea of all that you can do.

As with all the other extensions, you need to create a file in the extensions directory and define your functions there. In addition, you need to include a reference to the file in LocalSettings.php.

ParserBeforeStrip Hook

The ParserBeforeStrip hook is called by the parser object right before the first step of the parsing process that strips out <nowiki> tags, and so on. In other words, this function is called when the raw wikitext is available and has not been modified at all by the parsing process. The ParserBeforeStrip hook sends a reference to the parser object, the raw wiki text, and the strip_state object. Unlike the other examples, which are only executed when a page author includes the XML tag or parser function in their page, this function executes anytime wikitext is parsed (which includes parts of a page other than just the article content).

Setting up a simple hook like this is simpler than parser functions and XML tag extensions. All you need to do is define the function and then register it with the hook itself. The following hook will serve as a filter that replaces the abbreviations afaik, btw, and imho with the phrases they represent, regardless of whether the user wants it changed or not. The function does a simple string replace with the PHP function str_replace:

$wgHooks['ParserBeforeStrip'][] = 'myfilter' ;

function myfilter ( &$parser, &$text, &$strip_state ) {
 # This hook performs a simple text replacement before any of
 # the raw wikitext is parsed.
 $from = array("afaik", "btw", "imho");
 $to   = array("As far as I know", "By the way", "In my humble opinion");
 $text = str_replace($from, $to, $text);
}
?>

With this hook installed, the text "afaik, everyone liked the movie. I thought it stunk, imho" is translated into "As far as I know, everyone liked the movie. I thought it stunk, In my humble opinion."

EditPage::showEditForm:initial Hook

This hook is a little more complete than the previous hook. It enables you to add additional content to the edit page when a user is editing a document. The hook passes a reference to the EditPage object, which has defined five variables for the express purpose of enabling developers to leverage this hook.

You will find a reference to them in the EditPage.php file:

# Placeholders for text injection by hooks (must be HTML)
 # extensions should take care to _append_ to the present value
 public $editFormPageTop; // Before even the preview
 public $editFormTextTop;
 public $editFormTextAfterWarn;
 public $editFormTextAfterTools;
 public $editFormTextBottom;

The hook simply takes a reference to the form object and uses that to assign values to the object's member variables:

<?php
$wgHooks['EditPage::showEditForm:initial'][] = 'myformhook' ;

function myformhook( &$form ) {
         # EditForm.php specifies the following variables for use
         # in hooks such as this one.
         $form->editFormPageTop = "<h2>Form Page Top</h2>";
         $form->editFormTextTop ="<h3>Form Text Top</h3>";
         $form->editFormTextAfterWarn = "<h4>Form After Warn</h4>";
         $form->editFormTextAfterTools = "<h5>Form After Tools</h5>";
         $form->editFormTextBottom = "<h5>Form Text Bottom</h5>";
}

With this hook in place, you can edit any page and the new text will be presented on that page, as shown in Figure 9-1.

Text inserted into the edit page by myformhook

Figure 9.1. Text inserted into the edit page by myformhook

Special Pages

Special pages are dynamic pages, and as such are different animals than XML tag extensions or parser functions. They share a lot in common, however, and the steps required to create a special page will be familiar to those of you who are already familiar with parser functions and tag extensions.

While not strictly necessary, MediaWiki encourages the convention of using three distinct PHP files for the development of a special page. In this case, the files are as follows:

  • SpecialPageExample.php: The functions that register the special page

  • SpecialPageExample_body.php: The SpecialPage subclass is defined here, and the execute method is over-ridden.

  • SpecialPageExample.i18n.php: The localization for the message cache

Because special pages are dynamic pages, their content is often (but not necessarily) the output of a query against the database. The following example shows a special page that displays information about how many times it has been displayed.

SpecialPageExample.php

This file sets up the special page, but the process is quite a bit different from what takes place in other extensions. There is a global variable, $wgAutoLoadClasses, that contains references to PHP files whose contents should be loaded into memory when a request is received. This file adds the SpecialPageExample class to the list of classes that should be autoloaded. It also adds a reference to this page to the $wgSpecialPages global variable, so that the special page will be listed with the others:

<?php
# Not a valid entry point, skip unless MEDIAWIKI is defined
if (!defined('MEDIAWIKI')) {
        echo <<<EOT
To install my extension, put the following line in LocalSettings.php:
require_once( "$IP/extensions/SpecialPageExample/SpecialPageExample.php" );
EOT;
        exit( 1 );
}

$wgAutoloadClasses['SpecialPageExample'] =
   dirname(__FILE__) . '/SpecialPageExample_body.php';
$wgSpecialPages['SpecialPageExample'] = 'SpecialPageExample';
$wgHooks['LoadAllMessages'][] = 'SpecialPageExample::loadMessages';

?>

SpecialPageExample_body.php

The SpecialPage class is a subclass in this file, and the execute method is overridden. This is where the content for the special page is generated. Because special pages are dynamic pages, one common use for them is to publish updated information directly from a database. The following example does just that, making a database call to the site_stats table in order to find out how many times this page has been accessed (I suppose it is a somewhat narcissistic special page, as its only reason for being is to report on itself):

<?php
class SpecialPageExample extends SpecialPage
{
        function SpecialPageExample() {
                SpecialPage::SpecialPage("SpecialPageExample");
                self::loadMessages();
}

        function execute( $par ) {
                global $wgRequest, $wgOut;

                $this->setHeaders();

                # If parameters were passed in the query string, they
                # can be accessed through the global $wgRequest object.
                $param = $wgRequest->getText('param'),

                # MediaWiki uses the convention that references to the database
                # should be called $dbr if it is for read access and $dbw
                # for write access. In most cases, you would be reading
                # from the database in a special page.
                $dbr =& wfGetDB( DB_SLAVE );
                $fields = array(
                        'ss_row_id',
                        'ss_total_views',
                        ) ;
                $res = $dbr->select('site_stats',
                        $fields, ", 'SpecialPageExample', array('LIMIT'=> 1) );
                while ( $row = $dbr->fetchObject( $res ) ) {
                  $wgOut->addHTML( "<p>Total Views: " . $row->ss_total_views );
   }
                $dbr->freeResult( $res );

                # Output
                $wgOut->addWikiText( wfMsg( 'specialpageexample' ) );
}

        function loadMessages() {
                static $messagesLoaded = false;
                global $wgMessageCache;

                if ( $messagesLoaded ) return true;
                $messagesLoaded = true;
                require( dirname( __FILE__ ) . '/SpecialPageExample.i18n.php' );
                foreach ( $allMessages as $lang => $langMessages ) {
                        $wgMessageCache->addMessages( $langMessages, $lang );
   }

                return true;
}
}
?>

SpecialPageExample.i18n.php

The i18n page for special pages works exactly like the equivalent page in the previous parser function example:

<?php
$allMessages = array(
        'en' => array(
                'specialpageexample' => 'Special Page Example'
        )
);
?>

Summary

In this chapter, you have learned how to create your own extensions, including how to extend wikitext by adding new XML tag extensions and parser functions. You have also learned how to create your own special pages.

In the next chapter, you will learn how to interact with MediaWiki programmatically through MediaWiki's API, as well as how to use the pywikipedia.py bot to automate some of MediaWiki's processes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.150.56