Extensions de balise

From mediawiki.org
This page is a translated version of the page Manual:Tag extensions and the translation is 100% complete.
Extensions MediaWiki

Les projets individuels trouveront souvent utile d' étendre le marquage embarqué du wiki avec des possibilités supplémentaires, que ce soit un simple traitement de chaîne de caractères, ou la récupération complète d'informations. Les extensions de balise permettent aux utilisateurs de créer de nouvelles balises personnalisées qui font justement cela. Par exemple, on pourrait utiliser une extension de balise pour présenter une simple balise ‎<donation />, qui injecte dans la page, un formulaire pour faire un don. Les extensions, ainsi que les fonctions Parser et les accroches sont la manière la plus efficace de modifier ou d'étendre les fonctionalités de MediaWiki. Vous devriez toujours vérifier la matrice avant de commencer un travail sur une extension pour vous assurer que quelqu'un d'autre n'a pas déja fait exactement ce que vous essayez de faire.

Une simple extension de balise comprend une fonction de callback, qui est accrochée à l'analyseur de sorte que, lorsque celui-ci est appelé, il va trouver et remplacer toutes les instances d'une balise donnée, par un appel à la fonction de callback associée pour générer le HTML actuel.

Exemple

Dans extension.json, déclarer les accroches :

...
  "Hooks": {
    "ParserFirstCallInit": "ExampleExtension::onParserFirstCallInit"
   },
...

Et ajoutez l'accroche dans votre fichier PHP :

<?php

class ExampleExtension {
	// Enregistrer les fonctions callback de rendu avec l'analyseur
	public static function onParserFirstCallInit( Parser $parser ) {
		// Quand l'analyseur syntaxique voit la balise <sample>, il exécute renderTagSample (voir ci-dessous)
		$parser->setHook( 'sample', [ self::class, 'renderTagSample' ] );
	}

	// Render <sample>
	public static function renderTagSample( $input, array $args, Parser $parser, PPFrame $frame ) {
		// Rien d'excitant ici, échappez simplement l'entrée fournie par l'utilisateur et réinjectez-la de nouveau (par exemple)
		return htmlspecialchars( $input );
	}
}

Cet exemple enregistre une fonction de callback pour la balise ‎<sample>. Quand un utilisateur ajoute cette balise à une page comme cela : <sample arg1="xxx" arg2="xxx">...input...</sample>, l'analyseur va appeler la fonction renderTagSample() , en passant quatre arguments :

$input
Les entrées entre les balises ‎<sample> et ‎</sample> , ou null si la balise est « fermée », c'est à dire ‎<sample />
$args
Arguments de la balise, qui sont entrés comme des attributs de balise HTML; c'est un tableau associatif indexé par le nom de l'attribut.
$parser
L'analyseur parent (un objet Parser); les extensions plus avancées utilisent cela pour obtenir le titre contextuel, analyser le texte wiki, développer les accolades, enregistrer les relations des liens et les dépendances, etc.
$frame
Le cadre parent (un objet PPFrame). Ceci est utilisé avec $parser pour fournir à l'analyseur des informations plus complètes sur le contexte dans lequel l'extension a été appelée.

Pour un exemple plus élaboré, voir Exemple d'extension de balise

Attributs

Voyons un autre exemple :

<?php

$wgHooks['ParserFirstCallInit'][] = 'onParserFirstCallInit';

function onParserFirstCallInit( Parser $parser ) {
	$parser->setHook( 'sample', 'wfSampleRender' );
}

function wfSampleRender( $input, array $args, Parser $parser, PPFrame $frame ) {
	$attr = [];    
	// Cette fois, créez une liste d'attributs avec leurs valeurs et listez les en même temps que l'entrée utilisateur
	foreach( $args as $name => $value ) {
		$attr[] = '<strong>' . htmlspecialchars( $name ) . '</strong> = ' . htmlspecialchars( $value );
	}
	return implode( '<br />', $attr ) . "\n\n" . htmlspecialchars( $input );

/**
 * Les lignes suivantes peuvent être utilisées pour obtenir directement la valeur des variables :
 * $to = $args['to'] ;
 * $email = $args['email'] ;
 */
}

