Difference between revisions of "MediaWiki code snippets"

From Organic Design wiki
(Get a list of articles using a template: simplify with foreach)
(For MediaWiki 1.18 and above: moer conside)
 
(47 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
== Getting in to MediaWiki coding ==
 
== Getting in to MediaWiki coding ==
Here's a useful checklist of things to have set up before starting with MediaWiki development; see also [[MW:How to debug]] and [[MW:Developer hub]].
+
Here's a useful checklist of things to have set up before starting with MediaWiki development;
 
*Full browser and shell access to a running instance of every major version you're likely to work with
 
*Full browser and shell access to a running instance of every major version you're likely to work with
 
*Local access to the source code for all those versions
 
*Local access to the source code for all those versions
 
*Use ''wfDebugDieBacktrace()'' to stop code and report the call stack (see better way below)
 
*Use ''wfDebugDieBacktrace()'' to stop code and report the call stack (see better way below)
 
*Use ''debug=1'' in the query-string when making requests to MediaWiki. This will prevent any caching or code minification
 
*Use ''debug=1'' in the query-string when making requests to MediaWiki. This will prevent any caching or code minification
 +
*Use ''uselang=qqx'' in the query-string to show all the keys of messages used in the page in place actual message content
 
*A good text editor with regular expression support file search capability (we use [http://geany.uvena.de/ Geany] which runs on most platforms)
 
*A good text editor with regular expression support file search capability (we use [http://geany.uvena.de/ Geany] which runs on most platforms)
 
*Tools or code for being able to stop the php code and output the necessary items in the scope
 
*Tools or code for being able to stop the php code and output the necessary items in the scope
Line 11: Line 12:
 
*Bookmarks to code snippets and examples (within your own wiki, http://mediawiki.org, other sites and in the mediawiki code)
 
*Bookmarks to code snippets and examples (within your own wiki, http://mediawiki.org, other sites and in the mediawiki code)
 
*Your own repository of snippets for achieving common objectives in the mediawiki runtime environment
 
*Your own repository of snippets for achieving common objectives in the mediawiki runtime environment
 +
 +
Other useful MediaWiki.org pages for getting started:
 +
*[[MW:Developer hub]]
 +
*[[MW:Manual:Code]]
 +
*[[MW:Manual:Hooks]]
 +
*[[MW:Manual:Developing extensions]]
 +
*[[MW:Manual:MediaWiki architecture]]
 +
*[[MW:How to become a MediaWiki hacker]]
 +
*[[MW:How to debug]]
  
 
== General PHP coding tips ==
 
== General PHP coding tips ==
 
Remember to set full reporting of errors and exceptions with the following addition to LocalSettings.php
 
Remember to set full reporting of errors and exceptions with the following addition to LocalSettings.php
{{code|<php>ini_set("display_errors", "on");
+
<source lang="php">
ini_set('error_reporting', E_ALL);
+
ini_set( 'display_errors', 'on' );
 +
ini_set( 'error_reporting', E_ALL );
 
$wgShowExceptionDetails = true;
 
$wgShowExceptionDetails = true;
$wgShowSQLErrors = true;</php>}}
+
$wgShowSQLErrors = true;
 +
</source>
  
 +
A simple way to exit and output the call stack at a particular point in any PHP code is to use this:
 +
<source lang="php">
 +
$e = new Exception;
 +
var_dump( $e->getTraceAsString() );
 +
</source>
  
A simple way to exit and output the call stack at a particular point in any PHP code is to use this:
+
To get a full trace log of functions called in a request, install ''xdebug'' and add the following configuration to your ''php.ini'':
{{code|<php>$e = new Exception;
+
<source>
var_dump($e->getTraceAsString());</php>}}
+
zend_extension=xdebug.so
 +
xdebug.auto_trace=On
 +
xdebug.trace_output_dir=/var/www/xdebug
 +
</source>
 +
For more details about how to use the ''xdebug'' package, see [http://devzone.zend.com/1135/tracing-php-applications-with-xdebug/ this tutorial].
  
 
== Articles ==
 
== Articles ==
=== Get article content ===
+
=== Get wikitext content of an article ===
{{code|<php>
+
<source lang="php">
$title    = Title::newFromText($titleText);
+
$wikitext = WikiPage::factory( Title::newFromText( $titleText ) )->getContent()->getNativeData();
$article  = new Article($title);
+
</source>
$wikitext = $article->getContent();
 
</php>}}
 
  
 
=== Edit or create an article ===
 
=== Edit or create an article ===
Line 41: Line 60:
 
*'''EDIT_DEFER_UPDATES:''' Defer some of the updates until the end of index.php
 
*'''EDIT_DEFER_UPDATES:''' Defer some of the updates until the end of index.php
 
*'''EDIT_AUTOSUMMARY:''' Fill in blank summaries with generated text where possible
 
*'''EDIT_AUTOSUMMARY:''' Fill in blank summaries with generated text where possible
{{code|<php>
+
<source lang="php">
$title    = Title::newFromText($titleText);
+
WikiPage::factory( Title::newFromText( $titleText ) )->doEditContent(
$article  = new Article($title);
+
new WikitextContent( $wikiText ),
$article->doEdit($text, $summary, EDIT_UPDATE|EDIT_MINOR);
+
$summary,
</php>}}
+
EDIT_UPDATE | EDIT_MINOR
*'''Note:''' ''$wgUser'' must be set before calling this function.
+
);
 +
</source>
  
 
=== Image URLs ===
 
=== Image URLs ===
 
Given the name of an image page, obtain the full URL of the image.
 
Given the name of an image page, obtain the full URL of the image.
{{code|<php>
+
<source lang="php">
$title = Title::newFromText($v, NS_IMAGE);
+
$title = Title::newFromText( $v, NS_IMAGE );
$image = Image::newFromTitle($title);
+
$image = Image::newFromTitle( $title );
if ($image && $image->exists()) {
+
if( $image && $image->exists() ) {
$url     = $image->getURL();                     # Gets the URL for the image at its normal size
+
$url = $image->getURL();                             // Gets the URL for the image at its normal size
$url_50px = $image->getThumbnail(50,50)->getUrl(); # Gets the URL for the image at a specified size
+
$url_50px = $image->getThumbnail( 50, 50 )->getUrl(); // Gets the URL for the image at a specified size
 
}
 
}
</php>}}
+
</source>
  
 
=== Image links ===
 
=== Image links ===
 
Sometimes you might want to prevent images from linking to the image page. This extension function removes the link.
 
Sometimes you might want to prevent images from linking to the image page. This extension function removes the link.
{{code|<php>
+
<source lang="php">
 
$wgHooks['OutputPageBeforeHTML'][] = 'removeImageLinks';
 
$wgHooks['OutputPageBeforeHTML'][] = 'removeImageLinks';
 
function removeImageLinks( &$out, &$text ) {
 
function removeImageLinks( &$out, &$text ) {
Line 67: Line 87:
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
  
 
=== Adding stub code to newly created articles ===
 
=== Adding stub code to newly created articles ===
 
This will add <tt><nowiki>{{stub}}</nowiki></tt> to the beginning of article content when it is first created. It can then be removed on a subsequent edit if desired.
 
This will add <tt><nowiki>{{stub}}</nowiki></tt> to the beginning of article content when it is first created. It can then be removed on a subsequent edit if desired.
{{code|<php>
+
<source lang="php">
 
$wgHooks['ArticleSave'][] = 'wgAddStub';
 
$wgHooks['ArticleSave'][] = 'wgAddStub';
 
function wgAddStub( &$article, &$user, &$text, &$summary, $minor, $watchthis, $sectionanchor, &$flags, &$status ) {
 
function wgAddStub( &$article, &$user, &$text, &$summary, $minor, $watchthis, $sectionanchor, &$flags, &$status ) {
Line 77: Line 97:
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
  
 
== Security ==
 
== Security ==
 
=== Only allow articles in a specific category to be editable ===
 
=== Only allow articles in a specific category to be editable ===
 
Probably the most common security-related LocalSettings hack is to allow pages in locked down wikis to be publicly viewable if they're in a particular category. This following example adds articles in ''Category:Public'' to the ''$wgWhitelistRead'' array.
 
Probably the most common security-related LocalSettings hack is to allow pages in locked down wikis to be publicly viewable if they're in a particular category. This following example adds articles in ''Category:Public'' to the ''$wgWhitelistRead'' array.
{{code|<php>
+
<source lang="php">
 
$wgHooks['UserGetRights'][] = 'wfPublicCat';
 
$wgHooks['UserGetRights'][] = 'wfPublicCat';
 
function wfPublicCat() {
 
function wfPublicCat() {
Line 96: Line 116:
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
  
 
=== Add group information in body element classes ===
 
=== Add group information in body element classes ===
 
From MediaWiki 1.17 onwards, the [[MW:Manual:Hooks/OutputPageBodyAttributes|OutputPageBodyAttributes hook]] can be used to modify the classes and other attributes of the body tag. In the following example the hook is being used to add classes to the body tag depending on whether the user is anonymous, logged-in or a sysop.
 
From MediaWiki 1.17 onwards, the [[MW:Manual:Hooks/OutputPageBodyAttributes|OutputPageBodyAttributes hook]] can be used to modify the classes and other attributes of the body tag. In the following example the hook is being used to add classes to the body tag depending on whether the user is anonymous, logged-in or a sysop.
{{code|<php>
+
<source lang="php">
 
$wgHooks['OutputPageBodyAttributes'][] = 'wfAddBodyClasses';
 
$wgHooks['OutputPageBodyAttributes'][] = 'wfAddBodyClasses';
function wfAddBodyClasses( $out, $sk, $bodyAttrs ) {
+
function wfAddBodyClasses( $out, $sk, &$bodyAttrs ) {
 
global $wgUser;
 
global $wgUser;
 
if( $wgUser->isAnon() ) $bodyAttrs['class'] .= ' anon';
 
if( $wgUser->isAnon() ) $bodyAttrs['class'] .= ' anon';
Line 109: Line 129:
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
  
 
=== Prevent users from editing other users pages ===
 
=== Prevent users from editing other users pages ===
 
Here's an example which was created in response to a support-desk question. It prevents users from editing other users user-pages:
 
Here's an example which was created in response to a support-desk question. It prevents users from editing other users user-pages:
{{code|<php>
+
<source lang="php">
 
$wgExtensionFunctions[] = 'wfProtectUserPages';
 
$wgExtensionFunctions[] = 'wfProtectUserPages';
 
function wfProtectUserPages() {
 
function wfProtectUserPages() {
global $wgUser,$wgGroupPermissions;
+
global $wgUser, $wgGroupPermissions;
 
$title = Title::newFromText( $_REQUEST['title'] );
 
$title = Title::newFromText( $_REQUEST['title'] );
 
if( is_object( $title ) && $title->getNamespace() == NS_USER && $wgUser->getName() != $title->getText() )
 
if( is_object( $title ) && $title->getNamespace() == NS_USER && $wgUser->getName() != $title->getText() )
 
$wgGroupPermissions['user']['edit'] = false;
 
$wgGroupPermissions['user']['edit'] = false;
 
}
 
}
</php>}}
+
</source>
  
 
=== Restrict namespaces from anonymous users ===
 
=== Restrict namespaces from anonymous users ===
 
Here is a similar example which restricts all namespaces except MAIN from anonymous users:
 
Here is a similar example which restricts all namespaces except MAIN from anonymous users:
{{code|<php>
+
<source lang="php">
 
$wgExtensionFunctions[] = 'wfProtectNamespaces';
 
$wgExtensionFunctions[] = 'wfProtectNamespaces';
$wgWhitelistRead = array( 'Special:Userlogin', '-', 'MediaWiki:Monobook.css' );
+
$wgWhitelistRead = [ 'Special:Userlogin', '-', 'MediaWiki:Monobook.css' ];
 
function wfProtectNamespaces() {
 
function wfProtectNamespaces() {
 
global $wgUser,$wgGroupPermissions;
 
global $wgUser,$wgGroupPermissions;
Line 134: Line 154:
 
$wgGroupPermissions['*']['read'] = false;
 
$wgGroupPermissions['*']['read'] = false;
 
}
 
}
</php>}}
+
</source>
  
 
=== Redirect sysops to HTTPS ===
 
=== Redirect sysops to HTTPS ===
 
The following snippet causes all requests to be redirected to HTTPS if the user is a sysop. It also bounces users to non-HTTPS if not a sysop which can be useful if you don't want HTTPS links showing up in search engines for example if you have a self-signed certificate (it doesn't do this if the user is on the login page though since that confuses our [[wikid|bots]] that use HTTPS connections):
 
The following snippet causes all requests to be redirected to HTTPS if the user is a sysop. It also bounces users to non-HTTPS if not a sysop which can be useful if you don't want HTTPS links showing up in search engines for example if you have a self-signed certificate (it doesn't do this if the user is on the login page though since that confuses our [[wikid|bots]] that use HTTPS connections):
{{code|<php>
+
<source lang="php">
 
# Force HTTPS for sysops (and non-HTTPS for non-sysops)
 
# Force HTTPS for sysops (and non-HTTPS for non-sysops)
 
$wgExtensionFunctions[] = 'wfSecureSysops';
 
$wgExtensionFunctions[] = 'wfSecureSysops';
Line 155: Line 175:
 
}
 
}
 
}
 
}
</php>}}
+
</source>
  
 
=== Check whether or not a request is local ===
 
=== Check whether or not a request is local ===
 
The following code checks if a request is local, and works regardless of the IP address or whether it's IPv4 or IPv6.
 
The following code checks if a request is local, and works regardless of the IP address or whether it's IPv4 or IPv6.
{{code|<php>
+
<source lang="php">
 
function isLocal() {
 
function isLocal() {
 
return preg_match_all( "|inet6? addr:\s*([0-9a-f.:]+)|", `/sbin/ifconfig`, $matches ) && in_array( $_SERVER['REMOTE_ADDR'], $matches[1] );
 
return preg_match_all( "|inet6? addr:\s*([0-9a-f.:]+)|", `/sbin/ifconfig`, $matches ) && in_array( $_SERVER['REMOTE_ADDR'], $matches[1] );
 
}
 
}
</php>}}
+
</source>
  
 
=== Only allow raw HTML in a specific sysop-editable namespace ===
 
=== Only allow raw HTML in a specific sysop-editable namespace ===
 
First set up that namespace in LocalSettings and protect it so that only sysops can edit it, for example:
 
First set up that namespace in LocalSettings and protect it so that only sysops can edit it, for example:
{{code|<php>
+
<source lang="php">
 
define( 'NS_HTML',  5000 );
 
define( 'NS_HTML',  5000 );
 
$wgExtraNamespaces[NS_HTML] = 'Html';
 
$wgExtraNamespaces[NS_HTML] = 'Html';
 
$wgExtraNamespaces[NS_HTML+1] = 'Html_talk';
 
$wgExtraNamespaces[NS_HTML+1] = 'Html_talk';
$wgNamespaceProtection[NS_HTML] = array( 'html-edit' );
+
$wgNamespaceProtection[NS_HTML] = [ 'html-edit' ];
 
$wgGroupPermissions['sysop']['html-edit'] = true;
 
$wgGroupPermissions['sysop']['html-edit'] = true;
</php>}}
+
</source>
  
  
 
Then the nest thing is to only allow Raw HTML to work in that namespace. It's turned on by default, and then disabled if the current title is not in the NS_HTML namespace:
 
Then the nest thing is to only allow Raw HTML to work in that namespace. It's turned on by default, and then disabled if the current title is not in the NS_HTML namespace:
{{code|<php>
+
<source lang="php">
 
$wgRawHtml = true;
 
$wgRawHtml = true;
 
$wgExtensionFunctions[] = 'wfHtmlNamespace';
 
$wgExtensionFunctions[] = 'wfHtmlNamespace';
Line 189: Line 209:
 
     }
 
     }
 
}
 
}
</php>}}
+
</source>
  
 
== Returning content to the client ==
 
== Returning content to the client ==
Line 196: Line 216:
 
This function returns the raw content specified in ''$text'', it can be called any time from extension-setup or after. If the ''$save'' parameter is supplied it will bring up a download dialog with the default name set to ''$save'', otherwise it will download and open unprompted. If ''$expand'' is set to true, then any templates, parser-functions or variables in the content will be expanded.
 
This function returns the raw content specified in ''$text'', it can be called any time from extension-setup or after. If the ''$save'' parameter is supplied it will bring up a download dialog with the default name set to ''$save'', otherwise it will download and open unprompted. If ''$expand'' is set to true, then any templates, parser-functions or variables in the content will be expanded.
  
{{code|<php>
+
<source lang="php">
function raw($text, $expand = false, $save = false) {
+
function raw( $text, $expand = false, $save = false ) {
 
global $wgOut, $wgParser;
 
global $wgOut, $wgParser;
if ($expand) $text = $wgParser->preprocess($text, new Title(), new ParserOptions());
+
if ( $expand ) $text = $wgParser->preprocess( $text, new Title(), new ParserOptions() );
 
$wgOut->disable();
 
$wgOut->disable();
 
wfResetOutputBuffers();
 
wfResetOutputBuffers();
 
header('Content-Type: application/octet-stream');
 
header('Content-Type: application/octet-stream');
if ($save) header("Content-Disposition: attachment; filename=\"$save\"");
+
if ( $save ) header( "Content-Disposition: attachment; filename=\"$save\"" );
 
echo $text;
 
echo $text;
 
}
 
}
</php>}}
+
</source>
  
 
=== Return an HTTP error page ===
 
=== Return an HTTP error page ===
{{code|<php>
+
<source lang="php">
 
global $wgOut, $wgParser;
 
global $wgOut, $wgParser;
 
$wgOut->disable();
 
$wgOut->disable();
Line 217: Line 237:
 
<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>';
 
<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>';
 
echo $err;
 
echo $err;
</php>}}
+
</source>
  
 
=== Domain-based default redirect ===
 
=== Domain-based default redirect ===
 
If no page title is specified, redirect to a default depending on the domain name in the requested URL. In this example requests to ''abc.org'' or any of it's subdomains with no title specified will redirect to the ''Welcome to ABC'' article, and any requests to the exact domain of ''www.xyz.org'' without a title end up at the ''XYZ Home'' article. Titleless requests to any other domain which resolves to this example wiki will be unaffected and left to the rest of the configuration to deal with. This code should be executed early in the ''LocalSettings'' before any extensions are included, but after ''$wgServer'' is defined.
 
If no page title is specified, redirect to a default depending on the domain name in the requested URL. In this example requests to ''abc.org'' or any of it's subdomains with no title specified will redirect to the ''Welcome to ABC'' article, and any requests to the exact domain of ''www.xyz.org'' without a title end up at the ''XYZ Home'' article. Titleless requests to any other domain which resolves to this example wiki will be unaffected and left to the rest of the configuration to deal with. This code should be executed early in the ''LocalSettings'' before any extensions are included, but after ''$wgServer'' is defined.
{{code|<php>
+
<source lang="php">
 
$d = $_SERVER['SERVER_NAME'];
 
$d = $_SERVER['SERVER_NAME'];
 
$t = $_REQUEST['title'];
 
$t = $_REQUEST['title'];
if (empty($t)) $t = ereg_replace('^/', '', $_SERVER['PATH_INFO']);
+
if ( empty($t) ) $t = ereg_replace('^/', '', $_SERVER['PATH_INFO'] );
if (empty($t) || $t == 'Main_Page') {
+
if ( empty($t) || $t == 'Main_Page') {
if (ereg('abc.org$', $d)) header("Location: $wgServer/Welcome_to_ABC") && die;
+
if ( ereg( 'abc.org$', $d) ) header( "Location: $wgServer/Welcome_to_ABC" ) && die;
if ($d == 'www.xyz.com')  header("Location: $wgServer/XYZ_Home")      && die;
+
if ( $d == 'www.xyz.com')  header( "Location: $wgServer/XYZ_Home" )      && die;
 
}
 
}
</php>}}
+
</source>
  
 
=== Add a meta tags to the page ===
 
=== Add a meta tags to the page ===
{{code|<php>
+
<source lang="php">
 
$wgExtensionFunctions[] = 'wfAddMetaTag';
 
$wgExtensionFunctions[] = 'wfAddMetaTag';
 
function wfAddMetaTag() {
 
function wfAddMetaTag() {
 
global $wgOut;
 
global $wgOut;
$wgOut->addMeta('name1', 'value1');
+
$wgOut->addMeta( 'name1', 'value1' );
$wgOut->addMeta('name2', 'value2');
+
$wgOut->addMeta( 'name2', 'value2' );
 
}
 
}
</php>}}
+
</source>
  
 
== Using the parser ==
 
== Using the parser ==
 
=== Parse wikitext ===
 
=== Parse wikitext ===
{{code|<php>
+
The simplest way is via ''$wgOut'', e.g.
$html = $wgParser->parse($wikitext, $title, new ParserOptions(), true, true)->getText();
+
<source lang="php">
</php>}}
+
$html = $wgOut->parse( $wikitext );
 +
</source>
 +
 
 +
Or, if you want to have more control and use the parser directly,
 +
<source lang="php">
 +
$html = $wgParser->parse( $wikitext, $title, new ParserOptions(), true, true )->getText();
 +
</source>
  
 
=== Expand templates only ===
 
=== Expand templates only ===
{{code|<php>
+
<source lang="php">
$wikitext = $wgParser->preprocess($wikitext, $title, new ParserOptions());
+
$wikiText = $wgParser->preprocess( $wikiText, $title, new ParserOptions() );
</php>}}
+
</source>
  
 
=== Replace triple-brace arguments in wikitext content ===
 
=== Replace triple-brace arguments in wikitext content ===
{{code|<php>
+
<source lang="php">
$parser->replaceVariables($wikitext, $args, true);
+
$parser->replaceVariables( $wikiText, $args, true );
</php>}}
+
</source>
 
If ''$wikitext'' is set to <nowiki>"hello {{{you}}} this is {{{me}}}"</nowiki>, then the "you" and "me" keys of the ''$args'' array will replace the corresponding triple-brace arguments in the wikitext content. This is a useful method to know because there are actually a number of difficulties involved in implementing it since it must account for both named and ordered parameters and default values (which can also contain brace-expressions).
 
If ''$wikitext'' is set to <nowiki>"hello {{{you}}} this is {{{me}}}"</nowiki>, then the "you" and "me" keys of the ''$args'' array will replace the corresponding triple-brace arguments in the wikitext content. This is a useful method to know because there are actually a number of difficulties involved in implementing it since it must account for both named and ordered parameters and default values (which can also contain brace-expressions).
  
 
=== Using named parameters from a parser-function callback ===
 
=== Using named parameters from a parser-function callback ===
 
You can use named parameters in your parser functions, eg <tt><nowiki>{{#drops:for=Rikkukin the Defender|zone=The Ascent|h=3}}</nowiki></tt>, but you will need to manually split the args into key/value pairs in your callback function, such as in the following example code:
 
You can use named parameters in your parser functions, eg <tt><nowiki>{{#drops:for=Rikkukin the Defender|zone=The Ascent|h=3}}</nowiki></tt>, but you will need to manually split the args into key/value pairs in your callback function, such as in the following example code:
{{code|<php>
+
<source lang="php">
$args = array();
+
$args = [];
foreach ($argv as $arg)
+
foreach ( $argv as $arg )
if (!is_object($arg))
+
if ( !is_object($arg) )
preg_match('/^(\\w+)\\s*=\\s*(.+)$/is', $arg, $match) ? $args[$match[1]] = $match[2] : $args[] = $arg;
+
preg_match( '/^(\\w+)\\s*=\\s*(.+)$/is', $arg, $match ) ? $args[$match[1]] = $match[2] : $args[] = $arg;
</php>}}
+
</source>
 
This snippet will create a hash from the arguments passed to your callback function (ignoring any which are objects such as the first one which is ''$parser''). The resulting hash will contain numeric keys for all the normal non-named parameters in your parser-function, and non-numeric keys matching all the ''name=value'' parameters.
 
This snippet will create a hash from the arguments passed to your callback function (ignoring any which are objects such as the first one which is ''$parser''). The resulting hash will contain numeric keys for all the normal non-named parameters in your parser-function, and non-numeric keys matching all the ''name=value'' parameters.
  
 
=== Return codes for parser functions ===
 
=== Return codes for parser functions ===
 
Typical example.
 
Typical example.
{{code|<php>
+
<source lang="php">
return array(
+
return [
 
$text,
 
$text,
 
'found'  => true,
 
'found'  => true,
Line 278: Line 304:
 
'noargs'  => false,
 
'noargs'  => false,
 
'isHTML'  => false
 
'isHTML'  => false
);
+
];
</php>}}
+
</source>
 
See also: http://www.organicdesign.co.nz/Extension:Example
 
See also: http://www.organicdesign.co.nz/Extension:Example
  
Line 286: Line 312:
  
 
=== Post processing to remove unwanted breaks and entities from output ===
 
=== Post processing to remove unwanted breaks and entities from output ===
{{code|<php>
+
<source lang="php">
function efFixEmptyLineBug(&$parser, &$text) {
+
function efFixEmptyLineBug( $parser, &$text ) {
$text = preg_replace('|<p>\s*<br\s*/>\s*</p>|s', '', $text);
+
$text = preg_replace( '|<p>\s*<br\s*/>\s*</p>|s', '', $text );
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
  
 
== JavaScript ==
 
== JavaScript ==
Line 298: Line 324:
 
=== Make sortable table states persistent with cookies ===
 
=== Make sortable table states persistent with cookies ===
 
Note that this function requires ''getCookie'' and ''setCookie'' which are currently in our [http://svn.organicdesign.co.nz/filedetails.php?repname=extensions&path=%2FJavaScript%2Fnavbar.js navbar.js]. They're just the standard cookie getting and setting [http://www.w3schools.com/JS/js_cookies.asp functions recommended by W3C].
 
Note that this function requires ''getCookie'' and ''setCookie'' which are currently in our [http://svn.organicdesign.co.nz/filedetails.php?repname=extensions&path=%2FJavaScript%2Fnavbar.js navbar.js]. They're just the standard cookie getting and setting [http://www.w3schools.com/JS/js_cookies.asp functions recommended by W3C].
{{code|<js>
+
<source lang="js">
 
addOnloadHook( function() {
 
addOnloadHook( function() {
 
     $('.sortable').each( function() {
 
     $('.sortable').each( function() {
Line 315: Line 341:
 
     });
 
     });
 
});
 
});
</js>}}
+
</source>
  
 
== MediaWiki Environment ==
 
== MediaWiki Environment ==
 
=== Article title ===
 
=== Article title ===
 
This should be called at an appropriate time such as from the ''OutputPageBeforeHTML'' hook.
 
This should be called at an appropriate time such as from the ''OutputPageBeforeHTML'' hook.
{{code|<php>
+
<source lang="php">
 
$wgOut->setPageTitle( 'foo' );
 
$wgOut->setPageTitle( 'foo' );
</php>}}
+
</source>
  
 
== Article queries ==
 
== Article queries ==
Line 328: Line 354:
  
 
=== List article titles from a category ===
 
=== List article titles from a category ===
{{code|<php>
+
<source lang="php">
$list = array();
+
$list = [];
$dbr  = &wfGetDB( DB_SLAVE );
+
$dbr  = wfGetDB( DB_SLAVE );
$cl  = $dbr->tableName( 'categorylinks' );
+
$res  = $dbr->select( 'categorylinks', 'cl_from', [ 'cl_to' => $cat ], __METHOD__, [ 'ORDER BY' => 'cl_sortkey' ] );
$cat  = $dbr->addQuotes( Title::newFromText( $cat )->getDBkey() );
+
foreach( $res as $row ) $list[] = Title::newFromID( $row->cl_from )->getPrefixedText();
$res  = $dbr->select( $cl, 'cl_from', "cl_to = $cat", __METHOD__, array( 'ORDER BY' => 'cl_sortkey' ) );
+
</source>
while( $row = $dbr->fetchRow( $res ) ) $list[] = Title::newFromID( $row[0] )->getPrefixedText();
 
</php>}}
 
 
Adjust the $list addition to your own needs. This example creates a title object for each and then calls the ''getPrefixedText'' method which returns the title as a string including namespace.
 
Adjust the $list addition to your own needs. This example creates a title object for each and then calls the ''getPrefixedText'' method which returns the title as a string including namespace.
  
 
=== List categories an article belongs to ===
 
=== List categories an article belongs to ===
{{code|<php>
+
<source lang="php">
$list = array();
+
$list = [];
$dbr  = &wfGetDB( DB_SLAVE );
+
$dbr  = wfGetDB( DB_SLAVE );
 
$cl  = $dbr->tableName( 'categorylinks' );
 
$cl  = $dbr->tableName( 'categorylinks' );
 
$id  = Title::newFromText( 'article-title' )->getArticleID();
 
$id  = Title::newFromText( 'article-title' )->getArticleID();
$res  = $dbr->select( $cl, 'cl_to', "cl_from = $id", __METHOD__, array('ORDER BY' => 'cl_sortkey' ) );
+
$res  = $dbr->select( $cl, 'cl_to', "cl_from = $id", __METHOD__, [ 'ORDER BY' => 'cl_sortkey' ] );
foreach( $res as $row ) $list[] = $row->cl_to;
+
foreach( $res as $row ) $list[] = Title::newFromText( $row->cl_to )->getText();
</php>}}
+
</source>
  
 
=== Check if a title is in a category ===
 
=== Check if a title is in a category ===
{{code|<php>
+
<source lang="php">
 
function inCat( $title, $cat ) {
 
function inCat( $title, $cat ) {
 
if( !is_object( $title ) ) $title = Title::newFromText( $title );
 
if( !is_object( $title ) ) $title = Title::newFromText( $title );
$id  = $title->getArticleID();
+
$cat = Title::newFromText( $cat )->getDBkey();
$dbr  = &wfGetDB( DB_SLAVE );
+
$id  = $title->getArticleID();
$cat = $dbr->addQuotes( Title::newFromText( $cat )->getDBkey() );
+
$dbr = wfGetDB( DB_SLAVE );
$cl  = $dbr->tableName( 'categorylinks' );
+
return $dbr->selectRow( 'categorylinks', '1', [ 'cl_from' => $id, 'cl_to' => $cat ] );
return $dbr->selectRow( $cl, '0', "cl_from = $id AND cl_to = $cat", __METHOD__ );
 
 
}
 
}
</php>}}
+
</source>
  
 
=== Get a list of articles in a namespace (number) ===
 
=== Get a list of articles in a namespace (number) ===
{{code|<php>
+
<source lang="php">
 
function getArticlesInNamespace( $namespace ) {
 
function getArticlesInNamespace( $namespace ) {
$dbr  = &wfGetDB(DB_SLAVE);
+
$dbr  = wfGetDB( DB_SLAVE );
$list  = array();
+
$list  = [];
 
$table = $dbr->tableName( 'page' );
 
$table = $dbr->tableName( 'page' );
 
$res  = $dbr->select( $table, 'page_title', "page_namespace = $namespace" );
 
$res  = $dbr->select( $table, 'page_title', "page_namespace = $namespace" );
Line 370: Line 393:
 
return $list;
 
return $list;
 
}
 
}
</php>}}
+
</source>
  
 
=== Get a list of articles using a template ===
 
=== Get a list of articles using a template ===
{{code|<php>
+
<source lang="php">
 
function usesTemplate( $tmpl ) {
 
function usesTemplate( $tmpl ) {
$dbr   = &wfGetDB(DB_SLAVE);
+
$dbr = wfGetDB( DB_SLAVE );
$list = array();
+
$list = [];
$tmpl = $dbr->addQuotes( Title::newFromText( $tmpl )->getDBkey() );
+
$res = $dbr->select( 'templatelinks', 'tl_from', [ 'tl_namespace' => NS_TEMPLATE, 'tl_title' => $tmpl ] );
$table = $dbr->tableName( 'templatelinks' );
+
foreach( $res as $row ) $list[] = Title::newFromID( $row->tl_from )->getPrefixedText();
$res  = $dbr->select( $table, 'tl_from', "tl_namespace = 10 AND tl_title = $tmpl" );
 
foreach( $res as $row ) $list[] = $row->tl_from;
 
 
return $list;
 
return $list;
 
}
 
}
</php>}}
+
</source>
 +
 
 +
=== Return the "owner" of the passed title (person who created it) ===
 +
<source lang="php">
 +
function getOwner( $title ) {
 +
$id = $title->getArticleID();
 +
$dbr = wfGetDB( DB_SLAVE );
 +
if( $id > 0 && $row = $dbr->selectRow( 'revision', 'rev_user', [ 'rev_page' => $id ], __METHOD__, [ 'ORDER BY' => 'rev_timestamp' ] ) )
 +
return User::newFromID( $row->rev_user )->getName();
 +
return false;
 +
}
 +
</source>
  
 
=== Remove category pages for empty categories ===
 
=== Remove category pages for empty categories ===
{{code|<php>
+
<source lang="php">
 
function removeEmptyCategories() {
 
function removeEmptyCategories() {
  
 
// Get all category pages
 
// Get all category pages
$dbr  = &wfGetDB(DB_SLAVE);
+
$dbr  = wfGetDB( DB_SLAVE );
$cats  = array();
+
$cats  = [];
 
$table = $dbr->tableName( 'page' );
 
$table = $dbr->tableName( 'page' );
 
$res  = $dbr->select( $table, 'page_title', "page_namespace = " . NS_CATEGORY );
 
$res  = $dbr->select( $table, 'page_title', "page_namespace = " . NS_CATEGORY );
while( $row = $dbr->fetchRow( $res ) ) $cats[] = $row[0];
+
foreach( $res as $row ) $cats[] = $row->page_title;
$dbr->freeResult( $res );
 
  
 
// Delete those which are for empty cats
 
// Delete those which are for empty cats
 
foreach( $cats as $cat ) {
 
foreach( $cats as $cat ) {
$dbr  = &wfGetDB( DB_SLAVE );
+
$dbr  = wfGetDB( DB_SLAVE );
 
$cl  = $dbr->tableName( 'categorylinks' );
 
$cl  = $dbr->tableName( 'categorylinks' );
 
$qcat = $dbr->addQuotes( Title::newFromText( $cat )->getDBkey() );
 
$qcat = $dbr->addQuotes( Title::newFromText( $cat )->getDBkey() );
Line 409: Line 440:
 
}
 
}
 
}
 
}
</php>}}
+
</source>
  
===Article queries using DPL===
+
=== Article queries using DPL ===
 
This DPL query example provides a standard list of page links in wikitext bullet list format.
 
This DPL query example provides a standard list of page links in wikitext bullet list format.
{{code|<pre>
+
<source>
 
{{#dpl:category=Foo|format=,*[[%PAGE%]],\n,}}
 
{{#dpl:category=Foo|format=,*[[%PAGE%]],\n,}}
</pre>}}
+
</source>
  
 
== Misc ==
 
== Misc ==
 
=== examineBraces ===
 
=== examineBraces ===
 
This function returns an array of the brace structure found in the passed wikitext parameter. This has also been implemented in Perl, see the ''wikiExamineBraces'' function in [[wiki.pl]].
 
This function returns an array of the brace structure found in the passed wikitext parameter. This has also been implemented in Perl, see the ''wikiExamineBraces'' function in [[wiki.pl]].
{{code|<php>
+
<source lang="php">
 
function examineBraces( &$content ) {
 
function examineBraces( &$content ) {
$braces = array();
+
$braces = [];
$depths = array();
+
$depths = [];
 
$depth = 1;
 
$depth = 1;
 
$index = 0;
 
$index = 0;
Line 429: Line 460:
 
$index = $match[0][1] + 2;
 
$index = $match[0][1] + 2;
 
if( $match[0][0] == '}}' ) {
 
if( $match[0][0] == '}}' ) {
$brace =& $braces[$depths[$depth-1]];
+
if( $depth > 0 ) {
$brace[LENGTH] = $match[0][1] - $brace[OFFSET] + 2;
+
$brace =& $braces[$depths[--$depth]];
$brace[DEPTH]  = $depth--;
+
$brace[LENGTH] = $match[0][1] - $brace[OFFSET] + 2;
 +
$brace[DEPTH]  = $depth;
 +
}
 
}
 
}
 
else {
 
else {
 
$depths[$depth++] = count( $braces );
 
$depths[$depth++] = count( $braces );
$braces[] = array(
+
$braces[] = [
 
NAME  => $match[1][0],
 
NAME  => $match[1][0],
 
OFFSET => $match[0][1]
 
OFFSET => $match[0][1]
);
+
];
 
}
 
}
 
}
 
}
 
return $braces;
 
return $braces;
 
}
 
}
</php>}}
+
</source>
  
  
 
The following input,
 
The following input,
{{code|<pre>
+
<source>
 
foo{{#bar:baz|biz{{foo|shmoo}}}}{{moo}}baz
 
foo{{#bar:baz|biz{{foo|shmoo}}}}{{moo}}baz
</pre>}}
+
</source>
  
  
 
Gives the following array:
 
Gives the following array:
{{code|<pre>
+
<source>
 
Array(
 
Array(
 
     [0] => Array(
 
     [0] => Array(
Line 476: Line 509:
 
         )
 
         )
 
     )
 
     )
</pre>}}
+
</source>
 
The array output is designed to integrate with the [http://nz2.php.net/manual/en/function.substr-replace.php substr_replace] function arguments subject, replace, offset, length. The index 0, 1, 2 order referes to the order functions are found (from left to right).
 
The array output is designed to integrate with the [http://nz2.php.net/manual/en/function.substr-replace.php substr_replace] function arguments subject, replace, offset, length. The index 0, 1, 2 order referes to the order functions are found (from left to right).
  
 
=== Google Analytics ===
 
=== Google Analytics ===
{{code|<php>
+
<source lang="php">
 
$wgExtensionFunctions[] = 'wfGoogleAnalytics';
 
$wgExtensionFunctions[] = 'wfGoogleAnalytics';
 
function wfGoogleAnalytics() {
 
function wfGoogleAnalytics() {
Line 493: Line 526:
 
);
 
);
 
}
 
}
</php>}}
+
</source>
  
 
=== Force all headings to use outline numbering ===
 
=== Force all headings to use outline numbering ===
 
There is a user-preference to make all headings use outline numbering, but no way to make that a default for all users. Here's a few lines of code which can be added to your LocalSettings.php file which do that.
 
There is a user-preference to make all headings use outline numbering, but no way to make that a default for all users. Here's a few lines of code which can be added to your LocalSettings.php file which do that.
{{code|<php>
+
<source lang="php">
 
$wgExtensionFunctions[] = 'wfNumberHeadings';
 
$wgExtensionFunctions[] = 'wfNumberHeadings';
 
function wfNumberHeadings() {
 
function wfNumberHeadings() {
 
global $wgUser;
 
global $wgUser;
 
$wgUser->setOption('numberheadings', true);
 
$wgUser->setOption('numberheadings', true);
</php>}}
+
</source>
  
 
=== Create a sysop account from LocalSettings.php ===
 
=== Create a sysop account from LocalSettings.php ===
 
If you don't have admin access to a wiki, but you do have access to the ''LocalSettings.php'' file you can create a ''sysop'' account by adding the following snippet. Note that this is only temporary and after the snippet is removed the user will no longer have admin access, so to make it permanent, go to [[Special:UserRights]] and change the group membership for your user (you may have to set "bot" or something for this since both "sysop" and "beauracrat" will be checked). You can then remove the snippet from LocalSettings.php (and then remove yourself from the "bot" group if you had to add that).
 
If you don't have admin access to a wiki, but you do have access to the ''LocalSettings.php'' file you can create a ''sysop'' account by adding the following snippet. Note that this is only temporary and after the snippet is removed the user will no longer have admin access, so to make it permanent, go to [[Special:UserRights]] and change the group membership for your user (you may have to set "bot" or something for this since both "sysop" and "beauracrat" will be checked). You can then remove the snippet from LocalSettings.php (and then remove yourself from the "bot" group if you had to add that).
{{code|<php>
+
<source lang="php">
 
$wgExtensionFunctions[] = 'wfTemp';
 
$wgExtensionFunctions[] = 'wfTemp';
 
function wfTemp() {
 
function wfTemp() {
Line 512: Line 545:
 
if( $wgUser->getName() == 'USERNAME' ) $wgUser->addGroup( 'bureaucrat' );
 
if( $wgUser->getName() == 'USERNAME' ) $wgUser->addGroup( 'bureaucrat' );
 
}
 
}
 
+
</source>
</php>}}
 
  
 
=== Manipulating the search index for an article ===
 
=== Manipulating the search index for an article ===
 
The output of parser-functions are not included in the search indexing, so here's a snippet to add custom phrases to the index when an article is saved.
 
The output of parser-functions are not included in the search indexing, so here's a snippet to add custom phrases to the index when an article is saved.
{{code|<php>
+
<source lang="php">
 
function onArticleSave( &$article, &$user, &$text, &$summary, $minor, $watchthis, $sectionanchor, &$flags, &$status ) {
 
function onArticleSave( &$article, &$user, &$text, &$summary, $minor, $watchthis, $sectionanchor, &$flags, &$status ) {
 
global $wgHooks;
 
global $wgHooks;
Line 530: Line 562:
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
  
 
== Integrating with the Skin ==
 
== Integrating with the Skin ==
 
=== Adding a new action tab ===
 
=== Adding a new action tab ===
 
The following code adds a new action tab with a corresponding link. To process the new action, add a processing function to the ''UnknownAction'' hook.
 
The following code adds a new action tab with a corresponding link. To process the new action, add a processing function to the ''UnknownAction'' hook.
{{code|<php>
+
<source lang="php">
 
$wgExampleAction = 'example';
 
$wgExampleAction = 'example';
  
Line 545: Line 577:
 
$url      = $wgTitle->getLocalURL( "action=$wgExampleAction" );
 
$url      = $wgTitle->getLocalURL( "action=$wgExampleAction" );
 
if (is_object( $wgTitle ) ) {
 
if (is_object( $wgTitle ) ) {
$actions[$wgExampleAction] = array(
+
$actions[$wgExampleAction] = [
 
'text'  => $wgExampleAction, # should use wfMsg( $wgExampleAction )
 
'text'  => $wgExampleAction, # should use wfMsg( $wgExampleAction )
 
'class' => $selected,
 
'class' => $selected,
 
'href'  => $url
 
'href'  => $url
);
+
];
 
}
 
}
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
  
  
 
The Vector skin (optional on 1.16, default on 1.17 onwards) uses a new hook, SkinTemplateNavigation. You can cover both Vector and earlier skins like Monobook or Modern by including code for both hooks. The example below uses the MediaWiki namespace to name the relevant elements of the array. You can put this code in LocalSettings after the inclusion of extensions.
 
The Vector skin (optional on 1.16, default on 1.17 onwards) uses a new hook, SkinTemplateNavigation. You can cover both Vector and earlier skins like Monobook or Modern by including code for both hooks. The example below uses the MediaWiki namespace to name the relevant elements of the array. You can put this code in LocalSettings after the inclusion of extensions.
{{code|<php>
+
<source lang="php">
 
$wgHooks['SkinTemplateTabs'][] = 'onSkinTemplateTabs';
 
$wgHooks['SkinTemplateTabs'][] = 'onSkinTemplateTabs';
 
$wgHooks['SkinTemplateNavigation'][] = 'onSkinTemplateNavigation';
 
$wgHooks['SkinTemplateNavigation'][] = 'onSkinTemplateNavigation';
  
 
function onSkinTemplateTabs( $skin, &$actions ) {
 
function onSkinTemplateTabs( $skin, &$actions ) {
$actions[wfMsg( 'edit-addtab-id' )] = array(
+
$actions[wfMsg( 'edit-addtab-id' )] = [
 
'class' => wfMsg( 'edit-addtab-class' ),
 
'class' => wfMsg( 'edit-addtab-class' ),
 
'text' => wfMsg( 'edit-addtab-text' ),
 
'text' => wfMsg( 'edit-addtab-text' ),
 
'href' => wfMsg( 'edit-addtab-href' )
 
'href' => wfMsg( 'edit-addtab-href' )
);
+
];
 
return true;
 
return true;
 
}
 
}
  
 
function onSkinTemplateNavigation( &$skin, &$contentActions ) {
 
function onSkinTemplateNavigation( &$skin, &$contentActions ) {
$contentActions['views'][wfMsg( 'edit-addtab-id' )] = array(
+
$contentActions['views'][wfMsg( 'edit-addtab-id' )] = [
 
'class' => wfMsg( 'edit-addtab-class' ),
 
'class' => wfMsg( 'edit-addtab-class' ),
 
'text' => wfMsg( 'edit-addtab-text' ),
 
'text' => wfMsg( 'edit-addtab-text' ),
 
'href' => wfMsg( 'edit-addtab-href' )
 
'href' => wfMsg( 'edit-addtab-href' )
);
+
];
 
return true;
 
return true;
 
}
 
}
</php>}}
+
</source>
 
=== Wikitext in Sidebar ===
 
=== Wikitext in Sidebar ===
 
By default the [[MediaWiki:Sidebar]] article content is not normal wikitext. This snippet fixes that and also ensures that the content can fall back to an i18n message if the article is not present - the normal behaviour for articles in the ''MediaWiki'' namespace. The simplest way is to replace a section such as toolbox or navlinks in the ''skins/MonoBook.php'' file with the following:
 
By default the [[MediaWiki:Sidebar]] article content is not normal wikitext. This snippet fixes that and also ensures that the content can fall back to an i18n message if the article is not present - the normal behaviour for articles in the ''MediaWiki'' namespace. The simplest way is to replace a section such as toolbox or navlinks in the ''skins/MonoBook.php'' file with the following:
{{code|<php>
+
<source lang="php">
global $wgUser, $wgTitle, $wgParser;
+
global $wgTitle, $wgOut;
 
$title = 'sidebar'; # note the lcfirst is important here since it's also a msg key
 
$title = 'sidebar'; # note the lcfirst is important here since it's also a msg key
 
$article = new Article( Title::newFromText( $title, NS_MEDIAWIKI ) );
 
$article = new Article( Title::newFromText( $title, NS_MEDIAWIKI ) );
$text = $article->fetchContent();
+
$text = $article->getContent();
if ( empty( $text ) ) $text = wfMsg( $title );
+
if ( empty( $text ) ) $text = wfMessage( $title )->text();
if ( is_object( $wgParser ) ) { $psr = $wgParser; $opt = $wgParser->mOptions; }
+
echo $wgOut->parse( $text );
else { $psr = new Parser; $opt = NULL; }
+
</source>
if ( !is_object( $opt ) ) $opt = ParserOptions::newFromUser( $wgUser );
 
echo $psr->parse( $text, $wgTitle, $opt, true, true )->getText();
 
</php>}}
 
  
  
 
The procedure is the same with Vector-based skins, the change is you now place the code inside divs of id portal and then id body, as per the other examples in ''skins/Vector.php''. It should replace the following code and/or the toolbox:
 
The procedure is the same with Vector-based skins, the change is you now place the code inside divs of id portal and then id body, as per the other examples in ''skins/Vector.php''. It should replace the following code and/or the toolbox:
{{code|<php>
+
<source lang="php">
 
<?php $this->renderPortals( $this->data['sidebar'] ); ?>
 
<?php $this->renderPortals( $this->data['sidebar'] ); ?>
</php>}}
+
</source>
  
  
 
==== For MediaWiki 1.18 and above ====
 
==== For MediaWiki 1.18 and above ====
 
It often not desirable to modify the codebase, so a preferable method is to attach a function to an appropriate hook that does something similar to the above example. As of MediaWiki-1.18, I've found the best way is to attach a function to the [[MW:Manual:Hooks/BeforePageDisplay|BeforePageDisplay hook]] by adding the following snippet to your ''LocalSettings.php'' file:
 
It often not desirable to modify the codebase, so a preferable method is to attach a function to an appropriate hook that does something similar to the above example. As of MediaWiki-1.18, I've found the best way is to attach a function to the [[MW:Manual:Hooks/BeforePageDisplay|BeforePageDisplay hook]] by adding the following snippet to your ''LocalSettings.php'' file:
{{code|<php>
+
<source lang="php">
$wgHooks['BeforePageDisplay'][] = 'wfWikitextSidebar';
+
$wgHooks['BeforePageDisplay'][] = function( $out, $skin ) {
function wfWikitextSidebar( $out, $skin ) {
+
$page = WikiPage::factory( Title::newFromText( 'SidebarTree', NS_MEDIAWIKI ) );
global $wgUser, $wgParser;
+
$html = $out->parseAsContent( $page->getContent()->getNativeData() );
$opt = ParserOptions::newFromUser( $wgUser );
 
$title = Title::newFromText( 'Sidebar', NS_MEDIAWIKI );
 
$article = new Article( $title );
 
$html = $wgParser->parse( $article->fetchContent(), $title, $opt, true, true )->getText();
 
 
$out->addHTML( "<div id=\"wikitext-sidebar\">$html</div>" );
 
$out->addHTML( "<div id=\"wikitext-sidebar\">$html</div>" );
 
return true;
 
return true;
}
+
};
</php>}}
+
</source>
  
  
 
This creates a CSS-addressable ''div'' element containing the parsed content from the ''MediaWiki:Sidebar'' article. You can then use some JavaScript added to your [[MediaWiki:Common.js]] to move the element into a more appropriate location in the page DOM. For example the following JavaScript snippet inserts the rendered wikitext below the site logo.
 
This creates a CSS-addressable ''div'' element containing the parsed content from the ''MediaWiki:Sidebar'' article. You can then use some JavaScript added to your [[MediaWiki:Common.js]] to move the element into a more appropriate location in the page DOM. For example the following JavaScript snippet inserts the rendered wikitext below the site logo.
{{code|<js>
+
<source lang="js">
 
var tree = $('#wikitext-sidebar').parent();
 
var tree = $('#wikitext-sidebar').parent();
 
$('#p-logo').after( tree.html() );
 
$('#p-logo').after( tree.html() );
 
tree.html('');
 
tree.html('');
</js>}}
+
</source>
  
 
=== Development server identification ===
 
=== Development server identification ===
 
The idea is to have a picture or text that allows a developer to quickly see that they are on the development instance and not the production instance of  MediaWiki.
 
The idea is to have a picture or text that allows a developer to quickly see that they are on the development instance and not the production instance of  MediaWiki.
 
By adding to the content of $wgSiteNotice in a conditional every article will have the change for that domain map.
 
By adding to the content of $wgSiteNotice in a conditional every article will have the change for that domain map.
{{code|<php>
+
<source lang="php">
 
if( $wgServer == "http://localhost" )  # or any other domain
 
if( $wgServer == "http://localhost" )  # or any other domain
 
   $wgSiteNotice = '<span style="position:absolute;top:0px;left:-140px;z-index:10">[[Image:Gnome-devel.png|100px]]</span>';
 
   $wgSiteNotice = '<span style="position:absolute;top:0px;left:-140px;z-index:10">[[Image:Gnome-devel.png|100px]]</span>';
</php>
+
</source>
}}
 
 
This approach was based on [[W:User:East718/include]], and the image source is [http://commons.wikimedia.org/wiki/Image:Gnome-devel.svg Image:Gnome-devel.svg ]
 
This approach was based on [[W:User:East718/include]], and the image source is [http://commons.wikimedia.org/wiki/Image:Gnome-devel.svg Image:Gnome-devel.svg ]
  
 
== Importing and Exporting articles ==
 
== Importing and Exporting articles ==
 
The following snippet imports articles from the XML export file named in ''$file'' into the main namespace. The ''$resultCount'' variable holds the number of articles imported on success.
 
The following snippet imports articles from the XML export file named in ''$file'' into the main namespace. The ''$resultCount'' variable holds the number of articles imported on success.
{{code|<php>
+
<source lang="php">
 
$source = ImportStreamSource::newFromFile( $file );
 
$source = ImportStreamSource::newFromFile( $file );
 
$importer = new WikiImporter( $source );
 
$importer = new WikiImporter( $source );
Line 645: Line 669:
 
if( WikiError::isError( $result ) ) die( $result->getMessage() );
 
if( WikiError::isError( $result ) ) die( $result->getMessage() );
 
elseif( WikiError::isError( $resultCount ) ) die( $resultCount->getMessage() );
 
elseif( WikiError::isError( $resultCount ) ) die( $resultCount->getMessage() );
</php>}}
+
</source>
  
  
 
This one exports the current revision of all articles whose names are contained in the ''$pages'' array to the file named in ''$file''.
 
This one exports the current revision of all articles whose names are contained in the ''$pages'' array to the file named in ''$file''.
{{code|<php>
+
<source lang="php">
 
$dbr = wfGetDB( DB_SLAVE );
 
$dbr = wfGetDB( DB_SLAVE );
 
$exporter = new WikiExporter( $dbr, WikiExporter::CURRENT, WikiExporter::BUFFER );
 
$exporter = new WikiExporter( $dbr, WikiExporter::CURRENT, WikiExporter::BUFFER );
Line 661: Line 685:
 
$exporter->closeStream();
 
$exporter->closeStream();
 
fclose( $exporter->sink->handle );
 
fclose( $exporter->sink->handle );
</php>}}
+
</source>
  
  
Line 673: Line 697:
  
 
In MediaWiki 1.16, you need to install the [[MW:Extension:UsabilityInitiative|UsabilityInitiative Extension]], load custom JavaScript and add a jQuery snippet to enable the advanced editing functionality. You can manually load jQuery (if it is not already loaded elsewhere) and a JavaScript file in the skin to accomplish this:
 
In MediaWiki 1.16, you need to install the [[MW:Extension:UsabilityInitiative|UsabilityInitiative Extension]], load custom JavaScript and add a jQuery snippet to enable the advanced editing functionality. You can manually load jQuery (if it is not already loaded elsewhere) and a JavaScript file in the skin to accomplish this:
{{code|<php>
+
<source lang="php">
 
$out->addScriptFile($wgStylePath .'/path-to-js/jquery.js');
 
$out->addScriptFile($wgStylePath .'/path-to-js/jquery.js');
 
$out->addScriptFile($wgStylePath .'/path-to-js/onload.js');
 
$out->addScriptFile($wgStylePath .'/path-to-js/onload.js');
</php>
+
</source>
}}
 
  
  
 
Then just create onload.js, paste in this code and save it:
 
Then just create onload.js, paste in this code and save it:
{{code|<js>
+
<source lang="js">
 
$(document).ready(function() {
 
$(document).ready(function() {
 
if($('.wikiEditor-ui-toolbar')) {
 
if($('.wikiEditor-ui-toolbar')) {
Line 688: Line 711:
 
$( '.ns-talk .lqt-j-togglepage' ).css( 'display', 'block' );
 
$( '.ns-talk .lqt-j-togglepage' ).css( 'display', 'block' );
 
});
 
});
</js>
+
</source>
}}
 
  
  
Line 695: Line 717:
  
 
It is recommended that MediaWiki 1.18 or above be used - in MediaWiki 1.18 the process is simpler and you need no JavaScript. The WikiEditor extension comes with MediaWiki, you just need to enable it in LocalSettings:
 
It is recommended that MediaWiki 1.18 or above be used - in MediaWiki 1.18 the process is simpler and you need no JavaScript. The WikiEditor extension comes with MediaWiki, you just need to enable it in LocalSettings:
{{code|<php>
+
<source lang="php">
 
require_once( "$IP/extensions/WikiEditor/WikiEditor.php" );
 
require_once( "$IP/extensions/WikiEditor/WikiEditor.php" );
</php>
+
</source>
}}
 
  
  
 
You can also make the WYSIWYG wiki editor global for all users (so they don't need to set it in Preferences) by adding to LocalSettings:
 
You can also make the WYSIWYG wiki editor global for all users (so they don't need to set it in Preferences) by adding to LocalSettings:
{{code|<php>
+
<source lang="php">
 
$wgDefaultUserOptions['usebetatoolbar'] = true;  
 
$wgDefaultUserOptions['usebetatoolbar'] = true;  
$wgDefaultUserOptions['usebetatoolbar-cgd'] = true;  
+
$wgDefaultUserOptions['usebetatoolbar-cgd'] = true;
</php>
+
</source>
}}
 
  
  
 
Finally, you can enable the additional Advanced toolbar by default. In LocalSettings, add:
 
Finally, you can enable the additional Advanced toolbar by default. In LocalSettings, add:
{{code|<php>
+
<source lang="php">
 
$wgExtensionFunctions[] = 'wfAdvancedUI';
 
$wgExtensionFunctions[] = 'wfAdvancedUI';
 
function wfAdvancedUI() {
 
function wfAdvancedUI() {
Line 716: Line 736:
 
$wgOut->addStyle( 'your-skin-directory/advanced.ui.css', 'screen' );
 
$wgOut->addStyle( 'your-skin-directory/advanced.ui.css', 'screen' );
 
}
 
}
</php>
+
</source>
}}
 
  
  
 
Then create the file advanced.ui.css, add the following code and save it to the path you have specified above:
 
Then create the file advanced.ui.css, add the following code and save it to the path you have specified above:
{{code|<css>
+
<source lang="css">
 
.wikiEditor-ui-toolbar .sections {
 
.wikiEditor-ui-toolbar .sections {
 
display: block;
 
display: block;
Line 732: Line 751:
 
display: block;
 
display: block;
 
}
 
}
</css>
+
</source>
}}
 
  
  
Line 740: Line 758:
 
== JavaScript and AJAX ==
 
== JavaScript and AJAX ==
 
*[[MW:ResourceLoader/Migration guide for extension developers]]
 
*[[MW:ResourceLoader/Migration guide for extension developers]]
 +
 +
== Deprecations, issues and new features ==
 +
{{:MediaWiki deprecations and new features}}
  
 
== See also ==
 
== See also ==
*[http://svn.wikimedia.org/doc/ Deoxygen documentation]
+
*[[Development guidelines]]
*[http://svn.wikimedia.org/svnroot/mediawiki/ MediaWiki SVN] / [[MW:Subversion]]
+
*[https://doc.wikimedia.org/mediawiki-core/master/php/ Deoxygen auto-generated code documentation]
 +
*[https://github.com/wikimedia/mediawiki/releases MediaWiki releases on GitHub mirror] ([https://github.com/wikimedia/mediawiki/releases.atom atom feed])
 
*[[MW:Manual:Coding conventions]]
 
*[[MW:Manual:Coding conventions]]
 
*[[MW:How to become a MediaWiki hacker]]
 
*[[MW:How to become a MediaWiki hacker]]
 
*[http://blog.redwerks.org/2012/02/08/mediawiki-skinning-tutorial/ Excellent tutorial on MediaWiki skinning for 1.18+]
 
*[http://blog.redwerks.org/2012/02/08/mediawiki-skinning-tutorial/ Excellent tutorial on MediaWiki skinning for 1.18+]
 
+
*[https://web.archive.org/web/20110709125138/http://musialek.org/?p=94 The MediaWiki parser, uncovered]
 +
*[https://wiki.php.net/pplusplus/faq P++]
 
[[Category:MediaWiki]][[Category:Examples]][[Category:Help]]
 
[[Category:MediaWiki]][[Category:Examples]][[Category:Help]]

Latest revision as of 22:42, 18 December 2021

Contents

Getting in to MediaWiki coding

Here's a useful checklist of things to have set up before starting with MediaWiki development;

  • Full browser and shell access to a running instance of every major version you're likely to work with
  • Local access to the source code for all those versions
  • Use wfDebugDieBacktrace() to stop code and report the call stack (see better way below)
  • Use debug=1 in the query-string when making requests to MediaWiki. This will prevent any caching or code minification
  • Use uselang=qqx in the query-string to show all the keys of messages used in the page in place actual message content
  • A good text editor with regular expression support file search capability (we use Geany which runs on most platforms)
  • Tools or code for being able to stop the php code and output the necessary items in the scope
  • Tools for debugging the JS and CSS environment (eg. firebug)
  • Bookmarks for all the documentation you've found useful
  • Bookmarks to code snippets and examples (within your own wiki, http://mediawiki.org, other sites and in the mediawiki code)
  • Your own repository of snippets for achieving common objectives in the mediawiki runtime environment

Other useful MediaWiki.org pages for getting started:

General PHP coding tips

Remember to set full reporting of errors and exceptions with the following addition to LocalSettings.php

ini_set( 'display_errors', 'on' );
ini_set( 'error_reporting', E_ALL );
$wgShowExceptionDetails = true;
$wgShowSQLErrors = true;

A simple way to exit and output the call stack at a particular point in any PHP code is to use this:

$e = new Exception;
var_dump( $e->getTraceAsString() );

To get a full trace log of functions called in a request, install xdebug and add the following configuration to your php.ini:

zend_extension=xdebug.so
xdebug.auto_trace=On
xdebug.trace_output_dir=/var/www/xdebug

For more details about how to use the xdebug package, see this tutorial.

Articles

Get wikitext content of an article

$wikitext = WikiPage::factory( Title::newFromText( $titleText ) )->getContent()->getNativeData();

Edit or create an article

Editing or creating an article are both achieved using the Article::doEdit method which takes three parameters (the third is optional). The first two are the text content and edit summary. The third optional parameter is for flags which adjust the operation. The available flags and their meaning are listed below followed by example usage.

  • EDIT_NEW: Article is known or assumed to be non-existent, create a new one
  • EDIT_UPDATE: Article is known or assumed to be pre-existing, update it
  • EDIT_MINOR: Mark this edit minor, if the user is allowed to do so
  • EDIT_SUPPRESS_RC: Do not log the change in recentchanges
  • EDIT_FORCE_BOT: Mark the edit a "bot" edit regardless of user rights
  • EDIT_DEFER_UPDATES: Defer some of the updates until the end of index.php
  • EDIT_AUTOSUMMARY: Fill in blank summaries with generated text where possible
WikiPage::factory( Title::newFromText( $titleText ) )->doEditContent(
	new WikitextContent( $wikiText ),
	$summary,
	EDIT_UPDATE | EDIT_MINOR
);

Image URLs

Given the name of an image page, obtain the full URL of the image.

$title = Title::newFromText( $v, NS_IMAGE );
$image = Image::newFromTitle( $title );
if( $image && $image->exists() ) {
	$url = $image->getURL();                              // Gets the URL for the image at its normal size
	$url_50px = $image->getThumbnail( 50, 50 )->getUrl(); // Gets the URL for the image at a specified size
}

Image links

Sometimes you might want to prevent images from linking to the image page. This extension function removes the link.

$wgHooks['OutputPageBeforeHTML'][] = 'removeImageLinks';
function removeImageLinks( &$out, &$text ) {
	$text = preg_replace( '|<a.+?class=.image.+?>.*?(<img.+?>).*?</a>|i', '$1', $text );
	return true;
}

Adding stub code to newly created articles

This will add {{stub}} to the beginning of article content when it is first created. It can then be removed on a subsequent edit if desired.

$wgHooks['ArticleSave'][] = 'wgAddStub';
function wgAddStub( &$article, &$user, &$text, &$summary, $minor, $watchthis, $sectionanchor, &$flags, &$status ) {
	$text = ( $article->exists() ? "" : "{{stub}}\n" ) . $text;
	return true;
}

Security

Only allow articles in a specific category to be editable

Probably the most common security-related LocalSettings hack is to allow pages in locked down wikis to be publicly viewable if they're in a particular category. This following example adds articles in Category:Public to the $wgWhitelistRead array.

$wgHooks['UserGetRights'][] = 'wfPublicCat';
function wfPublicCat() {
	global $wgWhitelistRead;
	$title = Title::newFromText( $_REQUEST['title'] );
	if( is_object( $title ) ) {
		$id   = $title->getArticleID();
		$dbr  = wfGetDB( DB_SLAVE );
		$cat  = $dbr->addQuotes( 'Public' );
		$cl   = $dbr->tableName( 'categorylinks' );
		if( $dbr->selectRow( $cl, '0', "cl_from = $id AND cl_to = $cat" ) ) $wgWhitelistRead[] = $title->getPrefixedText();
	}
	return true;
}

Add group information in body element classes

From MediaWiki 1.17 onwards, the OutputPageBodyAttributes hook can be used to modify the classes and other attributes of the body tag. In the following example the hook is being used to add classes to the body tag depending on whether the user is anonymous, logged-in or a sysop.

$wgHooks['OutputPageBodyAttributes'][] = 'wfAddBodyClasses';
function wfAddBodyClasses( $out, $sk, &$bodyAttrs ) {
	global $wgUser;
	if( $wgUser->isAnon() ) $bodyAttrs['class'] .= ' anon';
	if( $wgUser->isLoggedIn() ) $bodyAttrs['class'] .= ' user';
	if( in_array( 'sysop', $wgUser->getEffectiveGroups() ) ) $bodyAttrs['class'] .= ' sysop';
	return true;
}

Prevent users from editing other users pages

Here's an example which was created in response to a support-desk question. It prevents users from editing other users user-pages:

$wgExtensionFunctions[] = 'wfProtectUserPages';
function wfProtectUserPages() {
	global $wgUser, $wgGroupPermissions;
	$title = Title::newFromText( $_REQUEST['title'] );
	if( is_object( $title ) && $title->getNamespace() == NS_USER && $wgUser->getName() != $title->getText() )
		$wgGroupPermissions['user']['edit'] = false;
}

Restrict namespaces from anonymous users

Here is a similar example which restricts all namespaces except MAIN from anonymous users:

$wgExtensionFunctions[] = 'wfProtectNamespaces';
$wgWhitelistRead = [ 'Special:Userlogin', '-', 'MediaWiki:Monobook.css' ];
function wfProtectNamespaces() {
	global $wgUser,$wgGroupPermissions;
	$title = Title::newFromText( $_REQUEST['title'] );
	if( is_object( $title ) && $title->getNamespace() != 0 && $wgUser->isAnon() )
		$wgGroupPermissions['*']['read'] = false;
}

Redirect sysops to HTTPS

The following snippet causes all requests to be redirected to HTTPS if the user is a sysop. It also bounces users to non-HTTPS if not a sysop which can be useful if you don't want HTTPS links showing up in search engines for example if you have a self-signed certificate (it doesn't do this if the user is on the login page though since that confuses our bots that use HTTPS connections):

# Force HTTPS for sysops (and non-HTTPS for non-sysops)
$wgExtensionFunctions[] = 'wfSecureSysops';
function wfSecureSysops() {
	global $wgUser;
	if( in_array( 'sysop', $wgUser->getEffectiveGroups() ) ) {
		if( !isset( $_SERVER['HTTPS'] ) ) {
			header( "Location: https://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
			exit;
		}
	} else {
		if( isset( $_SERVER['HTTPS'] ) && ( !array_key_exists( 'title', $_REQUEST ) || $_REQUEST['title'] != 'Special:UserLogin' ) ) {
			header( "Location: http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
			exit;
		}
	}
}

Check whether or not a request is local

The following code checks if a request is local, and works regardless of the IP address or whether it's IPv4 or IPv6.

function isLocal() {
	return preg_match_all( "|inet6? addr:\s*([0-9a-f.:]+)|", `/sbin/ifconfig`, $matches ) && in_array( $_SERVER['REMOTE_ADDR'], $matches[1] );
}

Only allow raw HTML in a specific sysop-editable namespace

First set up that namespace in LocalSettings and protect it so that only sysops can edit it, for example:

define( 'NS_HTML',  5000 );
$wgExtraNamespaces[NS_HTML] = 'Html';
$wgExtraNamespaces[NS_HTML+1] = 'Html_talk';
$wgNamespaceProtection[NS_HTML] = [ 'html-edit' ];
$wgGroupPermissions['sysop']['html-edit'] = true;


Then the nest thing is to only allow Raw HTML to work in that namespace. It's turned on by default, and then disabled if the current title is not in the NS_HTML namespace:

$wgRawHtml = true;
$wgExtensionFunctions[] = 'wfHtmlNamespace';
function wfHtmlNamespace() {
    if( array_key_exists( 'title', $_REQUEST ) ) {
        $title = Title::newFromText( $_REQUEST['title'] );
        if( is_object( $title ) ) {
            global $wgRawHtml;
            $wgRawHtml = $title->getNamespace() == NS_HTML;
        }
    }
}

Returning content to the client

Raw wikitext content

This function returns the raw content specified in $text, it can be called any time from extension-setup or after. If the $save parameter is supplied it will bring up a download dialog with the default name set to $save, otherwise it will download and open unprompted. If $expand is set to true, then any templates, parser-functions or variables in the content will be expanded.

function raw( $text, $expand = false, $save = false ) {
	global $wgOut, $wgParser;
	if ( $expand ) $text = $wgParser->preprocess( $text, new Title(), new ParserOptions() );
	$wgOut->disable();
	wfResetOutputBuffers();
	header('Content-Type: application/octet-stream');
	if ( $save ) header( "Content-Disposition: attachment; filename=\"$save\"" );
	echo $text;
}

Return an HTTP error page

global $wgOut, $wgParser;
$wgOut->disable();
wfResetOutputBuffers();
header('HTTP/1.0 404 Not Found');
$err = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>404 Not Found</title></head>
	<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>';
echo $err;

Domain-based default redirect

If no page title is specified, redirect to a default depending on the domain name in the requested URL. In this example requests to abc.org or any of it's subdomains with no title specified will redirect to the Welcome to ABC article, and any requests to the exact domain of www.xyz.org without a title end up at the XYZ Home article. Titleless requests to any other domain which resolves to this example wiki will be unaffected and left to the rest of the configuration to deal with. This code should be executed early in the LocalSettings before any extensions are included, but after $wgServer is defined.

$d = $_SERVER['SERVER_NAME'];
$t = $_REQUEST['title'];
if ( empty($t) ) $t = ereg_replace('^/', '', $_SERVER['PATH_INFO'] );
if ( empty($t) || $t == 'Main_Page') {
	if ( ereg( 'abc.org$', $d) ) header( "Location: $wgServer/Welcome_to_ABC" ) && die;
	if ( $d == 'www.xyz.com')  header( "Location: $wgServer/XYZ_Home" )       && die;
}

Add a meta tags to the page

$wgExtensionFunctions[] = 'wfAddMetaTag';
function wfAddMetaTag() {
	global $wgOut;
	$wgOut->addMeta( 'name1', 'value1' );
	$wgOut->addMeta( 'name2', 'value2' );
}

Using the parser

Parse wikitext

The simplest way is via $wgOut, e.g.

$html = $wgOut->parse( $wikitext );

Or, if you want to have more control and use the parser directly,

$html = $wgParser->parse( $wikitext, $title, new ParserOptions(), true, true )->getText();

Expand templates only

$wikiText = $wgParser->preprocess( $wikiText, $title, new ParserOptions() );

Replace triple-brace arguments in wikitext content

$parser->replaceVariables( $wikiText, $args, true );

If $wikitext is set to "hello {{{you}}} this is {{{me}}}", then the "you" and "me" keys of the $args array will replace the corresponding triple-brace arguments in the wikitext content. This is a useful method to know because there are actually a number of difficulties involved in implementing it since it must account for both named and ordered parameters and default values (which can also contain brace-expressions).

Using named parameters from a parser-function callback

You can use named parameters in your parser functions, eg {{#drops:for=Rikkukin the Defender|zone=The Ascent|h=3}}, but you will need to manually split the args into key/value pairs in your callback function, such as in the following example code:

$args = [];
foreach ( $argv as $arg )
	if ( !is_object($arg) )
		preg_match( '/^(\\w+)\\s*=\\s*(.+)$/is', $arg, $match ) ? $args[$match[1]] = $match[2] : $args[] = $arg;

This snippet will create a hash from the arguments passed to your callback function (ignoring any which are objects such as the first one which is $parser). The resulting hash will contain numeric keys for all the normal non-named parameters in your parser-function, and non-numeric keys matching all the name=value parameters.

Return codes for parser functions

Typical example.

return [
	$text,
	'found'   => true,
	'nowiki'  => false,
	'noparse' => false,
	'noargs'  => false,
	'isHTML'  => false
];

See also: http://www.organicdesign.co.nz/Extension:Example

Protecting raw HTML output from modification by MediaWiki

See: How can I avoid modification of my extension's HTML output on mediawiki.org. Thanks to Duesentrieb for digging out this info.

Post processing to remove unwanted breaks and entities from output

function efFixEmptyLineBug( $parser, &$text ) {
	$text = preg_replace( '|<p>\s*<br\s*/>\s*</p>|s', '', $text );
	return true;
}

JavaScript

Here's some JavaScript snippets that can be added to MediaWiki:Common.js

Make sortable table states persistent with cookies

Note that this function requires getCookie and setCookie which are currently in our navbar.js. They're just the standard cookie getting and setting functions recommended by W3C.

addOnloadHook( function() {
    $('.sortable').each( function() {
        var id = $(this).attr('id');
        document.shCookie = getCookie('sortheader-'+id);
        document.sortheaderId = 0;
        $('#'+id+' a.sortheader').each( function() {
            var id = $(this).parent().parent().parent().parent().attr('id');
            var sh = document.sortheaderId++;
            if( sh+100 == document.shCookie ) { ts_resortTable(this); ts_resortTable(this); }
            if( sh == document.shCookie ) { ts_resortTable(this); sh += 100; }
            $(this).bind('click', {id: id, sh: sh}, function(e) {
                setCookie('sortheader-'+e.data.id, e.data.sh, 1);
            });
        });
    });
});

MediaWiki Environment

Article title

This should be called at an appropriate time such as from the OutputPageBeforeHTML hook.

$wgOut->setPageTitle( 'foo' );

Article queries

Info.svg See MW:Manual:Database access for additional information about database access in MediaWiki.


List article titles from a category

$list = [];
$dbr  = wfGetDB( DB_SLAVE );
$res  = $dbr->select( 'categorylinks', 'cl_from', [ 'cl_to' => $cat ], __METHOD__, [ 'ORDER BY' => 'cl_sortkey' ] );
foreach( $res as $row ) $list[] = Title::newFromID( $row->cl_from )->getPrefixedText();

Adjust the $list addition to your own needs. This example creates a title object for each and then calls the getPrefixedText method which returns the title as a string including namespace.

List categories an article belongs to

$list = [];
$dbr  = wfGetDB( DB_SLAVE );
$cl   = $dbr->tableName( 'categorylinks' );
$id   = Title::newFromText( 'article-title' )->getArticleID();
$res  = $dbr->select( $cl, 'cl_to', "cl_from = $id", __METHOD__, [ 'ORDER BY' => 'cl_sortkey' ] );
foreach( $res as $row ) $list[] = Title::newFromText( $row->cl_to )->getText();

Check if a title is in a category

function inCat( $title, $cat ) {
	if( !is_object( $title ) ) $title = Title::newFromText( $title );
	$cat = Title::newFromText( $cat )->getDBkey();
	$id  = $title->getArticleID();
	$dbr = wfGetDB( DB_SLAVE );
	return $dbr->selectRow( 'categorylinks', '1', [ 'cl_from' => $id, 'cl_to' => $cat ] );
}

Get a list of articles in a namespace (number)

function getArticlesInNamespace( $namespace ) {
	$dbr   = wfGetDB( DB_SLAVE );
	$list  = [];
	$table = $dbr->tableName( 'page' );
	$res   = $dbr->select( $table, 'page_title', "page_namespace = $namespace" );
	foreach( $res as $row ) $list[] = $row->page_title;
	return $list;
}

Get a list of articles using a template

function usesTemplate( $tmpl ) {
	$dbr  = wfGetDB( DB_SLAVE );
	$list = [];
	$res  = $dbr->select( 'templatelinks', 'tl_from', [ 'tl_namespace' => NS_TEMPLATE, 'tl_title' => $tmpl ] );
	foreach( $res as $row ) $list[] = Title::newFromID( $row->tl_from )->getPrefixedText();
	return $list;
}

Return the "owner" of the passed title (person who created it)

function getOwner( $title ) {
	$id = $title->getArticleID();
	$dbr = wfGetDB( DB_SLAVE );
	if( $id > 0 && $row = $dbr->selectRow( 'revision', 'rev_user', [ 'rev_page' => $id ], __METHOD__, [ 'ORDER BY' => 'rev_timestamp' ] ) )
		return User::newFromID( $row->rev_user )->getName();
	return false;
}

Remove category pages for empty categories

function removeEmptyCategories() {

	// Get all category pages
	$dbr   = wfGetDB( DB_SLAVE );
	$cats  = [];
	$table = $dbr->tableName( 'page' );
	$res   = $dbr->select( $table, 'page_title', "page_namespace = " . NS_CATEGORY );
	foreach( $res as $row ) $cats[] = $row->page_title;

	// Delete those which are for empty cats
	foreach( $cats as $cat ) {
		$dbr  = wfGetDB( DB_SLAVE );
		$cl   = $dbr->tableName( 'categorylinks' );
		$qcat = $dbr->addQuotes( Title::newFromText( $cat )->getDBkey() );
		if( !$dbr->selectRow( $cl, 'cl_from', "cl_to = $qcat" ) ) {
			$title = Title::newFromText( $cat, NS_CATEGORY );
			$article = new Article( $title );
			$article->doDelete( "Obsolete category page - this category contains no items" );
		}
	}
}

Article queries using DPL

This DPL query example provides a standard list of page links in wikitext bullet list format.

{{#dpl:category=Foo|format=,*[[%PAGE%]],\n,}}

Misc

examineBraces

This function returns an array of the brace structure found in the passed wikitext parameter. This has also been implemented in Perl, see the wikiExamineBraces function in wiki.pl.

function examineBraces( &$content ) {
	$braces = [];
	$depths = [];
	$depth = 1;
	$index = 0;
	while( preg_match( '/\\{\\{\\s*([#a-z0-9_]*:?)|\\}\\}/is', $content, $match, PREG_OFFSET_CAPTURE, $index ) ) {
		$index = $match[0][1] + 2;
		if( $match[0][0] == '}}' ) {
			if( $depth > 0 ) {
				$brace =& $braces[$depths[--$depth]];
				$brace[LENGTH] = $match[0][1] - $brace[OFFSET] + 2;
				$brace[DEPTH]  = $depth;
			}
		}
		else {
			$depths[$depth++] = count( $braces );
			$braces[] = [
				NAME   => $match[1][0],
				OFFSET => $match[0][1]
			];
		}
	}
	return $braces;
}


The following input,

foo{{#bar:baz|biz{{foo|shmoo}}}}{{moo}}baz


Gives the following array:

Array(
    [0] => Array(
        [NAME]   => #bar
        [OFFSET] => 3
        [LENGTH] => 29
        [DEPTH]  => 1
        )

    [1] => Array(
        [NAME]   => foo
        [OFFSET] => 17
        [LENGTH] => 13
        [DEPTH]  => 2
        )

    [2] => Array(
        [NAME]   => moo
        [OFFSET] => 32
        [LENGTH] => 7
        [DEPTH]  => 1
        )
    )

The array output is designed to integrate with the substr_replace function arguments subject, replace, offset, length. The index 0, 1, 2 order referes to the order functions are found (from left to right).

Google Analytics

$wgExtensionFunctions[] = 'wfGoogleAnalytics';
function wfGoogleAnalytics() {
	global $wgOut;
	$wgOut->addScript(
		'<script type="text/javascript">
		var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
		document.write(unescape("%3Cscript src=\'" + gaJsHost + "google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E"));
		</script><script type="text/javascript">
		var pageTracker = _gat._getTracker("INSERT YOUR TRACKING CODE HERE");
		pageTracker._trackPageview();</script>'
	);
}

Force all headings to use outline numbering

There is a user-preference to make all headings use outline numbering, but no way to make that a default for all users. Here's a few lines of code which can be added to your LocalSettings.php file which do that.

$wgExtensionFunctions[] = 'wfNumberHeadings';
function wfNumberHeadings() {
	global $wgUser;
	$wgUser->setOption('numberheadings', true);

Create a sysop account from LocalSettings.php

If you don't have admin access to a wiki, but you do have access to the LocalSettings.php file you can create a sysop account by adding the following snippet. Note that this is only temporary and after the snippet is removed the user will no longer have admin access, so to make it permanent, go to Special:UserRights and change the group membership for your user (you may have to set "bot" or something for this since both "sysop" and "beauracrat" will be checked). You can then remove the snippet from LocalSettings.php (and then remove yourself from the "bot" group if you had to add that).

$wgExtensionFunctions[] = 'wfTemp';
function wfTemp() {
	global $wgUser;
	if( $wgUser->getName() == 'USERNAME' ) $wgUser->addGroup( 'bureaucrat' );
}

Manipulating the search index for an article

The output of parser-functions are not included in the search indexing, so here's a snippet to add custom phrases to the index when an article is saved.

function onArticleSave( &$article, &$user, &$text, &$summary, $minor, $watchthis, $sectionanchor, &$flags, &$status ) {
	global $wgHooks;
	$wgHooks['SearchUpdate'][] = 'updateSearchIndex';
	$u = new SearchUpdate( $article->getId(), $article->getTitle()->getPrefixedDBkey() );
	$u->doUpdate();
	return true;
}

function updateSearchIndex( $id, $namespace, $title, $text ) {
	$text .= "\nSOME SEARCH PHRASE\n";
	return true;
}

Integrating with the Skin

Adding a new action tab

The following code adds a new action tab with a corresponding link. To process the new action, add a processing function to the UnknownAction hook.

$wgExampleAction = 'example';

$wgHooks['SkinTemplateTabs'][] = 'wfAddExampleTab';

function wfAddExampleTab( &$skin, &$actions ) {
	global $wgTitle, $wgRequest, $wgExampleAction;
	$selected = $wgRequest->getText( 'action' ) == $wgExampleAction ? 'selected' : false;
	$url      = $wgTitle->getLocalURL( "action=$wgExampleAction" );
	if (is_object( $wgTitle ) ) {
		$actions[$wgExampleAction] = [
			'text'  => $wgExampleAction, # should use wfMsg( $wgExampleAction )
			'class' => $selected,
			'href'  => $url
		];
	}
	return true;
}


The Vector skin (optional on 1.16, default on 1.17 onwards) uses a new hook, SkinTemplateNavigation. You can cover both Vector and earlier skins like Monobook or Modern by including code for both hooks. The example below uses the MediaWiki namespace to name the relevant elements of the array. You can put this code in LocalSettings after the inclusion of extensions.

$wgHooks['SkinTemplateTabs'][] = 'onSkinTemplateTabs';
$wgHooks['SkinTemplateNavigation'][] = 'onSkinTemplateNavigation';

function onSkinTemplateTabs( $skin, &$actions ) {
	$actions[wfMsg( 'edit-addtab-id' )] = [
		'class' => wfMsg( 'edit-addtab-class' ),
		'text' => wfMsg( 'edit-addtab-text' ),
		'href' => wfMsg( 'edit-addtab-href' )
	];
	return true;
}

function onSkinTemplateNavigation( &$skin, &$contentActions ) {
	$contentActions['views'][wfMsg( 'edit-addtab-id' )] = [
		'class' => wfMsg( 'edit-addtab-class' ),
		'text' => wfMsg( 'edit-addtab-text' ),
		'href' => wfMsg( 'edit-addtab-href' )
	];
	return true;
}

Wikitext in Sidebar

By default the MediaWiki:Sidebar article content is not normal wikitext. This snippet fixes that and also ensures that the content can fall back to an i18n message if the article is not present - the normal behaviour for articles in the MediaWiki namespace. The simplest way is to replace a section such as toolbox or navlinks in the skins/MonoBook.php file with the following:

global $wgTitle, $wgOut;
$title = 'sidebar'; # note the lcfirst is important here since it's also a msg key
$article = new Article( Title::newFromText( $title, NS_MEDIAWIKI ) );
$text = $article->getContent();
if ( empty( $text ) ) $text = wfMessage( $title )->text();
echo $wgOut->parse( $text );


The procedure is the same with Vector-based skins, the change is you now place the code inside divs of id portal and then id body, as per the other examples in skins/Vector.php. It should replace the following code and/or the toolbox:

<?php $this->renderPortals( $this->data['sidebar'] ); ?>


For MediaWiki 1.18 and above

It often not desirable to modify the codebase, so a preferable method is to attach a function to an appropriate hook that does something similar to the above example. As of MediaWiki-1.18, I've found the best way is to attach a function to the BeforePageDisplay hook by adding the following snippet to your LocalSettings.php file:

$wgHooks['BeforePageDisplay'][] = function( $out, $skin ) {
	$page = WikiPage::factory( Title::newFromText( 'SidebarTree', NS_MEDIAWIKI ) );
	$html = $out->parseAsContent( $page->getContent()->getNativeData() );
	$out->addHTML( "<div id=\"wikitext-sidebar\">$html</div>" );
	return true;
};


This creates a CSS-addressable div element containing the parsed content from the MediaWiki:Sidebar article. You can then use some JavaScript added to your MediaWiki:Common.js to move the element into a more appropriate location in the page DOM. For example the following JavaScript snippet inserts the rendered wikitext below the site logo.

var tree = $('#wikitext-sidebar').parent();
$('#p-logo').after( tree.html() );
tree.html('');

Development server identification

The idea is to have a picture or text that allows a developer to quickly see that they are on the development instance and not the production instance of MediaWiki. By adding to the content of $wgSiteNotice in a conditional every article will have the change for that domain map.

if( $wgServer == "http://localhost" )  # or any other domain
  $wgSiteNotice = '<span style="position:absolute;top:0px;left:-140px;z-index:10">[[Image:Gnome-devel.png|100px]]</span>';

This approach was based on W:User:East718/include, and the image source is Image:Gnome-devel.svg

Importing and Exporting articles

The following snippet imports articles from the XML export file named in $file into the main namespace. The $resultCount variable holds the number of articles imported on success.

$source = ImportStreamSource::newFromFile( $file );
$importer = new WikiImporter( $source );
$importer->setTargetNamespace( NS_MAIN );
$reporter = new ImportReporter( $importer, false, false, false );
$reporter->open();
$result = $importer->doImport();
$resultCount = $reporter->close();
if( WikiError::isError( $result ) ) die( $result->getMessage() );
elseif( WikiError::isError( $resultCount ) ) die( $resultCount->getMessage() );


This one exports the current revision of all articles whose names are contained in the $pages array to the file named in $file.

$dbr = wfGetDB( DB_SLAVE );
$exporter = new WikiExporter( $dbr, WikiExporter::CURRENT, WikiExporter::BUFFER );
$exporter->list_authors = false;
$exporter->sink = new DumpFileOutput( $file );
$exporter->openStream();
foreach( $pages as $page ) {
	$title = Title::newFromText( $page );
	$exporter->pageByTitle( $title );
}
$exporter->closeStream();
fclose( $exporter->sink->handle );


Editing Tools

Display WYSIWYG (Rich Text) Editing Toolbar by Default

Mediawiki 1.16

In MediaWiki 1.16, you need to install the UsabilityInitiative Extension, load custom JavaScript and add a jQuery snippet to enable the advanced editing functionality. You can manually load jQuery (if it is not already loaded elsewhere) and a JavaScript file in the skin to accomplish this:

$out->addScriptFile($wgStylePath .'/path-to-js/jquery.js');
$out->addScriptFile($wgStylePath .'/path-to-js/onload.js');


Then just create onload.js, paste in this code and save it:

$(document).ready(function() {
	if($('.wikiEditor-ui-toolbar')) {
		$('.wikiEditor-ui-toolbar .sections .section-advanced').css('display','block');
	}
	$( '.ns-talk .lqt-j-togglepage' ).css( 'display', 'block' );
});


MediaWiki 1.18

It is recommended that MediaWiki 1.18 or above be used - in MediaWiki 1.18 the process is simpler and you need no JavaScript. The WikiEditor extension comes with MediaWiki, you just need to enable it in LocalSettings:

require_once( "$IP/extensions/WikiEditor/WikiEditor.php" );


You can also make the WYSIWYG wiki editor global for all users (so they don't need to set it in Preferences) by adding to LocalSettings:

$wgDefaultUserOptions['usebetatoolbar'] = true; 
$wgDefaultUserOptions['usebetatoolbar-cgd'] = true;


Finally, you can enable the additional Advanced toolbar by default. In LocalSettings, add:

$wgExtensionFunctions[] = 'wfAdvancedUI';
function wfAdvancedUI() {
	global $wgResourceModules, $wgStylePath, $IP, $wgOut;
	$wgOut->addStyle( 'your-skin-directory/advanced.ui.css', 'screen' );
}


Then create the file advanced.ui.css, add the following code and save it to the path you have specified above:

.wikiEditor-ui-toolbar .sections {
	display: block;
	overflow-x: hidden;
	overflow-y: hidden;
	height: 33px;
}

.wikiEditor-ui-toolbar .sections .section {
	display: block;
}


This overrides the CSS settings in load.php, which has the Advanced toolbar hidden by default, even when the WYSIWYG is installed.

JavaScript and AJAX

Deprecations, issues and new features

See also