Jump to content

手冊:標籤擴充功能

本頁使用了標題或全文手工轉換
From mediawiki.org
This page is a translated version of the page Manual:Tag extensions and the translation is 94% complete.
Outdated translations are marked like this.
MediaWiki擴充功能

在個別的專案中常會發現,利用外加的能力來擴大延伸wiki內嵌的標記是很有用的,無論是簡單的字串處理、還是已發展完備的資訊檢索,都是如此。 標籤的擴充功能,它做到了讓使用者新增的自訂標籤。 舉例來說,人們可以使用一個標籤擴充功能來引入一個簡單的‎<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()函數,傳入四個參數:

$input
‎<sample>‎</sample>標籤之間輸入,如果標籤是「關閉」則輸入'null,即‎<sample />
$args
標籤參數,像HTML標籤的屬性一樣輸入; 這是一個以屬性名稱作鍵的關聯數組(鍵-值對)。
$parser
父解析器(一個解析器物件); 更高級的擴充功能使用它來獲取上下文標題、解析維基文字、展開大括號、註冊鏈結關係和依賴關係等。
$frame
父框架(PPFrame物件)。 它與$parser一起使用,為解析器提供有關呼叫擴充功能的上下文的更完整資訊。

有關更詳細的範例,請參閱Tag擴充功能範例


屬性

讓我們看另一個例子:

<?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有一個名副其實的標籤擴充功能,其中一些在本網站列出; 其他人可以通過快速網路搜尋找到。 雖然其中一些對於它們的使用案例非常專業,但是有許多廣受歡迎且使用良好的擴充功能提供不同程度的功能。

慣例

有關擴充功能的一般佈局和設定,請參閱手冊:開發擴充功能

發佈您的擴充功能

  1. 在此Wiki上建立一個名為Extension:<擴充功能名>的新頁面,其中包含有關您的擴充功能的資訊、如何安裝它、以及正在使用的螢幕截圖。 已建立一個方便的模板來儲存名為模板:Extension 的資訊。 請參閱模板頁面以了解更多資訊。 您還應該盡可能多地向頁面正文添加詳細資訊,並且明智地定期檢查以回復相關聯談話頁面上的使用者問題。 另外,請確保該頁面屬於分類:擴充功能
  2. 在擴充功能的代碼中新增勾點的擴充功能應該在extension hook category之中註冊它們。
  3. 通知mediawiki-l郵件列表。

另請參見發佈你的擴充功能

常見問題

安全問題

您將注意到上面的範例中的輸入在返回之前使用htmlspecialchars()進行轉義。 在將所有使用者輸入回送給客戶端之前,以這種方式處理所有使用者輸入是至關重要的,以避免引入任意HTML插入的向量,這可能導致跨網站指令碼漏洞。

載入模組

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

function myCoolHook( $text, array $params, Parser $parser, PPFrame $frame ) {
	// ... 做一些事 ...
	$parser->getOutput()->addModules( 'ext.mycoolext' );
	$parser->getOutput()->addModuleStyles( 'ext.mycoolext.styles' );
	// ... 做更多的事 ...
}

附帶擴充功能

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

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

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

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

如何使用我的擴充功能禁用頁面快取?

從MediaWiki 1.5版開始,解析器作為第三個參數傳遞給擴充功能。 此解析器可用於使快取無效,如下所示:

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

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

也許您不想完全禁用快取,只需要在編輯其他頁面時重新生成頁面,類似於處理模板轉換的方式。 這可以使用傳遞給鉤子函數的解析器物件來完成。


快取行為的細緻度調整

您可以使用快取鍵來區分不同版本的擴充功能輸出,從而為擴充功能使用微量快取。 彩現時,您可以通過向鉤子函數添加addExtraKey方法為每個特徵添加快取鍵,例如:

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

然而,在解析期間修改$parser->getOptions()意味著在嘗試獲取快取頁面時不包括額外的選項鍵,只有在彩現頁面進入快取時,才能使用PageRenderingHash 勾點來設定額外的選項。 PageRenderingHash在將頁面放入快取並將其取出時都會運行,因此如果它們尚未存在,則僅向雜湊添加新密鑰非常重要。 例如:

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

關於此的一些重要說明:

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

如何在我的擴充功能中彩現wikitext?

