Customizing MediaWiki

Creating and installing extensions

MediaWiki is the popular wiki engine behind sites like Wikipedia. Its power and flexibility makes it an excellent choice for a community-driven knowledge base. The ease of developing various extensions for MediaWiki is one of the sources of this flexibility. This article will show you how to create different types of extensions for MediaWiki: wiki variables, special pages, and new tags. You'll also get a quick overview of what you'll need to do to create skins for MediaWiki.

Share:

Chris Herborth (chrish@pobox.com), Freelance Writer, Author

Photo of Chris HerborthChris Herborth is an award-winning senior technical writer with more than 10 years of experience writing about operating systems and programming. When he's not playing with his son, Alex, or hanging out with his wife, Lynette, he spends his time designing, writing, and researching (that is, playing) video games.



06 July 2010

Also available in Japanese Portuguese

Introduction

The MediaWiki application is probably best known for being the engine behind Wikipedia. Many people are finding that MediaWiki provides a usable environment for sharing information within workgroups and even entire organizations, as well as online communities. MediaWiki allows users to share information via blogs, wikis, and files. It also allows you to secure uploaded files, tag files for easy locating, and locate experts using a tag cloud. For more information, see Resources.

What if you want to introduce custom information that you don't want to manually update and insert into your wiki pages? What if you want to provide custom output formatting for a particular type of information? It's situations like these where MediaWiki makes sense, since you can easily add these site-specific features through the use of extensions.

Let's take a look at how you can create MediaWiki extensions that cooperate with different information sources while providing the data through the familiar user interface of the wiki page.


MediaWiki extensions

