Manual:标签扩展
在个别的项目中常会发现,利用外加的能力來擴大延伸wiki内嵌的标记是很有用的,无论是简单的字符串处理、还是已發展完備的信息检索,都是如此。
标签的扩展,它做到了讓用户创建新的自定义标签。
举例来说,人们可以使用一个标签扩展来引入一个简单的<donation />
标签,将一个捐赠表格插入到页面中。
這個扩展,包括解析器函数和钩子,是修改或增强MediaWiki功能最有效的方式。
你在开发扩展之前,总是應該檢查一下,避免步入别人已做过的後塵。
一個簡單的標籤扩展是由一個与解析器挂钩的回呼函式所構成,这样,在解析器运行时,它會找到所有特定标签的实例然後將它替换掉,透過调用相应的回呼函式來呈現實際的HTML。
示例
此頁面的一部分(与Section相关)已过时。 |
在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有一個名副其實的標籤扩展,其中一些在本網站列出; 其他人可以通過快速網絡搜索找到。 雖然其中一些對於它們的使用案例非常專業,但是有許多廣受歡迎且使用良好的扩展提供不同程度的功能。
慣例
有關扩展的一般佈局和設置,請參閱Manual:开发扩展 。
發佈您的扩展
- 在此Wiki上創建一個名為Extension:<扩展名>的新頁面,其中包含有關您的扩展的信息、如何安裝它、以及正在使用的屏幕截圖。 已創建一個方便的模板來保存名為Template:Extension/zh 的信息。 请参阅模板頁面以了解更多信息。 您還應該盡可能多地向頁面正文添加詳細信息,並且明智地定期檢查以回復相關聯談話頁面上的用戶問題。 另外,請確保該頁面屬於Category:扩展 。
- 在扩展的代碼中創建新钩子的扩展應該在extension hook category之中註冊它們。
- 通知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'的调用?
}
簡短的答案是:標籤名稱(foo
或bar
)並沒有出現在任何回呼的參數中。
但您可以通過,為每個標籤動態構建一個單獨的回呼函式,來解決此問題:
$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 ) {
//...
}
}
工具栏按钮
Extension: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' ] );
}
參見
- Help:魔术字 – 特殊標籤/變量列表,如{{PAGENAME}},{{SERVER}},……
- 解析器扩展标签 – 維基媒體的wiki上使用的解析器標籤列表。
- Manual:扩展
- Extensions FAQ
- Category:扩展
- Manual:$wgExtensionFunctions