48use Psr\Log\LoggerAwareTrait;
49use Psr\Log\NullLogger;
54use UnexpectedValueException;
87 public const CONSTRUCTOR_OPTIONS = [
88 'AllowRequiringEmailForResets',
93 'DisableLangConversion',
94 'EmailAuthentication',
98 'EnableUserEmailBlacklist',
100 'EnotifRevealEditorAddress',
110 'RCShowWatchingUsers',
111 'RCWatchCategoryMembership',
142 $this->logger =
new NullLogger();
164 OutputPage::setupOOUI(
165 strtolower(
$context->getSkin()->getSkinName() ),
180 Hooks::run(
'GetPreferences', [ $user, &$preferences ] );
183 $this->logger->debug(
"Created form descriptor for user '{$user->getName()}'" );
198 # # Remove preferences that wikis don't want to use
199 foreach ( $this->options->get(
'HiddenPrefs' ) as $pref ) {
200 if ( isset( $defaultPreferences[$pref] ) ) {
201 unset( $defaultPreferences[$pref] );
205 # # Make sure that form fields have their parent set. See T43337.
208 $disable = !$this->permissionManager->userHasRight( $user,
'editmyoptions' );
212 $this->
applyFilters( $userOptions, $defaultPreferences,
'filterForForm' );
213 # # Prod in defaults from the user
214 foreach ( $defaultPreferences as $name => &$info ) {
217 $info[
'disabled'] =
'disabled';
219 $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm );
220 $globalDefault = $defaultOptions[$name] ??
null;
223 if ( isset( $info[
'default'] ) ) {
226 } elseif ( !is_null( $prefFromUser ) &&
227 $field->validate( $prefFromUser, $user->
getOptions() ) ===
true ) {
228 $info[
'default'] = $prefFromUser;
229 } elseif ( $field->validate( $globalDefault, $user->
getOptions() ) ===
true ) {
230 $info[
'default'] = $globalDefault;
232 $globalDefault = json_encode( $globalDefault );
234 "Default '$globalDefault' is invalid for preference $name of user $user"
239 return $defaultPreferences;
251 $val = $userOptions[$name] ??
null;
254 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
255 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLMultiSelectField::class ) ) {
257 $prefix = $info[
'prefix'] ?? $name;
261 if ( $userOptions[
"$prefix$value"] ??
false ) {
268 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
269 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLCheckMatrix::class ) ) {
272 $prefix = $info[
'prefix'] ?? $name;
275 foreach ( $columns as $column ) {
276 foreach ( $rows as $row ) {
277 if ( $userOptions[
"$prefix$column-$row"] ??
false ) {
278 $val[] =
"$column-$row";
302 # # User info #####################################
304 $defaultPreferences[
'username'] = [
306 'label-message' => [
'username', $userName ],
307 'default' => $userName,
308 'section' =>
'personal/info',
313 # Get groups to which the user belongs
316 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
317 foreach ( $userEffectiveGroups as $ueg ) {
323 $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
336 $userTempGroups[] = $userG;
337 $userTempMembers[] = $userM;
339 $userGroups[] = $userG;
340 $userMembers[] = $userM;
344 sort( $userMembers );
345 sort( $userTempGroups );
346 sort( $userTempMembers );
347 $userGroups = array_merge( $userTempGroups, $userGroups );
348 $userMembers = array_merge( $userTempMembers, $userMembers );
350 $defaultPreferences[
'usergroups'] = [
352 'label' =>
$context->msg(
'prefs-memberingroups' )->numParams(
353 count( $userGroups ) )->params( $userName )->parse(),
354 'default' =>
$context->msg(
'prefs-memberingroups-type' )
355 ->rawParams(
$lang->commaList( $userGroups ),
$lang->commaList( $userMembers ) )
358 'section' =>
'personal/info',
361 $contribTitle = SpecialPage::getTitleFor(
"Contributions", $userName );
363 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
365 $defaultPreferences[
'editcount'] = [
368 'label-message' =>
'prefs-edits',
369 'default' => $editCount,
370 'section' =>
'personal/info',
376 $defaultPreferences[
'registrationdate'] = [
378 'label-message' =>
'prefs-registration',
380 'prefs-registration-date-time',
381 $lang->userTimeAndDate( $userRegistration, $displayUser ),
382 $lang->userDate( $userRegistration, $displayUser ),
383 $lang->userTime( $userRegistration, $displayUser )
385 'section' =>
'personal/info',
389 $canViewPrivateInfo = $this->permissionManager->userHasRight( $user,
'viewmyprivateinfo' );
390 $canEditPrivateInfo = $this->permissionManager->userHasRight( $user,
'editmyprivateinfo' );
393 $defaultPreferences[
'realname'] = [
395 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'realname' )
398 'section' =>
'personal/info',
399 'label-message' =>
'yourrealname',
400 'help-message' =>
'prefs-help-realname',
403 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
406 $defaultPreferences[
'password'] = [
409 'default' => (string)
new \OOUI\ButtonWidget( [
410 'href' => SpecialPage::getTitleFor(
'ChangePassword' )->getLinkURL( [
411 'returnto' => SpecialPage::getTitleFor(
'Preferences' )->getPrefixedText()
413 'label' =>
$context->msg(
'prefs-resetpass' )->text(),
415 'label-message' =>
'yourpassword',
416 'section' =>
'personal/info',
420 if ( !$this->options->get(
'ForceHTTPS' )
421 && $this->options->get(
'SecureLogin' )
424 $defaultPreferences[
'prefershttps'] = [
426 'label-message' =>
'tog-prefershttps',
427 'help-message' =>
'prefs-help-prefershttps',
428 'section' =>
'personal/info'
432 $languages = Language::fetchLanguageNames(
null,
'mwfile' );
433 $languageCode = $this->options->get(
'LanguageCode' );
434 if ( !array_key_exists( $languageCode,
$languages ) ) {
445 $defaultPreferences[
'language'] = [
447 'section' =>
'personal/i18n',
449 'label-message' =>
'yourlanguage',
452 $defaultPreferences[
'gender'] = [
454 'section' =>
'personal/i18n',
457 ->params(
$context->msg(
'gender-unknown' )->plain() )
458 ->escaped() =>
'unknown',
459 $context->msg(
'gender-female' )->escaped() =>
'female',
460 $context->msg(
'gender-male' )->escaped() =>
'male',
462 'label-message' =>
'yourgender',
463 'help-message' =>
'prefs-help-gender',
467 if ( !$this->options->get(
'DisableLangConversion' ) ) {
469 if ( $langCode == $this->contLang->getCode() ) {
470 if ( !$this->contLang->hasVariants() ) {
474 $variants = $this->contLang->getVariants();
476 foreach ( $variants as $v ) {
477 $v = str_replace(
'_',
'-', strtolower( $v ) );
478 $variantArray[$v] =
$lang->getVariantname( $v,
false );
482 foreach ( $variantArray as $code => $name ) {
487 $defaultPreferences[
'variant'] = [
488 'label-message' =>
'yourvariant',
491 'section' =>
'personal/i18n',
492 'help-message' =>
'prefs-help-variant',
495 $defaultPreferences[
"variant-$langCode"] = [
507 ParserOptions::newFromContext(
$context )
510 $context->getOutput()->parseAsContent( $oldsigWikiText )
512 $defaultPreferences[
'oldsig'] = [
515 'label-message' =>
'tog-oldsig',
516 'default' => $oldsigHTML,
517 'section' =>
'personal/signature',
519 $defaultPreferences[
'nickname'] = [
520 'type' => $this->authManager->allowsPropertyChange(
'nickname' ) ?
'text' :
'info',
521 'maxlength' => $this->options->get(
'MaxSigChars' ),
522 'label-message' =>
'yournick',
523 'validation-callback' =>
function ( $signature, $alldata,
HTMLForm $form ) {
526 'section' =>
'personal/signature',
527 'filter-callback' =>
function ( $signature, array $alldata,
HTMLForm $form ) {
531 $defaultPreferences[
'fancysig'] = [
533 'label-message' =>
'tog-fancysig',
535 'help-message' =>
'prefs-help-signature',
536 'section' =>
'personal/signature'
541 if ( $this->options->get(
'EnableEmail' ) ) {
542 if ( $canViewPrivateInfo ) {
544 $helpMessages[] = $this->options->get(
'EmailConfirmToEdit' )
545 ?
'prefs-help-email-required'
546 :
'prefs-help-email';
548 if ( $this->options->get(
'EnableUserEmail' ) ) {
550 $helpMessages[] =
'prefs-help-email-others';
553 $emailAddress = $user->
getEmail() ? htmlspecialchars( $user->
getEmail() ) :
'';
554 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'emailaddress' ) ) {
555 $button = new \OOUI\ButtonWidget( [
556 'href' => SpecialPage::getTitleFor(
'ChangeEmail' )->getLinkURL( [
557 'returnto' => SpecialPage::getTitleFor(
'Preferences' )->getPrefixedText()
560 $context->msg( $user->
getEmail() ?
'prefs-changeemail' :
'prefs-setemail' )->text(),
563 $emailAddress .= $emailAddress ==
'' ? $button : (
'<br />' . $button );
566 $defaultPreferences[
'emailaddress'] = [
569 'default' => $emailAddress,
570 'label-message' =>
'youremail',
571 'section' =>
'personal/email',
572 'help-messages' => $helpMessages,
573 # 'cssclass' chosen below
577 $disableEmailPrefs =
false;
579 if ( $this->options->get(
'EmailAuthentication' ) ) {
580 $emailauthenticationclass =
'mw-email-not-authenticated';
588 $time =
$lang->userTimeAndDate( $emailTimestamp, $displayUser );
589 $d =
$lang->userDate( $emailTimestamp, $displayUser );
590 $t =
$lang->userTime( $emailTimestamp, $displayUser );
591 $emailauthenticated =
$context->msg(
'emailauthenticated',
592 $time, $d,
$t )->parse() .
'<br />';
593 $disableEmailPrefs =
false;
594 $emailauthenticationclass =
'mw-email-authenticated';
596 $disableEmailPrefs =
true;
597 $emailauthenticated =
$context->msg(
'emailnotauthenticated' )->parse() .
'<br />' .
598 new \OOUI\ButtonWidget( [
599 'href' => SpecialPage::getTitleFor(
'Confirmemail' )->getLinkURL(),
600 'label' =>
$context->msg(
'emailconfirmlink' )->text(),
602 $emailauthenticationclass =
"mw-email-not-authenticated";
605 $disableEmailPrefs =
true;
606 $emailauthenticated =
$context->msg(
'noemailprefs' )->escaped();
607 $emailauthenticationclass =
'mw-email-none';
610 if ( $canViewPrivateInfo ) {
611 $defaultPreferences[
'emailauthentication'] = [
614 'section' =>
'personal/email',
615 'label-message' =>
'prefs-emailconfirm-label',
616 'default' => $emailauthenticated,
617 # Apply the same CSS class used on the input to the message:
618 'cssclass' => $emailauthenticationclass,
623 if ( $this->options->get(
'AllowRequiringEmailForResets' ) ) {
624 $defaultPreferences[
'requireemail'] = [
626 'label-message' =>
'tog-requireemail',
627 'help-message' =>
'prefs-help-requireemail',
628 'section' =>
'personal/email',
629 'disabled' => $disableEmailPrefs,
633 if ( $this->options->get(
'EnableUserEmail' ) &&
634 $this->permissionManager->userHasRight( $user,
'sendemail' )
636 $defaultPreferences[
'disablemail'] = [
637 'id' =>
'wpAllowEmail',
640 'section' =>
'personal/email',
641 'label-message' =>
'allowemail',
642 'disabled' => $disableEmailPrefs,
645 $defaultPreferences[
'email-allow-new-users'] = [
646 'id' =>
'wpAllowEmailFromNewUsers',
648 'section' =>
'personal/email',
649 'label-message' =>
'email-allow-new-users-label',
650 'disabled' => $disableEmailPrefs,
653 $defaultPreferences[
'ccmeonemails'] = [
655 'section' =>
'personal/email',
656 'label-message' =>
'tog-ccmeonemails',
657 'disabled' => $disableEmailPrefs,
660 if ( $this->options->get(
'EnableUserEmailBlacklist' ) ) {
661 $defaultPreferences[
'email-blacklist'] = [
662 'type' =>
'usersmultiselect',
663 'label-message' =>
'email-blacklist-label',
664 'section' =>
'personal/email',
665 'disabled' => $disableEmailPrefs,
666 'filter' => MultiUsernameFilter::class,
671 if ( $this->options->get(
'EnotifWatchlist' ) ) {
672 $defaultPreferences[
'enotifwatchlistpages'] = [
674 'section' =>
'personal/email',
675 'label-message' =>
'tog-enotifwatchlistpages',
676 'disabled' => $disableEmailPrefs,
679 if ( $this->options->get(
'EnotifUserTalk' ) ) {
680 $defaultPreferences[
'enotifusertalkpages'] = [
682 'section' =>
'personal/email',
683 'label-message' =>
'tog-enotifusertalkpages',
684 'disabled' => $disableEmailPrefs,
687 if ( $this->options->get(
'EnotifUserTalk' ) ||
688 $this->options->get(
'EnotifWatchlist' ) ) {
689 if ( $this->options->get(
'EnotifMinorEdits' ) ) {
690 $defaultPreferences[
'enotifminoredits'] = [
692 'section' =>
'personal/email',
693 'label-message' =>
'tog-enotifminoredits',
694 'disabled' => $disableEmailPrefs,
698 if ( $this->options->get(
'EnotifRevealEditorAddress' ) ) {
699 $defaultPreferences[
'enotifrevealaddr'] = [
701 'section' =>
'personal/email',
702 'label-message' =>
'tog-enotifrevealaddr',
703 'disabled' => $disableEmailPrefs,
717 # # Skin #####################################
721 if ( $skinOptions ) {
722 $defaultPreferences[
'skin'] = [
724 'options' => $skinOptions,
725 'section' =>
'rendering/skin',
729 $allowUserCss = $this->options->get(
'AllowUserCss' );
730 $allowUserJs = $this->options->get(
'AllowUserJs' );
731 # Create links to user CSS/JS pages for all skins
732 # This code is basically copied from generateSkinOptions(). It'd
733 # be nice to somehow merge this back in there to avoid redundancy.
734 if ( $allowUserCss || $allowUserJs ) {
738 if ( $allowUserCss ) {
739 $cssPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.css' );
740 $cssLinkText =
$context->msg(
'prefs-custom-css' )->text();
741 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
744 if ( $allowUserJs ) {
745 $jsPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.js' );
746 $jsLinkText =
$context->msg(
'prefs-custom-js' )->text();
747 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
750 $defaultPreferences[
'commoncssjs'] = [
753 'default' =>
$context->getLanguage()->pipeList( $linkTools ),
754 'label-message' =>
'prefs-common-config',
755 'section' =>
'rendering/skin',
765 # # Files #####################################
766 $defaultPreferences[
'imagesize'] = [
769 'label-message' =>
'imagemaxsize',
770 'section' =>
'rendering/files',
772 $defaultPreferences[
'thumbsize'] = [
775 'label-message' =>
'thumbsize',
776 'section' =>
'rendering/files',
787 # # Date and time #####################################
789 if ( $dateOptions ) {
790 $defaultPreferences[
'date'] = [
792 'options' => $dateOptions,
793 'section' =>
'rendering/dateformat',
800 $nowlocal =
Xml::element(
'span', [
'id' =>
'wpLocalTime' ],
801 $lang->userTime( $now, $user ) );
802 $nowserver =
$lang->userTime( $now, $user,
803 [
'format' =>
false,
'timecorrection' =>
false ] ) .
804 Html::hidden(
'wpServerTime', (
int)substr( $now, 8, 2 ) * 60 + (
int)substr( $now, 10, 2 ) );
806 $defaultPreferences[
'nowserver'] = [
809 'label-message' =>
'servertime',
810 'default' => $nowserver,
811 'section' =>
'rendering/timeoffset',
814 $defaultPreferences[
'nowlocal'] = [
817 'label-message' =>
'localtime',
818 'default' => $nowlocal,
819 'section' =>
'rendering/timeoffset',
823 $tzOffset = $user->getOption(
'timecorrection' );
824 $tz = explode(
'|', $tzOffset, 3 );
828 $tzSetting = $tzOffset;
829 if ( count( $tz ) > 1 && $tz[0] ==
'ZoneInfo' &&
834 $userTZ =
new DateTimeZone( $tz[2] );
835 $minDiff = floor( $userTZ->getOffset(
new DateTime(
'now' ) ) / 60 );
836 $tzSetting =
"ZoneInfo|$minDiff|{$tz[2]}";
837 }
catch ( Exception $e ) {
842 if ( count( $tz ) > 1 && $tz[0] ==
'Offset' ) {
844 $tzSetting = sprintf(
'%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
847 $defaultPreferences[
'timecorrection'] = [
848 'class' => \HTMLSelectOrOtherField::class,
849 'label-message' =>
'timezonelegend',
850 'options' => $tzOptions,
851 'default' => $tzSetting,
853 'section' =>
'rendering/timeoffset',
854 'id' =>
'wpTimeCorrection',
855 'filter' => TimezoneFilter::class,
856 'placeholder-message' =>
'timezone-useoffset-placeholder',
870 # # Diffs ####################################
871 $defaultPreferences[
'diffonly'] = [
873 'section' =>
'rendering/diffs',
874 'label-message' =>
'tog-diffonly',
876 $defaultPreferences[
'norollbackdiff'] = [
878 'section' =>
'rendering/diffs',
879 'label-message' =>
'tog-norollbackdiff',
882 # # Page Rendering ##############################
883 if ( $this->options->get(
'AllowUserCssPrefs' ) ) {
884 $defaultPreferences[
'underline'] = [
887 $l10n->
msg(
'underline-never' )->text() => 0,
888 $l10n->
msg(
'underline-always' )->text() => 1,
889 $l10n->
msg(
'underline-default' )->text() => 2,
891 'label-message' =>
'tog-underline',
892 'section' =>
'rendering/advancedrendering',
896 $stubThresholdValues = [ 50, 100, 500, 1000, 2000, 5000, 10000 ];
897 $stubThresholdOptions = [ $l10n->
msg(
'stub-threshold-disabled' )->text() => 0 ];
898 foreach ( $stubThresholdValues as $value ) {
899 $stubThresholdOptions[$l10n->
msg(
'size-bytes', $value )->text()] = $value;
902 $defaultPreferences[
'stubthreshold'] = [
904 'section' =>
'rendering/advancedrendering',
905 'options' => $stubThresholdOptions,
907 'label-raw' => $l10n->
msg(
'stub-threshold' )->rawParams(
909 $l10n->
msg(
'stub-threshold-sample-link' )->parse() .
913 $defaultPreferences[
'showhiddencats'] = [
915 'section' =>
'rendering/advancedrendering',
916 'label-message' =>
'tog-showhiddencats'
919 $defaultPreferences[
'numberheadings'] = [
921 'section' =>
'rendering/advancedrendering',
922 'label-message' =>
'tog-numberheadings',
925 if ( $this->permissionManager->userHasRight( $user,
'rollback' ) ) {
926 $defaultPreferences[
'showrollbackconfirmation'] = [
928 'section' =>
'rendering/advancedrendering',
929 'label-message' =>
'tog-showrollbackconfirmation',
940 # # Editing #####################################
941 $defaultPreferences[
'editsectiononrightclick'] = [
943 'section' =>
'editing/advancedediting',
944 'label-message' =>
'tog-editsectiononrightclick',
946 $defaultPreferences[
'editondblclick'] = [
948 'section' =>
'editing/advancedediting',
949 'label-message' =>
'tog-editondblclick',
952 if ( $this->options->get(
'AllowUserCssPrefs' ) ) {
953 $defaultPreferences[
'editfont'] = [
955 'section' =>
'editing/editor',
956 'label-message' =>
'editfont-style',
958 $l10n->
msg(
'editfont-monospace' )->text() =>
'monospace',
959 $l10n->
msg(
'editfont-sansserif' )->text() =>
'sans-serif',
960 $l10n->
msg(
'editfont-serif' )->text() =>
'serif',
965 if ( $this->permissionManager->userHasRight( $user,
'minoredit' ) ) {
966 $defaultPreferences[
'minordefault'] = [
968 'section' =>
'editing/editor',
969 'label-message' =>
'tog-minordefault',
973 $defaultPreferences[
'forceeditsummary'] = [
975 'section' =>
'editing/editor',
976 'label-message' =>
'tog-forceeditsummary',
978 $defaultPreferences[
'useeditwarning'] = [
980 'section' =>
'editing/editor',
981 'label-message' =>
'tog-useeditwarning',
984 $defaultPreferences[
'previewonfirst'] = [
986 'section' =>
'editing/preview',
987 'label-message' =>
'tog-previewonfirst',
989 $defaultPreferences[
'previewontop'] = [
991 'section' =>
'editing/preview',
992 'label-message' =>
'tog-previewontop',
994 $defaultPreferences[
'uselivepreview'] = [
996 'section' =>
'editing/preview',
997 'label-message' =>
'tog-uselivepreview',
1007 $rcMaxAge = $this->options->get(
'RCMaxAge' );
1008 # # RecentChanges #####################################
1009 $defaultPreferences[
'rcdays'] = [
1011 'label-message' =>
'recentchangesdays',
1012 'section' =>
'rc/displayrc',
1014 'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
1015 'help' => $l10n->
msg(
'recentchangesdays-max' )->numParams(
1016 ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped()
1018 $defaultPreferences[
'rclimit'] = [
1022 'label-message' =>
'recentchangescount',
1023 'help-message' =>
'prefs-help-recentchangescount',
1024 'section' =>
'rc/displayrc',
1025 'filter' => IntvalFilter::class,
1027 $defaultPreferences[
'usenewrc'] = [
1029 'label-message' =>
'tog-usenewrc',
1030 'section' =>
'rc/advancedrc',
1032 $defaultPreferences[
'hideminor'] = [
1034 'label-message' =>
'tog-hideminor',
1035 'section' =>
'rc/changesrc',
1037 $defaultPreferences[
'rcfilters-rc-collapsed'] = [
1040 $defaultPreferences[
'rcfilters-wl-collapsed'] = [
1043 $defaultPreferences[
'rcfilters-saved-queries'] = [
1046 $defaultPreferences[
'rcfilters-wl-saved-queries'] = [
1050 $defaultPreferences[
'rcfilters-limit'] = [
1053 $defaultPreferences[
'rcfilters-saved-queries-versionbackup'] = [
1056 $defaultPreferences[
'rcfilters-wl-saved-queries-versionbackup'] = [
1060 if ( $this->options->get(
'RCWatchCategoryMembership' ) ) {
1061 $defaultPreferences[
'hidecategorization'] = [
1063 'label-message' =>
'tog-hidecategorization',
1064 'section' =>
'rc/changesrc',
1069 $defaultPreferences[
'hidepatrolled'] = [
1071 'section' =>
'rc/changesrc',
1072 'label-message' =>
'tog-hidepatrolled',
1077 $defaultPreferences[
'newpageshidepatrolled'] = [
1079 'section' =>
'rc/changesrc',
1080 'label-message' =>
'tog-newpageshidepatrolled',
1084 if ( $this->options->get(
'RCShowWatchingUsers' ) ) {
1085 $defaultPreferences[
'shownumberswatching'] = [
1087 'section' =>
'rc/advancedrc',
1088 'label-message' =>
'tog-shownumberswatching',
1092 $defaultPreferences[
'rcenhancedfilters-disable'] = [
1094 'section' =>
'rc/advancedrc',
1095 'label-message' =>
'rcfilters-preference-label',
1096 'help-message' =>
'rcfilters-preference-help',
1108 $watchlistdaysMax = ceil( $this->options->get(
'RCMaxAge' ) / ( 3600 * 24 ) );
1110 # # Watchlist #####################################
1111 if ( $this->permissionManager->userHasRight( $user,
'editmywatchlist' ) ) {
1112 $editWatchlistLinks =
'';
1113 $editWatchlistModes = [
1114 'edit' => [
'subpage' =>
false,
'flags' => [] ],
1115 'raw' => [
'subpage' =>
'raw',
'flags' => [] ],
1116 'clear' => [
'subpage' =>
'clear',
'flags' => [
'destructive' ] ],
1118 foreach ( $editWatchlistModes as $mode =>
$options ) {
1120 $editWatchlistLinks .=
1121 new \OOUI\ButtonWidget( [
1122 'href' => SpecialPage::getTitleFor(
'EditWatchlist',
$options[
'subpage'] )->getLinkURL(),
1124 'label' =>
new \OOUI\HtmlSnippet(
1125 $context->msg(
"prefs-editwatchlist-{$mode}" )->parse()
1130 $defaultPreferences[
'editwatchlist'] = [
1133 'default' => $editWatchlistLinks,
1134 'label-message' =>
'prefs-editwatchlist-label',
1135 'section' =>
'watchlist/editwatchlist',
1139 $defaultPreferences[
'watchlistdays'] = [
1142 'max' => $watchlistdaysMax,
1143 'section' =>
'watchlist/displaywatchlist',
1144 'help' =>
$context->msg(
'prefs-watchlist-days-max' )->numParams(
1145 $watchlistdaysMax )->escaped(),
1146 'label-message' =>
'prefs-watchlist-days',
1148 $defaultPreferences[
'wllimit'] = [
1152 'label-message' =>
'prefs-watchlist-edits',
1153 'help' =>
$context->msg(
'prefs-watchlist-edits-max' )->escaped(),
1154 'section' =>
'watchlist/displaywatchlist',
1155 'filter' => IntvalFilter::class,
1157 $defaultPreferences[
'extendwatchlist'] = [
1159 'section' =>
'watchlist/advancedwatchlist',
1160 'label-message' =>
'tog-extendwatchlist',
1162 $defaultPreferences[
'watchlisthideminor'] = [
1164 'section' =>
'watchlist/changeswatchlist',
1165 'label-message' =>
'tog-watchlisthideminor',
1167 $defaultPreferences[
'watchlisthidebots'] = [
1169 'section' =>
'watchlist/changeswatchlist',
1170 'label-message' =>
'tog-watchlisthidebots',
1172 $defaultPreferences[
'watchlisthideown'] = [
1174 'section' =>
'watchlist/changeswatchlist',
1175 'label-message' =>
'tog-watchlisthideown',
1177 $defaultPreferences[
'watchlisthideanons'] = [
1179 'section' =>
'watchlist/changeswatchlist',
1180 'label-message' =>
'tog-watchlisthideanons',
1182 $defaultPreferences[
'watchlisthideliu'] = [
1184 'section' =>
'watchlist/changeswatchlist',
1185 'label-message' =>
'tog-watchlisthideliu',
1189 $defaultPreferences[
'watchlistreloadautomatically'] = [
1191 'section' =>
'watchlist/advancedwatchlist',
1192 'label-message' =>
'tog-watchlistreloadautomatically',
1196 $defaultPreferences[
'watchlistunwatchlinks'] = [
1198 'section' =>
'watchlist/advancedwatchlist',
1199 'label-message' =>
'tog-watchlistunwatchlinks',
1202 if ( $this->options->get(
'RCWatchCategoryMembership' ) ) {
1203 $defaultPreferences[
'watchlisthidecategorization'] = [
1205 'section' =>
'watchlist/changeswatchlist',
1206 'label-message' =>
'tog-watchlisthidecategorization',
1211 $defaultPreferences[
'watchlisthidepatrolled'] = [
1213 'section' =>
'watchlist/changeswatchlist',
1214 'label-message' =>
'tog-watchlisthidepatrolled',
1219 'edit' =>
'watchdefault',
1220 'move' =>
'watchmoves',
1221 'delete' =>
'watchdeletion'
1225 if ( $this->permissionManager->userHasAnyRight( $user,
'createpage',
'createtalk' ) ) {
1226 $watchTypes[
'read'] =
'watchcreations';
1229 if ( $this->permissionManager->userHasRight( $user,
'rollback' ) ) {
1230 $watchTypes[
'rollback'] =
'watchrollback';
1233 if ( $this->permissionManager->userHasRight( $user,
'upload' ) ) {
1234 $watchTypes[
'upload'] =
'watchuploads';
1237 foreach ( $watchTypes as $action => $pref ) {
1238 if ( $this->permissionManager->userHasRight( $user, $action ) ) {
1242 $defaultPreferences[$pref] = [
1244 'section' =>
'watchlist/pageswatchlist',
1245 'label-message' =>
"tog-$pref",
1250 $defaultPreferences[
'watchlisttoken'] = [
1254 $tokenButton = new \OOUI\ButtonWidget( [
1255 'href' => SpecialPage::getTitleFor(
'ResetTokens' )->getLinkURL( [
1256 'returnto' => SpecialPage::getTitleFor(
'Preferences' )->getPrefixedText()
1258 'label' =>
$context->msg(
'prefs-watchlist-managetokens' )->text(),
1260 $defaultPreferences[
'watchlisttoken-info'] = [
1262 'section' =>
'watchlist/tokenwatchlist',
1263 'label-message' =>
'prefs-watchlist-token',
1264 'help-message' =>
'prefs-help-tokenmanagement',
1266 'default' => (string)$tokenButton,
1269 $defaultPreferences[
'wlenhancedfilters-disable'] = [
1271 'section' =>
'watchlist/advancedwatchlist',
1272 'label-message' =>
'rcfilters-watchlist-preference-label',
1273 'help-message' =>
'rcfilters-watchlist-preference-help',
1281 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1282 $defaultPreferences[
'searchNs' . $n] = [
1296 $mptitle = Title::newMainPage();
1297 $previewtext =
$context->msg(
'skin-preview' )->escaped();
1299 # Only show skins that aren't disabled in $wgSkipSkins
1304 $useSkin =
$context->getRequest()->getRawVal(
'useskin' );
1305 if ( isset( $allInstalledSkins[$useSkin] )
1306 &&
$context->msg(
"skinname-$useSkin" )->exists()
1308 $validSkinNames[$useSkin] = $useSkin;
1312 $currentUserSkin = $user->
getOption(
'skin' );
1313 if ( isset( $allInstalledSkins[$currentUserSkin] )
1314 &&
$context->msg(
"skinname-$currentUserSkin" )->exists()
1316 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1319 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1320 $msg =
$context->msg(
"skinname-{$skinkey}" );
1321 if ( $msg->exists() ) {
1322 $skinname = htmlspecialchars( $msg->text() );
1326 $defaultSkin = $this->options->get(
'DefaultSkin' );
1327 $allowUserCss = $this->options->get(
'AllowUserCss' );
1328 $allowUserJs = $this->options->get(
'AllowUserJs' );
1330 # Sort by the internal name, so that the ordering is the same for each display language,
1331 # especially if some skin names are translated to use a different alphabet and some are not.
1332 uksort( $validSkinNames,
function ( $a, $b ) use ( $defaultSkin ) {
1333 # Display the default first in the list by comparing it as lesser than any other.
1334 if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1337 if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1340 return strcasecmp( $a, $b );
1343 $foundDefault =
false;
1344 foreach ( $validSkinNames as $skinkey => $sn ) {
1347 # Mark the default skin
1348 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1349 $linkTools[] =
$context->msg(
'default' )->escaped();
1350 $foundDefault =
true;
1353 # Create preview link
1354 $mplink = htmlspecialchars( $mptitle->getLocalURL( [
'useskin' => $skinkey ] ) );
1355 $linkTools[] =
"<a target='_blank' href=\"$mplink\">$previewtext</a>";
1357 # Create links to user CSS/JS pages
1358 if ( $allowUserCss ) {
1359 $cssPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.css' );
1360 $cssLinkText =
$context->msg(
'prefs-custom-css' )->text();
1361 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1364 if ( $allowUserJs ) {
1365 $jsPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.js' );
1366 $jsLinkText =
$context->msg(
'prefs-custom-js' )->text();
1367 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1370 $display = $sn .
' ' .
$context->msg(
'parentheses' )
1371 ->rawParams(
$context->getLanguage()->pipeList( $linkTools ) )
1373 $ret[$display] = $skinkey;
1376 if ( !$foundDefault ) {
1391 $dateopts =
$lang->getDatePreferences();
1396 if ( !in_array(
'default', $dateopts ) ) {
1397 $dateopts[] =
'default';
1407 foreach ( $dateopts as $key ) {
1408 if ( $key ==
'default' ) {
1409 $formatted =
$context->msg(
'datedefault' )->escaped();
1411 $formatted = htmlspecialchars(
$lang->timeanddate( $epoch,
false, $key ) );
1413 $ret[$formatted] = $key;
1425 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1427 foreach ( $this->options->get(
'ImageLimits' ) as $index => $limits ) {
1429 $display =
"{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1430 $ret[$display] = $index;
1442 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1444 foreach ( $this->options->get(
'ThumbLimits' ) as $index => $size ) {
1445 $display = $size . $pixels;
1446 $ret[$display] = $index;
1459 $maxSigChars = $this->options->get(
'MaxSigChars' );
1460 if ( mb_strlen( $signature ) > $maxSigChars ) {
1462 $form->
msg(
'badsiglength' )->numParams( $maxSigChars )->text() );
1463 } elseif ( isset( $alldata[
'fancysig'] ) &&
1464 $alldata[
'fancysig'] &&
1469 [
'class' =>
'error' ],
1470 $form->
msg(
'badsig' )->text()
1485 if ( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) {
1486 $signature = $parser->cleanSig( $signature );
1505 $formClass = PreferencesFormOOUI::class,
1509 $context->getOutput()->enableOOUI();
1512 if ( count( $remove ) ) {
1513 $removeKeys = array_flip( $remove );
1514 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1518 foreach ( $formDescriptor as $name => $info ) {
1519 if ( isset( $info[
'type'] ) && $info[
'type'] ===
'api' ) {
1520 unset( $formDescriptor[$name] );
1527 $htmlForm =
new $formClass( $formDescriptor,
$context,
'prefs' );
1533 $htmlForm->setAction(
$context->getTitle()->getLocalURL( [
1534 'useskin' =>
$context->getRequest()->getRawVal(
'useskin' )
1537 $htmlForm->setModifiedUser( $user );
1538 $htmlForm->setOptionsEditable( $this->permissionManager
1539 ->userHasRight( $user,
'editmyoptions' ) );
1540 $htmlForm->setPrivateInfoEditable( $this->permissionManager
1541 ->userHasRight( $user,
'editmyprivateinfo' ) );
1542 $htmlForm->setId(
'mw-prefs-form' );
1543 $htmlForm->setAutocomplete(
'off' );
1544 $htmlForm->setSubmitText(
$context->msg(
'saveprefs' )->text() );
1545 # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1546 $htmlForm->setSubmitTooltip(
'preferences-save' );
1547 $htmlForm->setSubmitID(
'prefcontrol' );
1548 $htmlForm->setSubmitCallback(
1550 return $this->
submitForm( $formData, $form, $formDescriptor );
1564 $localTZoffset = $this->options->get(
'LocalTZoffset' );
1569 if ( $localTZoffset == $timestamp->format(
'Z' ) / 60 ) {
1570 $timezoneName = $timestamp->getTimezone()->getName();
1572 if ( isset( $timeZoneList[$timezoneName] ) ) {
1573 $timezoneName = $timeZoneList[$timezoneName][
'name'];
1576 'timezoneuseserverdefault',
1580 $tzstring = sprintf(
1582 floor( $localTZoffset / 60 ),
1583 abs( $localTZoffset ) % 60
1585 $server_tz_msg =
$context->msg(
'timezoneuseserverdefault', $tzstring )->text();
1587 $opt[$server_tz_msg] =
"System|$localTZoffset";
1588 $opt[
$context->msg(
'timezoneuseoffset' )->text()] =
'other';
1589 $opt[
$context->msg(
'guesstimezone' )->text()] =
'guess';
1591 foreach ( $timeZoneList as $timeZoneInfo ) {
1592 $region = $timeZoneInfo[
'region'];
1593 if ( !isset( $opt[$region] ) ) {
1596 $opt[$region][$timeZoneInfo[
'name']] = $timeZoneInfo[
'timecorrection'];
1611 $hiddenPrefs = $this->options->get(
'HiddenPrefs' );
1614 if ( !$this->permissionManager
1615 ->userHasAnyRight( $user,
'editmyprivateinfo',
'editmyoptions' )
1617 return Status::newFatal(
'mypreferencesprotected' );
1621 $this->
applyFilters( $formData, $formDescriptor,
'filterFromForm' );
1626 if ( !in_array(
'realname', $hiddenPrefs )
1627 && $this->permissionManager->userHasRight( $user,
'editmyprivateinfo' )
1628 && array_key_exists(
'realname', $formData )
1630 $realName = $formData[
'realname'];
1631 $user->setRealName( $realName );
1634 if ( $this->permissionManager->userHasRight( $user,
'editmyoptions' ) ) {
1635 $oldUserOptions = $user->getOptions();
1638 unset( $formData[$b] );
1641 # If users have saved a value for a preference which has subsequently been disabled
1642 # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1643 # is subsequently re-enabled
1644 foreach ( $hiddenPrefs as $pref ) {
1645 # If the user has not set a non-default value here, the default will be returned
1646 # and subsequently discarded
1647 $formData[$pref] = $user->getOption( $pref,
null,
true );
1652 isset( $formData[
'rclimit'] ) &&
1653 intval( $formData[
'rclimit' ] ) !== $user->getIntOption(
'rclimit' )
1655 $formData[
'rcfilters-limit'] = $formData[
'rclimit'];
1659 $user->resetOptions(
'unused', $form->
getContext() );
1661 foreach ( $formData as $key => $value ) {
1662 $user->setOption( $key, $value );
1666 'PreferencesFormPreSave',
1667 [ $formData, $form, $user, &$result, $oldUserOptions ]
1671 $user->saveSettings();
1684 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1685 foreach ( $formDescriptor as $preference => $desc ) {
1686 if ( !isset( $desc[
'filter'] ) || !isset( $preferences[$preference] ) ) {
1689 $filterDesc = $desc[
'filter'];
1690 if ( $filterDesc instanceof
Filter ) {
1692 } elseif ( class_exists( $filterDesc ) ) {
1694 } elseif ( is_callable( $filterDesc ) ) {
1697 throw new UnexpectedValueException(
1698 "Unrecognized filter type for preference '$preference'"
1701 $preferences[$preference] =
$filter->$verb( $preferences[$preference] );
1716 array $formDescriptor
1720 if (
$res ===
true ) {
1724 if (
$res ===
'eauth' ) {
1725 $urlOptions[
'eauth'] = 1;
1730 $url = $form->
getTitle()->getFullURL( $urlOptions );
1733 $context->getRequest()->getSession()->set(
'specialPreferencesSaveSuccess', 1 );
1735 $context->getOutput()->redirect( $url );
1738 return (
$res ===
true ? Status::newGood() :
$res );
1750 $identifiers = DateTimeZone::listIdentifiers();
1752 if ( $identifiers ===
false ) {
1755 sort( $identifiers );
1758 'Africa' =>
wfMessage(
'timezoneregion-africa' )->inLanguage( $language )->text(),
1759 'America' =>
wfMessage(
'timezoneregion-america' )->inLanguage( $language )->text(),
1760 'Antarctica' =>
wfMessage(
'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1761 'Arctic' =>
wfMessage(
'timezoneregion-arctic' )->inLanguage( $language )->text(),
1762 'Asia' =>
wfMessage(
'timezoneregion-asia' )->inLanguage( $language )->text(),
1763 'Atlantic' =>
wfMessage(
'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1764 'Australia' =>
wfMessage(
'timezoneregion-australia' )->inLanguage( $language )->text(),
1765 'Europe' =>
wfMessage(
'timezoneregion-europe' )->inLanguage( $language )->text(),
1766 'Indian' =>
wfMessage(
'timezoneregion-indian' )->inLanguage( $language )->text(),
1767 'Pacific' =>
wfMessage(
'timezoneregion-pacific' )->inLanguage( $language )->text(),
1769 asort( $tzRegions );
1773 $now =
new DateTime();
1775 foreach ( $identifiers as $identifier ) {
1776 $parts = explode(
'/', $identifier, 2 );
1781 if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1786 $parts[0] = $tzRegions[$parts[0]];
1788 $dateTimeZone =
new DateTimeZone( $identifier );
1789 $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1791 $display = str_replace(
'_',
' ', $parts[0] .
'/' . $parts[1] );
1792 $value =
"ZoneInfo|$minDiff|$identifier";
1794 $timeZoneList[$identifier] = [
1796 'timecorrection' => $value,
1797 'region' => $parts[0],
1801 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.
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.
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Base class for language conversion.
static array $languagesWithVariants
languages supporting variants
Internationalisation code.
Library for creating and parsing MW-style timestamps.
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
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.
The main skin class which provides methods and properties for all other skins.
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
static getSkinNames()
Fetch the set of available skins.
Parent class for all special pages.
static checkStructuredFilterUiEnabled( $user)
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.
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
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.
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.
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.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
if(!isset( $args[0])) $lang
switch( $options['output']) $languages