56use Psr\Log\LoggerAwareTrait;
57use Psr\Log\NullLogger;
61use UnexpectedValueException;
94 private $languageConverter;
100 private $userOptionsManager;
103 private $languageConverterFactory;
109 private $skinFactory;
112 private $userGroupManager;
115 private $signatureValidatorFactory;
203 $this->logger =
new NullLogger();
204 $this->languageConverter = $languageConverter;
206 $this->hookRunner =
new HookRunner( $hookContainer );
210 $services =
static function () {
216 : $services()->getUserOptionsManager();
217 $this->languageConverterFactory = $languageConverterFactory ?? $services()->getLanguageConverterFactory();
219 $this->parser = $parser ?? $services()->getParser();
220 $this->skinFactory = $skinFactory ?? $services()->getSkinFactory();
221 $this->userGroupManager = $userGroupManager ?? $services()->getUserGroupManager();
222 $this->signatureValidatorFactory = $signatureValidatorFactory
223 ?? $services()->getSignatureValidatorFactory();
224 $this->config = $config ?? $services()->getMainConfig();
246 OutputPage::setupOOUI(
247 strtolower( $context->
getSkin()->getSkinName() ),
261 $this->hookRunner->onGetPreferences( $user, $preferences );
263 $this->loadPreferenceValues( $user, $context, $preferences );
264 $this->logger->debug(
"Created form descriptor for user '{$user->getName()}'" );
275 foreach ( $descriptor as $name => &$params ) {
278 if ( ( isset( $params[
'type'] ) && $params[
'type'] ===
'info' ) ||
279 ( isset( $params[
'class'] ) && $params[
'class'] === \HTMLInfoField::class )
281 unset( $descriptor[$name] );
286 foreach ( $params as $key => $value ) {
289 case 'options-message':
292 case 'options-messages':
293 unset( $params[$key] );
294 $params[
'options'] = $value;
297 if ( preg_match(
'/-messages?$/', $key ) ) {
299 unset( $params[$key] );
315 private function loadPreferenceValues(
User $user,
IContextSource $context, &$defaultPreferences ) {
318 unset( $defaultPreferences[$pref] );
323 $form =
new HTMLForm( $simplified, $context );
325 $disable = !$user->
isAllowed(
'editmyoptions' );
327 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
328 $userOptions = $this->userOptionsManager->getOptions( $user );
329 $this->
applyFilters( $userOptions, $defaultPreferences,
'filterForForm' );
331 foreach ( $simplified as $name => $_ ) {
332 $info = &$defaultPreferences[$name];
334 $info[
'disabled'] =
'disabled';
336 if ( isset( $info[
'default'] ) ) {
340 $field = $form->getField( $name );
341 $globalDefault = $defaultOptions[$name] ??
null;
347 if ( $prefFromUser !==
null &&
348 $field->validate( $prefFromUser, $this->userOptionsManager->getOptions( $user ) ) ===
true ) {
349 $info[
'default'] = $prefFromUser;
350 } elseif ( $field->validate( $globalDefault, $this->userOptionsManager->getOptions( $user ) ) ===
true ) {
351 $info[
'default'] = $globalDefault;
353 $globalDefault = json_encode( $globalDefault );
355 "Default '$globalDefault' is invalid for preference $name of user " . $user->
getName()
360 return $defaultPreferences;
372 $val = $userOptions[$name] ??
null;
375 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'multiselect' ) ||
376 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLMultiSelectField::class ) ) {
377 $options = HTMLFormField::flattenOptions( $info[
'options-messages'] ?? $info[
'options'] );
378 $prefix = $info[
'prefix'] ?? $name;
382 if ( $userOptions[
"$prefix$value"] ??
false ) {
389 if ( ( isset( $info[
'type'] ) && $info[
'type'] ==
'checkmatrix' ) ||
390 ( isset( $info[
'class'] ) && $info[
'class'] == \HTMLCheckMatrix::class ) ) {
391 $columns = HTMLFormField::flattenOptions( $info[
'columns'] );
392 $rows = HTMLFormField::flattenOptions( $info[
'rows'] );
393 $prefix = $info[
'prefix'] ?? $name;
396 foreach ( $columns as $column ) {
397 foreach ( $rows as $row ) {
398 if ( $userOptions[
"$prefix$column-$row"] ??
false ) {
399 $val[] =
"$column-$row";
422 $defaultPreferences[
'username'] = [
424 'label-message' => [
'username', $userName ],
425 'default' => $userName,
426 'section' =>
'personal/info',
432 $userEffectiveGroups = array_diff(
433 $this->userGroupManager->getUserEffectiveGroups( $user ),
436 $defaultPreferences[
'usergroups'] = [
438 'label-message' => [
'prefs-memberingroups',
439 \Message::numParam( count( $userEffectiveGroups ) ), $userName ],
440 'default' =>
function () use ( $user, $userEffectiveGroups, $context,
$lang, $userName ) {
441 $userGroupMemberships = $this->userGroupManager->getUserGroupMemberships( $user );
442 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
443 foreach ( $userEffectiveGroups as $ueg ) {
444 $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
446 $userG = UserGroupMembership::getLink( $groupStringOrObject, $context,
'html' );
447 $userM = UserGroupMembership::getLink( $groupStringOrObject, $context,
'html',
457 $userTempGroups[] = $userG;
458 $userTempMembers[] = $userM;
460 $userGroups[] = $userG;
461 $userMembers[] = $userM;
465 sort( $userMembers );
466 sort( $userTempGroups );
467 sort( $userTempMembers );
468 $userGroups = array_merge( $userTempGroups, $userGroups );
469 $userMembers = array_merge( $userTempMembers, $userMembers );
470 return $context->
msg(
'prefs-memberingroups-type' )
471 ->rawParams(
$lang->commaList( $userGroups ),
$lang->commaList( $userMembers ) )
475 'section' =>
'personal/info',
480 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
482 $defaultPreferences[
'editcount'] = [
485 'label-message' =>
'prefs-edits',
486 'default' => $editCount,
487 'section' =>
'personal/info',
491 $displayUser = $context->
getUser();
493 $defaultPreferences[
'registrationdate'] = [
495 'label-message' =>
'prefs-registration',
496 'default' => $context->
msg(
497 'prefs-registration-date-time',
498 $lang->userTimeAndDate( $userRegistration, $displayUser ),
499 $lang->userDate( $userRegistration, $displayUser ),
500 $lang->userTime( $userRegistration, $displayUser )
502 'section' =>
'personal/info',
506 $canViewPrivateInfo = $user->
isAllowed(
'viewmyprivateinfo' );
507 $canEditPrivateInfo = $user->
isAllowed(
'editmyprivateinfo' );
510 $defaultPreferences[
'realname'] = [
512 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'realname' )
515 'section' =>
'personal/info',
516 'label-message' =>
'yourrealname',
517 'help-message' =>
'prefs-help-realname',
520 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
523 $defaultPreferences[
'password'] = [
526 'default' => (string)
new \OOUI\ButtonWidget( [
530 'label' => $context->
msg(
'prefs-resetpass' )->text(),
532 'label-message' =>
'yourpassword',
536 ? $context->
msg(
'prefs-help-yourpassword',
537 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
539 'section' =>
'personal/info',
546 $defaultPreferences[
'prefershttps'] = [
548 'label-message' =>
'tog-prefershttps',
549 'help-message' =>
'prefs-help-prefershttps',
550 'section' =>
'personal/info'
554 $defaultPreferences[
'downloaduserdata'] = [
557 'label-message' =>
'prefs-user-downloaddata-label',
558 'default' => Html::element(
562 '/api.php?action=query&meta=userinfo&uiprop=*',
564 $context->
msg(
'prefs-user-downloaddata-info' )->text()
566 'help-message' => [
'prefs-user-downloaddata-help-message', urlencode( $user->
getTitleKey() ) ],
567 'section' =>
'personal/info',
570 $defaultPreferences[
'restoreprefs'] = [
573 'label-message' =>
'prefs-user-restoreprefs-label',
574 'default' => Html::element(
578 ->getSubpage(
'reset' )->getLocalURL()
580 $context->
msg(
'prefs-user-restoreprefs-info' )->text()
582 'section' =>
'personal/info',
585 $languages = $this->languageNameUtils->getLanguageNames(
586 LanguageNameUtils::AUTONYMS,
587 LanguageNameUtils::SUPPORTED
590 if ( !array_key_exists( $languageCode, $languages ) ) {
591 $languages[$languageCode] = $languageCode;
597 foreach ( $languages as $code => $name ) {
598 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
601 $defaultPreferences[
'language'] = [
603 'section' =>
'personal/i18n',
605 'label-message' =>
'yourlanguage',
608 $neutralGenderMessage = $context->
msg(
'gender-notknown' )->escaped() . (
609 !$context->
msg(
'gender-unknown' )->isDisabled()
610 ?
"<br>" . $context->
msg(
'parentheses' )
611 ->params( $context->
msg(
'gender-unknown' )->plain() )
616 $defaultPreferences[
'gender'] = [
618 'section' =>
'personal/i18n',
620 $neutralGenderMessage =>
'unknown',
621 $context->
msg(
'gender-female' )->escaped() =>
'female',
622 $context->
msg(
'gender-male' )->escaped() =>
'male',
624 'label-message' =>
'yourgender',
625 'help-message' =>
'prefs-help-gender',
629 if ( !$this->languageConverterFactory->isConversionDisabled() ) {
631 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
632 if ( $langCode == $this->contLang->getCode() ) {
633 if ( !$this->languageConverter->hasVariants() ) {
637 $variants = $this->languageConverter->getVariants();
639 foreach ( $variants as $v ) {
640 $v = str_replace(
'_',
'-', strtolower( $v ) );
641 $variantArray[$v] =
$lang->getVariantname( $v,
false );
645 foreach ( $variantArray as $code => $name ) {
646 $display = LanguageCode::bcp47( $code ) .
' - ' . $name;
650 $defaultPreferences[
'variant'] = [
651 'label-message' =>
'yourvariant',
654 'section' =>
'personal/i18n',
655 'help-message' =>
'prefs-help-variant',
658 $defaultPreferences[
"variant-$langCode"] = [
666 $oldsigWikiText = $this->parser->preSaveTransform(
670 ParserOptions::newFromContext( $context )
673 $context->
getOutput()->parseAsContent( $oldsigWikiText )
675 $signatureFieldConfig = [];
677 $signature = $this->userOptionsManager->getOption( $user,
'nickname' );
678 $useFancySig = $this->userOptionsManager->getBoolOption( $user,
'fancysig' );
679 if ( $useFancySig && $signature !==
'' ) {
680 $parserOpts = ParserOptions::newFromContext( $context );
681 $validator = $this->signatureValidatorFactory
682 ->newSignatureValidator( $user, $context, $parserOpts );
683 $signatureErrors = $validator->validateSignature( $signature );
684 if ( $signatureErrors ) {
686 $oldsigHTML .=
'<p><strong>' .
691 $context->
msg(
"prefs-signature-invalid-$sigValidation" )->parse() .
696 foreach ( $signatureErrors as &$sigError ) {
697 $sigError = new \OOUI\HtmlSnippet( $sigError );
700 $signatureFieldConfig = [
701 'warnings' => $sigValidation !==
'disallow' ? $signatureErrors :
null,
702 'errors' => $sigValidation ===
'disallow' ? $signatureErrors :
null,
707 $defaultPreferences[
'oldsig'] = [
712 'default' => new \OOUI\FieldLayout(
713 new \OOUI\LabelWidget( [
714 'label' =>
new \OOUI\HtmlSnippet( $oldsigHTML ),
718 'label' =>
new \OOUI\HtmlSnippet( $context->
msg(
'tog-oldsig' )->parse() )
719 ] + $signatureFieldConfig
721 'section' =>
'personal/signature',
723 $defaultPreferences[
'nickname'] = [
724 'type' => $this->authManager->allowsPropertyChange(
'nickname' ) ?
'text' :
'info',
726 'label-message' =>
'yournick',
727 'validation-callback' =>
function ( $signature, $alldata,
HTMLForm $form ) {
730 'section' =>
'personal/signature',
731 'filter-callback' =>
function ( $signature, array $alldata,
HTMLForm $form ) {
735 $defaultPreferences[
'fancysig'] = [
737 'label-message' =>
'tog-fancysig',
739 'help-message' =>
'prefs-help-signature',
740 'section' =>
'personal/signature'
745 if ( $canViewPrivateInfo ) {
748 ?
'prefs-help-email-required'
749 :
'prefs-help-email';
753 $helpMessages[] =
'prefs-help-email-others';
756 $emailAddress = $user->
getEmail() ? htmlspecialchars( $user->
getEmail() ) :
'';
757 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange(
'emailaddress' ) ) {
758 $button = new \OOUI\ButtonWidget( [
763 $context->
msg( $user->
getEmail() ?
'prefs-changeemail' :
'prefs-setemail' )->text(),
766 $emailAddress .= $emailAddress ==
'' ? $button : (
'<br />' . $button );
769 $defaultPreferences[
'emailaddress'] = [
772 'default' => $emailAddress,
773 'label-message' =>
'youremail',
774 'section' =>
'personal/email',
775 'help-messages' => $helpMessages,
780 $disableEmailPrefs =
false;
783 $defaultPreferences[
'requireemail'] = [
785 'label-message' =>
'tog-requireemail',
786 'help-message' =>
'prefs-help-requireemail',
787 'section' =>
'personal/email',
788 'disabled' => $user->
getEmail() ? false :
true,
798 $displayUser = $context->
getUser();
800 $time =
$lang->userTimeAndDate( $emailTimestamp, $displayUser );
801 $d =
$lang->userDate( $emailTimestamp, $displayUser );
802 $t =
$lang->userTime( $emailTimestamp, $displayUser );
803 $emailauthenticated = $context->
msg(
'emailauthenticated',
804 $time, $d,
$t )->parse() .
'<br />';
805 $emailauthenticationclass =
'mw-email-authenticated';
807 $disableEmailPrefs =
true;
808 $emailauthenticated = $context->
msg(
'emailnotauthenticated' )->parse() .
'<br />' .
809 new \OOUI\ButtonWidget( [
811 'label' => $context->
msg(
'emailconfirmlink' )->text(),
813 $emailauthenticationclass =
"mw-email-not-authenticated";
816 $disableEmailPrefs =
true;
817 $emailauthenticated = $context->
msg(
'noemailprefs' )->escaped();
818 $emailauthenticationclass =
'mw-email-none';
821 if ( $canViewPrivateInfo ) {
822 $defaultPreferences[
'emailauthentication'] = [
825 'section' =>
'personal/email',
826 'label-message' =>
'prefs-emailconfirm-label',
827 'default' => $emailauthenticated,
829 'cssclass' => $emailauthenticationclass,
837 $defaultPreferences[
'disablemail'] = [
838 'id' =>
'wpAllowEmail',
841 'section' =>
'personal/email',
842 'label-message' =>
'allowemail',
843 'disabled' => $disableEmailPrefs,
846 $defaultPreferences[
'email-allow-new-users'] = [
847 'id' =>
'wpAllowEmailFromNewUsers',
849 'section' =>
'personal/email',
850 'label-message' =>
'email-allow-new-users-label',
851 'disabled' => $disableEmailPrefs,
852 'disable-if' => [
'!==',
'disablemail',
'1' ],
855 $defaultPreferences[
'ccmeonemails'] = [
857 'section' =>
'personal/email',
858 'label-message' =>
'tog-ccmeonemails',
859 'disabled' => $disableEmailPrefs,
863 $defaultPreferences[
'email-blacklist'] = [
864 'type' =>
'usersmultiselect',
865 'label-message' =>
'email-mutelist-label',
866 'section' =>
'personal/email',
867 'disabled' => $disableEmailPrefs,
868 'filter' => MultiUsernameFilter::class,
874 $defaultPreferences[
'enotifwatchlistpages'] = [
876 'section' =>
'personal/email',
877 'label-message' =>
'tog-enotifwatchlistpages',
878 'disabled' => $disableEmailPrefs,
882 $defaultPreferences[
'enotifusertalkpages'] = [
884 'section' =>
'personal/email',
885 'label-message' =>
'tog-enotifusertalkpages',
886 'disabled' => $disableEmailPrefs,
892 $defaultPreferences[
'enotifminoredits'] = [
894 'section' =>
'personal/email',
895 'label-message' =>
'tog-enotifminoredits',
896 'disabled' => $disableEmailPrefs,
901 $defaultPreferences[
'enotifrevealaddr'] = [
903 'section' =>
'personal/email',
904 'label-message' =>
'tog-enotifrevealaddr',
905 'disabled' => $disableEmailPrefs,
921 if ( $skinOptions ) {
922 $defaultPreferences[
'skin'] = [
925 'options' => $skinOptions,
926 'section' =>
'rendering/skin',
928 $defaultPreferences[
'skin-responsive'] = [
930 'label-message' =>
'prefs-skin-responsive',
931 'section' =>
'rendering/skin/skin-prefs',
932 'help-message' =>
'prefs-help-skin-responsive',
941 if ( $allowUserCss || $allowUserJs ) {
945 if ( $allowUserCss ) {
947 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
948 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
951 if ( $allowUserJs ) {
953 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
954 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
957 $defaultPreferences[
'commoncssjs'] = [
960 'default' => $context->
getLanguage()->pipeList( $linkTools ),
961 'label-message' =>
'prefs-common-config',
962 'section' =>
'rendering/skin',
972 $defaultPreferences[
'imagesize'] = [
975 'label-message' =>
'imagemaxsize',
976 'section' =>
'rendering/files',
978 $defaultPreferences[
'thumbsize'] = [
981 'label-message' =>
'thumbsize',
982 'section' =>
'rendering/files',
996 if ( $dateOptions ) {
997 $defaultPreferences[
'date'] = [
999 'options' => $dateOptions,
1000 'section' =>
'rendering/dateformat',
1007 $nowlocal = Xml::element(
'span', [
'id' =>
'wpLocalTime' ],
1008 $lang->userTime( $now, $user ) );
1009 $nowserver =
$lang->userTime( $now, $user,
1010 [
'format' =>
false,
'timecorrection' =>
false ] ) .
1011 Html::hidden(
'wpServerTime', (
int)substr( $now, 8, 2 ) * 60 + (
int)substr( $now, 10, 2 ) );
1013 $defaultPreferences[
'nowserver'] = [
1016 'label-message' =>
'servertime',
1017 'default' => $nowserver,
1018 'section' =>
'rendering/timeoffset',
1021 $defaultPreferences[
'nowlocal'] = [
1024 'label-message' =>
'localtime',
1025 'default' => $nowlocal,
1026 'section' =>
'rendering/timeoffset',
1029 $userTimeCorrection = (string)$this->userOptionsManager->getOption( $user,
'timecorrection' );
1034 $userTimeCorrection,
1039 if ( $userTimeCorrectionObj->getCorrectionType() === UserTimeCorrection::OFFSET ) {
1040 $tzDefault = UserTimeCorrection::formatTimezoneOffset( $userTimeCorrectionObj->getTimeOffset() );
1042 $tzDefault = $userTimeCorrectionObj->toString();
1045 $defaultPreferences[
'timecorrection'] = [
1046 'type' =>
'timezone',
1047 'label-message' =>
'timezonelegend',
1048 'default' => $tzDefault,
1050 'section' =>
'rendering/timeoffset',
1051 'id' =>
'wpTimeCorrection',
1052 'filter' => TimezoneFilter::class,
1064 &$defaultPreferences
1067 $defaultPreferences[
'diffonly'] = [
1069 'section' =>
'rendering/diffs',
1070 'label-message' =>
'tog-diffonly',
1072 $defaultPreferences[
'norollbackdiff'] = [
1074 'section' =>
'rendering/diffs',
1075 'label-message' =>
'tog-norollbackdiff',
1080 $defaultPreferences[
'underline'] = [
1083 $l10n->
msg(
'underline-never' )->text() => 0,
1084 $l10n->
msg(
'underline-always' )->text() => 1,
1085 $l10n->
msg(
'underline-default' )->text() => 2,
1087 'label-message' =>
'tog-underline',
1088 'section' =>
'rendering/advancedrendering',
1092 $defaultPreferences[
'showhiddencats'] = [
1094 'section' =>
'rendering/advancedrendering',
1095 'label-message' =>
'tog-showhiddencats'
1099 $defaultPreferences[
'showrollbackconfirmation'] = [
1101 'section' =>
'rendering/advancedrendering',
1102 'label-message' =>
'tog-showrollbackconfirmation',
1113 $defaultPreferences[
'editsectiononrightclick'] = [
1115 'section' =>
'editing/advancedediting',
1116 'label-message' =>
'tog-editsectiononrightclick',
1118 $defaultPreferences[
'editondblclick'] = [
1120 'section' =>
'editing/advancedediting',
1121 'label-message' =>
'tog-editondblclick',
1125 $defaultPreferences[
'editfont'] = [
1127 'section' =>
'editing/editor',
1128 'label-message' =>
'editfont-style',
1130 $l10n->
msg(
'editfont-monospace' )->text() =>
'monospace',
1131 $l10n->
msg(
'editfont-sansserif' )->text() =>
'sans-serif',
1132 $l10n->
msg(
'editfont-serif' )->text() =>
'serif',
1137 if ( $user->
isAllowed(
'minoredit' ) ) {
1138 $defaultPreferences[
'minordefault'] = [
1140 'section' =>
'editing/editor',
1141 'label-message' =>
'tog-minordefault',
1145 $defaultPreferences[
'forceeditsummary'] = [
1147 'section' =>
'editing/editor',
1148 'label-message' =>
'tog-forceeditsummary',
1150 $defaultPreferences[
'useeditwarning'] = [
1152 'section' =>
'editing/editor',
1153 'label-message' =>
'tog-useeditwarning',
1156 $defaultPreferences[
'previewonfirst'] = [
1158 'section' =>
'editing/preview',
1159 'label-message' =>
'tog-previewonfirst',
1161 $defaultPreferences[
'previewontop'] = [
1163 'section' =>
'editing/preview',
1164 'label-message' =>
'tog-previewontop',
1166 $defaultPreferences[
'uselivepreview'] = [
1168 'section' =>
'editing/preview',
1169 'label-message' =>
'tog-uselivepreview',
1180 $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1181 $defaultPreferences[
'rcdays'] = [
1183 'label-message' =>
'recentchangesdays',
1184 'section' =>
'rc/displayrc',
1189 $defaultPreferences[
'rclimit'] = [
1193 'label-message' =>
'recentchangescount',
1194 'help-message' =>
'prefs-help-recentchangescount',
1195 'section' =>
'rc/displayrc',
1196 'filter' => IntvalFilter::class,
1198 $defaultPreferences[
'usenewrc'] = [
1200 'label-message' =>
'tog-usenewrc',
1201 'section' =>
'rc/advancedrc',
1203 $defaultPreferences[
'hideminor'] = [
1205 'label-message' =>
'tog-hideminor',
1206 'section' =>
'rc/changesrc',
1208 $defaultPreferences[
'pst-cssjs'] = [
1211 $defaultPreferences[
'rcfilters-rc-collapsed'] = [
1214 $defaultPreferences[
'rcfilters-wl-collapsed'] = [
1217 $defaultPreferences[
'rcfilters-saved-queries'] = [
1220 $defaultPreferences[
'rcfilters-wl-saved-queries'] = [
1224 $defaultPreferences[
'rcfilters-limit'] = [
1227 $defaultPreferences[
'rcfilters-saved-queries-versionbackup'] = [
1230 $defaultPreferences[
'rcfilters-wl-saved-queries-versionbackup'] = [
1235 $defaultPreferences[
'hidecategorization'] = [
1237 'label-message' =>
'tog-hidecategorization',
1238 'section' =>
'rc/changesrc',
1243 $defaultPreferences[
'hidepatrolled'] = [
1245 'section' =>
'rc/changesrc',
1246 'label-message' =>
'tog-hidepatrolled',
1251 $defaultPreferences[
'newpageshidepatrolled'] = [
1253 'section' =>
'rc/changesrc',
1254 'label-message' =>
'tog-newpageshidepatrolled',
1259 $defaultPreferences[
'shownumberswatching'] = [
1261 'section' =>
'rc/advancedrc',
1262 'label-message' =>
'tog-shownumberswatching',
1266 $defaultPreferences[
'rcenhancedfilters-disable'] = [
1268 'section' =>
'rc/advancedrc',
1269 'label-message' =>
'rcfilters-preference-label',
1270 'help-message' =>
'rcfilters-preference-help',
1284 if ( $user->
isAllowed(
'editmywatchlist' ) ) {
1285 $editWatchlistLinks =
'';
1286 $editWatchlistModes = [
1287 'edit' => [
'subpage' =>
false,
'flags' => [] ],
1288 'raw' => [
'subpage' =>
'raw',
'flags' => [] ],
1289 'clear' => [
'subpage' =>
'clear',
'flags' => [
'destructive' ] ],
1291 foreach ( $editWatchlistModes as $mode =>
$options ) {
1293 $editWatchlistLinks .=
1294 new \OOUI\ButtonWidget( [
1297 'label' =>
new \OOUI\HtmlSnippet(
1298 $context->
msg(
"prefs-editwatchlist-{$mode}" )->parse()
1303 $defaultPreferences[
'editwatchlist'] = [
1306 'default' => $editWatchlistLinks,
1307 'label-message' =>
'prefs-editwatchlist-label',
1308 'section' =>
'watchlist/editwatchlist',
1312 $defaultPreferences[
'watchlistdays'] = [
1315 'max' => $watchlistdaysMax,
1316 'section' =>
'watchlist/displaywatchlist',
1317 'help-message' => [
'prefs-watchlist-days-max',
Message::numParam( $watchlistdaysMax ) ],
1318 'label-message' =>
'prefs-watchlist-days',
1320 $defaultPreferences[
'wllimit'] = [
1324 'label-message' =>
'prefs-watchlist-edits',
1325 'help-message' =>
'prefs-watchlist-edits-max',
1326 'section' =>
'watchlist/displaywatchlist',
1327 'filter' => IntvalFilter::class,
1329 $defaultPreferences[
'extendwatchlist'] = [
1331 'section' =>
'watchlist/advancedwatchlist',
1332 'label-message' =>
'tog-extendwatchlist',
1334 $defaultPreferences[
'watchlisthideminor'] = [
1336 'section' =>
'watchlist/changeswatchlist',
1337 'label-message' =>
'tog-watchlisthideminor',
1339 $defaultPreferences[
'watchlisthidebots'] = [
1341 'section' =>
'watchlist/changeswatchlist',
1342 'label-message' =>
'tog-watchlisthidebots',
1344 $defaultPreferences[
'watchlisthideown'] = [
1346 'section' =>
'watchlist/changeswatchlist',
1347 'label-message' =>
'tog-watchlisthideown',
1349 $defaultPreferences[
'watchlisthideanons'] = [
1351 'section' =>
'watchlist/changeswatchlist',
1352 'label-message' =>
'tog-watchlisthideanons',
1354 $defaultPreferences[
'watchlisthideliu'] = [
1356 'section' =>
'watchlist/changeswatchlist',
1357 'label-message' =>
'tog-watchlisthideliu',
1361 $defaultPreferences[
'watchlistreloadautomatically'] = [
1363 'section' =>
'watchlist/advancedwatchlist',
1364 'label-message' =>
'tog-watchlistreloadautomatically',
1368 $defaultPreferences[
'watchlistunwatchlinks'] = [
1370 'section' =>
'watchlist/advancedwatchlist',
1371 'label-message' =>
'tog-watchlistunwatchlinks',
1375 $defaultPreferences[
'watchlisthidecategorization'] = [
1377 'section' =>
'watchlist/changeswatchlist',
1378 'label-message' =>
'tog-watchlisthidecategorization',
1383 $defaultPreferences[
'watchlisthidepatrolled'] = [
1385 'section' =>
'watchlist/changeswatchlist',
1386 'label-message' =>
'tog-watchlisthidepatrolled',
1391 'edit' =>
'watchdefault',
1392 'move' =>
'watchmoves',
1393 'delete' =>
'watchdeletion'
1397 if ( $user->
isAllowedAny(
'createpage',
'createtalk' ) ) {
1398 $watchTypes[
'read'] =
'watchcreations';
1402 $watchTypes[
'rollback'] =
'watchrollback';
1406 $watchTypes[
'upload'] =
'watchuploads';
1409 foreach ( $watchTypes as $action => $pref ) {
1414 $defaultPreferences[$pref] = [
1416 'section' =>
'watchlist/pageswatchlist',
1417 'label-message' =>
"tog-$pref",
1422 $defaultPreferences[
'watchlisttoken'] = [
1426 $tokenButton = new \OOUI\ButtonWidget( [
1430 'label' => $context->
msg(
'prefs-watchlist-managetokens' )->text(),
1432 $defaultPreferences[
'watchlisttoken-info'] = [
1434 'section' =>
'watchlist/tokenwatchlist',
1435 'label-message' =>
'prefs-watchlist-token',
1436 'help-message' =>
'prefs-help-tokenmanagement',
1438 'default' => (string)$tokenButton,
1441 $defaultPreferences[
'wlenhancedfilters-disable'] = [
1443 'section' =>
'watchlist/advancedwatchlist',
1444 'label-message' =>
'rcfilters-watchlist-preference-label',
1445 'help-message' =>
'rcfilters-watchlist-preference-help',
1454 $defaultPreferences[
'search-special-page'] = [
1458 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1459 $defaultPreferences[
'searchNs' . $n] = [
1465 $defaultPreferences[
'search-match-redirect'] = [
1467 'section' =>
'searchoptions/searchmisc',
1468 'label-message' =>
'search-match-redirect-label',
1469 'help-message' =>
'search-match-redirect-help',
1472 $defaultPreferences[
'search-match-redirect'] = [
1477 $defaultPreferences[
'searchlimit'] = [
1481 'section' =>
'searchoptions/searchmisc',
1482 'label-message' =>
'searchlimit-label',
1483 'help-message' => $context->
msg(
'searchlimit-help', 500 ),
1484 'filter' => IntvalFilter::class,
1489 $thumbNamespaces = $this->config->get(
'ThumbnailNamespaces' );
1490 $thumbNamespacesFormatted = array_combine(
1493 static function ( $namespaceId ) use ( $context ) {
1494 return $namespaceId ===
NS_MAIN
1495 ? $context->
msg(
'blanknamespace' )->escaped()
1496 : $context->
getLanguage()->getFormattedNsText( $namespaceId );
1501 $defaultThumbNamespacesFormatted =
1502 array_intersect_key( $thumbNamespacesFormatted, [
NS_FILE => 1 ] ) ?? [];
1503 $extraThumbNamespacesFormatted =
1504 array_diff_key( $thumbNamespacesFormatted, [
NS_FILE => 1 ] );
1505 if ( $extraThumbNamespacesFormatted ) {
1506 $defaultPreferences[
'search-thumbnail-extra-namespaces'] = [
1508 'section' =>
'searchoptions/searchmisc',
1509 'label-message' =>
'search-thumbnail-extra-namespaces-label',
1510 'help-message' => $context->
msg(
1511 'search-thumbnail-extra-namespaces-message',
1512 $context->
getLanguage()->listToText( $extraThumbNamespacesFormatted ),
1513 count( $extraThumbNamespacesFormatted ),
1514 $context->
getLanguage()->listToText( $defaultThumbNamespacesFormatted ),
1515 count( $defaultThumbNamespacesFormatted )
1530 private static function sortSkinNames( $a, $b, $currentSkin, $preferredSkins ) {
1532 if ( strcasecmp( $a, $currentSkin ) === 0 ) {
1535 if ( strcasecmp( $b, $currentSkin ) === 0 ) {
1539 if ( count( $preferredSkins ) ) {
1540 $aPreferred = array_search( $a, $preferredSkins );
1541 $bPreferred = array_search( $b, $preferredSkins );
1544 if ( $aPreferred !==
false && $bPreferred ===
false ) {
1547 if ( $aPreferred ===
false && $bPreferred !==
false ) {
1552 if ( $aPreferred !==
false && $bPreferred !==
false ) {
1553 return strcasecmp( $aPreferred, $bPreferred );
1557 return strcasecmp( $a, $b );
1569 $previewtext = $context->
msg(
'skin-preview' )->escaped();
1572 $validSkinNames = $this->skinFactory->getAllowedSkins();
1573 $allInstalledSkins = $this->skinFactory->getInstalledSkins();
1576 $useSkin = $context->
getRequest()->getRawVal(
'useskin' );
1577 if ( isset( $allInstalledSkins[$useSkin] )
1578 && $context->
msg(
"skinname-$useSkin" )->exists()
1580 $validSkinNames[$useSkin] = $useSkin;
1584 $currentUserSkin = $this->userOptionsManager->getOption( $user,
'skin' );
1585 if ( isset( $allInstalledSkins[$currentUserSkin] )
1586 && $context->
msg(
"skinname-$currentUserSkin" )->exists()
1588 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1591 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1592 $msg = $context->
msg(
"skinname-{$skinkey}" );
1593 if ( $msg->exists() ) {
1594 $skinname = htmlspecialchars( $msg->text() );
1601 uksort( $validSkinNames,
function ( $a, $b ) use ( $currentUserSkin, $preferredSkins ) {
1602 return $this->sortSkinNames( $a, $b, $currentUserSkin, $preferredSkins );
1608 $foundDefault =
false;
1609 foreach ( $validSkinNames as $skinkey => $sn ) {
1613 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1614 $linkTools[] = $context->
msg(
'default' )->escaped();
1615 $foundDefault =
true;
1619 $talkPageMsg = $context->
msg(
"$skinkey-prefs-talkpage" );
1620 if ( $talkPageMsg->exists() ) {
1621 $linkTools[] = $talkPageMsg->parse();
1625 $mplink = htmlspecialchars( $mptitle->getLocalURL( [
'useskin' => $skinkey ] ) );
1626 $linkTools[] =
"<a target='_blank' href=\"$mplink\">$previewtext</a>";
1630 if ( $allowUserCss ) {
1632 $cssLinkText = $context->
msg(
'prefs-custom-css' )->text();
1633 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1636 if ( $allowUserJs ) {
1638 $jsLinkText = $context->
msg(
'prefs-custom-js' )->text();
1639 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1642 $display = $sn .
' ' . $context->
msg(
'parentheses' )
1643 ->rawParams( $context->
getLanguage()->pipeList( $linkTools ) )
1645 $ret[$display] = $skinkey;
1648 if ( !$foundDefault ) {
1663 $dateopts =
$lang->getDatePreferences();
1668 if ( !in_array(
'default', $dateopts ) ) {
1669 $dateopts[] =
'default';
1679 foreach ( $dateopts as $key ) {
1680 if ( $key ==
'default' ) {
1681 $formatted = $context->
msg(
'datedefault' )->escaped();
1683 $formatted = htmlspecialchars(
$lang->timeanddate( $epoch,
false, $key ) );
1685 $ret[$formatted] = $key;
1697 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1701 $display =
"{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1702 $ret[$display] = $index;
1714 $pixels = $l10n->
msg(
'unit-pixel' )->text();
1717 $display = $size . $pixels;
1718 $ret[$display] = $index;
1733 if ( is_string( $signature ) && mb_strlen( $signature ) > $maxSigChars ) {
1734 return $form->
msg(
'badsiglength' )->numParams( $maxSigChars )->escaped();
1737 if ( $signature ===
null || $signature ===
'' ) {
1744 if ( !( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) ) {
1761 $signature === $this->userOptionsManager->getOption( $user,
'nickname' ) &&
1762 (
bool)$alldata[
'fancysig'] === $this->userOptionsManager->getBoolOption( $user,
'fancysig' )
1767 if ( $sigValidation ===
'new' || $sigValidation ===
'disallow' ) {
1769 $parserOpts = ParserOptions::newFromContext( $form->
getContext() );
1770 $validator = $this->signatureValidatorFactory
1771 ->newSignatureValidator( $user, $form->
getContext(), $parserOpts );
1772 $errors = $validator->validateSignature( $signature );
1781 if ( $this->parser->validateSig( $signature ) ===
false ) {
1782 return $form->
msg(
'badsig' )->escaped();
1795 if ( isset( $alldata[
'fancysig'] ) && $alldata[
'fancysig'] ) {
1796 $signature = $this->parser->cleanSig( $signature );
1815 $formClass = PreferencesFormOOUI::class,
1823 if ( count( $remove ) ) {
1824 $removeKeys = array_fill_keys( $remove,
true );
1825 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1829 foreach ( $formDescriptor as $name => $info ) {
1830 if ( isset( $info[
'type'] ) && $info[
'type'] ===
'api' ) {
1831 unset( $formDescriptor[$name] );
1838 $htmlForm =
new $formClass( $formDescriptor, $context,
'prefs' );
1844 $htmlForm->setAction( $context->
getTitle()->getLocalURL( [
1845 'useskin' => $context->
getRequest()->getRawVal(
'useskin' )
1848 $htmlForm->setModifiedUser( $user );
1849 $htmlForm->setOptionsEditable( $user->
isAllowed(
'editmyoptions' ) );
1850 $htmlForm->setPrivateInfoEditable( $user->
isAllowed(
'editmyprivateinfo' ) );
1851 $htmlForm->setId(
'mw-prefs-form' );
1852 $htmlForm->setAutocomplete(
'off' );
1853 $htmlForm->setSubmitTextMsg(
'saveprefs' );
1855 $htmlForm->setSubmitTooltip(
'preferences-save' );
1856 $htmlForm->setSubmitID(
'prefcontrol' );
1857 $htmlForm->setSubmitCallback(
1859 return $this->
submitForm( $formData, $form, $formDescriptor );
1879 if ( !$user->
isAllowedAny(
'editmyprivateinfo',
'editmyoptions' )
1881 return Status::newFatal(
'mypreferencesprotected' );
1885 $this->
applyFilters( $formData, $formDescriptor,
'filterFromForm' );
1890 if ( !in_array(
'realname', $hiddenPrefs )
1891 && $user->
isAllowed(
'editmyprivateinfo' )
1892 && array_key_exists(
'realname', $formData )
1894 $realName = $formData[
'realname'];
1898 if ( $user->
isAllowed(
'editmyoptions' ) ) {
1899 $oldUserOptions = $this->userOptionsManager->getOptions( $user );
1902 unset( $formData[$b] );
1908 foreach ( $hiddenPrefs as $pref ) {
1911 $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref,
null,
true );
1916 isset( $formData[
'rclimit'] ) &&
1917 intval( $formData[
'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user,
'rclimit' )
1919 $formData[
'rcfilters-limit'] = $formData[
'rclimit'];
1923 $this->userOptionsManager->resetOptions( $user, $form->
getContext(),
'unused' );
1925 foreach ( $formData as $key => $value ) {
1926 $this->userOptionsManager->setOption( $user, $key, $value );
1929 $this->hookRunner->onPreferencesFormPreSave(
1930 $formData, $form, $user, $result, $oldUserOptions );
1946 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1947 foreach ( $formDescriptor as $preference => $desc ) {
1948 if ( !isset( $desc[
'filter'] ) || !isset( $preferences[$preference] ) ) {
1951 $filterDesc = $desc[
'filter'];
1952 if ( $filterDesc instanceof
Filter ) {
1953 $filter = $filterDesc;
1954 } elseif ( class_exists( $filterDesc ) ) {
1955 $filter =
new $filterDesc();
1956 } elseif ( is_callable( $filterDesc ) ) {
1957 $filter = $filterDesc();
1959 throw new UnexpectedValueException(
1960 "Unrecognized filter type for preference '$preference'"
1963 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1978 array $formDescriptor
1982 if (
$res ===
true ) {
1988 $url = $form->
getTitle()->getFullURL( $urlOptions );
1991 $context->
getRequest()->getSession()->set(
'specialPreferencesSaveSuccess', 1 );
1993 $context->
getOutput()->redirect( $url );
1996 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 EmailAuthentication
Name constant for the EmailAuthentication setting, for use with Config::get()
The Message class deals with fetching and processing of interface message into a variety of formats.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
This is one of the Core classes and should be read at least once by any new developers.
Set options of the Parser.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
static stripOuterParagraph( $html)
Strip outer.
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Factory class to create Skin objects.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static checkStructuredFilterUiEnabled(UserIdentity $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 "user group membership" – a specific instance of a user belonging to a group.
getName()
Get the user name, or the IP of an anonymous user.
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
getRealName()
Get the user's real name.
getRegistration()
Get the timestamp of account creation.
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.
useNPPatrol()
Check whether to enable new pages patrol features for this user.
setRealName(string $str)
Set the user's real name.
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.
saveSettings()
Save this user's settings into the database.
getEmail()
Get the user's e-mail address.
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Module of static functions for generating XML.
$wgDefaultUserOptions
Config variable stub for the DefaultUserOptions setting, for use by phpdoc and IDEs.
Interface for configuration instances.
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