Manual:Extensiones de etiquetas

From MediaWiki.org
Jump to navigation Jump to search
This page is a translated version of the page Manual:Tag extensions and the translation is 41% complete.

Outdated translations are marked like this.
Other languages:
Bahasa Indonesia • ‎Deutsch • ‎English • ‎español • ‎français • ‎polski • ‎português do Brasil • ‎русский • ‎中文 • ‎日本語 • ‎한국어
Gnome-preferences-other.svg Extensiones: Desarrollo Tag extensions Funciones de análisis sintáctico Hooks Páginas especiales Apariencias Palabras mágicas API Content models
Extensiones de MediaWiki

A menudo, será útil para un proyecto dado extender el marcado wiki predefinido con funcionalidades adicionales, sea un simple procesamiento de cadenas de texto o la recuperación de información. Las extensiones de etiquetas permiten crear etiquetas personalizadas precisamente para hacer eso. Por ejemplo, se podría usar una extensión de etiqueta para introducir una simple etiqueta <donation />, que inyecta un formulario de donación en la página. Extensions, along with Parser Functions and Hooks are the most effective way to change or enhance the functionality of MediaWiki. You should always check the matrix before you start work on an extension to make sure someone else hasn't done exactly what you are trying to do.

A simple tag extension consists of a callback function, which is hooked to the parser so that, when the parser runs, it will find and replace all instances of a specific tag, calling the corresponding callback function to render the actual HTML.

Ejemplo

<?php

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

class ExampleExtension {
	// Register any render callbacks with the parser
	public static function onParserFirstCallInit( Parser $parser ) {
		// Cuando el analizador ve la etiqueta <sample>, ejecuta renderTagSample (véase a continuación)
		$parser->setHook( 'sample', [ self::class, 'renderTagSample' ] );
	}

	// Render <sample>
	public static function renderTagSample( $input, array $args, Parser $parser, PPFrame $frame ) {
		// No hay nada de interés aquí, sencillamente escapa la entrada proporcionada por el usuario y la devuelve (como ejemplo)
		return htmlspecialchars( $input );
	}
}

This example registers a callback function for the <sample> tag. When a user adds this tag to a page like this: <sample arg1="xxx" arg2="xxx">...input...</sample>, the parser will call the renderTagSample() function, passing in four arguments:

$input 
Input between the <sample> and </sample> tags, or null if the tag is "closed", i.e. <sample />
$args 
Tag arguments, which are entered like HTML tag attributes; this is an associative array indexed by attribute name.
$parser 
The parent parser (a Parser object); more advanced extensions use this to obtain the contextual Title, parse wiki text, expand braces, register link relationships and dependencies, 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

Veamos otro ejemplo:

<?php

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

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

function wfSampleRender( $input, array $args, Parser $parser, PPFrame $frame ) {
	$attr = [];    
	// Esta vez, crea una lista de atributos y sus valores y vuélcalos junto con la entrada del usuario
	foreach( $args as $name => $value ) {
		$attr[] = '<strong>' . htmlspecialchars( $name ) . '</strong> = ' . htmlspecialchars( $value );
	}
	return implode( '<br />', $attr ) . "\n\n" . htmlspecialchars( $input );

/**
 * Las siguientes líneas se pueden usar para obtener directamente los valores de las variables:
 * $to = $args['to'] ;
 * $email = $args['email'] ;
 */
}

Este ejemplo vuelca los atributos que se han pasado a la etiqueta, junto con sus valores. Es bastante evidente que esto permite especificar de forma flexible nuevas etiquetas personalizadas. Por ejemplo, puedes definir una extensión de etiqueta que permite que un usuario inyecte un formulario de contacto en su página de usuario mediante un código similar a <emailform to="usuario" email="usuario@dominio.com" />.

Hay una verdadera plétora de extensiones de etiquetas disponibles para MediaWiki, algunas de las cuales se muestran en este sitio; otras se pueden encontrar mediante una búsqueda rápida en Internet. Aunque varias de estas extensiones están bastante especializadas para su caso de uso, también hay otras muy queridas y utilizadas que proporcionan varios grados de funcionalidad.

Convenciones

Véase Manual:Desarrollo de extensiones para más información sobre el diseño general y la instalación de una extensión.

