From: daniel Date: Tue, 9 Oct 2012 09:34:24 +0000 (+0200) Subject: Merge branch 'Wikidata' into master. X-Git-Tag: 1.31.0-rc.0~22097^2 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22messagerie%22%29%20.%20%22?a=commitdiff_plain;h=329d5b3516daa2a338101a6625f2f8513cf443fa;p=lhc%2Fweb%2Fwiklou.git Merge branch 'Wikidata' into master. This introduces the ContentHandler facility into MediaWiki, see docs/contenthandler.txt. For convenient review, a squashed version is available at https://gerrit.wikimedia.org/r/27191 The ContentHandler facility is a major building block of the Wikidata project. It has been discussed repeatedly on wikitech-l. Change-Id: I3804e2d5f6f59e6a39db80744bdf61bfe8c14f98 --- 329d5b3516daa2a338101a6625f2f8513cf443fa diff --cc RELEASE-NOTES-1.21 index 529ae4dcfc,1964f813cc..a1fa4caf42 --- a/RELEASE-NOTES-1.21 +++ b/RELEASE-NOTES-1.21 @@@ -12,11 -12,11 +12,13 @@@ production === Configuration changes in 1.21 === * (bug 29374) $wgVectorUseSimpleSearch is now enabled by default. -* Deprecated $wgAllowRealName is removed. Use $wgHiddenPrefs[] = 'realname' instead +* Deprecated $wgAllowRealName is removed. Use $wgHiddenPrefs[] = 'realname' + instead. === New features in 1.21 === +* (bug 34876) jquery.makeCollapsible has been improved in performance. + * Added ContentHandler facility to allow extensions to support other content than wikitext. + See docs/contenthandler.txt for details. === Bug fixes in 1.21 === * (bug 40353) SpecialDoubleRedirect should support interwiki redirects. diff --cc docs/contenthandler.txt index 0000000000,be3f4a74ba..35614328a2 mode 000000,100644..100644 --- a/docs/contenthandler.txt +++ b/docs/contenthandler.txt @@@ -1,0 -1,184 +1,184 @@@ + The ContentHandler facility adds support for arbitrary content types on wiki pages, instead of relying on wikitext -for everything. It was introduced in MediaWiki 1.20. ++for everything. It was introduced in MediaWiki 1.21. + + Each kind of content ("content model") supported by MediaWiki is identified by unique name. The content model determines + how a page's content is rendered, compared, stored, edited, and so on. + + Built-in content types are: + + * wikitext - wikitext, as usual + * javascript - user provided javascript code + * css - user provided css code + * text - plain text + + In PHP, use the corresponding CONTENT_MODEL_XXX constant. + + A page's content model is available using the Title::getContentModel() method. A page's default model is determined by + ContentHandler::getDefaultModelFor($title) as follows: + + * The global setting $wgNamespaceContentModels specifies a content model for the given namespace. + * The hook ContentHandlerDefaultModelFor may be used to override the page's default model. + * Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript model if they end in .js or .css, respectively. + Pages in NS_MEDIAWIKI default to the wikitext model otherwise. + * The hook TitleIsCssOrJsPage may be used to force a page to use the CSS or JavaScript model. + This is a compatibility feature. The ContentHandlerDefaultModelFor hook should be used instead if possible. + * The hook TitleIsWikitextPage may be used to force a page to use the wikitext model. + This is a compatibility feature. The ContentHandlerDefaultModelFor hook should be used instead if possible. + * Otherwise, the wikitext model is used. + + Note that is currently no mechanism to convert a page from one content model to another, and there is no guarantee that + revisions of a page will all have the same content model. Use Revision::getContentModel() to find it. + + + == Architecture == + + Two class hierarchies are used to provide the functionality associated with the different content models: + + * Content interface (and AbstractContent base class) define functionality that acts on the concrete content of a page, and + * ContentHandler base class provides functionality specific to a content model, but not acting on concrete content. + + The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These + Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation + and analysis of page content must be done via the appropriate methods of the Content object. + + For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler + object for a given content model can be obtained using ContentHandler::getForModelID( $id ). Also Title, WikiPage and + Revision now have getContentHandler() methods for convenience. + + ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting + on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to + create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp. + Revision::getContent() to get a page's content as a Content object. These two methods should be the ONLY way in which + page content is accessed. + + Another important function of ContentHandler objects is to define custom action handlers for a content model, see + ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing. + + + == Serialization == + + With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface + are used to represent and handle the content internally. For storage and data exchange, each content model supports + at least one serialization format via ContentHandler::serializeContent( $content ). The list of supported formats for + a given content model can be accessed using ContentHandler::getSupportedFormats(). + + Content serialization formats are identified using MIME type like strings. The following formats are built in: + + * text/x-wiki - wikitext + * text/javascript - for js pages + * text/css - for css pages -* text/plain - for future use, e.g. with some plain-html messages. -* text/html - for future use, e.g. with some plain-html messages. ++* text/plain - for future use, e.g. with plain text messages. ++* text/html - for future use, e.g. with plain html messages. + * application/vnd.php.serialized - for future use with the api and for extensions + * application/json - for future use with the api, and for use by extensions + * application/xml - for future use with the api, and for use by extensions + + In PHP, use the corresponding CONTENT_FORMAT_XXX constant. + + Note that when using the API to access page content, especially action=edit, action=parse and action=query&prop=revisions, + the model and format of the content should always be handled explicitly. Without that information, interpretation of + the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or + Special:Export. + + Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and + contentformat is also json (or rather, application/json), the page content is represented as a string containing an + escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API + modules that allow access to that content in a more natural form. + + + == Compatibility == + + The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least + for pages that contain wikitext or other text based content. However, a number of functions and hooks have been + deprecated in favor of new versions that are aware of the page's content model, and will now generate warnings when + used. + + Most importantly, the following functions have been deprecated: + + * Revisions::getText() and Revisions::getRawText() is deprecated in favor Revisions::getContent() + * WikiPage::getText() is deprecated in favor WikiPage::getContent() + + Also, the old Article::getContent() (which returns text) is superceded by Article::getContentObject(). However, both + methods should be avoided since they do not provide clean access to the page's actual content. For instance, they may + return a system message for non-existing pages. Use WikiPage::getContent() instead. + + Code that relies on a textual representation of the page content should eventually be rewritten. However, + ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled + by $wgContentHandlerTextFallback; per default it will return the text for text based content, and null for any other + content. + + For rendering page content, Content::getParserOutput() should be used instead of accessing the parser directly. + ContentHandler::makeParserOptions() can be used to construct appropriate options. + + + Besides some functions, some hooks have also been replaced by new versions (see hooks.txt for details). + These hooks will now trigger a warning when used: + + * ArticleAfterFetchContent was replaced by ArticleAfterFetchContentObject + * ArticleInsertComplete was replaced by ArticleContentInsertComplete + * ArticleSave was replaced by ArticleContentSave + * ArticleSaveComplete was replaced by ArticleContentSaveComplete + * ArticleViewCustom was replaced by ArticleContentViewCustom (also consider a custom implementation of the view action) + * EditFilterMerged was replaced by EditFilterMergedContent + * EditPageGetDiffText was replaced by EditPageGetDiffContent + * EditPageGetPreviewText was replaced by EditPageGetPreviewContent + * ShowRawCssJs was deprecated in favor of custom rendering implemented in the respective ContentHandler object. + + + == Database Storage == + + Page content is stored in the database using the same mechanism as before. Non-text content is serialized first. The + appropriate serialization and deserialization is handled by the Revision class. + + Each revision's content model and serialization format is stored in the revision table (resp. in the archive table, if + the revision was deleted). The page's (current) content model (that is, the conent model of the latest revision) is also + stored in the page table. + + Note however that the content model and format is only stored if it differs from the page's default, as determined by + ContentHandler::getDefaultModelFor( $title ). The default values are represented as NULL in the database, to preserve + space. + + Storage of content model and format can be disabled altogether by setting $wgContentHandlerUseDB = false. In that case, + the page's default model (and the model's default format) will be used everywhere. Attempts to store a revision of a page + using a model or format different from the default will result in an error. + + + == Globals == + + There are some new globals that can be used to control the behavior of the ContentHandler facility: + + * $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses. + + * $wgNamespaceContentModels maps namespace IDs to a content model that should be the default for that namespace. + + * $wgContentHandlerUseDB determines whether each revision's content model should be stored in the database. + Defaults is true. + + * $wgContentHandlerTextFallback determines how the compatibility method ContentHandler::getContentText() will behave for + non-text content: + 'ignore' causes null to be returned for non-text content (default). + 'serialize' causes the serialized form of any non-text content to be returned (scary). + 'fail' causes an exception to be thrown for non-text content (strict). + + + == Caveats == + + There are some changes in behavior that might be surprising to users: + -* Javascript and CSS pages are no longer parsed as wikitext (though pre-safe transform is still applied). Most ++* Javascript and CSS pages are no longer parsed as wikitext (though pre-save transform is still applied). Most + importantly, this means that links, including categorization links, contained in the code will not work. + + * With $wgContentHandlerUseDB = false, pages can not be moved in a way that would change the + default model. E.g. [[MediaWiki:foo.js]] can not be moved to [[MediaWiki:foo bar]], but can still be moved to + [[User:John/foo.js]]. Also, in this mode, changing the default content model for a page (e.g. by changing + $wgNamespaceContentModels) may cause it to become inaccessible. + + * action=edit will fail for pages with non-text content, unless the respective ContentHandler implementation has + provided a specialized handler for the edit action. This is true for the API as well. + + * action=raw will fail for all non-text content. This seems better than serving content in other formats to an + unsuspecting recipient. This will also cause client-side diffs to fail. + + * File pages provide their own action overrides that do not combine gracefully with any custom handlers defined by a -ContentHandler. If for example a File page used a content model with a custom move action, this would be overridden by -WikiFilePage's move handler. ++ContentHandler. If for example a File page used a content model with a custom revert action, this would be overridden by ++WikiFilePage's handler for the revert action. diff --cc maintenance/language/messages.inc index 4d2542f9dc,86db0a571f..eaf170bad3 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@@ -689,6 -689,15 +689,15 @@@ $wgMessageStructure = array 'addsection-preload', 'addsection-editintro', 'defaultmessagetext', + 'content-failed-to-parse', + 'invalid-content-data', + 'content-not-allowed-here', + ), - 'contentmodel' => array( ++ 'contentmodels' => array( + 'content-model-wikitext', + 'content-model-text', + 'content-model-javascript', + 'content-model-css', ), 'parserwarnings' => array( 'expensive-parserfunction-warning', @@@ -3829,6 -3838,6 +3838,7 @@@ XHTML id names." 'toolbar' => 'Edit page toolbar', 'edit' => 'Edit pages', 'parserwarnings' => 'Parser/template warnings', ++ 'contentmodels' => 'Content models', 'undo' => '"Undo" feature', 'cantcreateaccount' => 'Account creation failure', 'history' => 'History pages', diff --cc tests/phpunit/MediaWikiTestCase.php index 6b580a5c4e,8d78e50867..ff13702871 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@@ -126,27 -129,17 +126,38 @@@ abstract class MediaWikiTestCase extend return $fname; } - protected function setup() { - parent::setup(); - - foreach ( $this->restoreGlobals as $var ) { - $v = $GLOBALS[ $var ]; + /** + * setUp and tearDown should (where significant) + * happen in reverse order. + */ + protected function setUp() { + parent::setUp(); + ++ /* ++ //@todo: global variables to restore for *every* test ++ array( ++ 'wgLang', ++ 'wgContLang', ++ 'wgLanguageCode', ++ 'wgUser', ++ 'wgTitle', ++ ); ++ */ + - if ( is_object( $v ) ) { - $v = clone $v; + // Cleaning up temporary files + foreach ( $this->tmpfiles as $fname ) { + if ( is_file( $fname ) || ( is_link( $fname ) ) ) { + unlink( $fname ); + } elseif ( is_dir( $fname ) ) { + wfRecursiveRemoveDir( $fname ); } + } - $this->savedGlobals[$var] = $v; + // Clean up open transactions + if ( $this->needsDB() && $this->db ) { + while( $this->db->trxLevel() > 0 ) { + $this->db->rollback(); + } } } @@@ -167,60 -160,14 +178,88 @@@ } } - // restore saved globals - foreach ( $this->savedGlobals as $k => $v ) { - $GLOBALS[ $k ] = $v; + // Restore mw globals + foreach ( $this->mwGlobals as $key => $value ) { + $GLOBALS[$key] = $value; + } + $this->mwGlobals = array(); + + parent::tearDown(); + } + + /** + * Individual test functions may override globals (either directly or through this + * setMwGlobals() function), however one must call this method at least once for + * each key within the setUp(). + * That way the key is added to the array of globals that will be reset afterwards + * in the tearDown(). And, equally important, that way all other tests are executed + * with the same settings (instead of using the unreliable local settings for most + * tests and fix it only for some tests). + * + * @example + * + * protected function setUp() { + * $this->setMwGlobals( 'wgRestrictStuff', true ); + * } + * + * function testFoo() {} + * + * function testBar() {} + * $this->assertTrue( self::getX()->doStuff() ); + * + * $this->setMwGlobals( 'wgRestrictStuff', false ); + * $this->assertTrue( self::getX()->doStuff() ); + * } + * + * function testQuux() {} + * + * + * @param array|string $pairs Key to the global variable, or an array + * of key/value pairs. + * @param mixed $value Value to set the global to (ignored + * if an array is given as first argument). + */ + protected function setMwGlobals( $pairs, $value = null ) { + if ( !is_array( $pairs ) ) { + $key = $pairs; + $this->mwGlobals[$key] = $GLOBALS[$key]; + $GLOBALS[$key] = $value; + } else { + foreach ( $pairs as $key => $value ) { + $this->mwGlobals[$key] = $GLOBALS[$key]; + $GLOBALS[$key] = $value; + } + } + } + ++ /** ++ * Merges the given values into a MW global array variable. ++ * Useful for setting some entries in a configuration array, instead of ++ * setting the entire array. ++ * ++ * @param String $name The name of the global, as in wgFooBar ++ * @param Array $values The array containing the entries to set in that global ++ * ++ * @throws MWException if the designated global is not an array. ++ */ ++ protected function mergeMwGlobalArrayValue( $name, $values ) { ++ if ( !isset( $GLOBALS[$name] ) ) { ++ $merged = $values; ++ } else { ++ if ( !is_array( $GLOBALS[$name] ) ) { ++ throw new MWException( "MW global $name is not an array." ); ++ } ++ ++ //NOTE: do not use array_merge, it screws up for numeric keys. ++ $merged = $GLOBALS[$name]; ++ foreach ( $values as $k => $v ) { ++ $merged[$k] = $v; ++ } + } + - parent::teardown(); ++ $this->setMwGlobals( $name, $merged ); + } + function dbPrefix() { return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX; } diff --cc tests/phpunit/includes/RevisionStorageTest.php index d2ecc55ee8,81404c7217..e06de7c52b --- a/tests/phpunit/includes/RevisionStorageTest.php +++ b/tests/phpunit/includes/RevisionStorageTest.php @@@ -34,9 -38,20 +38,19 @@@ class RevisionStorageTest extends Media 'iwlinks' ) ); } - protected function setUp() { + public function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + $wgExtraNamespaces[ 12312 ] = 'Dummy'; + $wgExtraNamespaces[ 12313 ] = 'Dummy_talk'; + + $wgNamespaceContentModels[ 12312 ] = 'DUMMY'; + $wgContentHandlers[ 'DUMMY' ] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache - if ( !$this->the_page ) { - $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page" ); + $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page", CONTENT_MODEL_WIKITEXT ); } } @@@ -305,12 -402,14 +401,14 @@@ $dbw = wfGetDB( DB_MASTER ); $rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false ); - $this->assertNotEquals( $orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision' ); - $this->assertEquals( $orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision' ); - $this->assertEquals( 'some testing text', $rev->getText() ); + $this->assertNotEquals( $orig->getId(), $rev->getId(), + 'new null revision shold have a different id from the original revision' ); + $this->assertEquals( $orig->getTextId(), $rev->getTextId(), + 'new null revision shold have the same text id as the original revision' ); + $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() ); } - public function dataUserWasLastToEdit() { + public static function provideUserWasLastToEdit() { return array( array( #0 3, true, # actually the last edit diff --cc tests/phpunit/includes/RevisionTest.php index e8f298b0b8,b819728ad6..00e7119660 --- a/tests/phpunit/includes/RevisionTest.php +++ b/tests/phpunit/includes/RevisionTest.php @@@ -1,14 -1,54 +1,56 @@@ setMwGlobals( array( + 'wgContLang' => Language::factory( 'en' ), 'wgLegacyEncoding' => false, 'wgCompressRevisions' => false, + - 'wgContentHandlerTextFallback' => $GLOBALS['wgContentHandlerTextFallback'], - 'wgExtraNamespaces' => $GLOBALS['wgExtraNamespaces'], - 'wgNamespaceContentModels' => $GLOBALS['wgNamespaceContentModels'], - 'wgContentHandlers' => $GLOBALS['wgContentHandlers'], - ); ++ 'wgContentHandlerTextFallback' => 'ignore', + ) ); + - foreach ( $globalSet as $var => $data ) { - $this->saveGlobals[$var] = $GLOBALS[$var]; - $GLOBALS[$var] = $data; - } ++ $this->mergeMwGlobalArrayValue( ++ 'wgExtraNamespaces', ++ array( ++ 12312 => 'Dummy', ++ 12313 => 'Dummy_talk', ++ ) ++ ); + - global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; - $wgExtraNamespaces[ 12312 ] = 'Dummy'; - $wgExtraNamespaces[ 12313 ] = 'Dummy_talk'; ++ $this->mergeMwGlobalArrayValue( ++ 'wgNamespaceContentModels', ++ array( ++ 12312 => 'testing', ++ ) ++ ); + - $wgNamespaceContentModels[ 12312 ] = "testing"; - $wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting'; - $wgContentHandlers[ "RevisionTestModifyableContent" ] = 'RevisionTestModifyableContentHandler'; ++ $this->mergeMwGlobalArrayValue( ++ 'wgContentHandlers', ++ array( ++ 'testing' => 'DummyContentHandlerForTesting', ++ 'RevisionTestModifyableContent' => 'RevisionTestModifyableContentHandler', ++ ) ++ ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache - - global $wgContentHandlerTextFallback; - $wgContentHandlerTextFallback = 'ignore'; + } + + function tearDown() { + global $wgContLang; + - foreach ( $this->saveGlobals as $var => $data ) { - $GLOBALS[$var] = $data; - } - + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache ++ ++ parent::tearDown(); } function testGetRevisionText() { diff --cc tests/phpunit/includes/TitleMethodsTest.php index 5a540b83e2,7291ae4ade..44fd69008d --- a/tests/phpunit/includes/TitleMethodsTest.php +++ b/tests/phpunit/includes/TitleMethodsTest.php @@@ -1,8 -1,39 +1,44 @@@ mergeMwGlobalArrayValue( ++ 'wgExtraNamespaces', ++ array( ++ 12302 => 'TEST-JS', ++ 12303 => 'TEST-JS_TALK', ++ ) ++ ); + - $wgNamespaceContentModels[ 12302 ] = CONTENT_MODEL_JAVASCRIPT; ++ $this->mergeMwGlobalArrayValue( ++ 'wgNamespaceContentModels', ++ array( ++ 12302 => CONTENT_MODEL_JAVASCRIPT, ++ ) ++ ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + + public function teardown() { - global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContLang; - - unset( $wgExtraNamespaces[ 12302 ] ); - unset( $wgExtraNamespaces[ 12303 ] ); - - unset( $wgNamespaceContentModels[ 12302 ] ); ++ global $wgContLang; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + - public function dataEquals() { + public static function provideEquals() { return array( array( 'Main Page', 'Main Page', true ), array( 'Main Page', 'Not The Main Page', false ), @@@ -75,11 -106,52 +111,52 @@@ $this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) ); } + public function dataGetContentModel() { + return array( + array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'TEST-JS:Foo', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS_TALK:Foo.js', CONTENT_MODEL_WIKITEXT ), + ); + } + + /** + * @dataProvider dataGetContentModel + */ + public function testGetContentModel( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedModelId, $title->getContentModel() ); + } + + /** + * @dataProvider dataGetContentModel + */ + public function testHasContentModel( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertTrue( $title->hasContentModel( $expectedModelId ) ); + } + - public function dataIsCssOrJsPage() { + public static function provideIsCssOrJsPage() { return array( - array( 'Foo', false ), - array( 'Foo.js', false ), - array( 'Foo/bar.js', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.js', false ), + array( 'Help:Foo/bar.js', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo/bar.js', false ), @@@ -104,11 -178,11 +183,11 @@@ } - public function dataIsCssJsSubpage() { + public static function provideIsCssJsSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.js', false ), - array( 'Foo/bar.js', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.js', false ), + array( 'Help:Foo/bar.js', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo/bar.js', true ), @@@ -130,10 -206,10 +211,10 @@@ $this->assertEquals( $expectedBool, $title->isCssJsSubpage() ); } - public function dataIsCssSubpage() { + public static function provideIsCssSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.css', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.css', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo.css', false ), @@@ -150,10 -226,10 +231,10 @@@ $this->assertEquals( $expectedBool, $title->isCssSubpage() ); } - public function dataIsJsSubpage() { + public static function provideIsJsSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.css', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.css', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo.css', false ), @@@ -170,11 -246,11 +251,11 @@@ $this->assertEquals( $expectedBool, $title->isJsSubpage() ); } - public function dataIsWikitextPage() { + public static function provideIsWikitextPage() { return array( - array( 'Foo', true ), - array( 'Foo.js', true ), - array( 'Foo/bar.js', true ), + array( 'Help:Foo', true ), + array( 'Help:Foo.js', true ), + array( 'Help:Foo/bar.js', true ), array( 'User:Foo', true ), array( 'User:Foo.js', true ), array( 'User:Foo/bar.js', false ), diff --cc tests/phpunit/includes/TitleTest.php index 8c22269fd8,ff535c3d35..0a3f6f6f86 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@@ -1,20 -1,12 +1,25 @@@ setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + // User language + 'wgLang' => Language::factory( 'en' ), + 'wgAllowUserJs' => false, + 'wgDefaultLanguageVariant' => false, + ) ); + } + function testLegalChars() { $titlechars = Title::legalChars(); diff --cc tests/phpunit/includes/WikiPageTest.php index e77a1334a1,1e34e51846..cf9b96bcd0 --- a/tests/phpunit/includes/WikiPageTest.php +++ b/tests/phpunit/includes/WikiPageTest.php @@@ -11,30 -12,33 +12,33 @@@ class WikiPageTest extends MediaWikiLan function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); - $this->tablesUsed = array_merge ( $this->tablesUsed, - array( 'page', - 'revision', - 'text', + $this->tablesUsed = array_merge ( + $this->tablesUsed, + array( 'page', + 'revision', + 'text', - 'recentchanges', - 'logging', + 'recentchanges', + 'logging', - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); } - public function setUp() { + protected function setUp() { parent::setUp(); $this->pages_to_delete = array(); + + LinkCache::singleton()->clear(); # avoid cached redirect status, etc } - public function tearDown() { + protected function tearDown() { foreach ( $this->pages_to_delete as $p ) { /* @var $p WikiPage */ @@@ -246,18 -401,22 +401,22 @@@ } } - public function dataGetRedirectTarget() { + public static function provideGetRedirectTarget() { return array( - array( 'WikiPageTest_testGetRedirectTarget_1', "hello world", null ), - array( 'WikiPageTest_testGetRedirectTarget_2', "#REDIRECT [[hello world]]", "Hello world" ), + array( 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ), + array( 'WikiPageTest_testGetRedirectTarget_2', CONTENT_MODEL_WIKITEXT, "#REDIRECT [[hello world]]", "Hello world" ), ); } /** - * @dataProvider dataGetRedirectTarget + * @dataProvider provideGetRedirectTarget */ - public function testGetRedirectTarget( $title, $text, $target ) { - $page = $this->createPage( $title, $text ); + public function testGetRedirectTarget( $title, $model, $text, $target ) { + $page = $this->createPage( $title, $text, $model ); + + # sanity check, because this test seems to fail for no reason for some people. + $c = $page->getContent(); + $this->assertEquals( 'WikitextContent', get_class( $c ) ); # now, test the actual redirect $t = $page->getRedirectTarget(); @@@ -265,10 -424,10 +424,10 @@@ } /** - * @dataProvider dataGetRedirectTarget + * @dataProvider provideGetRedirectTarget */ - public function testIsRedirect( $title, $text, $target ) { - $page = $this->createPage( $title, $text ); + public function testIsRedirect( $title, $model, $text, $target ) { + $page = $this->createPage( $title, $text, $model ); $this->assertEquals( !is_null( $target ), $page->isRedirect() ); } @@@ -366,40 -540,44 +540,44 @@@ /** - * @dataProvider dataIsCountable + * @dataProvider provideIsCountable */ - public function testIsCountable( $title, $text, $mode, $expected ) { + public function testIsCountable( $title, $model, $text, $mode, $expected ) { global $wgArticleCountMethod; - $old = $wgArticleCountMethod; + $oldArticleCountMethod = $wgArticleCountMethod; $wgArticleCountMethod = $mode; - $page = $this->createPage( $title, $text ); - $editInfo = $page->prepareTextForEdit( $page->getText() ); + $page = $this->createPage( $title, $text, $model ); + $hasLinks = wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1, + array( 'pl_from' => $page->getId() ), __METHOD__ ); + + $editInfo = $page->prepareContentForEdit( $page->getContent() ); $v = $page->isCountable(); $w = $page->isCountable( $editInfo ); - $wgArticleCountMethod = $old; + + $wgArticleCountMethod = $oldArticleCountMethod; $this->assertEquals( $expected, $v, "isCountable( null ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); $this->assertEquals( $expected, $w, "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); } - public function dataGetParserOutput() { + public static function provideGetParserOutput() { return array( - array("hello ''world''\n", "

