Introduce UserOptionsManager and DefaultOptionsManager
This converts user options management to a separate service for use in DI context. User options are accessed quite early on in installation process and full-on options management depends on the database. Prior we have protected from accessing the DB by setting a hacky $wgUser with 0 id, and relying on the implementation that it doesn't go into the database to get the default user options. Now we can't really do that since DBLoadBalancer is required to instantiate the options manager. Instead, we redefine the options manager with a DefaultOptionsManager, that only provides access to default options and doesn't require DB access. UserOptionsManager uses PreferencesFactory, however injecting it will produce a cyclic dependency. The problem is that we separate options to different kinds, which are inferred from the PreferencesFactory declaration for those options (e.g. if it's a radio button in the UI declaration, the option is of multiselect kind). This is plain wrong, the dependency should be wise versa. This will be addressed separately, since it's requires larger refactoring. For now the PreferencesFactory is obtained on demand. This will be addressed in a followup. Bug: T248527 Change-Id: I74917c5eaec184d188911a319895b941ed55ee87
This commit is contained in:
parent
539d673f2a
commit
788331c48a
19 changed files with 1301 additions and 406 deletions
|
|
@ -980,6 +980,10 @@ because of Phabricator reports.
|
|||
* wfGetScriptUrl() was deprecated. The script URL should be configured rather
|
||||
than detected. wfScript() can be used to get a configured script URL.
|
||||
* Action::factory() with null $action argument is hard deprecated
|
||||
* The following methods of the User class were deprecated: getDefaultOptions,
|
||||
getDefaultOption, getOptions, getOption, getBoolOption, getIntOption,
|
||||
setOption, listOptionKinds, getOptionKinds, resetOptions. Use corresponding
|
||||
methods in UserOptionsLookup or UserOptionsManager service classes instead.
|
||||
* …
|
||||
|
||||
=== Other changes in 1.35 ===
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ use MediaWiki\Storage\NameTableStore;
|
|||
use MediaWiki\Storage\NameTableStoreFactory;
|
||||
use MediaWiki\Storage\PageEditStash;
|
||||
use MediaWiki\User\UserNameUtils;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
use MediaWiki\User\UserOptionsManager;
|
||||
use MessageCache;
|
||||
use MimeAnalyzer;
|
||||
use MWException;
|
||||
|
|
@ -1217,6 +1219,22 @@ class MediaWikiServices extends ServiceContainer {
|
|||
return $this->getService( 'UserNameUtils' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.35
|
||||
* @return UserOptionsLookup
|
||||
*/
|
||||
public function getUserOptionsLookup() : UserOptionsLookup {
|
||||
return $this->getService( 'UserOptionsLookup' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.35
|
||||
* @return UserOptionsManager
|
||||
*/
|
||||
public function getUserOptionsManager() : UserOptionsManager {
|
||||
return $this->getService( 'UserOptionsManager' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.28
|
||||
* @return VirtualRESTServiceClient
|
||||
|
|
|
|||
|
|
@ -91,7 +91,10 @@ use MediaWiki\Storage\BlobStoreFactory;
|
|||
use MediaWiki\Storage\NameTableStoreFactory;
|
||||
use MediaWiki\Storage\PageEditStash;
|
||||
use MediaWiki\Storage\SqlBlobStore;
|
||||
use MediaWiki\User\DefaultOptionsManager;
|
||||
use MediaWiki\User\UserNameUtils;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
use MediaWiki\User\UserOptionsManager;
|
||||
use Wikimedia\DependencyStore\KeyValueDependencyStore;
|
||||
use Wikimedia\DependencyStore\SqlModuleDependencyStore;
|
||||
use Wikimedia\Message\IMessageFormatterFactory;
|
||||
|
|
@ -1108,6 +1111,20 @@ return [
|
|||
);
|
||||
},
|
||||
|
||||
'UserOptionsLookup' => function ( MediaWikiServices $services ) : UserOptionsLookup {
|
||||
return $services->getUserOptionsManager();
|
||||
},
|
||||
|
||||
'UserOptionsManager' => function ( MediaWikiServices $services ) : UserOptionsManager {
|
||||
return new UserOptionsManager(
|
||||
new ServiceOptions( UserOptionsManager::CONSTRUCTOR_OPTIONS, $services->getMainConfig() ),
|
||||
$services->get( '_DefaultOptionsManager' ),
|
||||
$services->getLanguageConverterFactory(),
|
||||
$services->getDBLoadBalancer(),
|
||||
LoggerFactory::getInstance( 'UserOptionsManager' )
|
||||
);
|
||||
},
|
||||
|
||||
'VirtualRESTServiceClient' =>
|
||||
function ( MediaWikiServices $services ) : VirtualRESTServiceClient {
|
||||
$config = $services->getMainConfig()->get( 'VirtualRestConfig' );
|
||||
|
|
@ -1167,6 +1184,13 @@ return [
|
|||
);
|
||||
},
|
||||
|
||||
'_DefaultOptionsManager' => function ( MediaWikiServices $services ) : DefaultOptionsManager {
|
||||
return new DefaultOptionsManager(
|
||||
new ServiceOptions( DefaultOptionsManager::CONSTRUCTOR_OPTIONS, $services->getMainConfig() ),
|
||||
$services->getContentLanguage()
|
||||
);
|
||||
},
|
||||
|
||||
'_MediaWikiTitleCodec' => function ( MediaWikiServices $services ) : MediaWikiTitleCodec {
|
||||
return new MediaWikiTitleCodec(
|
||||
$services->getContentLanguage(),
|
||||
|
|
|
|||
|
|
@ -437,8 +437,14 @@ abstract class Installer {
|
|||
$mwServices->redefineService( 'InterwikiLookup', function () {
|
||||
return new NullInterwikiLookup();
|
||||
} );
|
||||
// Disable user options database fetching, only rely on default options.
|
||||
$mwServices->redefineService(
|
||||
'UserOptionsLookup',
|
||||
function ( MediaWikiServices $services ) {
|
||||
return $services->get( '_DefaultOptionsManager' );
|
||||
}
|
||||
);
|
||||
|
||||
// Having a user with id = 0 safeguards us from DB access via User::loadOptions().
|
||||
$wgUser = User::newFromId( 0 );
|
||||
RequestContext::getMain()->setUser( $wgUser );
|
||||
|
||||
|
|
@ -1549,6 +1555,7 @@ abstract class Installer {
|
|||
[ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ],
|
||||
[ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ],
|
||||
[ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ],
|
||||
[ 'name' => 'restore-services', 'callback' => [ $this, 'restoreServices' ] ],
|
||||
[ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ],
|
||||
[ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ],
|
||||
];
|
||||
|
|
@ -1641,6 +1648,21 @@ abstract class Installer {
|
|||
return $this->doGenerateKeys( $keys );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore services that have been redefined in the early stage of installation
|
||||
* @return Status
|
||||
*/
|
||||
public function restoreServices() {
|
||||
MediaWikiServices::resetGlobalInstance();
|
||||
MediaWikiServices::getInstance()->redefineService(
|
||||
'UserOptionsLookup',
|
||||
function ( MediaWikiServices $services ) {
|
||||
return $services->get( 'UserOptionsManager' );
|
||||
}
|
||||
);
|
||||
return Status::newGood();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a secret value for variables using a secure generator.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@
|
|||
"config-install-user-grant-failed": "Granting permission to user \"$1\" failed: $2",
|
||||
"config-install-user-missing": "The specified user \"$1\" does not exist.",
|
||||
"config-install-user-missing-create": "The specified user \"$1\" does not exist.\nPlease click the \"create account\" checkbox below if you want to create it.",
|
||||
"config-install-restore-services": "Restoring mediawiki services",
|
||||
"config-install-tables": "Creating tables",
|
||||
"config-install-tables-exist": "<strong>Warning:</strong> MediaWiki tables seem to already exist.\nSkipping creation.",
|
||||
"config-install-tables-failed": "<strong>Error:</strong> Table creation failed with the following error: $1",
|
||||
|
|
|
|||
|
|
@ -282,6 +282,7 @@
|
|||
"config-install-user-grant-failed": "Parameters:\n* $1 is the database username for which granting rights failed\n* $2 is the error message",
|
||||
"config-install-user-missing": "Used as PostgreSQL error message. Parameters:\n* $1 - database username\nSee also:\n* {{msg-mw|Config-install-user-missing-create}}",
|
||||
"config-install-user-missing-create": "Used as PostgreSQL error message. Parameters:\n* $1 - database username\nSee also:\n* {{msg-mw|Config-install-user-missing}}",
|
||||
"config-install-restore-services": "Message indicates that MediaWiki services overridden during installation are being restored",
|
||||
"config-install-tables": "Message indicates that the tables are being created\n\nSee also:\n*{{msg-mw|Config-install-database}}\n*{{msg-mw|Config-install-tables}}\n*{{msg-mw|Config-install-interwiki}}\n*{{msg-mw|Config-install-stats}}\n*{{msg-mw|Config-install-keys}}\n*{{msg-mw|Config-install-updates}}\n*{{msg-mw|Config-install-schema}}\n*{{msg-mw|Config-install-user}}\n*{{msg-mw|Config-install-sysop}}\n*{{msg-mw|Config-install-mainpage}}",
|
||||
"config-install-tables-exist": "Error notice during the installation saying that the database already seems set up for MediaWiki, so it's continuing without taking that step.",
|
||||
"config-install-tables-failed": "Used as PostgreSQL error message. Parameters:\n* $1 - detailed error message",
|
||||
|
|
|
|||
140
includes/user/DefaultOptionsManager.php
Normal file
140
includes/user/DefaultOptionsManager.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
namespace MediaWiki\User;
|
||||
|
||||
use Hooks;
|
||||
use Language;
|
||||
use LanguageConverter;
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use Skin;
|
||||
use Wikimedia\Assert\Assert;
|
||||
|
||||
/**
|
||||
* A service class to control default user options
|
||||
* @since 1.35
|
||||
*/
|
||||
class DefaultOptionsManager extends UserOptionsLookup {
|
||||
|
||||
public const CONSTRUCTOR_OPTIONS = [
|
||||
'DefaultSkin',
|
||||
'DefaultUserOptions',
|
||||
'NamespacesToBeSearchedDefault'
|
||||
];
|
||||
|
||||
/** @var ServiceOptions */
|
||||
private $serviceOptions;
|
||||
|
||||
/** @var Language */
|
||||
private $contentLang;
|
||||
|
||||
/** @var array|null Cached default options */
|
||||
private $defaultOptions = null;
|
||||
|
||||
/**
|
||||
* @param ServiceOptions $options
|
||||
* @param Language $contentLang
|
||||
*/
|
||||
public function __construct(
|
||||
ServiceOptions $options,
|
||||
Language $contentLang
|
||||
) {
|
||||
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
||||
$this->serviceOptions = $options;
|
||||
$this->contentLang = $contentLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDefaultOptions(): array {
|
||||
if ( $this->defaultOptions !== null ) {
|
||||
return $this->defaultOptions;
|
||||
}
|
||||
|
||||
$this->defaultOptions = $this->serviceOptions->get( 'DefaultUserOptions' );
|
||||
|
||||
// Default language setting
|
||||
$this->defaultOptions['language'] = $this->contentLang->getCode();
|
||||
foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
|
||||
if ( $langCode === $this->contentLang->getCode() ) {
|
||||
$this->defaultOptions['variant'] = $langCode;
|
||||
} else {
|
||||
$this->defaultOptions["variant-$langCode"] = $langCode;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
|
||||
// since extensions may change the set of searchable namespaces depending
|
||||
// on user groups/permissions.
|
||||
foreach ( $this->serviceOptions->get( 'NamespacesToBeSearchedDefault' ) as $nsnum => $val ) {
|
||||
$this->defaultOptions['searchNs' . $nsnum] = (bool)$val;
|
||||
}
|
||||
$this->defaultOptions['skin'] = Skin::normalizeKey( $this->serviceOptions->get( 'DefaultSkin' ) );
|
||||
|
||||
Hooks::run( 'UserGetDefaultOptions', [ &$this->defaultOptions ] );
|
||||
|
||||
return $this->defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDefaultOption( string $opt ) {
|
||||
$defOpts = $this->getDefaultOptions();
|
||||
return $defOpts[$opt] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOption(
|
||||
UserIdentity $user,
|
||||
string $oname,
|
||||
$defaultOverride = null,
|
||||
bool $ignoreHidden = false
|
||||
) {
|
||||
$this->verifyUsable( $user, __METHOD__ );
|
||||
return $this->getDefaultOption( $oname ) ?? $defaultOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOptions( UserIdentity $user, int $flags = 0 ): array {
|
||||
$this->verifyUsable( $user, __METHOD__ );
|
||||
if ( $flags & self::EXCLUDE_DEFAULTS ) {
|
||||
return [];
|
||||
}
|
||||
return $this->getDefaultOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the DefaultOptionsManager is usable as an instance of UserOptionsLookup.
|
||||
* It only makes sense in an installer context when UserOptionsManager cannot be yet instantiated
|
||||
* as the database is not available. Thus, this can only be called for an anon user,
|
||||
* calling under different circumstances indicates a bug.
|
||||
* @param UserIdentity $user
|
||||
* @param string $fname
|
||||
*/
|
||||
private function verifyUsable( UserIdentity $user, string $fname ) {
|
||||
Assert::precondition( !$user->isRegistered(), "$fname called on a registered user " );
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ use MediaWiki\Session\SessionManager;
|
|||
use MediaWiki\Session\Token;
|
||||
use MediaWiki\User\UserIdentity;
|
||||
use MediaWiki\User\UserNameUtils;
|
||||
use Wikimedia\Assert\Assert;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
use Wikimedia\IPSet;
|
||||
use Wikimedia\IPUtils;
|
||||
use Wikimedia\Rdbms\Database;
|
||||
|
|
@ -67,13 +67,14 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* Version number to tag cached versions of serialized User objects. Should be increased when
|
||||
* {@link $mCacheVars} or one of it's members changes.
|
||||
*/
|
||||
const VERSION = 14;
|
||||
const VERSION = 15;
|
||||
|
||||
/**
|
||||
* Exclude user options that are set to their default value.
|
||||
* @deprecated since 1.35 Use UserOptionsLookup::EXCLUDE_DEFAULTS
|
||||
* @since 1.25
|
||||
*/
|
||||
const GETOPTIONS_EXCLUDE_DEFAULTS = 1;
|
||||
const GETOPTIONS_EXCLUDE_DEFAULTS = UserOptionsLookup::EXCLUDE_DEFAULTS;
|
||||
|
||||
/**
|
||||
* @since 1.27
|
||||
|
|
@ -107,8 +108,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
'mEditCount',
|
||||
// user_groups table
|
||||
'mGroupMemberships',
|
||||
// user_properties table
|
||||
'mOptionOverrides',
|
||||
// actor table
|
||||
'mActorId',
|
||||
];
|
||||
|
|
@ -144,16 +143,9 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
protected $mEditCount;
|
||||
/** @var UserGroupMembership[] Associative array of (group name => UserGroupMembership object) */
|
||||
protected $mGroupMemberships;
|
||||
/** @var array */
|
||||
protected $mOptionOverrides;
|
||||
// @}
|
||||
|
||||
// @{
|
||||
/**
|
||||
* @var bool Whether the cache variables have been loaded.
|
||||
*/
|
||||
public $mOptionsLoaded;
|
||||
|
||||
/**
|
||||
* @var array|bool Array with already loaded items or true if all items have been loaded.
|
||||
*/
|
||||
|
|
@ -211,8 +203,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* @var bool
|
||||
*/
|
||||
public $mHideName;
|
||||
/** @var array */
|
||||
public $mOptions;
|
||||
|
||||
/** @var WebRequest */
|
||||
private $mRequest;
|
||||
|
|
@ -263,6 +253,10 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
if ( $name === 'mRights' ) {
|
||||
$copy = $this->getRights();
|
||||
return $copy;
|
||||
} elseif ( $name === 'mOptions' ) {
|
||||
wfDeprecated( 'User::$mOptions', '1.35' );
|
||||
$options = $this->getOptions();
|
||||
return $options;
|
||||
} elseif ( !property_exists( $this, $name ) ) {
|
||||
// T227688 - do not break $u->foo['bar'] = 1
|
||||
wfLogWarning( 'tried to get non-existent property' );
|
||||
|
|
@ -283,6 +277,12 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
$this,
|
||||
$value === null ? [] : $value
|
||||
);
|
||||
} elseif ( $name === 'mOptions' ) {
|
||||
wfDeprecated( 'User::$mOptions', '1.35' );
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
|
||||
foreach ( $value as $key => $val ) {
|
||||
$this->setOption( $key, $val );
|
||||
}
|
||||
} elseif ( !property_exists( $this, $name ) ) {
|
||||
$this->$name = $value;
|
||||
} else {
|
||||
|
|
@ -481,7 +481,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
|
||||
$this->loadFromDatabase( self::READ_NORMAL );
|
||||
$this->loadGroups();
|
||||
$this->loadOptions();
|
||||
|
||||
$data = [];
|
||||
foreach ( self::$mCacheVars as $name ) {
|
||||
|
|
@ -1163,8 +1162,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
$this->mActorId = $actorId;
|
||||
$this->mRealName = '';
|
||||
$this->mEmail = '';
|
||||
$this->mOptionOverrides = null;
|
||||
$this->mOptionsLoaded = false;
|
||||
|
||||
$loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
|
||||
? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
|
||||
|
|
@ -1409,7 +1406,9 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
}
|
||||
}
|
||||
if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) {
|
||||
$this->loadOptions( $data['user_properties'] );
|
||||
MediaWikiServices::getInstance()
|
||||
->getUserOptionsManager()
|
||||
->loadUserOptions( $this, $this->queryFlagsUsed, $data['user_properties'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1570,8 +1569,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
$this->mEffectiveGroups = null;
|
||||
$this->mImplicitGroups = null;
|
||||
$this->mGroupMemberships = null;
|
||||
$this->mOptions = null;
|
||||
$this->mOptionsLoaded = false;
|
||||
$this->mEditCount = null;
|
||||
|
||||
// Replacement of former `$this->mRights = null` line
|
||||
|
|
@ -1579,6 +1576,7 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
|
||||
$this
|
||||
);
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
|
||||
}
|
||||
|
||||
if ( $reloadFrom ) {
|
||||
|
|
@ -1587,76 +1585,30 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
}
|
||||
}
|
||||
|
||||
/** @var array|null */
|
||||
private static $defOpt = null;
|
||||
/** @var string|null */
|
||||
private static $defOptLang = null;
|
||||
|
||||
/**
|
||||
* Reset the process cache of default user options. This is only necessary
|
||||
* if the wiki configuration has changed since defaults were calculated,
|
||||
* and as such should only be performed inside the testing suite that
|
||||
* regularly changes wiki configuration.
|
||||
*/
|
||||
public static function resetGetDefaultOptionsForTestsOnly() {
|
||||
Assert::invariant( defined( 'MW_PHPUNIT_TEST' ), 'Unit tests only' );
|
||||
self::$defOpt = null;
|
||||
self::$defOptLang = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the language default options with any site-specific options
|
||||
* and add the default language variants.
|
||||
*
|
||||
* @deprecated since 1.35 Use UserOptionsLookup::getDefaultOptions instead.
|
||||
* @return array Array of options; typically strings, possibly booleans
|
||||
*/
|
||||
public static function getDefaultOptions() {
|
||||
global $wgNamespacesToBeSearchedDefault,
|
||||
$wgDefaultUserOptions,
|
||||
$wgDefaultSkin;
|
||||
|
||||
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
|
||||
if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
|
||||
// The content language does not change (and should not change) mid-request, but the
|
||||
// unit tests change it anyway, and expect this method to return values relevant to the
|
||||
// current content language.
|
||||
return self::$defOpt;
|
||||
}
|
||||
|
||||
self::$defOpt = $wgDefaultUserOptions;
|
||||
// Default language setting
|
||||
self::$defOptLang = $contLang->getCode();
|
||||
self::$defOpt['language'] = self::$defOptLang;
|
||||
foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
|
||||
if ( $langCode === $contLang->getCode() ) {
|
||||
self::$defOpt['variant'] = $langCode;
|
||||
} else {
|
||||
self::$defOpt["variant-$langCode"] = $langCode;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
|
||||
// since extensions may change the set of searchable namespaces depending
|
||||
// on user groups/permissions.
|
||||
foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
|
||||
self::$defOpt['searchNs' . $nsnum] = (bool)$val;
|
||||
}
|
||||
self::$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
|
||||
|
||||
Hooks::run( 'UserGetDefaultOptions', [ &self::$defOpt ] );
|
||||
|
||||
return self::$defOpt;
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsLookup()
|
||||
->getDefaultOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a given default option value.
|
||||
*
|
||||
* @deprecated since 1.35 Use UserOptionsLookup::getDefaultOption instead.
|
||||
* @param string $opt Name of option to retrieve
|
||||
* @return string|null Default option value
|
||||
*/
|
||||
public static function getDefaultOption( $opt ) {
|
||||
$defOpts = self::getDefaultOptions();
|
||||
return $defOpts[$opt] ?? null;
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsLookup()
|
||||
->getDefaultOption( $opt );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2901,25 +2853,15 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* @return mixed|null User's current value for the option
|
||||
* @see getBoolOption()
|
||||
* @see getIntOption()
|
||||
* @deprecated since 1.35 Use UserOptionsLookup::getOption instead
|
||||
*/
|
||||
public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
|
||||
global $wgHiddenPrefs;
|
||||
$this->loadOptions();
|
||||
|
||||
# We want 'disabled' preferences to always behave as the default value for
|
||||
# users, even if they have set the option explicitly in their settings (ie they
|
||||
# set it, and then it was disabled removing their ability to change it). But
|
||||
# we don't want to erase the preferences in the database in case the preference
|
||||
# is re-enabled again. So don't touch $mOptions, just override the returned value
|
||||
if ( !$ignoreHidden && in_array( $oname, $wgHiddenPrefs ) ) {
|
||||
return self::getDefaultOption( $oname );
|
||||
if ( $oname === null ) {
|
||||
return null; // b/c
|
||||
}
|
||||
|
||||
if ( array_key_exists( $oname, $this->mOptions ) ) {
|
||||
return $this->mOptions[$oname];
|
||||
}
|
||||
|
||||
return $defaultOverride;
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsLookup()
|
||||
->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2929,29 +2871,12 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* User::GETOPTIONS_EXCLUDE_DEFAULTS Exclude user options that are set
|
||||
* to the default value. (Since 1.25)
|
||||
* @return array
|
||||
* @deprecated since 1.35 Use UserOptionsLookup::getOptions instead
|
||||
*/
|
||||
public function getOptions( $flags = 0 ) {
|
||||
global $wgHiddenPrefs;
|
||||
$this->loadOptions();
|
||||
$options = $this->mOptions;
|
||||
|
||||
# We want 'disabled' preferences to always behave as the default value for
|
||||
# users, even if they have set the option explicitly in their settings (ie they
|
||||
# set it, and then it was disabled removing their ability to change it). But
|
||||
# we don't want to erase the preferences in the database in case the preference
|
||||
# is re-enabled again. So don't touch $mOptions, just override the returned value
|
||||
foreach ( $wgHiddenPrefs as $pref ) {
|
||||
$default = self::getDefaultOption( $pref );
|
||||
if ( $default !== null ) {
|
||||
$options[$pref] = $default;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
|
||||
$options = array_diff_assoc( $options, self::getDefaultOptions() );
|
||||
}
|
||||
|
||||
return $options;
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsLookup()
|
||||
->getOptions( $this, $flags );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2960,9 +2885,12 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* @param string $oname The option to check
|
||||
* @return bool User's current value for the option
|
||||
* @see getOption()
|
||||
* @deprecated since 1.35 Use UserOptionsLookup::getBoolOption instead
|
||||
*/
|
||||
public function getBoolOption( $oname ) {
|
||||
return (bool)$this->getOption( $oname );
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsLookup()
|
||||
->getBoolOption( $this, $oname );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2972,13 +2900,15 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* @param int $defaultOverride A default value returned if the option does not exist
|
||||
* @return int User's current value for the option
|
||||
* @see getOption()
|
||||
* @deprecated since 1.35 Use UserOptionsLookup::getIntOption instead
|
||||
*/
|
||||
public function getIntOption( $oname, $defaultOverride = 0 ) {
|
||||
$val = $this->getOption( $oname );
|
||||
if ( $val == '' ) {
|
||||
$val = $defaultOverride;
|
||||
if ( $oname === null ) {
|
||||
return null; // b/c
|
||||
}
|
||||
return intval( $val );
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsLookup()
|
||||
->getIntOption( $this, $oname, $defaultOverride );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2988,16 +2918,12 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
*
|
||||
* @param string $oname The option to set
|
||||
* @param mixed $val New value to set
|
||||
* @deprecated since 1.35 Use UserOptionsManager::setOption instead
|
||||
*/
|
||||
public function setOption( $oname, $val ) {
|
||||
$this->loadOptions();
|
||||
|
||||
// Explicitly NULL values should refer to defaults
|
||||
if ( $val === null ) {
|
||||
$val = self::getDefaultOption( $oname );
|
||||
}
|
||||
|
||||
$this->mOptions[$oname] = $val;
|
||||
MediaWikiServices::getInstance()
|
||||
->getUserOptionsManager()
|
||||
->setOption( $this, $oname, $val );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3071,16 +2997,12 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
*
|
||||
* @see User::getOptionKinds
|
||||
* @return array Option kinds
|
||||
* @deprecated since 1.35 Use UserOptionsManager::listOptionKinds instead
|
||||
*/
|
||||
public static function listOptionKinds() {
|
||||
return [
|
||||
'registered',
|
||||
'registered-multiselect',
|
||||
'registered-checkmatrix',
|
||||
'userjs',
|
||||
'special',
|
||||
'unused'
|
||||
];
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsManager()
|
||||
->listOptionKinds();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3094,76 +3016,12 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* @param array|null $options Assoc. array with options keys to check as keys.
|
||||
* Defaults to $this->mOptions.
|
||||
* @return array The key => kind mapping data
|
||||
* @deprecated since 1.35 Use UserOptionsManager::getOptionKinds instead
|
||||
*/
|
||||
public function getOptionKinds( IContextSource $context, $options = null ) {
|
||||
$this->loadOptions();
|
||||
if ( $options === null ) {
|
||||
$options = $this->mOptions;
|
||||
}
|
||||
|
||||
$preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
|
||||
$prefs = $preferencesFactory->getFormDescriptor( $this, $context );
|
||||
$mapping = [];
|
||||
|
||||
// Pull out the "special" options, so they don't get converted as
|
||||
// multiselect or checkmatrix.
|
||||
$specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
|
||||
foreach ( $specialOptions as $name => $value ) {
|
||||
unset( $prefs[$name] );
|
||||
}
|
||||
|
||||
// Multiselect and checkmatrix options are stored in the database with
|
||||
// one key per option, each having a boolean value. Extract those keys.
|
||||
$multiselectOptions = [];
|
||||
foreach ( $prefs as $name => $info ) {
|
||||
if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
|
||||
( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) {
|
||||
$opts = HTMLFormField::flattenOptions( $info['options'] );
|
||||
$prefix = $info['prefix'] ?? $name;
|
||||
|
||||
foreach ( $opts as $value ) {
|
||||
$multiselectOptions["$prefix$value"] = true;
|
||||
}
|
||||
|
||||
unset( $prefs[$name] );
|
||||
}
|
||||
}
|
||||
$checkmatrixOptions = [];
|
||||
foreach ( $prefs as $name => $info ) {
|
||||
if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
|
||||
( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) {
|
||||
$columns = HTMLFormField::flattenOptions( $info['columns'] );
|
||||
$rows = HTMLFormField::flattenOptions( $info['rows'] );
|
||||
$prefix = $info['prefix'] ?? $name;
|
||||
|
||||
foreach ( $columns as $column ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$checkmatrixOptions["$prefix$column-$row"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $prefs[$name] );
|
||||
}
|
||||
}
|
||||
|
||||
// $value is ignored
|
||||
foreach ( $options as $key => $value ) {
|
||||
if ( isset( $prefs[$key] ) ) {
|
||||
$mapping[$key] = 'registered';
|
||||
} elseif ( isset( $multiselectOptions[$key] ) ) {
|
||||
$mapping[$key] = 'registered-multiselect';
|
||||
} elseif ( isset( $checkmatrixOptions[$key] ) ) {
|
||||
$mapping[$key] = 'registered-checkmatrix';
|
||||
} elseif ( isset( $specialOptions[$key] ) ) {
|
||||
$mapping[$key] = 'special';
|
||||
} elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
|
||||
$mapping[$key] = 'userjs';
|
||||
} else {
|
||||
$mapping[$key] = 'unused';
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
return MediaWikiServices::getInstance()
|
||||
->getUserOptionsManager()
|
||||
->getOptionKinds( $this, $context, $options );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3179,46 +3037,19 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* @param IContextSource|null $context Context source used when $resetKinds
|
||||
* does not contain 'all', passed to getOptionKinds().
|
||||
* Defaults to RequestContext::getMain() when null.
|
||||
* @deprecated since 1.35 Use UserOptionsManager::resetOptions instead.
|
||||
*/
|
||||
public function resetOptions(
|
||||
$resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ],
|
||||
IContextSource $context = null
|
||||
) {
|
||||
$this->load();
|
||||
$defaultOptions = self::getDefaultOptions();
|
||||
|
||||
if ( !is_array( $resetKinds ) ) {
|
||||
$resetKinds = [ $resetKinds ];
|
||||
}
|
||||
|
||||
if ( in_array( 'all', $resetKinds ) ) {
|
||||
$newOptions = $defaultOptions;
|
||||
} else {
|
||||
if ( $context === null ) {
|
||||
$context = RequestContext::getMain();
|
||||
}
|
||||
|
||||
$optionKinds = $this->getOptionKinds( $context );
|
||||
$resetKinds = array_intersect( $resetKinds, self::listOptionKinds() );
|
||||
$newOptions = [];
|
||||
|
||||
// Use default values for the options that should be deleted, and
|
||||
// copy old values for the ones that shouldn't.
|
||||
foreach ( $this->mOptions as $key => $value ) {
|
||||
if ( in_array( $optionKinds[$key], $resetKinds ) ) {
|
||||
if ( array_key_exists( $key, $defaultOptions ) ) {
|
||||
$newOptions[$key] = $defaultOptions[$key];
|
||||
}
|
||||
} else {
|
||||
$newOptions[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Hooks::run( 'UserResetAllOptions', [ $this, &$newOptions, $this->mOptions, $resetKinds ] );
|
||||
|
||||
$this->mOptions = $newOptions;
|
||||
$this->mOptionsLoaded = true;
|
||||
MediaWikiServices::getInstance()
|
||||
->getUserOptionsManager()
|
||||
->resetOptions(
|
||||
$this,
|
||||
$context ?? RequestContext::getMain(),
|
||||
$resetKinds
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3990,7 +3821,7 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
} );
|
||||
|
||||
$this->mTouched = $newTouched;
|
||||
$this->saveOptions();
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
|
||||
|
||||
Hooks::run( 'UserSaveSettings', [ $this ] );
|
||||
$this->clearSharedCache( 'changed' );
|
||||
|
|
@ -4051,7 +3882,9 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
$user->load();
|
||||
$user->setToken(); // init token
|
||||
if ( isset( $params['options'] ) ) {
|
||||
$user->mOptions = $params['options'] + (array)$user->mOptions;
|
||||
MediaWikiServices::getInstance()
|
||||
->getUserOptionsManager()
|
||||
->loadUserOptions( $user, $user->queryFlagsUsed, $params['options'] );
|
||||
unset( $params['options'] );
|
||||
}
|
||||
$dbw = wfGetDB( DB_MASTER );
|
||||
|
|
@ -4177,7 +4010,7 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
// Clear instance cache other than user table data and actor, which is already accurate
|
||||
$this->clearInstanceCache();
|
||||
|
||||
$this->saveOptions();
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
|
||||
return Status::newGood();
|
||||
}
|
||||
|
||||
|
|
@ -5074,149 +4907,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
return true; // disabled
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the user options either from cache, the database or an array
|
||||
*
|
||||
* @param array|null $data Rows for the current user out of the user_properties table
|
||||
*/
|
||||
protected function loadOptions( $data = null ) {
|
||||
$this->load();
|
||||
|
||||
if ( $this->mOptionsLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->mOptions = self::getDefaultOptions();
|
||||
|
||||
if ( !$this->getId() ) {
|
||||
// For unlogged-in users, load language/variant options from request.
|
||||
// There's no need to do it for logged-in users: they can set preferences,
|
||||
// and handling of page content is done by $pageLang->getPreferredVariant() and such,
|
||||
// so don't override user's choice (especially when the user chooses site default).
|
||||
$factory = MediaWikiServices::getInstance()->getLanguageConverterFactory();
|
||||
$variant = $factory->getLanguageConverter()->getDefaultVariant();
|
||||
$this->mOptions['variant'] = $variant;
|
||||
$this->mOptions['language'] = $variant;
|
||||
$this->mOptionsLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Maybe load from the object
|
||||
if ( $this->mOptionOverrides !== null ) {
|
||||
wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );
|
||||
foreach ( $this->mOptionOverrides as $key => $value ) {
|
||||
$this->mOptions[$key] = $value;
|
||||
}
|
||||
} else {
|
||||
if ( !is_array( $data ) ) {
|
||||
wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
|
||||
// Load from database
|
||||
$dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
|
||||
? wfGetDB( DB_MASTER )
|
||||
: wfGetDB( DB_REPLICA );
|
||||
|
||||
$res = $dbr->select(
|
||||
'user_properties',
|
||||
[ 'up_property', 'up_value' ],
|
||||
[ 'up_user' => $this->getId() ],
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
$this->mOptionOverrides = [];
|
||||
$data = [];
|
||||
foreach ( $res as $row ) {
|
||||
// Convert '0' to 0. PHP's boolean conversion considers them both
|
||||
// false, but e.g. JavaScript considers the former as true.
|
||||
// @todo: T54542 Somehow determine the desired type (string/int/bool)
|
||||
// and convert all values here.
|
||||
if ( $row->up_value === '0' ) {
|
||||
$row->up_value = 0;
|
||||
}
|
||||
$data[$row->up_property] = $row->up_value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $data as $property => $value ) {
|
||||
$this->mOptionOverrides[$property] = $value;
|
||||
$this->mOptions[$property] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace deprecated language codes
|
||||
$this->mOptions['language'] = LanguageCode::replaceDeprecatedCodes(
|
||||
$this->mOptions['language']
|
||||
);
|
||||
|
||||
$this->mOptionsLoaded = true;
|
||||
|
||||
Hooks::run( 'UserLoadOptions', [ $this, &$this->mOptions ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the non-default options for this user, as previously set e.g. via
|
||||
* setOption(), in the database's "user_properties" (preferences) table.
|
||||
* Usually used via saveSettings().
|
||||
*/
|
||||
protected function saveOptions() {
|
||||
$this->loadOptions();
|
||||
|
||||
// Not using getOptions(), to keep hidden preferences in database
|
||||
$saveOptions = $this->mOptions;
|
||||
|
||||
// Allow hooks to abort, for instance to save to a global profile.
|
||||
// Reset options to default state before saving.
|
||||
if ( !Hooks::run( 'UserSaveOptions', [ $this, &$saveOptions ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = $this->getId();
|
||||
|
||||
$insert_rows = []; // all the new preference rows
|
||||
foreach ( $saveOptions as $key => $value ) {
|
||||
// Don't bother storing default values
|
||||
$defaultOption = self::getDefaultOption( $key );
|
||||
if ( ( $defaultOption === null && $value !== false && $value !== null )
|
||||
|| $value != $defaultOption
|
||||
) {
|
||||
$insert_rows[] = [
|
||||
'up_user' => $userId,
|
||||
'up_property' => $key,
|
||||
'up_value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$dbw = wfGetDB( DB_MASTER );
|
||||
|
||||
$res = $dbw->select( 'user_properties',
|
||||
[ 'up_property', 'up_value' ], [ 'up_user' => $userId ], __METHOD__ );
|
||||
|
||||
// Find prior rows that need to be removed or updated. These rows will
|
||||
// all be deleted (the latter so that INSERT IGNORE applies the new values).
|
||||
$keysDelete = [];
|
||||
foreach ( $res as $row ) {
|
||||
if ( !isset( $saveOptions[$row->up_property] )
|
||||
|| strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
|
||||
) {
|
||||
$keysDelete[] = $row->up_property;
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $keysDelete ) ) {
|
||||
// Do the DELETE by PRIMARY KEY for prior rows.
|
||||
// In the past a very large portion of calls to this function are for setting
|
||||
// 'rememberpassword' for new accounts (a preference that has since been removed).
|
||||
// Doing a blanket per-user DELETE for new accounts with no rows in the table
|
||||
// caused gap locks on [max user ID,+infinity) which caused high contention since
|
||||
// updates would pile up on each other as they are for higher (newer) user IDs.
|
||||
// It might not be necessary these days, but it shouldn't hurt either.
|
||||
$dbw->delete( 'user_properties',
|
||||
[ 'up_user' => $userId, 'up_property' => $keysDelete ], __METHOD__ );
|
||||
}
|
||||
// Insert the new preference rows
|
||||
$dbw->insert( 'user_properties', $insert_rows, __METHOD__, [ 'IGNORE' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of user fields that should be selected to create
|
||||
* a new user object.
|
||||
|
|
@ -5340,5 +5030,4 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
public function isAllowUsertalk() {
|
||||
return $this->mAllowUsertalk;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
107
includes/user/UserOptionsLookup.php
Normal file
107
includes/user/UserOptionsLookup.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
namespace MediaWiki\User;
|
||||
|
||||
/**
|
||||
* Provides access to user options
|
||||
* @since 1.35
|
||||
*/
|
||||
abstract class UserOptionsLookup {
|
||||
|
||||
/**
|
||||
* Exclude user options that are set to their default value.
|
||||
*/
|
||||
public const EXCLUDE_DEFAULTS = 1;
|
||||
|
||||
/**
|
||||
* Combine the language default options with any site-specific options
|
||||
* and add the default language variants.
|
||||
*
|
||||
* @return array Array of String options
|
||||
*/
|
||||
abstract public function getDefaultOptions(): array;
|
||||
|
||||
/**
|
||||
* Get a given default option value.
|
||||
*
|
||||
* @param string $opt Name of option to retrieve
|
||||
* @return string|null Default option value
|
||||
*/
|
||||
abstract public function getDefaultOption( string $opt );
|
||||
|
||||
/**
|
||||
* Get the user's current setting for a given option.
|
||||
*
|
||||
* @param UserIdentity $user The user to get the option for
|
||||
* @param string $oname The option to check
|
||||
* @param mixed|null $defaultOverride A default value returned if the option does not exist
|
||||
* @param bool $ignoreHidden Whether to ignore the effects of $wgHiddenPrefs
|
||||
* @return mixed|null User's current value for the option
|
||||
* @see getBoolOption()
|
||||
* @see getIntOption()
|
||||
*/
|
||||
abstract public function getOption(
|
||||
UserIdentity $user,
|
||||
string $oname,
|
||||
$defaultOverride = null,
|
||||
bool $ignoreHidden = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Get all user's options
|
||||
*
|
||||
* @param UserIdentity $user The user to get the option for
|
||||
* @param int $flags Bitwise combination of:
|
||||
* UserOptionsManager::EXCLUDE_DEFAULTS Exclude user options that are set
|
||||
* to the default value.
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getOptions( UserIdentity $user, int $flags = 0 ): array;
|
||||
|
||||
/**
|
||||
* Get the user's current setting for a given option, as a boolean value.
|
||||
*
|
||||
* @param UserIdentity $user The user to get the option for
|
||||
* @param string $oname The option to check
|
||||
* @return bool User's current value for the option
|
||||
* @see getOption()
|
||||
*/
|
||||
public function getBoolOption( UserIdentity $user, string $oname ): bool {
|
||||
return (bool)$this->getOption( $user, $oname );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's current setting for a given option, as an integer value.
|
||||
*
|
||||
* @param UserIdentity $user The user to get the option for
|
||||
* @param string $oname The option to check
|
||||
* @param int $defaultOverride A default value returned if the option does not exist
|
||||
* @return int User's current value for the option
|
||||
* @see getOption()
|
||||
*/
|
||||
public function getIntOption( UserIdentity $user, string $oname, int $defaultOverride = 0 ): int {
|
||||
$val = $this->getOption( $user, $oname );
|
||||
if ( $val == '' ) {
|
||||
$val = $defaultOverride;
|
||||
}
|
||||
return intval( $val );
|
||||
}
|
||||
}
|
||||
556
includes/user/UserOptionsManager.php
Normal file
556
includes/user/UserOptionsManager.php
Normal file
|
|
@ -0,0 +1,556 @@
|
|||
<?php
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
namespace MediaWiki\User;
|
||||
|
||||
use DBAccessObjectUtils;
|
||||
use Hooks;
|
||||
use HTMLCheckMatrix;
|
||||
use HTMLFormField;
|
||||
use HTMLMultiSelectField;
|
||||
use IContextSource;
|
||||
use IDBAccessObject;
|
||||
use LanguageCode;
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\Languages\LanguageConverterFactory;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use User;
|
||||
use Wikimedia\Rdbms\IDatabase;
|
||||
use Wikimedia\Rdbms\ILoadBalancer;
|
||||
|
||||
/**
|
||||
* A service class to control user options
|
||||
* @since 1.35
|
||||
*/
|
||||
class UserOptionsManager extends UserOptionsLookup implements IDBAccessObject {
|
||||
|
||||
public const CONSTRUCTOR_OPTIONS = [
|
||||
'HiddenPrefs'
|
||||
];
|
||||
|
||||
/** @var ServiceOptions */
|
||||
private $serviceOptions;
|
||||
|
||||
/** @var DefaultOptionsManager */
|
||||
private $defaultOptionsManager;
|
||||
|
||||
/** @var LanguageConverterFactory */
|
||||
private $languageConverterFactory;
|
||||
|
||||
/** @var ILoadBalancer */
|
||||
private $loadBalancer;
|
||||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/** @var array Cached options by user */
|
||||
private $optionsCache = [];
|
||||
|
||||
/** @var array Cached original user options fetched from database */
|
||||
private $originalOptionsCache = [];
|
||||
|
||||
/**
|
||||
* UserOptionsManager constructor.
|
||||
* @param ServiceOptions $options
|
||||
* @param DefaultOptionsManager $defaultOptionsManager
|
||||
* @param LanguageConverterFactory $languageConverterFactory
|
||||
* @param ILoadBalancer $loadBalancer
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(
|
||||
ServiceOptions $options,
|
||||
DefaultOptionsManager $defaultOptionsManager,
|
||||
LanguageConverterFactory $languageConverterFactory,
|
||||
ILoadBalancer $loadBalancer,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
||||
$this->serviceOptions = $options;
|
||||
$this->defaultOptionsManager = $defaultOptionsManager;
|
||||
$this->languageConverterFactory = $languageConverterFactory;
|
||||
$this->loadBalancer = $loadBalancer;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDefaultOptions(): array {
|
||||
return $this->defaultOptionsManager->getDefaultOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getDefaultOption( string $opt ) {
|
||||
return $this->defaultOptionsManager->getDefaultOption( $opt );
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOption(
|
||||
UserIdentity $user,
|
||||
string $oname,
|
||||
$defaultOverride = null,
|
||||
bool $ignoreHidden = false
|
||||
) {
|
||||
# We want 'disabled' preferences to always behave as the default value for
|
||||
# users, even if they have set the option explicitly in their settings (ie they
|
||||
# set it, and then it was disabled removing their ability to change it). But
|
||||
# we don't want to erase the preferences in the database in case the preference
|
||||
# is re-enabled again. So don't touch $mOptions, just override the returned value
|
||||
if ( !$ignoreHidden && in_array( $oname, $this->serviceOptions->get( 'HiddenPrefs' ) ) ) {
|
||||
return $this->defaultOptionsManager->getDefaultOption( $oname );
|
||||
}
|
||||
|
||||
$options = $this->loadUserOptions( $user );
|
||||
if ( array_key_exists( $oname, $options ) ) {
|
||||
return $options[$oname];
|
||||
}
|
||||
return $defaultOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOptions( UserIdentity $user, int $flags = 0 ): array {
|
||||
$options = $this->loadUserOptions( $user );
|
||||
|
||||
# We want 'disabled' preferences to always behave as the default value for
|
||||
# users, even if they have set the option explicitly in their settings (ie they
|
||||
# set it, and then it was disabled removing their ability to change it). But
|
||||
# we don't want to erase the preferences in the database in case the preference
|
||||
# is re-enabled again. So don't touch $mOptions, just override the returned value
|
||||
foreach ( $this->serviceOptions->get( 'HiddenPrefs' ) as $pref ) {
|
||||
$default = $this->defaultOptionsManager->getDefaultOption( $pref );
|
||||
if ( $default !== null ) {
|
||||
$options[$pref] = $default;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $flags & self::EXCLUDE_DEFAULTS ) {
|
||||
$options = array_diff_assoc( $options, $this->defaultOptionsManager->getDefaultOptions() );
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given option for a user.
|
||||
*
|
||||
* You need to call saveOptions() to actually write to the database.
|
||||
*
|
||||
* @param UserIdentity $user
|
||||
* @param string $oname The option to set
|
||||
* @param mixed $val New value to set
|
||||
*/
|
||||
public function setOption( UserIdentity $user, string $oname, $val ) {
|
||||
$this->loadUserOptions( $user );
|
||||
|
||||
// Explicitly NULL values should refer to defaults
|
||||
if ( $val === null ) {
|
||||
$val = $this->defaultOptionsManager->getDefaultOption( $oname );
|
||||
}
|
||||
|
||||
$userKey = $this->getCacheKey( $user );
|
||||
$this->optionsCache[$userKey][$oname] = $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset certain (or all) options to the site defaults
|
||||
*
|
||||
* The optional parameter determines which kinds of preferences will be reset.
|
||||
* Supported values are everything that can be reported by getOptionKinds()
|
||||
* and 'all', which forces a reset of *all* preferences and overrides everything else.
|
||||
*
|
||||
* @param UserIdentity $user
|
||||
* @param IContextSource $context Context source used when $resetKinds does not contain 'all'.
|
||||
* @param array|string $resetKinds Which kinds of preferences to reset.
|
||||
* Defaults to [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ]
|
||||
*/
|
||||
public function resetOptions(
|
||||
UserIdentity $user,
|
||||
IContextSource $context,
|
||||
$resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ]
|
||||
) {
|
||||
$oldOptions = $this->loadUserOptions( $user );
|
||||
$defaultOptions = $this->defaultOptionsManager->getDefaultOptions();
|
||||
|
||||
if ( !is_array( $resetKinds ) ) {
|
||||
$resetKinds = [ $resetKinds ];
|
||||
}
|
||||
|
||||
if ( in_array( 'all', $resetKinds ) ) {
|
||||
$newOptions = $defaultOptions;
|
||||
} else {
|
||||
$optionKinds = $this->getOptionKinds( $user, $context );
|
||||
$resetKinds = array_intersect( $resetKinds, $this->listOptionKinds() );
|
||||
$newOptions = [];
|
||||
|
||||
// Use default values for the options that should be deleted, and
|
||||
// copy old values for the ones that shouldn't.
|
||||
foreach ( $oldOptions as $key => $value ) {
|
||||
if ( in_array( $optionKinds[$key], $resetKinds ) ) {
|
||||
if ( array_key_exists( $key, $defaultOptions ) ) {
|
||||
$newOptions[$key] = $defaultOptions[$key];
|
||||
}
|
||||
} else {
|
||||
$newOptions[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Deprecate passing full user to the hook
|
||||
Hooks::run( 'UserResetAllOptions', [
|
||||
User::newFromIdentity( $user ), &$newOptions, $oldOptions, $resetKinds
|
||||
] );
|
||||
|
||||
$this->optionsCache[$this->getCacheKey( $user )] = $newOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of the types of user options currently returned by
|
||||
* UserOptionsManager::getOptionKinds().
|
||||
*
|
||||
* Currently, the option kinds are:
|
||||
* - 'registered' - preferences which are registered in core MediaWiki or
|
||||
* by extensions using the UserGetDefaultOptions hook.
|
||||
* - 'registered-multiselect' - as above, using the 'multiselect' type.
|
||||
* - 'registered-checkmatrix' - as above, using the 'checkmatrix' type.
|
||||
* - 'userjs' - preferences with names starting with 'userjs-', intended to
|
||||
* be used by user scripts.
|
||||
* - 'special' - "preferences" that are not accessible via User::getOptions
|
||||
* or UserOptionsManager::setOptions.
|
||||
* - 'unused' - preferences about which MediaWiki doesn't know anything.
|
||||
* These are usually legacy options, removed in newer versions.
|
||||
*
|
||||
* The API (and possibly others) use this function to determine the possible
|
||||
* option types for validation purposes, so make sure to update this when a
|
||||
* new option kind is added.
|
||||
*
|
||||
* @see getOptionKinds
|
||||
* @return array Option kinds
|
||||
*/
|
||||
public function listOptionKinds(): array {
|
||||
return [
|
||||
'registered',
|
||||
'registered-multiselect',
|
||||
'registered-checkmatrix',
|
||||
'userjs',
|
||||
'special',
|
||||
'unused'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an associative array mapping preferences keys to the kind of a preference they're
|
||||
* used for. Different kinds are handled differently when setting or reading preferences.
|
||||
*
|
||||
* See UserOptionsManager::listOptionKinds for the list of valid option types that can be provided.
|
||||
*
|
||||
* @see UserOptionsManager::listOptionKinds
|
||||
* @param UserIdentity $user
|
||||
* @param IContextSource $context
|
||||
* @param array|null $options Assoc. array with options keys to check as keys.
|
||||
* Defaults user options.
|
||||
* @return array The key => kind mapping data
|
||||
*/
|
||||
public function getOptionKinds(
|
||||
UserIdentity $user,
|
||||
IContextSource $context,
|
||||
$options = null
|
||||
): array {
|
||||
if ( $options === null ) {
|
||||
$options = $this->loadUserOptions( $user );
|
||||
}
|
||||
|
||||
// TODO: injecting the preferences factory creates a cyclic dependency between
|
||||
// PreferencesFactory and UserOptionsManager. See T250822
|
||||
$preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
|
||||
$prefs = $preferencesFactory->getFormDescriptor( User::newFromIdentity( $user ), $context );
|
||||
$mapping = [];
|
||||
|
||||
// Pull out the "special" options, so they don't get converted as
|
||||
// multiselect or checkmatrix.
|
||||
$specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
|
||||
foreach ( $specialOptions as $name => $value ) {
|
||||
unset( $prefs[$name] );
|
||||
}
|
||||
|
||||
// Multiselect and checkmatrix options are stored in the database with
|
||||
// one key per option, each having a boolean value. Extract those keys.
|
||||
$multiselectOptions = [];
|
||||
foreach ( $prefs as $name => $info ) {
|
||||
if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
|
||||
( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class )
|
||||
) {
|
||||
$opts = HTMLFormField::flattenOptions( $info['options'] );
|
||||
$prefix = $info['prefix'] ?? $name;
|
||||
|
||||
foreach ( $opts as $value ) {
|
||||
$multiselectOptions["$prefix$value"] = true;
|
||||
}
|
||||
|
||||
unset( $prefs[$name] );
|
||||
}
|
||||
}
|
||||
$checkmatrixOptions = [];
|
||||
foreach ( $prefs as $name => $info ) {
|
||||
if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
|
||||
( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class )
|
||||
) {
|
||||
$columns = HTMLFormField::flattenOptions( $info['columns'] );
|
||||
$rows = HTMLFormField::flattenOptions( $info['rows'] );
|
||||
$prefix = $info['prefix'] ?? $name;
|
||||
|
||||
foreach ( $columns as $column ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$checkmatrixOptions["$prefix$column-$row"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $prefs[$name] );
|
||||
}
|
||||
}
|
||||
|
||||
// $value is ignored
|
||||
foreach ( $options as $key => $value ) {
|
||||
if ( isset( $prefs[$key] ) ) {
|
||||
$mapping[$key] = 'registered';
|
||||
} elseif ( isset( $multiselectOptions[$key] ) ) {
|
||||
$mapping[$key] = 'registered-multiselect';
|
||||
} elseif ( isset( $checkmatrixOptions[$key] ) ) {
|
||||
$mapping[$key] = 'registered-checkmatrix';
|
||||
} elseif ( isset( $specialOptions[$key] ) ) {
|
||||
$mapping[$key] = 'special';
|
||||
} elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
|
||||
$mapping[$key] = 'userjs';
|
||||
} else {
|
||||
$mapping[$key] = 'unused';
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the non-default options for this user, as previously set e.g. via
|
||||
* setOption(), in the database's "user_properties" (preferences) table.
|
||||
* Usually used via saveSettings().
|
||||
* @param UserIdentity $user
|
||||
* @internal
|
||||
*/
|
||||
public function saveOptions( UserIdentity $user ) {
|
||||
// Not using getOptions(), to keep hidden preferences in database
|
||||
$saveOptions = $this->loadUserOptions( $user, self::READ_LATEST );
|
||||
|
||||
// Allow hooks to abort, for instance to save to a global profile.
|
||||
// Reset options to default state before saving.
|
||||
// TODO: Deprecate passing User to the hook.
|
||||
if ( !Hooks::run( 'UserSaveOptions', [ User::newFromIdentity( $user ), &$saveOptions ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = $user->getId();
|
||||
|
||||
$insert_rows = []; // all the new preference rows
|
||||
foreach ( $saveOptions as $key => $value ) {
|
||||
// Don't bother storing default values
|
||||
$defaultOption = $this->defaultOptionsManager->getDefaultOption( $key );
|
||||
if ( ( $defaultOption === null && $value !== false && $value !== null )
|
||||
|| $value != $defaultOption
|
||||
) {
|
||||
$insert_rows[] = [
|
||||
'up_user' => $userId,
|
||||
'up_property' => $key,
|
||||
'up_value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
|
||||
|
||||
$res = $dbw->select(
|
||||
'user_properties',
|
||||
[ 'up_property', 'up_value' ],
|
||||
[ 'up_user' => $userId ],
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
// Find prior rows that need to be removed or updated. These rows will
|
||||
// all be deleted (the latter so that INSERT IGNORE applies the new values).
|
||||
$keysDelete = [];
|
||||
foreach ( $res as $row ) {
|
||||
if ( !isset( $saveOptions[$row->up_property] ) ||
|
||||
$saveOptions[$row->up_property] !== $row->up_value
|
||||
) {
|
||||
$keysDelete[] = $row->up_property;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !count( $keysDelete ) && !count( $insert_rows ) ) {
|
||||
return;
|
||||
}
|
||||
$this->originalOptionsCache[$this->getCacheKey( $user )] = null;
|
||||
|
||||
if ( count( $keysDelete ) ) {
|
||||
// Do the DELETE by PRIMARY KEY for prior rows.
|
||||
// In the past a very large portion of calls to this function are for setting
|
||||
// 'rememberpassword' for new accounts (a preference that has since been removed).
|
||||
// Doing a blanket per-user DELETE for new accounts with no rows in the table
|
||||
// caused gap locks on [max user ID,+infinity) which caused high contention since
|
||||
// updates would pile up on each other as they are for higher (newer) user IDs.
|
||||
// It might not be necessary these days, but it shouldn't hurt either.
|
||||
$dbw->delete(
|
||||
'user_properties',
|
||||
[
|
||||
'up_user' => $userId,
|
||||
'up_property' => $keysDelete
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
// Insert the new preference rows
|
||||
$dbw->insert(
|
||||
'user_properties',
|
||||
$insert_rows,
|
||||
__METHOD__,
|
||||
[ 'IGNORE' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserIdentity $user
|
||||
* @param int $queryFlags
|
||||
* @param array|null $data preloaded row from the user_properties table
|
||||
* @return array
|
||||
* @internal To be called by User loading code to provide the $data
|
||||
*/
|
||||
public function loadUserOptions(
|
||||
UserIdentity $user,
|
||||
int $queryFlags = self::READ_NORMAL,
|
||||
array $data = null
|
||||
): array {
|
||||
$userKey = $this->getCacheKey( $user );
|
||||
if ( isset( $this->optionsCache[$userKey] ) ) {
|
||||
return $this->optionsCache[$userKey];
|
||||
}
|
||||
|
||||
$options = $this->defaultOptionsManager->getDefaultOptions();
|
||||
|
||||
if ( !$user->isRegistered() ) {
|
||||
// For unlogged-in users, load language/variant options from request.
|
||||
// There's no need to do it for logged-in users: they can set preferences,
|
||||
// and handling of page content is done by $pageLang->getPreferredVariant() and such,
|
||||
// so don't override user's choice (especially when the user chooses site default).
|
||||
$variant = $this->languageConverterFactory->getLanguageConverter()->getDefaultVariant();
|
||||
$options['variant'] = $variant;
|
||||
$options['language'] = $variant;
|
||||
$this->optionsCache[$userKey] = $options;
|
||||
return $options;
|
||||
}
|
||||
|
||||
// In case options were already loaded from the database before and no options
|
||||
// changes were saved to the database, we can use the cached original options.
|
||||
if ( isset( $this->originalOptionsCache[$userKey] ) ) {
|
||||
$this->logger->debug( 'Loading options from override cache', [
|
||||
'user_id' => $user->getId()
|
||||
] );
|
||||
foreach ( $this->originalOptionsCache[$userKey] as $key => $value ) {
|
||||
$options[$key] = $value;
|
||||
}
|
||||
} else {
|
||||
if ( !is_array( $data ) ) {
|
||||
$this->logger->debug( 'Loading options from database', [
|
||||
'user_id' => $user->getId()
|
||||
] );
|
||||
|
||||
$dbr = $this->getDBForQueryFlags( $queryFlags );
|
||||
$res = $dbr->select(
|
||||
'user_properties',
|
||||
[ 'up_property', 'up_value' ],
|
||||
[ 'up_user' => $user->getId() ],
|
||||
__METHOD__
|
||||
);
|
||||
|
||||
$this->originalOptionsCache[$userKey] = [];
|
||||
$data = [];
|
||||
foreach ( $res as $row ) {
|
||||
// Convert '0' to 0. PHP's boolean conversion considers them both
|
||||
// false, but e.g. JavaScript considers the former as true.
|
||||
// @todo: T54542 Somehow determine the desired type (string/int/bool)
|
||||
// and convert all values here.
|
||||
if ( $row->up_value === '0' ) {
|
||||
$row->up_value = 0;
|
||||
}
|
||||
$data[$row->up_property] = $row->up_value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $data as $property => $value ) {
|
||||
$this->originalOptionsCache[$userKey][$property] = $value;
|
||||
$options[$property] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace deprecated language codes
|
||||
$options['language'] = LanguageCode::replaceDeprecatedCodes( $options['language'] );
|
||||
$this->optionsCache[$userKey] = $options;
|
||||
|
||||
// TODO: Deprecate passing full User object into the hook.
|
||||
Hooks::run(
|
||||
'UserLoadOptions',
|
||||
[ User::newFromIdentity( $user ), &$this->optionsCache[$userKey] ]
|
||||
);
|
||||
|
||||
return $this->optionsCache[$userKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cached user options.
|
||||
* @internal To be used by User::clearInstanceCache
|
||||
* @param UserIdentity $user
|
||||
*/
|
||||
public function clearUserOptionsCache( UserIdentity $user ) {
|
||||
$cacheKey = $this->getCacheKey( $user );
|
||||
$this->optionsCache[$cacheKey] = null;
|
||||
$this->originalOptionsCache[$cacheKey] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique key for various caches.
|
||||
* @param UserIdentity $user
|
||||
* @return string
|
||||
*/
|
||||
private function getCacheKey( UserIdentity $user ): string {
|
||||
return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $queryFlags a bit field composed of READ_XXX flags
|
||||
* @return IDatabase
|
||||
*/
|
||||
private function getDBForQueryFlags( $queryFlags ): IDatabase {
|
||||
list( $mode, ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
|
||||
return $this->loadBalancer->getConnectionRef( $mode, [] );
|
||||
}
|
||||
}
|
||||
|
|
@ -170,6 +170,9 @@ $wgAutoloadClasses += [
|
|||
'MediaWiki\Tests\Revision\RevisionStoreDbTestBase' => "$testDir/phpunit/includes/Revision/RevisionStoreDbTestBase.php",
|
||||
'MediaWiki\Tests\Revision\RevisionStoreRecordTest' => "$testDir/phpunit/includes/Revision/RevisionStoreRecordTest.php",
|
||||
|
||||
# test/phpunit/includes/user
|
||||
'UserOptionsLookupTest' => "$testDir/phpunit/includes/user/UserOptionsLookupTest.php",
|
||||
|
||||
# tests/phpunit/languages
|
||||
'DummyConverter' => "$testDir/phpunit/mocks/languages/DummyConverter.php",
|
||||
'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
|
||||
|
|
|
|||
|
|
@ -362,7 +362,6 @@ abstract class MediaWikiIntegrationTestCase extends PHPUnit\Framework\TestCase {
|
|||
public static function resetNonServiceCaches() {
|
||||
global $wgRequest, $wgJobClasses;
|
||||
|
||||
User::resetGetDefaultOptionsForTestsOnly();
|
||||
foreach ( $wgJobClasses as $type => $class ) {
|
||||
JobQueueGroup::singleton()->get( $type )->delete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,9 +318,10 @@ class ApiMainTest extends ApiTestCase {
|
|||
private function doTestCheckMaxLag( $lag ) {
|
||||
$mockLB = $this->getMockBuilder( LoadBalancer::class )
|
||||
->disableOriginalConstructor()
|
||||
->setMethods( [ 'getMaxLag', '__destruct' ] )
|
||||
->setMethods( [ 'getMaxLag', 'getConnectionRef', '__destruct' ] )
|
||||
->getMock();
|
||||
$mockLB->method( 'getMaxLag' )->willReturn( [ 'somehost', $lag ] );
|
||||
$mockLB->method( 'getConnectionRef' )->willReturn( $this->db );
|
||||
$this->setService( 'DBLoadBalancer', $mockLB );
|
||||
|
||||
$req = new FauxRequest();
|
||||
|
|
|
|||
|
|
@ -105,12 +105,6 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
|
|||
$redirected = true;
|
||||
}
|
||||
);
|
||||
$ctx = new RequestContext();
|
||||
|
||||
// Give users patrol permissions so we can test that.
|
||||
$user = $this->getTestSysop()->getUser();
|
||||
$user->setOption( 'rcenhancedfilters-disable', $rcfilters ? 0 : 1 );
|
||||
$ctx->setUser( $user );
|
||||
|
||||
// Disable this hook or it could break changeType
|
||||
// depending on which other extensions are running.
|
||||
|
|
@ -119,6 +113,12 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
|
|||
null
|
||||
);
|
||||
|
||||
// Give users patrol permissions so we can test that.
|
||||
$user = $this->getTestSysop()->getUser();
|
||||
$user->setOption( 'rcenhancedfilters-disable', $rcfilters ? 0 : 1 );
|
||||
$ctx = new RequestContext();
|
||||
$ctx->setUser( $user );
|
||||
|
||||
$ctx->setOutput( $output );
|
||||
$clsp = $this->changesListSpecialPage;
|
||||
$clsp->setContext( $ctx );
|
||||
|
|
|
|||
52
tests/phpunit/includes/user/DefaultOptionsManagerTest.php
Normal file
52
tests/phpunit/includes/user/DefaultOptionsManagerTest.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\User\DefaultOptionsManager;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager
|
||||
*/
|
||||
class DefaultOptionsManagerTest extends UserOptionsLookupTest {
|
||||
protected function getLookup(
|
||||
string $langCode = 'qqq',
|
||||
array $defaultOptionsOverrides = []
|
||||
) : UserOptionsLookup {
|
||||
return $this->getDefaultManager( $langCode, $defaultOptionsOverrides );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getOption
|
||||
*/
|
||||
public function testGetOptionsExcludeDefaults() {
|
||||
$this->assertSame( [], $this->getLookup()
|
||||
->getOptions( $this->getAnon(), DefaultOptionsManager::EXCLUDE_DEFAULTS ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getDefaultOptions
|
||||
*/
|
||||
public function testGetDefaultOptionsHook() {
|
||||
$this->setTemporaryHook( 'UserGetDefaultOptions', function ( &$options ) {
|
||||
$options['from_hook'] = 'value_from_hook';
|
||||
} );
|
||||
$this->assertSame( 'value_from_hook', $this->getLookup()->getDefaultOption( 'from_hook' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getDefaultOptions
|
||||
*/
|
||||
public function testSearchNS() {
|
||||
$this->assertTrue( $this->getLookup()->getDefaultOption( 'searchNs0' ) );
|
||||
$this->assertNull( $this->getLookup()->getDefaultOption( 'searchNs5' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getDefaultOptions
|
||||
*/
|
||||
public function testLangVariantOptions() {
|
||||
$managerZh = $this->getLookup( 'zh' );
|
||||
$this->assertSame( 'zh', $managerZh->getDefaultOption( 'language' ) );
|
||||
$this->assertSame( 'gan', $managerZh->getDefaultOption( 'variant-gan' ) );
|
||||
$this->assertSame( 'zh', $managerZh->getDefaultOption( 'variant' ) );
|
||||
}
|
||||
}
|
||||
138
tests/phpunit/includes/user/UserOptionsLookupTest.php
Normal file
138
tests/phpunit/includes/user/UserOptionsLookupTest.php
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\User\DefaultOptionsManager;
|
||||
use MediaWiki\User\UserIdentity;
|
||||
use MediaWiki\User\UserIdentityValue;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager
|
||||
* @covers MediaWiki\User\UserOptionsManager
|
||||
* @covers MediaWiki\User\UserOptionsLookup
|
||||
*/
|
||||
abstract class UserOptionsLookupTest extends MediaWikiIntegrationTestCase {
|
||||
use MediaWikiCoversValidator;
|
||||
|
||||
protected function getAnon(
|
||||
string $name = 'anon'
|
||||
) : UserIdentity {
|
||||
return new UserIdentityValue( 0, $name, 0 );
|
||||
}
|
||||
|
||||
abstract protected function getLookup(
|
||||
string $langCode = 'qqq',
|
||||
array $defaultOptionsOverrides = []
|
||||
) : UserOptionsLookup;
|
||||
|
||||
protected function getDefaultManager(
|
||||
string $langCode = 'qqq',
|
||||
array $defaultOptionsOverrides = []
|
||||
) : DefaultOptionsManager {
|
||||
$lang = $this->createMock( Language::class );
|
||||
$lang->method( 'getCode' )->willReturn( $langCode );
|
||||
return new DefaultOptionsManager(
|
||||
new ServiceOptions(
|
||||
DefaultOptionsManager::CONSTRUCTOR_OPTIONS,
|
||||
new HashConfig( [
|
||||
'DefaultSkin' => 'test',
|
||||
'DefaultUserOptions' => array_merge( [
|
||||
'default_string_option' => 'string_value',
|
||||
'default_int_option' => 1,
|
||||
'default_bool_option' => true
|
||||
], $defaultOptionsOverrides ),
|
||||
'NamespacesToBeSearchedDefault' => [
|
||||
NS_MAIN => true,
|
||||
NS_TALK => true
|
||||
]
|
||||
] )
|
||||
),
|
||||
$lang
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getDefaultOptions
|
||||
* @covers MediaWiki\User\UserOptionsManager::getDefaultOptions
|
||||
*/
|
||||
public function testGetDefaultOptions() {
|
||||
$options = $this->getLookup()->getDefaultOptions();
|
||||
$this->assertSame( 'string_value', $options['default_string_option'] );
|
||||
$this->assertSame( 1, $options['default_int_option'] );
|
||||
$this->assertSame( true, $options['default_bool_option'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getDefaultOption
|
||||
* @covers MediaWiki\User\UserOptionsManager::getDefaultOption
|
||||
*/
|
||||
public function testGetDefaultOption() {
|
||||
$manager = $this->getLookup();
|
||||
$this->assertSame( 'string_value', $manager->getDefaultOption( 'default_string_option' ) );
|
||||
$this->assertSame( 1, $manager->getDefaultOption( 'default_int_option' ) );
|
||||
$this->assertSame( true, $manager->getDefaultOption( 'default_bool_option' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getOptions
|
||||
* @covers MediaWiki\User\UserOptionsManager::getOptions
|
||||
*/
|
||||
public function testGetOptions() {
|
||||
$options = $this->getLookup()->getOptions( $this->getAnon() );
|
||||
$this->assertSame( 'string_value', $options['default_string_option'] );
|
||||
$this->assertSame( 1, $options['default_int_option'] );
|
||||
$this->assertSame( true, $options['default_bool_option'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getOption
|
||||
* @covers MediaWiki\User\UserOptionsManager::getOption
|
||||
*/
|
||||
public function testGetOptionDefault() {
|
||||
$manager = $this->getLookup();
|
||||
$this->assertSame( 'string_value',
|
||||
$manager->getOption( $this->getAnon(), 'default_string_option' ) );
|
||||
$this->assertSame( 1, $manager->getOption( $this->getAnon(), 'default_int_option' ) );
|
||||
$this->assertSame( true, $manager->getOption( $this->getAnon(), 'default_bool_option' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getOption
|
||||
* @covers MediaWiki\User\UserOptionsManager::getOption
|
||||
*/
|
||||
public function testGetOptionDefaultNotExist() {
|
||||
$this->assertNull( $this->getLookup()
|
||||
->getOption( $this->getAnon(), 'this_option_does_not_exist' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\DefaultOptionsManager::getOption
|
||||
* @covers MediaWiki\User\UserOptionsManager::getOption
|
||||
*/
|
||||
public function testGetOptionDefaultNotExistDefaultOverride() {
|
||||
$this->assertSame( 'override', $this->getLookup()
|
||||
->getOption( $this->getAnon(), 'this_option_does_not_exist', 'override' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsLookup::getIntOption
|
||||
*/
|
||||
public function testGetIntOption() {
|
||||
$this->assertSame(
|
||||
2,
|
||||
$this->getLookup( 'qqq', [ 'default_int_option' => '2' ] )
|
||||
->getIntOption( $this->getAnon(), 'default_int_option' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsLookup::getBoolOption
|
||||
*/
|
||||
public function testGetBoolOption() {
|
||||
$this->assertSame(
|
||||
true,
|
||||
$this->getLookup( 'qqq', [ 'default_bool_option' => 'true' ] )
|
||||
->getBoolOption( $this->getAnon(), 'default_bool_option' )
|
||||
);
|
||||
}
|
||||
}
|
||||
149
tests/phpunit/includes/user/UserOptionsManagerTest.php
Normal file
149
tests/phpunit/includes/user/UserOptionsManagerTest.php
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
use MediaWiki\User\UserOptionsManager;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @group Database
|
||||
* @covers MediaWiki\User\UserOptionsManager
|
||||
*/
|
||||
class UserOptionsManagerTest extends UserOptionsLookupTest {
|
||||
|
||||
private function getManager(
|
||||
string $langCode = 'qqq',
|
||||
array $defaultOptionsOverrides = []
|
||||
) {
|
||||
$services = MediaWikiServices::getInstance();
|
||||
return new UserOptionsManager(
|
||||
new ServiceOptions(
|
||||
UserOptionsManager::CONSTRUCTOR_OPTIONS,
|
||||
new HashConfig( [ 'HiddenPrefs' => [ 'hidden_user_option' ] ] )
|
||||
),
|
||||
$this->getDefaultManager( $langCode, $defaultOptionsOverrides ),
|
||||
$services->getLanguageConverterFactory(),
|
||||
$services->getDBLoadBalancer(),
|
||||
new NullLogger()
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLookup(
|
||||
string $langCode = 'qqq',
|
||||
array $defaultOptionsOverrides = []
|
||||
) : UserOptionsLookup {
|
||||
return $this->getManager( $langCode, $defaultOptionsOverrides );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsManager::getOption
|
||||
*/
|
||||
public function testGetOptionsExcludeDefaults() {
|
||||
$manager = $this->getManager();
|
||||
$manager->setOption( $this->getAnon( __METHOD__ ), 'new_option', 'new_value' );
|
||||
$this->assertSame( [
|
||||
'language' => 'en',
|
||||
'variant' => 'en',
|
||||
'new_option' => 'new_value'
|
||||
], $manager->getOptions( $this->getAnon( __METHOD__ ), UserOptionsManager::EXCLUDE_DEFAULTS ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsManager::getOption
|
||||
*/
|
||||
public function testGetOptionHiddenPref() {
|
||||
$user = $this->getAnon( __METHOD__ );
|
||||
$manager = $this->getManager();
|
||||
$manager->setOption( $user, 'hidden_user_option', 'hidden_value' );
|
||||
$this->assertNull( $manager->getOption( $user, 'hidden_user_option' ) );
|
||||
$this->assertSame( 'hidden_value',
|
||||
$manager->getOption( $user, 'hidden_user_option', null, true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsManager::setOption
|
||||
*/
|
||||
public function testSetOptionNullIsDefault() {
|
||||
$user = $this->getAnon( __METHOD__ );
|
||||
$manager = $this->getManager();
|
||||
$manager->setOption( $user, 'default_string_option', 'override_value' );
|
||||
$this->assertSame( 'override_value', $manager->getOption( $user, 'default_string_option' ) );
|
||||
$manager->setOption( $user, 'default_string_option', null );
|
||||
$this->assertSame( 'string_value', $manager->getOption( $user, 'default_string_option' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsManager::getOption
|
||||
* @covers MediaWiki\User\UserOptionsManager::setOption
|
||||
* @covers MediaWiki\User\UserOptionsManager::saveOptions
|
||||
*/
|
||||
public function testGetSetSave() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$manager = $this->getManager();
|
||||
$this->assertSame( [], $manager->getOptions( $user, UserOptionsManager::EXCLUDE_DEFAULTS ) );
|
||||
$manager->setOption( $user, 'string_option', 'user_value' );
|
||||
$manager->setOption( $user, 'int_option', 42 );
|
||||
$manager->setOption( $user, 'bool_option', true );
|
||||
$this->assertSame( 'user_value', $manager->getOption( $user, 'string_option' ) );
|
||||
$this->assertSame( 42, $manager->getIntOption( $user, 'int_option' ) );
|
||||
$this->assertSame( true, $manager->getBoolOption( $user, 'bool_option' ) );
|
||||
$manager->saveOptions( $user );
|
||||
$manager = $this->getManager();
|
||||
$this->assertSame( 'user_value', $manager->getOption( $user, 'string_option' ) );
|
||||
$this->assertSame( 42, $manager->getIntOption( $user, 'int_option' ) );
|
||||
$this->assertSame( true, $manager->getBoolOption( $user, 'bool_option' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsManager::loadUserOptions
|
||||
*/
|
||||
public function testLoadUserOptionsHook() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$this->setTemporaryHook(
|
||||
'UserLoadOptions',
|
||||
function ( User $hookUser, &$options ) use ( $user ) {
|
||||
if ( $hookUser->equals( $user ) ) {
|
||||
$options['from_hook'] = 'value_from_hook';
|
||||
}
|
||||
}
|
||||
);
|
||||
$this->assertSame( 'value_from_hook', $this->getManager()->getOption( $user, 'from_hook' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsManager::saveOptions
|
||||
*/
|
||||
public function testSaveUserOptionsHookAbort() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$this->setTemporaryHook(
|
||||
'UserSaveOptions',
|
||||
function () {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$manager = $this->getManager();
|
||||
$manager->setOption( $user, 'will_be_aborted_by_hook', 'value' );
|
||||
$manager->saveOptions( $user );
|
||||
$this->assertNull( $this->getManager()->getOption( $user, 'will_be_aborted_by_hook' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MediaWiki\User\UserOptionsManager::saveOptions
|
||||
*/
|
||||
public function testSaveUserOptionsHookModify() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$this->setTemporaryHook(
|
||||
'UserLoadOptions',
|
||||
function ( User $hookUser, &$options ) use ( $user ) {
|
||||
if ( $hookUser->equals( $user ) ) {
|
||||
$options['from_hook'] = 'value_from_hook';
|
||||
}
|
||||
}
|
||||
);
|
||||
$manager = $this->getManager();
|
||||
$manager->saveOptions( $user );
|
||||
$this->assertSame( 'value_from_hook', $manager->getOption( $user, 'from_hook' ) );
|
||||
$this->assertSame( 'value_from_hook', $this->getManager()->getOption( $user, 'from_hook' ) );
|
||||
}
|
||||
}
|
||||
|
|
@ -458,8 +458,7 @@ class UserTest extends MediaWikiTestCase {
|
|||
$user->setOption( 'userjs-usedefaultoverride', '' );
|
||||
$user->saveSettings();
|
||||
|
||||
$user = User::newFromName( $user->getName() );
|
||||
$user->load( User::READ_LATEST );
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $user );
|
||||
$this->assertSame( 'test', $user->getOption( 'userjs-someoption' ) );
|
||||
$this->assertTrue( $user->getBoolOption( 'userjs-someoption' ) );
|
||||
$this->assertEquals( 200, $user->getOption( 'rclimit' ) );
|
||||
|
|
@ -475,7 +474,7 @@ class UserTest extends MediaWikiTestCase {
|
|||
'Valid stub threshold preferences are respected'
|
||||
);
|
||||
|
||||
$user = User::newFromName( $user->getName() );
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $user );
|
||||
MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
|
||||
$this->assertSame( 'test', $user->getOption( 'userjs-someoption' ) );
|
||||
$this->assertTrue( $user->getBoolOption( 'userjs-someoption' ) );
|
||||
|
|
@ -493,8 +492,7 @@ class UserTest extends MediaWikiTestCase {
|
|||
);
|
||||
|
||||
// Check that an option saved as a string '0' is returned as an integer.
|
||||
$user = User::newFromName( $user->getName() );
|
||||
$user->load( User::READ_LATEST );
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $user );
|
||||
$this->assertSame( 0, $user->getOption( 'wpwatchlistdays' ) );
|
||||
$this->assertFalse( $user->getBoolOption( 'wpwatchlistdays' ) );
|
||||
|
||||
|
|
@ -511,7 +509,7 @@ class UserTest extends MediaWikiTestCase {
|
|||
/**
|
||||
* T39963
|
||||
* Make sure defaults are loaded when setOption is called.
|
||||
* @covers User::loadOptions
|
||||
* @covers User::setOption
|
||||
*/
|
||||
public function testAnonOptions() {
|
||||
global $wgDefaultUserOptions;
|
||||
|
|
@ -2172,8 +2170,8 @@ class UserTest extends MediaWikiTestCase {
|
|||
* @covers User::getDefaultOption
|
||||
* @covers User::getDefaultOptions
|
||||
*/
|
||||
public function testDefaultOptions() {
|
||||
User::resetGetDefaultOptionsForTestsOnly();
|
||||
public function testGetDefaultOptions() {
|
||||
$this->resetServices();
|
||||
|
||||
$this->setTemporaryHook( 'UserGetDefaultOptions', function ( &$defaults ) {
|
||||
$defaults['extraoption'] = 42;
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
|
|||
$user->load(); // from 'defaults'
|
||||
$user->mId = 1;
|
||||
$user->mDataLoaded = true;
|
||||
$user->mOptionsLoaded = true;
|
||||
$user->setOption( 'variant', 'tg-latn' );
|
||||
|
||||
$wgUser = $user;
|
||||
|
|
@ -168,7 +167,6 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
|
|||
$user->load(); // from 'defaults'
|
||||
$user->mId = 1;
|
||||
$user->mDataLoaded = true;
|
||||
$user->mOptionsLoaded = true;
|
||||
$user->setOption( 'variant', 'bat-smg' );
|
||||
|
||||
$wgUser = $user;
|
||||
|
|
@ -186,7 +184,6 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
|
|||
$user->load(); // from 'defaults'
|
||||
$user->mId = 1;
|
||||
$user->mDataLoaded = true;
|
||||
$user->mOptionsLoaded = true;
|
||||
$user->setOption( 'variant', 'en-simple' );
|
||||
|
||||
$wgUser = $user;
|
||||
|
|
@ -206,7 +203,6 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
|
|||
$user->load(); // from 'defaults'
|
||||
$user->mId = 1;
|
||||
$user->mDataLoaded = true;
|
||||
$user->mOptionsLoaded = true;
|
||||
$user->setOption( 'variant-tg', 'tg-latn' );
|
||||
|
||||
$wgUser = $user;
|
||||
|
|
@ -226,7 +222,6 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
|
|||
$user->load(); // from 'defaults'
|
||||
$user->mId = 1;
|
||||
$user->mDataLoaded = true;
|
||||
$user->mOptionsLoaded = true;
|
||||
$user->setOption( 'variant-tg', 'bat-smg' );
|
||||
|
||||
$wgUser = $user;
|
||||
|
|
@ -246,7 +241,6 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
|
|||
$user->load(); // from 'defaults'
|
||||
$user->mId = 1;
|
||||
$user->mDataLoaded = true;
|
||||
$user->mOptionsLoaded = true;
|
||||
$user->setOption( 'variant-tg', 'en-simple' );
|
||||
|
||||
$wgUser = $user;
|
||||
|
|
@ -267,7 +261,6 @@ class LanguageConverterTest extends MediaWikiLangTestCase {
|
|||
$user = User::newFromId( "admin" );
|
||||
$user->setId( 1 );
|
||||
$user->mFrom = 'defaults';
|
||||
$user->mOptionsLoaded = true;
|
||||
// The user's data is ignored because the variant is set in the URL.
|
||||
$user->setOption( 'variant', 'tg-latn' );
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue