Add supportsDirectEditing methods to ContentHandler
authoraude <aude.wiki@gmail.com>
Wed, 15 Apr 2015 08:26:22 +0000 (10:26 +0200)
committerAude <aude.wiki@gmail.com>
Fri, 17 Apr 2015 15:55:20 +0000 (15:55 +0000)
This adds supportsDirectApiEditing and
supportsDirectEditing methods to ContentHandler. Both
return false by default for the ContentHandler base
class, and true for TextContentHandler and it's
derivatives. (everything in core)

Extension content types that directly extend
AbstractContent / ContentHandler, often / generally don't
support direct editing. EntityContent in Wikibase
and Flow boards are the two such content types currently
in gerrit-hosted extensions.

The use and direct settings of the allowNonTextContent
member variable is replaced by enableApiEditOverride and
a setter for that. The only place allowNonTextContent is
used in all of Wikimedia-hosted git repos is core itself
(EditPage and ApiEditPage), so should be safe to make
this change.

With this change, Wikibase can remove its ApiCheckCanExecute
hook handler that disallows editing there, and MobileFrontend
could check if direct editing is allowed before enabling it's
editing features, instead of Wikibase having to add
MobileFrontend hook handlers to disable the features.

Bug: T96382
Change-Id: I276cd6ecedf38108f1f2be16b38e699e8c5d2d0c

15 files changed:
RELEASE-NOTES-1.26
includes/EditPage.php
includes/api/ApiEditPage.php
includes/content/ContentHandler.php
includes/content/TextContentHandler.php
tests/TestsAutoLoader.php
tests/phpunit/includes/EditPageTest.php
tests/phpunit/includes/api/ApiEditPageTest.php
tests/phpunit/includes/content/ContentHandlerTest.php
tests/phpunit/includes/content/TextContentHandlerTest.php [new file with mode: 0644]
tests/phpunit/includes/content/WikitextContentHandlerTest.php
tests/phpunit/mocks/content/DummyContentForTesting.php [new file with mode: 0644]
tests/phpunit/mocks/content/DummyContentHandlerForTesting.php [new file with mode: 0644]
tests/phpunit/mocks/content/DummyNonTextContent.php [new file with mode: 0644]
tests/phpunit/mocks/content/DummyNonTextContentHandler.php [new file with mode: 0644]

index bf9341d..eddfe30 100644 (file)
@@ -35,6 +35,13 @@ changes to languages because of Bugzilla reports.
 * ChangeTags::tagDescription() will return false if the interface message
   for the tag is disabled.
 * Added PageHistoryPager::doBatchLookups hook.
+* supportsDirectEditing and supportsDirectApiEditing methods added to
+ContentHandler, to provide a way for ApiEditPage and EditPage to check
+if direct editing of content is allowed. These methods return false,
+by default for the ContentHandler base class and true for TextContentHandler
+and it's derivative classes (everything in core). For Content types that
+do not support direct editing, an alternative mechanism should be provided
+for editing, such as action overrides or specific api modules.
 
 == Compatibility ==
 
