51use Psr\Log\LoggerAwareTrait;
52use Psr\Log\NullLogger;
57use UnexpectedValueException;
99 public const CONSTRUCTOR_OPTIONS = [
100 'AllowRequiringEmailForResets',
105 'DisableLangConversion',
106 'EmailAuthentication',
107 'EmailConfirmToEdit',
110 'EnableUserEmailBlacklist',
112 'EnotifRevealEditorAddress',
122 'RCShowWatchingUsers',
123 'RCWatchCategoryMembership',
124 'SearchMatchRedirectPreference',
127 'SignatureValidation',
161 $this->logger =
new NullLogger();
164 wfDeprecated( __METHOD__ .
' without $languageConverter parameter',
'1.35' );
166 ->getLanguageConverterFactory()
167 ->getLanguageConverter();
172 wfDeprecated( __METHOD__ .
' without $languageNameUtils parameter',
'1.35' );
177 if ( !$hookContainer ) {
180 $this->hookRunner =
new HookRunner( $hookContainer );
202 OutputPage::setupOOUI(
203 strtolower( $context->
getSkin()->getSkinName() ),
218 $this->hookRunner->onGetPreferences( $user, $preferences );
221 $this->logger->debug(
"Created form descriptor for user '{$user->getName()}'" );
235 foreach ( $this->options->get(
'HiddenPrefs' ) as $pref ) {
236 if ( isset( $defaultPreferences[$pref] ) ) {
237 unset( $defaultPreferences[$pref] );
242 $dummyForm =
new HTMLForm( [], $context );
244 $disable = !$this->permissionManager->userHasRight( $user,
'editmyoptions' );
248 $this->
applyFilters( $userOptions, $defaultPreferences,
'filterForForm' );
250 foreach ( $defaultPreferences as $name => &$info ) {
253 $info[
'disabled'] =
'disabled';
255 $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm );
256 $globalDefault = $defaultOptions[$name] ??
null;
259 if ( isset( $info[
'default'] ) ) {
262 } elseif ( $prefFromUser !==
null &&
263 $field->validate( $prefFromUser, $user->
getOptions() ) ===
true ) {
264 $info[
'default'] = $prefFromUser;
265 } elseif ( $field->validate( $globalDefault, $user->
getOptions() ) ===
true ) {
266 $info[
'default'] = $globalDefault;
268 $globalDefault = json_encode( $globalDefault );
270 "Default '$globalDefault' is invalid for preference $name of user $user"
275 return $defaultPreferences;
287 $val = $userOptions[$name] ??
null;
290 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
291 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLMultiSelectField::class ) ) {
292 $options = HTMLFormField::flattenOptions( $info[
'options'] );
293 $prefix = $info[
'prefix'] ?? $name;
297 if ( $userOptions[
"$prefix$value"] ??
false ) {
304 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
305 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLCheckMatrix::class ) ) {
306 $columns = HTMLFormField::flattenOptions( $info[
'columns'] );
307 $rows = HTMLFormField::flattenOptions( $info[
'rows'] );
308 $prefix = $info[
'prefix'] ?? $name;
311 foreach ( $columns as $column ) {
312 foreach ( $rows as $row ) {
313 if ( $userOptions[
"$prefix$column-$row"] ??
false ) {
314 $val[] =
"$column-$row";
341 $defaultPreferences[
'username'] = [
343 'label-message' => [
'username', $userName ],
344 'default' => $userName,
345 'section' =>
'personal/info',
353 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
354 foreach ( $userEffectiveGroups as $ueg ) {
360 $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
362 $userG = UserGroupMembership::getLink( $groupStringOrObject, $context,
'html' );
363 $userM = UserGroupMembership::getLink( $groupStringOrObject, $context,
'html',
373 $userTempGroups[] = $userG;
374 $userTempMembers[] = $userM;
376 $userGroups[] = $userG;
377 $userMembers[] = $userM;
381 sort( $userMembers );
382 sort( $userTempGroups );
383 sort( $userTempMembers );
384 $userGroups = array_merge( $userTempGroups, $userGroups );
385 $userMembers = array_merge( $userTempMembers, $userMembers );
387 $defaultPreferences[
'usergroups'] = [
389 'label' => $context->
msg(
'prefs-memberingroups' )->numParams(
390 count( $userGroups ) )->params( $userName )->text(),
391 'default' => $context->
msg(
'prefs-memberingroups-type' )
392 ->rawParams(
$lang->commaList( $userGroups ),
$lang->commaList( $userMembers ) )
395 'section' =>
'personal/info',
400 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
402 $defaultPreferences[
'editcount'] = [
405 'label-message' =>
'prefs-edits',
406 'default' => $editCount,
407 'section' =>
'personal/info',
411 $displayUser = $context->
getUser();
413 $defaultPreferences[
'registrationdate'] = [
415 'label-message' =>
'prefs-registration',
416 'default' => $context->
msg(
417 'prefs-registration-date-time',
418 $lang->userTimeAndDate( $userRegistration, $displayUser ),
419 $lang->userDate( $userRegistration, $displayUser ),
420 $lang->userTime( $userRegistration, $displayUser )
422 'section' =>
'personal/info',
426 $canViewPrivateInfo = $this->permissionManager->userHasRight( $user,
'viewmyprivateinfo' );
427 $canEditPrivateInfo = $this->permissionManager->userHasRight( $user,
'editmyprivateinfo' );
430 $defaultPreferences[
'realname'] = [
432 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'realname' )
435 'section' =>
'personal/info',
436 'label-message' =>
'yourrealname',
437 'help-message' =>
'prefs-help-realname',
440 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
443 $defaultPreferences[
'password'] = [
446 'default' => (string)
new \OOUI\ButtonWidget( [
450 'label' => $context->
msg(
'prefs-resetpass' )->text(),
452 'label-message' =>
'yourpassword',
454 'help' => $this->options->get(
'AllowRequiringEmailForResets' ) && $user->
getEmail()
455 ? $context->
msg(
'prefs-help-yourpassword',
456 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
458 'section' =>
'personal/info',
462 if ( !$this->options->get(
'ForceHTTPS' )
463 && $this->options->get(
'SecureLogin' )
466 $defaultPreferences[
'prefershttps'] = [
468 'label-message' =>
'tog-prefershttps',
469 'help-message' =>
'prefs-help-prefershttps',
470 'section' =>
'personal/info'
474 $defaultPreferences[
'downloaduserdata'] = [
477 'label-message' =>
'prefs-user-downloaddata-label',
478 'default' => HTML::Element(
481 'href' => $this->options->get(
'ScriptPath' ) .
482 '/api.php?action=query&meta=userinfo&uiprop=*',
484 $context->
msg(
'prefs-user-downloaddata-info' )->text()
486 'help-message' => [
'prefs-user-downloaddata-help-message', $user->
getTitleKey() ],
487 'section' =>
'personal/info',
490 $languages = $this->languageNameUtils->getLanguageNames(
null,
'mwfile' );
491 $languageCode = $this->options->get(
'LanguageCode' );
492 if ( !array_key_exists( $languageCode, $languages ) ) {
493 $languages[$languageCode] = $languageCode;
499 foreach ( $languages as $code => $name ) {
500 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
503 $defaultPreferences[
'language'] = [
505 'section' =>
'personal/i18n',
507 'label-message' =>
'yourlanguage',
510 $neutralGenderMessage = $context->
msg(
'gender-notknown' )->escaped() . (
511 !$context->
msg(
'gender-unknown' )->isDisabled()
512 ?
"<br>" . $context->
msg(
'parentheses' )
513 ->params( $context->
msg(
'gender-unknown' )->plain() )
518 $defaultPreferences[
'gender'] = [
520 'section' =>
'personal/i18n',
522 $neutralGenderMessage =>
'unknown',
523 $context->
msg(
'gender-female' )->escaped() =>
'female',
524 $context->
msg(
'gender-male' )->escaped() =>
'male',
526 'label-message' =>
'yourgender',
527 'help-message' =>
'prefs-help-gender',
531 if ( !$this->options->get(
'DisableLangConversion' ) ) {
533 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
534 if ( $langCode == $this->contLang->getCode() ) {
535 if ( !$this->languageConverter->hasVariants() ) {
539 $variants = $this->languageConverter->getVariants();
541 foreach ( $variants as $v ) {
542 $v = str_replace(
'_',
'-', strtolower( $v ) );
543 $variantArray[$v] =
$lang->getVariantname( $v,
false );
547 foreach ( $variantArray as $code => $name ) {
548 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
552 $defaultPreferences[
'variant'] = [
553 'label-message' =>
'yourvariant',
556 'section' =>
'personal/i18n',
557 'help-message' =>
'prefs-help-variant',
560 $defaultPreferences[
"variant-$langCode"] = [
568 $oldsigWikiText = $services->getParser()->preSaveTransform(
572 ParserOptions::newFromContext( $context )
574 $oldsigHTML = Parser::stripOuterParagraph(
575 $context->
getOutput()->parseAsContent( $oldsigWikiText )
577 $signatureFieldConfig = [];
583 ParserOptions::newFromContext( $context )
585 $signatureErrors = $validator->validateSignature( $user->
getOption(
'nickname' ) );
586 if ( $signatureErrors ) {
587 $sigValidation = $this->options->get(
'SignatureValidation' );
588 $oldsigHTML .=
'<p><strong>' .
593 $context->
msg(
"prefs-signature-invalid-$sigValidation" )->parse() .
598 foreach ( $signatureErrors as &$sigError ) {
599 $sigError = new \OOUI\HtmlSnippet( $sigError );
602 $signatureFieldConfig = [
603 'warnings' => $sigValidation !==
'disallow' ? $signatureErrors :
null,
604 'errors' => $sigValidation ===
'disallow' ? $signatureErrors :
null,
609 $defaultPreferences[
'oldsig'] = [
614 'default' => new \OOUI\FieldLayout(
615 new \OOUI\LabelWidget( [
616 'label' =>
new \OOUI\HtmlSnippet( $oldsigHTML ),
620 'label' =>
new \OOUI\HtmlSnippet( $context->
msg(
'tog-oldsig' )->parse() )
621 ] + $signatureFieldConfig
623 'section' =>
'personal/signature',
625 $defaultPreferences[
'nickname'] = [
626 'type' => $this->authManager->allowsPropertyChange(
'nickname' ) ?
'text' :
'info',
627 'maxlength' => $this->options->get(
'MaxSigChars' ),
628 'label-message' =>
'yournick',
629 'validation-callback' =>
function ( $signature, $alldata,
HTMLForm $form ) {
632 'section' =>
'personal/signature',
633 'filter-callback' =>
function ( $signature, array $alldata,
HTMLForm $form ) {
637 $defaultPreferences[
'fancysig'] = [
639 'label-message' =>
'tog-fancysig',
641 'help-message' =>
'prefs-help-signature',
642 'section' =>
'personal/signature'
646 if ( $this->options->get(
'EnableEmail' ) ) {
647 if ( $canViewPrivateInfo ) {
649 $helpMessages[] = $this->options->get(
'EmailConfirmToEdit' )
650 ?
'prefs-help-email-required'
651 :
'prefs-help-email';
653 if ( $this->options->get(
'EnableUserEmail' ) ) {
655 $helpMessages[] =
'prefs-help-email-others';
658 $emailAddress = $user->
getEmail() ? htmlspecialchars( $user->
getEmail() ) :
'';
659 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'emailaddress' ) ) {
660 $button = new \OOUI\ButtonWidget( [
665 $context->
msg( $user->
getEmail() ?
'prefs-changeemail' :
'prefs-setemail' )->text(),
668 $emailAddress .= $emailAddress ==
'' ? $button : (
'<br />' . $button );
671 $defaultPreferences[
'emailaddress'] = [
674 'default' => $emailAddress,
675 'label-message' =>
'youremail',
676 'section' =>
'personal/email',
677 'help-messages' => $helpMessages,
682 $disableEmailPrefs =
false;
684 if ( $this->options->get(
'AllowRequiringEmailForResets' ) ) {
685 $defaultPreferences[
'requireemail'] = [
687 'label-message' =>
'tog-requireemail',
688 'help-message' =>
'prefs-help-requireemail',
689 'section' =>
'personal/email',
690 'disabled' => $user->
getEmail() ? false :
true,
694 if ( $this->options->get(
'EmailAuthentication' ) ) {
695 $emailauthenticationclass =
'mw-email-not-authenticated';
701 $displayUser = $context->
getUser();
703 $time =
$lang->userTimeAndDate( $emailTimestamp, $displayUser );
704 $d =
$lang->userDate( $emailTimestamp, $displayUser );
705 $t =
$lang->userTime( $emailTimestamp, $displayUser );
706 $emailauthenticated = $context->
msg(
'emailauthenticated',
707 $time, $d,
$t )->parse() .
'<br />';
708 $disableEmailPrefs =
false;
709 $emailauthenticationclass =
'mw-email-authenticated';
711 $disableEmailPrefs =
true;
712 $emailauthenticated = $context->
msg(
'emailnotauthenticated' )->parse() .
'<br />' .
713 new \OOUI\ButtonWidget( [
715 'label' => $context->
msg(
'emailconfirmlink' )->text(),
717 $emailauthenticationclass =
"mw-email-not-authenticated";
720 $disableEmailPrefs =
true;
721 $emailauthenticated = $context->
msg(
'noemailprefs' )->escaped();
722 $emailauthenticationclass =
'mw-email-none';
725 if ( $canViewPrivateInfo ) {
726 $defaultPreferences[
'emailauthentication'] = [
729 'section' =>
'personal/email',
730 'label-message' =>
'prefs-emailconfirm-label',
731 'default' => $emailauthenticated,
733 'cssclass' => $emailauthenticationclass,
738 if ( $this->options->get(
'EnableUserEmail' ) &&
739 $this->permissionManager->userHasRight( $user,
'sendemail' )
741 $defaultPreferences[
'disablemail'] = [
742 'id' =>
'wpAllowEmail',
745 'section' =>
'personal/email',
746 'label-message' =>
'allowemail',
747 'disabled' => $disableEmailPrefs,
750 $defaultPreferences[
'email-allow-new-users'] = [
751 'id' =>
'wpAllowEmailFromNewUsers',
753 'section' =>
'personal/email',
754 'label-message' =>
'email-allow-new-users-label',
755 'disabled' => $disableEmailPrefs,
758 $defaultPreferences[
'ccmeonemails'] = [
760 'section' =>
'personal/email',
761 'label-message' =>
'tog-ccmeonemails',
762 'disabled' => $disableEmailPrefs,
765 if ( $this->options->get(
'EnableUserEmailBlacklist' ) ) {
766 $defaultPreferences[
'email-blacklist'] = [
767 'type' =>
'usersmultiselect',
768 'label-message' =>
'email-blacklist-label',
769 'section' =>
'personal/email',
770 'disabled' => $disableEmailPrefs,
771 'filter' => MultiUsernameFilter::class,
776 if ( $this->options->get(
'EnotifWatchlist' ) ) {
777 $defaultPreferences[
'enotifwatchlistpages'] = [
779 'section' =>
'personal/email',
780 'label-message' =>
'tog-enotifwatchlistpages',
781 'disabled' => $disableEmailPrefs,
784 if ( $this->options->get(
'EnotifUserTalk' ) ) {
785 $defaultPreferences[
'enotifusertalkpages'] = [
787 'section' =>
'personal/email',
788 'label-message' =>
'tog-enotifusertalkpages',
789 'disabled' => $disableEmailPrefs,
792 if ( $this->options->get(
'EnotifUserTalk' ) ||
793 $this->options->get(
'EnotifWatchlist' ) ) {
794 if ( $this->options->get(
'EnotifMinorEdits' ) ) {
795 $defaultPreferences[
'enotifminoredits'] = [
797 'section' =>
'personal/email',
798 'label-message' =>
'tog-enotifminoredits',
799 'disabled' => $disableEmailPrefs,
803 if ( $this->options->get(
'EnotifRevealEditorAddress' ) ) {
804 $defaultPreferences[
'enotifrevealaddr'] = [
806 'section' =>
'personal/email',
807 'label-message' =>
'tog-enotifrevealaddr',
808 'disabled' => $disableEmailPrefs,
824 if ( $skinOptions ) {
825 $defaultPreferences[
'skin'] = [
827 'options' => $skinOptions,
828 'section' =>
'rendering/skin',
832 $allowUserCss = $this->options->get(
'AllowUserCss' );
833 $allowUserJs = $this->options->get(
'AllowUserJs' );
837 if ( $allowUserCss || $allowUserJs ) {
841 if ( $allowUserCss ) {
842 $cssPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.css' );
843 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
844 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
847 if ( $allowUserJs ) {
848 $jsPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.js' );
849 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
850 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
853 $defaultPreferences[
'commoncssjs'] = [
856 'default' => $context->
getLanguage()->pipeList( $linkTools ),
857 'label-message' =>
'prefs-common-config',
858 'section' =>
'rendering/skin',
868 $defaultPreferences[
'imagesize'] = [
871 'label-message' =>
'imagemaxsize',
872 'section' =>
'rendering/files',
874 $defaultPreferences[
'thumbsize'] = [
877 'label-message' =>
'thumbsize',
878 'section' =>
'rendering/files',
892 if ( $dateOptions ) {
893 $defaultPreferences[
'date'] = [
895 'options' => $dateOptions,
896 'section' =>
'rendering/dateformat',
903 $nowlocal =
Xml::element(
'span', [
'id' =>
'wpLocalTime' ],
904 $lang->userTime( $now, $user ) );
905 $nowserver =
$lang->userTime( $now, $user,
906 [
'format' =>
false,
'timecorrection' =>
false ] ) .
907 Html::hidden(
'wpServerTime', (
int)substr( $now, 8, 2 ) * 60 + (
int)substr( $now, 10, 2 ) );
909 $defaultPreferences[
'nowserver'] = [
912 'label-message' =>
'servertime',
913 'default' => $nowserver,
914 'section' =>
'rendering/timeoffset',
917 $defaultPreferences[
'nowlocal'] = [
920 'label-message' =>
'localtime',
921 'default' => $nowlocal,
922 'section' =>
'rendering/timeoffset',
926 $tzOffset = $user->
getOption(
'timecorrection' );
927 $tz = explode(
'|', $tzOffset, 3 );
931 $tzSetting = $tzOffset;
932 if ( count( $tz ) > 1 && $tz[0] ==
'ZoneInfo' &&
933 !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
937 $userTZ =
new DateTimeZone( $tz[2] );
938 $minDiff = floor( $userTZ->getOffset(
new DateTime(
'now' ) ) / 60 );
939 $tzSetting =
"ZoneInfo|$minDiff|{$tz[2]}";
940 }
catch ( Exception $e ) {
945 if ( count( $tz ) > 1 && $tz[0] ==
'Offset' ) {
947 $tzSetting = sprintf(
'%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
950 $defaultPreferences[
'timecorrection'] = [
951 'class' => \HTMLSelectOrOtherField::class,
952 'label-message' =>
'timezonelegend',
953 'options' => $tzOptions,
954 'default' => $tzSetting,
956 'section' =>
'rendering/timeoffset',
957 'id' =>
'wpTimeCorrection',
958 'filter' => TimezoneFilter::class,
959 'placeholder-message' =>
'timezone-useoffset-placeholder',
974 $defaultPreferences[
'diffonly'] = [
976 'section' =>
'rendering/diffs',
977 'label-message' =>
'tog-diffonly',
979 $defaultPreferences[
'norollbackdiff'] = [
981 'section' =>
'rendering/diffs',
982 'label-message' =>
'tog-norollbackdiff',
986 if ( $this->options->get(
'AllowUserCssPrefs' ) ) {
987 $defaultPreferences[
'underline'] = [
990 $l10n->
msg(
'underline-never' )->text() => 0,
991 $l10n->
msg(
'underline-always' )->text() => 1,
992 $l10n->
msg(
'underline-default' )->text() => 2,
994 'label-message' =>
'tog-underline',
995 'section' =>
'rendering/advancedrendering',
999 $stubThresholdValues = [ 50, 100, 500, 1000, 2000, 5000, 10000 ];
1000 $stubThresholdOptions = [ $l10n->
msg(
'stub-threshold-disabled' )->text() => 0 ];
1001 foreach ( $stubThresholdValues as $value ) {
1002 $stubThresholdOptions[$l10n->
msg(
'size-bytes', $value )->text()] = $value;
1005 $defaultPreferences[
'stubthreshold'] = [
1007 'section' =>
'rendering/advancedrendering',
1008 'options' => $stubThresholdOptions,
1010 'label-raw' => $l10n->
msg(
'stub-threshold' )->rawParams(
1011 '<a class="stub">' .
1012 $l10n->
msg(
'stub-threshold-sample-link' )->parse() .
1016 $defaultPreferences[
'showhiddencats'] = [
1018 'section' =>
'rendering/advancedrendering',
1019 'label-message' =>
'tog-showhiddencats'
1022 $defaultPreferences[
'numberheadings'] = [
1024 'section' =>
'rendering/advancedrendering',
1025 'label-message' =>
'tog-numberheadings',
1028 if ( $this->permissionManager->userHasRight( $user,
'rollback' ) ) {
1029 $defaultPreferences[
'showrollbackconfirmation'] = [
1031 'section' =>
'rendering/advancedrendering',
1032 'label-message' =>
'tog-showrollbackconfirmation',
1043 $defaultPreferences[
'editsectiononrightclick'] = [
1045 'section' =>
'editing/advancedediting',
1046 'label-message' =>
'tog-editsectiononrightclick',
1048 $defaultPreferences[
'editondblclick'] = [
1050 'section' =>
'editing/advancedediting',
1051 'label-message' =>
'tog-editondblclick',
1054 if ( $this->options->get(
'AllowUserCssPrefs' ) ) {
1055 $defaultPreferences[
'editfont'] = [
1057 'section' =>
'editing/editor',
1058 'label-message' =>
'editfont-style',
1060 $l10n->
msg(
'editfont-monospace' )->text() =>
'monospace',
1061 $l10n->
msg(
'editfont-sansserif' )->text() =>
'sans-serif',
1062 $l10n->
msg(
'editfont-serif' )->text() =>
'serif',
1067 if ( $this->permissionManager->userHasRight( $user,
'minoredit' ) ) {
1068 $defaultPreferences[
'minordefault'] = [
1070 'section' =>
'editing/editor',
1071 'label-message' =>
'tog-minordefault',
1075 $defaultPreferences[
'forceeditsummary'] = [
1077 'section' =>
'editing/editor',
1078 'label-message' =>
'tog-forceeditsummary',
1080 $defaultPreferences[
'useeditwarning'] = [
1082 'section' =>
'editing/editor',
1083 'label-message' =>
'tog-useeditwarning',
1086 $defaultPreferences[
'previewonfirst'] = [
1088 'section' =>
'editing/preview',
1089 'label-message' =>
'tog-previewonfirst',
1091 $defaultPreferences[
'previewontop'] = [
1093 'section' =>
'editing/preview',
1094 'label-message' =>
'tog-previewontop',
1096 $defaultPreferences[
'uselivepreview'] = [
1098 'section' =>
'editing/preview',
1099 'label-message' =>
'tog-uselivepreview',
1109 $rcMaxAge = $this->options->get(
'RCMaxAge' );
1110 $defaultPreferences[
'rcdays'] = [
1112 'label-message' =>
'recentchangesdays',
1113 'section' =>
'rc/displayrc',
1115 'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
1116 'help' => $l10n->
msg(
'recentchangesdays-max' )->numParams(
1117 ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped()
1119 $defaultPreferences[
'rclimit'] = [
1123 'label-message' =>
'recentchangescount',
1124 'help-message' =>
'prefs-help-recentchangescount',
1125 'section' =>
'rc/displayrc',
1126 'filter' => IntvalFilter::class,
1128 $defaultPreferences[
'usenewrc'] = [
1130 'label-message' =>
'tog-usenewrc',
1131 'section' =>
'rc/advancedrc',
1133 $defaultPreferences[
'hideminor'] = [
1135 'label-message' =>
'tog-hideminor',
1136 'section' =>
'rc/changesrc',
1138 $defaultPreferences[
'rcfilters-rc-collapsed'] = [
1141 $defaultPreferences[
'rcfilters-wl-collapsed'] = [
1144 $defaultPreferences[
'rcfilters-saved-queries'] = [
1147 $defaultPreferences[
'rcfilters-wl-saved-queries'] = [
1151 $defaultPreferences[
'rcfilters-limit'] = [
1154 $defaultPreferences[
'rcfilters-saved-queries-versionbackup'] = [
1157 $defaultPreferences[
'rcfilters-wl-saved-queries-versionbackup'] = [
1161 if ( $this->options->get(
'RCWatchCategoryMembership' ) ) {
1162 $defaultPreferences[
'hidecategorization'] = [
1164 'label-message' =>
'tog-hidecategorization',
1165 'section' =>
'rc/changesrc',
1170 $defaultPreferences[
'hidepatrolled'] = [
1172 'section' =>
'rc/changesrc',
1173 'label-message' =>
'tog-hidepatrolled',
1178 $defaultPreferences[
'newpageshidepatrolled'] = [
1180 'section' =>
'rc/changesrc',
1181 'label-message' =>
'tog-newpageshidepatrolled',
1185 if ( $this->options->get(
'RCShowWatchingUsers' ) ) {
1186 $defaultPreferences[
'shownumberswatching'] = [
1188 'section' =>
'rc/advancedrc',
1189 'label-message' =>
'tog-shownumberswatching',
1193 $defaultPreferences[
'rcenhancedfilters-disable'] = [
1195 'section' =>
'rc/advancedrc',
1196 'label-message' =>
'rcfilters-preference-label',
1197 'help-message' =>
'rcfilters-preference-help',
1209 $watchlistdaysMax = ceil( $this->options->get(
'RCMaxAge' ) / ( 3600 * 24 ) );
1211 if ( $this->permissionManager->userHasRight( $user,
'editmywatchlist' ) ) {
1212 $editWatchlistLinks =
'';
1213 $editWatchlistModes = [
1214 'edit' => [
'subpage' =>
false,
'flags' => [] ],
1215 'raw' => [
'subpage' =>
'raw',
'flags' => [] ],
1216 'clear' => [
'subpage' =>
'clear',
'flags' => [
'destructive' ] ],
1218 foreach ( $editWatchlistModes as $mode =>
$options ) {
1220 $editWatchlistLinks .=
1221 new \OOUI\ButtonWidget( [
1224 'label' =>
new \OOUI\HtmlSnippet(
1225 $context->
msg(
"prefs-editwatchlist-{$mode}" )->parse()
1230 $defaultPreferences[
'editwatchlist'] = [
1233 'default' => $editWatchlistLinks,
1234 'label-message' =>
'prefs-editwatchlist-label',
1235 'section' =>
'watchlist/editwatchlist',
1239 $defaultPreferences[
'watchlistdays'] = [
1242 'max' => $watchlistdaysMax,
1243 'section' =>
'watchlist/displaywatchlist',
1244 'help' => $context->
msg(
'prefs-watchlist-days-max' )->numParams(
1245 $watchlistdaysMax )->escaped(),
1246 'label-message' =>
'prefs-watchlist-days',
1248 $defaultPreferences[
'wllimit'] = [
1252 'label-message' =>
'prefs-watchlist-edits',
1253 'help' => $context->
msg(
'prefs-watchlist-edits-max' )->escaped(),
1254 'section' =>
'watchlist/displaywatchlist',
1255 'filter' => IntvalFilter::class,
1257 $defaultPreferences[
'extendwatchlist'] = [
1259 'section' =>
'watchlist/advancedwatchlist',
1260 'label-message' =>
'tog-extendwatchlist',
1262 $defaultPreferences[
'watchlisthideminor'] = [
1264 'section' =>
'watchlist/changeswatchlist',
1265 'label-message' =>
'tog-watchlisthideminor',
1267 $defaultPreferences[
'watchlisthidebots'] = [
1269 'section' =>
'watchlist/changeswatchlist',
1270 'label-message' =>
'tog-watchlisthidebots',
1272 $defaultPreferences[
'watchlisthideown'] = [
1274 'section' =>
'watchlist/changeswatchlist',
1275 'label-message' =>
'tog-watchlisthideown',
1277 $defaultPreferences[
'watchlisthideanons'] = [
1279 'section' =>
'watchlist/changeswatchlist',
1280 'label-message' =>
'tog-watchlisthideanons',
1282 $defaultPreferences[
'watchlisthideliu'] = [
1284 'section' =>
'watchlist/changeswatchlist',
1285 'label-message' =>
'tog-watchlisthideliu',
1289 $defaultPreferences[
'watchlistreloadautomatically'] = [
1291 'section' =>
'watchlist/advancedwatchlist',
1292 'label-message' =>
'tog-watchlistreloadautomatically',
1296 $defaultPreferences[
'watchlistunwatchlinks'] = [
1298 'section' =>
'watchlist/advancedwatchlist',
1299 'label-message' =>
'tog-watchlistunwatchlinks',
1302 if ( $this->options->get(
'RCWatchCategoryMembership' ) ) {
1303 $defaultPreferences[
'watchlisthidecategorization'] = [
1305 'section' =>
'watchlist/changeswatchlist',
1306 'label-message' =>
'tog-watchlisthidecategorization',
1311 $defaultPreferences[
'watchlisthidepatrolled'] = [
1313 'section' =>
'watchlist/changeswatchlist',
1314 'label-message' =>
'tog-watchlisthidepatrolled',
1319 'edit' =>
'watchdefault',
1320 'move' =>
'watchmoves',
1321 'delete' =>
'watchdeletion'
1325 if ( $this->permissionManager->userHasAnyRight( $user,
'createpage',
'createtalk' ) ) {
1326 $watchTypes[
'read'] =
'watchcreations';
1329 if ( $this->permissionManager->userHasRight( $user,
'rollback' ) ) {
1330 $watchTypes[
'rollback'] =
'watchrollback';
1333 if ( $this->permissionManager->userHasRight( $user,
'upload' ) ) {
1334 $watchTypes[
'upload'] =
'watchuploads';
1337 foreach ( $watchTypes as $action => $pref ) {
1338 if ( $this->permissionManager->userHasRight( $user, $action ) ) {
1342 $defaultPreferences[$pref] = [
1344 'section' =>
'watchlist/pageswatchlist',
1345 'label-message' =>
"tog-$pref",
1350 $defaultPreferences[
'watchlisttoken'] = [
1354 $tokenButton = new \OOUI\ButtonWidget( [
1358 'label' => $context->
msg(
'prefs-watchlist-managetokens' )->text(),
1360 $defaultPreferences[
'watchlisttoken-info'] = [
1362 'section' =>
'watchlist/tokenwatchlist',
1363 'label-message' =>
'prefs-watchlist-token',
1364 'help-message' =>
'prefs-help-tokenmanagement',
1366 'default' => (string)$tokenButton,
1369 $defaultPreferences[
'wlenhancedfilters-disable'] = [
1371 'section' =>
'watchlist/advancedwatchlist',
1372 'label-message' =>
'rcfilters-watchlist-preference-label',
1373 'help-message' =>
'rcfilters-watchlist-preference-help',
1381 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1382 $defaultPreferences[
'searchNs' . $n] = [
1387 if ( $this->options->get(
'SearchMatchRedirectPreference' ) ) {
1388 $defaultPreferences[
'search-match-redirect'] = [
1390 'section' =>
'searchoptions',
1391 'label-message' =>
'search-match-redirect-label',
1392 'help-message' =>
'search-match-redirect-help',
1395 $defaultPreferences[
'search-match-redirect'] = [
1409 $mptitle = Title::newMainPage();
1410 $previewtext = $context->
msg(
'skin-preview' )->escaped();
1413 $validSkinNames = Skin::getAllowedSkins();
1414 $allInstalledSkins = Skin::getSkinNames();
1417 $useSkin = $context->
getRequest()->getRawVal(
'useskin' );
1418 if ( isset( $allInstalledSkins[$useSkin] )
1419 && $context->
msg(
"skinname-$useSkin" )->exists()
1421 $validSkinNames[$useSkin] = $useSkin;
1425 $currentUserSkin = $user->
getOption(
'skin' );
1426 if ( isset( $allInstalledSkins[$currentUserSkin] )
1427 && $context->
msg(
"skinname-$currentUserSkin" )->exists()
1429 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1432 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1433 $msg = $context->
msg(
"skinname-{$skinkey}" );
1434 if ( $msg->exists() ) {
1435 $skinname = htmlspecialchars( $msg->text() );
1439 $defaultSkin = $this->options->get(
'DefaultSkin' );
1440 $allowUserCss = $this->options->get(
'AllowUserCss' );
1441 $allowUserJs = $this->options->get(
'AllowUserJs' );
1445 uksort( $validSkinNames,
function ( $a, $b ) use ( $defaultSkin ) {
1447 if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1450 if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1453 return strcasecmp( $a, $b );
1456 $foundDefault =
false;
1457 foreach ( $validSkinNames as $skinkey => $sn ) {
1461 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1462 $linkTools[] = $context->
msg(
'default' )->escaped();
1463 $foundDefault =
true;
1467 $mplink = htmlspecialchars( $mptitle->getLocalURL( [
'useskin' => $skinkey ] ) );
1468 $linkTools[] =
"<a target='_blank' href=\"$mplink\">$previewtext</a>";
1472 if ( $allowUserCss ) {
1473 $cssPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.css' );
1474 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
1475 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1478 if ( $allowUserJs ) {
1479 $jsPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.js' );
1480 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
1481 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1484 $display = $sn .
' ' . $context->
msg(
'parentheses' )
1485 ->rawParams( $context->
getLanguage()->pipeList( $linkTools ) )
1487 $ret[$display] = $skinkey;
1490 if ( !$foundDefault ) {
1505 $dateopts =
$lang->getDatePreferences();
1510 if ( !in_array(
'default', $dateopts ) ) {
1511 $dateopts[] =
'default';
1521 foreach ( $dateopts as $key ) {
1522 if ( $key ==
'default' ) {
1523 $formatted = $context->
msg(
'datedefault' )->escaped();
1525 $formatted = htmlspecialchars(
$lang->timeanddate( $epoch,
false, $key ) );
1527 $ret[$formatted] = $key;
1539 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1541 foreach ( $this->options->get(
'ImageLimits' ) as $index => $limits ) {
1543 $display =
"{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1544 $ret[$display] = $index;
1556 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1558 foreach ( $this->options->get(
'ThumbLimits' ) as $index => $size ) {
1559 $display = $size . $pixels;
1560 $ret[$display] = $index;
1573 $sigValidation = $this->options->get(
'SignatureValidation' );
1574 $maxSigChars = $this->options->get(
'MaxSigChars' );
1575 if ( mb_strlen( $signature ) > $maxSigChars ) {
1576 return $form->
msg(
'badsiglength' )->numParams( $maxSigChars )->escaped();
1580 if ( !( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) ) {
1600 $signature === $form->
getUser()->getOption(
'nickname' ) &&
1601 (
bool)$alldata[
'fancysig'] === $form->
getUser()->getBoolOption(
'fancysig' )
1606 if ( $sigValidation ===
'new' || $sigValidation ===
'disallow' ) {
1611 ParserOptions::newFromContext( $form->
getContext() )
1613 $errors = $validator->validateSignature( $signature );
1623 if ( $parser->validateSig( $signature ) ===
false ) {
1624 return $form->
msg(
'badsig' )->escaped();
1638 if ( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) {
1639 $signature = $parser->cleanSig( $signature );
1642 $signature = Parser::cleanSigInSig( $signature );
1658 $formClass = PreferencesFormOOUI::class,
1666 if ( count( $remove ) ) {
1667 $removeKeys = array_flip( $remove );
1668 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1672 foreach ( $formDescriptor as $name => $info ) {
1673 if ( isset( $info[
'type'] ) && $info[
'type'] ===
'api' ) {
1674 unset( $formDescriptor[$name] );
1681 $htmlForm =
new $formClass( $formDescriptor, $context,
'prefs' );
1687 $htmlForm->setAction( $context->
getTitle()->getLocalURL( [
1688 'useskin' => $context->
getRequest()->getRawVal(
'useskin' )
1691 $htmlForm->setModifiedUser( $user );
1692 $htmlForm->setOptionsEditable( $this->permissionManager
1693 ->userHasRight( $user,
'editmyoptions' ) );
1694 $htmlForm->setPrivateInfoEditable( $this->permissionManager
1695 ->userHasRight( $user,
'editmyprivateinfo' ) );
1696 $htmlForm->setId(
'mw-prefs-form' );
1697 $htmlForm->setAutocomplete(
'off' );
1698 $htmlForm->setSubmitText( $context->
msg(
'saveprefs' )->text() );
1700 $htmlForm->setSubmitTooltip(
'preferences-save' );
1701 $htmlForm->setSubmitID(
'prefcontrol' );
1702 $htmlForm->setSubmitCallback(
1704 return $this->
submitForm( $formData, $form, $formDescriptor );
1718 $localTZoffset = $this->options->get(
'LocalTZoffset' );
1721 $timestamp = MWTimestamp::getLocalInstance();
1723 if ( $localTZoffset == $timestamp->format(
'Z' ) / 60 ) {
1724 $timezoneName = $timestamp->getTimezone()->getName();
1726 if ( isset( $timeZoneList[$timezoneName] ) ) {
1727 $timezoneName = $timeZoneList[$timezoneName][
'name'];
1729 $server_tz_msg = $context->
msg(
1730 'timezoneuseserverdefault',
1734 $tzstring = sprintf(
1736 floor( $localTZoffset / 60 ),
1737 abs( $localTZoffset ) % 60
1739 $server_tz_msg = $context->
msg(
'timezoneuseserverdefault', $tzstring )->text();
1741 $opt[$server_tz_msg] =
"System|$localTZoffset";
1742 $opt[$context->
msg(
'timezoneuseoffset' )->text()] =
'other';
1743 $opt[$context->
msg(
'guesstimezone' )->text()] =
'guess';
1745 foreach ( $timeZoneList as $timeZoneInfo ) {
1746 $region = $timeZoneInfo[
'region'];
1747 if ( !isset( $opt[$region] ) ) {
1750 $opt[$region][$timeZoneInfo[
'name']] = $timeZoneInfo[
'timecorrection'];
1765 $hiddenPrefs = $this->options->get(
'HiddenPrefs' );
1768 if ( !$this->permissionManager
1769 ->userHasAnyRight( $user,
'editmyprivateinfo',
'editmyoptions' )
1771 return Status::newFatal(
'mypreferencesprotected' );
1775 $this->
applyFilters( $formData, $formDescriptor,
'filterFromForm' );
1780 if ( !in_array(
'realname', $hiddenPrefs )
1781 && $this->permissionManager->userHasRight( $user,
'editmyprivateinfo' )
1782 && array_key_exists(
'realname', $formData )
1784 $realName = $formData[
'realname'];
1785 $user->setRealName( $realName );
1788 if ( $this->permissionManager->userHasRight( $user,
'editmyoptions' ) ) {
1789 $oldUserOptions = $user->getOptions();
1792 unset( $formData[$b] );
1798 foreach ( $hiddenPrefs as $pref ) {
1801 $formData[$pref] = $user->getOption( $pref,
null,
true );
1806 isset( $formData[
'rclimit'] ) &&
1807 intval( $formData[
'rclimit' ] ) !== $user->getIntOption(
'rclimit' )
1809 $formData[
'rcfilters-limit'] = $formData[
'rclimit'];
1813 $user->resetOptions(
'unused', $form->
getContext() );
1815 foreach ( $formData as $key => $value ) {
1816 $user->setOption( $key, $value );
1819 $this->hookRunner->onPreferencesFormPreSave(
1820 $formData, $form, $user, $result, $oldUserOptions );
1823 $user->saveSettings();
1836 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1837 foreach ( $formDescriptor as $preference => $desc ) {
1838 if ( !isset( $desc[
'filter'] ) || !isset( $preferences[$preference] ) ) {
1841 $filterDesc = $desc[
'filter'];
1842 if ( $filterDesc instanceof
Filter ) {
1843 $filter = $filterDesc;
1844 } elseif ( class_exists( $filterDesc ) ) {
1845 $filter =
new $filterDesc();
1846 } elseif ( is_callable( $filterDesc ) ) {
1847 $filter = $filterDesc();
1849 throw new UnexpectedValueException(
1850 "Unrecognized filter type for preference '$preference'"
1853 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1868 array $formDescriptor
1872 if (
$res ===
true ) {
1878 $url = $form->
getTitle()->getFullURL( $urlOptions );
1881 $context->getRequest()->getSession()->set(
'specialPreferencesSaveSuccess', 1 );
1883 $context->getOutput()->redirect( $url );
1886 return (
$res ===
true ? Status::newGood() :
$res );
1898 $identifiers = DateTimeZone::listIdentifiers();
1900 if ( $identifiers ===
false ) {
1903 sort( $identifiers );
1906 'Africa' =>
wfMessage(
'timezoneregion-africa' )->inLanguage( $language )->text(),
1907 'America' =>
wfMessage(
'timezoneregion-america' )->inLanguage( $language )->text(),
1908 'Antarctica' =>
wfMessage(
'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1909 'Arctic' =>
wfMessage(
'timezoneregion-arctic' )->inLanguage( $language )->text(),
1910 'Asia' =>
wfMessage(
'timezoneregion-asia' )->inLanguage( $language )->text(),
1911 'Atlantic' =>
wfMessage(
'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1912 'Australia' =>
wfMessage(
'timezoneregion-australia' )->inLanguage( $language )->text(),
1913 'Europe' =>
wfMessage(
'timezoneregion-europe' )->inLanguage( $language )->text(),
1914 'Indian' =>
wfMessage(
'timezoneregion-indian' )->inLanguage( $language )->text(),
1915 'Pacific' =>
wfMessage(
'timezoneregion-pacific' )->inLanguage( $language )->text(),
1917 asort( $tzRegions );
1921 $now =
new DateTime();
1923 foreach ( $identifiers as $identifier ) {
1924 $parts = explode(
'/', $identifier, 2 );
1929 if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1934 $parts[0] = $tzRegions[$parts[0]];
1936 $dateTimeZone =
new DateTimeZone( $identifier );
1937 $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1939 $display = str_replace(
'_',
' ', $parts[0] .
'/' . $parts[1] );
1940 $value =
"ZoneInfo|$minDiff|$identifier";
1942 $timeZoneList[$identifier] = [
1944 'timecorrection' => $value,
1945 'region' => $parts[0],
1949 return $timeZoneList;
$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.
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
getUser()
Stable to override.
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.
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...
The main skin class which provides methods and properties for all other skins.
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,...
getOptions( $flags=0)
Get all user's options.
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.
getBoolOption( $oname)
Get the user's current setting for a given option, as a boolean value.
getRegistration()
Get the timestamp of account creation.
useNPPatrol()
Check whether to enable new pages patrol features for this user.
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects.
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.
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