Manual:Extensiones de etiquetas
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.
Por ejemplo, puede definir una extensión de etiqueta que permita a un usuario inyectar un formulario de contacto en su página de usuario, utilizando algo como $1.
Por ejemplo, puede definir una extensión de etiqueta que permita a un usuario inyectar un formulario de contacto en su página de usuario, utilizando algo como $1.
Una extensión de etiqueta simple consiste en una función callback, que está enganchada al analizador sintáctico para que, cuando éste se ejecute, encuentre y reemplace todas las instancias de una etiqueta específica, llamando a la función callback correspondiente para renderizar el HTML real.
Ejemplo
Partes de esta página (aquellas relacionadas con Section) están desactualizadas. |
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 {
// Registrar cualquier llamada de retorno con el parser
public function onParserFirstCallInit( $parser ) {
// Cuando el analizador ve la etiqueta <sample>, ejecuta renderTagSample (véase a continuación)
$parser->setHook( 'sample', [ $this, 'renderTagSample' ] );
}
// Render <sample>
public 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 );
}
}
Este ejemplo registra una función de devolución de llamada para la etiqueta <sample>
.
Cuando un usuario añade esta etiqueta a una página como esta: <sample arg1="xxx" arg2="xxx">...input...</sample>
, el analizador sintáctico llamará a la función renderTagSample()
, pasando cuatro argumentos:
- $input
- Entrada entre las etiquetas
<sample>
y</sample>
, o null si la etiqueta está "cerrada", es decir,<sample />
- $args
- Argumentos de la etiqueta, que se introducen como los atributos de la etiqueta HTML; se trata de una matriz asociativa indexada por el nombre del atributo.
- $parser
- El parser padre (un objeto Parser); las extensiones más avanzadas lo utilizan para obtener el Título contextual, parsear el texto del wiki, expandir las llaves, registrar las relaciones y dependencias de los enlaces, etc.
- $frame
- El marco padre (un objeto PPFrame). Se utiliza junto con $parser para proporcionar al analizador una información más completa sobre el contexto en el que se llamó a la extensión.
Para un ejemplo más elaborado, véase Ejemplo de extensión de etiquetas
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, puede definir una extensión de etiqueta que permita a un usuario inyectar un formulario de contacto en su página de usuario, utilizando algo como <emailform to="User" email="user@foo.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
Se ha sugerido que esta página o sección se fusione con Manual:Developing extensions#Deploying and registering#Publishing.(Discusión) |
- 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 .
- Las extensiones que crean nuevos hooks dentro del código de extensión debe registrarlos en hooks de extensión del registro.
- Notificar a la lista de correo mediawiki-l.
Véase también publicar su extensión.
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
Si cambias el código de una extensión, todas las páginas que utilicen la extensión, teóricamente, reflejarán de inmediato los resultados del nuevo código. Técnicamente hablando, esto significa de que tu código es ejecutado cada vez que en una página que contenga la extensión se renderice.
En práctica, esto a menudo no es el caso, dado al almacenamiento en caché de la página - ya sea por el software MediaWiki, el navegador o por un intermediario proxy o firewall.
Para evitar el caché del analizador de MediaWiki y asegurarse de que se genera una nueva versión de la página, haz clic en editar, sustituye "action=edit" en la URL que aparece en la barra de direcciones de tu navegador por "action=purge" y envía la nueva URL. La página y todas las plantillas a las que hace referencia se regenerarán, ignorando todos los datos almacenados en la caché. La acción de purga es necesaria si la página principal en sí no ha sido modificada, pero la forma en que debe ser renderizada ha cambiado (la extensión fue modificada, o sólo una plantilla referenciada fue modificada).
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.
¿Cómo puedo desactivar el almacenamiento en caché de las páginas que utilizan mi extensión?
Desde MediaWiki 1.5, el parser se pasa como tercer parámetro a una extensión. Este parser se puede utilizar para invalidar la caché de esta manera:
function wfSampleSomeHookFunction( $text, array $args, Parser $parser, PPFrame $frame ) {
$parser->getOutput()->updateCacheExpiry(0);
// ...
}
Regeneración de la página cuando se edita otra página
Tal vez no quieras deshabilitar la caché por completo, sólo quieres que la página se regenere cada vez que se edite otra página, de forma similar a como se manejan las transclusiones de plantillas. Esto se puede hacer utilizando el objeto parser que se pasa a su función hook.
Ajuste de grano fino del comportamiento de la caché
Puede utilizar la caché de grano fino para su extensión mediante el uso de claves de caché para diferenciar entre las diferentes versiones de la salida de su extensión. Mientras se renderiza se pueden añadir claves de caché para cada función añadiendo un método addExtraKey a su función hook, por ejemplo
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' );
...
}
Sin embargo, modificar $parser->getOptions()
durante el parse significa que las claves de las opciones extra no se incluyen cuando se intenta obtener una página en caché, sólo cuando se renderiza una página para ir a la caché, por lo que se puede utilizar el hook PageRenderingHash para establecer opciones extra.
PageRenderingHash se ejecuta tanto al poner una página en la caché, como al sacarla, por lo que es importante sólo añadir nuevas claves al hash si no están ya allí.
p. ej.
$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' );
}
}
Algunas notas importantes en esto:
- Usar "!setting1=$value" en lugar de sólo "!$value" en el confstr asegura que la caché del parser no se desordene si se instalan diferentes extensiones o su orden de carga cambia. ! Se utiliza un separador para las diferentes opciones de representación
- Algunas personas utilizan
$parser->getOptions()->addExtraKey()
en lugar de$parser->getOptions()->optionUsed()
. Ten en cuenta que addExtraKey no indica a la caché del analizador sintáctico que la clave extra está en uso, y por lo tanto puede resultar fácilmente en la ruptura de la caché si no tienes cuidado.
¿Cómo puedo reproducir wikitexto en mi extensión?
A partir de la versión 1.16
Versión de MediaWiki: | ≥ 1.16 |
A las funciones hook del parser se les pasa una referencia al objeto parser y un objeto frame; estos deben ser usados para parsear el wikitexto.
function wfSampleWonderfulHook( $text, array $args, Parser $parser, PPFrame $frame ) {
$output = $parser->recursiveTagParse( $text, $frame );
return '<div class="wonderful">' . $output . '</div>';
}
Parser::recursiveTagParse()
existe desde la versión 1.8.
Sus ventajas incluyen la simplicidad (sólo toma un argumento y devuelve una cadena) y el hecho de que analiza las etiquetas de extensión en $text
, por lo que puede anidar las etiquetas de extensión.
El segundo parámetro de recursiveTagParse, $frame
, es un argumento opcional introducido en MW 1.16 alpha (r55682).
- Si se proporciona
$frame
(usando el valor de$frame
pasado a su extensión), entonces cualquier parámetro de plantilla en$text
será expandido.
En otras palabras, el contenido como {{{1}}}
será reconocido y convertido en el valor apropiado.
- Si no se proporciona
$frame
(por ejemplo,$parser->recursiveTagParse( $text )
), o si$frame
se establece como falso, los parámetros de la plantilla no se expandirán;{{1}}
no se modificará.
Aunque es poco probable que este sea el comportamiento deseado, esta era la única opción disponible antes de MW 1.16.
Sin embargo, un paso del análisis sintáctico que todavía se omite para las etiquetas, incluso cuando se utiliza recursiveTagParse, es Parser::preSaveTransform. preSaveTransform es el primer paso del análisis sintáctico, responsable de realizar cambios permanentes en el wikitexto que se va a guardar, como por ejemplo
- Conversión de firmas (~~~, ~~~~, ~~~~~)
- Expandir las etiquetas de los enlaces, también conocido como pipe-trick (por ejemplo, cambiar [[Help:Contents|]] por [[Help:Contents|Contents]]). Sin este paso, los enlaces abreviados como [[Help:Contents|]] se consideran inválidos, y se dejan en su forma de wikitext cuando se analizan.
- Expansión de plantillas {{subst:}}.
La llamada original a preSaveTransform se salta intencionadamente estas conversiones dentro de todas las etiquetas de extensión. Si necesita que se realice una transformación previa al guardado, debería considerar el uso de una función parser function en su lugar. Todas las extensiones de etiquetas también pueden llamarse como una función de análisis sintáctico utilizando <código>{{#tag:tagname|input|attribute_name=value}}</código> que tendrá aplicada la transformación de preguardado.
¿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.
Los parámetros se pasan como segundo parámetro a la función hook, como un array asociativo.
Las cadenas de valores ya han tenido entidades de caracteres HTML decodificadas para ti, así que si las emites de vuelta a HTML, no olvides usar htmlspecialchars( $codeToEncode, ENT_QUOTES )
, para evitar el riesgo de inyección de HTML.
¿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):
- Sustituir strip markers. Los marcadores de banda son ciertos elementos que se insertan en varias etapas del procesamiento del wikitexto para actuar como un marcador para volver a insertar el contenido eliminado en un momento posterior. Esto no es algo de lo que deban preocuparse las extensiones.
- Parser::doBlockLevels que convierte los * en listas, y convierte cualquier línea que comience con un espacio inicial en un <pre>, entre otras cosas.
Esto puede ser un problema en algunas extensiones.
Las extensiones de las etiquetas también admiten la devolución de una matriz en lugar de una cadena (al igual que las funciones del analizador sintáctico) con el fin de cambiar la forma en que se interpreta el valor de retorno.
El valor de la posición 0 de la matriz debe ser el HTML.
La clave "markerType" puede establecerse como nowiki
para detener el análisis sintáctico.
Hacer algo como return [ $html, 'markerType' => 'nowiki' ];
aseguraría que el valor de $html no se modificara más y se tratara como simple 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.
Para ello, añada una variable $wgExtensionCredits
como primera línea de código ejecutable antes de la línea hook o de la definición de la función.
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.
El myextensionmsg
es el nombre de un mensaje de interfaz/i18n que describe su extensión y que tendrá que ser definido en el archivo i18n.php de su extensión.
Si omite este campo, se utilizará en su lugar el campo descripción
.
Recuperar el nombre de la etiqueta dentro del callback
Suponga que tiene varias etiquetas <foo>
y <bar>
que comparten la misma devolución de llamada, y dentro de la función de devolución de llamada, quiere obtener el nombre de la etiqueta que invocó la devolución de llamada.
$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 ) {
// ¿Cómo distinguir entre las llamadas 'foo' y 'bar'?
}
La respuesta corta es: el nombre de la etiqueta (foo
o bar
) no está presente en ninguno de los argumentos de la llamada de retorno.
Pero se puede evitar esto construyendo dinámicamente una llamada de retorno para cada etiqueta:
$wgHooks['ParserFirstCallInit'][] = 'onParserFirstCallInit';
# ...
public function onParserFirstCallInit( Parser $parser ) {
// Para cada nombre de etiqueta
foreach ( [ 'foo', 'bar' ] as $tagName ) {
// Crear dinámicamente una función de devolución de llamada
$callback = function( $input, $args, $parser, $frame ) use ( $tagName ) {
// La llamada de retorno invoca la función compartida.
// Observa que ahora pasamos el nombre de la etiqueta como parámetro.
return sharedFunctionality( $input, $args, $parser, $frame, $tagName );
};
// Asignar la devolución de llamada a la etiqueta
$parser->setHook( $tagName, $callback );
}
}
# ...
public function sharedFunctionality( $input, array $args, Parser $parser, PPFrame $frame, $tagName) {
// Ahora podemos recuperar el nombre de la etiqueta y realizar acciones personalizadas para esa etiqueta
switch ( $tagName ) {
//...
}
}
Toolbar buttons
Extensión: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": { // sustituir por el nombre de su extensión
label: 'TagName', // reemplazar con la etiqueta que debe aparecer al mover el botón
type: 'button',
icon: "extensions/ExtensionName/images/button-image.svg", // ruta de la imagen que debe ir en el botón
action: {
type: 'encapsulate',
options: {
pre: "<tagName>", // que se insertan cuando se hace clic en el botón
post: "</tagName>"
}
}
}
}
});
};
/* Compruebe si la vista está en modo de edición y que los módulos necesarios están disponibles. Luego, personaliza la barra de herramientas... */
if ( [ 'edit', 'submit' ].indexOf( mw.config.get( 'wgAction' ) ) !== -1 ) {
mw.loader.using( 'user.options' ).then( function () {
// Puede ser la cadena "0" si el usuario desactivó la preferencia ([[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 ResourceLoader 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' ] );
}
Véase también
- Ayuda:Palabras mágicas – Lista de variables de etiqueta/especial como Tag extensions/es, Template:SERVIDOR, ...
- Etiquetas de extensiones sintácticas – Lista de etiquetas del analizados en uso en los wikis de Wikimedia.
- Manual:Extensiones
- Extensions FAQ
- Categoría: extensiones
- Manual:$wgExtensionFunctions