index 8d27eac..1679c3b 100644 (file)
@@ -380,12 +380,14 @@ class EditPage {
 
        public $suppressIntro = false;
 
-       /** @var bool Set to true to allow editing of non-text content types. */
-       public $allowNonTextContent = false;
-
        /** @var bool */
        protected $edit;
 
+       /**
+        * @var bool Set in ApiEditPage, based on ContentHandler::allowsDirectApiEditing
+        */
+       private $enableApiEditOverride = false;
+
        /**
         * @param Article $article
         */
@@ -447,8 +449,18 @@ class EditPage {
         * @throws MWException If $modelId has no known handler
         */
        public function isSupportedContentModel( $modelId ) {
-               return $this->allowNonTextContent ||
-                       ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler;
+               return $this->enableApiEditOverride === true ||
+                       ContentHandler::getForModelID( $modelId )->supportsDirectEditing();
+       }
+
+       /**
+        * Allow editing of content that supports API direct editing, but not general
+        * direct editing. Set to false by default.
+        *
+        * @param bool $enableOverride
+        */
+       public function setApiEditOverride( $enableOverride ) {
+               $this->enableApiEditOverride = $enableOverride;
        }
 
        function submit() {
index 56c67e0..601c64c 100644 (file)
@@ -96,8 +96,12 @@ class ApiEditPage extends ApiBase {
                        $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
                }
 
-               // @todo Ask handler whether direct editing is supported at all! make
-               // allowFlatEdit() method or some such
+               if ( $contentHandler->supportsDirectApiEditing() === false ) {
+                       $this->dieUsage(
+                               'Direct editing via API is not supported for this content type.',
+                               'no-direct-editing'
+                       );
+               }
 
                if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
                        $params['contentformat'] = $contentHandler->getDefaultFormat();
@@ -362,9 +366,7 @@ class ApiEditPage extends ApiBase {
 
                $ep = new EditPage( $articleObject );
 
-               // allow editing of non-textual content.
-               $ep->allowNonTextContent = true;
-
+               $ep->setApiEditOverride( true );
                $ep->setContextTitle( $titleObj );
                $ep->importFormData( $req );
                $content = $ep->textbox1;
index 371b267..9c2435a 100644 (file)
@@ -1057,6 +1057,24 @@ abstract class ContentHandler {
                return false;
        }
 
+       /**
+        * Return true if this content model supports direct editing, such as via EditPage.
+        *
+        * @return bool Default is false, and true for TextContent and it's derivatives.
+        */
+       public function supportsDirectEditing() {
+               return false;
+       }
+
+       /**
+        * Whether or not this content model supports direct editing via ApiEditPage
+        *
+        * @return bool Default is false, and true for TextContent and derivatives.
+        */
+       public function supportsDirectApiEditing() {
+               return $this->supportsDirectEditing();
+       }
+
        /**
         * Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if
         * self::$enableDeprecationWarnings is set to true.
index ffe1acb..f5e8783 100644 (file)
@@ -134,4 +134,13 @@ class TextContentHandler extends ContentHandler {
                return new $class( '' );
        }
 
+       /**
+        * @see ContentHandler::supportsDirectEditing
+        *
+        * @return bool Default is true for TextContent and derivatives.
+        */
+       public function supportsDirectEditing() {
+               return true;
+       }
+
 }
index def23da..1a2e0cb 100644 (file)
@@ -66,8 +66,10 @@ $wgAutoloadClasses += array(
        'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
 
        # tests/phpunit/includes/content
-       'DummyContentHandlerForTesting' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
-       'DummyContentForTesting' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
+       'DummyContentHandlerForTesting' => "$testDir/phpunit/mocks/content/DummyContentHandlerForTesting.php",
+       'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
+       'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
+       'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
        'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
        'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
        'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
index 15778e4..27959b1 100644 (file)
  */
 class EditPageTest extends MediaWikiLangTestCase {
 
+       protected function setUp() {
+               global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+
+               parent::setUp();
+
+               $this->setMwGlobals( array(
+                       'wgExtraNamespaces' => $wgExtraNamespaces,
+                       'wgNamespaceContentModels' => $wgNamespaceContentModels,
+                       'wgContentHandlers' => $wgContentHandlers,
+                       'wgContLang' => $wgContLang,
+               ) );
+
+               $wgExtraNamespaces[12312] = 'Dummy';
+               $wgExtraNamespaces[12313] = 'Dummy_talk';
+
+               $wgNamespaceContentModels[12312] = "testing";
+               $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
+
+               MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+               $wgContLang->resetNamespaces(); # reset namespace cache
+       }
+
        /**
         * @dataProvider provideExtractSectionTitle
         * @covers EditPage::extractSectionTitle
@@ -499,4 +521,37 @@ hello
                $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
                        $expectedCode, $expectedText, $message );
        }
+
+       /**
+        * @depends testAutoMerge
+        */
+       public function testCheckDirectEditingDisallowed_forNonTextContent() {
+               $title = Title::newFromText( 'Dummy:NonTextPageForEditPage' );
+               $page = WikiPage::factory( $title );
+
+               $article = new Article( $title );
+               $article->getContext()->setTitle( $title );
+               $ep = new EditPage( $article );
+               $ep->setContextTitle( $title );
+
+               $user = $GLOBALS['wgUser'];
+
+               $edit = array(
+                       'wpTextbox1' => serialize( 'non-text content' ),
+                       'wpEditToken' => $user->getEditToken(),
+                       'wpEdittime' => '',
+                       'wpStarttime' => wfTimestampNow()
+               );
+
+               $req = new FauxRequest( $edit, true );
+               $ep->importFormData( $req );
+
+               $this->setExpectedException(
+                       'MWException',
+                       'This content model is not supported: testing'
+               );
+
+               $ep->internalAttemptSave( $result, false );
+       }
+
 }
index 3179a45..865f1c2 100644 (file)
@@ -27,9 +27,14 @@ class ApiEditPageTest extends ApiTestCase {
 
                $wgExtraNamespaces[12312] = 'Dummy';
                $wgExtraNamespaces[12313] = 'Dummy_talk';
+               $wgExtraNamespaces[12314] = 'DummyNonText';
+               $wgExtraNamespaces[12315] = 'DummyNonText_talk';
 
                $wgNamespaceContentModels[12312] = "testing";
+               $wgNamespaceContentModels[12314] = "testing-nontext";
+
                $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
+               $wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler';
 
                MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
                $wgContLang->resetNamespaces(); # reset namespace cache
@@ -96,33 +101,6 @@ class ApiEditPageTest extends ApiTestCase {
                );
        }
 
-       public function testNonTextEdit() {
-               $name = 'Dummy:ApiEditPageTest_testNonTextEdit';
-               $data = serialize( 'some bla bla text' );
-
-               // -- test new page --------------------------------------------
-               $apiResult = $this->doApiRequestWithToken( array(
-                       'action' => 'edit',
-                       'title' => $name,
-                       'text' => $data, ) );
-               $apiResult = $apiResult[0];
-
-               // Validate API result data
-               $this->assertArrayHasKey( 'edit', $apiResult );
-               $this->assertArrayHasKey( 'result', $apiResult['edit'] );
-               $this->assertEquals( 'Success', $apiResult['edit']['result'] );
-
-               $this->assertArrayHasKey( 'new', $apiResult['edit'] );
-               $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
-
-               $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
-
-               // validate resulting revision
-               $page = WikiPage::factory( Title::newFromText( $name ) );
-               $this->assertEquals( "testing", $page->getContentModel() );
-               $this->assertEquals( $data, $page->getContent()->serialize() );
-       }
-
        /**
         * @return array
         */
@@ -493,4 +471,45 @@ class ApiEditPageTest extends ApiTestCase {
 
                $page->clear();
        }
+
+       public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
+               $this->setExpectedException(
+                       'UsageException',
+                       'Direct editing via API is not supported for this content type.'
+               );
+
+               $this->doApiRequestWithToken( array(
+                       'action' => 'edit',
+                       'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit',
+                       'text' => '{"animals":["kittens!"]}'
+               ) );
+       }
+
+       public function testSupportsDirectApiEditing_withContentHandlerOverride() {
+               $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit';
+               $data = serialize( 'some bla bla text' );
+
+               $result = $this->doApiRequestWithToken( array(
+                       'action' => 'edit',
+                       'title' => $name,
+                       'text' => $data,
+               ) );
+
+               $apiResult = $result[0];
+
+               // Validate API result data
+               $this->assertArrayHasKey( 'edit', $apiResult );
+               $this->assertArrayHasKey( 'result', $apiResult['edit'] );
+               $this->assertEquals( 'Success', $apiResult['edit']['result'] );
+
+               $this->assertArrayHasKey( 'new', $apiResult['edit'] );
+               $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
+
+               $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
+
+               // validate resulting revision
+               $page = WikiPage::factory( Title::newFromText( $name ) );
+               $this->assertEquals( "testing-nontext", $page->getContentModel() );
+               $this->assertEquals( $data, $page->getContent()->serialize() );
+       }
 }
index 988a59e..63743eb 100644 (file)
@@ -338,6 +338,11 @@ class ContentHandlerTest extends MediaWikiTestCase {
        }
        */
 
+       public function testSupportsDirectEditing() {
+               $handler = new DummyContentHandlerForTesting( CONTENT_MODEL_JSON );
+               $this->assertFalse( $handler->supportsDirectEditing(), 'direct editing is not supported' );
+       }
+
        /**
         * @covers ContentHandler::runLegacyHooks
         */
@@ -365,164 +370,3 @@ class ContentHandlerTest extends MediaWikiTestCase {
                return true;
        }
 }
-
-class DummyContentHandlerForTesting extends ContentHandler {
-
-       public function __construct( $dataModel ) {
-               parent::__construct( $dataModel, array( "testing" ) );
-       }
-
-       /**
-        * @see ContentHandler::serializeContent
-        *
-        * @param Content $content
-        * @param string $format
-        *
-        * @return string
-        */
-       public function serializeContent( Content $content, $format = null ) {
-               return $content->serialize();
-       }
-
-       /**
-        * @see ContentHandler::unserializeContent
-        *
-        * @param string $blob
-        * @param string $format Unused.
-        *
-        * @return Content
-        */
-       public function unserializeContent( $blob, $format = null ) {
-               $d = unserialize( $blob );
-
-               return new DummyContentForTesting( $d );
-       }
-
-       /**
-        * Creates an empty Content object of the type supported by this ContentHandler.
-        *
-        */
-       public function makeEmptyContent() {
-               return new DummyContentForTesting( '' );
-       }
-}
-
-class DummyContentForTesting extends AbstractContent {
-
-       public function __construct( $data ) {
-               parent::__construct( "testing" );
-
-               $this->data = $data;
-       }
-
-       public function serialize( $format = null ) {
-               return serialize( $this->data );
-       }
-
-       /**
-        * @return string A string representing the content in a way useful for
-        *   building a full text search index. If no useful representation exists,
-        *   this method returns an empty string.
-        */
-       public function getTextForSearchIndex() {
-               return '';
-       }
-
-       /**
-        * @return string|bool The wikitext to include when another page includes this  content,
-        *  or false if the content is not includable in a wikitext page.
-        */
-       public function getWikitextForTransclusion() {
-               return false;
-       }
-
-       /**
-        * Returns a textual representation of the content suitable for use in edit
-        * summaries and log messages.
-        *
-        * @param int $maxlength Maximum length of the summary text.
-        * @return string The summary text.
-        */
-       public function getTextForSummary( $maxlength = 250 ) {
-               return '';
-       }
-
-       /**
-        * Returns native represenation of the data. Interpretation depends on the data model used,
-        * as given by getDataModel().
-        *
-        * @return mixed The native representation of the content. Could be a string, a nested array
-        *  structure, an object, a binary blob... anything, really.
-        */
-       public function getNativeData() {
-               return $this->data;
-       }
-
-       /**
-        * returns the content's nominal size in bogo-bytes.
-        *
-        * @return int
-        */
-       public function getSize() {
-               return strlen( $this->data );
-       }
-
-       /**
-        * Return a copy of this Content object. The following must be true for the object returned
-        * if $copy = $original->copy()
-        *
-        * * get_class($original) === get_class($copy)
-        * * $original->getModel() === $copy->getModel()
-        * * $original->equals( $copy )
-        *
-        * If and only if the Content object is imutable, the copy() method can and should
-        * return $this. That is,  $copy === $original may be true, but only for imutable content
-        * objects.
-        *
-        * @return Content A copy of this object
-        */
-       public function copy() {
-               return $this;
-       }
-
-       /**
-        * Returns true if this content is countable as a "real" wiki page, provided
-        * that it's also in a countable location (e.g. a current revision in the main namespace).
-        *
-        * @param bool $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;
-       }
-
-       /**
-        * @param Title $title
-        * @param int $revId Unused.
-        * @param null|ParserOptions $options
-        * @param bool $generateHtml Whether to generate Html (default: true). If false, the result
-        *  of calling getText() on the ParserOutput object returned by this method is undefined.
-        *
-        * @return ParserOutput
-        */
-       public function getParserOutput( Title $title, $revId = null,
-               ParserOptions $options = null, $generateHtml = true
-       ) {
-               return new ParserOutput( $this->getNativeData() );
-       }
-
-       /**
-        * @see AbstractContent::fillParserOutput()
-        *
-        * @param Title $title Context title for parsing
-        * @param int|null $revId Revision ID (for {{REVISIONID}})
-        * @param ParserOptions $options Parser options
-        * @param bool $generateHtml Whether or not to generate HTML
-        * @param ParserOutput &$output The output object to fill (reference).
-        */
-       protected function fillParserOutput( Title $title, $revId,
-                       ParserOptions $options, $generateHtml, ParserOutput &$output ) {
-               $output = new ParserOutput( $this->getNativeData() );
-       }
-}
diff --git a/tests/phpunit/includes/content/TextContentHandlerTest.php b/tests/phpunit/includes/content/TextContentHandlerTest.php
new file mode 100644 (file)
index 0000000..33861f1
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @group ContentHandler
+ */
+class TextContentHandlerTest extends MediaWikiLangTestCase {
+
+       public function testSupportsDirectEditing() {
+               $handler = new TextContentHandler();
+               $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' );
+       }
+
+}
index 38fb573..361238b 100644 (file)
@@ -115,6 +115,11 @@ class WikitextContentHandlerTest extends MediaWikiLangTestCase {
                $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) );
        }
 
