62use Psr\Log\LoggerAwareTrait;
63use Psr\Log\NullLogger;
65use UnexpectedValueException;
96 private $languageConverter;
105 private $languageConverterFactory;
108 private $parserFactory;
111 private $skinFactory;
114 private $userGroupManager;
117 private $signatureValidatorFactory;
205 $this->logger =
new NullLogger();
206 $this->languageConverter = $languageConverter;
208 $this->hookRunner =
new HookRunner( $hookContainer );
212 $services =
static function () {
218 : $services()->getUserOptionsManager();
219 $this->languageConverterFactory = $languageConverterFactory ?? $services()->getLanguageConverterFactory();
221 $this->parserFactory = $parserFactory ?? $services()->getParserFactory();
222 $this->skinFactory = $skinFactory ?? $services()->getSkinFactory();
223 $this->userGroupManager = $userGroupManager ?? $services()->getUserGroupManager();
224 $this->signatureValidatorFactory = $signatureValidatorFactory
225 ?? $services()->getSignatureValidatorFactory();
226 $this->config = $config ?? $services()->getMainConfig();
248 OutputPage::setupOOUI(
249 strtolower( $context->
getSkin()->getSkinName() ),
263 $this->hookRunner->onGetPreferences( $user, $preferences );
265 $this->loadPreferenceValues( $user, $context, $preferences );
266 $this->logger->debug(
"Created form descriptor for user '{$user->getName()}'" );
277 foreach ( $descriptor as $name => &$params ) {
280 if ( ( isset( $params[
'type'] ) && $params[
'type'] ===
'info' ) ||
281 ( isset( $params[
'class'] ) && $params[
'class'] === \HTMLInfoField::class )
283 unset( $descriptor[$name] );
288 foreach ( $params as $key => $value ) {
291 case 'options-message':
294 case 'options-messages':
295 unset( $params[$key] );
296 $params[
'options'] = $value;
299 if ( preg_match(
'/-messages?$/', $key ) ) {
301 unset( $params[$key] );
317 private function loadPreferenceValues(
User $user,
IContextSource $context, &$defaultPreferences ) {
320 unset( $defaultPreferences[$pref] );
325 $form =
new HTMLForm( $simplified, $context );
327 $disable = !$user->
isAllowed(
'editmyoptions' );
329 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
330 $userOptions = $this->userOptionsManager->getOptions( $user );
331 $this->
applyFilters( $userOptions, $defaultPreferences,
'filterForForm' );
333 foreach ( $simplified as $name => $_ ) {
334 $info = &$defaultPreferences[$name];
336 $info[
'disabled'] =
'disabled';
338 if ( isset( $info[
'default'] ) ) {
342 $field = $form->getField( $name );
343 $globalDefault = $defaultOptions[$name] ??
null;
344 $prefFromUser = static::getPreferenceForField( $name, $field, $userOptions );
349 if ( $prefFromUser !==
null &&
350 $field->validate( $prefFromUser, $this->userOptionsManager->getOptions( $user ) ) ===
true ) {
351 $info[
'default'] = $prefFromUser;
352 } elseif ( $field->validate( $globalDefault, $this->userOptionsManager->getOptions( $user ) ) ===
true ) {
353 $info[
'default'] = $globalDefault;
355 $globalDefault = json_encode( $globalDefault );
357 "Default '$globalDefault' is invalid for preference $name of user " . $user->
getName()
362 return $defaultPreferences;
376 $val = $userOptions[$name] ??
null;
380 $prefix = $field->mParams[
'prefix'] ?? $name;
382 $keys = array_keys( $field->filterDataForSubmit( [] ) );
383 foreach ( $keys as $key ) {
384 if ( $userOptions[$prefix . $key] ??
false ) {
403 $val = $userOptions[$name] ??
null;
406 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
407 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLMultiSelectField::class ) ) {
409 $prefix = $info[
'prefix'] ?? $name;
413 if ( $userOptions[
"$prefix$value"] ??
false ) {
420 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
421 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLCheckMatrix::class ) ) {
424 $prefix = $info[
'prefix'] ?? $name;
427 foreach ( $columns as $column ) {
428 foreach ( $rows as $row ) {
429 if ( $userOptions[
"$prefix$column-$row"] ??
false ) {
430 $val[] =
"$column-$row";
453 $defaultPreferences[
'username'] = [
455 'label-message' => [
'username', $userName ],
456 'default' => $userName,
457 'section' =>
'personal/info',
463 $userEffectiveGroups = array_diff(
464 $this->userGroupManager->getUserEffectiveGroups( $user ),
467 $defaultPreferences[
'usergroups'] = [
469 'label-message' => [
'prefs-memberingroups',
470 \Message::numParam( count( $userEffectiveGroups ) ), $userName ],
471 'default' =>
function () use ( $user, $userEffectiveGroups, $context, $lang, $userName ) {
472 $userGroupMemberships = $this->userGroupManager->getUserGroupMemberships( $user );
473 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
474 foreach ( $userEffectiveGroups as $ueg ) {
475 $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
477 $userG = UserGroupMembership::getLinkHTML( $groupStringOrObject, $context );
478 $userM = UserGroupMembership::getLinkHTML( $groupStringOrObject, $context, $userName );
487 $userTempGroups[] = $userG;
488 $userTempMembers[] = $userM;
490 $userGroups[] = $userG;
491 $userMembers[] = $userM;
495 sort( $userMembers );
496 sort( $userTempGroups );
497 sort( $userTempMembers );
498 $userGroups = array_merge( $userTempGroups, $userGroups );
499 $userMembers = array_merge( $userTempMembers, $userMembers );
500 return $context->
msg(
'prefs-memberingroups-type' )
501 ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
505 'section' =>
'personal/info',
509 $formattedEditCount = $lang->formatNum( $user->
getEditCount() );
510 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
512 $defaultPreferences[
'editcount'] = [
515 'label-message' =>
'prefs-edits',
516 'default' => $editCount,
517 'section' =>
'personal/info',
521 $displayUser = $context->
getUser();
523 $defaultPreferences[
'registrationdate'] = [
525 'label-message' =>
'prefs-registration',
526 'default' => $context->
msg(
527 'prefs-registration-date-time',
528 $lang->userTimeAndDate( $userRegistration, $displayUser ),
529 $lang->userDate( $userRegistration, $displayUser ),
530 $lang->userTime( $userRegistration, $displayUser )
532 'section' =>
'personal/info',
536 $canViewPrivateInfo = $user->
isAllowed(
'viewmyprivateinfo' );
537 $canEditPrivateInfo = $user->
isAllowed(
'editmyprivateinfo' );
540 $defaultPreferences[
'realname'] = [
542 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'realname' )
545 'section' =>
'personal/info',
546 'label-message' =>
'yourrealname',
547 'help-message' =>
'prefs-help-realname',
550 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
553 $defaultPreferences[
'password'] = [
556 'default' => (string)
new \OOUI\ButtonWidget( [
560 'label' => $context->
msg(
'prefs-resetpass' )->text(),
562 'label-message' =>
'yourpassword',
566 ? $context->
msg(
'prefs-help-yourpassword',
567 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
569 'section' =>
'personal/info',
576 $defaultPreferences[
'prefershttps'] = [
578 'label-message' =>
'tog-prefershttps',
579 'help-message' =>
'prefs-help-prefershttps',
580 'section' =>
'personal/info'
584 $defaultPreferences[
'downloaduserdata'] = [
587 'label-message' =>
'prefs-user-downloaddata-label',
588 'default' => Html::element(
592 '/api.php?action=query&meta=userinfo&uiprop=*&formatversion=2',
594 $context->
msg(
'prefs-user-downloaddata-info' )->text()
596 'help-message' => [
'prefs-user-downloaddata-help-message', urlencode( $user->
getTitleKey() ) ],
597 'section' =>
'personal/info',
600 $defaultPreferences[
'restoreprefs'] = [
603 'label-message' =>
'prefs-user-restoreprefs-label',
604 'default' => Html::element(
608 ->getSubpage(
'reset' )->getLocalURL()
610 $context->
msg(
'prefs-user-restoreprefs-info' )->text()
612 'section' =>
'personal/info',
615 $languages = $this->languageNameUtils->getLanguageNames(
616 LanguageNameUtils::AUTONYMS,
617 LanguageNameUtils::SUPPORTED
620 if ( !array_key_exists( $languageCode, $languages ) ) {
621 $languages[$languageCode] = $languageCode;
627 foreach ( $languages as $code => $name ) {
628 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
631 $defaultPreferences[
'language'] = [
633 'section' =>
'personal/i18n',
635 'label-message' =>
'yourlanguage',
638 $neutralGenderMessage = $context->
msg(
'gender-notknown' )->escaped() . (
639 !$context->
msg(
'gender-unknown' )->isDisabled()
640 ?
"<br>" . $context->
msg(
'parentheses' )
641 ->params( $context->
msg(
'gender-unknown' )->plain() )
646 $defaultPreferences[
'gender'] = [
648 'section' =>
'personal/i18n',
650 $neutralGenderMessage =>
'unknown',
651 $context->
msg(
'gender-female' )->escaped() =>
'female',
652 $context->
msg(
'gender-male' )->escaped() =>
'male',
654 'label-message' =>
'yourgender',
655 'help-message' =>
'prefs-help-gender',
659 if ( !$this->languageConverterFactory->isConversionDisabled() ) {
661 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
662 if ( $langCode == $this->contLang->getCode() ) {
663 if ( !$this->languageConverter->hasVariants() ) {
667 $variants = $this->languageConverter->getVariants();
669 foreach ( $variants as $v ) {
670 $v = str_replace(
'_',
'-', strtolower( $v ) );
671 $variantArray[$v] = $lang->getVariantname( $v,
false );
675 foreach ( $variantArray as $code => $name ) {
676 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
680 $defaultPreferences[
'variant'] = [
681 'label-message' =>
'yourvariant',
684 'section' =>
'personal/i18n',
685 'help-message' =>
'prefs-help-variant',
688 $defaultPreferences[
"variant-$langCode"] = [
696 $oldsigWikiText = $this->parserFactory->getInstance()->preSaveTransform(
700 ParserOptions::newFromContext( $context )
703 $context->
getOutput()->parseAsContent( $oldsigWikiText )
705 $signatureFieldConfig = [];
707 $signature = $this->userOptionsManager->getOption( $user,
'nickname' );
708 $useFancySig = $this->userOptionsManager->getBoolOption( $user,
'fancysig' );
709 if ( $useFancySig && $signature !==
'' ) {
710 $parserOpts = ParserOptions::newFromContext( $context );
711 $validator = $this->signatureValidatorFactory
712 ->newSignatureValidator( $user, $context, $parserOpts );
713 $signatureErrors = $validator->validateSignature( $signature );
714 if ( $signatureErrors ) {
716 $oldsigHTML .=
'<p><strong>' .
721 $context->
msg(
"prefs-signature-invalid-$sigValidation" )->parse() .
726 foreach ( $signatureErrors as &$sigError ) {
727 $sigError = new \OOUI\HtmlSnippet( $sigError );
730 $signatureFieldConfig = [
731 'warnings' => $sigValidation !==
'disallow' ? $signatureErrors :
null,
732 'errors' => $sigValidation ===
'disallow' ? $signatureErrors :
null,
737 $defaultPreferences[
'oldsig'] = [
742 'default' => new \OOUI\FieldLayout(
743 new \OOUI\LabelWidget( [
744 'label' =>
new \OOUI\HtmlSnippet( $oldsigHTML ),
748 'label' =>
new \OOUI\HtmlSnippet( $context->
msg(
'tog-oldsig' )->parse() )
749 ] + $signatureFieldConfig
751 'section' =>
'personal/signature',
753 $defaultPreferences[
'nickname'] = [
754 'type' => $this->authManager->allowsPropertyChange(
'nickname' ) ?
'text' :
'info',
756 'label-message' =>
'yournick',
757 'validation-callback' =>
function ( $signature, $alldata,
HTMLForm $form ) {
760 'section' =>
'personal/signature',
761 'filter-callback' =>
function ( $signature, array $alldata,
HTMLForm $form ) {
765 $defaultPreferences[
'fancysig'] = [
767 'label-message' =>
'tog-fancysig',
769 'help-message' =>
'prefs-help-signature',
770 'section' =>
'personal/signature'
775 if ( $canViewPrivateInfo ) {
778 ?
'prefs-help-email-required'
779 :
'prefs-help-email';
783 $helpMessages[] =
'prefs-help-email-others';
786 $emailAddress = $user->
getEmail() ? htmlspecialchars( $user->
getEmail() ) :
'';
787 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'emailaddress' ) ) {
788 $button = new \OOUI\ButtonWidget( [
793 $context->
msg( $user->
getEmail() ?
'prefs-changeemail' :
'prefs-setemail' )->text(),
796 $emailAddress .= $emailAddress ==
'' ? $button : (
'<br />' . $button );
799 $defaultPreferences[
'emailaddress'] = [
802 'default' => $emailAddress,
803 'label-message' =>
'youremail',
804 'section' =>
'personal/email',
805 'help-messages' => $helpMessages,
810 $disableEmailPrefs =
false;
813 $defaultPreferences[
'requireemail'] = [
815 'label-message' =>
'tog-requireemail',
816 'help-message' =>
'prefs-help-requireemail',
817 'section' =>
'personal/email',
828 $displayUser = $context->
getUser();
830 $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
831 $d = $lang->userDate( $emailTimestamp, $displayUser );
832 $t = $lang->userTime( $emailTimestamp, $displayUser );
833 $emailauthenticated = $context->
msg(
'emailauthenticated',
834 $time, $d, $t )->parse() .
'<br />';
835 $emailauthenticationclass =
'mw-email-authenticated';
837 $disableEmailPrefs =
true;
838 $emailauthenticated = $context->
msg(
'emailnotauthenticated' )->parse() .
'<br />' .
839 new \OOUI\ButtonWidget( [
841 'label' => $context->
msg(
'emailconfirmlink' )->text(),
843 $emailauthenticationclass =
"mw-email-not-authenticated";
846 $disableEmailPrefs =
true;
847 $emailauthenticated = $context->
msg(
'noemailprefs' )->escaped();
848 $emailauthenticationclass =
'mw-email-none';
851 if ( $canViewPrivateInfo ) {
852 $defaultPreferences[
'emailauthentication'] = [
855 'section' =>
'personal/email',
856 'label-message' =>
'prefs-emailconfirm-label',
857 'default' => $emailauthenticated,
859 'cssclass' => $emailauthenticationclass,
867 $defaultPreferences[
'disablemail'] = [
868 'id' =>
'wpAllowEmail',
871 'section' =>
'personal/email',
872 'label-message' =>
'allowemail',
873 'disabled' => $disableEmailPrefs,
876 $defaultPreferences[
'email-allow-new-users'] = [
877 'id' =>
'wpAllowEmailFromNewUsers',
879 'section' =>
'personal/email',
880 'label-message' =>
'email-allow-new-users-label',
881 'disabled' => $disableEmailPrefs,
882 'disable-if' => [
'!==',
'disablemail',
'1' ],
885 $defaultPreferences[
'ccmeonemails'] = [
887 'section' =>
'personal/email',
888 'label-message' =>
'tog-ccmeonemails',
889 'disabled' => $disableEmailPrefs,
893 $defaultPreferences[
'email-blacklist'] = [
894 'type' =>
'usersmultiselect',
895 'label-message' =>
'email-mutelist-label',
896 'section' =>
'personal/email',
897 'disabled' => $disableEmailPrefs,
898 'filter' => MultiUsernameFilter::class,
904 $defaultPreferences[
'enotifwatchlistpages'] = [
906 'section' =>
'personal/email',
907 'label-message' =>
'tog-enotifwatchlistpages',
908 'disabled' => $disableEmailPrefs,
912 $defaultPreferences[
'enotifusertalkpages'] = [
914 'section' =>
'personal/email',
915 'label-message' =>
'tog-enotifusertalkpages',
916 'disabled' => $disableEmailPrefs,
922 $defaultPreferences[
'enotifminoredits'] = [
924 'section' =>
'personal/email',
925 'label-message' =>
'tog-enotifminoredits',
926 'disabled' => $disableEmailPrefs,
931 $defaultPreferences[
'enotifrevealaddr'] = [
933 'section' =>
'personal/email',
934 'label-message' =>
'tog-enotifrevealaddr',
935 'disabled' => $disableEmailPrefs,
950 $validSkinNames = $this->getValidSkinNames( $user, $context );
951 if ( $validSkinNames ) {
952 $defaultPreferences[
'skin'] = [
955 'section' =>
'rendering/skin',
957 $hideCond = [
'AND' ];
958 foreach ( $validSkinNames as $skinName => $_ ) {
959 $options = $this->skinFactory->getSkinOptions( $skinName );
960 if (
$options[
'responsive'] ??
false ) {
961 $hideCond[] = [
'!==',
'skin', $skinName ];
964 if ( $hideCond === [
'AND' ] ) {
967 $defaultPreferences[
'skin-responsive'] = [
969 'label-message' =>
'prefs-skin-responsive',
970 'section' =>
'rendering/skin/skin-prefs',
971 'help-message' =>
'prefs-help-skin-responsive',
972 'hide-if' => $hideCond,
978 $safeMode = $this->userOptionsManager->getOption( $user,
'forcesafemode' );
982 if ( $allowUserCss || $allowUserJs ) {
984 $defaultPreferences[
'customcssjs-safemode'] = [
987 'default' => Html::warningBox( $context->
msg(
'prefs-custom-cssjs-safemode' )->parse() ),
988 'section' =>
'rendering/skin',
994 if ( $allowUserCss ) {
995 $cssPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.css' );
996 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
997 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1000 if ( $allowUserJs ) {
1001 $jsPage = Title::makeTitleSafe(
NS_USER, $userName .
'/common.js' );
1002 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
1003 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1006 $defaultPreferences[
'commoncssjs'] = [
1009 'default' => $context->
getLanguage()->pipeList( $linkTools ),
1010 'label-message' =>
'prefs-common-config',
1011 'section' =>
'rendering/skin',
1022 $defaultPreferences[
'imagesize'] = [
1025 'label-message' =>
'imagemaxsize',
1026 'section' =>
'rendering/files',
1028 $defaultPreferences[
'thumbsize'] = [
1031 'label-message' =>
'thumbsize',
1032 'section' =>
'rendering/files',
1046 if ( $dateOptions ) {
1047 $defaultPreferences[
'date'] = [
1049 'options' => $dateOptions,
1050 'section' =>
'rendering/dateformat',
1057 $nowlocal = Xml::element(
'span', [
'id' =>
'wpLocalTime' ],
1058 $lang->userTime( $now, $user ) );
1059 $nowserver = $lang->userTime( $now, $user,
1060 [
'format' =>
false,
'timecorrection' =>
false ] ) .
1061 Html::hidden(
'wpServerTime', (
int)substr( $now, 8, 2 ) * 60 + (
int)substr( $now, 10, 2 ) );
1063 $defaultPreferences[
'nowserver'] = [
1066 'label-message' =>
'servertime',
1067 'default' => $nowserver,
1068 'section' =>
'rendering/timeoffset',
1071 $defaultPreferences[
'nowlocal'] = [
1074 'label-message' =>
'localtime',
1075 'default' => $nowlocal,
1076 'section' =>
'rendering/timeoffset',
1079 $userTimeCorrection = (string)$this->userOptionsManager->getOption( $user,
'timecorrection' );
1084 $userTimeCorrection,
1089 if ( $userTimeCorrectionObj->getCorrectionType() === UserTimeCorrection::OFFSET ) {
1090 $tzDefault = UserTimeCorrection::formatTimezoneOffset( $userTimeCorrectionObj->getTimeOffset() );
1092 $tzDefault = $userTimeCorrectionObj->toString();
1095 $defaultPreferences[
'timecorrection'] = [
1096 'type' =>
'timezone',
1097 'label-message' =>
'timezonelegend',
1098 'default' => $tzDefault,
1100 'section' =>
'rendering/timeoffset',
1101 'id' =>
'wpTimeCorrection',
1102 'filter' => TimezoneFilter::class,
1114 &$defaultPreferences
1117 $defaultPreferences[
'diffonly'] = [
1119 'section' =>
'rendering/diffs',
1120 'label-message' =>
'tog-diffonly',
1122 $defaultPreferences[
'norollbackdiff'] = [
1124 'section' =>
'rendering/diffs',
1125 'label-message' =>
'tog-norollbackdiff',
1127 $defaultPreferences[
'diff-type'] = [
1133 $defaultPreferences[
'underline'] = [
1136 $l10n->
msg(
'underline-never' )->text() => 0,
1137 $l10n->
msg(
'underline-always' )->text() => 1,
1138 $l10n->
msg(
'underline-default' )->text() => 2,
1140 'label-message' =>
'tog-underline',
1141 'section' =>
'rendering/advancedrendering',
1145 $defaultPreferences[
'showhiddencats'] = [
1147 'section' =>
'rendering/advancedrendering',
1148 'label-message' =>
'tog-showhiddencats'
1152 $defaultPreferences[
'showrollbackconfirmation'] = [
1154 'section' =>
'rendering/advancedrendering',
1155 'label-message' =>
'tog-showrollbackconfirmation',
1159 $defaultPreferences[
'forcesafemode'] = [
1161 'section' =>
'rendering/advancedrendering',
1162 'label-message' =>
'tog-forcesafemode',
1163 'help-message' =>
'prefs-help-forcesafemode'
1173 $defaultPreferences[
'editsectiononrightclick'] = [
1175 'section' =>
'editing/advancedediting',
1176 'label-message' =>
'tog-editsectiononrightclick',
1178 $defaultPreferences[
'editondblclick'] = [
1180 'section' =>
'editing/advancedediting',
1181 'label-message' =>
'tog-editondblclick',
1185 $defaultPreferences[
'editfont'] = [
1187 'section' =>
'editing/editor',
1188 'label-message' =>
'editfont-style',
1190 $l10n->
msg(
'editfont-monospace' )->text() =>
'monospace',
1191 $l10n->
msg(
'editfont-sansserif' )->text() =>
'sans-serif',
1192 $l10n->
msg(
'editfont-serif' )->text() =>
'serif',
1197 if ( $user->
isAllowed(
'minoredit' ) ) {
1198 $defaultPreferences[
'minordefault'] = [
1200 'section' =>
'editing/editor',
1201 'label-message' =>
'tog-minordefault',
1205 $defaultPreferences[
'forceeditsummary'] = [
1207 'section' =>
'editing/editor',
1208 'label-message' =>
'tog-forceeditsummary',
1210 $defaultPreferences[
'useeditwarning'] = [
1212 'section' =>
'editing/editor',
1213 'label-message' =>
'tog-useeditwarning',
1216 $defaultPreferences[
'previewonfirst'] = [
1218 'section' =>
'editing/preview',
1219 'label-message' =>
'tog-previewonfirst',
1221 $defaultPreferences[
'previewontop'] = [
1223 'section' =>
'editing/preview',
1224 'label-message' =>
'tog-previewontop',
1226 $defaultPreferences[
'uselivepreview'] = [
1228 'section' =>
'editing/preview',
1229 'label-message' =>
'tog-uselivepreview',
1240 $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1241 $defaultPreferences[
'rcdays'] = [
1243 'label-message' =>
'recentchangesdays',
1244 'section' =>
'rc/displayrc',
1249 $defaultPreferences[
'rclimit'] = [
1253 'label-message' =>
'recentchangescount',
1254 'help-message' =>
'prefs-help-recentchangescount',
1255 'section' =>
'rc/displayrc',
1256 'filter' => IntvalFilter::class,
1258 $defaultPreferences[
'usenewrc'] = [
1260 'label-message' =>
'tog-usenewrc',
1261 'section' =>
'rc/advancedrc',
1263 $defaultPreferences[
'hideminor'] = [
1265 'label-message' =>
'tog-hideminor',
1266 'section' =>
'rc/changesrc',
1268 $defaultPreferences[
'pst-cssjs'] = [
1271 $defaultPreferences[
'rcfilters-rc-collapsed'] = [
1274 $defaultPreferences[
'rcfilters-wl-collapsed'] = [
1277 $defaultPreferences[
'rcfilters-saved-queries'] = [
1280 $defaultPreferences[
'rcfilters-wl-saved-queries'] = [
1284 $defaultPreferences[
'rcfilters-limit'] = [
1287 $defaultPreferences[
'rcfilters-saved-queries-versionbackup'] = [
1290 $defaultPreferences[
'rcfilters-wl-saved-queries-versionbackup'] = [
1295 $defaultPreferences[
'hidecategorization'] = [
1297 'label-message' =>
'tog-hidecategorization',
1298 'section' =>
'rc/changesrc',
1303 $defaultPreferences[
'hidepatrolled'] = [
1305 'section' =>
'rc/changesrc',
1306 'label-message' =>
'tog-hidepatrolled',
1311 $defaultPreferences[
'newpageshidepatrolled'] = [
1313 'section' =>
'rc/changesrc',
1314 'label-message' =>
'tog-newpageshidepatrolled',
1319 $defaultPreferences[
'shownumberswatching'] = [
1321 'section' =>
'rc/advancedrc',
1322 'label-message' =>
'tog-shownumberswatching',
1326 $defaultPreferences[
'rcenhancedfilters-disable'] = [
1328 'section' =>
'rc/advancedrc',
1329 'label-message' =>
'rcfilters-preference-label',
1330 'help-message' =>
'rcfilters-preference-help',
1344 if ( $user->
isAllowed(
'editmywatchlist' ) ) {
1345 $editWatchlistLinks =
'';
1346 $editWatchlistModes = [
1347 'edit' => [
'subpage' =>
false,
'flags' => [] ],
1348 'raw' => [
'subpage' =>
'raw',
'flags' => [] ],
1349 'clear' => [
'subpage' =>
'clear',
'flags' => [
'destructive' ] ],
1351 foreach ( $editWatchlistModes as $mode =>
$options ) {
1353 $editWatchlistLinks .=
1354 new \OOUI\ButtonWidget( [
1357 'label' =>
new \OOUI\HtmlSnippet(
1358 $context->
msg(
"prefs-editwatchlist-{$mode}" )->parse()
1363 $defaultPreferences[
'editwatchlist'] = [
1366 'default' => $editWatchlistLinks,
1367 'label-message' =>
'prefs-editwatchlist-label',
1368 'section' =>
'watchlist/editwatchlist',
1372 $defaultPreferences[
'watchlistdays'] = [
1375 'max' => $watchlistdaysMax,
1376 'section' =>
'watchlist/displaywatchlist',
1377 'help-message' => [
'prefs-watchlist-days-max',
Message::numParam( $watchlistdaysMax ) ],
1378 'label-message' =>
'prefs-watchlist-days',
1380 $defaultPreferences[
'wllimit'] = [
1384 'label-message' =>
'prefs-watchlist-edits',
1385 'help-message' =>
'prefs-watchlist-edits-max',
1386 'section' =>
'watchlist/displaywatchlist',
1387 'filter' => IntvalFilter::class,
1389 $defaultPreferences[
'extendwatchlist'] = [
1391 'section' =>
'watchlist/advancedwatchlist',
1392 'label-message' =>
'tog-extendwatchlist',
1394 $defaultPreferences[
'watchlisthideminor'] = [
1396 'section' =>
'watchlist/changeswatchlist',
1397 'label-message' =>
'tog-watchlisthideminor',
1399 $defaultPreferences[
'watchlisthidebots'] = [
1401 'section' =>
'watchlist/changeswatchlist',
1402 'label-message' =>
'tog-watchlisthidebots',
1404 $defaultPreferences[
'watchlisthideown'] = [
1406 'section' =>
'watchlist/changeswatchlist',
1407 'label-message' =>
'tog-watchlisthideown',
1409 $defaultPreferences[
'watchlisthideanons'] = [
1411 'section' =>
'watchlist/changeswatchlist',
1412 'label-message' =>
'tog-watchlisthideanons',
1414 $defaultPreferences[
'watchlisthideliu'] = [
1416 'section' =>
'watchlist/changeswatchlist',
1417 'label-message' =>
'tog-watchlisthideliu',
1421 $defaultPreferences[
'watchlistreloadautomatically'] = [
1423 'section' =>
'watchlist/advancedwatchlist',
1424 'label-message' =>
'tog-watchlistreloadautomatically',
1428 $defaultPreferences[
'watchlistunwatchlinks'] = [
1430 'section' =>
'watchlist/advancedwatchlist',
1431 'label-message' =>
'tog-watchlistunwatchlinks',
1435 $defaultPreferences[
'watchlisthidecategorization'] = [
1437 'section' =>
'watchlist/changeswatchlist',
1438 'label-message' =>
'tog-watchlisthidecategorization',
1443 $defaultPreferences[
'watchlisthidepatrolled'] = [
1445 'section' =>
'watchlist/changeswatchlist',
1446 'label-message' =>
'tog-watchlisthidepatrolled',
1451 'edit' =>
'watchdefault',
1452 'move' =>
'watchmoves',
1456 if ( $user->
isAllowedAny(
'createpage',
'createtalk' ) ) {
1457 $watchTypes[
'read'] =
'watchcreations';
1462 'rollback' =>
'watchrollback',
1463 'upload' =>
'watchuploads',
1464 'delete' =>
'watchdeletion',
1467 foreach ( $watchTypes as $action => $pref ) {
1472 $defaultPreferences[$pref] = [
1474 'section' =>
'watchlist/pageswatchlist',
1475 'label-message' =>
"tog-$pref",
1480 $defaultPreferences[
'watchlisttoken'] = [
1484 $tokenButton = new \OOUI\ButtonWidget( [
1488 'label' => $context->
msg(
'prefs-watchlist-managetokens' )->text(),
1490 $defaultPreferences[
'watchlisttoken-info'] = [
1492 'section' =>
'watchlist/tokenwatchlist',
1493 'label-message' =>
'prefs-watchlist-token',
1494 'help-message' =>
'prefs-help-tokenmanagement',
1496 'default' => (string)$tokenButton,
1499 $defaultPreferences[
'wlenhancedfilters-disable'] = [
1501 'section' =>
'watchlist/advancedwatchlist',
1502 'label-message' =>
'rcfilters-watchlist-preference-label',
1503 'help-message' =>
'rcfilters-watchlist-preference-help',
1512 $defaultPreferences[
'search-special-page'] = [
1516 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1517 $defaultPreferences[
'searchNs' . $n] = [
1523 $defaultPreferences[
'search-match-redirect'] = [
1525 'section' =>
'searchoptions/searchmisc',
1526 'label-message' =>
'search-match-redirect-label',
1527 'help-message' =>
'search-match-redirect-help',
1530 $defaultPreferences[
'search-match-redirect'] = [
1535 $defaultPreferences[
'searchlimit'] = [
1539 'section' =>
'searchoptions/searchmisc',
1540 'label-message' =>
'searchlimit-label',
1541 'help-message' => $context->
msg(
'searchlimit-help', 500 ),
1542 'filter' => IntvalFilter::class,
1548 $thumbNamespacesFormatted = array_combine(
1551 static function ( $namespaceId ) use ( $context ) {
1552 return $namespaceId ===
NS_MAIN
1553 ? $context->
msg(
'blanknamespace' )->escaped()
1554 : $context->
getLanguage()->getFormattedNsText( $namespaceId );
1559 $defaultThumbNamespacesFormatted =
1560 array_intersect_key( $thumbNamespacesFormatted, [
NS_FILE => 1 ] ) ?? [];
1561 $extraThumbNamespacesFormatted =
1562 array_diff_key( $thumbNamespacesFormatted, [
NS_FILE => 1 ] );
1563 if ( $extraThumbNamespacesFormatted ) {
1564 $defaultPreferences[
'search-thumbnail-extra-namespaces'] = [
1566 'section' =>
'searchoptions/searchmisc',
1567 'label-message' =>
'search-thumbnail-extra-namespaces-label',
1568 'help-message' => $context->
msg(
1569 'search-thumbnail-extra-namespaces-message',
1570 $context->
getLanguage()->listToText( $extraThumbNamespacesFormatted ),
1571 count( $extraThumbNamespacesFormatted ),
1572 $context->
getLanguage()->listToText( $defaultThumbNamespacesFormatted ),
1573 count( $defaultThumbNamespacesFormatted )
1588 private static function sortSkinNames( $a, $b, $currentSkin, $preferredSkins ) {
1590 if ( strcasecmp( $a, $currentSkin ) === 0 ) {
1593 if ( strcasecmp( $b, $currentSkin ) === 0 ) {
1597 if ( count( $preferredSkins ) ) {
1598 $aPreferred = array_search( $a, $preferredSkins );
1599 $bPreferred = array_search( $b, $preferredSkins );
1602 if ( $aPreferred !==
false && $bPreferred ===
false ) {
1605 if ( $aPreferred ===
false && $bPreferred !==
false ) {
1610 if ( $aPreferred !==
false && $bPreferred !==
false ) {
1611 return strcasecmp( $aPreferred, $bPreferred );
1615 return strcasecmp( $a, $b );
1626 private function getValidSkinNames( User $user,
IContextSource $context ) {
1628 $validSkinNames = $this->skinFactory->getAllowedSkins();
1629 $allInstalledSkins = $this->skinFactory->getInstalledSkins();
1632 $useSkin = $context->
getRequest()->getRawVal(
'useskin' );
1633 if ( isset( $allInstalledSkins[$useSkin] )
1634 && $context->
msg(
"skinname-$useSkin" )->exists()
1636 $validSkinNames[$useSkin] = $useSkin;
1640 $currentUserSkin = $this->userOptionsManager->getOption( $user,
'skin' );
1641 if ( isset( $allInstalledSkins[$currentUserSkin] )
1642 && $context->
msg(
"skinname-$currentUserSkin" )->exists()
1644 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1647 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1648 $msg = $context->
msg(
"skinname-{$skinkey}" );
1649 if ( $msg->exists() ) {
1650 $skinname = htmlspecialchars( $msg->text() );
1657 uksort( $validSkinNames,
function ( $a, $b ) use ( $currentUserSkin, $preferredSkins ) {
1658 return $this->sortSkinNames( $a, $b, $currentUserSkin, $preferredSkins );
1661 return $validSkinNames;
1673 $mptitle = Title::newMainPage();
1674 $previewtext = $context->
msg(
'skin-preview' )->escaped();
1678 $safeMode = $this->userOptionsManager->getOption( $user,
'forcesafemode' );
1679 $foundDefault =
false;
1680 foreach ( $validSkinNames as $skinkey => $sn ) {
1684 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1685 $linkTools[] = $context->
msg(
'default' )->escaped();
1686 $foundDefault =
true;
1690 $talkPageMsg = $context->
msg(
"$skinkey-prefs-talkpage" );
1691 if ( $talkPageMsg->exists() ) {
1692 $linkTools[] = $talkPageMsg->parse();
1696 $mplink = htmlspecialchars( $mptitle->getLocalURL( [
'useskin' => $skinkey ] ) );
1697 $linkTools[] =
"<a target='_blank' href=\"$mplink\">$previewtext</a>";
1702 if ( $allowUserCss ) {
1703 $cssPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.css' );
1704 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
1705 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1708 if ( $allowUserJs ) {
1709 $jsPage = Title::makeTitleSafe(
NS_USER, $user->
getName() .
'/' . $skinkey .
'.js' );
1710 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
1711 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1715 $display = $sn .
' ' . $context->
msg(
'parentheses' )
1716 ->rawParams( $context->
getLanguage()->pipeList( $linkTools ) )
1718 $ret[$display] = $skinkey;
1721 if ( !$foundDefault ) {
1736 $dateopts = $lang->getDatePreferences();
1741 if ( !in_array(
'default', $dateopts ) ) {
1742 $dateopts[] =
'default';
1752 foreach ( $dateopts as $key ) {
1753 if ( $key ==
'default' ) {
1754 $formatted = $context->
msg(
'datedefault' )->escaped();
1756 $formatted = htmlspecialchars( $lang->timeanddate( $epoch,
false, $key ) );
1758 $ret[$formatted] = $key;
1770 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1774 $display =
"{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1775 $ret[$display] = $index;
1787 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1790 $display = $size . $pixels;
1791 $ret[$display] = $index;
1806 if ( is_string( $signature ) && mb_strlen( $signature ) > $maxSigChars ) {
1807 return $form->
msg(
'badsiglength' )->numParams( $maxSigChars )->escaped();
1810 if ( $signature ===
null || $signature ===
'' ) {
1817 if ( !( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) ) {
1834 $signature === $this->userOptionsManager->getOption( $user,
'nickname' ) &&
1835 (
bool)$alldata[
'fancysig'] === $this->userOptionsManager->getBoolOption( $user,
'fancysig' )
1840 if ( $sigValidation ===
'new' || $sigValidation ===
'disallow' ) {
1842 $parserOpts = ParserOptions::newFromContext( $form->
getContext() );
1843 $validator = $this->signatureValidatorFactory
1844 ->newSignatureValidator( $user, $form->
getContext(), $parserOpts );
1845 $errors = $validator->validateSignature( $signature );
1854 if ( $this->parserFactory->getInstance()->validateSig( $signature ) ===
false ) {
1855 return $form->
msg(
'badsig' )->escaped();
1868 if ( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) {
1869 $signature = $this->parserFactory->getInstance()->cleanSig( $signature );
1888 $formClass = PreferencesFormOOUI::class,
1896 if ( count( $remove ) ) {
1897 $removeKeys = array_fill_keys( $remove,
true );
1898 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1902 foreach ( $formDescriptor as $name => $info ) {
1903 if ( isset( $info[
'type'] ) && $info[
'type'] ===
'api' ) {
1904 unset( $formDescriptor[$name] );
1911 $htmlForm =
new $formClass( $formDescriptor, $context,
'prefs' );
1917 $htmlForm->setAction( $context->
getTitle()->getLocalURL( [
1918 'useskin' => $context->
getRequest()->getRawVal(
'useskin' )
1921 $htmlForm->setModifiedUser( $user );
1922 $htmlForm->setOptionsEditable( $user->
isAllowed(
'editmyoptions' ) );
1923 $htmlForm->setPrivateInfoEditable( $user->
isAllowed(
'editmyprivateinfo' ) );
1924 $htmlForm->setId(
'mw-prefs-form' );
1925 $htmlForm->setAutocomplete(
'off' );
1926 $htmlForm->setSubmitTextMsg(
'saveprefs' );
1928 $htmlForm->setSubmitTooltip(
'preferences-save' );
1929 $htmlForm->setSubmitID(
'prefcontrol' );
1930 $htmlForm->setSubmitCallback(
1932 return $this->
submitForm( $formData, $form, $formDescriptor );
1952 if ( !$user->isAllowedAny(
'editmyprivateinfo',
'editmyoptions' ) ) {
1953 return Status::newFatal(
'mypreferencesprotected' );
1957 $this->
applyFilters( $formData, $formDescriptor,
'filterFromForm' );
1962 if ( !in_array(
'realname', $hiddenPrefs )
1963 && $user->isAllowed(
'editmyprivateinfo' )
1964 && array_key_exists(
'realname', $formData )
1966 $realName = $formData[
'realname'];
1967 $user->setRealName( $realName );
1970 if ( $user->isAllowed(
'editmyoptions' ) ) {
1971 $oldUserOptions = $this->userOptionsManager->getOptions( $user );
1974 unset( $formData[$b] );
1980 foreach ( $hiddenPrefs as $pref ) {
1983 $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref,
null,
true );
1988 isset( $formData[
'rclimit'] ) &&
1989 intval( $formData[
'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user,
'rclimit' )
1991 $formData[
'rcfilters-limit'] = $formData[
'rclimit'];
1995 $this->userOptionsManager->resetOptions( $user, $form->
getContext(),
'unused' );
1997 foreach ( $formData as $key => $value ) {
1998 $this->userOptionsManager->setOption( $user, $key, $value );
2001 $this->hookRunner->onPreferencesFormPreSave(
2002 $formData, $form, $user, $result, $oldUserOptions );
2005 $user->saveSettings();
2018 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
2019 foreach ( $formDescriptor as $preference => $desc ) {
2020 if ( !isset( $desc[
'filter'] ) || !isset( $preferences[$preference] ) ) {
2023 $filterDesc = $desc[
'filter'];
2024 if ( $filterDesc instanceof
Filter ) {
2025 $filter = $filterDesc;
2026 } elseif ( class_exists( $filterDesc ) ) {
2027 $filter =
new $filterDesc();
2028 } elseif ( is_callable( $filterDesc ) ) {
2029 $filter = $filterDesc();
2031 throw new UnexpectedValueException(
2032 "Unrecognized filter type for preference '$preference'"
2035 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
2050 array $formDescriptor
2052 $res = $this->
saveFormData( $formData, $form, $formDescriptor );
2054 if ( $res ===
true ) {
2060 $url = $form->
getTitle()->getFullURL( $urlOptions );
2063 $context->
getRequest()->getSession()->set(
'specialPreferencesSaveSuccess', 1 );
2065 $context->
getOutput()->redirect( $url );
2068 return ( $res ===
true ? Status::newGood() : $res );
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
Methods for dealing with language codes.
Base class for multi-variant language conversion.
Base class for language-specific code.
A class containing constants representing the names of configuration variables.
const HiddenPrefs
Name constant for the HiddenPrefs setting, for use with Config::get()
const ForceHTTPS
Name constant for the ForceHTTPS setting, for use with Config::get()
const EnotifWatchlist
Name constant for the EnotifWatchlist setting, for use with Config::get()
const MaxSigChars
Name constant for the MaxSigChars setting, for use with Config::get()
const RCMaxAge
Name constant for the RCMaxAge setting, for use with Config::get()
const DefaultSkin
Name constant for the DefaultSkin setting, for use with Config::get()
const EnableUserEmailMuteList
Name constant for the EnableUserEmailMuteList setting, for use with Config::get()
const EnotifRevealEditorAddress
Name constant for the EnotifRevealEditorAddress setting, for use with Config::get()
const EnableUserEmail
Name constant for the EnableUserEmail setting, for use with Config::get()
const SkinsPreferred
Name constant for the SkinsPreferred setting, for use with Config::get()
const EmailConfirmToEdit
Name constant for the EmailConfirmToEdit setting, for use with Config::get()
const EnableEmail
Name constant for the EnableEmail setting, for use with Config::get()
const LocalTZoffset
Name constant for the LocalTZoffset setting, for use with Config::get()
const RCShowWatchingUsers
Name constant for the RCShowWatchingUsers setting, for use with Config::get()
const EnotifUserTalk
Name constant for the EnotifUserTalk setting, for use with Config::get()
const AllowUserJs
Name constant for the AllowUserJs setting, for use with Config::get()
const ImageLimits
Name constant for the ImageLimits setting, for use with Config::get()
const SearchMatchRedirectPreference
Name constant for the SearchMatchRedirectPreference setting, for use with Config::get()
const EnotifMinorEdits
Name constant for the EnotifMinorEdits setting, for use with Config::get()
const ScriptPath
Name constant for the ScriptPath setting, for use with Config::get()
const AllowUserCss
Name constant for the AllowUserCss setting, for use with Config::get()
const AllowRequiringEmailForResets
Name constant for the AllowRequiringEmailForResets setting, for use with Config::get()
const ThumbLimits
Name constant for the ThumbLimits setting, for use with Config::get()
const SecureLogin
Name constant for the SecureLogin setting, for use with Config::get()
const LanguageCode
Name constant for the LanguageCode setting, for use with Config::get()
const SignatureValidation
Name constant for the SignatureValidation setting, for use with Config::get()
const AllowUserCssPrefs
Name constant for the AllowUserCssPrefs setting, for use with Config::get()
const RCWatchCategoryMembership
Name constant for the RCWatchCategoryMembership setting, for use with Config::get()
const ThumbnailNamespaces
Name constant for the ThumbnailNamespaces setting, for use with Config::get()
const EmailAuthentication
Name constant for the EmailAuthentication setting, for use with Config::get()
This is one of the Core classes and should be read at least once by any new developers.
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,...
The Message class deals with fetching and processing of interface message into a variety of formats.
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.
Module of static functions for generating XML.
$wgDefaultUserOptions
Config variable stub for the DefaultUserOptions setting, for use by phpdoc and IDEs.
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.