帮助:编写扩展

来自THBWiki
跳转至: 导航搜索
本页是THBWiki的编辑帮助文档

本页面用于记录部分编写扩展时会用到的功能接口、需要注意的事项,扩展的修改记录可以参看帮助:扩展修改记录

理论

缓存机制

根据我的观测和猜测,MW的缓存机制很复杂,光是我能明显区分的层次就有4个,可能还有更多的层次实现着各种各样奇葩的功能。

  • 源代码,就是WikiCode,最原始的状态,在任何一个词条按右上的“编辑”就能浏览得到。
  • 一级缓存,此层次只会在词条本身有改动或嵌入的模板有改动的时候刷新,虽然不清楚具体储存了什么,但如果不刷新此层次有部分应该已经改变了的东西仍然会停留在旧的状态。
  • 二级缓存,储存和特殊:展开模板差不多的文字,所有可以展开的都展开了,只剩少量MW基本语法(比如[[]] == == * # :),可以通过action=purge刷新。如果没有打开FileCache,或者是不能使用FileCache的情况,一般页面会从这个缓存开始,通过简单处理、寻找链接判断红蓝,整理出HTML显示。
  • FileCache,为非登录用户储存整理后的纯HTML,侧边栏,顶部底部都会被缓存,极节省时间。

功能接口实作

全域函数

wfMessage

通过Message名称获取Message物件

wfMessage( 'xxx-msg' );

wfEscapeWikiText

转义所有特殊字符,用以在HTML中显示,是强化版的htmlspecialchars

wfEscapeWikiText( $text );

最最最最大的问题是他没有反函数,所以在很多情况下都带来很多麻烦。

wfSetVar

特殊的设变量方法,会把$variable的值换成$value,然后传回$variable原本的值,当$force是true的时候即使$value是null也会强制设定$variable

wfSetVar( $variable, $value );
wfSetVar( $variable, $value, $force );

wfSuppressWarnings

强制禁止输出警告,用true来复原

wfSuppressWarnings();
wfSuppressWarnings(true);

wfGetDB

获取DatabaseBase物件

wfGetDB(DB_MASTER);
wfGetDB(DB_SLAVE);

DB_MASTER用于写,DB_SLAVE用于读。


Title

  • Title::newFromDBkey,用DBkey来生产Title,DBkey就是把空格转为_,包含命名空间的页面标题。
$title = Title::newFromDBkey( $key );
  • Title::newFromText,用纯文字来生产Title,是最常用的一个,还可以强制设定命名空间。
$title = Title::newFromText( $text, NS_MAIN );
  • Title::newFromID,用页面ID来生产Title,是最准确的一个,而且在提交表格时比起传送标题文字,传送数字更简单。
$title = Title::newFromID( $id );
  • Title::newFromIDs,用多个页面ID来生产多个Title,输入输出都是array。
  • Title::newFromRow,用sql结果行来生产Title,有时候需要做query,顺便连结获取结果词条的所有资料,就省下再用newFromID获取Title资料的功夫了,不过获取的资料不能有缺漏。需要的列有:page_namespace、page_title、page_id、page_len、page_is_redirect、和page_latest,page_lang和page_content_model可有可无。
$title = Title::newFromRow( $row );
  • Title::makeTitle,最一般的方法,页面名称和命名空间分开两个参数传入,可以更严格地限制Title的命名空间。
$title = Title::makeTitle( $ns, $title, $fragment, $interwiki );
  • Title::makeTitleSafe,和Title::makeTitle差不多,只不过强化了安全性,适合对用户输入操作。
$title = Title::makeTitleSafe( $ns, $title, $fragment, $interwiki );
  • Title::newMainPage,获取主页的Title,一般没什么用。
  • Title::compare,比较两个Title,用于usort排序Title。
Title::compare( $a, $b );
  • getText,获取标题文字,用空格,无命名空间
  • getPartialURL,获取URL编码后的标题,无命名空间
  • getDBkey,获取DBkey,用_,有命名空间
  • getNamespace,获取命名空间的数字
  • getNsText,获取命名空间的文字
  • getSubjectNsText,获取命名空间的文字,如果是讨论页获取讨论内容的命名空间
  • getTalkNsText,获取对应讨论页的命名空间
  • getPrefixedDBkey,获取标题文字,用_,有命名空间
  • getPrefixedText,获取标题文字,用空格,有命名空间
  • getFullText,获取标题文字,用空格,有命名空间,再加上#和段落
  • getRootText,获取根页面标题文字,用空格,无命名空间
  • getRootTitle,获取根页面Title
  • getBaseText,获取上一级页面标题文字,用空格,无命名空间
  • getBaseTitle,获取上一级页面Title
  • getSubpageText,获取子页面标题文字,用空格,无命名空间,比如User:Foo/Bar/Baz返回Baz
  • getSubpage,获取指定子页面Title
Title::newFromText('User:Foo/Bar/Baz')->getSubpage("Asdf");
// 返回标题是 User:Foo/Bar/Baz/Asdf 的Title
  • getPrefixedURL,获取URL编码后的标题,有命名空间
  • getFullURL,获取完整网址,可以加上query
$title->getFullURL( $query );
  • getLocalURL,获取内链网址,不包含域名,可以加上query
$title->getFullURL( $query );
  • getEditURL,获取编辑页面的链接
  • getArticleID,获取页面ID
  • getLatestRevID,获取当前版本ID
  • isSpecialPage,是否特殊页面
  • isMainPage,是否主页
  • isSubpage,是否子页面
  • isRedirect,是否重定向页
  • isNewPage,是否新页面
  • exists,页面是否存在
  • inNamespace,是否位于此命名空间
$title->inNamespace( $ns );
  • inNamespace,是否位于其中一个命名空间
$title->inNamespace( $ns1, $ns2, $ns3 );
$title->inNamespace( array( $ns1, $ns2, $ns3 ) );
  • getSubpages,获取子页面,返回Title组成的array
$title->getSubpages( $limit );

可以设定$limit限制最大获取的子页面数,不设定或者设为-1代表不限制。

  • getParentCategories,获取页面所属的分类,返回分类名是key的array
$title->getParentCategories();
// 会返回 array( '分类:XXX' => '词条名', '分类:YYY' => '词条名', '分类:ZZZ' => '词条名' )格式的array,有点坑
  • getRedirectsHere,获取重定向到此页面的页面,返回Title组成的array
$title->getRedirectsHere( $ns );

设定$ns可以限制返回的页面的命名空间

Message

用来获取Message的类,Message可以由扩展的i18n json、php及MediaWiki命名空间的词条定义。

  • exists,辨别该Message是否存在。
  • text、plain、escape、parse、parseAsBlock,以各种形式输出Message的内容,实际上都是几下要输出的类型然后丢给__toString的,一般情况什么都不写自动__toString也是不会出问题的。text返回把{{}}都展开了的文字;plain返回源码;escape返回HTML转义了的text;parse返回所有wiki代码都展开了的纯html;parseAsBlock返回带框的parse。
  • inLanguage,设定语言
wfMessage( 'msg-name-xxx' )->inLanguage( 'en' )->text();
// 把输出的语言转为en
  • inContentLanguage,设定语言为当前语言,如果没有操作过inLanguage的话这个是完全不会用到的。
wfMessage( 'msg-name-xxx' )->inContentLanguage()->text();
// 把输出的语言转为en
  • numParams,为Message添加参数,会顺序把Message里面的$1$2$n替换成那些参数。
wfMessage( 'msg-name-xxx' )->numParams( '1', '2' );
// 较常用的比如
wfMessage( 'parentheses' )->numParams( '括号内容' )->text();
// 会输出:(括号内容) 实际用什么类型的括号受到选用的语言影响,比如en的话就是()
wfMessage( 'quotation-marks' )->numParams( '引号内容' )->text();
// 会输出:“括号内容” 实际用什么类型的引号受到选用的语言影响,比如en的话就是""

其他常用的有:

  • semicolon-separator,分号(排比分隔符)";"
  • comma-separator,逗号(并列分隔符)"、"
  • colon-separator,冒号":"
  • pipe-separator,竖杠" | "
  • word-separator,字词分隔符(中文里并没有)""
  • parentheses,括号"($1)"
  • quotation-marks,引号"“$1”"
  • imgmultipageprev,上一页"← 上一页"
  • imgmultipagenext,下一页"下一页 →"

全域变量

wgExtensionCredits

定义扩展信息列表,用来在特殊:版本信息中显示

define( 'XXX_VERSION', '1.0.0' );
$wgExtensionCredits['parserhook'][] = array(
	'path' => __FILE__,
	'name' => 'ExtensionName',
	'version' => XXX_VERSION,
	'author' => array( 'XXX' ),
	'url' => 'http://thwiki.cc/',
	'descriptionmsg' => 'extensionname-desc',
);

一般都是parserhook或者other。

wgAutoloadClasses

定义autoload对照表,简单地

$wgAutoloadClasses['ClassNameXXX'] = __DIR__ . '/ClassNameXXX.php';

就行了,用autoload而不是require或include能节省很多服务器资源。

wgMessagesDirs

定义扩展Messages所在的文件夹,对于所有扩展都是一个写法,适用使用i18n json的情况

$wgMessagesDirs['ExtensionNameXXX'] = __DIR__ . '/i18n';

自MW1.21起扩展所有Messages都应该写在i18n json里。

wgExtensionMessagesFiles

然而wgMessagesDirs只能定义Message,不能用来定义magic和alias,所以还需要用这个定义扩展magic和alias所在的文件

$wgExtensionMessagesFiles['ExtensionNameXXXMagic'] = __DIR__ . '/ExtensionNameXXX.magic.php';
wgSpecialPages

定义特殊页面列表及定义特殊页面所用的类,需要增加特殊页面的时候需要

$wgSpecialPages['SpecialPageNameXXX'] = 'SpecialPageClassNameXXX';

同时也需要加到autoload里面。

wgSpecialPageGroups

定义各个特殊页面的分组

$wgSpecialPageGroups['SpecialPageNameXXX'] = 'pagetools';

差不多都是pagetools或parserhook。

wgJobClasses

定义Job列表及定义Job所用的类

$wgJobClasses['JobNameXXX'] = 'JobClassNameXXX';

同时也需要加到autoload里面。

wgAPIModules

定义api模组列表及定义api模组所用的类

$wgAPIModules['APIModulesNameXXX'] = 'APIModulesClassNameXXX';

当链接到api.php?action=APIModulesNameXXX的时候就会调用APIModulesClassNameXXX处理请求。

wgResourceModules

定义js/css模组列表

$wgResourceModules['ext.ModulesXXX'] = array(
	'localBasePath' => __DIR__,
	'remoteExtPath' => 'ExtensionNameXXX',
	'scripts' => array( 'ExtensionNameXXX.js' ),
	'styles' => array( 'ExtensionNameXXX.css' ),
);

其他还可以定义messages,一个包含着模组需要用到的Message的名称的array,在js里可以用

mw.message( 'MessageNameXXX' ).plain();
mw.message.apply( 'MessageNameXXX', ['aaa','bbb'] ).plain();

简单地调用在服务器端里定义了的Message。
以及dependency,一个包含着模组需要用到的模组的名称的array,系统会确保在dependency都加载好了的时候才加载你的模组。


Hook

GetPreferences

当用户进入到特殊:参数设置时会就调用,用来增加可以设定的项目。

function onGetPreferences ( User $user, array &$preferences ) {
	$preferences['prefs-name-xxx'] = array(
		'type' => 'toggle',
		'label-message' => 'prefs-name-xxx-msg',
		'section' => 'editing/advancedediting',
	);
	return true;
}

可以通过$user获取个别用户的详细资讯,不过多数用不着。
$preferences是一个定义了一大堆设定项的阵列,prefs-name-xxx就是把此设定储存到数据库时使用的key,必须确保不会重复。

AdminLinks

AdminLinks用的Hook,用来增加管理员链接页面的内容

function onAdminLinks ( &$tree ) {
	$section = $tree->getSection( wfMessage( 'adminlinks_general' )->text() );
	if ( is_null( $section ) ) return true;
	$row = $section->getRow( 'main' );
	$row->addItem( ALItem::newFromExternalLink( 'http://thwiki.cc/', '首页' ) );
	return true;
}
OutputPageParserOutput

在一级缓存之后,二级缓存之前,要对页面内容作出更改时用的

function onOutputPageParserOutput ( OutputPage &$page, ParserOutput $output ) {
	$id = $page->getTitle()->getArticleID();
	$output->setText( '<div>ID of this page is ' . $id . '</div>' . $output->getText() );
	return true;
}

$output不是传参考的所以改了也没用,不过即使只有$page也能做足够多的操作了。

BeforePageDisplay

在二级缓存之后,filecache之前,要对页面内容作出更改时用的,多数用于增加js

function onBeforePageDisplay ( OutputPage &$out, Skin &$sk ) {
	$out->setPageTitle( 'XXX' );
	$out->addModules( 'ext.ModulesXXX' );
	return true;
}

$out可以做很多种操作,就是不能简单地更改页面内容。

SkinTemplateNavigation

用于在页面顶部导航栏(阅读 编辑 查看历史)内增加内容。

function onSkinTemplateNavigation ( SkinTemplate &$sktemplate, array &$links ) {
	$links['views']['actionnamexxx'] = array(
		'class' => false,
		'text' => wfMessage( 'actionnamexxx-msg' )->text(),
		'href' => $sktemplate->getTitle()->getLocalURL( array( 'action' => 'edit', 'actionnamexxx' => '1' ) )
	);
	return true;
}

class设为false或是空字串的话该导航项就会在页面过窄的时候自动隐藏,节省空间。设为selected的话就会变成当前显示的那项,不过也需要手动把原本显示的项设为空。

ParserFirstCallInit

写ParserHook最重要的一个函数,Parser初始化的时候会调用一次,用来定义各种ParserHook。

class ExtMultiArrayMap {
	public static function Init( Parser &$parser ) {
		$parser->setFunctionHook( 'multimap', 'ExtMultiArrayMap::Map', SFH_OBJECT_ARGS );
		$parser->setFunctionHook( 'multitemplate', 'ExtMultiArrayMap::Template' );
		$parser->setFunctionHook( 'countmap', 'ExtMultiArrayMap::Count', SFH_OBJECT_ARGS );
		$parser->setFunctionHook( 'counttemplate', 'ExtMultiArrayMap::CountTem' );
		return true;
	}
}

定义ParserHook的同时,该ParserHook必须在magic内有对应的值

$magicWords['en'] = array(
	'multimap' => array( 0, 'multimap', 'multiarraymap' ),
	'multitemplate' => array( 0, 'multitem', 'multiarraytemplate', 'multitemplate' ),
	'countmap' => array( 0, 'countmap' ),
	'counttemplate' => array( 0, 'counttem', 'counttemplate' ),
);

array的第一个值是0则表示调用时不区分大小写,1则是区分大小写。

MagicWordwgVariableIDs

定义魔术字需要用另外一个Hook

class ExtMultiArrayMap {
	public static function setMagic ( &$list ) {
		$nohash = array(
			'pagename', 'fullpagename', 'rootpagename', 'basepagename',
			'subpagename', 'talkpagename', 'subjectpagename'
		);
		foreach ( $nohash as $func ) {
			$list[] = $func . 'h';
		}
	}
}

该魔术字也必须在magic内有对应的值

$magicWords['en'] = array(
	'pagenameh' => array( 0, 'PAGENAMEH' ),
	'fullpagenameh' => array( 0, 'FULLPAGENAMEH' ),
	'rootpagenameh' => array( 0, 'ROOTPAGENAMEH' ),
	'basepagenameh' => array( 0, 'BASEPAGENAMEH' ),
	'subpagenameh' => array( 0, 'SUBPAGENAMEH' ),
	'talkpagenameh' => array( 0, 'TALKPAGENAMEH' ),
	'subjectpagenameh' => array( 0, 'SUBJECTPAGENAMEH' ),
);

array的第一个值是0则表示调用时不区分大小写,1则是区分大小写。

ParserGetVariableValueSwitch

然后再用这个函数返回魔术字对应的值

class ExtMultiArrayMap {
	public static function getMagic ( &$parser, &$cache, &$index, &$value ) {
		switch( $index ) {
			case 'pagenameh':
				$value = ( $parser->mTitle->getText() );
				break;
			case 'fullpagenameh':
				$value = ( $parser->mTitle->getPrefixedText() );
				break;
			case 'subpagenameh':
				$value = ( $parser->mTitle->getSubpageText() );
				break;
			case 'rootpagenameh':
				$value = ( $parser->mTitle->getRootText() );
				break;
			case 'basepagenameh':
				$value = ( $parser->mTitle->getBaseText() );
				break;
			case 'talkpagenameh':
				if ( $parser->mTitle->canTalk() ) {
					$talkPage = $parser->mTitle->getTalkPage();
					$value = ( $talkPage->getPrefixedText() );
				} else {
					$value = '';
				}
				break;
			case 'subjectpagenameh':
				$subjPage = $parser->mTitle->getSubjectPage();
				$value = ( $subjPage->getPrefixedText() );
				break;
		}
		return true;
	}
}
ParserLimitReport

用来在ParserLimitReport里增加内容,一般情况下不会太用到,但是在调试时可以当作trace来用

function onParserLimitReport( $parser, &$report ) {
	global $wfxxxusedcount;
	$report .= 'XXX use count: ' . $wfxxxusedcount . "\n";
	return true;
}
<!-- 
NewPP limit report
CPU time usage: 0.102 seconds
Real time usage: 0.105 seconds
Preprocessor visited node count: 16/1000000
Preprocessor generated node count: 82/1000000
Post‐expand include size: 1504/2097152 bytes
Template argument size: 0/2097152 bytes
Highest expansion depth: 2/40
Expensive parser function count: 0/100
RegexRedirect use time: 0.00149703025818
CollisionManager use time: 4.29153442383E‐5
XXX use count: 0
ExtLoops count: 0/100
ExtRegexFun count: 0
-->