28 use InvalidArgumentException;
35 use Psr\Log\LoggerInterface;
97 $this->serviceOptions = $options;
102 $this->hookRunner =
new HookRunner( $hookContainer );
109 return $this->defaultOptionsLookup->getDefaultOptions();
116 return $this->defaultOptionsLookup->getDefaultOption( $opt );
125 $defaultOverride =
null,
126 bool $ignoreHidden =
false,
127 int $queryFlags = self::READ_NORMAL
129 # We want 'disabled' preferences to always behave as the default value for
130 # users, even if they have set the option explicitly in their settings (ie they
131 # set it, and then it was disabled removing their ability to change it). But
132 # we don't want to erase the preferences in the database in case the preference
133 # is re-enabled again. So don't touch $mOptions, just override the returned value
134 if ( !$ignoreHidden && in_array( $oname, $this->serviceOptions->get(
'HiddenPrefs' ) ) ) {
135 return $this->defaultOptionsLookup->getDefaultOption( $oname );
139 if ( array_key_exists( $oname, $options ) ) {
140 return $options[$oname];
142 return $defaultOverride;
151 int $queryFlags = self::READ_NORMAL
155 # We want 'disabled' preferences to always behave as the default value for
156 # users, even if they have set the option explicitly in their settings (ie they
157 # set it, and then it was disabled removing their ability to change it). But
158 # we don't want to erase the preferences in the database in case the preference
159 # is re-enabled again. So don't touch $mOptions, just override the returned value
160 foreach ( $this->serviceOptions->get(
'HiddenPrefs' ) as $pref ) {
161 $default = $this->defaultOptionsLookup->getDefaultOption( $pref );
162 if ( $default !==
null ) {
163 $options[$pref] = $default;
167 if ( $flags & self::EXCLUDE_DEFAULTS ) {
168 $options = array_diff_assoc( $options, $this->defaultOptionsLookup->getDefaultOptions() );
190 if ( $val ===
null ) {
191 $val = $this->defaultOptionsLookup->getDefaultOption( $oname );
195 $this->optionsCache[$userKey][$oname] = $val;
213 $resetKinds = [
'registered',
'registered-multiselect',
'registered-checkmatrix',
'unused' ]
216 $defaultOptions = $this->defaultOptionsLookup->getDefaultOptions();
218 if ( !is_array( $resetKinds ) ) {
219 $resetKinds = [ $resetKinds ];
222 if ( in_array(
'all', $resetKinds ) ) {
223 $newOptions = $defaultOptions;
226 $resetKinds = array_intersect( $resetKinds, $this->
listOptionKinds() );
231 foreach ( $oldOptions as $key => $value ) {
232 if ( in_array( $optionKinds[$key], $resetKinds ) ) {
233 if ( array_key_exists( $key, $defaultOptions ) ) {
234 $newOptions[$key] = $defaultOptions[$key];
237 $newOptions[$key] = $value;
243 $this->hookRunner->onUserResetAllOptions(
247 $this->optionsCache[$this->
getCacheKey( $user )] = $newOptions;
276 'registered-multiselect',
277 'registered-checkmatrix',
302 if ( $options ===
null ) {
310 $prefs = $preferencesFactory->getFormDescriptor( $user,
$context );
315 $specialOptions = array_fill_keys( $preferencesFactory->getSaveBlacklist(),
true );
316 foreach ( $specialOptions as $name => $value ) {
317 unset( $prefs[$name] );
322 $multiselectOptions = [];
323 foreach ( $prefs as $name => $info ) {
324 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
325 ( isset( $info[
'class'] ) && $info[
'class'] == HTMLMultiSelectField::class )
328 $prefix = $info[
'prefix'] ?? $name;
330 foreach ( $opts as $value ) {
331 $multiselectOptions[
"$prefix$value"] =
true;
334 unset( $prefs[$name] );
337 $checkmatrixOptions = [];
338 foreach ( $prefs as $name => $info ) {
339 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
340 ( isset( $info[
'class'] ) && $info[
'class'] == HTMLCheckMatrix::class )
344 $prefix = $info[
'prefix'] ?? $name;
346 foreach ( $columns as $column ) {
347 foreach ( $rows as $row ) {
348 $checkmatrixOptions[
"$prefix$column-$row"] =
true;
352 unset( $prefs[$name] );
357 foreach ( $options as $key => $value ) {
358 if ( isset( $prefs[$key] ) ) {
359 $mapping[$key] =
'registered';
360 } elseif ( isset( $multiselectOptions[$key] ) ) {
361 $mapping[$key] =
'registered-multiselect';
362 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
363 $mapping[$key] =
'registered-checkmatrix';
364 } elseif ( isset( $specialOptions[$key] ) ) {
365 $mapping[$key] =
'special';
366 } elseif ( substr( $key, 0, 7 ) ===
'userjs-' ) {
367 $mapping[$key] =
'userjs';
369 $mapping[$key] =
'unused';
385 throw new InvalidArgumentException( __METHOD__ .
' was called on anon user' );
391 $originalOptions = $this->originalOptionsCache[$userKey] ?? [];
396 if ( !$this->hookRunner->onUserSaveOptions(
402 $this->optionsCache[$userKey] = $saveOptions;
404 $userId = $user->
getId();
406 foreach ( $saveOptions as $key => $value ) {
408 $defaultOption = $this->defaultOptionsLookup->getDefaultOption( $key );
409 if ( ( $defaultOption ===
null && $value !==
false && $value !==
null )
410 || $value != $defaultOption
413 'up_user' => $userId,
414 'up_property' => $key,
415 'up_value' => $value,
420 $dbw = $this->loadBalancer->getConnectionRef(
DB_MASTER );
424 [
'up_property',
'up_value' ],
425 [
'up_user' => $userId ],
432 foreach (
$res as $row ) {
433 if ( !isset( $saveOptions[$row->up_property] ) ||
434 $saveOptions[$row->up_property] !== $row->up_value
436 $keysDelete[] = $row->up_property;
440 if ( !count( $keysDelete ) && !count( $insert_rows ) ) {
443 $this->originalOptionsCache[$userKey] =
null;
445 if ( count( $keysDelete ) ) {
456 'up_user' => $userId,
457 'up_property' => $keysDelete
487 int $queryFlags = self::READ_NORMAL,
492 && isset( $this->optionsCache[$userKey] )
494 return $this->optionsCache[$userKey];
497 $options = $this->defaultOptionsLookup->getDefaultOptions();
504 $variant = $this->languageConverterFactory->getLanguageConverter()->getDefaultVariant();
505 $options[
'variant'] = $variant;
506 $options[
'language'] = $variant;
507 $this->optionsCache[$userKey] = $options;
514 && isset( $this->originalOptionsCache[$userKey] )
516 $this->logger->debug(
'Loading options from override cache', [
517 'user_id' => $user->
getId()
519 return $this->originalOptionsCache[$userKey];
521 if ( !is_array( $data ) ) {
522 $this->logger->debug(
'Loading options from database', [
523 'user_id' => $user->
getId()
529 [
'up_property',
'up_value' ],
530 [
'up_user' => $user->
getId() ],
535 foreach (
$res as $row ) {
540 if ( $row->up_value ===
'0' ) {
543 $data[$row->up_property] = $row->up_value;
547 foreach ( $data as $property => $value ) {
548 $options[$property] = $value;
556 $this->originalOptionsCache[$userKey] = $options;
557 $this->queryFlagsUsedForCaching[$userKey] = $queryFlags;
559 $this->hookRunner->onUserLoadOptions(
563 $this->originalOptionsCache[$userKey] = $options;
564 $this->optionsCache[$userKey] = $options;
566 return $this->optionsCache[$userKey];
576 $this->optionsCache[$cacheKey] =
null;
577 $this->originalOptionsCache[$cacheKey] =
null;
578 $this->queryFlagsUsedForCaching[$cacheKey] =
null;
587 return $user->
isRegistered() ?
"u:{$user->getId()}" :
'anon';
596 return $this->loadBalancer->getConnectionRef( $mode, [] );
611 if ( $queryFlags >= self::READ_LOCKING ) {
615 $queryFlagsUsed = $this->queryFlagsUsedForCaching[$userKey] ??
self::READ_NONE;
616 return $queryFlagsUsed >= $queryFlags;