* $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.
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()
'ConfigException' => 'includes/config/ConfigException.php',
'ConfigFactory' => 'includes/config/ConfigFactory.php',
'GlobalVarConfig' => 'includes/config/GlobalVarConfig.php',
+ 'HashConfig' => 'includes/config/HashConfig.php',
+ 'MultiConfig' => 'includes/config/MultiConfig.php',
'MutableConfig' => 'includes/config/MutableConfig.php',
# includes/content
$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();
// 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;
}
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' );
* @throws ConfigException
*/
public function get( $name );
+
+ /**
+ * Check whether a configuration option is set for the given name
+ *
+ * @param string $name Name of configuration option
+ * @return bool
+ * @since 1.24
+ */
+ public function has( $name );
}
* @see Config::get
*/
public function get( $name ) {
+ if ( !$this->has( $name ) ) {
+ throw new ConfigException( __METHOD__ . ": undefined option: '$name'" );
+ }
return $this->getWithPrefix( $this->prefix, $name );
}
+ /**
+ * @see Config::has
+ */
+ public function has( $name ) {
+ return $this->hasWithPrefix( $this->prefix, $name );
+ }
+
/**
* @see MutableConfig::set
* @deprecated since 1.24
*
* @param string $prefix Prefix to use on the variable, if one.
* @param string $name Variable name without prefix
- * @throws ConfigException
* @return mixed
*/
protected function getWithPrefix( $prefix, $name ) {
+ return $GLOBALS[$prefix . $name];
+ }
+
+ /**
+ * Check if a variable with a given prefix is set
+ *
+ * @param string $prefix Prefix to use on the variable
+ * @param string $name Variable name without prefix
+ * @return bool
+ */
+ protected function hasWithPrefix( $prefix, $name ) {
$var = $prefix . $name;
- if ( !array_key_exists( $var, $GLOBALS ) ) {
- throw new ConfigException( __METHOD__ . ": undefined variable: '$var'" );
- }
- return $GLOBALS[$var];
+ return array_key_exists( $var, $GLOBALS );
}
/**
--- /dev/null
+<?php
+/**
+ * Copyright 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * A Config instance which stores all settings as a member variable
+ *
+ * @since 1.24
+ */
+class HashConfig implements Config, MutableConfig {
+
+ /**
+ * Array of config settings
+ *
+ * @var array
+ */
+ private $settings;
+
+ /**
+ * @return HashConfig
+ */
+ public static function newInstance() {
+ return new HashConfig;
+ }
+
+ /**
+ * @param array $settings Any current settings to pre-load
+ */
+ public function __construct( array $settings = array() ) {
+ $this->settings = $settings;
+ }
+
+ /**
+ * @see Config::get
+ */
+ public function get( $name ) {
+ if ( !$this->has( $name ) ) {
+ throw new ConfigException( __METHOD__ . ": undefined option: '$name'" );
+ }
+
+ return $this->settings[$name];
+ }
+
+ /**
+ * @see Config::has
+ */
+ public function has( $name ) {
+ return array_key_exists( $name, $this->settings );
+ }
+
+ /**
+ * @see Config::set
+ */
+ public function set( $name, $value ) {
+ $this->settings[$name] = $value;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Provides a fallback sequence for Config objects
+ *
+ * @since 1.24
+ */
+class MultiConfig implements Config {
+
+ /**
+ * Array of Config objects to use
+ * Order matters, the Config objects
+ * will be checked in order to see
+ * whether they have the requested setting
+ *
+ * @var Config[]
+ */
+ private $configs;
+
+ /**
+ * @param Config[] $configs
+ */
+ public function __construct( array $configs ) {
+ $this->configs = $configs;
+ }
+
+ /**
+ * @see Config::get
+ */
+ public function get( $name ) {
+ foreach ( $this->configs as $config ) {
+ if ( $config->has( $name ) ) {
+ return $config->get( $name );
+ }
+ }
+
+ throw new ConfigException( __METHOD__ . ": undefined option: '$name'" );
+ }
+
+ /**
+ * @see Config::has
+ */
+ public function has( $name ) {
+ foreach ( $this->configs as $config ) {
+ if ( $config->has( $name ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
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;
// 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__ );
}
* 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
$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 );
}
public static function exists( $name ) {
list( $title, /*...*/ ) = self::resolveAlias( $name );
- $specialPageList = self::getList();
- return isset( $specialPageList[$title] );
+ return property_exists( self::getListObject(), $title );
}
/**
*/
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;
}
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() );
*/
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;
global $wgUser;
$user = $wgUser;
}
- foreach ( self::getList() as $name => $rec ) {
+ foreach ( self::getListObject() as $name => $rec ) {
$page = self::getPage( $name );
if (
$page->isListed()
* @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
* @param string|bool $subpage
* @return string
*/
- static function getLocalNameFor( $name, $subpage = false ) {
+ public static function getLocalNameFor( $name, $subpage = false ) {
global $wgContLang;
$aliases = $wgContLang->getSpecialPageAliases();
* @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 );
$link = $this->makeOptionsLink( $linkMessage->text(),
array( $key => 1 - $options[$key] ), $nondefaults );
- $links[] = $this->msg( $msg )->rawParams( $link )->escaped();
+ $links[] = "<span class=\"$msg rcshowhideoption\">" . $this->msg( $msg )->rawParams( $link )->escaped() . '</span>';
}
// show from this onward link
$now = $lang->userTimeAndDate( $timestamp, $user );
$timenow = $lang->userTime( $timestamp, $user );
$datenow = $lang->userDate( $timestamp, $user );
- $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )
- ->parse();
- $rclistfrom = $this->makeOptionsLink(
+ $pipedLinks = '<span class="rcshowhide">' . $lang->pipeList( $links ) . '</span>';
+
+ $rclinks = '<span class="rclinks">' . $this->msg( 'rclinks' )->rawParams( $cl, $dl, $pipedLinks )
+ ->parse() . '</span>';
+
+ $rclistfrom = '<span class="rclistfrom">' . $this->makeOptionsLink(
$this->msg( 'rclistfrom' )->rawParams( $now, $timenow, $datenow )->parse(),
array( 'from' => $timestamp ),
$nondefaults
- );
+ ) . '</span>';
return "{$note}$rclinks<br />$rclistfrom";
}
);
}
+ /**
+ * @covers GlobalVarConfig::has
+ */
+ public function testHas() {
+ $this->maybeStashGlobal( 'wgGlobalVarConfigTestHas' );
+ $GLOBALS['wgGlobalVarConfigTestHas'] = wfRandomString();
+ $this->maybeStashGlobal( 'wgGlobalVarConfigTestNotHas' );
+ $config = new GlobalVarConfig();
+ $this->assertTrue( $config->has( 'GlobalVarConfigTestHas' ) );
+ $this->assertFalse( $config->has( 'GlobalVarConfigTestNotHas' ) );
+ }
+
public function provideGet() {
$set = array(
'wgSomething' => 'default1',
public function testGet( $name, $prefix, $expected ) {
$config = new GlobalVarConfig( $prefix );
if ( $expected === false ) {
- $this->setExpectedException( 'ConfigException', 'GlobalVarConfig::getWithPrefix: undefined variable:' );
+ $this->setExpectedException( 'ConfigException', 'GlobalVarConfig::get: undefined option:' );
}
$this->assertEquals( $config->get( $name ), $expected );
}
--- /dev/null
+<?php
+
+class HashConfigTest extends MediaWikiTestCase {
+
+ /**
+ * @covers HashConfig::newInstance
+ */
+ public function testNewInstance() {
+ $conf = HashConfig::newInstance();
+ $this->assertInstanceOf( 'HashConfig', $conf );
+ }
+
+ /**
+ * @covers HashConfig::__construct
+ */
+ public function testConstructor() {
+ $conf = new HashConfig();
+ $this->assertInstanceOf( 'HashConfig', $conf );
+
+ // Test passing arguments to the constructor
+ $conf2 = new HashConfig( array(
+ 'one' => '1',
+ ) );
+ $this->assertEquals( '1', $conf2->get( 'one' ) );
+ }
+
+ /**
+ * @covers HashConfig::get
+ */
+ public function testGet() {
+ $conf = new HashConfig( array(
+ 'one' => '1',
+ ));
+ $this->assertEquals( '1', $conf->get( 'one' ) );
+ $this->setExpectedException( 'ConfigException', 'HashConfig::get: undefined option' );
+ $conf->get( 'two' );
+ }
+
+ /**
+ * @covers HashConfig::has
+ */
+ public function testHas() {
+ $conf = new HashConfig( array(
+ 'one' => '1',
+ ) );
+ $this->assertTrue( $conf->has( 'one' ) );
+ $this->assertFalse( $conf->has( 'two' ) );
+ }
+
+ /**
+ * @covers HashConfig::set
+ */
+ public function testSet() {
+ $conf = new HashConfig( array(
+ 'one' => '1',
+ ) );
+ $conf->set( 'two', '2' );
+ $this->assertEquals( '2', $conf->get( 'two' ) );
+ // Check that set overwrites
+ $conf->set( 'one', '3' );
+ $this->assertEquals( '3', $conf->get( 'one' ) );
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class MultiConfigTest extends MediaWikiTestCase {
+
+ /**
+ * Tests that settings are fetched in the right order
+ *
+ * @covers MultiConfig::get
+ */
+ public function testGet() {
+ $multi = new MultiConfig( array(
+ new HashConfig( array( 'foo' => 'bar' ) ),
+ new HashConfig( array( 'foo' => 'baz', 'bar' => 'foo' ) ),
+ new HashConfig( array( 'bar' => 'baz' ) ),
+ ) );
+
+ $this->assertEquals( 'bar', $multi->get( 'foo' ) );
+ $this->assertEquals( 'foo', $multi->get( 'bar' ) );
+ $this->setExpectedException( 'ConfigException', 'MultiConfig::get: undefined option:' );
+ $multi->get( 'notset' );
+ }
+
+ /**
+ * @covers MultiConfig::has
+ */
+ public function testHas() {
+ $conf = new MultiConfig( array(
+ new HashConfig( array( 'foo' => 'foo' ) ),
+ new HashConfig( array( 'something' => 'bleh' ) ),
+ new HashConfig( array( 'meh' => 'eh' ) ),
+ ) );
+
+ $this->assertTrue( $conf->has( 'foo' ) );
+ $this->assertTrue( $conf->has( 'something' ) );
+ $this->assertTrue( $conf->has( 'meh' ) );
+ $this->assertFalse( $conf->has( 'what' ) );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Factory for handling the special page list and generating SpecialPage objects.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @covers SpecialPageFactory
+ * @group SpecialPage
+ */
+class SpecialPageFactoryTest extends MediaWikiTestCase {
+
+ public function newSpecialAllPages() {
+ return new SpecialAllPages();
+ }
+
+ public function specialPageProvider() {
+ return array(
+ 'class name' => 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();
+ }
+
+}