56use Psr\Log\LoggerAwareTrait;
57use Psr\Log\NullLogger;
62use UnexpectedValueException;
119 'AllowRequiringEmailForResets',
124 'EmailAuthentication',
125 'EmailConfirmToEdit',
128 'EnableUserEmailMuteList',
130 'EnotifRevealEditorAddress',
140 'RCShowWatchingUsers',
141 'RCWatchCategoryMembership',
142 'SearchMatchRedirectPreference',
145 'SignatureValidation',
194 $this->logger =
new NullLogger();
197 $this->hookRunner =
new HookRunner( $hookContainer );
201 $services =
static function () {
207 : $services()->getUserOptionsManager();
209 $this->parser =
$parser ?? $services()->getParser();
210 $this->skinFactory =
$skinFactory ?? $services()->getSkinFactory();
211 $this->userGroupManager =
$userGroupManager ?? $services()->getUserGroupManager();
233 OutputPage::setupOOUI(
234 strtolower( $context->
getSkin()->getSkinName() ),
248 $this->hookRunner->onGetPreferences( $user, $preferences );
251 $this->logger->debug(
"Created form descriptor for user '{$user->getName()}'" );
265 foreach ( $this->options->get(
'HiddenPrefs' ) as $pref ) {
266 unset( $defaultPreferences[$pref] );
270 $dummyForm =
new HTMLForm( [], $context );
272 $disable = !$user->
isAllowed(
'editmyoptions' );
274 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
275 $userOptions = $this->userOptionsManager->getOptions( $user );
276 $this->
applyFilters( $userOptions, $defaultPreferences,
'filterForForm' );
278 foreach ( $defaultPreferences as $name => &$info ) {
281 $info[
'disabled'] =
'disabled';
283 $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm );
284 $globalDefault = $defaultOptions[$name] ??
null;
287 if ( isset( $info[
'default'] ) ) {
291 if ( $prefFromUser !==
null &&
292 $field->validate( $prefFromUser, $this->userOptionsManager->getOptions( $user ) ) ===
true ) {
293 $info[
'default'] = $prefFromUser;
294 } elseif ( $field->validate( $globalDefault, $this->userOptionsManager->getOptions( $user ) ) ===
true ) {
295 $info[
'default'] = $globalDefault;
297 $globalDefault = json_encode( $globalDefault );
299 "Default '$globalDefault' is invalid for preference $name of user " . $user->
getName()
304 return $defaultPreferences;
316 $val = $userOptions[$name] ??
null;
319 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
320 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLMultiSelectField::class ) ) {
321 $options = HTMLFormField::flattenOptions( $info[
'options-messages'] ?? $info[
'options'] );
322 $prefix = $info[
'prefix'] ?? $name;
326 if ( $userOptions[
"$prefix$value"] ??
false ) {
333 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
334 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLCheckMatrix::class ) ) {
335 $columns = HTMLFormField::flattenOptions( $info[
'columns'] );
336 $rows = HTMLFormField::flattenOptions( $info[
'rows'] );
337 $prefix = $info[
'prefix'] ?? $name;
340 foreach ( $columns as $column ) {
341 foreach ( $rows as $row ) {
342 if ( $userOptions[
"$prefix$column-$row"] ??
false ) {
343 $val[] =
"$column-$row";
366 $defaultPreferences[
'username'] = [
368 'label-message' => [
'username', $userName ],
369 'default' => $userName,
370 'section' =>
'personal/info',
376 $userEffectiveGroups = array_diff(
377 $this->userGroupManager->getUserEffectiveGroups( $user ),
380 $defaultPreferences[
'usergroups'] = [
382 'label-message' => [
'prefs-memberingroups',
383 \Message::numParam( count( $userEffectiveGroups ) ), $userName ],
384 'default' =>
function () use ( $user, $userEffectiveGroups, $context,
$lang, $userName ) {
385 $userGroupMemberships = $this->userGroupManager->getUserGroupMemberships( $user );
386 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
387 foreach ( $userEffectiveGroups as $ueg ) {
388 $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
390 $userG = UserGroupMembership::getLink( $groupStringOrObject, $context,
'html' );
391 $userM = UserGroupMembership::getLink( $groupStringOrObject, $context,
'html',
401 $userTempGroups[] = $userG;
402 $userTempMembers[] = $userM;
404 $userGroups[] = $userG;
405 $userMembers[] = $userM;
409 sort( $userMembers );
410 sort( $userTempGroups );
411 sort( $userTempMembers );
412 $userGroups = array_merge( $userTempGroups, $userGroups );
413 $userMembers = array_merge( $userTempMembers, $userMembers );
414 return $context->
msg(
'prefs-memberingroups-type' )
415 ->rawParams(
$lang->commaList( $userGroups ),
$lang->commaList( $userMembers ) )
419 'section' =>
'personal/info',
424 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
426 $defaultPreferences[
'editcount'] = [
429 'label-message' =>
'prefs-edits',
430 'default' => $editCount,
431 'section' =>
'personal/info',
435 $displayUser = $context->
getUser();
437 $defaultPreferences[
'registrationdate'] = [
439 'label-message' =>
'prefs-registration',
440 'default' => $context->
msg(
441 'prefs-registration-date-time',
442 $lang->userTimeAndDate( $userRegistration, $displayUser ),
443 $lang->userDate( $userRegistration, $displayUser ),
444 $lang->userTime( $userRegistration, $displayUser )
446 'section' =>
'personal/info',
450 $canViewPrivateInfo = $user->
isAllowed(
'viewmyprivateinfo' );
451 $canEditPrivateInfo = $user->
isAllowed(
'editmyprivateinfo' );
454 $defaultPreferences[
'realname'] = [
456 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'realname' )
459 'section' =>
'personal/info',
460 'label-message' =>
'yourrealname',
461 'help-message' =>
'prefs-help-realname',
464 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
467 $defaultPreferences[
'password'] = [
470 'default' => (string)
new \OOUI\ButtonWidget( [
474 'label' => $context->
msg(
'prefs-resetpass' )->text(),
476 'label-message' =>
'yourpassword',
478 'help' => $this->options->get(
'AllowRequiringEmailForResets' ) && $user->
getEmail()
479 ? $context->
msg(
'prefs-help-yourpassword',
480 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
482 'section' =>
'personal/info',
486 if ( !$this->options->get(
'ForceHTTPS' )
487 && $this->options->get(
'SecureLogin' )
489 $defaultPreferences[
'prefershttps'] = [
491 'label-message' =>
'tog-prefershttps',
492 'help-message' =>
'prefs-help-prefershttps',
493 'section' =>
'personal/info'
497 $defaultPreferences[
'downloaduserdata'] = [
500 'label-message' =>
'prefs-user-downloaddata-label',
501 'default' => HTML::Element(
504 'href' => $this->options->get(
'ScriptPath' ) .
505 '/api.php?action=query&meta=userinfo&uiprop=*',
507 $context->
msg(
'prefs-user-downloaddata-info' )->text()
509 'help-message' => [
'prefs-user-downloaddata-help-message', urlencode( $user->
getTitleKey() ) ],
510 'section' =>
'personal/info',
513 $languages = $this->languageNameUtils->getLanguageNames(
null,
'mwfile' );
514 $languageCode = $this->options->get(
'LanguageCode' );
515 if ( !array_key_exists( $languageCode, $languages ) ) {
516 $languages[$languageCode] = $languageCode;
522 foreach ( $languages as $code => $name ) {
523 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
526 $defaultPreferences[
'language'] = [
528 'section' =>
'personal/i18n',
530 'label-message' =>
'yourlanguage',
533 $neutralGenderMessage = $context->
msg(
'gender-notknown' )->escaped() . (
534 !$context->
msg(
'gender-unknown' )->isDisabled()
535 ?
"<br>" . $context->
msg(
'parentheses' )
536 ->params( $context->
msg(
'gender-unknown' )->plain() )
541 $defaultPreferences[
'gender'] = [
543 'section' =>
'personal/i18n',
545 $neutralGenderMessage =>
'unknown',
546 $context->
msg(
'gender-female' )->escaped() =>
'female',
547 $context->
msg(
'gender-male' )->escaped() =>
'male',
549 'label-message' =>
'yourgender',
550 'help-message' =>
'prefs-help-gender',
554 if ( !$this->languageConverterFactory->isConversionDisabled() ) {
556 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
557 if ( $langCode == $this->contLang->getCode() ) {
558 if ( !$this->languageConverter->hasVariants() ) {
562 $variants = $this->languageConverter->getVariants();
564 foreach ( $variants as $v ) {
565 $v = str_replace(
'_',
'-', strtolower( $v ) );
566 $variantArray[$v] =
$lang->getVariantname( $v,
false );
570 foreach ( $variantArray as $code => $name ) {
571 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
575 $defaultPreferences[
'variant'] = [
576 'label-message' =>
'yourvariant',
579 'section' =>
'personal/i18n',
580 'help-message' =>
'prefs-help-variant',
583 $defaultPreferences[
"variant-$langCode"] = [
591 $oldsigWikiText = $this->parser->preSaveTransform(
595 ParserOptions::newFromContext( $context )
598 $context->
getOutput()->parseAsContent( $oldsigWikiText )
600 $signatureFieldConfig = [];
602 $signature = $this->userOptionsManager->getOption( $user,
'nickname' );
603 $useFancySig = $this->userOptionsManager->getBoolOption( $user,
'fancysig' );
604 if ( $useFancySig && $signature !==
'' ) {
608 ParserOptions::newFromContext( $context )
610 $signatureErrors = $validator->validateSignature( $signature );
611 if ( $signatureErrors ) {
612 $sigValidation = $this->options->get(
'SignatureValidation' );
613 $oldsigHTML .=
'<p><strong>' .
618 $context->
msg(
"prefs-signature-invalid-$sigValidation" )->parse() .
623 foreach ( $signatureErrors as &$sigError ) {
624 $sigError = new \OOUI\HtmlSnippet( $sigError );
627 $signatureFieldConfig = [
628 'warnings' => $sigValidation !==
'disallow' ? $signatureErrors :
null,
629 'errors' => $sigValidation ===
'disallow' ? $signatureErrors :
null,
634 $defaultPreferences[
'oldsig'] = [
639 'default' => new \OOUI\FieldLayout(
640 new \OOUI\LabelWidget( [
641 'label' =>
new \OOUI\HtmlSnippet( $oldsigHTML ),
645 'label' =>
new \OOUI\HtmlSnippet( $context->
msg(
'tog-oldsig' )->parse() )
646 ] + $signatureFieldConfig
648 'section' =>
'personal/signature',
650 $defaultPreferences[
'nickname'] = [
651 'type' => $this->authManager->allowsPropertyChange(
'nickname' ) ?
'text' :
'info',
652 'maxlength' => $this->options->get(
'MaxSigChars' ),
653 'label-message' =>
'yournick',
654 'validation-callback' =>
function ( $signature, $alldata,
HTMLForm $form ) {
657 'section' =>
'personal/signature',
658 'filter-callback' =>
function ( $signature, array $alldata,
HTMLForm $form ) {
662 $defaultPreferences[
'fancysig'] = [
664 'label-message' =>
'tog-fancysig',
666 'help-message' =>
'prefs-help-signature',
667 'section' =>
'personal/signature'
671 if ( $this->options->get(
'EnableEmail' ) ) {
672 if ( $canViewPrivateInfo ) {
674 $helpMessages[] = $this->options->get(
'EmailConfirmToEdit' )
675 ?
'prefs-help-email-required'
676 :
'prefs-help-email';
678 if ( $this->options->get(
'EnableUserEmail' ) ) {
680 $helpMessages[] =
'prefs-help-email-others';
683 $emailAddress = $user->
getEmail() ? htmlspecialchars( $user->
getEmail() ) :
'';
684 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'emailaddress' ) ) {
685 $button = new \OOUI\ButtonWidget( [
690 $context->
msg( $user->
getEmail() ?
'prefs-changeemail' :
'prefs-setemail' )->text(),
693 $emailAddress .= $emailAddress ==
'' ? $button : (
'<br />' . $button );
696 $defaultPreferences[
'emailaddress'] = [
699 'default' => $emailAddress,
700 'label-message' =>
'youremail',
701 'section' =>
'personal/email',
702 'help-messages' => $helpMessages,
707 $disableEmailPrefs =
false;
709 if ( $this->options->get(
'AllowRequiringEmailForResets' ) ) {
710 $defaultPreferences[
'requireemail'] = [
712 'label-message' =>
'tog-requireemail',
713 'help-message' =>
'prefs-help-requireemail',
714 'section' =>
'personal/email',
715 'disabled' => $user->
getEmail() ? false :
true,
719 if ( $this->options->get(
'EmailAuthentication' ) ) {
725 $displayUser = $context->
getUser();
727 $time =
$lang->userTimeAndDate( $emailTimestamp, $displayUser );
728 $d =
$lang->userDate( $emailTimestamp, $displayUser );
729 $t =
$lang->userTime( $emailTimestamp, $displayUser );
730 $emailauthenticated = $context->
msg(
'emailauthenticated',
731 $time, $d,
$t )->parse() .
'<br />';
732 $emailauthenticationclass =
'mw-email-authenticated';
734 $disableEmailPrefs =
true;
735 $emailauthenticated = $context->
msg(
'emailnotauthenticated' )->parse() .
'<br />' .
736 new \OOUI\ButtonWidget( [
738 'label' => $context->
msg(
'emailconfirmlink' )->text(),
740 $emailauthenticationclass =
"mw-email-not-authenticated";
743 $disableEmailPrefs =
true;
744 $emailauthenticated = $context->
msg(
'noemailprefs' )->escaped();
745 $emailauthenticationclass =
'mw-email-none';
748 if ( $canViewPrivateInfo ) {
749 $defaultPreferences[
'emailauthentication'] = [
752 'section' =>
'personal/email',
753 'label-message' =>
'prefs-emailconfirm-label',
754 'default' => $emailauthenticated,
756 'cssclass' => $emailauthenticationclass,
761 if ( $this->options->get(
'EnableUserEmail' ) &&
764 $defaultPreferences[
'disablemail'] = [
765 'id' =>
'wpAllowEmail',
768 'section' =>
'personal/email',
769 'label-message' =>
'allowemail',
770 'disabled' => $disableEmailPrefs,
773 $defaultPreferences[
'email-allow-new-users'] = [
774 'id' =>
'wpAllowEmailFromNewUsers',
776 'section' =>
'personal/email',
777 'label-message' =>
'email-allow-new-users-label',
778 'disabled' => $disableEmailPrefs,
781 $defaultPreferences[
'ccmeonemails'] = [
783 'section' =>
'personal/email',
784 'label-message' =>
'tog-ccmeonemails',
785 'disabled' => $disableEmailPrefs,
788 if ( $this->options->get(
'EnableUserEmailMuteList' ) ) {
789 $defaultPreferences[
'email-blacklist'] = [
790 'type' =>
'usersmultiselect',
791 'label-message' =>
'email-mutelist-label',
792 'section' =>
'personal/email',
793 'disabled' => $disableEmailPrefs,
794 'filter' => MultiUsernameFilter::class,
799 if ( $this->options->get(
'EnotifWatchlist' ) ) {
800 $defaultPreferences[
'enotifwatchlistpages'] = [
802 'section' =>
'personal/email',
803 'label-message' =>
'tog-enotifwatchlistpages',
804 'disabled' => $disableEmailPrefs,
807 if ( $this->options->get(
'EnotifUserTalk' ) ) {
808 $defaultPreferences[
'enotifusertalkpages'] = [
810 'section' =>
'personal/email',
811 'label-message' =>
'tog-enotifusertalkpages',
812 'disabled' => $disableEmailPrefs,
815 if ( $this->options->get(
'EnotifUserTalk' ) ||
816 $this->options->get(
'EnotifWatchlist' ) ) {
817 if ( $this->options->get(
'EnotifMinorEdits' ) ) {
818 $defaultPreferences[
'enotifminoredits'] = [
820 'section' =>
'personal/email',
821 'label-message' =>
'tog-enotifminoredits',
822 'disabled' => $disableEmailPrefs,
826 if ( $this->options->get(
'EnotifRevealEditorAddress' ) ) {
827 $defaultPreferences[
'enotifrevealaddr'] = [
829 'section' =>
'personal/email',
830 'label-message' =>
'tog-enotifrevealaddr',
831 'disabled' => $disableEmailPrefs,
847 if ( $skinOptions ) {
848 $defaultPreferences[
'skin'] = [
851 'options' => $skinOptions,
852 'section' =>
'rendering/skin',
854 $defaultPreferences[
'skin-responsive'] = [
856 'label-message' =>
'prefs-skin-responsive',
857 'section' =>
'rendering/skin/skin-prefs',
858 'help-message' =>
'prefs-help-skin-responsive',
862 $allowUserCss = $this->options->get(
'AllowUserCss' );
863 $allowUserJs = $this->options->get(
'AllowUserJs' );
867 if ( $allowUserCss || $allowUserJs ) {
871 if ( $allowUserCss ) {
872 $cssPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.css' );
873 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
874 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
877 if ( $allowUserJs ) {
878 $jsPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.js' );
879 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
880 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
883 $defaultPreferences[
'commoncssjs'] = [
886 'default' => $context->
getLanguage()->pipeList( $linkTools ),
887 'label-message' =>
'prefs-common-config',
888 'section' =>
'rendering/skin',
898 $defaultPreferences[
'imagesize'] = [
901 'label-message' =>
'imagemaxsize',
902 'section' =>
'rendering/files',
904 $defaultPreferences[
'thumbsize'] = [
907 'label-message' =>
'thumbsize',
908 'section' =>
'rendering/files',
922 if ( $dateOptions ) {
923 $defaultPreferences[
'date'] = [
925 'options' => $dateOptions,
926 'section' =>
'rendering/dateformat',
933 $nowlocal =
Xml::element(
'span', [
'id' =>
'wpLocalTime' ],
934 $lang->userTime( $now, $user ) );
935 $nowserver =
$lang->userTime( $now, $user,
936 [
'format' =>
false,
'timecorrection' =>
false ] ) .
937 Html::hidden(
'wpServerTime', (
int)substr( $now, 8, 2 ) * 60 + (
int)substr( $now, 10, 2 ) );
939 $defaultPreferences[
'nowserver'] = [
942 'label-message' =>
'servertime',
943 'default' => $nowserver,
944 'section' =>
'rendering/timeoffset',
947 $defaultPreferences[
'nowlocal'] = [
950 'label-message' =>
'localtime',
951 'default' => $nowlocal,
952 'section' =>
'rendering/timeoffset',
956 $tzOffset = $this->userOptionsManager->getOption( $user,
'timecorrection' );
957 $tz = explode(
'|', $tzOffset, 3 );
961 $tzSetting = $tzOffset;
962 if ( count( $tz ) > 1 && $tz[0] ==
'ZoneInfo' &&
963 !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
967 $userTZ =
new DateTimeZone( $tz[2] );
968 $minDiff = floor( $userTZ->getOffset(
new DateTime(
'now' ) ) / 60 );
969 $tzSetting =
"ZoneInfo|$minDiff|{$tz[2]}";
970 }
catch ( Exception $e ) {
975 if ( count( $tz ) > 1 && $tz[0] ==
'Offset' ) {
977 $tzSetting = sprintf(
'%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
980 $defaultPreferences[
'timecorrection'] = [
981 'class' => \HTMLSelectOrOtherField::class,
982 'label-message' =>
'timezonelegend',
983 'options' => $tzOptions,
984 'default' => $tzSetting,
986 'section' =>
'rendering/timeoffset',
987 'id' =>
'wpTimeCorrection',
988 'filter' => TimezoneFilter::class,
989 'placeholder-message' =>
'timezone-useoffset-placeholder',
1001 &$defaultPreferences
1004 $defaultPreferences[
'diffonly'] = [
1006 'section' =>
'rendering/diffs',
1007 'label-message' =>
'tog-diffonly',
1009 $defaultPreferences[
'norollbackdiff'] = [
1011 'section' =>
'rendering/diffs',
1012 'label-message' =>
'tog-norollbackdiff',
1016 if ( $this->options->get(
'AllowUserCssPrefs' ) ) {
1017 $defaultPreferences[
'underline'] = [
1020 $l10n->
msg(
'underline-never' )->text() => 0,
1021 $l10n->
msg(
'underline-always' )->text() => 1,
1022 $l10n->
msg(
'underline-default' )->text() => 2,
1024 'label-message' =>
'tog-underline',
1025 'section' =>
'rendering/advancedrendering',
1029 $defaultPreferences[
'showhiddencats'] = [
1031 'section' =>
'rendering/advancedrendering',
1032 'label-message' =>
'tog-showhiddencats'
1035 $defaultPreferences[
'numberheadings'] = [
1037 'section' =>
'rendering/advancedrendering',
1038 'label-message' =>
'tog-numberheadings',
1042 $defaultPreferences[
'showrollbackconfirmation'] = [
1044 'section' =>
'rendering/advancedrendering',
1045 'label-message' =>
'tog-showrollbackconfirmation',
1056 $defaultPreferences[
'editsectiononrightclick'] = [
1058 'section' =>
'editing/advancedediting',
1059 'label-message' =>
'tog-editsectiononrightclick',
1061 $defaultPreferences[
'editondblclick'] = [
1063 'section' =>
'editing/advancedediting',
1064 'label-message' =>
'tog-editondblclick',
1067 if ( $this->options->get(
'AllowUserCssPrefs' ) ) {
1068 $defaultPreferences[
'editfont'] = [
1070 'section' =>
'editing/editor',
1071 'label-message' =>
'editfont-style',
1073 $l10n->
msg(
'editfont-monospace' )->text() =>
'monospace',
1074 $l10n->
msg(
'editfont-sansserif' )->text() =>
'sans-serif',
1075 $l10n->
msg(
'editfont-serif' )->text() =>
'serif',
1080 if ( $user->
isAllowed(
'minoredit' ) ) {
1081 $defaultPreferences[
'minordefault'] = [
1083 'section' =>
'editing/editor',
1084 'label-message' =>
'tog-minordefault',
1088 $defaultPreferences[
'forceeditsummary'] = [
1090 'section' =>
'editing/editor',
1091 'label-message' =>
'tog-forceeditsummary',
1093 $defaultPreferences[
'useeditwarning'] = [
1095 'section' =>
'editing/editor',
1096 'label-message' =>
'tog-useeditwarning',
1099 $defaultPreferences[
'previewonfirst'] = [
1101 'section' =>
'editing/preview',
1102 'label-message' =>
'tog-previewonfirst',
1104 $defaultPreferences[
'previewontop'] = [
1106 'section' =>
'editing/preview',
1107 'label-message' =>
'tog-previewontop',
1109 $defaultPreferences[
'uselivepreview'] = [
1111 'section' =>
'editing/preview',
1112 'label-message' =>
'tog-uselivepreview',
1122 $rcMaxAge = $this->options->get(
'RCMaxAge' );
1123 $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1124 $defaultPreferences[
'rcdays'] = [
1126 'label-message' =>
'recentchangesdays',
1127 'section' =>
'rc/displayrc',
1132 $defaultPreferences[
'rclimit'] = [
1136 'label-message' =>
'recentchangescount',
1137 'help-message' =>
'prefs-help-recentchangescount',
1138 'section' =>
'rc/displayrc',
1139 'filter' => IntvalFilter::class,
1141 $defaultPreferences[
'usenewrc'] = [
1143 'label-message' =>
'tog-usenewrc',
1144 'section' =>
'rc/advancedrc',
1146 $defaultPreferences[
'hideminor'] = [
1148 'label-message' =>
'tog-hideminor',
1149 'section' =>
'rc/changesrc',
1151 $defaultPreferences[
'pst-cssjs'] = [
1154 $defaultPreferences[
'rcfilters-rc-collapsed'] = [
1157 $defaultPreferences[
'rcfilters-wl-collapsed'] = [
1160 $defaultPreferences[
'rcfilters-saved-queries'] = [
1163 $defaultPreferences[
'rcfilters-wl-saved-queries'] = [
1167 $defaultPreferences[
'rcfilters-limit'] = [
1170 $defaultPreferences[
'rcfilters-saved-queries-versionbackup'] = [
1173 $defaultPreferences[
'rcfilters-wl-saved-queries-versionbackup'] = [
1177 if ( $this->options->get(
'RCWatchCategoryMembership' ) ) {
1178 $defaultPreferences[
'hidecategorization'] = [
1180 'label-message' =>
'tog-hidecategorization',
1181 'section' =>
'rc/changesrc',
1186 $defaultPreferences[
'hidepatrolled'] = [
1188 'section' =>
'rc/changesrc',
1189 'label-message' =>
'tog-hidepatrolled',
1194 $defaultPreferences[
'newpageshidepatrolled'] = [
1196 'section' =>
'rc/changesrc',
1197 'label-message' =>
'tog-newpageshidepatrolled',
1201 if ( $this->options->get(
'RCShowWatchingUsers' ) ) {
1202 $defaultPreferences[
'shownumberswatching'] = [
1204 'section' =>
'rc/advancedrc',
1205 'label-message' =>
'tog-shownumberswatching',
1209 $defaultPreferences[
'rcenhancedfilters-disable'] = [
1211 'section' =>
'rc/advancedrc',
1212 'label-message' =>
'rcfilters-preference-label',
1213 'help-message' =>
'rcfilters-preference-help',
1225 $watchlistdaysMax = ceil( $this->options->get(
'RCMaxAge' ) / ( 3600 * 24 ) );
1227 if ( $user->
isAllowed(
'editmywatchlist' ) ) {
1228 $editWatchlistLinks =
'';
1229 $editWatchlistModes = [
1230 'edit' => [
'subpage' =>
false,
'flags' => [] ],
1231 'raw' => [
'subpage' =>
'raw',
'flags' => [] ],
1232 'clear' => [
'subpage' =>
'clear',
'flags' => [
'destructive' ] ],
1234 foreach ( $editWatchlistModes as $mode =>
$options ) {
1236 $editWatchlistLinks .=
1237 new \OOUI\ButtonWidget( [
1240 'label' =>
new \OOUI\HtmlSnippet(
1241 $context->
msg(
"prefs-editwatchlist-{$mode}" )->parse()
1246 $defaultPreferences[
'editwatchlist'] = [
1249 'default' => $editWatchlistLinks,
1250 'label-message' =>
'prefs-editwatchlist-label',
1251 'section' =>
'watchlist/editwatchlist',
1255 $defaultPreferences[
'watchlistdays'] = [
1258 'max' => $watchlistdaysMax,
1259 'section' =>
'watchlist/displaywatchlist',
1260 'help-message' => [
'prefs-watchlist-days-max',
Message::numParam( $watchlistdaysMax ) ],
1261 'label-message' =>
'prefs-watchlist-days',
1263 $defaultPreferences[
'wllimit'] = [
1267 'label-message' =>
'prefs-watchlist-edits',
1268 'help-message' =>
'prefs-watchlist-edits-max',
1269 'section' =>
'watchlist/displaywatchlist',
1270 'filter' => IntvalFilter::class,
1272 $defaultPreferences[
'extendwatchlist'] = [
1274 'section' =>
'watchlist/advancedwatchlist',
1275 'label-message' =>
'tog-extendwatchlist',
1277 $defaultPreferences[
'watchlisthideminor'] = [
1279 'section' =>
'watchlist/changeswatchlist',
1280 'label-message' =>
'tog-watchlisthideminor',
1282 $defaultPreferences[
'watchlisthidebots'] = [
1284 'section' =>
'watchlist/changeswatchlist',
1285 'label-message' =>
'tog-watchlisthidebots',
1287 $defaultPreferences[
'watchlisthideown'] = [
1289 'section' =>
'watchlist/changeswatchlist',
1290 'label-message' =>
'tog-watchlisthideown',
1292 $defaultPreferences[
'watchlisthideanons'] = [
1294 'section' =>
'watchlist/changeswatchlist',
1295 'label-message' =>
'tog-watchlisthideanons',
1297 $defaultPreferences[
'watchlisthideliu'] = [
1299 'section' =>
'watchlist/changeswatchlist',
1300 'label-message' =>
'tog-watchlisthideliu',
1304 $defaultPreferences[
'watchlistreloadautomatically'] = [
1306 'section' =>
'watchlist/advancedwatchlist',
1307 'label-message' =>
'tog-watchlistreloadautomatically',
1311 $defaultPreferences[
'watchlistunwatchlinks'] = [
1313 'section' =>
'watchlist/advancedwatchlist',
1314 'label-message' =>
'tog-watchlistunwatchlinks',
1317 if ( $this->options->get(
'RCWatchCategoryMembership' ) ) {
1318 $defaultPreferences[
'watchlisthidecategorization'] = [
1320 'section' =>
'watchlist/changeswatchlist',
1321 'label-message' =>
'tog-watchlisthidecategorization',
1326 $defaultPreferences[
'watchlisthidepatrolled'] = [
1328 'section' =>
'watchlist/changeswatchlist',
1329 'label-message' =>
'tog-watchlisthidepatrolled',
1334 'edit' =>
'watchdefault',
1335 'move' =>
'watchmoves',
1336 'delete' =>
'watchdeletion'
1340 if ( $user->
isAllowedAny(
'createpage',
'createtalk' ) ) {
1341 $watchTypes[
'read'] =
'watchcreations';
1345 $watchTypes[
'rollback'] =
'watchrollback';
1349 $watchTypes[
'upload'] =
'watchuploads';
1352 foreach ( $watchTypes as $action => $pref ) {
1357 $defaultPreferences[$pref] = [
1359 'section' =>
'watchlist/pageswatchlist',
1360 'label-message' =>
"tog-$pref",
1365 $defaultPreferences[
'watchlisttoken'] = [
1369 $tokenButton = new \OOUI\ButtonWidget( [
1373 'label' => $context->
msg(
'prefs-watchlist-managetokens' )->text(),
1375 $defaultPreferences[
'watchlisttoken-info'] = [
1377 'section' =>
'watchlist/tokenwatchlist',
1378 'label-message' =>
'prefs-watchlist-token',
1379 'help-message' =>
'prefs-help-tokenmanagement',
1381 'default' => (string)$tokenButton,
1384 $defaultPreferences[
'wlenhancedfilters-disable'] = [
1386 'section' =>
'watchlist/advancedwatchlist',
1387 'label-message' =>
'rcfilters-watchlist-preference-label',
1388 'help-message' =>
'rcfilters-watchlist-preference-help',
1396 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1397 $defaultPreferences[
'searchNs' . $n] = [
1402 if ( $this->options->get(
'SearchMatchRedirectPreference' ) ) {
1403 $defaultPreferences[
'search-match-redirect'] = [
1405 'section' =>
'searchoptions/searchmisc',
1406 'label-message' =>
'search-match-redirect-label',
1407 'help-message' =>
'search-match-redirect-help',
1410 $defaultPreferences[
'search-match-redirect'] = [
1424 $mptitle = Title::newMainPage();
1425 $previewtext = $context->
msg(
'skin-preview' )->escaped();
1428 $validSkinNames = $this->skinFactory->getAllowedSkins();
1429 $allInstalledSkins = $this->skinFactory->getSkinNames();
1432 $useSkin = $context->
getRequest()->getRawVal(
'useskin' );
1433 if ( isset( $allInstalledSkins[$useSkin] )
1434 && $context->
msg(
"skinname-$useSkin" )->exists()
1436 $validSkinNames[$useSkin] = $useSkin;
1440 $currentUserSkin = $this->userOptionsManager->getOption( $user,
'skin' );
1441 if ( isset( $allInstalledSkins[$currentUserSkin] )
1442 && $context->
msg(
"skinname-$currentUserSkin" )->exists()
1444 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1447 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1448 $msg = $context->
msg(
"skinname-{$skinkey}" );
1449 if ( $msg->exists() ) {
1450 $skinname = htmlspecialchars( $msg->text() );
1454 $defaultSkin = $this->options->get(
'DefaultSkin' );
1455 $allowUserCss = $this->options->get(
'AllowUserCss' );
1456 $allowUserJs = $this->options->get(
'AllowUserJs' );
1460 uksort( $validSkinNames,
static function ( $a, $b ) use ( $defaultSkin ) {
1462 if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1465 if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1468 return strcasecmp( $a, $b );
1471 $foundDefault =
false;
1472 foreach ( $validSkinNames as $skinkey => $sn ) {
1476 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1477 $linkTools[] = $context->
msg(
'default' )->escaped();
1478 $foundDefault =
true;
1482 $mplink = htmlspecialchars( $mptitle->getLocalURL( [
'useskin' => $skinkey ] ) );
1483 $linkTools[] =
"<a target='_blank' href=\"$mplink\">$previewtext</a>";
1487 if ( $allowUserCss ) {
1488 $cssPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.css' );
1489 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
1490 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1493 if ( $allowUserJs ) {
1494 $jsPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.js' );
1495 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
1496 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1499 $display = $sn .
' ' . $context->
msg(
'parentheses' )
1500 ->rawParams( $context->
getLanguage()->pipeList( $linkTools ) )
1502 $ret[$display] = $skinkey;
1505 if ( !$foundDefault ) {
1520 $dateopts =
$lang->getDatePreferences();
1525 if ( !in_array(
'default', $dateopts ) ) {
1526 $dateopts[] =
'default';
1536 foreach ( $dateopts as $key ) {
1537 if ( $key ==
'default' ) {
1538 $formatted = $context->
msg(
'datedefault' )->escaped();
1540 $formatted = htmlspecialchars(
$lang->timeanddate( $epoch,
false, $key ) );
1542 $ret[$formatted] = $key;
1554 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1556 foreach ( $this->options->get(
'ImageLimits' ) as $index => $limits ) {
1558 $display =
"{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1559 $ret[$display] = $index;
1571 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1573 foreach ( $this->options->get(
'ThumbLimits' ) as $index => $size ) {
1574 $display = $size . $pixels;
1575 $ret[$display] = $index;
1588 $sigValidation = $this->options->get(
'SignatureValidation' );
1589 $maxSigChars = $this->options->get(
'MaxSigChars' );
1590 if ( is_string( $signature ) && mb_strlen( $signature ) > $maxSigChars ) {
1591 return $form->
msg(
'badsiglength' )->numParams( $maxSigChars )->escaped();
1594 if ( $signature ===
null || $signature ===
'' ) {
1601 if ( !( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) ) {
1618 $signature === $this->userOptionsManager->getOption( $user,
'nickname' ) &&
1619 (
bool)$alldata[
'fancysig'] === $this->userOptionsManager->getBoolOption( $user,
'fancysig' )
1624 if ( $sigValidation ===
'new' || $sigValidation ===
'disallow' ) {
1629 ParserOptions::newFromContext( $form->
getContext() )
1631 $errors = $validator->validateSignature( $signature );
1640 if ( $this->parser->validateSig( $signature ) ===
false ) {
1641 return $form->
msg(
'badsig' )->escaped();
1654 if ( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) {
1655 $signature = $this->parser->cleanSig( $signature );
1674 $formClass = PreferencesFormOOUI::class,
1682 if ( count( $remove ) ) {
1683 $removeKeys = array_fill_keys( $remove,
true );
1684 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1688 foreach ( $formDescriptor as $name => $info ) {
1689 if ( isset( $info[
'type'] ) && $info[
'type'] ===
'api' ) {
1690 unset( $formDescriptor[$name] );
1697 $htmlForm =
new $formClass( $formDescriptor, $context,
'prefs' );
1703 $htmlForm->setAction( $context->
getTitle()->getLocalURL( [
1704 'useskin' => $context->
getRequest()->getRawVal(
'useskin' )
1707 $htmlForm->setModifiedUser( $user );
1708 $htmlForm->setOptionsEditable( $user->
isAllowed(
'editmyoptions' ) );
1709 $htmlForm->setPrivateInfoEditable( $user->
isAllowed(
'editmyprivateinfo' ) );
1710 $htmlForm->setId(
'mw-prefs-form' );
1711 $htmlForm->setAutocomplete(
'off' );
1712 $htmlForm->setSubmitTextMsg(
'saveprefs' );
1714 $htmlForm->setSubmitTooltip(
'preferences-save' );
1715 $htmlForm->setSubmitID(
'prefcontrol' );
1716 $htmlForm->setSubmitCallback(
1718 return $this->
submitForm( $formData, $form, $formDescriptor );
1732 $localTZoffset = $this->options->get(
'LocalTZoffset' );
1735 $timestamp = MWTimestamp::getLocalInstance();
1737 if ( $localTZoffset === $timestamp->format(
'Z' ) / 60 ) {
1738 $timezoneName = $timestamp->getTimezone()->getName();
1740 if ( isset( $timeZoneList[$timezoneName] ) ) {
1741 $timezoneName = $timeZoneList[$timezoneName][
'name'];
1743 $server_tz_msg = $context->
msg(
1744 'timezoneuseserverdefault',
1748 $tzstring = sprintf(
1750 floor( $localTZoffset / 60 ),
1751 abs( $localTZoffset ) % 60
1753 $server_tz_msg = $context->
msg(
'timezoneuseserverdefault', $tzstring )->text();
1755 $opt[$server_tz_msg] =
"System|$localTZoffset";
1756 $opt[$context->
msg(
'timezoneuseoffset' )->text()] =
'other';
1757 $opt[$context->
msg(
'guesstimezone' )->text()] =
'guess';
1759 foreach ( $timeZoneList as $timeZoneInfo ) {
1760 $region = $timeZoneInfo[
'region'];
1761 if ( !isset( $opt[$region] ) ) {
1764 $opt[$region][$timeZoneInfo[
'name']] = $timeZoneInfo[
'timecorrection'];
1779 $hiddenPrefs = $this->options->get(
'HiddenPrefs' );
1782 if ( !$user->isAllowedAny(
'editmyprivateinfo',
'editmyoptions' )
1784 return Status::newFatal(
'mypreferencesprotected' );
1788 $this->
applyFilters( $formData, $formDescriptor,
'filterFromForm' );
1793 if ( !in_array(
'realname', $hiddenPrefs )
1794 && $user->isAllowed(
'editmyprivateinfo' )
1795 && array_key_exists(
'realname', $formData )
1797 $realName = $formData[
'realname'];
1798 $user->setRealName( $realName );
1801 if ( $user->isAllowed(
'editmyoptions' ) ) {
1802 $oldUserOptions = $this->userOptionsManager->getOptions( $user );
1805 unset( $formData[$b] );
1811 foreach ( $hiddenPrefs as $pref ) {
1814 $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref,
null,
true );
1819 isset( $formData[
'rclimit'] ) &&
1820 intval( $formData[
'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user,
'rclimit' )
1822 $formData[
'rcfilters-limit'] = $formData[
'rclimit'];
1826 $this->userOptionsManager->resetOptions( $user, $form->
getContext(),
'unused' );
1828 foreach ( $formData as $key => $value ) {
1829 $this->userOptionsManager->setOption( $user, $key, $value );
1832 $this->hookRunner->onPreferencesFormPreSave(
1833 $formData, $form, $user, $result, $oldUserOptions );
1836 $user->saveSettings();
1849 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1850 foreach ( $formDescriptor as $preference => $desc ) {
1851 if ( !isset( $desc[
'filter'] ) || !isset( $preferences[$preference] ) ) {
1854 $filterDesc = $desc[
'filter'];
1855 if ( $filterDesc instanceof
Filter ) {
1856 $filter = $filterDesc;
1857 } elseif ( class_exists( $filterDesc ) ) {
1858 $filter =
new $filterDesc();
1859 } elseif ( is_callable( $filterDesc ) ) {
1860 $filter = $filterDesc();
1862 throw new UnexpectedValueException(
1863 "Unrecognized filter type for preference '$preference'"
1866 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1881 array $formDescriptor
1885 if (
$res ===
true ) {
1891 $url = $form->
getTitle()->getFullURL( $urlOptions );
1894 $context->getRequest()->getSession()->set(
'specialPreferencesSaveSuccess', 1 );
1896 $context->getOutput()->redirect( $url );
1899 return (
$res ===
true ? Status::newGood() :
$res );
1911 $identifiers = DateTimeZone::listIdentifiers();
1913 if ( $identifiers ===
false ) {
1916 sort( $identifiers );
1919 'Africa' =>
wfMessage(
'timezoneregion-africa' )->inLanguage( $language )->text(),
1920 'America' =>
wfMessage(
'timezoneregion-america' )->inLanguage( $language )->text(),
1921 'Antarctica' =>
wfMessage(
'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1922 'Arctic' =>
wfMessage(
'timezoneregion-arctic' )->inLanguage( $language )->text(),
1923 'Asia' =>
wfMessage(
'timezoneregion-asia' )->inLanguage( $language )->text(),
1924 'Atlantic' =>
wfMessage(
'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1925 'Australia' =>
wfMessage(
'timezoneregion-australia' )->inLanguage( $language )->text(),
1926 'Europe' =>
wfMessage(
'timezoneregion-europe' )->inLanguage( $language )->text(),
1927 'Indian' =>
wfMessage(
'timezoneregion-indian' )->inLanguage( $language )->text(),
1928 'Pacific' =>
wfMessage(
'timezoneregion-pacific' )->inLanguage( $language )->text(),
1930 asort( $tzRegions );
1934 $now =
new DateTime();
1936 foreach ( $identifiers as $identifier ) {
1937 $parts = explode(
'/', $identifier, 2 );
1942 if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1947 $parts[0] = $tzRegions[$parts[0]];
1949 $dateTimeZone =
new DateTimeZone( $identifier );
1950 $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1952 $display = str_replace(
'_',
' ', $parts[0] .
'/' . $parts[1] );
1953 $value =
"ZoneInfo|$minDiff|$identifier";
1955 $timeZoneList[$identifier] = [
1957 'timecorrection' => $value,
1958 'region' => $parts[0],
1962 return $timeZoneList;
UserOptionsLookup $userOptionsLookup
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
This class is a collection of static functions that serve two purposes:
Methods for dealing with language codes.
Base class for multi-variant language conversion.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Library for creating and parsing MW-style timestamps.
The Message class deals with fetching and processing of interface message into a variety of formats.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
This is one of the Core classes and should be read at least once by any new developers.
Set options of the Parser.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
static stripOuterParagraph( $html)
Strip outer.
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Factory class to create Skin objects.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static checkStructuredFilterUiEnabled( $user)
Static method to check whether StructuredFilter UI is enabled for the given user.1....
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Represents a title within MediaWiki.
Represents a "user group membership" – a specific instance of a user belonging to a group.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
getName()
Get the user name, or the IP of an anonymous user.
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
getRealName()
Get the user's real name.
getRegistration()
Get the timestamp of account creation.
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.
useNPPatrol()
Check whether to enable new pages patrol features for this user.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
getEditCount()
Get the user's edit count.
getTitleKey()
Get the user's name escaped by underscores.
getEmail()
Get the user's e-mail address.
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Module of static functions for generating XML.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Interface for objects which can provide a MediaWiki context on request.
The shared interface for all language converters.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
if(!isset( $args[0])) $lang