Manual:页面内容模型
在 MediaWiki 1.21 中引入的 ContentHandler 可讓您添加除 wikitext 以外的新的内容模型。 它使 wiki 页面可以由 wikitext 以外的数据组成,并以任何方式表示——例如:Markdown、reStructuredText、icalendar 或自定义 XML 格式。 这些内容模型的显示和编辑可以通过自定义的方式进行处理(例如不同的语法突出显示或全新的数据输入表单)。
本文将逐步介绍如何在扩展中创建新的内容模型。 假定你对一般的扩展开发实践有一定的了解。要有关要求的摘要,请参阅摘要部分。
一个毫无意义的“Goat”内容模型将用于示例。 您还可以查看 DataPages 扩展,它是 扩展:Examples 的一部分。
注册
首先,将内容模型的名称和处理程序类添加到 extension.json:
"ContentHandlers": {
"goat": "MediaWiki\\Extension\\GoatExt\\GoatContentHandler"
}
- 此处左侧的值是内容类型的名称,它可以是所需的任何唯一字符串,它与五种内置内容类型并存:'wikitext', 'JavaScript', 'CSS', 'plain text', 'JSON'。 此值在 Special:ChangeContentModel 和页面信息等地方向用户公开。
- 右侧的值是扩展 ContentHandler 的类的完全限定名称。
这将需要创建两个新类,例如 \MediaWiki\Extension\GoatExt\GoatContent
和 \MediaWiki\Extension\GoatExt\GoatContentHandler
(确保它们的命名空间已注册到 AutoloadNamespaces)。
下面是有关这些类的更多信息。
可选内容模型常量
上面的“goat”字符串是内容模型的 ID(在代码中通常称为 $modelId
),通常也定义为常量。
这些常量是为所有内置内容模型定义的,许多文档都引用了“CONTENT_MODEL_XXX”常量。
如果您尚未定义它们,这可能会出现异常。
定义应通过 extension.json
中的回调项完成。例如:
在 extension.json
中:
"callback": "MediaWiki\\Extension\\GoatExt\\Hooks::registrationCallback"
在 includes/Hooks.php
中:
namespace MediaWiki\Extension\GoatExt;
class Hooks {
public static function registrationCallback() {
// Must match the name used in the 'ContentHandlers' section of extension.json
define( 'CONTENT_MODEL_GOAT', 'goat' );
}
}
您不必这样做,只需使用字符串即可。
将内容模型分配给页面
页面可以手动更改其内容类型,但将其默认设为正确的内容类型很有用。设置默认内容模型的两种常见方法是按命名空间和按文件扩展名。
按命名空间:如果您希望某个 wiki 命名空间具有一个默认内容模型,您可以在 $1 中将其定义为: If you want an entire wiki namespace to have a default content model, you can define it as such in extension.json:
"namespaces": [
{
"id": 550,
"constant": "NS_GOAT",
"name": "Goat",
"subpages": false,
"content": true,
"defaultcontentmodel": "goat"
},
{
"id": 551,
"constant": "NS_GOAT_TALK",
"name": "Goat_talk",
"subpages": true,
"content": false,
"defaultcontentmodel": "wikitext"
}
]
请注意,已发布的扩展应在 扩展默认命名空间 页面上注册它们使用的命名空间 ID(550
和 551
以上)。
按文件扩展名:如果要通过在 wiki 页面名称上添加准文件类型后缀来确定内容类型,则可以使用 ContentHandlerDefaultModelFor 钩子。例如:
namespace MediaWiki\Extension\GoatExt;
class Hooks {
public static function onContentHandlerDefaultModelFor( \Title $title, &$model ) {
// Any page title (in any namespace) ending in '.goat'.
$ext = '.goat';
if ( substr( $title->getText(), -strlen( $ext ) ) === $ext ) {
// This is the constant you defined earlier.
$model = CONTENT_MODEL_GOAT;
// If you change the content model, return false.
return false;
}
// If you don't change it, return true.
return true;
}
}
ContentHandler
接下来要定义的是 GoatContentHandler 类,我们还在其中指定此内容类型的存储格式(在本例中为 text)。 ContentHandlers 对任何特定页面内容一无所知,但确定内容的一般结构和存储。
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatContentHandler extends \ContentHandler {
public function __construct( $modelId = 'goat' ) {
parent::__construct( $modelId, [ CONTENT_FORMAT_TEXT ] );
}
public function serializeContent( \Content $content, $format = null ) {
}
public function unserializeContent( $blob, $format = null ) {
}
public function makeEmptyContent() {
return new GoatContent();
}
public function supportsDirectEditing() {
return true;
}
}
Content
GoatContent
类是内容数据的表示形式,对页面、修订或它在数据库中的存储方式一无所知。
除了所需的 7 个继承方法外,还可以添加其他特定于域的公共方法;在这种情况下,我们希望能够检索 goat 的名字。
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatContent extends \AbstractContent {
public function __construct( $modelId = 'goat' ) {
parent::__construct( $modelId );
}
public function getTextForSearchIndex() {
}
public function getWikitextForTransclusion() {
}
public function getTextForSummary( $maxLength = 250 ) {
}
public function getNativeData() {
}
public function getSize() {
}
public function copy() {
}
public function isCountable( $hasLinks = null ) {
}
public function getName() {
return 'Garry';
}
}
编辑表单
现在我们已经设置好了骨架,我们将尝试编辑一个 Goat。
为此,我们需要创建 GoatContentHandler::getActionOverrides()
并指定哪些操作映射到哪些类。
首先,我们将只处理“编辑”(对应于网址中的 ?action=edit
)。
public function getActionOverrides() {
return [
'edit' => GoatEditAction::class,
];
}
我们将创建新的 GoatEditAction
类,与核心 EditAction
基本相同,但使用我们自己的 GoatEditPage
:
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatEditAction extends \EditAction {
public function show() {
$this->useTransactionalTimeLimit();
$editPage = new GoatEditPage( $this->getArticle() );
$editPage->setContextTitle( $this->getTitle() );
$editPage->edit();
}
}
我们新的 GoatEditPage
类是动作发生的地方:
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatEditPage extends \MediaWiki\EditPage\EditPage {
protected function showContentForm() {
$out = $this->context->getOutput();
// Get the data.
$name = $this->getCurrentContent()->getGoatName();
// Create the form.
$nameField = new \OOUI\FieldLayout(
new \OOUI\TextInputWidget( [ 'name' => 'goat_name', 'value' => $name ] ),
[ 'label' => 'Name', 'align' => 'left' ]
);
$out->addHTML( $nameField );
}
}
您现在应该能够编辑页面并查看您的表单。 但是当您将数据放入其中并点击“预览”时,您会看到事情尚未完全正常运行,您没有得到任何输出,您提交的文本也没有再次显示在表单中。
因此,我们还必须覆写“提交”操作,使用新的 GoatSubmitAction
类,并在 GoatContentHandler::getActionOverrides()
方法中添加 'submit' => GoatSubmitAction::class,
。
我们的 GoatSubmitAction
类应该与 core 的类相同,但继承自我们的 GoatEditAction
。
显示
内容模型负责生成显示所需的任何输出。 这通常涉及处理其数据并以某种方式生成 HTML,以添加到解析器输出中。
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatContentHandler extends \AbstractContentHandler {
protected function fillParserOutput(
Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output
) {
// e.g. $output->setText( $html );
}
}
显示一则说明/文档
有时您可能希望显示具有自定义内容模型(如 JSON)的文章的某些信息或某些文档。 实际上,没有系统消息可以在这些页面上显示一些文本(除了 MediaWiki:Clearyourcache 仅显示在 JavaScript 和 CSS 页面上方)。 您可能希望查看 phab:T206395 以获取更多详细信息。
比较修订
GoatDifferenceEngine
类是 goat 内容之间差异的展示类。
我们覆盖默认的 generateContentDiffBody
方法来生成差异。
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatDifferenceEngine extends \DifferenceEngine {
public function generateContentDiffBody( Content $old, Content $new ) {
}
}
为了告诉 MediaWiki 使用我们的 GoatDifferenceEngine
,我们覆盖了 GoatContentHandler
中的 getDiffEngineClass
。
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatContentHandler extends \ContentHandler {
public function getDiffEngineClass() {
return GoatDifferenceEngine::class;
}
}
摘要
若要使用自定义编辑表单实现新的内容模型,请创建以下内容:
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatContent extends \AbstractContent {
public function __construct( $modelId = 'goat' ) {
parent::__construct($modelId);
}
public function getTextForSearchIndex() {}
public function getWikitextForTransclusion() {}
public function getTextForSummary( $maxLength = 250 ) {}
public function getNativeData() {}
public function getSize() {}
public function copy() {}
public function isCountable( $hasLinks = null ) {}
}
<?php
namespace MediaWiki\Extension\GoatExt;
class GoatContentHandler extends \ContentHandler {
public function __construct( $modelId = CONTENT_MODEL_GOAT, $formats = ['text/x-goat'] ) {
parent::__construct($modelId, $formats);
}
protected function getContentClass() {}
public function supportsDirectEditing() {}
public function serializeContent( \Content $content, $format = null ) {}
public function unserializeContent( $blob, $format = null ) {}
public function makeEmptyContent() {}
public function getActionOverrides() {}
protected function fillParserOutput( \Title $title, $revId, \ParserOptions $options, $generateHtml, \ParserOutput &$output) {}
}
Checklist
Action | Example | |
---|---|---|
Define a content model id (al. content type) and content model constant | goat CONTENT_MODEL_GOAT
| |
Choose your preferred mechanism of applying a content model: | ||
(a) By namespace. Define namespace constants, ids and names given any of them is not already taken |
NS_GOAT (id: 550, name 'Goat')NS_GOAT_TALK (id: 551, name: 'Goat talk')
| |
(b) By extension in pagename | .goat
| |
(c) Other | through 'Page information' interface (&action=info )
| |
extension.json
| ||
ContentHandlers - register a ContentHandler subclass for the new content model
|
"ContentHandlers": {
"goat": "GoatExt\\GoatContentHandler"
}
|
↑ |
callback - name your class method for callbacks, conventionally in your Hooks file
|
"callback": "GoatExt\\Hooks::registrationCallback"
|
↑ |
Hooks - provide a hook method for ContentHandlerDefaultModelFor
|
"Hooks": {
"ContentHandlerDefaultModelFor": [ "GoatExt\\Hooks::onContentHandlerDefaultModelFor" ]
}
| |
namespaces - define namespaces with the appropriate value for defaultcontentmodel .
For more options, use |
"namespaces": [
{
"id": 550,
"constant": "NS_GOAT",
"name": "Goat",
"subpages": false,
"content": false,
"defaultcontentmodel": "goat"
},
{
"id": 551,
"constant": "NS_GOAT_TALK",
"name": "Goat_talk",
"subpages": true,
"content": false,
"defaultcontentmodel": "wikitext"
}
]
|
↑ |
AutoloadNamespaces - load Content and ContentHandler subclasses in subdirectory
|
"AutoloadNamespaces": {
"GoatExt\\": "includes/"
}
| |
[[Special:MyLanguage/Manual:Extension.json/Schema#AutoloadClasses|]] - load Content and ContentHandler subclasses explicitly
|
"AutoloadClasses": {
"GoatExt\\Hooks": "includes/Hooks.php",
"GoatExt\\GoatContent": "includes/GoatContent.php",
"GoatExt\\GoatContentHandler": "includes/GoatContentHandler.php"
}
| |
Hooks file | GoatExt\\Hooks
| |
Add method for the callback | public static function registrationCallback() {
// Must match the name used in the 'ContentHandlers' section of extension.json
define( 'CONTENT_MODEL_GOAT', 'goat' );
}
| |
Add method for the ContentHandlerDefaultModelFor hook for wiki pages in the intended namespace
|
public static function onContentHandlerDefaultModelFor( \Title $title, &$model ) {
if ( $title->inNamespace( NS_GOAT ) {
$model = CONTENT_MODEL_GOAT;
return false;
}
return true;
}
|
|
Or add method for the ContentHandlerDefaultModelFor hook for wiki pages with the intended file extension
|
public static function onContentHandlerDefaultModelFor( \Title $title, &$model ) {
$ext = '.goat';
if ( substr( $title->getText(), -strlen( $ext ) ) === $ext ) {
$model = CONTENT_MODEL_GOAT;
return false;
}
return true;
}
|
↑ |
Content and ContentHandler subclasses | ||
Add a Content subclass by extending AbstractContent or one of the subclasses available (e.g. TextContent ) | namespace GoatExt;
class GoatContent extends \AbstractContent {
public function __construct( $modelId = 'goat' ) {
parent::__construct( $modelId );
}
public function getTextForSearchIndex() {
}
public function getWikitextForTransclusion() {
}
public function getTextForSummary( $maxLength = 250 ) {
}
public function getNativeData() {
}
public function getSize() {
}
public function copy() {
}
public function isCountable( $hasLinks = null ) {
}
// etc. See documentation for details.
}
|
↑ |
Add a ContentHandler subclass by extending either ContentHandler or one of its subclasses (e.g. TextContentHandler or CodeContentHandler ) | namespace GoatExt;
class GoatContentHandler extends \ContentHandler {
public function __construct( $modelId = 'goat' ) {
parent::__construct( $modelId, [ CONTENT_FORMAT_TEXT ] );
}
public function serializeContent( \Content $content, $format = null ) {
}
public function unserializeContent( $blob, $format = null ) {
}
public function makeEmptyContent() {
return new GoatContent();
}
public function supportsDirectEditing() {
return true;
}
protected function fillParserOutput(
Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output
) {
// e.g. $output->setText( $html );
}
// etc. See documentation for details
}
|
↑ |
Compare revisions | Override ContentHandler::getDiffEngineClass() and DifferenceEngine::generateContentDiffBody() | ↑ |
Modify associated actions such as 'edit' | Override ContentHandler::getActionOverrides() and EditAction::show() | ↑ |
参见
- Manual:Hooks/ContentHandlerDefaultModelFor
- Help:更改内容模型 - 有关更改内容模型的用户文档。
- 扩展:Examples - 有关自定义内容模型(但尚未包含任何自定义编辑表单)的示例。