Cette exemple fournit les attributs passés à la balise ainsi que leur valeur. Il est bien évident que cela permet une spécification plus souple des nouvelles balises personnalisées. Vous pourriez par exemple, définir une extension de balise qui autorise un utilisateur à injecter un formulaire de sur sa page utilisateur, en utilisant quelque chose comme <emailform to="User" email="user@foo.com" />.

Il existe une véritable pléthore d'extensions de balises disponibles pour MediaWiki, dont certaines sont listées sur ce site; les autres peuvent être trouvées par une recherche rapide sur le web. Alors qu'un nombre d'entre elles est particulièrement spécialisé pour leur cas d'utilisation, elles sont un grand défit en regard des extensions bien aimées et régulièrement utilisées fournissant différents degrés de fonctionalité.

Conventions

Voir Développement d'extensions pour la présentation générale et la configuration d'une extension.

Publier vos extensions

  1. Créez une nouvelle page sur ce wiki appelée Extension:<extension_name> avec les informations sur votre extension, comment l'installer, et les captures d'écran sur son utilisation en cours. Un modèle adapté a été créé pour contenir cette information appelé Modèle:Extension . Voir la page du modèle pour plus d'informations. Vous devez aussi ajouter autant de détails que possible au corps de la page, et il est sage de la vérifier assez régulièrement pour répondre aux questions des l'utilisateurs sur la page de discussion associée. Assurez-vous également que la page appartient à Catégorie:Extensions .
  2. Les extensions qui créent de nouvelles accroches dans le code de l'extension doivent les enregistrer dans la catégorie des accroches d'extension.
  3. Avertissez la liste de diffusion mediawiki-l .

Voir aussi publier votre extension.

FAQ

Problèmes de sécurité

Vous remarquerez ci-dessus que l'entrée des exemples est échappée en utilisant htmlspecialchars() avant d'être rendue. Il est vital que toutes les entrées utilisateur soient traitées de cette manière avant de les retourner aux clients, pour éviter d'introduire des vecteurs d'injection de code HTML arbitraire, qui peut conduire à des failles de script inter-sites.

Charger des modules

La bonne manière d'ajouter des modules à votre extension est de les rattacher à la sortie de l'analyseur plutôt qu'à $wgOut. La liste des modules sera ensuite automatiquement prise de l'objet ParserOutput et ajoutée à $wgOut même si la génération de la page est en pré-cache. Si vous ajoutez directement les modules à $wgOut il est possible qu'ils ne soient pas mis en cache à la sortie de l'analyseur.

function myCoolHook( $text, array $params, Parser $parser, PPFrame $frame ) {
	// ... ajouter du code ...
	$parser->getOutput()->addModules( 'ext.mycoolext' );
	$parser->getOutput()->addModuleStyles( 'ext.mycoolext.styles' );
	// ... ajouter encore du code ...
}

Chronologie et extensions

Si vous modifiez le code d'une extension, toutes les pages qui utilisent l'extension vont, théoriquement, refléter immédiatement les résultats du nouveau code. D'une manière technique, c'est dire que que votre code est exécuté à chaque fois qu'une page qui contient l'extension, est générée.

En pratique ce n'est pas souvent le cas à cause de la mise en cache - soit par le logiciel MediaWiki, le navigateur, un mandataire intermédiaire (proxy) ou un pare-feu.

Pour contourner le cache de l'analyseur MediaWiki et vous assurer qu'une nouvelle version de la page a bien été générée cliquez sur Modifier, remplacez « action=edit » dans l'URL affichée sur la barre d'adresse de votre navigateur par « action=purge » et soumettez la nouvelle URL. La page et tous les modèles qu'elle référence seront regénérés, en ignorant toutes les données en cache. L'action de purge est nécessaire si la page principale elle-même n'est pas modifiée, mais que la manière dont elle doit être générée a été changée (l'extension a été modifiée, ou seulement un modèle référencé a été modifié).

Si cela n'est pas suffisant pour obtenir une copie récente de la page, vous pouvez normalement contourner les caches intermédiaires en ajoutant '&rand=somerandomtext' à la fin de l'URL ci-dessus. Assurez-vous que 'somerandomtext' est différent à chaque fois.

Comment désactiver la mise en cache des pages en utilisant mon extension ?

