Manual:タグ拡張機能

This page is a translated version of the page Manual:Tag extensions and the translation is 44% complete.
MediaWiki 拡張機能

単純な文字列処理でも本格的な情報検索でも、追加の機能を加えて組み込みウィキマークアップを拡張すると、個々のプロジェクトで有効な場合があります。 タグ拡張機能を使用すると、ユーザーはそのような新しいカスタムタグを作成できます。 たとえば、タグ拡張を使用して単純な‎<donation />タグを導入すると、寄付フォームがページに挿入されます。 拡張機能は、パーサー関数ハックと共に、MediaWikiの機能を変更または強化する最も効果的な方法です。 拡張機能に関する作業を始める前に、あなたがやろうとしていることを既に他の誰かがつくっていないかを確認するため拡張機能一覧をチェックするようにしてください。

単純なタグ拡張は、パーサーにフックされるコールバック関数で構成されています。そのため、パーサーが実行されると特定のタグに関するインスタンスがすべて検索・置換され、対応するコールバック関数が呼び出されて実際の HTML がレンダリングされます。

extension.json ファイルで、フックをセットアップします:

...
  "Hooks": {
       "ParserFirstCallInit": "ExampleExtensionHooks"
   },
   "HookHandlers": {
       "ExampleExtensionHooks": {
           "class": "MediaWiki\\Extension\\ExampleExtension\\Hooks"
       }
   }
...

そして、PHP ファイルにフックを追加します

<?php
namespace MediaWiki\Extension\ExampleExtension;

class ExampleExtension implements ParserFirstCallInitHook {
	// レンダリング コールバックをパーサーに登録します
	public function onParserFirstCallInit( $parser ) {
		// パーサーが<sample>タグを見つけると、renderTagSample(下記参照)を実行します
		$parser->setHook( 'sample', [ $this, 'renderTagSample' ] );
	}

	// Render <sample>
	public function renderTagSample( $input, array $args, Parser $parser, PPFrame $frame ) {
		// 目立ったことは何もありません。ただユーザーの入力をエスケープし、再び返しているだけです(例として)
		return htmlspecialchars( $input );
	}
}

この例では ‎<sample> タグのコールバック関数を登録します。 ユーザーがこのタグを <sample arg1="xxx" arg2="xxx">...input...</sample> のようにしてページに書くと、パーサーは renderTagSample() 関数を呼び出し、4つの引数を引き渡します。

$input
‎<sample> タグと ‎</sample> タグとの間に記された入力。あるいは、タグが閉じている場合、つまり ‎<sample /> であった場合は null
$args
HTMLタグ属性のように入力されたタグの引数。属性名をキーとする連想配列。
$parser
上位パーサー(パーサーオブジェクト)。より高度な拡張機能はこれを使ってコンテキストタイトルを取得し、ウィキテキストを解析し、括弧を展開し、リンク関係と依存関係を登録します。
$frame
上位フレーム(PPFrameオブジェクト)。 $parser と一緒に用いられ、拡張機能が呼び出されたコンテキストに関するより完全な情報をパーサーに提供します。

より複雑な例については、タグ拡張の例を参照してください


属性

別の例を見てみましょう:

<?php

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

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

function wfSampleRender( $input, array $args, Parser $parser, PPFrame $frame ) {
	$attr = [];    
	// 今回は、属性名とその値のリストを作成し、ユーザーの入力と合わせてそれらを出力します。
	foreach( $args as $name => $value ) {
		$attr[] = '<strong>' . htmlspecialchars( $name ) . '</strong> = ' . htmlspecialchars( $value );
	}
	return implode( '<br />', $attr ) . "\n\n" . htmlspecialchars( $input );

/**
 * 以下のようにして変数値を直接取得することができます:
 * $to = $args['to'] ;
 * $email = $args['email'] ;
 */
}

この例ではタグに渡された属性の名前と値を出力します。 こうして新たなカスタムタグを柔軟に指定できることは明らかです。 例えば、<emailform to="User" email="user@foo.com" /> のように、利用者が自分の利用者ページに連絡先フォームを挿入できるようにするタグ拡張機能を定義することができます。

MediaWikiには非常にたくさんの拡張機能があり、その一部はこのサイトに掲載されています。その他の拡張機能も簡易ウェブ検索で見つけることができます。 これらの多くは特定の利用場面に特化したものですが、よく好まれよく利用されているたくさんの拡張機能があります。それらの機能の程度もさまざまです。

規約

拡張機能の一般的な設計と設定については Manual:拡張機能の開発 を参照してください。

拡張機能の公開

  1. このウィキ上で、拡張機能に関する情報やインストール方法、使用例のスクリーンショットを含む、Extension:<拡張機能名> という名前の新しいページを作成してください。 この情報を保持するために便利なテンプレートが作成されており、Template:Extension と呼ばれています。 詳細情報はテンプレートのページを参照してください。 ページの本文にはできる限り詳細を追加するべきであり、関連するトークページでの利用者の質問に対応するために定期的に確認するのが賢明です。 また、そのページが カテゴリ:拡張機能 に属していることを確認してください。
  2. 拡張機能コード内で新しいフックを作成する拡張機能は、それらを拡張機能フック カテゴリに登録する必要があります。
  3. mediawiki-l メーリング リストに通知する。

よくある質問

セキュリティ上の問題

上記の例では、返される前に入力が htmlspecialchars() を使ってエスケープされていることに気づくでしょう。 クライアントに返す前に、すべての利用者入力がこの方法で処理されることが重要です。そうしないと、任意の HTML インジェクションのベクターが導入され、クロスサイト スクリプティングの脆弱性を引き起こす可能性があります。

モジュールの読み込み

拡張機能のモジュールを追加する正しい方法は、それらを $wgOut ではなく、ParserOutput に追加することです。 その後、モジュール リストは自動的に ParserOutput オブジェクトから取得され、ページ レンダリングが事前にキャッシュされている場合でも $wgOut に追加されます。 モジュールを直接 $wgOut に追加している場合、それらはパーサー出力にキャッシュされない可能性があります。

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

拡張機能のコードを変更した場合、その拡張機能を使用しているすべてのページは、理論的には新しいコードの結果を即座に反映します。 技術的には、拡張機能を含むページがレンダリングされるたびに、あなたのコードが実行されることを意味します。

実際には、ページ キャッシュ (MediaWiki ソフトウェア、ブラウザー、中間プロキシ、ファイアウォールによる) が原因で、必ずしもそうはなりません。

MediaWiki のパーサー キャッシュをバイパスして、新しいバージョンのページが生成されるようにするには、編集をクリックし、ブラウザーのアドレス バーに表示されている URL の「action=edit」を「action=purge」に置き換え、新しい URL を送信 (submit) してください。 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.

拡張機能を使用するページのキャッシュの無効化

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

他ページの編集時にページを再生成

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.


キャッシュ動作の微調整

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. 例:

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

拡張機能でのウィキテキストの表示

バージョン1.16以降

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.

拡張タグでのXMLスタイルパラメーターの渡し

バージョン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.

拡張機能のHTML出力による変更の回避

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

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.

Special:Versionへの拡張機能の表示

See Manual:拡張機能の登録 and register your extension accordingly.

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

Extension: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 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' ] );
}

関連項目