手册:标签扩展

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 99% complete.

Other languages:
Bahasa Indonesia • ‎Deutsch • ‎English • ‎español • ‎français • ‎polski • ‎português do Brasil • ‎русский • ‎中文 • ‎日本語 • ‎한국어
Gnome-preferences-other.svg 扩展: 开发 标签扩展 手册:解析器函数 钩子 特殊页面 手册:皮肤 手册:魔術字 API Content models
MediaWiki扩展

对于个人项目来说,扩展内嵌的wiki标签以提升wiki的功能是非常有用的,无论是简单的字符串处理,还是成熟的信息检索。 标签扩展允许用户建立新的自定义标签用以实现上述功能。 举例来说,一个人可能使用标签扩展来推行一个简单的<donation />(捐赠)标签,来在页面中插入捐赠表格。 擴展,以及解析器功能是更改或增強MediaWiki功能的最有效方法。 在開始擴展工作之前,您應該始終檢查矩陣,以確保其他人沒有完全按照您的嘗試進行操作。

一個簡單的標籤擴展包含一個回调函数,它到解析器,這樣當解析器運行時,它會找到並替換所有特定標記的實例,調用相應的回调函数來呈現實際的HTML。

例子

<?php

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

class ExampleExtension {
	// 使用解析器註冊任何渲染回調
	public static function onParserSetup( Parser $parser ) {
		// 當解析器看到<sample>標記時,它會執行renderTagSample(見下文)
		$parser->setHook( 'sample', 'ExampleExtension::renderTagSample' );
	}

	// Render <sample>
	public static function renderTagSample( $input, array $args, Parser $parser, PPFrame $frame ) {
		// 沒有什麼令人興奮的,只是逃避用戶提供的輸入並再次拋出它(例如)
		return htmlspecialchars( $input );
	}
}

?>

此示例為<sample>標記註冊回調函數。 當用戶將此標記添加到如下頁面:<sample arg1="xxx" arg2="xxx">...input...</sample>時,解析器將調用renderTagSample()函數,傳入四個參數:

$input 
<sample></sample>標記之間輸入,或者如果標記是“關閉”則輸入'null,即<sample />
$args 
標記參數,像超文本標記語言標記屬性一樣輸入; 這是一個由屬性名稱索引的關聯數組。
$parser 
父解析器(一個解析器對象); 更高級的擴展使用它來獲取上下文標題,解析維基文本,擴展大括號,註冊鏈接關係和依賴關係等。
$frame 
父框架(PPFrame對象)。 它與$parser一起使用,為解析器提供有關調用擴展的上下文的更完整信息。

有關更詳細的示例,請參閱Tag擴展示例


属性

讓我們看另一個例子:

<?php

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

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