Depuis MediaWiki 1.5, l'analyseur est passé dans le troisième paramètre de l'extension. Cet analyseur peut être utilisé pour invalider le cache ainsi :

function wfSampleSomeHookFunction( $text, array $args, Parser $parser, PPFrame $frame ) {
	$parser->getOutput()->updateCacheExpiry(0);
	// ...
}

Regénérer la page quand une autre page est modifiée

Peut-être que vous ne voulez pas désactiver totalement le mécanisme de cache, vous voulez simplement que la page soit regénérée quand une autre page est modifiée, de la même façon que les transclusions de modèles. Ceci peut être fait par l'objet Parser passé à votre fonction d'accroche. La méthode suivante a été récupérée de CoreParserFunctions.php et semble fonctionner sur ce point.

/** déclarer que la page à analyser a une dépendance sur $page via la table ''templatelinks''. 
*/
function wfSampleaddTemplateLinkDependency( Parser $parser, $page )  {
	$title = Title::newFromText( $page );
	$rev = Revision::newFromTitle( $title );
	$id = $rev ? $rev->getPage() : 0;
	// Register dependency in templatelinks
	$parser->getOutput()->addTemplate( $title, $id, $rev ? $rev->getId() : 0 );
}


Ajustement fin du principe de mise en cache

Vous pouvez obtenir une granularité très fine pour les mises en cache de votre extension en utilisant des clés de cache pour différencier différentes versions des sorties de votre extension. Durant la génération, vous pouvez ajouter des clés de cache pour chaque fonctionalité en déclarant une méthode addExtraKey dans votre fonction d'accroche, par exemple :

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' );
	...
}

Néanmoins, modifier $parser->getOptions() pendant une analyse signifie que les clés d'options supplémentaires ne sont pas incluses lors de la recherche d'une page dans le cache; seulement lorsque la page sera générée et mise en cache, vous pourrez utiliser l'accroche PageRenderingHash pour définir les options supplémentaires. PageRenderingHash est exécuté à la fois lorsque vous mettez une page en cache, et lorsque vous la récupérez; il est donc important d'ajouter les nouvelles clés au code de hachage que si elles n'y figurent pas déjà. par exemple :

$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' );
	}
}

Quelques notes importantes sur le sujet :

  • en utilisant « !setting1=$value » au lieu de simplement « !$value » dans le confstr vous vous assurez que le cache de l'analyseur n'est pas perturbé lorsque différentes extensions sont installées ou que leur ordre de chargement est modifié. ! est utilisé comme séparateur pour différentes options de rendu
  • Certaines personnes utilisent $parser->getOptions()->addExtraKey() au lieu de $parser->getOptions()->optionUsed(). Notez bien que addExtraKey ne dit pas au cache de l'analyseur, que la clé supplémentaire est maintenant utilisée, ce qui peut perturber le cache si vous ne faites pas attention.

Comment faire le rendu du texte wiki dans mon extension ?

Depuis la version 1.16

Version de MediaWiki :
1.16

Les fonctions d'accroche de l'analyseur reçoivent la référence de l'objet Parser et celle de l'objet Frame utilisées pour analyser le texte wiki.

function wfSampleWonderfulHook( $text, array $args, Parser $parser, PPFrame $frame ) {
	$output = $parser->recursiveTagParse( $text, $frame );
	return '<div class="wonderful">' . $output . '</div>';
}

