=== 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.
--- /dev/null
-for everything. It was introduced in MediaWiki 1.20.
+ The ContentHandler facility adds support for arbitrary content types on wiki pages, instead of relying on wikitext
-* text/plain - for future use, e.g. with some plain-html messages.
-* text/html - for future use, e.g. with some plain-html messages.
++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
-* Javascript and CSS pages are no longer parsed as wikitext (though pre-safe transform is still applied). Most
++* 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:
+
-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.
++* 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 revert action, this would be overridden by
++WikiFilePage's handler for the revert action.
'addsection-preload',
'addsection-editintro',
'defaultmessagetext',
- 'contentmodel' => array(
+ 'content-failed-to-parse',
+ 'invalid-content-data',
+ 'content-not-allowed-here',
+ ),
++ 'contentmodels' => array(
+ 'content-model-wikitext',
+ 'content-model-text',
+ 'content-model-javascript',
+ 'content-model-css',
),
'parserwarnings' => array(
'expensive-parserfunction-warning',
'toolbar' => 'Edit page toolbar',
'edit' => 'Edit pages',
'parserwarnings' => 'Parser/template warnings',
++ 'contentmodels' => 'Content models',
'undo' => '"Undo" feature',
'cantcreateaccount' => 'Account creation failure',
'history' => 'History pages',
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();
+ }
}
}
}
}
- // 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
+ * <code>
+ * 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() {}
+ * </code>
+ *
+ * @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;
}
'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 );
}
}
$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
<?php
+ /**
+ * @group ContentHandler
+ */
class RevisionTest extends MediaWikiTestCase {
- var $saveGlobals = array();
-
- function setUp() {
+ protected function setUp() {
+ global $wgContLang;
- $wgContLang = Language::factory( 'en' );
+
- $globalSet = array(
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgContLang' => Language::factory( 'en' ),
'wgLegacyEncoding' => false,
'wgCompressRevisions' => false,
- 'wgContentHandlerTextFallback' => $GLOBALS['wgContentHandlerTextFallback'],
- 'wgExtraNamespaces' => $GLOBALS['wgExtraNamespaces'],
- 'wgNamespaceContentModels' => $GLOBALS['wgNamespaceContentModels'],
- 'wgContentHandlers' => $GLOBALS['wgContentHandlers'],
- );
+
- foreach ( $globalSet as $var => $data ) {
- $this->saveGlobals[$var] = $GLOBALS[$var];
- $GLOBALS[$var] = $data;
- }
++ 'wgContentHandlerTextFallback' => 'ignore',
+ ) );
+
- global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
- $wgExtraNamespaces[ 12312 ] = 'Dummy';
- $wgExtraNamespaces[ 12313 ] = 'Dummy_talk';
++ $this->mergeMwGlobalArrayValue(
++ 'wgExtraNamespaces',
++ array(
++ 12312 => 'Dummy',
++ 12313 => 'Dummy_talk',
++ )
++ );
+
- $wgNamespaceContentModels[ 12312 ] = "testing";
- $wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting';
- $wgContentHandlers[ "RevisionTestModifyableContent" ] = 'RevisionTestModifyableContentHandler';
++ $this->mergeMwGlobalArrayValue(
++ 'wgNamespaceContentModels',
++ array(
++ 12312 => 'testing',
++ )
++ );
+
-
- global $wgContentHandlerTextFallback;
- $wgContentHandlerTextFallback = 'ignore';
++ $this->mergeMwGlobalArrayValue(
++ 'wgContentHandlers',
++ array(
++ 'testing' => 'DummyContentHandlerForTesting',
++ 'RevisionTestModifyableContent' => 'RevisionTestModifyableContentHandler',
++ )
++ );
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
- foreach ( $this->saveGlobals as $var => $data ) {
- $GLOBALS[$var] = $data;
- }
-
+ }
+
+ function tearDown() {
+ global $wgContLang;
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
++
++ parent::tearDown();
}
function testGetRevisionText() {
<?php
+ /**
+ * @group ContentHandler
+ *
+ * @note: We don't make assumptions about the main namespace.
+ * But we do expect the Help namespace to contain Wikitext.
+ *
+ */
class TitleMethodsTest extends MediaWikiTestCase {
- $wgExtraNamespaces[ 12302 ] = 'TEST-JS';
- $wgExtraNamespaces[ 12303 ] = 'TEST-JS_TALK';
+ public function setup() {
+ global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContLang;
+
- $wgNamespaceContentModels[ 12302 ] = CONTENT_MODEL_JAVASCRIPT;
++ $this->mergeMwGlobalArrayValue(
++ 'wgExtraNamespaces',
++ array(
++ 12302 => 'TEST-JS',
++ 12303 => 'TEST-JS_TALK',
++ )
++ );
+
- global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContLang;
-
- unset( $wgExtraNamespaces[ 12302 ] );
- unset( $wgExtraNamespaces[ 12303 ] );
-
- unset( $wgNamespaceContentModels[ 12302 ] );
++ $this->mergeMwGlobalArrayValue(
++ 'wgNamespaceContentModels',
++ array(
++ 12302 => CONTENT_MODEL_JAVASCRIPT,
++ )
++ );
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+ }
+
+ public function teardown() {
- public function dataEquals() {
++ global $wgContLang;
+
+ MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
+ $wgContLang->resetNamespaces(); # reset namespace cache
+ }
+
+ public static function provideEquals() {
return array(
array( 'Main Page', 'Main Page', true ),
array( 'Main Page', 'Not The Main Page', false ),
$this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) );
}
- public function dataIsCssOrJsPage() {
+ 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 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 ),
}
- 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 ),
$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 ),
$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 ),
$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 ),
<?php
+ /**
+ *
+ * @group Database
+ * ^--- needed for language cache stuff
+ */
class TitleTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => 'en',
+ 'wgContLang' => Language::factory( 'en' ),
+ // User language
+ 'wgLang' => Language::factory( 'en' ),
+ 'wgAllowUserJs' => false,
+ 'wgDefaultLanguageVariant' => false,
+ ) );
+ }
+
function testLegalChars() {
$titlechars = Title::legalChars();
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 */
}
}
- 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();
}
/**
- * @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() );
}
/**
- * @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", "<p>hello <i>world</i></p>"),
+ array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "<p>hello <i>world</i></p>"),
// @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 );
}
$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!',
}
/**
- * @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(),
$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 <nowiki>~~~</nowiki>',
- 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
),
);
}