MediaWiki master
DefaultPreferencesFactory.php
Go to the documentation of this file.
1<?php
22
24use Language;
25use LanguageCode;
58use OOUI\ButtonWidget;
59use OOUI\FieldLayout;
60use OOUI\HtmlSnippet;
61use OOUI\LabelWidget;
65use Psr\Log\LoggerAwareTrait;
66use Psr\Log\NullLogger;
67use SkinFactory;
68use UnexpectedValueException;
69use Xml;
70
75 use LoggerAwareTrait;
76
78 protected $options;
79
81 protected $contLang;
82
85
87 protected $authManager;
88
90 protected $linkRenderer;
91
93 protected $nsInfo;
94
97
99 private $languageConverter;
100
102 private $hookRunner;
103
106
108 private $languageConverterFactory;
109
111 private $parserFactory;
112
114 private $skinFactory;
115
117 private $userGroupManager;
118
120 private $signatureValidatorFactory;
121
125 public const CONSTRUCTOR_OPTIONS = [
157 ];
158
176 public function __construct(
183 ILanguageConverter $languageConverter,
185 HookContainer $hookContainer,
186 UserOptionsLookup $userOptionsLookup,
187 LanguageConverterFactory $languageConverterFactory = null,
188 ParserFactory $parserFactory = null,
189 SkinFactory $skinFactory = null,
190 UserGroupManager $userGroupManager = null,
191 SignatureValidatorFactory $signatureValidatorFactory = null
192 ) {
193 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
194
195 $this->options = $options;
196 $this->contLang = $contLang;
197 $this->authManager = $authManager;
198 $this->linkRenderer = $linkRenderer;
199 $this->nsInfo = $nsInfo;
200
201 // We don't use the PermissionManager anymore, but we need to be careful
202 // removing the parameter since this class is extended by GlobalPreferencesFactory
203 // in the GlobalPreferences extension, and that class uses it
204 $this->permissionManager = $permissionManager;
205
206 $this->logger = new NullLogger();
207 $this->languageConverter = $languageConverter;
208 $this->languageNameUtils = $languageNameUtils;
209 $this->hookRunner = new HookRunner( $hookContainer );
210
211 // Don't break GlobalPreferences, fall back to global state if missing services
212 // or if passed a UserOptionsLookup that isn't UserOptionsManager
213 $services = static function () {
214 // BC hack. Use a closure so this can be unit-tested.
216 };
217 $this->userOptionsManager = ( $userOptionsLookup instanceof UserOptionsManager )
218 ? $userOptionsLookup
219 : $services()->getUserOptionsManager();
220 $this->languageConverterFactory = $languageConverterFactory ?? $services()->getLanguageConverterFactory();
221
222 $this->parserFactory = $parserFactory ?? $services()->getParserFactory();
223 $this->skinFactory = $skinFactory ?? $services()->getSkinFactory();
224 $this->userGroupManager = $userGroupManager ?? $services()->getUserGroupManager();
225 $this->signatureValidatorFactory = $signatureValidatorFactory
226 ?? $services()->getSignatureValidatorFactory();
227 }
228
232 public function getSaveBlacklist() {
233 return [
234 'realname',
235 'emailaddress',
236 ];
237 }
238
244 public function getFormDescriptor( User $user, IContextSource $context ) {
245 $preferences = [];
246
247 OutputPage::setupOOUI(
248 strtolower( $context->getSkin()->getSkinName() ),
249 $context->getLanguage()->getDir()
250 );
251
252 $this->profilePreferences( $user, $context, $preferences );
253 $this->skinPreferences( $user, $context, $preferences );
254 $this->datetimePreferences( $user, $context, $preferences );
255 $this->filesPreferences( $context, $preferences );
256 $this->renderingPreferences( $user, $context, $preferences );
257 $this->editingPreferences( $user, $context, $preferences );
258 $this->rcPreferences( $user, $context, $preferences );
259 $this->watchlistPreferences( $user, $context, $preferences );
260 $this->searchPreferences( $context, $preferences );
261
262 $this->hookRunner->onGetPreferences( $user, $preferences );
263
264 $this->loadPreferenceValues( $user, $context, $preferences );
265 $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
266 return $preferences;
267 }
268
275 public static function simplifyFormDescriptor( array $descriptor ) {
276 foreach ( $descriptor as $name => &$params ) {
277 // Info fields are useless and can use complicated closure to provide
278 // text, skip all of them.
279 if ( ( isset( $params['type'] ) && $params['type'] === 'info' ) ||
280 ( isset( $params['class'] ) && $params['class'] === \HTMLInfoField::class )
281 ) {
282 unset( $descriptor[$name] );
283 continue;
284 }
285 // Message parsing is the heaviest load when constructing the field,
286 // but we just want to validate data.
287 foreach ( $params as $key => $value ) {
288 switch ( $key ) {
289 // Special case, should be kept.
290 case 'options-message':
291 break;
292 // Special case, should be transferred.
293 case 'options-messages':
294 unset( $params[$key] );
295 $params['options'] = $value;
296 break;
297 default:
298 if ( preg_match( '/-messages?$/', $key ) ) {
299 // Unwanted.
300 unset( $params[$key] );
301 }
302 }
303 }
304 }
305 return $descriptor;
306 }
307
315 private function loadPreferenceValues( User $user, IContextSource $context, &$defaultPreferences ) {
316 // Remove preferences that wikis don't want to use
317 foreach ( $this->options->get( MainConfigNames::HiddenPrefs ) as $pref ) {
318 unset( $defaultPreferences[$pref] );
319 }
320
321 // For validation.
322 $simplified = self::simplifyFormDescriptor( $defaultPreferences );
323 $form = new HTMLForm( $simplified, $context );
324
325 $disable = !$user->isAllowed( 'editmyoptions' );
326
327 $defaultOptions = $this->userOptionsManager->getDefaultOptions( $user );
328 $userOptions = $this->userOptionsManager->getOptions( $user );
329 $this->applyFilters( $userOptions, $defaultPreferences, 'filterForForm' );
330 // Add in defaults from the user
331 foreach ( $simplified as $name => $_ ) {
332 $info = &$defaultPreferences[$name];
333 if ( $disable && !in_array( $name, $this->getSaveBlacklist() ) ) {
334 $info['disabled'] = 'disabled';
335 }
336 if ( isset( $info['default'] ) ) {
337 // Already set, no problem
338 continue;
339 }
340 $field = $form->getField( $name );
341 $globalDefault = $defaultOptions[$name] ?? null;
342 $prefFromUser = static::getPreferenceForField( $name, $field, $userOptions );
343
344 // If it validates, set it as the default
345 // FIXME: That's not how the validate() function works! Values of nested fields
346 // (e.g. CheckMatix) would be missing.
347 if ( $prefFromUser !== null && // Make sure we're not just pulling nothing
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;
352 } else {
353 $globalDefault = json_encode( $globalDefault );
354 throw new UnexpectedValueException(
355 "Default '$globalDefault' is invalid for preference $name of user " . $user->getName()
356 );
357 }
358 }
359
360 return $defaultPreferences;
361 }
362
373 public static function getPreferenceForField( $name, HTMLFormField $field, array $userOptions ) {
374 $val = $userOptions[$name] ?? null;
375
376 if ( $field instanceof HTMLNestedFilterable ) {
377 $val = [];
378 $prefix = $field->mParams['prefix'] ?? $name;
379 // Fetch all possible preference keys of the given field on this wiki.
380 $keys = array_keys( $field->filterDataForSubmit( [] ) );
381 foreach ( $keys as $key ) {
382 if ( $userOptions[$prefix . $key] ?? false ) {
383 $val[] = $key;
384 }
385 }
386 }
387
388 return $val;
389 }
390
400 protected function getOptionFromUser( $name, $info, array $userOptions ) {
401 $val = $userOptions[$name] ?? null;
402
403 // Handling for multiselect preferences
404 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
405 ( isset( $info['class'] ) && $info['class'] == \HTMLMultiSelectField::class ) ) {
406 $options = HTMLFormField::flattenOptions( $info['options-messages'] ?? $info['options'] );
407 $prefix = $info['prefix'] ?? $name;
408 $val = [];
409
410 foreach ( $options as $value ) {
411 if ( $userOptions["$prefix$value"] ?? false ) {
412 $val[] = $value;
413 }
414 }
415 }
416
417 // Handling for checkmatrix preferences
418 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
419 ( isset( $info['class'] ) && $info['class'] == \HTMLCheckMatrix::class ) ) {
420 $columns = HTMLFormField::flattenOptions( $info['columns'] );
421 $rows = HTMLFormField::flattenOptions( $info['rows'] );
422 $prefix = $info['prefix'] ?? $name;
423 $val = [];
424
425 foreach ( $columns as $column ) {
426 foreach ( $rows as $row ) {
427 if ( $userOptions["$prefix$column-$row"] ?? false ) {
428 $val[] = "$column-$row";
429 }
430 }
431 }
432 }
433
434 return $val;
435 }
436
444 protected function profilePreferences(
445 User $user, IContextSource $context, &$defaultPreferences
446 ) {
447 // retrieving user name for GENDER and misc.
448 $userName = $user->getName();
449
450 // Information panel
451 $defaultPreferences['username'] = [
452 'type' => 'info',
453 'label-message' => [ 'username', $userName ],
454 'default' => $userName,
455 'section' => 'personal/info',
456 ];
457
458 $lang = $context->getLanguage();
459
460 // Get groups to which the user belongs, Skip the default * group, seems useless here
461 $userEffectiveGroups = array_diff(
462 $this->userGroupManager->getUserEffectiveGroups( $user ),
463 [ '*' ]
464 );
465 $defaultPreferences['usergroups'] = [
466 'type' => 'info',
467 'label-message' => [ 'prefs-memberingroups',
468 \Message::numParam( count( $userEffectiveGroups ) ), $userName ],
469 'default' => function () use ( $user, $userEffectiveGroups, $context, $lang, $userName ) {
470 $userGroupMemberships = $this->userGroupManager->getUserGroupMemberships( $user );
471 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
472 foreach ( $userEffectiveGroups as $ueg ) {
473 $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
474
475 $userG = UserGroupMembership::getLinkHTML( $groupStringOrObject, $context );
476 $userM = UserGroupMembership::getLinkHTML( $groupStringOrObject, $context, $userName );
477
478 // Store expiring groups separately, so we can place them before non-expiring
479 // groups in the list. This is to avoid the ambiguity of something like
480 // "administrator, bureaucrat (until X date)" -- users might wonder whether the
481 // expiry date applies to both groups, or just the last one
482 if ( $groupStringOrObject instanceof UserGroupMembership &&
483 $groupStringOrObject->getExpiry()
484 ) {
485 $userTempGroups[] = $userG;
486 $userTempMembers[] = $userM;
487 } else {
488 $userGroups[] = $userG;
489 $userMembers[] = $userM;
490 }
491 }
492 sort( $userGroups );
493 sort( $userMembers );
494 sort( $userTempGroups );
495 sort( $userTempMembers );
496 $userGroups = array_merge( $userTempGroups, $userGroups );
497 $userMembers = array_merge( $userTempMembers, $userMembers );
498 return $context->msg( 'prefs-memberingroups-type' )
499 ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
500 ->escaped();
501 },
502 'raw' => true,
503 'section' => 'personal/info',
504 ];
505
506 $contribTitle = SpecialPage::getTitleFor( "Contributions", $userName );
507 $formattedEditCount = $lang->formatNum( $user->getEditCount() );
508 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
509
510 $defaultPreferences['editcount'] = [
511 'type' => 'info',
512 'raw' => true,
513 'label-message' => 'prefs-edits',
514 'default' => $editCount,
515 'section' => 'personal/info',
516 ];
517
518 if ( $user->getRegistration() ) {
519 $displayUser = $context->getUser();
520 $userRegistration = $user->getRegistration();
521 $defaultPreferences['registrationdate'] = [
522 'type' => 'info',
523 'label-message' => 'prefs-registration',
524 'default' => $context->msg(
525 'prefs-registration-date-time',
526 $lang->userTimeAndDate( $userRegistration, $displayUser ),
527 $lang->userDate( $userRegistration, $displayUser ),
528 $lang->userTime( $userRegistration, $displayUser )
529 )->text(),
530 'section' => 'personal/info',
531 ];
532 }
533
534 $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
535 $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
536
537 // Actually changeable stuff
538 $defaultPreferences['realname'] = [
539 // (not really "private", but still shouldn't be edited without permission)
540 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'realname' )
541 ? 'text' : 'info',
542 'default' => $user->getRealName(),
543 'section' => 'personal/info',
544 'label-message' => 'yourrealname',
545 'help-message' => 'prefs-help-realname',
546 ];
547
548 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
549 new PasswordAuthenticationRequest(), false )->isGood()
550 ) {
551 $defaultPreferences['password'] = [
552 'type' => 'info',
553 'raw' => true,
554 'default' => (string)new ButtonWidget( [
555 'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
556 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
557 ] ),
558 'label' => $context->msg( 'prefs-resetpass' )->text(),
559 ] ),
560 'label-message' => 'yourpassword',
561 // email password reset feature only works for users that have an email set up
562 'help' => $this->options->get( MainConfigNames::AllowRequiringEmailForResets ) &&
563 $user->getEmail()
564 ? $context->msg( 'prefs-help-yourpassword',
565 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
566 : '',
567 'section' => 'personal/info',
568 ];
569 }
570 // Only show prefershttps if secure login is turned on
571 if ( !$this->options->get( MainConfigNames::ForceHTTPS )
572 && $this->options->get( MainConfigNames::SecureLogin )
573 ) {
574 $defaultPreferences['prefershttps'] = [
575 'type' => 'toggle',
576 'label-message' => 'tog-prefershttps',
577 'help-message' => 'prefs-help-prefershttps',
578 'section' => 'personal/info'
579 ];
580 }
581
582 $defaultPreferences['downloaduserdata'] = [
583 'type' => 'info',
584 'raw' => true,
585 'label-message' => 'prefs-user-downloaddata-label',
586 'default' => Html::element(
587 'a',
588 [
589 'href' => $this->options->get( MainConfigNames::ScriptPath ) .
590 '/api.php?action=query&meta=userinfo&uiprop=*&formatversion=2',
591 ],
592 $context->msg( 'prefs-user-downloaddata-info' )->text()
593 ),
594 'help-message' => [ 'prefs-user-downloaddata-help-message', urlencode( $user->getTitleKey() ) ],
595 'section' => 'personal/info',
596 ];
597
598 $defaultPreferences['restoreprefs'] = [
599 'type' => 'info',
600 'raw' => true,
601 'label-message' => 'prefs-user-restoreprefs-label',
602 'default' => Html::element(
603 'a',
604 [
605 'href' => SpecialPage::getTitleFor( 'Preferences' )
606 ->getSubpage( 'reset' )->getLocalURL()
607 ],
608 $context->msg( 'prefs-user-restoreprefs-info' )->text()
609 ),
610 'section' => 'personal/info',
611 ];
612
613 $languages = $this->languageNameUtils->getLanguageNames(
614 LanguageNameUtils::AUTONYMS,
615 LanguageNameUtils::SUPPORTED
616 );
617 $languageCode = $this->options->get( MainConfigNames::LanguageCode );
618 if ( !array_key_exists( $languageCode, $languages ) ) {
619 $languages[$languageCode] = $languageCode;
620 // Sort the array again
621 ksort( $languages );
622 }
623
624 $options = [];
625 foreach ( $languages as $code => $name ) {
626 $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
627 $options[$display] = $code;
628 }
629 $defaultPreferences['language'] = [
630 'type' => 'select',
631 'section' => 'personal/i18n',
632 'options' => $options,
633 'label-message' => 'yourlanguage',
634 ];
635
636 $neutralGenderMessage = $context->msg( 'gender-notknown' )->escaped() . (
637 !$context->msg( 'gender-unknown' )->isDisabled()
638 ? "<br>" . $context->msg( 'parentheses' )
639 ->params( $context->msg( 'gender-unknown' )->plain() )
640 ->escaped()
641 : ''
642 );
643
644 $defaultPreferences['gender'] = [
645 'type' => 'radio',
646 'section' => 'personal/i18n',
647 'options' => [
648 $neutralGenderMessage => 'unknown',
649 $context->msg( 'gender-female' )->escaped() => 'female',
650 $context->msg( 'gender-male' )->escaped() => 'male',
651 ],
652 'label-message' => 'yourgender',
653 'help-message' => 'prefs-help-gender',
654 ];
655
656 // see if there are multiple language variants to choose from
657 if ( !$this->languageConverterFactory->isConversionDisabled() ) {
658
659 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
660 if ( $langCode == $this->contLang->getCode() ) {
661 if ( !$this->languageConverter->hasVariants() ) {
662 continue;
663 }
664
665 $variants = $this->languageConverter->getVariants();
666 $variantArray = [];
667 foreach ( $variants as $v ) {
668 $v = str_replace( '_', '-', strtolower( $v ) );
669 $variantArray[$v] = $lang->getVariantname( $v, false );
670 }
671
672 $options = [];
673 foreach ( $variantArray as $code => $name ) {
674 $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
675 $options[$display] = $code;
676 }
677
678 $defaultPreferences['variant'] = [
679 'label-message' => 'yourvariant',
680 'type' => 'select',
681 'options' => $options,
682 'section' => 'personal/i18n',
683 'help-message' => 'prefs-help-variant',
684 ];
685 } else {
686 $defaultPreferences["variant-$langCode"] = [
687 'type' => 'api',
688 ];
689 }
690 }
691 }
692
693 // show a preview of the old signature first
694 $oldsigWikiText = $this->parserFactory->getInstance()->preSaveTransform(
695 '~~~',
696 $context->getTitle(),
697 $user,
698 ParserOptions::newFromContext( $context )
699 );
700 $oldsigHTML = Parser::stripOuterParagraph(
701 $context->getOutput()->parseAsContent( $oldsigWikiText )
702 );
703 $signatureFieldConfig = [];
704 // Validate existing signature and show a message about it
705 $signature = $this->userOptionsManager->getOption( $user, 'nickname' );
706 $useFancySig = $this->userOptionsManager->getBoolOption( $user, 'fancysig' );
707 if ( $useFancySig && $signature !== '' ) {
708 $parserOpts = ParserOptions::newFromContext( $context );
709 $validator = $this->signatureValidatorFactory
710 ->newSignatureValidator( $user, $context, $parserOpts );
711 $signatureErrors = $validator->validateSignature( $signature );
712 if ( $signatureErrors ) {
713 $sigValidation = $this->options->get( MainConfigNames::SignatureValidation );
714 $oldsigHTML .= '<p><strong>' .
715 // Messages used here:
716 // * prefs-signature-invalid-warning
717 // * prefs-signature-invalid-new
718 // * prefs-signature-invalid-disallow
719 $context->msg( "prefs-signature-invalid-$sigValidation" )->parse() .
720 '</strong></p>';
721
722 // On initial page load, show the warnings as well
723 // (when posting, you get normal validation errors instead)
724 foreach ( $signatureErrors as &$sigError ) {
725 $sigError = new HtmlSnippet( $sigError );
726 }
727 if ( !$context->getRequest()->wasPosted() ) {
728 $signatureFieldConfig = [
729 'warnings' => $sigValidation !== 'disallow' ? $signatureErrors : null,
730 'errors' => $sigValidation === 'disallow' ? $signatureErrors : null,
731 ];
732 }
733 }
734 }
735 $defaultPreferences['oldsig'] = [
736 'type' => 'info',
737 // Normally HTMLFormFields do not display warnings, so we need to use 'rawrow'
738 // and provide the entire OOUI\FieldLayout here
739 'rawrow' => true,
740 'default' => new FieldLayout(
741 new LabelWidget( [
742 'label' => new HtmlSnippet( $oldsigHTML ),
743 ] ),
744 [
745 'align' => 'top',
746 'label' => new HtmlSnippet( $context->msg( 'tog-oldsig' )->parse() )
747 ] + $signatureFieldConfig
748 ),
749 'section' => 'personal/signature',
750 ];
751 $defaultPreferences['nickname'] = [
752 'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
753 'maxlength' => $this->options->get( MainConfigNames::MaxSigChars ),
754 'label-message' => 'yournick',
755 'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
756 return $this->validateSignature( $signature, $alldata, $form );
757 },
758 'section' => 'personal/signature',
759 'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
760 return $this->cleanSignature( $signature, $alldata, $form );
761 },
762 ];
763 $defaultPreferences['fancysig'] = [
764 'type' => 'toggle',
765 'label-message' => 'tog-fancysig',
766 // show general help about signature at the bottom of the section
767 'help-message' => 'prefs-help-signature',
768 'section' => 'personal/signature'
769 ];
770
771 // Email preferences
772 if ( $this->options->get( MainConfigNames::EnableEmail ) ) {
773 if ( $canViewPrivateInfo ) {
774 $helpMessages = [];
775 $helpMessages[] = $this->options->get( MainConfigNames::EmailConfirmToEdit )
776 ? 'prefs-help-email-required'
777 : 'prefs-help-email';
778
779 if ( $this->options->get( MainConfigNames::EnableUserEmail ) ) {
780 // additional messages when users can send email to each other
781 $helpMessages[] = 'prefs-help-email-others';
782 }
783
784 $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
785 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
786 $button = new ButtonWidget( [
787 'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
788 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
789 ] ),
790 'label' =>
791 $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
792 ] );
793
794 $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
795 }
796
797 $defaultPreferences['emailaddress'] = [
798 'type' => 'info',
799 'raw' => true,
800 'default' => $emailAddress,
801 'label-message' => 'youremail',
802 'section' => 'personal/email',
803 'help-messages' => $helpMessages,
804 // 'cssclass' chosen below
805 ];
806 }
807
808 $disableEmailPrefs = false;
809
810 if ( $this->options->get( MainConfigNames::AllowRequiringEmailForResets ) ) {
811 $defaultPreferences['requireemail'] = [
812 'type' => 'toggle',
813 'label-message' => 'tog-requireemail',
814 'help-message' => 'prefs-help-requireemail',
815 'section' => 'personal/email',
816 'disabled' => !$user->getEmail(),
817 ];
818 }
819
820 if ( $this->options->get( MainConfigNames::EmailAuthentication ) ) {
821 if ( $user->getEmail() ) {
822 if ( $user->getEmailAuthenticationTimestamp() ) {
823 // date and time are separate parameters to facilitate localisation.
824 // $time is kept for backward compat reasons.
825 // 'emailauthenticated' is also used in SpecialConfirmemail.php
826 $displayUser = $context->getUser();
827 $emailTimestamp = $user->getEmailAuthenticationTimestamp();
828 $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
829 $d = $lang->userDate( $emailTimestamp, $displayUser );
830 $t = $lang->userTime( $emailTimestamp, $displayUser );
831 $emailauthenticated = $context->msg( 'emailauthenticated',
832 $time, $d, $t )->parse() . '<br />';
833 $emailauthenticationclass = 'mw-email-authenticated';
834 } else {
835 $disableEmailPrefs = true;
836 $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
837 new ButtonWidget( [
838 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
839 'label' => $context->msg( 'emailconfirmlink' )->text(),
840 ] );
841 $emailauthenticationclass = "mw-email-not-authenticated";
842 }
843 } else {
844 $disableEmailPrefs = true;
845 $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
846 $emailauthenticationclass = 'mw-email-none';
847 }
848
849 if ( $canViewPrivateInfo ) {
850 $defaultPreferences['emailauthentication'] = [
851 'type' => 'info',
852 'raw' => true,
853 'section' => 'personal/email',
854 'label-message' => 'prefs-emailconfirm-label',
855 'default' => $emailauthenticated,
856 // Apply the same CSS class used on the input to the message:
857 'cssclass' => $emailauthenticationclass,
858 ];
859 }
860 }
861
862 if ( $this->options->get( MainConfigNames::EnableUserEmail ) &&
863 $user->isAllowed( 'sendemail' )
864 ) {
865 $defaultPreferences['disablemail'] = [
866 'id' => 'wpAllowEmail',
867 'type' => 'toggle',
868 'invert' => true,
869 'section' => 'personal/email',
870 'label-message' => 'allowemail',
871 'disabled' => $disableEmailPrefs,
872 ];
873
874 $defaultPreferences['email-allow-new-users'] = [
875 'id' => 'wpAllowEmailFromNewUsers',
876 'type' => 'toggle',
877 'section' => 'personal/email',
878 'label-message' => 'email-allow-new-users-label',
879 'disabled' => $disableEmailPrefs,
880 'disable-if' => [ '!==', 'disablemail', '1' ],
881 ];
882
883 $defaultPreferences['ccmeonemails'] = [
884 'type' => 'toggle',
885 'section' => 'personal/email',
886 'label-message' => 'tog-ccmeonemails',
887 'disabled' => $disableEmailPrefs,
888 ];
889
890 if ( $this->options->get( MainConfigNames::EnableUserEmailMuteList ) ) {
891 $defaultPreferences['email-blacklist'] = [
892 'type' => 'usersmultiselect',
893 'label-message' => 'email-mutelist-label',
894 'section' => 'personal/email',
895 'disabled' => $disableEmailPrefs,
896 'filter' => MultiUsernameFilter::class,
897 ];
898 }
899 }
900
901 if ( $this->options->get( MainConfigNames::EnotifWatchlist ) ) {
902 $defaultPreferences['enotifwatchlistpages'] = [
903 'type' => 'toggle',
904 'section' => 'personal/email',
905 'label-message' => 'tog-enotifwatchlistpages',
906 'disabled' => $disableEmailPrefs,
907 ];
908 }
909 if ( $this->options->get( MainConfigNames::EnotifUserTalk ) ) {
910 $defaultPreferences['enotifusertalkpages'] = [
911 'type' => 'toggle',
912 'section' => 'personal/email',
913 'label-message' => 'tog-enotifusertalkpages',
914 'disabled' => $disableEmailPrefs,
915 ];
916 }
917 if ( $this->options->get( MainConfigNames::EnotifUserTalk ) ||
918 $this->options->get( MainConfigNames::EnotifWatchlist ) ) {
919 if ( $this->options->get( MainConfigNames::EnotifMinorEdits ) ) {
920 $defaultPreferences['enotifminoredits'] = [
921 'type' => 'toggle',
922 'section' => 'personal/email',
923 'label-message' => 'tog-enotifminoredits',
924 'disabled' => $disableEmailPrefs,
925 ];
926 }
927
928 if ( $this->options->get( MainConfigNames::EnotifRevealEditorAddress ) ) {
929 $defaultPreferences['enotifrevealaddr'] = [
930 'type' => 'toggle',
931 'section' => 'personal/email',
932 'label-message' => 'tog-enotifrevealaddr',
933 'disabled' => $disableEmailPrefs,
934 ];
935 }
936 }
937 }
938 }
939
946 protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
947 // Skin selector, if there is at least one valid skin
948 $validSkinNames = $this->getValidSkinNames( $user, $context );
949 if ( $validSkinNames ) {
950 $defaultPreferences['skin'] = [
951 // @phan-suppress-next-line SecurityCheck-XSS False +ve, label is escaped in generateSkinOptions()
952 'type' => 'radio',
953 'options' => $this->generateSkinOptions( $user, $context, $validSkinNames ),
954 'section' => 'rendering/skin',
955 ];
956 $hideCond = [ 'AND' ];
957 foreach ( $validSkinNames as $skinName => $_ ) {
958 $options = $this->skinFactory->getSkinOptions( $skinName );
959 if ( $options['responsive'] ?? false ) {
960 $hideCond[] = [ '!==', 'skin', $skinName ];
961 }
962 }
963 if ( $hideCond === [ 'AND' ] ) {
964 $hideCond = [];
965 }
966 $defaultPreferences['skin-responsive'] = [
967 'type' => 'check',
968 'label-message' => 'prefs-skin-responsive',
969 'section' => 'rendering/skin/skin-prefs',
970 'help-message' => 'prefs-help-skin-responsive',
971 'hide-if' => $hideCond,
972 ];
973 }
974
975 $allowUserCss = $this->options->get( MainConfigNames::AllowUserCss );
976 $allowUserJs = $this->options->get( MainConfigNames::AllowUserJs );
977 $safeMode = $this->userOptionsManager->getOption( $user, 'forcesafemode' );
978 // Create links to user CSS/JS pages for all skins.
979 // This code is basically copied from generateSkinOptions().
980 // @todo Refactor this and the similar code in generateSkinOptions().
981 if ( $allowUserCss || $allowUserJs ) {
982 if ( $safeMode ) {
983 $defaultPreferences['customcssjs-safemode'] = [
984 'type' => 'info',
985 'raw' => true,
986 'default' => Html::warningBox( $context->msg( 'prefs-custom-cssjs-safemode' )->parse() ),
987 'section' => 'rendering/skin',
988 ];
989 } else {
990 $linkTools = [];
991 $userName = $user->getName();
992
993 if ( $allowUserCss ) {
994 $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
995 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
996 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
997 }
998
999 if ( $allowUserJs ) {
1000 $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
1001 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1002 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1003 }
1004
1005 $defaultPreferences['commoncssjs'] = [
1006 'type' => 'info',
1007 'raw' => true,
1008 'default' => $context->getLanguage()->pipeList( $linkTools ),
1009 'label-message' => 'prefs-common-config',
1010 'section' => 'rendering/skin',
1011 ];
1012 }
1013 }
1014 }
1015
1020 protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
1021 $defaultPreferences['imagesize'] = [
1022 'type' => 'select',
1023 'options' => $this->getImageSizes( $context ),
1024 'label-message' => 'imagemaxsize',
1025 'section' => 'rendering/files',
1026 ];
1027 $defaultPreferences['thumbsize'] = [
1028 'type' => 'select',
1029 'options' => $this->getThumbSizes( $context ),
1030 'label-message' => 'thumbsize',
1031 'section' => 'rendering/files',
1032 ];
1033 }
1034
1041 protected function datetimePreferences(
1042 User $user, IContextSource $context, &$defaultPreferences
1043 ) {
1044 $dateOptions = $this->getDateOptions( $context );
1045 if ( $dateOptions ) {
1046 $defaultPreferences['date'] = [
1047 'type' => 'radio',
1048 'options' => $dateOptions,
1049 'section' => 'rendering/dateformat',
1050 ];
1051 }
1052
1053 // Info
1054 $now = wfTimestampNow();
1055 $lang = $context->getLanguage();
1056 $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ],
1057 $lang->userTime( $now, $user ) );
1058 $nowserver = $lang->userTime( $now, $user,
1059 [ 'format' => false, 'timecorrection' => false ] ) .
1060 Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
1061
1062 $defaultPreferences['nowserver'] = [
1063 'type' => 'info',
1064 'raw' => 1,
1065 'label-message' => 'servertime',
1066 'default' => $nowserver,
1067 'section' => 'rendering/timeoffset',
1068 ];
1069
1070 $defaultPreferences['nowlocal'] = [
1071 'type' => 'info',
1072 'raw' => 1,
1073 'label-message' => 'localtime',
1074 'default' => $nowlocal,
1075 'section' => 'rendering/timeoffset',
1076 ];
1077
1078 $userTimeCorrection = (string)$this->userOptionsManager->getOption( $user, 'timecorrection' );
1079 // This value should already be normalized by UserTimeCorrection, so it should always be valid and not
1080 // in the legacy format. However, let's be sure about that and normalize it again.
1081 // Also, recompute the offset because it can change with DST.
1082 $userTimeCorrectionObj = new UserTimeCorrection(
1083 $userTimeCorrection,
1084 null,
1085 $this->options->get( MainConfigNames::LocalTZoffset )
1086 );
1087
1088 if ( $userTimeCorrectionObj->getCorrectionType() === UserTimeCorrection::OFFSET ) {
1089 $tzDefault = UserTimeCorrection::formatTimezoneOffset( $userTimeCorrectionObj->getTimeOffset() );
1090 } else {
1091 $tzDefault = $userTimeCorrectionObj->toString();
1092 }
1093
1094 $defaultPreferences['timecorrection'] = [
1095 'type' => 'timezone',
1096 'label-message' => 'timezonelegend',
1097 'default' => $tzDefault,
1098 'size' => 20,
1099 'section' => 'rendering/timeoffset',
1100 'id' => 'wpTimeCorrection',
1101 'filter' => TimezoneFilter::class,
1102 ];
1103 }
1104
1110 protected function renderingPreferences(
1111 User $user,
1112 MessageLocalizer $l10n,
1113 &$defaultPreferences
1114 ) {
1115 // Diffs
1116 $defaultPreferences['diffonly'] = [
1117 'type' => 'toggle',
1118 'section' => 'rendering/diffs',
1119 'label-message' => 'tog-diffonly',
1120 ];
1121 $defaultPreferences['norollbackdiff'] = [
1122 'type' => 'toggle',
1123 'section' => 'rendering/diffs',
1124 'label-message' => 'tog-norollbackdiff',
1125 ];
1126 $defaultPreferences['diff-type'] = [
1127 'type' => 'api',
1128 ];
1129
1130 // Page Rendering
1131 if ( $this->options->get( MainConfigNames::AllowUserCssPrefs ) ) {
1132 $defaultPreferences['underline'] = [
1133 'type' => 'select',
1134 'options' => [
1135 $l10n->msg( 'underline-never' )->text() => 0,
1136 $l10n->msg( 'underline-always' )->text() => 1,
1137 $l10n->msg( 'underline-default' )->text() => 2,
1138 ],
1139 'label-message' => 'tog-underline',
1140 'section' => 'rendering/advancedrendering',
1141 ];
1142 }
1143
1144 $defaultPreferences['showhiddencats'] = [
1145 'type' => 'toggle',
1146 'section' => 'rendering/advancedrendering',
1147 'label-message' => 'tog-showhiddencats'
1148 ];
1149
1150 if ( $user->isAllowed( 'rollback' ) ) {
1151 $defaultPreferences['showrollbackconfirmation'] = [
1152 'type' => 'toggle',
1153 'section' => 'rendering/advancedrendering',
1154 'label-message' => 'tog-showrollbackconfirmation',
1155 ];
1156 }
1157
1158 $defaultPreferences['forcesafemode'] = [
1159 'type' => 'toggle',
1160 'section' => 'rendering/advancedrendering',
1161 'label-message' => 'tog-forcesafemode',
1162 'help-message' => 'prefs-help-forcesafemode'
1163 ];
1164 }
1165
1171 protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1172 $defaultPreferences['editsectiononrightclick'] = [
1173 'type' => 'toggle',
1174 'section' => 'editing/advancedediting',
1175 'label-message' => 'tog-editsectiononrightclick',
1176 ];
1177 $defaultPreferences['editondblclick'] = [
1178 'type' => 'toggle',
1179 'section' => 'editing/advancedediting',
1180 'label-message' => 'tog-editondblclick',
1181 ];
1182
1183 if ( $this->options->get( MainConfigNames::AllowUserCssPrefs ) ) {
1184 $defaultPreferences['editfont'] = [
1185 'type' => 'select',
1186 'section' => 'editing/editor',
1187 'label-message' => 'editfont-style',
1188 'options' => [
1189 $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
1190 $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
1191 $l10n->msg( 'editfont-serif' )->text() => 'serif',
1192 ]
1193 ];
1194 }
1195
1196 if ( $user->isAllowed( 'minoredit' ) ) {
1197 $defaultPreferences['minordefault'] = [
1198 'type' => 'toggle',
1199 'section' => 'editing/editor',
1200 'label-message' => 'tog-minordefault',
1201 ];
1202 }
1203
1204 $defaultPreferences['forceeditsummary'] = [
1205 'type' => 'toggle',
1206 'section' => 'editing/editor',
1207 'label-message' => 'tog-forceeditsummary',
1208 ];
1209
1210 // T350653
1211 if ( $this->options->get( MainConfigNames::EnableEditRecovery ) ) {
1212 $defaultPreferences['editrecovery'] = [
1213 'type' => 'toggle',
1214 'section' => 'editing/editor',
1215 'label-message' => 'tog-editrecovery',
1216 'help-message' => [
1217 'tog-editrecovery-help',
1218 'https://meta.wikimedia.org/wiki/Talk:Community_Wishlist_Survey_2023/Edit-recovery_feature',
1219 ],
1220 ];
1221 }
1222
1223 $defaultPreferences['useeditwarning'] = [
1224 'type' => 'toggle',
1225 'section' => 'editing/editor',
1226 'label-message' => 'tog-useeditwarning',
1227 ];
1228
1229 $defaultPreferences['previewonfirst'] = [
1230 'type' => 'toggle',
1231 'section' => 'editing/preview',
1232 'label-message' => 'tog-previewonfirst',
1233 ];
1234 $defaultPreferences['previewontop'] = [
1235 'type' => 'toggle',
1236 'section' => 'editing/preview',
1237 'label-message' => 'tog-previewontop',
1238 ];
1239 $defaultPreferences['uselivepreview'] = [
1240 'type' => 'toggle',
1241 'section' => 'editing/preview',
1242 'label-message' => 'tog-uselivepreview',
1243 ];
1244 }
1245
1251 protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1252 $rcMaxAge = $this->options->get( MainConfigNames::RCMaxAge );
1253 $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1254 $defaultPreferences['rcdays'] = [
1255 'type' => 'float',
1256 'label-message' => 'recentchangesdays',
1257 'section' => 'rc/displayrc',
1258 'min' => 1 / 24,
1259 'max' => $rcMax,
1260 'help-message' => [ 'recentchangesdays-max', Message::numParam( $rcMax ) ],
1261 ];
1262 $defaultPreferences['rclimit'] = [
1263 'type' => 'int',
1264 'min' => 1,
1265 'max' => 1000,
1266 'label-message' => 'recentchangescount',
1267 'help-message' => 'prefs-help-recentchangescount',
1268 'section' => 'rc/displayrc',
1269 'filter' => IntvalFilter::class,
1270 ];
1271 $defaultPreferences['usenewrc'] = [
1272 'type' => 'toggle',
1273 'label-message' => 'tog-usenewrc',
1274 'section' => 'rc/advancedrc',
1275 ];
1276 $defaultPreferences['hideminor'] = [
1277 'type' => 'toggle',
1278 'label-message' => 'tog-hideminor',
1279 'section' => 'rc/changesrc',
1280 ];
1281 $defaultPreferences['pst-cssjs'] = [
1282 'type' => 'api',
1283 ];
1284 $defaultPreferences['rcfilters-rc-collapsed'] = [
1285 'type' => 'api',
1286 ];
1287 $defaultPreferences['rcfilters-wl-collapsed'] = [
1288 'type' => 'api',
1289 ];
1290 $defaultPreferences['rcfilters-saved-queries'] = [
1291 'type' => 'api',
1292 ];
1293 $defaultPreferences['rcfilters-wl-saved-queries'] = [
1294 'type' => 'api',
1295 ];
1296 // Override RCFilters preferences for RecentChanges 'limit'
1297 $defaultPreferences['rcfilters-limit'] = [
1298 'type' => 'api',
1299 ];
1300 $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
1301 'type' => 'api',
1302 ];
1303 $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
1304 'type' => 'api',
1305 ];
1306
1307 if ( $this->options->get( MainConfigNames::RCWatchCategoryMembership ) ) {
1308 $defaultPreferences['hidecategorization'] = [
1309 'type' => 'toggle',
1310 'label-message' => 'tog-hidecategorization',
1311 'section' => 'rc/changesrc',
1312 ];
1313 }
1314
1315 if ( $user->useRCPatrol() ) {
1316 $defaultPreferences['hidepatrolled'] = [
1317 'type' => 'toggle',
1318 'section' => 'rc/changesrc',
1319 'label-message' => 'tog-hidepatrolled',
1320 ];
1321 }
1322
1323 if ( $user->useNPPatrol() ) {
1324 $defaultPreferences['newpageshidepatrolled'] = [
1325 'type' => 'toggle',
1326 'section' => 'rc/changesrc',
1327 'label-message' => 'tog-newpageshidepatrolled',
1328 ];
1329 }
1330
1331 if ( $this->options->get( MainConfigNames::RCShowWatchingUsers ) ) {
1332 $defaultPreferences['shownumberswatching'] = [
1333 'type' => 'toggle',
1334 'section' => 'rc/advancedrc',
1335 'label-message' => 'tog-shownumberswatching',
1336 ];
1337 }
1338
1339 $defaultPreferences['rcenhancedfilters-disable'] = [
1340 'type' => 'toggle',
1341 'section' => 'rc/advancedrc',
1342 'label-message' => 'rcfilters-preference-label',
1343 'help-message' => 'rcfilters-preference-help',
1344 ];
1345 }
1346
1352 protected function watchlistPreferences(
1353 User $user, IContextSource $context, &$defaultPreferences
1354 ) {
1355 $watchlistdaysMax = ceil( $this->options->get( MainConfigNames::RCMaxAge ) / ( 3600 * 24 ) );
1356
1357 if ( $user->isAllowed( 'editmywatchlist' ) ) {
1358 $editWatchlistLinks = '';
1359 $editWatchlistModes = [
1360 'edit' => [ 'subpage' => false, 'flags' => [] ],
1361 'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
1362 'clear' => [ 'subpage' => 'clear', 'flags' => [ 'destructive' ] ],
1363 ];
1364 foreach ( $editWatchlistModes as $mode => $options ) {
1365 // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1366 $editWatchlistLinks .=
1367 new ButtonWidget( [
1368 'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
1369 'flags' => $options[ 'flags' ],
1370 'label' => new HtmlSnippet(
1371 $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
1372 ),
1373 ] );
1374 }
1375
1376 $defaultPreferences['editwatchlist'] = [
1377 'type' => 'info',
1378 'raw' => true,
1379 'default' => $editWatchlistLinks,
1380 'label-message' => 'prefs-editwatchlist-label',
1381 'section' => 'watchlist/editwatchlist',
1382 ];
1383 }
1384
1385 $defaultPreferences['watchlistdays'] = [
1386 'type' => 'float',
1387 'min' => 1 / 24,
1388 'max' => $watchlistdaysMax,
1389 'section' => 'watchlist/displaywatchlist',
1390 'help-message' => [ 'prefs-watchlist-days-max', Message::numParam( $watchlistdaysMax ) ],
1391 'label-message' => 'prefs-watchlist-days',
1392 ];
1393 $defaultPreferences['wllimit'] = [
1394 'type' => 'int',
1395 'min' => 1,
1396 'max' => 1000,
1397 'label-message' => 'prefs-watchlist-edits',
1398 'help-message' => 'prefs-watchlist-edits-max',
1399 'section' => 'watchlist/displaywatchlist',
1400 'filter' => IntvalFilter::class,
1401 ];
1402 $defaultPreferences['extendwatchlist'] = [
1403 'type' => 'toggle',
1404 'section' => 'watchlist/advancedwatchlist',
1405 'label-message' => 'tog-extendwatchlist',
1406 ];
1407 $defaultPreferences['watchlisthideminor'] = [
1408 'type' => 'toggle',
1409 'section' => 'watchlist/changeswatchlist',
1410 'label-message' => 'tog-watchlisthideminor',
1411 ];
1412 $defaultPreferences['watchlisthidebots'] = [
1413 'type' => 'toggle',
1414 'section' => 'watchlist/changeswatchlist',
1415 'label-message' => 'tog-watchlisthidebots',
1416 ];
1417 $defaultPreferences['watchlisthideown'] = [
1418 'type' => 'toggle',
1419 'section' => 'watchlist/changeswatchlist',
1420 'label-message' => 'tog-watchlisthideown',
1421 ];
1422 $defaultPreferences['watchlisthideanons'] = [
1423 'type' => 'toggle',
1424 'section' => 'watchlist/changeswatchlist',
1425 'label-message' => 'tog-watchlisthideanons',
1426 ];
1427 $defaultPreferences['watchlisthideliu'] = [
1428 'type' => 'toggle',
1429 'section' => 'watchlist/changeswatchlist',
1430 'label-message' => 'tog-watchlisthideliu',
1431 ];
1432
1434 $defaultPreferences['watchlistreloadautomatically'] = [
1435 'type' => 'toggle',
1436 'section' => 'watchlist/advancedwatchlist',
1437 'label-message' => 'tog-watchlistreloadautomatically',
1438 ];
1439 }
1440
1441 $defaultPreferences['watchlistunwatchlinks'] = [
1442 'type' => 'toggle',
1443 'section' => 'watchlist/advancedwatchlist',
1444 'label-message' => 'tog-watchlistunwatchlinks',
1445 ];
1446
1447 if ( $this->options->get( MainConfigNames::RCWatchCategoryMembership ) ) {
1448 $defaultPreferences['watchlisthidecategorization'] = [
1449 'type' => 'toggle',
1450 'section' => 'watchlist/changeswatchlist',
1451 'label-message' => 'tog-watchlisthidecategorization',
1452 ];
1453 }
1454
1455 if ( $user->useRCPatrol() ) {
1456 $defaultPreferences['watchlisthidepatrolled'] = [
1457 'type' => 'toggle',
1458 'section' => 'watchlist/changeswatchlist',
1459 'label-message' => 'tog-watchlisthidepatrolled',
1460 ];
1461 }
1462
1463 $watchTypes = [
1464 'edit' => 'watchdefault',
1465 'move' => 'watchmoves',
1466 ];
1467
1468 // Kinda hacky
1469 if ( $user->isAllowedAny( 'createpage', 'createtalk' ) ) {
1470 $watchTypes['read'] = 'watchcreations';
1471 }
1472
1473 // Move uncommon actions to end of list
1474 $watchTypes += [
1475 'rollback' => 'watchrollback',
1476 'upload' => 'watchuploads',
1477 'delete' => 'watchdeletion',
1478 ];
1479
1480 foreach ( $watchTypes as $action => $pref ) {
1481 if ( $user->isAllowed( $action ) ) {
1482 // Messages:
1483 // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1484 // tog-watchrollback
1485 $defaultPreferences[$pref] = [
1486 'type' => 'toggle',
1487 'section' => 'watchlist/pageswatchlist',
1488 'label-message' => "tog-$pref",
1489 ];
1490 }
1491 }
1492
1493 $defaultPreferences['watchlisttoken'] = [
1494 'type' => 'api',
1495 ];
1496
1497 $tokenButton = new ButtonWidget( [
1498 'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1499 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1500 ] ),
1501 'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1502 ] );
1503 $defaultPreferences['watchlisttoken-info'] = [
1504 'type' => 'info',
1505 'section' => 'watchlist/tokenwatchlist',
1506 'label-message' => 'prefs-watchlist-token',
1507 'help-message' => 'prefs-help-tokenmanagement',
1508 'raw' => true,
1509 'default' => (string)$tokenButton,
1510 ];
1511
1512 $defaultPreferences['wlenhancedfilters-disable'] = [
1513 'type' => 'toggle',
1514 'section' => 'watchlist/advancedwatchlist',
1515 'label-message' => 'rcfilters-watchlist-preference-label',
1516 'help-message' => 'rcfilters-watchlist-preference-help',
1517 ];
1518 }
1519
1524 protected function searchPreferences( $context, &$defaultPreferences ) {
1525 $defaultPreferences['search-special-page'] = [
1526 'type' => 'api',
1527 ];
1528
1529 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1530 $defaultPreferences['searchNs' . $n] = [
1531 'type' => 'api',
1532 ];
1533 }
1534
1535 if ( $this->options->get( MainConfigNames::SearchMatchRedirectPreference ) ) {
1536 $defaultPreferences['search-match-redirect'] = [
1537 'type' => 'toggle',
1538 'section' => 'searchoptions/searchmisc',
1539 'label-message' => 'search-match-redirect-label',
1540 'help-message' => 'search-match-redirect-help',
1541 ];
1542 } else {
1543 $defaultPreferences['search-match-redirect'] = [
1544 'type' => 'api',
1545 ];
1546 }
1547
1548 $defaultPreferences['searchlimit'] = [
1549 'type' => 'int',
1550 'min' => 1,
1551 'max' => 500,
1552 'section' => 'searchoptions/searchmisc',
1553 'label-message' => 'searchlimit-label',
1554 'help-message' => $context->msg( 'searchlimit-help', 500 ),
1555 'filter' => IntvalFilter::class,
1556 ];
1557
1558 // show a preference for thumbnails from namespaces other than NS_FILE,
1559 // only when there they're actually configured to be served
1560 $thumbNamespaces = $this->options->get( MainConfigNames::ThumbnailNamespaces );
1561 $thumbNamespacesFormatted = array_combine(
1562 $thumbNamespaces,
1563 array_map(
1564 static function ( $namespaceId ) use ( $context ) {
1565 return $namespaceId === NS_MAIN
1566 ? $context->msg( 'blanknamespace' )->escaped()
1567 : $context->getLanguage()->getFormattedNsText( $namespaceId );
1568 },
1569 $thumbNamespaces
1570 )
1571 );
1572 $defaultThumbNamespacesFormatted =
1573 array_intersect_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] ) ?? [];
1574 $extraThumbNamespacesFormatted =
1575 array_diff_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] );
1576 if ( $extraThumbNamespacesFormatted ) {
1577 $defaultPreferences['search-thumbnail-extra-namespaces'] = [
1578 'type' => 'toggle',
1579 'section' => 'searchoptions/searchmisc',
1580 'label-message' => 'search-thumbnail-extra-namespaces-label',
1581 'help-message' => $context->msg(
1582 'search-thumbnail-extra-namespaces-message',
1583 $context->getLanguage()->listToText( $extraThumbNamespacesFormatted ),
1584 count( $extraThumbNamespacesFormatted ),
1585 $context->getLanguage()->listToText( $defaultThumbNamespacesFormatted ),
1586 count( $defaultThumbNamespacesFormatted )
1587 ),
1588 ];
1589 }
1590 }
1591
1592 /*
1593 * Custom skin string comparison function that takes into account current and preferred skins.
1594 *
1595 * @param string $a
1596 * @param string $b
1597 * @param string $currentSkin
1598 * @param array $preferredSkins
1599 * @return int
1600 */
1601 private static function sortSkinNames( $a, $b, $currentSkin, $preferredSkins ) {
1602 // Display the current skin first in the list
1603 if ( strcasecmp( $a, $currentSkin ) === 0 ) {
1604 return -1;
1605 }
1606 if ( strcasecmp( $b, $currentSkin ) === 0 ) {
1607 return 1;
1608 }
1609 // Display preferred skins over other skins
1610 if ( count( $preferredSkins ) ) {
1611 $aPreferred = array_search( $a, $preferredSkins );
1612 $bPreferred = array_search( $b, $preferredSkins );
1613 // Cannot use ! operator because array_search returns the
1614 // index of the array item if found (i.e. 0) and false otherwise
1615 if ( $aPreferred !== false && $bPreferred === false ) {
1616 return -1;
1617 }
1618 if ( $aPreferred === false && $bPreferred !== false ) {
1619 return 1;
1620 }
1621 // When both skins are preferred, default to the ordering
1622 // specified by the preferred skins config array
1623 if ( $aPreferred !== false && $bPreferred !== false ) {
1624 return strcasecmp( $aPreferred, $bPreferred );
1625 }
1626 }
1627 // Use normal string comparison if both strings are not preferred
1628 return strcasecmp( $a, $b );
1629 }
1630
1639 private function getValidSkinNames( User $user, IContextSource $context ) {
1640 // Only show skins that aren't disabled
1641 $validSkinNames = $this->skinFactory->getAllowedSkins();
1642 $allInstalledSkins = $this->skinFactory->getInstalledSkins();
1643
1644 // Display the installed skin the user has specifically requested via useskin=….
1645 $useSkin = $context->getRequest()->getRawVal( 'useskin' );
1646 if ( isset( $allInstalledSkins[$useSkin] )
1647 && $context->msg( "skinname-$useSkin" )->exists()
1648 ) {
1649 $validSkinNames[$useSkin] = $useSkin;
1650 }
1651
1652 // Display the skin if the user has set it as a preference already before it was hidden.
1653 $currentUserSkin = $this->userOptionsManager->getOption( $user, 'skin' );
1654 if ( isset( $allInstalledSkins[$currentUserSkin] )
1655 && $context->msg( "skinname-$currentUserSkin" )->exists()
1656 ) {
1657 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1658 }
1659
1660 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1661 $msg = $context->msg( "skinname-{$skinkey}" );
1662 if ( $msg->exists() ) {
1663 $skinname = htmlspecialchars( $msg->text() );
1664 }
1665 }
1666
1667 $preferredSkins = $this->options->get( MainConfigNames::SkinsPreferred );
1668 // Sort by the internal name, so that the ordering is the same for each display language,
1669 // especially if some skin names are translated to use a different alphabet and some are not.
1670 uksort( $validSkinNames, function ( $a, $b ) use ( $currentUserSkin, $preferredSkins ) {
1671 return $this->sortSkinNames( $a, $b, $currentUserSkin, $preferredSkins );
1672 } );
1673
1674 return $validSkinNames;
1675 }
1676
1683 protected function generateSkinOptions( User $user, IContextSource $context, array $validSkinNames ) {
1684 $ret = [];
1685
1686 $mptitle = Title::newMainPage();
1687 $previewtext = $context->msg( 'skin-preview' )->escaped();
1688 $defaultSkin = $this->options->get( MainConfigNames::DefaultSkin );
1689 $allowUserCss = $this->options->get( MainConfigNames::AllowUserCss );
1690 $allowUserJs = $this->options->get( MainConfigNames::AllowUserJs );
1691 $safeMode = $this->userOptionsManager->getOption( $user, 'forcesafemode' );
1692 $foundDefault = false;
1693 foreach ( $validSkinNames as $skinkey => $sn ) {
1694 $linkTools = [];
1695
1696 // Mark the default skin
1697 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1698 $linkTools[] = $context->msg( 'default' )->escaped();
1699 $foundDefault = true;
1700 }
1701
1702 // Create talk page link if relevant message exists.
1703 $talkPageMsg = $context->msg( "$skinkey-prefs-talkpage" );
1704 if ( $talkPageMsg->exists() ) {
1705 $linkTools[] = $talkPageMsg->parse();
1706 }
1707
1708 // Create preview link
1709 $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1710 $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1711
1712 if ( !$safeMode ) {
1713 // Create links to user CSS/JS pages
1714 // @todo Refactor this and the similar code in skinPreferences().
1715 if ( $allowUserCss ) {
1716 $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1717 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1718 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1719 }
1720
1721 if ( $allowUserJs ) {
1722 $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1723 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1724 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1725 }
1726 }
1727
1728 $display = $sn . ' ' . $context->msg( 'parentheses' )
1729 ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1730 ->escaped();
1731 $ret[$display] = $skinkey;
1732 }
1733
1734 if ( !$foundDefault ) {
1735 // If the default skin is not available, things are going to break horribly because the
1736 // default value for skin selector will not be a valid value. Let's just not show it then.
1737 return [];
1738 }
1739
1740 return $ret;
1741 }
1742
1747 protected function getDateOptions( IContextSource $context ) {
1748 $lang = $context->getLanguage();
1749 $dateopts = $lang->getDatePreferences();
1750
1751 $ret = [];
1752
1753 if ( $dateopts ) {
1754 if ( !in_array( 'default', $dateopts ) ) {
1755 $dateopts[] = 'default'; // Make sure default is always valid T21237
1756 }
1757
1758 // FIXME KLUGE: site default might not be valid for user language
1759 global $wgDefaultUserOptions;
1760 if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1761 $wgDefaultUserOptions['date'] = 'default';
1762 }
1763
1764 $epoch = wfTimestampNow();
1765 foreach ( $dateopts as $key ) {
1766 if ( $key == 'default' ) {
1767 $formatted = $context->msg( 'datedefault' )->escaped();
1768 } else {
1769 $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1770 }
1771 $ret[$formatted] = $key;
1772 }
1773 }
1774 return $ret;
1775 }
1776
1781 protected function getImageSizes( MessageLocalizer $l10n ) {
1782 $ret = [];
1783 $pixels = $l10n->msg( 'unit-pixel' )->text();
1784
1785 foreach ( $this->options->get( MainConfigNames::ImageLimits ) as $index => $limits ) {
1786 // Note: A left-to-right marker (U+200E) is inserted, see T144386
1787 $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1788 $ret[$display] = $index;
1789 }
1790
1791 return $ret;
1792 }
1793
1798 protected function getThumbSizes( MessageLocalizer $l10n ) {
1799 $ret = [];
1800 $pixels = $l10n->msg( 'unit-pixel' )->text();
1801
1802 foreach ( $this->options->get( MainConfigNames::ThumbLimits ) as $index => $size ) {
1803 $display = $size . $pixels;
1804 $ret[$display] = $index;
1805 }
1806
1807 return $ret;
1808 }
1809
1816 protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1817 $sigValidation = $this->options->get( MainConfigNames::SignatureValidation );
1818 $maxSigChars = $this->options->get( MainConfigNames::MaxSigChars );
1819 if ( is_string( $signature ) && mb_strlen( $signature ) > $maxSigChars ) {
1820 return $form->msg( 'badsiglength' )->numParams( $maxSigChars )->escaped();
1821 }
1822
1823 if ( $signature === null || $signature === '' ) {
1824 // Make sure leaving the field empty is valid, since that's used as the default (T288151).
1825 // Code using this preference in Parser::getUserSig() handles this case specially.
1826 return true;
1827 }
1828
1829 // Remaining checks only apply to fancy signatures
1830 if ( !( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) ) {
1831 return true;
1832 }
1833
1834 // HERE BE DRAGONS:
1835 //
1836 // If this value is already saved as the user's signature, treat it as valid, even if it
1837 // would be invalid to save now, and even if $wgSignatureValidation is set to 'disallow'.
1838 //
1839 // It can become invalid when we introduce new validation, or when the value just transcludes
1840 // some page containing the real signature and that page is edited (which we can't validate),
1841 // or when someone's username is changed.
1842 //
1843 // Otherwise it would be completely removed when the user opens their preferences page, which
1844 // would be very unfriendly.
1845 $user = $form->getUser();
1846 if (
1847 $signature === $this->userOptionsManager->getOption( $user, 'nickname' ) &&
1848 (bool)$alldata['fancysig'] === $this->userOptionsManager->getBoolOption( $user, 'fancysig' )
1849 ) {
1850 return true;
1851 }
1852
1853 if ( $sigValidation === 'new' || $sigValidation === 'disallow' ) {
1854 // Validate everything
1855 $parserOpts = ParserOptions::newFromContext( $form->getContext() );
1856 $validator = $this->signatureValidatorFactory
1857 ->newSignatureValidator( $user, $form->getContext(), $parserOpts );
1858 $errors = $validator->validateSignature( $signature );
1859 if ( $errors ) {
1860 return $errors;
1861 }
1862 }
1863
1864 // Quick check for mismatched HTML tags in the input.
1865 // Note that this is easily fooled by wikitext templates or bold/italic markup.
1866 // We're only keeping this until Parsoid is integrated and guaranteed to be available.
1867 if ( $this->parserFactory->getInstance()->validateSig( $signature ) === false ) {
1868 return $form->msg( 'badsig' )->escaped();
1869 }
1870
1871 return true;
1872 }
1873
1880 protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
1881 if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1882 $signature = $this->parserFactory->getInstance()->cleanSig( $signature );
1883 } else {
1884 // When no fancy sig used, make sure ~{3,5} get removed.
1885 $signature = Parser::cleanSigInSig( $signature );
1886 }
1887
1888 return $signature;
1889 }
1890
1898 public function getForm(
1899 User $user,
1900 IContextSource $context,
1901 $formClass = PreferencesFormOOUI::class,
1902 array $remove = []
1903 ) {
1904 // We use ButtonWidgets in some of the getPreferences() functions
1905 $context->getOutput()->enableOOUI();
1906
1907 // Note that the $user parameter of getFormDescriptor() is deprecated.
1908 $formDescriptor = $this->getFormDescriptor( $user, $context );
1909 if ( count( $remove ) ) {
1910 $removeKeys = array_fill_keys( $remove, true );
1911 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1912 }
1913
1914 // Remove type=api preferences. They are not intended for rendering in the form.
1915 foreach ( $formDescriptor as $name => $info ) {
1916 if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1917 unset( $formDescriptor[$name] );
1918 }
1919 }
1920
1924 $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1925
1926 // This allows users to opt-in to hidden skins. While this should be discouraged and is not
1927 // discoverable, this allows users to still use hidden skins while preventing new users from
1928 // adopting unsupported skins. If no useskin=… parameter was provided, it will not show up
1929 // in the resulting URL.
1930 $htmlForm->setAction( $context->getTitle()->getLocalURL( [
1931 'useskin' => $context->getRequest()->getRawVal( 'useskin' )
1932 ] ) );
1933
1934 $htmlForm->setModifiedUser( $user );
1935 $htmlForm->setOptionsEditable( $user->isAllowed( 'editmyoptions' ) );
1936 $htmlForm->setPrivateInfoEditable( $user->isAllowed( 'editmyprivateinfo' ) );
1937 $htmlForm->setId( 'mw-prefs-form' );
1938 $htmlForm->setAutocomplete( 'off' );
1939 $htmlForm->setSubmitTextMsg( 'saveprefs' );
1940 // Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1941 $htmlForm->setSubmitTooltip( 'preferences-save' );
1942 $htmlForm->setSubmitID( 'prefcontrol' );
1943 $htmlForm->setSubmitCallback(
1944 function ( array $formData, PreferencesFormOOUI $form ) use ( $formDescriptor ) {
1945 return $this->submitForm( $formData, $form, $formDescriptor );
1946 }
1947 );
1948
1949 return $htmlForm;
1950 }
1951
1960 protected function saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor ) {
1961 $user = $form->getModifiedUser();
1962 $hiddenPrefs = $this->options->get( MainConfigNames::HiddenPrefs );
1963 $result = true;
1964
1965 if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
1966 return Status::newFatal( 'mypreferencesprotected' );
1967 }
1968
1969 // Filter input
1970 $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
1971
1972 // Fortunately, the realname field is MUCH simpler
1973 // (not really "private", but still shouldn't be edited without permission)
1974
1975 if ( !in_array( 'realname', $hiddenPrefs )
1976 && $user->isAllowed( 'editmyprivateinfo' )
1977 && array_key_exists( 'realname', $formData )
1978 ) {
1979 $realName = $formData['realname'];
1980 $user->setRealName( $realName );
1981 }
1982
1983 if ( $user->isAllowed( 'editmyoptions' ) ) {
1984 $oldUserOptions = $this->userOptionsManager->getOptions( $user );
1985
1986 foreach ( $this->getSaveBlacklist() as $b ) {
1987 unset( $formData[$b] );
1988 }
1989
1990 // If users have saved a value for a preference which has subsequently been disabled
1991 // via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1992 // is subsequently re-enabled
1993 foreach ( $hiddenPrefs as $pref ) {
1994 // If the user has not set a non-default value here, the default will be returned
1995 // and subsequently discarded
1996 $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref, null, true );
1997 }
1998
1999 // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
2000 if (
2001 isset( $formData['rclimit'] ) &&
2002 intval( $formData[ 'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user, 'rclimit' )
2003 ) {
2004 $formData['rcfilters-limit'] = $formData['rclimit'];
2005 }
2006
2007 // Keep old preferences from interfering due to back-compat code, etc.
2008 $this->userOptionsManager->resetOptions( $user, $form->getContext(), 'unused' );
2009
2010 foreach ( $formData as $key => $value ) {
2011 $this->userOptionsManager->setOption( $user, $key, $value );
2012 }
2013
2014 $this->hookRunner->onPreferencesFormPreSave(
2015 $formData, $form, $user, $result, $oldUserOptions );
2016 }
2017
2018 $user->saveSettings();
2019
2020 return $result;
2021 }
2022
2031 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
2032 foreach ( $formDescriptor as $preference => $desc ) {
2033 if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
2034 continue;
2035 }
2036 $filterDesc = $desc['filter'];
2037 if ( $filterDesc instanceof Filter ) {
2038 $filter = $filterDesc;
2039 } elseif ( class_exists( $filterDesc ) ) {
2040 $filter = new $filterDesc();
2041 } elseif ( is_callable( $filterDesc ) ) {
2042 $filter = $filterDesc();
2043 } else {
2044 throw new UnexpectedValueException(
2045 "Unrecognized filter type for preference '$preference'"
2046 );
2047 }
2048 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
2049 }
2050 }
2051
2060 protected function submitForm(
2061 array $formData,
2062 PreferencesFormOOUI $form,
2063 array $formDescriptor
2064 ) {
2065 $res = $this->saveFormData( $formData, $form, $formDescriptor );
2066
2067 if ( $res === true ) {
2068 $context = $form->getContext();
2069 $urlOptions = [];
2070
2071 $urlOptions += $form->getExtraSuccessRedirectParameters();
2072
2073 $url = $form->getTitle()->getFullURL( $urlOptions );
2074
2075 // Set session data for the success message
2076 $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
2077
2078 $context->getOutput()->redirect( $url );
2079 }
2080
2081 return ( $res === true ? Status::newGood() : $res );
2082 }
2083}
const NS_USER
Definition Defines.php:66
const NS_FILE
Definition Defines.php:70
const NS_MAIN
Definition Defines.php:64
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
array $params
The job parameters.
Methods for dealing with language codes.
Base class for multi-variant language conversion.
Base class for language-specific code.
Definition Language.php:63
This serves as the entry point to the authentication system.
This is a value object for authentication requests with a username and password.
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
The parent class to generate form fields.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:206
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
An interface for creating language converters.
A service that provides utilities to do with language names and codes.
Class that generates HTML for internal links.
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 EnableEditRecovery
Name constant for the EnableEditRecovery 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()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
This is one of the Core classes and should be read at least once by any new developers.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:155
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
This is the default implementation of PreferencesFactory.
validateSignature( $signature, $alldata, HTMLForm $form)
rcPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
watchlistPreferences(User $user, IContextSource $context, &$defaultPreferences)
profilePreferences(User $user, IContextSource $context, &$defaultPreferences)
renderingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
getForm(User $user, IContextSource $context, $formClass=PreferencesFormOOUI::class, array $remove=[])
skinPreferences(User $user, IContextSource $context, &$defaultPreferences)
__construct(ServiceOptions $options, Language $contLang, AuthManager $authManager, LinkRenderer $linkRenderer, NamespaceInfo $nsInfo, PermissionManager $permissionManager, ILanguageConverter $languageConverter, LanguageNameUtils $languageNameUtils, HookContainer $hookContainer, UserOptionsLookup $userOptionsLookup, LanguageConverterFactory $languageConverterFactory=null, ParserFactory $parserFactory=null, SkinFactory $skinFactory=null, UserGroupManager $userGroupManager=null, SignatureValidatorFactory $signatureValidatorFactory=null)
generateSkinOptions(User $user, IContextSource $context, array $validSkinNames)
static simplifyFormDescriptor(array $descriptor)
Simplify form descriptor for validation or something similar.
editingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
getOptionFromUser( $name, $info, array $userOptions)
Pull option from a user account.
datetimePreferences(User $user, IContextSource $context, &$defaultPreferences)
cleanSignature( $signature, $alldata, HTMLForm $form)
getSaveBlacklist()
Get the names of preferences that should never be saved (such as 'realname' and 'emailaddress')....
applyFilters(array &$preferences, array $formDescriptor, $verb)
Applies filters to preferences either before or after form usage.
static getPreferenceForField( $name, HTMLFormField $field, array $userOptions)
Get preference values for the 'default' param of html form descriptor, compatible with nested fields.
filesPreferences(IContextSource $context, &$defaultPreferences)
submitForm(array $formData, PreferencesFormOOUI $form, array $formDescriptor)
Save the form data and reload the page.
saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor)
Handle the form submission if everything validated properly.
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,...
A special page that lists last changes made to the wiki, limited to user-defined list of titles.
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.
Definition Status.php:54
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:78
Provides access to user options.
A service class to control user options.
Represents a "user group membership" – a specific instance of a user belonging to a group.
Utility class to parse the TimeCorrection string value.
internal since 1.36
Definition User.php:93
getRegistration()
Get the timestamp of account creation.
Definition User.php:3212
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:2299
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.
Definition User.php:2283
getEditCount()
Get the user's edit count.
Definition User.php:2182
getRealName()
Get the user's real name.
Definition User.php:2009
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:1686
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:1925
isAllowed(string $permission, PermissionStatus $status=null)
Checks whether this authority has the given permission in general.
Definition User.php:2291
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:2309
getEmail()
Get the user's e-mail address.
Definition User.php:1912
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1594
Set options of the Parser.
Form to edit user preferences.
getExtraSuccessRedirectParameters()
Get extra parameters for the query string when redirecting after successful save.
Factory class to create Skin objects.
Module of static functions for generating XML.
Definition Xml.php:33
$wgDefaultUserOptions
Config variable stub for the DefaultUserOptions setting, for use by phpdoc and IDEs.
The shared interface for all language converters.
Interface for objects which can provide a MediaWiki context on request.
Base interface for user preference filters that work as a middleware between storage and interface.
Definition Filter.php:27
A PreferencesFactory is a MediaWiki service that provides the definitions of preferences for a given ...
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
element(SerializerNode $parent, SerializerNode $node, $contents)