Publica tus propias extensiones

  1. Crea una nueva página en este wiki llamada Extension:<nombre_de_la_extensión> con información sobre tu extensión, cómo instalarla y capturas de pantallas de la extensión en uso. Disponemos de una plantilla útil para almacenar este tipo de información llamada Plantilla:Extensión . Para más información, véase la página de la plantilla. Asimismo, deberías añadir toda la información que sea posible al cuerpo de la página. Es conveniente volver a consultar la página de forma regular para responder a las preguntas que tengan otros usuarios en la página de discusión asociada. Además, asegúrate de que la página pertenezca a Categoría:Extensiones .
  2. Extensions that create new hooks within the extension code should register them on extension hook registry.
  3. Notify the mediawiki-l mailing list.

See also publishing your extension.

Preguntas frecuentes

Cuestiones de seguridad

Podrás ver que los datos introducidos en los ejemplos mostrados arriba están escapados mediante htmlspecialchars() antes de ser devueltos.

Es crucial que se trate cualquier entrada de datos de esta manera antes de devolverla a los clientes, con el fin de evitar introducir vectores de inyección HTML, que puede dar lugar a vulnerabilidades de tipo cross-site scripting.

Carga de módulos

La forma correcta de añadir módulos a tu extensión es conactarlos a ParserOutput en vez de a $wgOut. La lista de módulos se sacará automáticamente del objeto ParserOutput y se adjuntará a $wgOut incluso cuando la carga de la página está preguardada en caché. Si añades directamente los módulos a $wgOut, puede que no se guarden en caché en la salida del analizador sintáctico.

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 ...
}


Sincronización y extensiones

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).

Si esto no basta para obtener una copia nueva de la página, normalmente puedes saltar cualquier caché intermedia añadiendo '&rand=algúntextoaleatorio' al final de la URL. Asegúrate de que 'algúntextoaleatorio' sea distinto cada vez.

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 cache like this:

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

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. The following method was lifted from CoreParserFunctions.php and appears to work for this purpose.

/** Make the page being parsed have a dependency on $page via the templatelinks table. 
*/
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 );
}

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 ) {
	$setting1= (int) $parser->getUser()->getOption('setting1');
	$parser->getOptions()->optionUsed( 'setting1' );
	$setting2= (int) $parser->getUser()->getOption('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 ) {
	if ( in_array( 'setting1', $optionsUsed ) ) {
		$confstr .= "!setting1=" . $user->getOption('setting1');
	}
	if ( in_array( 'setting2', $optionsUsed ) ) {
		$confstr .= "!setting2=" . $user->getOption('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.

¿Cómo puedo reproducir wikitexto en mi extensión?

A partir de la versión 1.16

Versión de 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.

¿Cómo puedo pasar parámetros de tipo XML en mi etiqueta de extensión?

A partir de la versión 1.5

A partir de la versión 1.5, MediaWiki cuenta con soporte para parámetros (atributos de etiqueta) de tipo XML. 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.

¿Cómo puedo evitar la modificación de la salida HTML de mi extensión?

El valor devuelto por una extensión de etiqueta se considera casi texto analizado, que quiere decir que no se considerará HTML puro, sino ligeramente modificado. Hay dos cosas principales que se hacen a la salida de una extensión de etiqueta (junto con otras dos cosas menores):

  • 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. El valor de la posición 0 de la matriz debe ser el HTML. The "markerType" key can be set to nowiki in order to stop further parsing. Doing something like return array( $html, "markerType" => 'nowiki' ); would ensure that the $html value is not further modified and treated as just plain html.

¿Cómo puedo hacer que se muestre mi extensión en Especial:Versión?

Para que tu extensión se muestre en la página de MediaWiki Especial:Versión, debes asignarle créditos de extensión en el código PHP.

To do this, add a $wgExtensionCredits variable as the first executable line of code before the hook line or function definition.

Aquí se muestra un ejemplo de crédito de extensión:

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

Reemplaza validextensionclass por una de las siguientes clases (a menos que tu extensión pertenezca a varias clases, en cuyo caso, deberás crear un crédito para cada clase):

  • 'specialpage'—reservada para añadir páginas especiales de MediaWiki;
  • 'parserhook'—si tu extensión modifica, complementa o reemplaza las funciones del analizador sintáctico de MediaWiki;
  • 'variable'—extensión que añade funcionalidades múltiples a MediaWiki;
  • 'media'—si tu extensión es un controlador de archivos multimedia
  • 'other'—cualquier otra extensión.

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


Véase también

Extensiones : Categoría All SolicitudesExtensiones de etiquetas Extensions FAQ Extension hook registryExtension default namespaces