Parser::recursiveTagParse() est présent depuis la version 1.8. Ses avantages comprennent la simplicité (il ne prend qu'un argument et renvoie une chaîne) et le fait qu'il analyse les extensions de balises dans $text, vous permet d'imbriquer des extensions de balises.

Le second paramètre de recursiveTagParse, $frame, est un argument optionnel introduit dans MW 1.16 alpha (r55682).

  • Si $frame est fourni (en utilisant la valeur de $frame passée à votre extension), alors chacun des paramètres de modèle de $text sera interprété. En d'autres termes, un contenu tel que {{{1}}} sera reconnu et converti dans la valeur appropriée.
  • Si $frame n'est pas fourni (par exemple, $parser->recursiveTagParse( $text )), ou si $frame est mis à faux, alors les paramètres du modèle ne seront pas interprétés; {{{1}}} ne sera pas modifié. Bien que cela ne soit pas le comportement désiré, c'était l'unique option disponible avant MW 1.16.

Néanmoins, une étape d'analyse qui est encore sautée pour les balises, même en utilisant recursiveTagParse, c'est Parser::preSaveTransform. preSaveTransform est la première étape de l'analyse, responsable de faire les modificatons permanentes de ce qui sera le texte wiki à enregistrer, telles que :

  • la conversion des signatures (~~~, ~~~~, ~~~~~)
  • l'expansion des étiquettes de liens, connue aussi sous le nom de pipe-trick (par exemple, changer [[Help:Contents|]] en [[Help:Contents|Contents]]). Sans cette étape, les liens courts tels que [[Help:Contents|]] sont considérés comme non valides, et restent sous leur forme initiale dans le texte wiki analysé.
  • le développement des modèles {{subst:}}.

L'appel original à preSaveTransform saute intentionellement de telles conversions dans toutes les balises d'extension. Si vous voulez exécuter une transformation avant enregistrement, vous devez considérer l'utilisation d'une fonction d'analyse à la place. Toutes les extensions de balise peuvent aussi être appelées comme une fonction d'analyse en utilisant {{#tag:tagname|input|attribute_name=value}} ce qui appliquera la transformation avant l'enregistrement.

Comment passer des paramètres de style XML à ma balise d'extension ?

Depuis la version 1.5

Depuis MediaWiki 1.5, les paramètres de style XML (attributs des balises) sont pris en charge. Les paramètres sont passés dans le deuxième paramètre de la fonction de l'accroche, en tant que tableau associatif. Les chaînes des valeurs ont déjà eu les entités caractères de HTML décodées pour vous, donc si vous les réémettez vers HTML, n'oubliez pas d'utiliser htmlspecialchars( $codeToEncode, ENT_QUOTES ), pour éviter le risque d'injection de HTML.

Comment empêcher la modification de la sortie HTML de mon extension ?

La valeur retournée par une extension de balise est considérée comme du texte presque analysé, ce qui signifie qu'il n'est pas traité comme du HTML pur, mais qu'il est encore un peu modifié. Il y a deux choses principales qui sont faites sur la sortie d'une extension de balise (avec en plus quelques choses mineures):

  • le remplacement des marqueurs de supression (strip markers). Les marqueurs de supression sont des éléments insérés à diverses étapes du traitement du texte wiki et qui agissent comme des marques permettant la réinsértion ultérieure du contenu supprimé. Ceci n'est pas quelque chose dont les extensions ont habituellement besoin de s'inquiéter.
  • Parser::doBlockLevels qui développe les caractères * en listes, et modifie les lignes commençant par un caractère espace en ‎<pre> entre autres choses. Cela peut quelques fois être un problème dans certaines extensions.

Les extensions de balises prennent en charge également le renvoi d'un tableau au lieu simplement d'une chaîne de caractères (comme le plus souvent les fonctions d'analyseur) pour modifier la façon dont la valeur de retour est interprétée. La valeur en position 0 du tableau doit être le HTML. La clé du « markerType » peut être initialisée à nowiki pour empêcher d'autres analyses. Faire quelque chose comme return [ $html, 'markerType' => 'nowiki' ]; garantirait que la valeur $html ne soit pas modifiée davantage et traitée comme un simple html.

Comment faire apparaître mon extension sur Special:Version ?

Pour que votre extension soit affichée sur la page Special:Version de MediaWiki, vous devez assigner des crédits à l'extension dans le code PHP.

Pour faire cela, ajoutez une variable $wgExtensionCredits à la première ligne exécutable du code avant la ligne de l'accroche ou la définition de fonction.

Un exemple de crédit d'extension est :

<?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(){
	// ...
}

Remplacez validextensionclass par l'une des valeurs suivantes (sauf si votre extension est du type multi-classes —puis créez un crédit pour chacune des classes):

  • 'specialpage'—réservé pour ajouter des pages spéciales à MediaWiki;
  • 'parserhook'—utilisé si votre extension modifie, complémente, ou remplace les fonctions d'analyseur dans MediaWiki;
  • 'variable'—extension qui ajoute de multiples fonctionalités à MediaWiki;
  • 'media'—utilisé si votre extension est un gestionnaire de media de type particulier
  • 'other'—toutes les autres extensions.

Le myextensionmsg est le nom d'une interface ou d'un message i18n qui décrit votre extension et qui devra être défini dans le fichier i18n.php de votre extension. Si vous omettez ce champ, le champ description sera utilisé à la place.

Récupérer le nom de balise dans la fonction de callback

Supposez que vous avez plusieurs balises ‎<foo> et ‎<bar> qui se partagent la même fonction de callback, dans laquelle vous voulez obtenir le nom de la balise qui a appelé la fonction.

$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 ) {
	// Comment distinguer les appels à 'foo' de ceux à 'bar' ?
}

La réponse rapide est : le nom de la balise (foo ou bar) n'est pas présent dans aucun des arguments de la fonction de callback. Mais vous pouvez contourner cela en construisant dynamiquement une fonction de callback séparée pour chaque balise :

$wgHooks['ParserFirstCallInit'][] = 'onParserFirstCallInit';

# ...

public function onParserFirstCallInit( Parser $parser ) {
	// Pour chacun des noms de balise
	foreach ( [ 'foo', 'bar' ] as $tagName ) {
		// Créer dynamiquement une fonction de callback
		$callback = function( $input, $args, $parser, $frame ) use ( $tagName ) {
			// La fonction de callback appelle la function partagée.
			// Remarquez que nous passons maintenant le nom de la balise en tant que paramètre.
			return sharedFunctionality( $input, $args, $parser, $frame, $tagName );
		};
		// Assigner la fonction de callback à la balise
		$parser->setHook( $tagName, $callback );
	}
}

# ...

public function sharedFunctionality( $input, array $args, Parser $parser, PPFrame $frame, $tagName) {
	// Maintenant nous pouvons récupérer le nom de la balise et réaliser les actions spécifiques à celle-ci
	switch ( $tagName ) {
		//...
	}
}

Boutons de la barre d'outils

Extension:WikiEditor fournit une barre d'outils d'édition, permettant aux utilisateurs d'ajouter des balises dans leur éditeur en cliquant simplement sur un bouton. Si vous souhaitez un bouton dans votre barre d'outils, créez dans le dossier resources de votre extension, un fichier dont le nom est similaire à toolbar-button.js. Le fichier devrait être similaire à :

var customizeToolbar = function () {
    $('#wpTextbox1').wikiEditor('addToToolbar', {
        section: 'main',
        group: 'format',
        tools: {
            "ExtensionName": { // remplacez par le nom de votre extension
                label: 'TagName', // remplacez par le texte qui doit apparaître lorsque vous survolez le bouton
                type: 'button',
                icon: "extensions/ExtensionName/images/button-image.svg", // chemin vers l'image qui doit figurer sur le bouton
                action: {
                    type: 'encapsulate',
                    options: {
                        pre: "<tagName>", // balises à insérer quand on clic sur le bouton
                        post: "</tagName>"
                    }
                }
            }
        }
    });
};

/* Contrôle que l'affichage est en mode édition et que les modules nécessaires sont disponibles. Puis personnalisez la barre d'outils ... */
if ( [ 'edit', 'submit' ].indexOf( mw.config.get( 'wgAction' ) ) !== -1 ) {
    mw.loader.using( 'user.options' ).then( function () {
        // Peut valoir la chaîne "0" si l'utilisateur a désactivé la préférence ([[phab:T54542#555387]])
        if ( mw.user.options.get( 'usebetatoolbar' ) == 1 ) {
            $.when(
                mw.loader.using( 'ext.wikiEditor' ), $.ready
            ).then( customizeToolbar );
        }
    } );
}

D'autres détails ici, sur la personnalisation de ce fichier. Une fois ce fichier créé, enregistrez-le avec ResourceLoader pour qu'il soit vu des viisiteurs; ceci se fait en modifiant votre extension.json :

"Hooks": {
    "BeforePageDisplay": "ExtensionName::onBeforePageDisplay"
}
"ResourceModules": {
    "ext.ExtensionName": {
        "scripts": ["toolbarButton.js"]
    }
}

Puis dans votre fichier PHP :

public static function onBeforePageDisplay( OutputPage $out ) {
    $out->addModules( [ 'ext.ExtensionName' ] );
}

Voir aussi