From: daniel Date: Thu, 7 Aug 2014 15:40:55 +0000 (+0200) Subject: Allow callback functions for creating SpecialPages. X-Git-Tag: 1.31.0-rc.0~13963^2 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22sites_tous%22%29%20.%20%22?a=commitdiff_plain;h=4f0b2f42419c283e8c94a5be6147d54ae7731a6b;p=lhc%2Fweb%2Fwiklou.git Allow callback functions for creating SpecialPages. This enables factory functions to be registered for special pages, as an alterative to giving a class name. This follows the same rationale as Ieb85493a7765, which introduced factory functions for API modules. Change-Id: Ia2107dc5af7869187ba5dc02a1bef46d6801e138 --- diff --git a/RELEASE-NOTES-1.24 b/RELEASE-NOTES-1.24 index 771a821359..79b8a49ff7 100644 --- a/RELEASE-NOTES-1.24 +++ b/RELEASE-NOTES-1.24 @@ -47,6 +47,8 @@ production. * $wgCompiledFiles has been removed. * $wgSortSpecialPages was removed, the listing on Special:SpecialPages is now always sorted. +* $wgSpecialPages may now use callback functions as an alternative to plain class names. + This allows more control over constructor parameters. * $wgHTCPMulticastAddress, $wgHTCPMulticastRouting and $wgHTCPPort were removed. * $wgRC2UDPAddress, $wgRC2UDPInterwikiPrefix, $wgRC2UDPOmitBots, $wgRC2UDPPort and $wgRC2UDPPrefix have been removed. @@ -440,6 +442,8 @@ changes to languages because of Bugzilla reports. meaning that JavaScript is no longer executed in these browser versions. * Browser support for Opera 11 lowered from Grade A to Grade C. * Removed IEFixes module which existed purely to provide support for MSIE versions +* Deprecated SpecialPageFactory::getList() in favor of + SpecialPageFactory::getNames() below 7 (conditionally loaded only for those browsers). * Action::checkCanExecute() no longer has a return value. * Removed cleanupForIRC(), loadFromCurRow(), newFromCurRow(), notifyRC2UDP() diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 2123af1070..9f1b4c709d 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -6189,8 +6189,10 @@ $wgEnableParserLimitReporting = true; $wgValidSkinNames = array(); /** - * Special page list. - * See the top of SpecialPage.php for documentation. + * Special page list. This is an associative array mapping the (canonical) names of + * special pages to either a class name to be instantiated, or a callback to use for + * creating the special page object. In both cases, the result must be an instance of + * SpecialPage. */ $wgSpecialPages = array(); diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index 295183c62a..718750f58c 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -195,12 +195,12 @@ abstract class PrefixSearch { // Unlike SpecialPage itself, we want the canonical forms of both // canonical and alias title forms... $keys = array(); - foreach ( SpecialPageFactory::getList() as $page => $class ) { + foreach ( SpecialPageFactory::getNames() as $page ) { $keys[$wgContLang->caseFold( $page )] = $page; } foreach ( $wgContLang->getSpecialPageAliases() as $page => $aliases ) { - if ( !array_key_exists( $page, SpecialPageFactory::getList() ) ) {# bug 20885 + if ( !in_array( $page, SpecialPageFactory::getNames() ) ) {# bug 20885 continue; } diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index fd0c429d35..311438fd0e 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -344,7 +344,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { global $wgContLang; $data = array(); $aliases = $wgContLang->getSpecialPageAliases(); - foreach ( SpecialPageFactory::getList() as $specialpage => $stuff ) { + foreach ( SpecialPageFactory::getNames() as $specialpage ) { if ( isset( $aliases[$specialpage] ) ) { $arr = array( 'realname' => $specialpage, 'aliases' => $aliases[$specialpage] ); $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' ); diff --git a/includes/specialpage/SpecialPageFactory.php b/includes/specialpage/SpecialPageFactory.php index 0138cf9c91..e0737a0b83 100644 --- a/includes/specialpage/SpecialPageFactory.php +++ b/includes/specialpage/SpecialPageFactory.php @@ -176,11 +176,43 @@ class SpecialPageFactory { private static $aliases; /** - * Get the special page list + * Reset the internal list of special pages. Useful when changing $wgSpecialPages after + * the internal list has already been initialized, e.g. during testing. + */ + public static function resetList() { + self::$list = null; + self::$aliases = null; + } + + /** + * Returns a list of canonical special page names. + * May be used to iterate over all registered special pages. + * + * @return string[] + */ + public static function getNames() { + return array_keys( get_object_vars( self::getListObject() ) ); + } + + /** + * Get the special page list as an object, with each special page represented by a member + * field in the object. * - * @return array + * @deprecated since 1.24, use getNames() instead. + * @return object */ - static function getList() { + public static function getList() { + wfDeprecated( __FUNCTION__, '1.24' ); + return self::getListObject(); + } + + /** + * Get the special page list as an object, with each special page represented by a member + * field in the object. + * + * @return object + */ + private static function getListObject() { global $wgSpecialPages; global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication; global $wgEnableEmail, $wgEnableJavaScriptTest; @@ -223,6 +255,8 @@ class SpecialPageFactory { // This hook can be used to remove undesired built-in special pages wfRunHooks( 'SpecialPage_initList', array( &self::$list ) ); + self::$list = (object)self::$list; + wfProfileOut( __METHOD__ ); } @@ -237,12 +271,13 @@ class SpecialPageFactory { * contain at least one entry (English fallbacks will be added if necessary). * @return object */ - static function getAliasList() { + private static function getAliasListObject() { if ( !is_object( self::$aliases ) ) { global $wgContLang; $aliases = $wgContLang->getSpecialPageAliases(); - $missingPages = self::getList(); + // Objects are passed by reference by default, need to create a copy + $missingPages = clone self::getListObject(); self::$aliases = array(); // Check for $aliases being an array since Language::getSpecialPageAliases can return null @@ -279,8 +314,8 @@ class SpecialPageFactory { $caseFoldedAlias = $wgContLang->caseFold( $bits[0] ); $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias ); - if ( isset( self::getAliasList()->$caseFoldedAlias ) ) { - $name = self::getAliasList()->$caseFoldedAlias; + if ( isset( self::getAliasListObject()->$caseFoldedAlias ) ) { + $name = self::getAliasListObject()->$caseFoldedAlias; } else { return array( null, null ); } @@ -331,8 +366,7 @@ class SpecialPageFactory { public static function exists( $name ) { list( $title, /*...*/ ) = self::resolveAlias( $name ); - $specialPageList = self::getList(); - return isset( $specialPageList[$title] ); + return property_exists( self::getListObject(), $title ); } /** @@ -343,22 +377,36 @@ class SpecialPageFactory { */ public static function getPage( $name ) { list( $realName, /*...*/ ) = self::resolveAlias( $name ); - $specialPageList = self::getList(); - if ( isset( $specialPageList[$realName] ) ) { - $rec = $specialPageList[$realName]; + if ( property_exists( self::getListObject(), $realName ) ) { + $rec = self::getListObject()->$realName; + if ( is_string( $rec ) ) { $className = $rec; - - return new $className; + $page = new $className; + } elseif ( is_callable( $rec ) ) { + // Use callback to instantiate the special page + $page = call_user_func( $rec ); } elseif ( is_array( $rec ) ) { $className = array_shift( $rec ); // @deprecated, officially since 1.18, unofficially since forever wfDeprecated( "Array syntax for \$wgSpecialPages is deprecated ($className), " . "define a subclass of SpecialPage instead.", '1.18' ); - $specialPageList[$realName] = MWFunction::newObj( $className, $rec ); + $page = MWFunction::newObj( $className, $rec ); + } elseif ( $rec instanceof SpecialPage ) { + $page = $rec; //XXX: we should deep clone here + } else { + $page = null; + } + + if ( $page instanceof SpecialPage ) { + return $page; + } else { + // It's not a classname, nor a callback, nor a legacy constructor array, + // nor a special page object. Give up. + wfLogWarning( "Cannot instantiate special page $realName: bad spec!" ); + return null; } - return $specialPageList[$realName]; } else { return null; } @@ -378,7 +426,7 @@ class SpecialPageFactory { global $wgUser; $user = $wgUser; } - foreach ( self::getList() as $name => $rec ) { + foreach ( self::getListObject() as $name => $rec ) { $page = self::getPage( $name ); if ( $page ) { // not null $page->setContext( RequestContext::getMain() ); @@ -400,7 +448,7 @@ class SpecialPageFactory { */ public static function getRegularPages() { $pages = array(); - foreach ( self::getList() as $name => $rec ) { + foreach ( self::getListObject() as $name => $rec ) { $page = self::getPage( $name ); if ( $page->isListed() && !$page->isRestricted() ) { $pages[$name] = $page; @@ -423,7 +471,7 @@ class SpecialPageFactory { global $wgUser; $user = $wgUser; } - foreach ( self::getList() as $name => $rec ) { + foreach ( self::getListObject() as $name => $rec ) { $page = self::getPage( $name ); if ( $page->isListed() @@ -532,7 +580,7 @@ class SpecialPageFactory { * @param IContextSource $context * @return string HTML fragment */ - static function capturePath( Title $title, IContextSource $context ) { + public static function capturePath( Title $title, IContextSource $context ) { global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgLang; // Save current globals @@ -569,7 +617,7 @@ class SpecialPageFactory { * @param string|bool $subpage * @return string */ - static function getLocalNameFor( $name, $subpage = false ) { + public static function getLocalNameFor( $name, $subpage = false ) { global $wgContLang; $aliases = $wgContLang->getSpecialPageAliases(); @@ -608,7 +656,7 @@ class SpecialPageFactory { * @param string $alias * @return Title|null Title or null if there is no such alias */ - static function getTitleForAlias( $alias ) { + public static function getTitleForAlias( $alias ) { list( $name, $subpage ) = self::resolveAlias( $alias ); if ( $name != null ) { return SpecialPage::getTitleFor( $name, $subpage ); diff --git a/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php new file mode 100644 index 0000000000..b1d4257281 --- /dev/null +++ b/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php @@ -0,0 +1,101 @@ + array( 'SpecialAllPages', false ), + 'closure' => array( function() { + return new SpecialAllPages(); + }, false ), + 'function' => array( array( $this, 'newSpecialAllPages' ), false ), + ); + } + + /** + * @dataProvider specialPageProvider + */ + public function testGetPage( $spec, $shouldReuseInstance ) { + $this->mergeMwGlobalArrayValue( 'wgSpecialPages', array( 'testdummy' => $spec ) ); + + SpecialPageFactory::resetList(); + + $page = SpecialPageFactory::getPage( 'testdummy' ); + $this->assertInstanceOf( 'SpecialPage', $page ); + + $page2 = SpecialPageFactory::getPage( 'testdummy' ); + $this->assertEquals( $shouldReuseInstance, $page2 === $page, "Should re-use instance:" ); + + SpecialPageFactory::resetList(); + } + + public function testGetNames() { + $this->mergeMwGlobalArrayValue( 'wgSpecialPages', array( 'testdummy' => 'SpecialAllPages' ) ); + + SpecialPageFactory::resetList(); + $names = SpecialPageFactory::getNames(); + $this->assertInternalType( 'array', $names ); + $this->assertContains( 'testdummy', $names ); + SpecialPageFactory::resetList(); + } + + public function testResolveAlias() { + $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); + + SpecialPageFactory::resetList(); + + list( $name, $param ) = SpecialPageFactory::resolveAlias( 'Spezialseiten/Foo' ); + $this->assertEquals( 'Specialpages', $name ); + $this->assertEquals( 'Foo', $param ); + + SpecialPageFactory::resetList(); + } + + public function testGetLocalNameFor() { + $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); + + SpecialPageFactory::resetList(); + + $name = SpecialPageFactory::getLocalNameFor( 'Specialpages', 'Foo' ); + $this->assertEquals( 'Spezialseiten/Foo', $name ); + + SpecialPageFactory::resetList(); + } + + public function testGetTitleForAlias() { + $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) ); + + SpecialPageFactory::resetList(); + + $title = SpecialPageFactory::getTitleForAlias( 'Specialpages/Foo' ); + $this->assertEquals( 'Spezialseiten/Foo', $title->getText() ); + $this->assertEquals( NS_SPECIAL, $title->getNamespace() ); + + SpecialPageFactory::resetList(); + } + +}