Extensions can add new tags to the wiki markup used to write articles, add new reporting and administrative features by creating special pages, change the look and feel of the wiki through formatting skins, and even integrate with external authentication methods (although authentication won't be covered in this article).

Extensions are written in PHP and make use of MediaWiki's various internal hooks, classes, and methods to get their jobs done efficiently. While you can develop and deploy MediaWiki using any supported Web server and your favorite PHP development environment, you will be using the following in this article:

  • Eclipse V3.5.2 — Support for a wide range of programming languages and environments
  • PHP Development Tools (PDT) V2.2.0 — A PHP plug-in for Eclipse
  • MAMP Pro V1.9 — A handy package for Mac OS X, which provides Apache, MySQL, and PHP in an isolated environment with a helpful GUI front end. Even though OS X ships with Apache and PHP installed, I opted to use this because the version of PHP in OS X V10.6 Snow Leopard (V5.3.1) apparently has a bug that prevents MediaWiki from operating correctly.
  • MediaWiki V1.15.3 — The current stable version of MediaWiki

You can find all these in the Resources section.

Before you jump into looking at the different kinds of extensions, let's take a quick look at the folder and file layout used by most of them. After that, you'll have a high-level overview of skin extensions to change the look and feel of your MediaWiki site. Next you'll create a special-page extension that produces some administrative statistics. Finally, you'll see how to add custom XML tag markup support you can take advantage of while writing wiki pages.


Extension anatomy

MediaWiki extensions get installed in the extensions directory in the main MediaWiki path. Most modern extensions are installed in their own directory and consist of three files (extension is the name of the extension):

  • extension/extension.php
  • extension/extension.body.php
  • extension/extension.i18n.php

The first file performs initialization and any setup tasks. The second file is the body of the extension; this is the working code that implements the extension. Finally, the last file contains internationalization (i18n is a common short form) strings. By abstracting your extension's message strings into the i18n file, you can provide localized versions of your extension for any of MediaWiki's supported locales (assuming you can find some help translating the text).

As an example, I've created a sort of Hello World extension called CHTimeStamp (see Downloads for the source code for this example and the other examples in this article). CHTimeStamp will insert the current date/time stamp whenever someone inserts {{CHSTAMP}} on a wiki page. It consists of the following files:

  • CHTimeStamp/CHTimeStamp.php
  • CHTimeStamp/CHTimeStamp.body.php
  • CHTimeStamp/CHTimeStamp.i18n.php
Figure 1. CHTimeStamp layout in Eclipse
Screenshot of the CHTimeStamp file structure in Eclipse

The CHTimeStamp extension adds a {{CHSTAMP}} variable to MediaWiki's markup. Whenever you put {{CHSTAMP}} into a page, it's replaced by a timestamp. Trivial and easy to follow, right? If you're curious, please take a look at the code (see Download); I'm only describing it in broad strokes here to introduce you to the general layout and conventions of MediaWiki extensions.

My CHTimeStamp.php registers the internationalization messages file, tells the wiki engine that it can find the CHTimeStamp class in CHTimeStamp.body.php and adds the CHTimeStamp::registerHooks method to the array of extension functions.

In CHTimeStamp.body.php, you define the CHTimeStamp class. If you look at the code, this is made up entirely of static methods, so it could have also been written as a series of functions without changing the extension's behavior. CHTimeStamp's registerHooks method registers static methods to create the {{CHSTAMP}} variable and to handle pages that use it.

Finally, in CHTimeStamp.i18n.php, I've created translations for the only static string in the extension: its description. With the help of Google Translate, CHTimeStamp supports French, German, and Spanish locales. But I hope the automated translations didn't turn my English into awkward (or inappropriate) blurbs!

After you've created or downloaded an extension, you'll need to install it into MediaWiki and activate it.


Installing an extension

Once you have an interesting or useful extension ready for your MediaWiki site, you'll want to install and enable it:

  1. Copy or unpack the extension into MediaWiki's extensions directory.
  2. Edit LocalSettings.php in MediaWiki's root directory. Using your favorite text editor, add lines to configure the new extension, then activate it using PHP's require_once() statement.

For example, to install CHTimeStamp, I've copied its CHTimeStamp directory to the extensions directory and added the following to LocalSettings.php: require_once( "$IP/extensions/CHTimeStamp/CHTimeStamp.php" );.

Check to make sure your extension has been loaded by visiting the wiki's Special:Version page. In addition to information about the MediaWiki version you're running, the Special:Version page lists the extensions that have successfully loaded.

Figure 2. Special:Version page showing CHTimeStamp
Screenshot of the Version page in MediaWiki shows that the CHTimeStamp extension is installed

Customizing the look and feel

MediaWiki takes advantage of PHP's ability to mix code and HTML markup to give you control over the look and feel of your wiki through the use of skins. Besides the main PHP code, a skin can include various CSS files and supporting images or JavaScript.

A skin generally consists of two PHP files, and a directory for additional support files. For example, the famous default skin, MonoBook, is made up of:

  • MonoBook.php — Main MonoBook skin code
  • MonoBook.deps.php — A fix for a bug in the APC opcode cache of PHP V5
  • monobook/ — Supporting CSS and graphics

The naming convention for skins is very strict, requiring SkinName.php, SkinName.deps.php and skinname (lowercase) as the support folder name.

Inside the skinname folder will be main.css for the skin's styling. Browser-specific stylesheet fixes belong here, as well, so you'll often find FF2Fixes.css, IE6Fixes.css, Opera6Fixes.css, etc. there.

SkinName.php will start off with some helpful metadata.

Listing 1. MediaWiki skin metadata
/**
 * [SkinName] skin
 *
 * @file
 * @ingroup Skins
 * @version [#].[#].[#]
 * @author [name] ([URL] / [E-Mail])
 * @license [URL] [name]
 */

Replace everything in square brackets with something suitable for your skin.

Next, you'll need to create a subclass of SkinTemplate and override the initPage method to indicate your skin's name, style, and template. Remember to replace SkinName and skinname with the name of your skin.

Listing 2. Extending SkinTemplate to provide a new skin
// inherit main code from SkinTemplate, set the CSS and template filter
class SkinSkinName extends SkinTemplate {
        function initPage( OutputPage $out ) {
                parent::initPage( $out );
                $this->skinname  = 'skinname';
                $this->stylename = 'skinname';
                $this->template  = 'SkinNameTemplate';
        }
}

Your skin's workhorse will be your QuickTemplate subclass.

Listing 3. Most of the work is done in the template
class SkinNameTemplate extends QuickTemplate {
...
        /**
         * Template filter callback for this skin.
         * Takes an associative array of data set from a SkinTemplate-based
         * class, and a wrapper for MediaWiki's localization database, and
         * outputs a formatted page.
         */
        public function execute() {
                global $wgUser, $wgSitename;
                $skin = $wgUser->getSkin();
 
                // retrieve site name
                $this->set( 'sitename', $wgSitename );
 
                // suppress warnings to prevent notices about missing indexes 
                // in $this->data
                wfSuppressWarnings();
 
                /* compose XHTML output */
 
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
...

Inside the QuickTemplate subclass, you'll override methods to format and style things like category listings and cross-references the way you want. This class's execute method lays out the entire page as an XHTML document, giving you complete control over page organization and styling.

You can't go into XHTML and CSS page layout and styling here, so take a look in your MediaWiki's skins folder for examples you can try out right away.


Adding special pages

Special pages in MediaWiki are generated on demand to do something specific and possibly useful for the wiki, such as letting you edit the system-wide message text, list the installed extensions, or get a list of external links.

Unless you specify otherwise, your special page will be available to anyone and show up in the Special:SpecialPages list of special pages. You can also set up your special page so it can be included inline on a page using {{Special:YourPageName}} syntax.

Like other extensions, special pages are installed as a directory into the extensions folder. They generally consist of four files:

  • specialpage/specialpage.php — The extension's setup file
  • specialpage/specialpage.aliases.php — Aliases for the special page's name
  • specialpage/specialpage.body.php — The main code for the special page
  • specialpage/specialpage.i18n.php — Internationalization strings for the special page

For example, if you're creating a special page named CHStats, its layout will look like Figure 3.

Figure 3. CHStats in Eclipse
Screenshot shows the CHStats file hierarchy in Eclipse

The code in CHStats.php adds credits for the extension, registers the aliases, body and i18n files, and tells the wiki engine to autoload the CHStats class when it's needed.

Listing 4. Setting up the CHStats special page
<?php
# This is not a valid entry point to MediaWiki.
if( !defined( 'MEDIAWIKI' ) ) {
    echo <<<EOT
To install CHStats, put the following line in LocalSettings.php:
require_once( "\$IP/extensions/CHStats/CHStats.php" );
EOT;
    exit( 1 );
}

# Take credit for this extension.
$wgExtensionCredits['specialpage'][] = array(
    'name' => 'CHStats',
    'author' => 'Chris Herborth (chrish@pobox.com)',
    'url' => 'http://www.pobox.com/~chrish/CHStats/',
    'description' => 'A simple special page demonstration, showing some DB stats.',
    'descriptionmsg' => 'chstats-desc',
    'version' => '1.0.0',
);
 
$dir = dirname( __FILE__ ) . '/';

# Register the extension's main code/class.
$wgAutoloadClasses['CHStats'] = $dir . 'CHStats.body.php';

# Register our internationalization files.
$wgExtensionMessagesFiles['CHStats'] = $dir . 'CHStats.i18n.php';
$wgExtensionAliasesFiles['CHStats'] = $dir . 'CHStats.aliases.php';

# Let MediaWiki know about the new special page.
$wgSpecialPages['CHStats'] = 'CHStats';

?>

In CHStats.body.php, you create a new class, CHStats, which extends the SpecialPage class. In the constructor, you initialize the parent class, then load your internationalization messages by calling wfLoadExtensionMessages. Check the Special Pages developer's guide page (see Resources) for more information about the SpecialPage class constructor, which will let you restrict access, hide the page, etc.

The override for the execute method does the work of generating the page.

Listing 5. Generating the CHStats special page
# This is where the special page's output is created.
function execute( $par ) {
    global $wgOut;

    # Initialize the output page.
    $this->setHeaders();

    # Do stuff.
    $wgOut->addWikiText( "Some stats about this '''Wiki''':" );

    $db = wfGetDB( DB_SLAVE );
    // SELECT ... FROM site_stats
    $result = $db->select( 'site_stats', 
                           array( 'ss_total_views', 'ss_total_edits', 
'ss_total_pages', 'ss_users' ) );
    $statList = array();
    foreach( $result as $row ) {
        $statList[] = '* Total page views: ' . $row->ss_total_views;
        $statList[] = '* Total page edits: ' . $row->ss_total_edits;
        $statList[] = '* Total # of users: ' . $row->ss_users;
    }

    $wgOut->addWikiText( implode( "\n", $statList ) );

    $wgOut->addWikiText( "That's it." );
}

In the execute method, the $par argument is the sub-page. For example, if you loaded Special:CHStats/foo, $par would be set to foo (its name is apparently a historical oddity).

First you use the setHeaders method to set up the page header, then call $wgOut->addWikiText to write some markup to the output stream. You can also use $wgOut->addHTML to write formatted HTML directly, but I'm using wiki markup in the output. Refer to the Special Pages developer's guide for more information about these (see Resources), and how to properly add wiki markup and/or HTML to special pages that can be included inline in other pages.

The CHStats page uses the wfGetDB function to get a reference to the database (use DB_SLAVE for read-only operations, and DB_MASTER for write operations). Then you select several fields from the site_stats database and format the results as a bulleted list using wiki markup.

You can see what the output of this special page looks like in Figure 4.

Figure 4. CHStats in action
Screenshot shows the CHStats listing lf statistics, including page views, page edits and total users

The internationalization strings found in CHStats.i18n.php consist of an array, with one entry per supported language (English, French, German, and Spanish, in this case). In each entry is an array mapping string IDs to what is hopefully their localized text. Google Translate was used for the French, German, and Spanish bits.

CHStats.aliases.php has a similar array containing the localized versions of the CHStats page name itself. This lets French users (for example) access the page as Spécial:StatsCH.


Adding tags

Another popular way to extend MediaWiki is by adding support for new XML tags to the markup. These tags can produce different output based on the tag attributes or the content and are useful for inserting inline HTML or even large blocks of formatted output.

Tag extensions are installed in their own directory under the extensions folder and use the three-file convention described at the start of this article. Let's look at a simple one I've cooked up named CHUser:

  • CHUser/CHUser.php — Extension setup
  • CHUser/CHUser.body.php — Main extension code
  • CHUser/CHUser.i18n.php — Internationalization data

The extension setup going on in CHUser.php is similar to what you've already seen, although it uses the $wgHooks array to add the extension's init method to the ParserFirstCallInit list. CHUser::init will be called the first time it's used.

Listing 6. Setting up the tag extension
# Let MediaWiki know about the new tag.
$wgHooks['ParserFirstCallInit'][] = 'CHUser::init';

Inside CHUser.body.php, the init method registers two tags: <chuser> and <bz> (see Listing 7). This extension provides two different tags in just one extension. You could easily combine all of the extensions discussed in this article if you wanted to, there's no requirement (other than your own sanity) to split things up.

Listing 7. Registering the <chuser> and <bz> tags
public static function init( &$parser ) {
    # Add our <chuser> tag handler, the continue.
    $parser->setHook( 'chuser', 'CHUser::render' );
    $parser->setHook( 'bz', 'CHUser::renderBugzilla' );
    return true;
}

Whenever the wiki markup engine encounters a <chuser> tag, it will call the CHUser::render method, and <bz> tags will call CHUser::renderBugzilla.

The <chuser> rendering method selects the specified user's full name and e-mail address, and formats it as a mailto: link wrapped around the user's full name (if there is one). You can see from Listing 8 that most of the logic is simply handling cases where the full name or e-mail address isn't present in the database (such as for the Admin account on my wiki).

Listing 8. Handling <chuser>UserName</chuser>
public static function render( $input, $args, $parser, $frame ) {
    $user = mysql_escape_string( $input );

    $db = wfGetDB( DB_SLAVE );
    // SELECT ... FROM user
    $result = $db->select( 'user', 
                           array( 'user_real_name', 'user_email' ),
                           'user_name = \'' . $user . '\'' );
    $mailtos = array();
    foreach( $result as $row ) {
        $thisUser = '<span class="user">';

        if( $row->user_email ) {
            $thisUser = $thisUser . '<a href="mailto:' 
. htmlspecialchars( $row->user_email ) . '">';
        }

        if( $row->user_real_name ) {
            $thisUser = $thisUser . htmlspecialchars( $row->user_real_name ); 
        } else {
            $thisUser = $thisUser . htmlspecialchars( $input );
        }

        if( $row->user_email ) {
            $thisUser = $thisUser . '</a>';
        }
        
        $thisUser = $thisUser . '</span>';

        $mailtos[] = $thisUser;
    }

    return implode( ", ", $mailtos );
}

Listing 9 shows you how the <bz> tag uses its ID attribute to link to a specified bug report in the MediaWiki Bugzilla database. This shows how easy it is to work with external data sources. If you wanted to get fancy you could use Ajax to load the bug report instead of linking to it, and display some of its data instead of the link.

Listing 9. <bz id="number" /> links to the MediaWiki Bugzilla
public static function renderBugzilla( $input, $args, $parser, $frame ) {
    $retval = '';
    if( $args['id'] ) {
        $bzId = htmlspecialchars( $args['id'] );
        
        $retval = '<a href="https://bugzilla.wikimedia.org/show_bug.cgi?id=' 
. $bzId . '">MediaWiki Bug #' . $bzId . '</a>';
    } else {
        $retval = '<span class="error">No bug ID specified</span>';
    }
    
    return $retval;
}

You can see these two tags in action in Figure 5.

Figure 5. Your extensions in action
Screenshot of the CHUser extension showing the screen name and real names and real names of the users

Listing 10 shows you what the wiki markup looks like for that section of the page.

Listing 10. The wiki markup demonstrating these extensions
== Extension testing ==

If this is working, we should see a timestamp: {{CHSTAMP}}

CHUser info:

* '''Admin''': <chuser>Admin</chuser>
* '''Chrish''': <chuser>Chrish</chuser>

<bz id="1024" />

As you can see from the CHUser extension, adding support for custom XML tags is easy and lets you do almost anything. All PHP features and MediaWiki services are available to you, so you can pull data from (or send data to) external systems, change your behavior based on the current user's credentials and permissions or insert JavaScript to run tasks directly in the viewer's browser. The possibilities and endless and limited only by your specific needs and requirements.


Summary

In this article, you were introduced to several extension techniques supported by MediaWiki, an open source wiki system similar to commercial wiki software like Lotus® Connections.

You were shown MediaWiki's extension conventions and shown how to create a simple wiki variable extension as an introduction to writing MediaWiki extensions. An overview of MediaWiki's skinning features help you get started creating your own customized site layout.

Special pages are often used to generate information, whether retrieved from a database or other source, so you made one that displays some of the system's statistics. You then created a couple of custom XML tags that could be included in wiki markup on any page.


Download

DescriptionNameSize
Article source codeos-mediawiki-CH.Examples.zip7KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=498779
ArticleTitle=Customizing MediaWiki
publish-date=07062010