hello world

"), + array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "

hello world

"), // @todo: more...? ); } /** - * @dataProvider dataGetParserOutput + * @dataProvider provideGetParserOutput */ - public function testGetParserOutput( $text, $expectedHtml ) { - $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text ); + public function testGetParserOutput( $model, $text, $expectedHtml ) { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model ); $opt = new ParserOptions(); $po = $page->getParserOutput( $opt ); @@@ -609,11 -815,12 +815,12 @@@ more stuf } $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" ); - $this->assertEquals( "one", $page->getText() ); + $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one", $page->getContent()->getNativeData() ); } - public function dataGetAutosummary( ) { + public static function provideGetAutosummary( ) { return array( array( 'Hello there, world!', @@@ -655,17 -862,20 +862,20 @@@ } /** - * @dataProvider dataGetAutoSummary + * @dataProvider provideGetAutoSummary */ public function testGetAutosummary( $old, $new, $flags, $expected ) { + $this->hideDeprecated( "WikiPage::getAutosummary" ); + $page = $this->newPage( "WikiPageTest_testGetAutosummary" ); $summary = $page->getAutosummary( $old, $new, $flags ); - $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" ); + $this->assertTrue( (bool)preg_match( $expected, $summary ), + "Autosummary didn't match expected pattern $expected: $summary" ); } - public function dataGetAutoDeleteReason( ) { + public static function provideGetAutoDeleteReason( ) { return array( array( array(), @@@ -754,13 -969,13 +969,13 @@@ $page->doDeleteArticle( "done" ); } - public function dataPreSaveTransform() { + public static function providePreSaveTransform() { return array( array( 'hello this is ~~~', - "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", ), array( 'hello \'\'this\'\' is ~~~', - 'hello \'\'this\'\' is ~~~', + 'hello \'\'this\'\' is ~~~', ), ); }