function wfSampleRender( $input, array $args, Parser $parser, PPFrame $frame ) {
	$attr = array();    
	// 這一次,列出屬性及其值,並將其與用戶輸入一起轉儲
	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有一個名副其實的標籤擴展名,其中一些在本網站列出; 其他人可以通過快速網絡搜索找到。 雖然其中一些對於它們的使用案例非常專業,但是有許多廣受歡迎且使用良好的擴展提供不同程度的功能。

約定

有關擴展的一般佈局和設置,請參閱手册:开发扩展

發布您的擴展程序

#在此Wiki上創建一個名為Extension:<extension_name>的新頁面,其中包含有關您的擴展程序的信息,如何安裝它以及正在使用的屏幕截圖。 已創建一個方便的模板來保存名為模板:扩展 的信息。 请参阅Help:指南了解更多信息。 您還應該盡可能多地向頁面正文添加詳細信息,並且明智地定期檢查以回復相關聯談話頁面上的用戶問題。 另外,請確保該頁面屬於分类:扩展 。 在擴展代碼中創建新的#Extensions應該在extension hook registry上註冊它們。

  1. Notify mediawiki-l郵件列表。

另請參見發布擴展程序

常见问题

报告关注

您將注意到上面的示例中的輸入在返回之前使用 htmlspecialchars() 進行轉義。 在將所有用戶輸入回送給客戶端之前,以這種方式處理所有用戶輸入是至關重要的,以避免引入任意超文本標記語言注入的向量,這可能導致跨站點腳本漏洞。

加載模塊

為擴展程序添加模塊的正確方法是將它們附加到ParserOutput而不是$wgOut。 然後,模塊列表將自動從ParserOutput對像中獲取,並且即使在預先緩存頁面呈現時也會添加到$wgOut。 如果您直接將模塊添加到$wgOut,它們可能不會緩存在解析器輸出中。

function myCoolHook($text, $params, $parser, $frame) {
    // ... do stuff ...
    $parser->getOutput()->addModules( 'ext.mycoolext' );
    $parser->getOutput()->addModuleStyles( 'ext.mycoolext.styles' );
    // ... do more stuff ...
  }


附带扩展

如果您更改擴展程序的代碼,理論上,使用該擴展程序的所有頁面都會立即反映新代碼的結果。 從技術上講,這意味著每次呈現包含擴展名的頁面時都會執行代碼。

實際上,由於頁面緩存(通過MediaWiki軟件,瀏覽器或中間代理或防火牆)通常不是這種情況。

要繞過MediaWiki的解析器緩存並確保生成新版本的頁面,請單擊編輯,將“action=purge”替換為瀏覽器地址欄中顯示的網址中的“action=edit”並提交新網址。 將重新生成頁面及其引用的所有模板,忽略所有緩存的數據。 如果主頁面本身未被修改,則需要清除操作,但必須更改它的方式(擴展已被修改,或僅修改了引用的模板)。

如果這不足以為您提供頁面的新副本,則通常可以通過在上述URL的末尾添加“&rand=somerandomtext”來繞過中間緩存。 確保'somerandomtext'每次都不同。

如何使用我的擴展程序禁用頁面緩存?

從MediaWiki 1.5開始,解析器作為第三個參數傳遞給擴展。 此解析器可用於使緩存無效,如下所示:

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

在編輯其他頁面時重新生成頁面

也許您不想完全禁用緩存,只需要在編輯其他頁面時重新生成頁面,類似於處理模板轉換的方式。 這可以使用傳遞給鉤子函數的解析器對象來完成。 以下方法取自CoreParserFunctions.php並且似乎可以用於此目的。

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

緩存行為的細粒度調整

您可以使用緩存鍵來區分不同版本的擴展輸出,從而為擴展使用細粒度緩存。 渲染時,您可以通過向鉤子函數添加addExtraKey方法為每個特徵添加緩存鍵,例如:

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

但是,在解析期間修改$parser->getOptions()意味著在嘗試獲取緩存頁面時不包括額外的選項鍵,只有在渲染頁面進入緩存時,才能使用PageRenderingHash鉤子來設置額外的選項。 PageRenderingHash在將頁面放入緩存並將其取出時都會運行,因此如果它們尚未存在,則僅向哈希添加新密鑰非常重要。 例如:

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

關於此的一些重要說明:

  • 在confstr中使用“!setting1=$value”而不僅僅是“!$value”可確保如果安裝了不同的擴展或其加載順序更改,則解析器緩存不會變得混亂。 !用作不同渲染選項的分隔符
  • 有些人使用$parser->getOptions()->addExtraKey()而不是$parser->getOptions()->optionUsed()。 請注意,addExtraKey不會告訴解析器緩存額外的密鑰正在使用中,因此如果您不小心,很容易導致破壞緩存。

如何在我的擴展中渲染wiki文件?

自版本1.16起

MediaWiki版本: 1.16

解析器鉤子函數傳遞對解析器對象和幀對象的引用; 這些應該用於解析wiki文本。

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

Parser::recursiveTagParse()自1.8版本開始出現。 它的優點包括簡單(它只需要一個參數並返回一個字符串)以及它解析$text中的擴展標記這一事實,因此您可以嵌套擴展標記。

recursiveTagParse的第二個參數,$frame是MW 1.16 alpha (r55682)中引入的可選參數。

  • 如果提供$frame(使用傳遞給您的擴展名的$frame的值),那麼$text中的任何模板參數都將被擴展。 換句話說,{{{1}}}等內容將被識別並轉換為適當的值。
  • 如果未提供$frame(例如,$parser->recursiveTagParse( $text )),或者$frame設置為false ,然後模板參數不會被擴展; {{{1}}}不會被更改。 雖然這不太可能是理想的行為,但這是MW 1.16之前唯一可用的選擇。

但是,即使使用recursiveTagParse,仍然會跳過標記的一步解析,即Parser::preSaveTransform。 preSaveTransform是解析的第一步,負責對即將保存的wiki文本進行永久性更改,例如:

  • 您现有的签名 (~~~, ~~~~, ~~~~~)
  • 擴展鏈接標籤,也稱為“管道技巧”(例如,將[[Help:Contents|]]更改為[[Help:Contents|Contents]])。 如果沒有此步驟,[[Help:Contents|]]之類的速記鏈接將被視為無效,並在解析時保留為wiki文本格式。
  • 擴展{{subst:}}模板。

對preSaveTransform的原始調用故意在所有擴展標記內跳過此類轉換。 如果您需要執行預保存轉換,則應考慮使用 parser function。 所有標記擴展也可以使用{{#tag:tagname|input|attribute_name=value}}作為解析器函數調用,它將應用預保存轉換。

如何在擴展標記中傳遞XML樣式參數?

自版本1.5起

從MediaWiki 1.5開始,支持XML樣式參數(標記屬性)。 參數作為第二個參數傳遞給鉤子函數,作為關聯數組。 值字符串已經為您解碼了HTML字符實體,因此如果將它們發送回HTML,請不要忘記使用 htmlspecialchars($codeToEncode,ENT_QUOTES),以避免HTML注入的風險。

如何避免修改擴展程序的超文本標記語言輸出?

標籤擴展的返回值被認為是“幾乎”解析的文本,這意味著它不被視為純超文本標記語言,但仍然略有修改。 對標籤擴展的輸出做了兩件主要的事情(以及其他一些小事):

  • 替換strip marker。 條帶標記是在處理wikitext的各個階段插入的某些項目,以作為標記在以後重新插入刪除的內容。 這不是擴展通常需要擔心的事情。
  • Parser::doBlockLevels which turns *'s into lists, and turns any line starting with a leading space into a <pre> among other things. 在某些擴展中,這有時可能是一個問題。

標記擴展還支持返回數組而不僅僅是字符串(很像解析器函數),以便更改返回值的解釋方式。 數組的第0個值必須是html。 “markerType”鍵可以設置為nowiki ,以便停止進一步解析。 執行類似return array( $html, "markerType" => 'nowiki' );將確保$html值不會被進一步修改並視為純超文本標記語言。

如何讓我的擴展程序出現在Special:Version?

要使您的擴展程序顯示在MediaWikiSpecial:Version頁面上,您必須在PHP代碼中指定擴展名。

為此,請在鉤子線或函數定義之前添加$wgExtensionCredits變量作為第一個可執行代碼行。

示例擴展信用是:

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

}

validextensionclass替換為以下之一(除非您的擴展屬於多個類&mdash;然後為each類創建一個信用):

  • 'specialpage'&mdash;保留用於添加MediaWiki特殊頁面;
  • 'parserhook'&mdash;如果你的擴展修改,補充或替換MediaWiki中的解析器函數,則使用它;
  • 'variable'&mdash;為MediaWiki添加多項功能的擴展程序;
  • 'media'&mdash;如果您的擴展程序是某種媒體處理程序,則使用它
  • '其他'&mdash;所有其他擴展。

myextensionmsg是接口/i18n消息的名稱,它描述了您的擴展,需要在擴展的i18n.php文件中定義。 如果省略此字段,則將使用description字段。

在回調中檢索標記名稱

假設你有幾個共享相同回調的標籤<foo><bar>,並且在回調函數中,你想獲得調用回調的標籤名'。

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

# ...

public function mySetup(Parser &$parser) {
  $parser->setHook('foo', 'sharedFunctionality');
  $parser->setHook('bar', 'sharedFunctionality');
  return true;
}

# ...

public function sharedFunctionality($input, $argv, $parser, $frame) {
  // 如何區分'foo'和'bar'回調?
}

簡短的回答是:標記名稱(foobar)不存在於任何回調的參數中。 但您可以通過為每個標記動態構建單獨的回調來解決此問題:

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

# ...

public function mySetup(Parser &$parser) {
  // 對於每個標籤名稱
  foreach (array('foo', 'bar') as $tagName) {
    // 動態創建回調函數
    $callback = function($input, $argv, $parser, $frame) use ($tagName) {
      // 回調調用共享函數。
      // 請注意,我們現在將標記名稱作為參數傳遞。
      return sharedFunctionality($input, $argv, $parser, $frame, $tagName);
    };
    // 將回調分配給標記
    $parser->setHook($tagName, $callback);
  }
  return true;
}

# ...

public function sharedFunctionality($input, $argv, $parser, $frame, $tagName) {
  // 現在,我們可以檢索標記名稱並為該標記執行自定義操作
  switch ($tagName) {
    //...
  }
  return true;
}

参见

扩展 : 分类 所有 申请手册:标签扩展 扩展FAQ 扩展钩子注册扩展默认名字空间