Manual:Desenvolvendo extensões
Muitas vezes, alguns projetos acham utilidade em ampliar a marcação wiki integrada com capacidades adicionais, seja num simples processamento de strings, seja numa coleta total de dados.
Extensões de marcação permitem criar marcações (tags) personalizadas precisamente para fazer isso.
A título de exemplo, pode-se usar uma dessas extensões para adicionar um simples <donation />
, que injeta um formulário de doação à página.
Extensões, junto com funções do analisador sintático e hooks são a maneira mas efetiva de modificar ou ampliar a funcionalidade do MediaWiki.
Sempre confira a matriz antes de começar a trabalhar numa extensão para certificar-se de que ninguém já tenha feito o que você está tentando fazer.
Uma extensão de marcação simples consiste de uma função de callback, que se liga por meio de um hook ao analisador sintático (parser) para que, quando o último for executado, ele ache e substitua todas as instâncias de uma tag específica, chamando a função de callback correspondente para renderizar o novo HTML.
Exemplo
Partes dessa página (aquelas relacionadas a Section) estão desatualizadas. |
In extension.json , set up the hooks:
...
"Hooks": {
"ParserFirstCallInit": "ExampleExtensionHooks"
},
"HookHandlers": {
"ExampleExtensionHooks": {
"class": "MediaWiki\\Extension\\ExampleExtension\\Hooks"
}
}
...
And add the hook into a PHP file
<?php
namespace MediaWiki\Extension\ExampleExtension;
class ExampleExtension implements ParserFirstCallInitHook {
// Registra ''callbacks'' ao renderizador com o analisador sintático
public function onParserFirstCallInit( $parser ) {
// Quando o analisador sintático vê a marcação <sample>, ele executa a função renderTagSample (veja abaixo)
$parser->setHook( 'sample', [ $this, 'renderTagSample' ] );
}
// Render <sample>
public function renderTagSample( $input, array $args, Parser $parser, PPFrame $frame ) {
// Nada de novo aqui, a entrada do usuário é retornada (como exemplo)
return htmlspecialchars( $input );
}
}
Esse exemplo registra uma função de callback para a marcação <sample>
.
Quando um usuário adicionar essa marcação a uma página assim: <sample arg1="xxx" arg2="xxx">...input...</sample>
, o analisador sintático chamará a função renderTagSample()
, aplicando quatro argumentos:
- $input
- Entrada entre as marcações
<sample>
e</sample>
ou null se ela estiver “fechada” (<sample />
). - $args
- Argumentos da marcação, inseridos como atributos HTML; é um arranjo associativo indexado por nome do atributo.
- $parser
- O analisador pai (um objeto Parser); extensões mais avançadas usam isso para obter o título contextual, analisar wikitexto, expandir chaves, registrar relações e dependências de links, etc.
- $frame
- The parent frame (a PPFrame object). This is used together with $parser to provide the parser with more complete information on the context in which the extension was called.
For a more elaborate example, see Tag extension example
Atributos
Let's look at another example:
<?php
$wgHooks['ParserFirstCallInit'][] = 'onParserFirstCallInit';
function onParserFirstCallInit( Parser $parser ) {
$parser->setHook( 'sample', 'wfSampleRender' );
}
function wfSampleRender( $input, array $args, Parser $parser, PPFrame $frame ) {
$attr = [];
// This time, make a list of attributes and their values, and dump them, along with the user input
foreach( $args as $name => $value ) {
$attr[] = '<strong>' . htmlspecialchars( $name ) . '</strong> = ' . htmlspecialchars( $value );
}
return implode( '<br />', $attr ) . "\n\n" . htmlspecialchars( $input );
/**
* The following lines can be used to get the variable values directly:
* $to = $args['to'] ;
* $email = $args['email'] ;
*/
}
This example dumps the attributes passed to the tag, along with their values.
It's quite evident that this allows for flexible specification of new, custom tags.
You might, for example, define a tag extension that allows a user to inject a contact form on their user page, using something like <emailform to="User" email="user@foo.com" />
.
There is a veritable plethora of tag extensions available for MediaWiki, some of which are listed on this site; others can be found via a quick web search. While a number of these are quite specialised for their use case, there are a great deal of well-loved and well-used extensions providing varying degrees of functionality.
Convenções
See Manual:Desenvolvendo extensões for the general layout and setup of an extension.
Publicar as suas extensões
It has been suggested that this page or section be merged with Manual:Developing extensions#Deploying and registering#Publishing.(Discuss) |
- Create a new page on this wiki named Extension:<extension_name> with information on your extension, how to install it, and screenshots of it in use.
A convenient template has been created to hold this information called Predefinição:Extensão . See the template page for more information. You should also add as much detail as possible to the body of the page, and it is wise to check back fairly regularly to respond to user questions on the associated talk page. Also, make sure the page belongs to Categoria:Extensões .
- Extensions that create new hooks within the extension code should register them in the extension hook category.
- Notify the mediawiki-l mailing list.
See also publishing your extension.
FAQ
Security concerns
You'll notice above that the input in the examples above is escaped using htmlspecialchars()
before being returned.
It is vital that all user input is treated in this manner before echoing it back to the clients, to avoid introducing vectors for arbitrary HTML injection, which can lead to cross-site scripting vulnerabilities.
Loading modules
The right way to add modules for your extension is to attach them to the ParserOutput rather than to $wgOut. The module list will then be automatically taken from the ParserOutput object and added to $wgOut even when the page rendering is pre-cached. If you are directly adding the modules to $wgOut they might not be cached in the parser output.
function myCoolHook( $text, array $params, Parser $parser, PPFrame $frame ) {
// ... do stuff ...
$parser->getOutput()->addModules( 'ext.mycoolext' );
$parser->getOutput()->addModuleStyles( 'ext.mycoolext.styles' );
// ... do more stuff ...
}
Timing and extensions
If you change the code for an extension, all pages that use the extension will, theoretically, immediately reflect the results of new code. Technically speaking, this means your code is executed each and every time a page containing the extension is rendered.
In practice, this is often not the case, due to page caching - either by the MediaWiki software, the browser or by an intermediary proxy or firewall.
To bypass MediaWiki's parser cache and ensure a new version of the page is generated, click on edit, replace "action=edit" in the URL shown in the address bar of your browser by "action=purge" and submit the new URL. The page and all templates it references will be regenerated, ignoring all cached data. The purge action is needed if the main page itself is not modified, but the way it must be rendered has changed (the extension was modified, or only a referenced template was modified).
If this is not sufficient to get you a fresh copy of the page, you can normally bypass intermediary caches by adding '&rand=somerandomtext' to the end of the above URL. Make sure 'somerandomtext' is different every time.
How do I disable caching for pages using my extension?
Since MediaWiki 1.5, the parser is passed as the third parameter to an extension. This parser can be used to invalidate the parser cache like this:
function wfSampleSomeHookFunction( $text, array $args, Parser $parser, PPFrame $frame ) {
$parser->getOutput()->updateCacheExpiry(0);
// ...
}
Regenerating the page when another page is edited
Maybe you don't want to disable caching entirely, you just want the page to be regenerated whenever another page is edited, similar to the way that template transclusions are handled.
This can be done using the parser object that is passed to your hook function and calling the addTemplate
.
Fine grained adjustment of caching behavior
You can use fine grained caching for your extension by using cache keys to differentiate between different versions of your extension output. While rendering you can add cache keys for every feature by adding an addExtraKey method to your hook function, e.g.:
function wfSampleSomeHookFunction( $text, array $args, Parser $parser, PPFrame $frame ) {
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
$setting1= (int)$userOptionsLookup->getOption( $parser->getUserIdentity(), 'setting1' );
$parser->getOptions()->optionUsed( 'setting1' );
$setting2= (int)$userOptionsLookup->getOption( $parser->getUserIdentity(), 'setting2' );
$parser->getOptions()->optionUsed( 'setting2' );
...
}
However, modifying $parser->getOptions()
during parse means that the extra option keys aren't included when trying to get a cached page, only when rendering a page to go into cache, so you can use the PageRenderingHash hook to set extra options.
PageRenderingHash is run both when putting a page into cache, and getting it out, so its important to only add new keys to the hash if they're not already there.
e.g:
$wgHooks['PageRenderingHash'][] = 'wfMyExtOnPageRenderingHash';
function wfMyExtOnPageRenderingHash( &$confstr, $user, $optionsUsed ) {
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
if ( in_array( 'setting1', $optionsUsed ) ) {
$confstr .= "!setting1=" . $userOptionsLookup->getOption( $user, 'setting1' );
}
if ( in_array( 'setting2', $optionsUsed ) ) {
$confstr .= "!setting2=" . $userOptionsLookup->getOption( $user, 'setting2' );
}
}
Some important notes on this:
- Using "!setting1=$value" instead of just "!$value" in the confstr ensures that the parser cache does not become messed up if different extensions are installed or their load order changes.
! is used a separator for different rendering options
- Some people use
$parser->getOptions()->addExtraKey()
instead of$parser->getOptions()->optionUsed()
.
Be warned that addExtraKey does not tell the parser cache that the extra key is in use, and thus can easily result in breaking the cache if you are not careful.
How do I render wikitext in my extension?
Since version 1.16
Versão MediaWiki: | ≥ 1.16 |
Parser hook functions are passed a reference to the parser object and a frame object; these should be used to parse wikitext.
function wfSampleWonderfulHook( $text, array $args, Parser $parser, PPFrame $frame ) {
$output = $parser->recursiveTagParse( $text, $frame );
return '<div class="wonderful">' . $output . '</div>';
}
Parser::recursiveTagParse()
has been around since version 1.8.
Its advantages include simplicity (it takes just one argument and returns a string) and the fact that it parses extension tags in $text
, so you can nest extension tags.
The second parameter to recursiveTagParse, $frame
, is an optional argument introduced in MW 1.16 alpha (r55682).
- If
$frame
is provided (using the value of$frame
passed to your extension), then any template parameters in$text
will be expanded.
In other words, content such as {{{1}}}
will be recognized and converted into the appropriate value.
- If
$frame
is not provided (e.g.,$parser->recursiveTagParse( $text )
), or if$frame
is set to false, then template parameters will not be expanded;{{{1}}}
will not be altered.
Although this unlikely to be the desired behavior, this was the only option available before MW 1.16.
However, one step of parsing that is still skipped for tags, even when using recursiveTagParse, is Parser::preSaveTransform
.
preSaveTransform is the first step of parsing, responsible for making permanent changes to the about-to-be saved wikitext, such as:
- Converting signatures
(~~~, ~~~~, ~~~~~)
- Expanding link labels, also known as the pipe-trick (e.g., changing [[Help:Contents|]] into [[Help:Contents|Contents]]).
Without this step, shorthand links such as [[Help:Contents|]] are considered to be invalid, and are left in their wikitext form when parsed.
- Expanding {{subst:}} templates.
The original call to preSaveTransform intentionally skips such conversions within all extension tags.
If you need pre save transform to be done, you should consider using a parser function instead.
All tag extensions can also be called as a parser function using {{#tag:tagname|input|attribute_name=value}}
which will have pre save transform applied.
How can I pass XML-style parameters in my extension tag?
Since version 1.5
Since MediaWiki 1.5, XML-style parameters (tag attributes) are supported.
The parameters are passed as the second parameter to the hook function, as an associative array.
The value strings have already had HTML character entities decoded for you, so if you emit them back to HTML, don't forget to use htmlspecialchars( $codeToEncode, ENT_QUOTES )
, to avoid the risk of HTML injection.
How can I avoid modification of my extension's HTML output?
The return value of a tag extension is considered almost parsed text, which means its not treated as pure html, but still modified slightly. There are two main things that are done to the output of a tag extension (Along with a couple other minor things):
- Replace strip markers.
Strip markers are certain items which are inserted at various stages of processing wikitext to act as a marker to re-insert removed content at a later time. This is not something extensions usually need to worry about.
- Parser::doBlockLevels which turns *'s into lists, and turns any line starting with a leading space into a
<pre>
among other things.
This can sometimes be an issue in some extensions.
Tag extensions also support returning an array instead of just a string (Much like parser functions) in order to change how the return value is interpreted.
The 0th value of the array must be the HTML.
The "markerType" key can be set to nowiki
in order to stop further parsing.
Doing something like return [ $html, 'markerType' => 'nowiki' ];
would ensure that the $html value is not further modified and treated as just plain html.
How do I get my extension to show up on Special:Version?
In order for your extension to be displayed on the MediaWiki Special:Version page, you must assign extension credits within the PHP code.
To do this, add a $wgExtensionCredits
variable as the first executable line of code before the hook line or function definition.
An example extension credit is:
<?php
/**
* ExampleExtension - this extension is an example that does nothing
*
* To activate this extension, add the following into your LocalSettings.php file:
* require_once('$IP/extensions/Example.php');
*
* @ingroup Extensions
* @author John Doe <john.doe@example.com>
* @version 1.0
* @link https://www.mediawiki.org/wiki/Extension:MyExtension Documentation
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
/**
* Protect against register_globals vulnerabilities.
* This line must be present before any global variable is referenced.
*/
if( !defined( 'MEDIAWIKI' ) ) {
echo( "This is an extension to the MediaWiki package and cannot be run standalone.\n" );
die( -1 );
}
// Extension credits that will show up on Special:Version
$wgExtensionCredits['validextensionclass'][] = array(
'path' => __FILE__,
'name' => 'Example',
'version' => '1.0',
'author' => 'John Doe',
'url' => 'https://www.mediawiki.org/wiki/Extension:MyExtension',
'descriptionmsg' => 'example-desc', // Message key in i18n file.
'description' => 'This extension is an example and performs no discernible function'
);
$wgExtensionMessagesFiles[] = __DIR__ . '/Example.i18n.php';
// Here is where we set up our extension
function wfExample(){
// ...
}
Replace validextensionclass
with one of the following (unless your extension falls under multiple classes—then create a credit for each class):
- 'specialpage'—reserved for additions to MediaWiki Special Pages;
- 'parserhook'—used if your extension modifies, complements, or replaces the parser functions in MediaWiki;
- 'variable'—extension that add multiple functionality to MediaWiki;
- 'media'—used if your extension is a media handler of some sort
- 'other'—all other extensions.
The myextensionmsg
is the name of an interface/i18n message that describes your extension that will need to be defined in your extension's i18n.php file.
If you omit this field, the description
field will be used instead.
Retrieving the tag name inside of the callback
Suppose you have several tags <foo>
and <bar>
that share the same callback, and inside the callback function, you want to obtain the name of the tag that invoked the callback.
$wgHooks['ParserFirstCallInit'][] = 'onParserFirstCallInit';
# ...
public function onParserFirstCallInit( Parser $parser ) {
$parser->setHook( 'foo', 'sharedFunctionality' );
$parser->setHook( 'bar', 'sharedFunctionality' );
}
# ...
public function sharedFunctionality( $input, array $args, Parser $parser, PPFrame $frame ) {
// How to distinguish between 'foo' and 'bar' calls?
}
The short answer is: the tag name (foo
or bar
) is not present in any of the callback's arguments.
But you can work around this by dynamically constructing a separate callback for each tag:
$wgHooks['ParserFirstCallInit'][] = 'onParserFirstCallInit';
# ...
public function onParserFirstCallInit( Parser $parser ) {
// For each tag name
foreach ( [ 'foo', 'bar' ] as $tagName ) {
// Dynamically create a callback function
$callback = function( $input, $args, $parser, $frame ) use ( $tagName ) {
// The callback invokes the shared function.
// Notice we now pass the tag name as a parameter.
return sharedFunctionality( $input, $args, $parser, $frame, $tagName );
};
// Assign the callback to the tag
$parser->setHook( $tagName, $callback );
}
}
# ...
public function sharedFunctionality( $input, array $args, Parser $parser, PPFrame $frame, $tagName) {
// Now we can retrieve the tag name and perform custom actions for that tag
switch ( $tagName ) {
//...
}
}
Toolbar buttons
Extensão:WikiEditor provides an editing toolbar, allowing users to add tags into their editor by simply clicking a button.
If you want a toolbar button for your new tag, create a file named something like toolbar-button.js
in your extension's resources
folder. The file should look like this:
var customizeToolbar = function () {
$('#wpTextbox1').wikiEditor('addToToolbar', {
section: 'main',
group: 'format',
tools: {
"ExtensionName": { // replace with the name of your extension
label: 'TagName', // replace with the label that should appear when hoving the button
type: 'button',
icon: "extensions/ExtensionName/images/button-image.svg", // path to the image that should go on the button
action: {
type: 'encapsulate',
options: {
pre: "<tagName>", // tags that get inserted when the button is clicked
post: "</tagName>"
}
}
}
}
});
};
/* Check if view is in edit mode and that the required modules are available. Then, customize the toolbar … */
if ( [ 'edit', 'submit' ].indexOf( mw.config.get( 'wgAction' ) ) !== -1 ) {
mw.loader.using( 'user.options' ).then( function () {
// This can be the string "0" if the user disabled the preference ([[phab:T54542#555387]])
if ( mw.user.options.get( 'usebetatoolbar' ) == 1 ) {
$.when(
mw.loader.using( 'ext.wikiEditor' ), $.ready
).then( customizeToolbar );
}
} );
}
Once you've created the file, you need to register it with Resource Loader so it will be delivered to visitors; this is done by editing your extension.json
:
"Hooks": {
"BeforePageDisplay": "ExtensionName::onBeforePageDisplay"
}
"ResourceModules": {
"ext.ExtensionName": {
"scripts": ["toolbarButton.js"]
}
}
Then, in your PHP file:
public static function onBeforePageDisplay( OutputPage $out ) {
$out->addModules( [ 'ext.ExtensionName' ] );
}
See also
- Ajuda:Palavras mágicas – List of special tag/variables like {{PAGENAME}}, {{SERVER}}, ...
- Parser extension tags – List of parser tags in use on Wikimedia wikis.
- Manual:Extensões
- Extensions FAQ
- Categoria:Extensões
- Manual:$wgExtensionFunctions