MediaWiki  master
UserOptionsManager.php
Go to the documentation of this file.
1 <?php
21 namespace MediaWiki\User;
22 
24 use HTMLCheckMatrix;
25 use HTMLFormField;
27 use IContextSource;
28 use InvalidArgumentException;
29 use LanguageCode;
35 use Psr\Log\LoggerInterface;
36 use User;
39 
45 
46  public const CONSTRUCTOR_OPTIONS = [
47  'HiddenPrefs'
48  ];
49 
51  private $serviceOptions;
52 
55 
58 
60  private $loadBalancer;
61 
63  private $logger;
64 
66  private $optionsCache = [];
67 
69  private $originalOptionsCache = [];
70 
72  private $hookRunner;
73 
76 
85  public function __construct(
86  ServiceOptions $options,
90  LoggerInterface $logger,
91  HookContainer $hookContainer
92  ) {
93  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
94  $this->serviceOptions = $options;
95  $this->defaultOptionsLookup = $defaultOptionsLookup;
96  $this->languageConverterFactory = $languageConverterFactory;
97  $this->loadBalancer = $loadBalancer;
98  $this->logger = $logger;
99  $this->hookRunner = new HookRunner( $hookContainer );
100  }
101 
105  public function getDefaultOptions(): array {
106  return $this->defaultOptionsLookup->getDefaultOptions();
107  }
108 
112  public function getDefaultOption( string $opt ) {
113  return $this->defaultOptionsLookup->getDefaultOption( $opt );
114  }
115 
119  public function getOption(
120  UserIdentity $user,
121  string $oname,
122  $defaultOverride = null,
123  bool $ignoreHidden = false,
124  int $queryFlags = self::READ_NORMAL
125  ) {
126  # We want 'disabled' preferences to always behave as the default value for
127  # users, even if they have set the option explicitly in their settings (ie they
128  # set it, and then it was disabled removing their ability to change it). But
129  # we don't want to erase the preferences in the database in case the preference
130  # is re-enabled again. So don't touch $mOptions, just override the returned value
131  if ( !$ignoreHidden && in_array( $oname, $this->serviceOptions->get( 'HiddenPrefs' ) ) ) {
132  return $this->defaultOptionsLookup->getDefaultOption( $oname );
133  }
134 
135  $options = $this->loadUserOptions( $user, $queryFlags );
136  if ( array_key_exists( $oname, $options ) ) {
137  return $options[$oname];
138  }
139  return $defaultOverride;
140  }
141 
145  public function getOptions(
146  UserIdentity $user,
147  int $flags = 0,
148  int $queryFlags = self::READ_NORMAL
149  ): array {
150  $options = $this->loadUserOptions( $user, $queryFlags );
151 
152  # We want 'disabled' preferences to always behave as the default value for
153  # users, even if they have set the option explicitly in their settings (ie they
154  # set it, and then it was disabled removing their ability to change it). But
155  # we don't want to erase the preferences in the database in case the preference
156  # is re-enabled again. So don't touch $mOptions, just override the returned value
157  foreach ( $this->serviceOptions->get( 'HiddenPrefs' ) as $pref ) {
158  $default = $this->defaultOptionsLookup->getDefaultOption( $pref );
159  if ( $default !== null ) {
160  $options[$pref] = $default;
161  }
162  }
163 
164  if ( $flags & self::EXCLUDE_DEFAULTS ) {
165  $options = array_diff_assoc( $options, $this->defaultOptionsLookup->getDefaultOptions() );
166  }
167 
168  return $options;
169  }
170 
180  public function setOption( UserIdentity $user, string $oname, $val ) {
181  // In case the options are modified, we need to refetch
182  // latest options in case they were not fetched from master
183  // so that we don't get a race condition trying to save modified options.
184  $this->loadUserOptions( $user, self::READ_LATEST );
185 
186  // Explicitly NULL values should refer to defaults
187  if ( $val === null ) {
188  $val = $this->defaultOptionsLookup->getDefaultOption( $oname );
189  }
190 
191  $userKey = $this->getCacheKey( $user );
192  $this->optionsCache[$userKey][$oname] = $val;
193  }
194 
207  public function resetOptions(
208  UserIdentity $user,
210  $resetKinds = [ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ]
211  ) {
212  $oldOptions = $this->loadUserOptions( $user );
213  $defaultOptions = $this->defaultOptionsLookup->getDefaultOptions();
214 
215  if ( !is_array( $resetKinds ) ) {
216  $resetKinds = [ $resetKinds ];
217  }
218 
219  if ( in_array( 'all', $resetKinds ) ) {
220  $newOptions = $defaultOptions;
221  } else {
222  $optionKinds = $this->getOptionKinds( $user, $context );
223  $resetKinds = array_intersect( $resetKinds, $this->listOptionKinds() );
224  $newOptions = [];
225 
226  // Use default values for the options that should be deleted, and
227  // copy old values for the ones that shouldn't.
228  foreach ( $oldOptions as $key => $value ) {
229  if ( in_array( $optionKinds[$key], $resetKinds ) ) {
230  if ( array_key_exists( $key, $defaultOptions ) ) {
231  $newOptions[$key] = $defaultOptions[$key];
232  }
233  } else {
234  $newOptions[$key] = $value;
235  }
236  }
237  }
238 
239  // TODO: Deprecate passing full user to the hook
240  $this->hookRunner->onUserResetAllOptions(
241  User::newFromIdentity( $user ), $newOptions, $oldOptions, $resetKinds
242  );
243 
244  $this->optionsCache[$this->getCacheKey( $user )] = $newOptions;
245  }
246 
270  public function listOptionKinds(): array {
271  return [
272  'registered',
273  'registered-multiselect',
274  'registered-checkmatrix',
275  'userjs',
276  'special',
277  'unused'
278  ];
279  }
280 
294  public function getOptionKinds(
295  UserIdentity $userIdentity,
297  $options = null
298  ): array {
299  if ( $options === null ) {
300  $options = $this->loadUserOptions( $userIdentity );
301  }
302 
303  // TODO: injecting the preferences factory creates a cyclic dependency between
304  // PreferencesFactory and UserOptionsManager. See T250822
305  $preferencesFactory = MediaWikiServices::getInstance()->getPreferencesFactory();
306  $user = User::newFromIdentity( $userIdentity );
307  $prefs = $preferencesFactory->getFormDescriptor( $user, $context );
308  $mapping = [];
309 
310  // Pull out the "special" options, so they don't get converted as
311  // multiselect or checkmatrix.
312  $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(), true );
313  foreach ( $specialOptions as $name => $value ) {
314  unset( $prefs[$name] );
315  }
316 
317  // Multiselect and checkmatrix options are stored in the database with
318  // one key per option, each having a boolean value. Extract those keys.
319  $multiselectOptions = [];
320  foreach ( $prefs as $name => $info ) {
321  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
322  ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class )
323  ) {
324  $opts = HTMLFormField::flattenOptions( $info['options'] );
325  $prefix = $info['prefix'] ?? $name;
326 
327  foreach ( $opts as $value ) {
328  $multiselectOptions["$prefix$value"] = true;
329  }
330 
331  unset( $prefs[$name] );
332  }
333  }
334  $checkmatrixOptions = [];
335  foreach ( $prefs as $name => $info ) {
336  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
337  ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class )
338  ) {
339  $columns = HTMLFormField::flattenOptions( $info['columns'] );
340  $rows = HTMLFormField::flattenOptions( $info['rows'] );
341  $prefix = $info['prefix'] ?? $name;
342 
343  foreach ( $columns as $column ) {
344  foreach ( $rows as $row ) {
345  $checkmatrixOptions["$prefix$column-$row"] = true;
346  }
347  }
348 
349  unset( $prefs[$name] );
350  }
351  }
352 
353  // $value is ignored
354  foreach ( $options as $key => $value ) {
355  if ( isset( $prefs[$key] ) ) {
356  $mapping[$key] = 'registered';
357  } elseif ( isset( $multiselectOptions[$key] ) ) {
358  $mapping[$key] = 'registered-multiselect';
359  } elseif ( isset( $checkmatrixOptions[$key] ) ) {
360  $mapping[$key] = 'registered-checkmatrix';
361  } elseif ( isset( $specialOptions[$key] ) ) {
362  $mapping[$key] = 'special';
363  } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) {
364  $mapping[$key] = 'userjs';
365  } else {
366  $mapping[$key] = 'unused';
367  }
368  }
369 
370  return $mapping;
371  }
372 
380  public function saveOptions( UserIdentity $user ) {
381  if ( !$user->isRegistered() ) {
382  throw new InvalidArgumentException( __METHOD__ . ' was called on anon user' );
383  }
384 
385  $userKey = $this->getCacheKey( $user );
386  // Not using getOptions(), to keep hidden preferences in database
387  $saveOptions = $this->loadUserOptions( $user, self::READ_LATEST );
388  $originalOptions = $this->originalOptionsCache[$userKey] ?? [];
389 
390  // Allow hooks to abort, for instance to save to a global profile.
391  // Reset options to default state before saving.
392  // TODO: Deprecate passing User to the hook.
393  if ( !$this->hookRunner->onUserSaveOptions(
394  User::newFromIdentity( $user ), $saveOptions, $originalOptions )
395  ) {
396  return;
397  }
398  // In case options were modified by the hook
399  $this->optionsCache[$userKey] = $saveOptions;
400 
401  $userId = $user->getId();
402  $insert_rows = []; // all the new preference rows
403  foreach ( $saveOptions as $key => $value ) {
404  // Don't bother storing default values
405  $defaultOption = $this->defaultOptionsLookup->getDefaultOption( $key );
406  if ( ( $defaultOption === null && $value !== false && $value !== null )
407  || $value != $defaultOption
408  ) {
409  $insert_rows[] = [
410  'up_user' => $userId,
411  'up_property' => $key,
412  'up_value' => $value,
413  ];
414  }
415  }
416 
417  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
418 
419  $res = $dbw->select(
420  'user_properties',
421  [ 'up_property', 'up_value' ],
422  [ 'up_user' => $userId ],
423  __METHOD__
424  );
425 
426  // Find prior rows that need to be removed or updated. These rows will
427  // all be deleted (the latter so that INSERT IGNORE applies the new values).
428  $keysDelete = [];
429  foreach ( $res as $row ) {
430  if ( !isset( $saveOptions[$row->up_property] ) ||
431  $saveOptions[$row->up_property] !== $row->up_value
432  ) {
433  $keysDelete[] = $row->up_property;
434  }
435  }
436 
437  if ( !count( $keysDelete ) && !count( $insert_rows ) ) {
438  return;
439  }
440  $this->originalOptionsCache[$userKey] = null;
441 
442  if ( count( $keysDelete ) ) {
443  // Do the DELETE by PRIMARY KEY for prior rows.
444  // In the past a very large portion of calls to this function are for setting
445  // 'rememberpassword' for new accounts (a preference that has since been removed).
446  // Doing a blanket per-user DELETE for new accounts with no rows in the table
447  // caused gap locks on [max user ID,+infinity) which caused high contention since
448  // updates would pile up on each other as they are for higher (newer) user IDs.
449  // It might not be necessary these days, but it shouldn't hurt either.
450  $dbw->delete(
451  'user_properties',
452  [
453  'up_user' => $userId,
454  'up_property' => $keysDelete
455  ],
456  __METHOD__
457  );
458  }
459  // Insert the new preference rows
460  $dbw->insert(
461  'user_properties',
462  $insert_rows,
463  __METHOD__,
464  [ 'IGNORE' ]
465  );
466  }
467 
482  public function loadUserOptions(
483  UserIdentity $user,
484  int $queryFlags = self::READ_NORMAL,
485  array $data = null
486  ): array {
487  $userKey = $this->getCacheKey( $user );
488  if ( $this->canUseCachedValues( $user, $queryFlags )
489  && isset( $this->optionsCache[$userKey] )
490  ) {
491  return $this->optionsCache[$userKey];
492  }
493 
494  $options = $this->defaultOptionsLookup->getDefaultOptions();
495 
496  if ( !$user->isRegistered() ) {
497  // For unlogged-in users, load language/variant options from request.
498  // There's no need to do it for logged-in users: they can set preferences,
499  // and handling of page content is done by $pageLang->getPreferredVariant() and such,
500  // so don't override user's choice (especially when the user chooses site default).
501  $variant = $this->languageConverterFactory->getLanguageConverter()->getDefaultVariant();
502  $options['variant'] = $variant;
503  $options['language'] = $variant;
504  $this->optionsCache[$userKey] = $options;
505  return $options;
506  }
507 
508  // In case options were already loaded from the database before and no options
509  // changes were saved to the database, we can use the cached original options.
510  if ( $this->canUseCachedValues( $user, $queryFlags )
511  && isset( $this->originalOptionsCache[$userKey] )
512  ) {
513  $this->logger->debug( 'Loading options from override cache', [
514  'user_id' => $user->getId()
515  ] );
516  return $this->originalOptionsCache[$userKey];
517  } else {
518  if ( !is_array( $data ) ) {
519  $this->logger->debug( 'Loading options from database', [
520  'user_id' => $user->getId()
521  ] );
522 
523  $dbr = $this->getDBForQueryFlags( $queryFlags );
524  $res = $dbr->select(
525  'user_properties',
526  [ 'up_property', 'up_value' ],
527  [ 'up_user' => $user->getId() ],
528  __METHOD__
529  );
530 
531  $data = [];
532  foreach ( $res as $row ) {
533  // Convert '0' to 0. PHP's boolean conversion considers them both
534  // false, but e.g. JavaScript considers the former as true.
535  // @todo: T54542 Somehow determine the desired type (string/int/bool)
536  // and convert all values here.
537  if ( $row->up_value === '0' ) {
538  $row->up_value = 0;
539  }
540  $data[$row->up_property] = $row->up_value;
541  }
542  }
543 
544  foreach ( $data as $property => $value ) {
545  $options[$property] = $value;
546  }
547  }
548 
549  // Replace deprecated language codes
550  $options['language'] = LanguageCode::replaceDeprecatedCodes( $options['language'] );
551  // Need to store what we have so far before the hook to prevent
552  // infinite recursion if the hook attempts to reload options
553  $this->originalOptionsCache[$userKey] = $options;
554  $this->queryFlagsUsedForCaching[$userKey] = $queryFlags;
555  // TODO: Deprecate passing full User object into the hook.
556  $this->hookRunner->onUserLoadOptions(
557  User::newFromIdentity( $user ), $options
558  );
559 
560  $this->originalOptionsCache[$userKey] = $options;
561  $this->optionsCache[$userKey] = $options;
562 
563  return $this->optionsCache[$userKey];
564  }
565 
571  public function clearUserOptionsCache( UserIdentity $user ) {
572  $cacheKey = $this->getCacheKey( $user );
573  $this->optionsCache[$cacheKey] = null;
574  $this->originalOptionsCache[$cacheKey] = null;
575  $this->queryFlagsUsedForCaching[$cacheKey] = null;
576  }
577 
583  private function getCacheKey( UserIdentity $user ): string {
584  return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
585  }
586 
591  private function getDBForQueryFlags( $queryFlags ): IDatabase {
592  list( $mode, ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
593  return $this->loadBalancer->getConnectionRef( $mode, [] );
594  }
595 
602  private function canUseCachedValues( UserIdentity $user, int $queryFlags ) : bool {
603  if ( !$user->isRegistered() ) {
604  // Anon users don't have options stored in the database,
605  // so $queryFlags are ignored.
606  return true;
607  }
608  if ( $queryFlags >= self::READ_LOCKING ) {
609  return false;
610  }
611  $userKey = $this->getCacheKey( $user );
612  $queryFlagsUsed = $this->queryFlagsUsedForCaching[$userKey] ?? self::READ_NONE;
613  return $queryFlagsUsed >= $queryFlags;
614  }
615 }
MediaWiki\User\UserOptionsManager\setOption
setOption(UserIdentity $user, string $oname, $val)
Set the given option for a user.
Definition: UserOptionsManager.php:180
LanguageCode\replaceDeprecatedCodes
static replaceDeprecatedCodes( $code)
Replace deprecated language codes that were used in previous versions of MediaWiki to up-to-date,...
Definition: LanguageCode.php:161
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
MediaWiki\User\UserOptionsManager\saveOptions
saveOptions(UserIdentity $user)
Saves the non-default options for this user, as previously set e.g.
Definition: UserOptionsManager.php:380
MediaWiki\User\UserOptionsManager\getDefaultOptions
getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: UserOptionsManager.php:105
MediaWiki\User\UserOptionsManager\$hookRunner
HookRunner $hookRunner
Definition: UserOptionsManager.php:72
MediaWiki\User\UserOptionsManager\getOptions
getOptions(UserIdentity $user, int $flags=0, int $queryFlags=self::READ_NORMAL)
Get all user's options.The user to get the option for Bitwise combination of: UserOptionsManager::EXC...
Definition: UserOptionsManager.php:145
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:595
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
$res
$res
Definition: testCompression.php:57
MediaWiki\Languages\LanguageConverterFactory
An interface for creating language converters.
Definition: LanguageConverterFactory.php:44
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
MediaWiki\User\UserOptionsManager\listOptionKinds
listOptionKinds()
Return a list of the types of user options currently returned by UserOptionsManager::getOptionKinds()...
Definition: UserOptionsManager.php:270
MediaWiki\User\UserOptionsManager\getOption
getOption(UserIdentity $user, string $oname, $defaultOverride=null, bool $ignoreHidden=false, int $queryFlags=self::READ_NORMAL)
Get the user's current setting for a given option.The user to get the option for The option to check ...
Definition: UserOptionsManager.php:119
$dbr
$dbr
Definition: testCompression.php:54
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:185
MediaWiki\User\DefaultOptionsLookup
A service class to control default user options.
Definition: DefaultOptionsLookup.php:35
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:25
MediaWiki\User\UserOptionsManager\$loadBalancer
ILoadBalancer $loadBalancer
Definition: UserOptionsManager.php:60
MediaWiki\User\UserIdentity\isRegistered
isRegistered()
MediaWiki\User\UserOptionsManager\getDBForQueryFlags
getDBForQueryFlags( $queryFlags)
Definition: UserOptionsManager.php:591
MediaWiki\User\UserOptionsManager\getCacheKey
getCacheKey(UserIdentity $user)
Gets a unique key for various caches.
Definition: UserOptionsManager.php:583
MediaWiki\User\UserOptionsManager\$originalOptionsCache
array $originalOptionsCache
Cached original user options fetched from database.
Definition: UserOptionsManager.php:69
HTMLFormField
The parent class to generate form fields.
Definition: HTMLFormField.php:9
MediaWiki\User\UserOptionsManager\clearUserOptionsCache
clearUserOptionsCache(UserIdentity $user)
Clears cached user options.
Definition: UserOptionsManager.php:571
MediaWiki\User\UserOptionsManager\$defaultOptionsLookup
DefaultOptionsLookup $defaultOptionsLookup
Definition: UserOptionsManager.php:54
MediaWiki\User\UserOptionsManager\getOptionKinds
getOptionKinds(UserIdentity $userIdentity, IContextSource $context, $options=null)
Return an associative array mapping preferences keys to the kind of a preference they're used for.
Definition: UserOptionsManager.php:294
DB_MASTER
const DB_MASTER
Definition: defines.php:26
IDBAccessObject\READ_NONE
const READ_NONE
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:75
MediaWiki\User\UserOptionsManager\getDefaultOption
getDefaultOption(string $opt)
Get a given default option value.Name of option to retrieve string|null Default option value
Definition: UserOptionsManager.php:112
DBAccessObjectUtils
Helper class for DAO classes.
Definition: DBAccessObjectUtils.php:29
MediaWiki\User\UserOptionsManager\$queryFlagsUsedForCaching
array $queryFlagsUsedForCaching
Query flags used to retrieve options from database.
Definition: UserOptionsManager.php:75
MediaWiki\User\UserOptionsManager\loadUserOptions
loadUserOptions(UserIdentity $user, int $queryFlags=self::READ_NORMAL, array $data=null)
Loads user options either from cache or from the database.
Definition: UserOptionsManager.php:482
MediaWiki\User\UserOptionsManager\resetOptions
resetOptions(UserIdentity $user, IContextSource $context, $resetKinds=[ 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused'])
Reset certain (or all) options to the site defaults.
Definition: UserOptionsManager.php:207
MediaWiki\User\UserOptionsManager\$logger
LoggerInterface $logger
Definition: UserOptionsManager.php:63
MediaWiki\User
Definition: DefaultOptionsLookup.php:21
MediaWiki\User\UserOptionsLookup
Provides access to user options.
Definition: UserOptionsLookup.php:29
MediaWiki\User\UserOptionsManager\$serviceOptions
ServiceOptions $serviceOptions
Definition: UserOptionsManager.php:51
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
MediaWiki\User\UserOptionsManager
A service class to control user options.
Definition: UserOptionsManager.php:44
MediaWiki\User\UserIdentity\getId
getId()
HTMLCheckMatrix
A checkbox matrix Operates similarly to HTMLMultiSelectField, but instead of using an array of option...
Definition: HTMLCheckMatrix.php:27
MediaWiki\User\UserOptionsManager\$languageConverterFactory
LanguageConverterFactory $languageConverterFactory
Definition: UserOptionsManager.php:57
MediaWiki\User\UserOptionsManager\$optionsCache
array $optionsCache
Cached options by user.
Definition: UserOptionsManager.php:66
HTMLMultiSelectField
Multi-select field.
Definition: HTMLMultiSelectField.php:8
MediaWiki\User\UserOptionsManager\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: UserOptionsManager.php:46
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:44
LanguageCode
Methods for dealing with language codes.
Definition: LanguageCode.php:27
HTMLFormField\flattenOptions
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Definition: HTMLFormField.php:1135
MediaWiki\User\UserOptionsManager\canUseCachedValues
canUseCachedValues(UserIdentity $user, int $queryFlags)
Determines if it's ok to use cached options values for a given user and query flags.
Definition: UserOptionsManager.php:602
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:570
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:40
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:62
MediaWiki\User\UserOptionsManager\__construct
__construct(ServiceOptions $options, DefaultOptionsLookup $defaultOptionsLookup, LanguageConverterFactory $languageConverterFactory, ILoadBalancer $loadBalancer, LoggerInterface $logger, HookContainer $hookContainer)
Definition: UserOptionsManager.php:85