'UncategorizedTemplatesPage' => __DIR__ . '/includes/specials/SpecialUncategorizedtemplates.php',
'Undelete' => __DIR__ . '/maintenance/undelete.php',
'UnifiedDiffFormatter' => __DIR__ . '/includes/diff/UnifiedDiffFormatter.php',
+ 'UnknownContent' => __DIR__ . '/includes/content/UnknownContent.php',
+ 'UnknownContentHandler' => __DIR__ . '/includes/content/UnknownContentHandler.php',
'UnlistedSpecialPage' => __DIR__ . '/includes/specialpage/UnlistedSpecialPage.php',
'UnprotectAction' => __DIR__ . '/includes/actions/UnprotectAction.php',
'UnregisteredLocalFile' => __DIR__ . '/includes/filerepo/file/UnregisteredLocalFile.php',
+ 'UnsupportedSlotDiffRenderer' => __DIR__ . '/includes/diff/UnsupportedSlotDiffRenderer.php',
'UnusedCategoriesPage' => __DIR__ . '/includes/specials/SpecialUnusedcategories.php',
'UnusedimagesPage' => __DIR__ . '/includes/specials/SpecialUnusedimages.php',
'UnusedtemplatesPage' => __DIR__ . '/includes/specials/SpecialUnusedtemplates.php',
# checking, etc.
if ( $this->formtype == 'initial' || $this->firsttime ) {
if ( $this->initialiseForm() === false ) {
- $out = $this->context->getOutput();
- if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it
- $this->noSuchSectionPage();
- }
return;
}
$content = $this->getContentObject( false ); # TODO: track content object?!
if ( $content === false ) {
+ $out = $this->context->getOutput();
+ if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it
+ $this->noSuchSectionPage();
+ }
+ return false;
+ }
+
+ if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
+ $modelMsg = $this->getContext()->msg( 'content-model-' . $content->getModel() );
+ $modelName = $modelMsg->exists() ? $modelMsg->text() : $content->getModel();
+
+ $out = $this->context->getOutput();
+ $out->showErrorPage(
+ 'modeleditnotsupported-title',
+ 'modeleditnotsupported-text',
+ $modelName
+ );
return false;
}
+
$this->textbox1 = $this->toEditText( $content );
$user = $this->context->getUser();
--- /dev/null
+<?php
+/**
+ * Content object implementation for representing unknown content.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.34
+ *
+ * @file
+ * @ingroup Content
+ *
+ * @author Daniel Kinzler
+ */
+
+/**
+ * Content object implementation representing unknown content.
+ *
+ * This can be used to handle content for which no ContentHandler exists on the system,
+ * perhaps because the extension that provided it has been removed.
+ *
+ * UnknownContent instances are immutable.
+ *
+ * @ingroup Content
+ */
+class UnknownContent extends AbstractContent {
+
+ /** @var string */
+ private $data;
+
+ /**
+ * @param string $data
+ * @param string $model_id The model ID to handle
+ */
+ public function __construct( $data, $model_id ) {
+ parent::__construct( $model_id );
+
+ $this->data = $data;
+ }
+
+ /**
+ * @return Content $this
+ */
+ public function copy() {
+ // UnknownContent is immutable, so no need to copy.
+ return $this;
+ }
+
+ /**
+ * Returns an empty string.
+ *
+ * @param int $maxlength
+ *
+ * @return string
+ */
+ public function getTextForSummary( $maxlength = 250 ) {
+ return '';
+ }
+
+ /**
+ * Returns the data size in bytes.
+ *
+ * @return int
+ */
+ public function getSize() {
+ return strlen( $this->data );
+ }
+
+ /**
+ * Returns false.
+ *
+ * @param bool|null $hasLinks If it is known whether this content contains links,
+ * provide this information here, to avoid redundant parsing to find out.
+ *
+ * @return bool
+ */
+ public function isCountable( $hasLinks = null ) {
+ return false;
+ }
+
+ /**
+ * @return string data of unknown format and meaning
+ */
+ public function getNativeData() {
+ return $this->getData();
+ }
+
+ /**
+ * @return string data of unknown format and meaning
+ */
+ public function getData() {
+ return $this->data;
+ }
+
+ /**
+ * Returns an empty string.
+ *
+ * @return string The raw text.
+ */
+ public function getTextForSearchIndex() {
+ return '';
+ }
+
+ /**
+ * Returns false.
+ */
+ public function getWikitextForTransclusion() {
+ return false;
+ }
+
+ /**
+ * Fills the ParserOutput with an error message.
+ */
+ protected function fillParserOutput( Title $title, $revId,
+ ParserOptions $options, $generateHtml, ParserOutput &$output
+ ) {
+ $msg = wfMessage( 'unsupported-content-model', [ $this->getModel() ] );
+ $html = Html::rawElement( 'div', [ 'class' => 'error' ], $msg->inContentLanguage()->parse() );
+ $output->setText( $html );
+ }
+
+ /**
+ * Returns false.
+ */
+ public function convert( $toModel, $lossy = '' ) {
+ return false;
+ }
+
+ protected function equalsInternal( Content $that ) {
+ if ( !$that instanceof UnknownContent ) {
+ return false;
+ }
+
+ return $this->getData() == $that->getData();
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Base content handler class for flat text contents.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.34
+ *
+ * @file
+ * @ingroup Content
+ */
+
+/**
+ * Content handler implementation for unknown content.
+ *
+ * This can be used to handle content for which no ContentHandler exists on the system,
+ * perhaps because the extension that provided it has been removed.
+ *
+ * @ingroup Content
+ */
+class UnknownContentHandler extends ContentHandler {
+
+ /**
+ * Constructs an UnknownContentHandler. Since UnknownContentHandler can be registered
+ * for multiple model IDs on a system, multiple instances of UnknownContentHandler may
+ * coexist.
+ *
+ * To preserve the serialization format of the original content model, it must be supplied
+ * to the constructor via the $formats parameter. If not given, the default format is
+ * reported as 'application/octet-stream'.
+ *
+ * @param string $modelId
+ * @param string[]|null $formats
+ */
+ public function __construct( $modelId, $formats = null ) {
+ parent::__construct(
+ $modelId,
+ $formats ?? [
+ 'application/octet-stream',
+ 'application/unknown',
+ 'application/x-binary',
+ 'text/unknown',
+ 'unknown/unknown',
+ ]
+ );
+ }
+
+ /**
+ * Returns the content's data as-is.
+ *
+ * @param Content $content
+ * @param string|null $format The serialization format to check
+ *
+ * @return mixed
+ */
+ public function serializeContent( Content $content, $format = null ) {
+ /** @var UnknownContent $content */
+ return $content->getData();
+ }
+
+ /**
+ * Constructs an UnknownContent instance wrapping the given data.
+ *
+ * @since 1.21
+ *
+ * @param string $blob serialized content in an unknown format
+ * @param string|null $format ignored
+ *
+ * @return Content The UnknownContent object wrapping $data
+ */
+ public function unserializeContent( $blob, $format = null ) {
+ return new UnknownContent( $blob, $this->getModelID() );
+ }
+
+ /**
+ * Creates an empty UnknownContent object.
+ *
+ * @since 1.21
+ *
+ * @return Content A new UnknownContent object with empty text.
+ */
+ public function makeEmptyContent() {
+ return $this->unserializeContent( '' );
+ }
+
+ /**
+ * @return false
+ */
+ public function supportsDirectEditing() {
+ return false;
+ }
+
+ /**
+ * @param IContextSource $context
+ *
+ * @return SlotDiffRenderer
+ */
+ protected function getSlotDiffRendererInternal( IContextSource $context ) {
+ return new UnsupportedSlotDiffRenderer( $context );
+ }
+}
}
}
- if ( !$this->mOldRev->isDeleted( RevisionRecord::DELETED_TEXT ) &&
- !$this->mNewRev->isDeleted( RevisionRecord::DELETED_TEXT )
+ if ( $this->userCanEdit( $this->mOldRev ) &&
+ $this->userCanEdit( $this->mNewRev )
) {
$undoLink = Html::element( 'a', [
'href' => $this->mNewPage->getLocalURL( [
return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
}
+ /**
+ * @param Revision $rev
+ * @return bool whether the user can see and edit the revision.
+ */
+ private function userCanEdit( Revision $rev ) {
+ $user = $this->getUser();
+
+ if ( !$rev->getContentHandler()->supportsDirectEditing() ) {
+ return false;
+ }
+
+ if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Get a header for a specified revision.
*
$header = Linker::linkKnown( $title, $header, [],
[ 'oldid' => $rev->getId() ] );
- if ( $rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
+ if ( $this->userCanEdit( $rev ) ) {
$editQuery = [ 'action' => 'edit' ];
if ( !$rev->isCurrent() ) {
$editQuery['oldid'] = $rev->getId();
* must have the same content model that was used to obtain this diff renderer.
* @param Content|null $oldContent
* @param Content|null $newContent
- * @return string
+ * @return string HTML, one or more <tr> tags.
*/
abstract public function getDiff( Content $oldContent = null, Content $newContent = null );
* Diff the text representations of two content objects (or just two pieces of text in general).
* @param string $oldText
* @param string $newText
- * @return string
+ * @return string HTML, one or more <tr> tags.
*/
public function getTextDiff( $oldText, $newText ) {
Assert::parameterType( 'string', $oldText, '$oldText' );
--- /dev/null
+<?php
+/**
+ * Renders a slot diff by doing a text diff on the native representation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+/**
+ * Produces a warning message about not being able to render a slot diff.
+ *
+ * @since 1.34
+ *
+ * @ingroup DifferenceEngine
+ */
+class UnsupportedSlotDiffRenderer extends SlotDiffRenderer {
+
+ /**
+ * @var MessageLocalizer
+ */
+ private $localizer;
+
+ /**
+ * UnsupportedSlotDiffRenderer constructor.
+ *
+ * @param MessageLocalizer $localizer
+ */
+ public function __construct( MessageLocalizer $localizer ) {
+ $this->localizer = $localizer;
+ }
+
+ /** @inheritDoc */
+ public function getDiff( Content $oldContent = null, Content $newContent = null ) {
+ $this->normalizeContents( $oldContent, $newContent );
+
+ $oldModel = $oldContent->getModel();
+ $newModel = $newContent->getModel();
+
+ if ( $oldModel !== $newModel ) {
+ $msg = $this->localizer->msg( 'unsupported-content-diff2', $oldModel, $newModel );
+ } else {
+ $msg = $this->localizer->msg( 'unsupported-content-diff', $oldModel );
+ }
+
+ return Html::rawElement(
+ 'tr',
+ [],
+ Html::rawElement(
+ 'td',
+ [ 'colspan' => 4, 'class' => 'error' ],
+ $msg->parse()
+ )
+ );
+ }
+
+}
"nocreate-loggedin": "You do not have permission to create new pages.",
"sectioneditnotsupported-title": "Section editing not supported",
"sectioneditnotsupported-text": "Section editing is not supported in this page.",
+ "modeleditnotsupported-title": "Editing not supported",
+ "modeleditnotsupported-text": "Editing is not supported for content model $1.",
"permissionserrors": "Permission error",
"permissionserrorstext": "You do not have permission to do that, for the following {{PLURAL:$1|reason|reasons}}:",
"permissionserrorstext-withaction": "You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:",
"content-model-json": "JSON",
"content-json-empty-object": "Empty object",
"content-json-empty-array": "Empty array",
+ "unsupported-content-model": "<strong>Warning:</strong> Content model $1 is not supported on this wiki.",
+ "unsupported-content-diff": "Diffs are not supported for content model $1.",
+ "unsupported-content-diff2": "Diffs between the content models $1 and $2 are not supported on this wiki.",
"deprecated-self-close-category": "Pages using invalid self-closed HTML tags",
"deprecated-self-close-category-desc": "The page contains invalid self-closed HTML tags, such as <code><b/></code> or <code><span/></code>. The behavior of these will change soon to be consistent with the HTML5 specification, so their use in wikitext is deprecated.",
"duplicate-args-warning": "<strong>Warning:</strong> [[:$1]] is calling [[:$2]] with more than one value for the \"$3\" parameter. Only the last value provided will be used.",
"nocreate-loggedin": "Used as error message.\n\nSee also:\n* {{msg-mw|Nocreatetext}}",
"sectioneditnotsupported-title": "Page title of special page, which presumably appears when someone tries to edit a section, and section editing is disabled. Explanation of section editing on [[meta:Help:Section_editing#Section_editing|meta]].",
"sectioneditnotsupported-text": "I think this is the text of an error message, which presumably appears when someone tries to edit a section, and section editing is disabled. Explanation of section editing on [[meta:Help:Section_editing#Section_editing|meta]].",
+ "modeleditnotsupported-title": "Page title used on the edit page when editing is not supported for the page's content model.",
+ "modeleditnotsupported-text": "Error message show on the edit page when editing is not supported for the page's content model..\n\nParameters:\n* $1 - the name of the content model.",
"permissionserrors": "Used as title of error message.\n\nSee also:\n* {{msg-mw|loginreqtitle}}\n{{Identical|Permission error}}",
"permissionserrorstext": "This message is \"without action\" version of {{msg-mw|Permissionserrorstext-withaction}}.\n\nParameters:\n* $1 - the number of reasons that were found why ''the action'' cannot be performed",
"permissionserrorstext-withaction": "This message is \"with action\" version of {{msg-mw|Permissionserrorstext}}.\n\nParameters:\n* $1 - the number of reasons that were found why the action cannot be performed\n* $2 - one of the action-* messages (for example {{msg-mw|action-edit}}) or other such messages tagged with {{tl|doc-action}} in their documentation\n\nPlease report at [[Support]] if you are unable to properly translate this message. Also see [[phab:T16246]] (now closed) for background.",
"content-model-json": "{{optional}}\nName for the JSON content model, used when decribing what type of content a page contains.\n\nThis message is substituted in:\n*{{msg-mw|Bad-target-model}}\n*{{msg-mw|Content-not-allowed-here}}\n{{identical|JSON}}",
"content-json-empty-object": "Used to represent an object with no properties on a JSON content model page.",
"content-json-empty-array": "Used to represent an array with no values on a JSON content model page.",
+ "unsupported-content-model": "Warning shown when trying to display content with an unknown model.\n\nParameters:\n* $1 - the technical name of the content model.",
+ "unsupported-content-diff": "Warning shown when trying to display a diff between content with a model that does not support diffing (perhaps because it's an unknown model).\n\nParameters:\n* $1 - the technical name of the model of the content",
+ "unsupported-content-diff2": "Warning shown when trying to display a diff between content that uses models that do not support diffing with each other.\n\nParameters:\n* $1 - the technical name of the model of the old content\n* $2 - the technical name of the model of the new content.",
"deprecated-self-close-category": "This message is used as a category name for a [[mw:Special:MyLanguage/Help:Tracking categories|tracking category]] where pages are placed automatically if they contain invalid self-closed HTML tags, such as <code><b/></code> or <code><span/></code>. The behavior of these will change soon to be consistent with the HTML5 specification, so their use in wikitext is deprecated.",
"deprecated-self-close-category-desc": "Invalid self-closed HTML tag category description. Shown on [[Special:TrackingCategories]].\n\nSee also:\n* {{msg-mw|deprecated-self-close-category}}",
"duplicate-args-warning": "If a page calls a template and specifies the same argument more than once, such as <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> or <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>, this warning is displayed when previewing.\n\nParameters:\n* $1 - The calling page\n* $2 - The called template\n* $3 - The name of the duplicated argument",
global $wgHooks;
$wgHooks[$hookName] = [ $handler ];
}
+
+ protected function getMockMessage( $text, ...$params ) {
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+
+ $msg = $this->getMockBuilder( Message::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [] )
+ ->getMock();
+
+ $msg->method( 'toString' )->willReturn( $text );
+ $msg->method( '__toString' )->willReturn( $text );
+ $msg->method( 'text' )->willReturn( $text );
+ $msg->method( 'parse' )->willReturn( $text );
+ $msg->method( 'plain' )->willReturn( $text );
+ $msg->method( 'parseAsBlock' )->willReturn( $text );
+ $msg->method( 'escaped' )->willReturn( $text );
+
+ $msg->method( 'title' )->willReturn( $msg );
+ $msg->method( 'inLanguage' )->willReturn( $msg );
+ $msg->method( 'inContentLanguage' )->willReturn( $msg );
+ $msg->method( 'useDatabase' )->willReturn( $msg );
+ $msg->method( 'setContext' )->willReturn( $msg );
+
+ $msg->method( 'exists' )->willReturn( true );
+ $msg->method( 'content' )->willReturn( new MessageContent( $msg ) );
+
+ return $msg;
+ }
}
--- /dev/null
+<?php
+
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SlotRenderingProvider;
+
+/**
+ * @group ContentHandler
+ */
+class UnknownContentHandlerTest extends MediaWikiLangTestCase {
+ /**
+ * @covers UnknownContentHandler::supportsDirectEditing
+ */
+ public function testSupportsDirectEditing() {
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing supported' );
+ }
+
+ /**
+ * @covers UnknownContentHandler::serializeContent
+ */
+ public function testSerializeContent() {
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $content = new UnknownContent( 'hello world', 'horkyporky' );
+
+ $this->assertEquals( 'hello world', $handler->serializeContent( $content ) );
+ $this->assertEquals(
+ 'hello world',
+ $handler->serializeContent( $content, 'application/horkyporky' )
+ );
+ }
+
+ /**
+ * @covers UnknownContentHandler::unserializeContent
+ */
+ public function testUnserializeContent() {
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $content = $handler->unserializeContent( 'hello world' );
+ $this->assertEquals( 'hello world', $content->getData() );
+
+ $content = $handler->unserializeContent( 'hello world', 'application/horkyporky' );
+ $this->assertEquals( 'hello world', $content->getData() );
+ }
+
+ /**
+ * @covers UnknownContentHandler::makeEmptyContent
+ */
+ public function testMakeEmptyContent() {
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $content = $handler->makeEmptyContent();
+
+ $this->assertTrue( $content->isEmpty() );
+ $this->assertEquals( '', $content->getData() );
+ }
+
+ public static function dataIsSupportedFormat() {
+ return [
+ [ null, true ],
+ [ 'application/octet-stream', true ],
+ [ 'unknown/unknown', true ],
+ [ 'text/plain', false ],
+ [ 99887766, false ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataIsSupportedFormat
+ * @covers UnknownContentHandler::isSupportedFormat
+ */
+ public function testIsSupportedFormat( $format, $supported ) {
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $this->assertEquals( $supported, $handler->isSupportedFormat( $format ) );
+ }
+
+ /**
+ * @covers ContentHandler::getSecondaryDataUpdates
+ */
+ public function testGetSecondaryDataUpdates() {
+ $title = Title::newFromText( 'Somefile.jpg', NS_FILE );
+ $content = new UnknownContent( '', 'horkyporky' );
+
+ /** @var SlotRenderingProvider $srp */
+ $srp = $this->getMock( SlotRenderingProvider::class );
+
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $updates = $handler->getSecondaryDataUpdates( $title, $content, SlotRecord::MAIN, $srp );
+
+ $this->assertEquals( [], $updates );
+ }
+
+ /**
+ * @covers ContentHandler::getDeletionUpdates
+ */
+ public function testGetDeletionUpdates() {
+ $title = Title::newFromText( 'Somefile.jpg', NS_FILE );
+
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $updates = $handler->getDeletionUpdates( $title, SlotRecord::MAIN );
+
+ $this->assertEquals( [], $updates );
+ }
+
+ /**
+ * @covers ContentHandler::getDeletionUpdates
+ */
+ public function testGetSlotDiffRenderer() {
+ $context = new RequestContext();
+ $context->setRequest( new FauxRequest() );
+
+ $handler = new UnknownContentHandler( 'horkyporky' );
+ $slotDiffRenderer = $handler->getSlotDiffRenderer( $context );
+
+ $oldContent = $handler->unserializeContent( 'Foo' );
+ $newContent = $handler->unserializeContent( 'Foo bar' );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ $this->assertNotEmpty( $diff );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class UnknownContentTest extends MediaWikiLangTestCase {
+
+ /**
+ * @param string $data
+ * @return UnknownContent
+ */
+ public function newContent( $data, $type = 'xyzzy' ) {
+ return new UnknownContent( $data, $type );
+ }
+
+ /**
+ * @covers UnknownContent::getParserOutput
+ */
+ public function testGetParserOutput() {
+ $this->setUserLang( 'en' );
+ $this->setContentLang( 'qqx' );
+
+ $title = Title::newFromText( 'Test' );
+ $content = $this->newContent( 'Horkyporky' );
+
+ $po = $content->getParserOutput( $title );
+ $html = $po->getText();
+ $html = preg_replace( '#<!--.*?-->#sm', '', $html ); // strip comments
+
+ $this->assertNotContains( 'Horkyporky', $html );
+ $this->assertNotContains( '(unsupported-content-model)', $html );
+ }
+
+ /**
+ * @covers UnknownContent::preSaveTransform
+ */
+ public function testPreSaveTransform() {
+ $title = Title::newFromText( 'Test' );
+ $user = $this->getTestUser()->getUser();
+ $content = $this->newContent( 'Horkyporky ~~~' );
+
+ $options = new ParserOptions();
+
+ $this->assertSame( $content, $content->preSaveTransform( $title, $user, $options ) );
+ }
+
+ /**
+ * @covers UnknownContent::preloadTransform
+ */
+ public function testPreloadTransform() {
+ $title = Title::newFromText( 'Test' );
+ $content = $this->newContent( 'Horkyporky ~~~' );
+
+ $options = new ParserOptions();
+
+ $this->assertSame( $content, $content->preloadTransform( $title, $options ) );
+ }
+
+ /**
+ * @covers UnknownContent::getRedirectTarget
+ */
+ public function testGetRedirectTarget() {
+ $content = $this->newContent( '#REDIRECT [[Horkyporky]]' );
+ $this->assertNull( $content->getRedirectTarget() );
+ }
+
+ /**
+ * @covers UnknownContent::isRedirect
+ */
+ public function testIsRedirect() {
+ $content = $this->newContent( '#REDIRECT [[Horkyporky]]' );
+ $this->assertFalse( $content->isRedirect() );
+ }
+
+ /**
+ * @covers UnknownContent::isCountable
+ */
+ public function testIsCountable() {
+ $content = $this->newContent( '[[Horkyporky]]' );
+ $this->assertFalse( $content->isCountable( true ) );
+ }
+
+ /**
+ * @covers UnknownContent::getTextForSummary
+ */
+ public function testGetTextForSummary() {
+ $content = $this->newContent( 'Horkyporky' );
+ $this->assertSame( '', $content->getTextForSummary() );
+ }
+
+ /**
+ * @covers UnknownContent::getTextForSearchIndex
+ */
+ public function testGetTextForSearchIndex() {
+ $content = $this->newContent( 'Horkyporky' );
+ $this->assertSame( '', $content->getTextForSearchIndex() );
+ }
+
+ /**
+ * @covers UnknownContent::copy
+ */
+ public function testCopy() {
+ $content = $this->newContent( 'hello world.' );
+ $copy = $content->copy();
+
+ $this->assertSame( $content, $copy );
+ }
+
+ /**
+ * @covers UnknownContent::getSize
+ */
+ public function testGetSize() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( 12, $content->getSize() );
+ }
+
+ /**
+ * @covers UnknownContent::getData
+ */
+ public function testGetData() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( 'hello world.', $content->getData() );
+ }
+
+ /**
+ * @covers UnknownContent::getNativeData
+ */
+ public function testGetNativeData() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( 'hello world.', $content->getNativeData() );
+ }
+
+ /**
+ * @covers UnknownContent::getWikitextForTransclusion
+ */
+ public function testGetWikitextForTransclusion() {
+ $content = $this->newContent( 'hello world.' );
+
+ $this->assertEquals( '', $content->getWikitextForTransclusion() );
+ }
+
+ /**
+ * @covers UnknownContent::getModel
+ */
+ public function testGetModel() {
+ $content = $this->newContent( "hello world.", 'horkyporky' );
+
+ $this->assertEquals( 'horkyporky', $content->getModel() );
+ }
+
+ /**
+ * @covers UnknownContent::getContentHandler
+ */
+ public function testGetContentHandler() {
+ $this->mergeMwGlobalArrayValue(
+ 'wgContentHandlers',
+ [ 'horkyporky' => 'UnknownContentHandler' ]
+ );
+
+ $content = $this->newContent( "hello world.", 'horkyporky' );
+
+ $this->assertInstanceOf( UnknownContentHandler::class, $content->getContentHandler() );
+ $this->assertEquals( 'horkyporky', $content->getContentHandler()->getModelID() );
+ }
+
+ public static function dataIsEmpty() {
+ return [
+ [ '', true ],
+ [ ' ', false ],
+ [ '0', false ],
+ [ 'hallo welt.', false ],
+ ];
+ }
+
+ /**
+ * @dataProvider dataIsEmpty
+ * @covers UnknownContent::isEmpty
+ */
+ public function testIsEmpty( $text, $empty ) {
+ $content = $this->newContent( $text );
+
+ $this->assertEquals( $empty, $content->isEmpty() );
+ }
+
+ public function provideEquals() {
+ return [
+ [ new UnknownContent( "hallo", 'horky' ), null, false ],
+ [ new UnknownContent( "hallo", 'horky' ), new UnknownContent( "hallo", 'horky' ), true ],
+ [ new UnknownContent( "hallo", 'horky' ), new UnknownContent( "hallo", 'xyzzy' ), false ],
+ [ new UnknownContent( "hallo", 'horky' ), new JavaScriptContent( "hallo" ), false ],
+ [ new UnknownContent( "hallo", 'horky' ), new WikitextContent( "hallo" ), false ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideEquals
+ * @covers UnknownContent::equals
+ */
+ public function testEquals( Content $a, Content $b = null, $equal = false ) {
+ $this->assertEquals( $equal, $a->equals( $b ) );
+ }
+
+ public static function provideConvert() {
+ return [
+ [ // #0
+ 'Hallo Welt',
+ CONTENT_MODEL_WIKITEXT,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ [ // #1
+ 'Hallo Welt',
+ CONTENT_MODEL_WIKITEXT,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ [ // #1
+ 'Hallo Welt',
+ CONTENT_MODEL_CSS,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ [ // #1
+ 'Hallo Welt',
+ CONTENT_MODEL_JAVASCRIPT,
+ 'lossless',
+ 'Hallo Welt'
+ ],
+ ];
+ }
+
+ /**
+ * @covers UnknownContent::convert
+ */
+ public function testConvert() {
+ $content = $this->newContent( 'More horkyporky?' );
+
+ $this->assertFalse( $content->convert( CONTENT_MODEL_TEXT ) );
+ }
+
+ /**
+ * @covers UnknownContent::__construct
+ * @covers UnknownContentHandler::serializeContent
+ */
+ public function testSerialize() {
+ $this->mergeMwGlobalArrayValue(
+ 'wgContentHandlers',
+ [ 'horkyporky' => 'UnknownContentHandler' ]
+ );
+
+ $content = $this->newContent( 'Hörkypörky', 'horkyporky' );
+
+ $this->assertSame( 'Hörkypörky', $content->serialize() );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers UnsupportedSlotDiffRenderer
+ */
+class UnsupportedSlotDiffRendererTest extends MediaWikiTestCase {
+
+ public function provideDiff() {
+ $oldContent = new TextContent( 'Kittens' );
+ $newContent = new TextContent( 'Goats' );
+ $badContent = new UnknownContent( 'Dragons', 'xyzzy' );
+
+ yield [ '(unsupported-content-diff)', $oldContent, null ];
+ yield [ '(unsupported-content-diff)', null, $newContent ];
+ yield [ '(unsupported-content-diff)', $oldContent, $newContent ];
+ yield [ '(unsupported-content-diff2)', $badContent, $newContent ];
+ yield [ '(unsupported-content-diff2)', $oldContent, $badContent ];
+ yield [ '(unsupported-content-diff)', null, $badContent ];
+ yield [ '(unsupported-content-diff)', $badContent, null ];
+ }
+
+ /**
+ * @dataProvider provideDiff
+ */
+ public function testDiff( $expected, $oldContent, $newContent ) {
+ $this->mergeMwGlobalArrayValue(
+ 'wgContentHandlers',
+ [ 'xyzzy' => 'UnknownContentHandler' ]
+ );
+
+ $localizer = $this->getMock( MessageLocalizer::class );
+
+ $localizer->method( 'msg' )
+ ->willReturnCallback( function ( $key, ...$params ) {
+ return new RawMessage( "($key)", $params );
+ } );
+
+ $sdr = new UnsupportedSlotDiffRenderer( $localizer );
+ $this->assertContains( $expected, $sdr->getDiff( $oldContent, $newContent ) );
+ }
+
+}