Manual:页面内容模型

This page is a translated version of the page Manual:Page content models and the translation is 64% complete.
Outdated translations are marked like this.
关于content pages vs. non-content pages,参见:Manual:Using custom namespaces#Content namespaces

MediaWiki 1.21 中引入的 ContentHandler 可讓您添加除 wikitext 以外的新的内容模型。 它使 wiki 页面可以由 wikitext 以外的数据组成,并以任何方式表示——例如:Markdown、reStructuredText、icalendar 或自定义 XML 格式。 这些内容模型的显示和编辑可以通过自定义的方式进行处理(例如不同的语法突出显示或全新的数据输入表单)。

本文将逐步介绍如何在扩展中创建新的内容模型。 假定你对一般的扩展开发实践有一定的了解。要有关要求的摘要,请参阅摘要部分

一个毫无意义的“Goat”内容模型将用于示例。 您还可以查看 DataPages 扩展,它是 扩展:Examples 的一部分。

警告 警告: Some sections of this page are outdated. In MW 1.38, Content::getParserOutput and AbstractContent::fillParserOutput were hard-deprecated in favour of ContentRenderer::getParserOutput. Extensions defining a content model should override ContentHandler::fillParserOutput.

注册

首先,将内容模型的名称和处理程序类添加到 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(550551 以上)。

By file extension

按文件扩展名:如果要通过在 wiki 页面名称上添加准文件类型后缀来确定内容类型,则可以使用 ContentHandlerDefaultModelFor 钩子。例如:

namespace MediaWiki\Extension\GoatExt;
use MediaWiki\Revision\Hook\ContentHandlerDefaultModelForHook;

class Hooks implements ContentHandlerDefaultModelForHook {
	public function onContentHandlerDefaultModelFor( $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 ContentHandlerDefaultModelFor hook instead.

"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 function onContentHandlerDefaultModelFor( $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 function onContentHandlerDefaultModelFor( $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()


参见