自版本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的原始呼叫故意在所有擴充功能標籤內跳過此類轉換。 如果您需要執行預儲存轉換,則應考慮使用解析器函數。 所有標籤的擴充功能也可以使用{{#tag:tagname|input|attribute_name=value}}作為解析器函數呼叫,它將應用預儲存轉換。

如何在擴充功能標籤中傳遞XML樣式參數?

自版本1.5起

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

如何避免我的擴充功能的HTML輸出的修改?

標籤的擴充功能的返回值被認為「幾乎是」解析的文字,這意味著它不被視為純HTML,但仍然略有修改。 對標籤的擴充功能的輸出做了兩件主要的事情(以及其他一些小事):

  • 替換strip marker。 條帶標記是在處理wikitext的各個階段插入的某些項目,以作為標記在以後重新插入刪除的內容。 這不是擴充功能通常需要擔心的事情。
  • Parser::doBlockLevels會將*轉換為無序列表,並將以空格符為起始的一行轉換為‎<pre>。 在某些擴充功能中,這有時可能是一個問題。

標籤的擴充功能還支援返回數組而不僅僅是字串(很像解析器函數),以便更改返回值的解釋方式。 陣列的第0個值必須是html。 「markerType」鍵可以設定為nowiki ,以便停止進一步解析。 執行類似return [ $html, 'markerType' => 'nowiki' ];將確保$html值不會被進一步修改並視為純HTML。

如何讓我的擴充功能出現在Special:Version?

要使您的擴充功能顯示在MediaWikiSpecial:Version頁面上,您必須在PHP代碼中指定擴充功能credit。

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

一個範例勾點credit是:

<?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替換為以下之一(除非您的擴充功能屬於多個類—然後為「每一個」類建立一個credit):

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

myextensionmsg是介面/i18n訊息的名稱,它描述了您的擴充功能,需要在擴充功能的i18n.php檔案中定義。 如果省略此欄位,則將使用description欄位。

在回呼函數中檢索標籤名稱

假設你有幾個共享同一個回呼的標籤‎<foo>‎<bar>,並且在回呼函數中,你想獲得那個能喚起回呼的「標籤名」。

$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 ) {
	// 如何區分'foo'和'bar'的调用?
}

簡短的答案是:標籤名稱(foobar)並沒有出現在任何回呼的參數中。 但您可以通過,為每個標籤動態構建一個單獨的回呼函數,來解決此問題:

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

# ...

public function onParserFirstCallInit( Parser $parser ) {
	// 給每一個標籤名稱
	foreach ( [ 'foo', 'bar' ] as $tagName ) {
		// 動態創建一個回呼函數
		$callback = function( $input, $args, $parser, $frame ) use ( $tagName ) {
			// 回呼函數喚起共享的函數。
			// 請注意,我們現在將標籤名稱作為參數傳遞。
			return sharedFunctionality( $input, $args, $parser, $frame, $tagName );
		};
		// 分配回呼函式給標籤
		$parser->setHook( $tagName, $callback );
	}
}

# ...

public function sharedFunctionality( $input, array $args, Parser $parser, PPFrame $frame, $tagName) {
	// 現在,我們可以檢索標籤名稱並為該標籤執行自定義的動作了
	switch ( $tagName ) {
		//...
	}
}

工具列按鈕

擴充功能:WikiEditor 提供了一個編輯工具列,使用者只需點擊一個按鈕,就能在編輯器中添加標籤。 如果你想為你的新標籤建立一個工具列按鈕,請在副檔名的 resources 資料夾中建立一個名為 toolbar-button.js 的檔案。 檔案應該是這樣的:

var customizeToolbar = function () {
    $('#wpTextbox1').wikiEditor('addToToolbar', {
        section: 'main',
        group: 'format',
        tools: {
            "ExtensionName": { // 替换为你的扩展名称
                label: 'TagName', // 替换为移动按钮时应出现的标签。
                type: 'button',
                icon: "extensions/ExtensionName/images/button-image.svg", // 按钮上的图像的路径
                action: {
                    type: 'encapsulate',
                    options: {
                        pre: "<tagName>", // 在点击按钮时被插入的标签
                        post: "</tagName>"
                    }
                }
            }
        }
    });
};

/* 检查view是否处于编辑模式,所需模块是否可用。 然后,自定义工具条... */
if ( [ 'edit', 'submit' ].indexOf( mw.config.get( 'wgAction' ) ) !== -1 ) {
    mw.loader.using( 'user.options' ).then( function () {
        // 如果用户禁用该偏好,这可以是字符串 "0"([[phab:T54542#555387]])。
        if ( mw.user.options.get( 'usebetatoolbar' ) == 1 ) {
            $.when(
                mw.loader.using( 'ext.wikiEditor' ), $.ready
            ).then( customizeToolbar );
        }
    } );
}

有關自訂此檔案的更多詳情,請參見此處。 一旦您已建立檔案後,您需要將其註冊為 資源載入器 檔案,以便將其傳送給訪客;這可通過編輯您的 extension.json 檔案來完成:

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

然後,在你的 PHP 檔案中:

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

參見