+       public function testSupportsDirectEditing() {
+               $handler = new WikiTextContentHandler();
+               $this->assertTrue( $handler->supportsDirectEditing(), 'direct editing is supported' );
+       }
+
        public static function dataMerge3() {
                return array(
                        array(
diff --git a/tests/phpunit/mocks/content/DummyContentForTesting.php b/tests/phpunit/mocks/content/DummyContentForTesting.php
new file mode 100644 (file)
index 0000000..0c69027
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+
+class DummyContentForTesting extends AbstractContent {
+
+       public function __construct( $data ) {
+               parent::__construct( "testing" );
+
+               $this->data = $data;
+       }
+
+       public function serialize( $format = null ) {
+               return serialize( $this->data );
+       }
+
+       /**
+        * @return string A string representing the content in a way useful for
+        *   building a full text search index. If no useful representation exists,
+        *   this method returns an empty string.
+        */
+       public function getTextForSearchIndex() {
+               return '';
+       }
+
+       /**
+        * @return string|bool The wikitext to include when another page includes this  content,
+        *  or false if the content is not includable in a wikitext page.
+        */
+       public function getWikitextForTransclusion() {
+               return false;
+       }
+
+       /**
+        * Returns a textual representation of the content suitable for use in edit
+        * summaries and log messages.
+        *
+        * @param int $maxlength Maximum length of the summary text.
+        * @return string The summary text.
+        */
+       public function getTextForSummary( $maxlength = 250 ) {
+               return '';
+       }
+
+       /**
+        * Returns native represenation of the data. Interpretation depends on the data model used,
+        * as given by getDataModel().
+        *
+        * @return mixed The native representation of the content. Could be a string, a nested array
+        *  structure, an object, a binary blob... anything, really.
+        */
+       public function getNativeData() {
+               return $this->data;
+       }
+
+       /**
+        * returns the content's nominal size in bogo-bytes.
+        *
+        * @return int
+        */
+       public function getSize() {
+               return strlen( $this->data );
+       }
+
+       /**
+        * Return a copy of this Content object. The following must be true for the object returned
+        * if $copy = $original->copy()
+        *
+        * * get_class($original) === get_class($copy)
+        * * $original->getModel() === $copy->getModel()
+        * * $original->equals( $copy )
+        *
+        * If and only if the Content object is imutable, the copy() method can and should
+        * return $this. That is,  $copy === $original may be true, but only for imutable content
+        * objects.
+        *
+        * @return Content A copy of this object
+        */
+       public function copy() {
+               return $this;
+       }
+
+       /**
+        * Returns true if this content is countable as a "real" wiki page, provided
+        * that it's also in a countable location (e.g. a current revision in the main namespace).
+        *
+        * @param bool $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;
+       }
+
+       /**
+        * @param Title $title
+        * @param int $revId Unused.
+        * @param null|ParserOptions $options
+        * @param bool $generateHtml Whether to generate Html (default: true). If false, the result
+        *  of calling getText() on the ParserOutput object returned by this method is undefined.
+        *
+        * @return ParserOutput
+        */
+       public function getParserOutput( Title $title, $revId = null,
+               ParserOptions $options = null, $generateHtml = true
+       ) {
+               return new ParserOutput( $this->getNativeData() );
+       }
+
+       /**
+        * @see AbstractContent::fillParserOutput()
+        *
+        * @param Title $title Context title for parsing
+        * @param int|null $revId Revision ID (for {{REVISIONID}})
+        * @param ParserOptions $options Parser options
+        * @param bool $generateHtml Whether or not to generate HTML
+        * @param ParserOutput &$output The output object to fill (reference).
+        */
+       protected function fillParserOutput( Title $title, $revId,
+                       ParserOptions $options, $generateHtml, ParserOutput &$output ) {
+               $output = new ParserOutput( $this->getNativeData() );
+       }
+}
diff --git a/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php b/tests/phpunit/mocks/content/DummyContentHandlerForTesting.php
new file mode 100644 (file)
index 0000000..fd253f2
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+class DummyContentHandlerForTesting extends ContentHandler {
+
+       public function __construct( $dataModel ) {
+               parent::__construct( $dataModel, array( "testing" ) );
+       }
+
+       /**
+        * @see ContentHandler::serializeContent
+        *
+        * @param Content $content
+        * @param string $format
+        *
+        * @return string
+        */
+       public function serializeContent( Content $content, $format = null ) {
+               return $content->serialize();
+       }
+
+       /**
+        * @see ContentHandler::unserializeContent
+        *
+        * @param string $blob
+        * @param string $format Unused.
+        *
+        * @return Content
+        */
+       public function unserializeContent( $blob, $format = null ) {
+               $d = unserialize( $blob );
+
+               return new DummyContentForTesting( $d );
+       }
+
+       /**
+        * Creates an empty Content object of the type supported by this ContentHandler.
+        *
+        */
+       public function makeEmptyContent() {
+               return new DummyContentForTesting( '' );
+       }
+}
diff --git a/tests/phpunit/mocks/content/DummyNonTextContent.php b/tests/phpunit/mocks/content/DummyNonTextContent.php
new file mode 100644 (file)
index 0000000..889efb7
--- /dev/null
@@ -0,0 +1,121 @@
+<?php
+
+class DummyNonTextContent extends AbstractContent {
+
+       public function __construct( $data ) {
+               parent::__construct( "testing-nontext" );
+
+               $this->data = $data;
+       }
+
+       public function serialize( $format = null ) {
+               return serialize( $this->data );
+       }
+
+       /**
+        * @return string A string representing the content in a way useful for
+        *   building a full text search index. If no useful representation exists,
+        *   this method returns an empty string.
+        */
+       public function getTextForSearchIndex() {
+               return '';
+       }
+
+       /**
+        * @return string|bool The wikitext to include when another page includes this  content,
+        *  or false if the content is not includable in a wikitext page.
+        */
+       public function getWikitextForTransclusion() {
+               return false;
+       }
+
+       /**
+        * Returns a textual representation of the content suitable for use in edit
+        * summaries and log messages.
+        *
+        * @param int $maxlength Maximum length of the summary text.
+        * @return string The summary text.
+        */
+       public function getTextForSummary( $maxlength = 250 ) {
+               return '';
+       }
+
+       /**
+        * Returns native represenation of the data. Interpretation depends on the data model used,
+        * as given by getDataModel().
+        *
+        * @return mixed The native representation of the content. Could be a string, a nested array
+        *  structure, an object, a binary blob... anything, really.
+        */
+       public function getNativeData() {
+               return $this->data;
+       }
+
+       /**
+        * returns the content's nominal size in bogo-bytes.
+        *
+        * @return int
+        */
+       public function getSize() {
+               return strlen( $this->data );
+       }
+
+       /**
+        * Return a copy of this Content object. The following must be true for the object returned
+        * if $copy = $original->copy()
+        *
+        * * get_class($original) === get_class($copy)
+        * * $original->getModel() === $copy->getModel()
+        * * $original->equals( $copy )
+        *
+        * If and only if the Content object is imutable, the copy() method can and should
+        * return $this. That is,  $copy === $original may be true, but only for imutable content
+        * objects.
+        *
+        * @return Content A copy of this object
+        */
+       public function copy() {
+               return $this;
+       }
+
+       /**
+        * Returns true if this content is countable as a "real" wiki page, provided
+        * that it's also in a countable location (e.g. a current revision in the main namespace).
+        *
+        * @param bool $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;
+       }
+
+       /**
+        * @param Title $title
+        * @param int $revId Unused.
+        * @param null|ParserOptions $options
+        * @param bool $generateHtml Whether to generate Html (default: true). If false, the result
+        *  of calling getText() on the ParserOutput object returned by this method is undefined.
+        *
+        * @return ParserOutput
+        */
+       public function getParserOutput( Title $title, $revId = null,
+               ParserOptions $options = null, $generateHtml = true
+       ) {
+               return new ParserOutput( $this->getNativeData() );
+       }
+
+       /**
+        * @see AbstractContent::fillParserOutput()
+        *
+        * @param Title $title Context title for parsing
+        * @param int|null $revId Revision ID (for {{REVISIONID}})
+        * @param ParserOptions $options Parser options
+        * @param bool $generateHtml Whether or not to generate HTML
+        * @param ParserOutput &$output The output object to fill (reference).
+        */
+       protected function fillParserOutput( Title $title, $revId,
+                       ParserOptions $options, $generateHtml, ParserOutput &$output ) {
+               $output = new ParserOutput( $this->getNativeData() );
+       }
+}
diff --git a/tests/phpunit/mocks/content/DummyNonTextContentHandler.php b/tests/phpunit/mocks/content/DummyNonTextContentHandler.php
new file mode 100644 (file)
index 0000000..6995ae7
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+class DummyNonTextContentHandler extends DummyContentHandlerForTesting {
+
+       public function __construct( $dataModel ) {
+               parent::__construct( $dataModel, array( "testing-nontext" ) );
+       }
+
+       /**
+        * @see ContentHandler::serializeContent
+        *
+        * @param Content $content
+        * @param string $format
+        *
+        * @return string
+        */
+       public function serializeContent( Content $content, $format = null ) {
+               return $content->serialize();
+       }
+
+       /**
+        * @see ContentHandler::unserializeContent
+        *
+        * @param string $blob
+        * @param string $format Unused.
+        *
+        * @return Content
+        */
+       public function unserializeContent( $blob, $format = null ) {
+               $d = unserialize( $blob );
+
+               return new DummyNonTextContent( $d );
+       }
+
+       /**
+        * Creates an empty Content object of the type supported by this ContentHandler.
+        */
+       public function makeEmptyContent() {
+               return new DummyNonTextContent( '' );
+       }
+
+       public function supportsDirectApiEditing() {
+               return true;
+       }
+
+}