Views
Extending MediaWiki
From Shadowfax
One of the beauties of open source software is that you get the source code which means you can modify it. Modifying someone else's software, however, is a non-trivial exercise, and can lead to problems down the line, particularly when you decide to upgrade. Fortunately the people who wrote MediaWiki are aware of this and have created ways to extend the software without having to dabble in the core.
Once you get into it, extending MediaWiki is relatively easy. The biggest hurdle is getting your head around where to start. There is a lot of MediaWiki documentation, but (sadly as is often the case with Open Source) some of it is quite sparse, examples are few and far between and the entry points are not obvious. I think that the later may be more a Google issue than a MediaWiki issue. With so many wikis out there google's results can be hard to navigate when you have a specific query.
Contents |
Some Useful Entry Points
The best place to start is the MediaWiki Developer Hub. It has good links into a number of introductory manuals and details the ways of extending MediaWiki.
Another key resource is the MediaWiki Object Model. The latter is a useful (but highly frustrating!) primer. Useful, because it lists pretty much every object. Frustrating because many functions give relatively little to go on, both in terms of what they achieve and how you access to them. Also intriguing in its absence is any reference to the MediaWiki version to which each entry pertains. This can be very important if you are modding an older version as not all the functions are present or their form may have changed!
Even with the documentation, setting out can be rather daunting. MediaWiki is very big and it is not always clear what is important and what is not. The following sections (by no means complete) should give a taster on how to get started.
Some useful Objects
Title
The title object holds details about an entry's title, namespace, URL and attributes (eg is it protected, does it have a talk page and so on). The title of the current article is always accessible via the $wgTitle variable.
If you want to deal with a new title object rather than the current page's title it can be accessed directly. eg
$title = Title::newFromText ($pageName, $namespaceID);
Article
The article object holds more details about the actual content of the article. It is accessible via the $wgArticle variable.
Parser
The parser object holds all the functions that MediaWiki uses to turn wiki markup into html, and can be a useful way of safely coding links or generating content on the fly. It is accessible via the $wgParser variable.
User defined markup
The parser also contains the methods which enable you to create your own tags. For example this rather trivial extension
if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) { $wgHooks['ParserFirstCallInit'][] = 'myExtensionInit'; } else { $wgExtensionFunctions[] = 'myExtensionInit'; } function myExtensionInit() { global $wgParser; $wgParser->setHook( 'mytag', 'doMyTag' ); return true; } function doMyTag( $input, $args, $parser ) { return htmlspecialchars( "Tag says: " . $input); }
would render the tag <mytag>hello</mytag> as "Tag says: hello"
Hooks
It doesn't stop with just adding your own tags, however. The parser also holds an array of predefined hooks which you can attach code to to extend the behaviour of MediaWiki. These are like the events an object-oriented progragramming language and are triggered on defined activities. See the Hook Manual for a full list of the hooks
For example, if you wanted to place some code after a user clicks to delete a page but before the page is actually deleted you csn attach an event handler to the hook ArticleDelete. The return value you send back influences how the activity proceeds If you return true the action will continue as expected, if you return false it will not. So in the case of ArticleDelete if you return false and have not deleted the article in your extension it will not be deleted.
$wgHooks['ArticleDelete'][] = array('myArticleDelete'); function myArticleDelete(&$article, &$user, &$reason, &$error) { // your code here return true; }
User
The user object holds details relating to the user's session and setup. It is accessible via the $wgUser variable. The user object is also a handy hop-off point to getting access to the current skin object. The skin object inherits from another useful object, the linker object, which has all sorts of useful methods to do with creating and managing links. For example:
// Function to build a url from text function linkFromText($page, $displayText, $namespaceID=0) { global $wgUser; $skin = $wgUser->getSkin(); $title = Title::newFromText ($page, $namespaceID); if (!is_null($title)) return $skin->makeKnownLinkObj($title, $displayText, ""); else return ""; }
Database
You can access the MediaWiki database directly but it is not recommended (for compatability and security reasons). Instead there is a rather arcane API to access the database via the DatabaseBase class. A good starting place for this is the Database Access Manual.
The database class handles the connection to the database, and if accessed via the recommended methods it also handles escaping to minimise the risk of SQL insertion attacks. To get access to the database you first need to call wf_GetDB and tell it if you want a read or write connection. The manual has some pretty dire warnings about attempting to write via a read connection, so it is probably safest to follow MediaWiki conventions!
If you know SQL then using the API may come as something of a surprise. The following snippet is an example of a simple query using the API
//Get a read connection to the database $dbr = wfGetDB( DB_SLAVE ); // Equivalent SQL Query to this would be // SELECT page_id FROM mywiki_page WHERE page_title = "$page" and page_namespace = $nsID $pgID = $dbr->selectField( 'page', 'page_id', array("page_title" => $page, "page_namespace" => $nsID), __METHOD__ )
Using this mechanism to do simple SQL queries is relatively straightforward. I've found doing table joins to be a little more tricky. For example, to submit the following SQL
SELECT old_text FROM `mywiki_page` LEFT JOIN `mywiki_revision` ON rev_page=page_id LEFT JOIN `mywiki_text` ON old_id=rev_text_id WHERE page_title = 'Main_Page' AND page_namespace = '0' ORDER BY rev_id DESC LIMIT 1
the MediaWiki syntax looks like
select(array ("revision", "text", "page",), "old_text", array( "page_title" =>$pgName, "page_namespace" => $ns), __METHOD__, array ("ORDER BY"=>"rev_id desc limit 1"), array("text" => array ("LEFT JOIN", "old_id=rev_text_id" ), "revision" => array( 'LEFT JOIN', 'rev_page=page_id')));
It does work - but the ordering of the array clauses feels very non-intuitive to me. When creating queries of this type it is worth setting $wgShowSQLErrors = true in LocalSettings.php as this will show the full SQL that is being generated when it is an error.
Other Resources
Developing software seldom goes to plan. There is a useful article called how to debug which details ways of finding out why that wonderful feature you've just coded is behaving almost (but not entirely) unlike you'd intended.
Leave your comment