MediaWiki REL1_40
DefaultPreferencesFactory.php
Go to the documentation of this file.
1<?php
22
23use Config;
24use HTMLForm;
28use Language;
29use LanguageCode;
48use Message;
50use MWException;
52use OutputPage;
53use Parser;
56use Psr\Log\LoggerAwareTrait;
57use Psr\Log\NullLogger;
58use SkinFactory;
59use SpecialPage;
60use Status;
61use UnexpectedValueException;
62use User;
64use Xml;
65
70 use LoggerAwareTrait;
71
73 protected $options;
74
76 protected $contLang;
77
80
82 protected $authManager;
83
85 protected $linkRenderer;
86
88 protected $nsInfo;
89
92
94 private $languageConverter;
95
97 private $hookRunner;
98
100 private $userOptionsManager;
101
103 private $languageConverterFactory;
104
106 private $parser;
107
109 private $skinFactory;
110
112 private $userGroupManager;
113
115 private $signatureValidatorFactory;
116
118 private $config;
119
123 public const CONSTRUCTOR_OPTIONS = [
152 ];
153
172 public function __construct(
179 ILanguageConverter $languageConverter,
181 HookContainer $hookContainer,
182 UserOptionsLookup $userOptionsLookup,
183 LanguageConverterFactory $languageConverterFactory = null,
184 Parser $parser = null,
185 SkinFactory $skinFactory = null,
186 UserGroupManager $userGroupManager = null,
187 SignatureValidatorFactory $signatureValidatorFactory = null,
188 Config $config = null
189 ) {
190 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
191
192 $this->options = $options;
193 $this->contLang = $contLang;
194 $this->authManager = $authManager;
195 $this->linkRenderer = $linkRenderer;
196 $this->nsInfo = $nsInfo;
197
198 // We don't use the PermissionManager anymore, but we need to be careful
199 // removing the parameter since this class is extended by GlobalPreferencesFactory
200 // in the GlobalPreferences extension, and that class uses it
201 $this->permissionManager = $permissionManager;
202
203 $this->logger = new NullLogger();
204 $this->languageConverter = $languageConverter;
205 $this->languageNameUtils = $languageNameUtils;
206 $this->hookRunner = new HookRunner( $hookContainer );
207
208 // Don't break GlobalPreferences, fall back to global state if missing services
209 // or if passed a UserOptionsLookup that isn't UserOptionsManager
210 $services = static function () {
211 // BC hack. Use a closure so this can be unit-tested.
213 };
214 $this->userOptionsManager = ( $userOptionsLookup instanceof UserOptionsManager )
215 ? $userOptionsLookup
216 : $services()->getUserOptionsManager();
217 $this->languageConverterFactory = $languageConverterFactory ?? $services()->getLanguageConverterFactory();
218
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();
225 }
226
230 public function getSaveBlacklist() {
231 return [
232 'realname',
233 'emailaddress',
234 ];
235 }
236
243 public function getFormDescriptor( User $user, IContextSource $context ) {
244 $preferences = [];
245
246 OutputPage::setupOOUI(
247 strtolower( $context->getSkin()->getSkinName() ),
248 $context->getLanguage()->getDir()
249 );
250
251 $this->profilePreferences( $user, $context, $preferences );
252 $this->skinPreferences( $user, $context, $preferences );
253 $this->datetimePreferences( $user, $context, $preferences );
254 $this->filesPreferences( $context, $preferences );
255 $this->renderingPreferences( $user, $context, $preferences );
256 $this->editingPreferences( $user, $context, $preferences );
257 $this->rcPreferences( $user, $context, $preferences );
258 $this->watchlistPreferences( $user, $context, $preferences );
259 $this->searchPreferences( $context, $preferences );
260
261 $this->hookRunner->onGetPreferences( $user, $preferences );
262
263 $this->loadPreferenceValues( $user, $context, $preferences );
264 $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
265 return $preferences;
266 }
267
274 public static function simplifyFormDescriptor( array $descriptor ) {
275 foreach ( $descriptor as $name => &$params ) {
276 // Info fields are useless and can use complicated closure to provide
277 // text, skip all of them.
278 if ( ( isset( $params['type'] ) && $params['type'] === 'info' ) ||
279 ( isset( $params['class'] ) && $params['class'] === \HTMLInfoField::class )
280 ) {
281 unset( $descriptor[$name] );
282 continue;
283 }
284 // Message parsing is the heaviest load when constructing the field,
285 // but we just want to validate data.
286 foreach ( $params as $key => $value ) {
287 switch ( $key ) {
288 // Special case, should be kept.
289 case 'options-message':
290 break;
291 // Special case, should be transferred.
292 case 'options-messages':
293 unset( $params[$key] );
294 $params['options'] = $value;
295 break;
296 default:
297 if ( preg_match( '/-messages?$/', $key ) ) {
298 // Unwanted.
299 unset( $params[$key] );
300 }
301 }
302 }
303 }
304 return $descriptor;
305 }
306
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();
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 = $this->getOptionFromUser( $name, $info, $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 MWException(
355 "Default '$globalDefault' is invalid for preference $name of user " . $user->getName()
356 );
357 }
358 }
359
360 return $defaultPreferences;
361 }
362
371 protected function getOptionFromUser( $name, $info, array $userOptions ) {
372 $val = $userOptions[$name] ?? null;
373
374 // Handling for multiselect preferences
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;
379 $val = [];
380
381 foreach ( $options as $value ) {
382 if ( $userOptions["$prefix$value"] ?? false ) {
383 $val[] = $value;
384 }
385 }
386 }
387
388 // Handling for checkmatrix preferences
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;
394 $val = [];
395
396 foreach ( $columns as $column ) {
397 foreach ( $rows as $row ) {
398 if ( $userOptions["$prefix$column-$row"] ?? false ) {
399 $val[] = "$column-$row";
400 }
401 }
402 }
403 }
404
405 return $val;
406 }
407
415 protected function profilePreferences(
416 User $user, IContextSource $context, &$defaultPreferences
417 ) {
418 // retrieving user name for GENDER and misc.
419 $userName = $user->getName();
420
421 // Information panel
422 $defaultPreferences['username'] = [
423 'type' => 'info',
424 'label-message' => [ 'username', $userName ],
425 'default' => $userName,
426 'section' => 'personal/info',
427 ];
428
429 $lang = $context->getLanguage();
430
431 // Get groups to which the user belongs, Skip the default * group, seems useless here
432 $userEffectiveGroups = array_diff(
433 $this->userGroupManager->getUserEffectiveGroups( $user ),
434 [ '*' ]
435 );
436 $defaultPreferences['usergroups'] = [
437 'type' => 'info',
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;
445
446 $userG = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html' );
447 $userM = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html',
448 $userName );
449
450 // Store expiring groups separately, so we can place them before non-expiring
451 // groups in the list. This is to avoid the ambiguity of something like
452 // "administrator, bureaucrat (until X date)" -- users might wonder whether the
453 // expiry date applies to both groups, or just the last one
454 if ( $groupStringOrObject instanceof UserGroupMembership &&
455 $groupStringOrObject->getExpiry()
456 ) {
457 $userTempGroups[] = $userG;
458 $userTempMembers[] = $userM;
459 } else {
460 $userGroups[] = $userG;
461 $userMembers[] = $userM;
462 }
463 }
464 sort( $userGroups );
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 ) )
472 ->escaped();
473 },
474 'raw' => true,
475 'section' => 'personal/info',
476 ];
477
478 $contribTitle = SpecialPage::getTitleFor( "Contributions", $userName );
479 $formattedEditCount = $lang->formatNum( $user->getEditCount() );
480 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
481
482 $defaultPreferences['editcount'] = [
483 'type' => 'info',
484 'raw' => true,
485 'label-message' => 'prefs-edits',
486 'default' => $editCount,
487 'section' => 'personal/info',
488 ];
489
490 if ( $user->getRegistration() ) {
491 $displayUser = $context->getUser();
492 $userRegistration = $user->getRegistration();
493 $defaultPreferences['registrationdate'] = [
494 'type' => 'info',
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 )
501 )->text(),
502 'section' => 'personal/info',
503 ];
504 }
505
506 $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
507 $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
508
509 // Actually changeable stuff
510 $defaultPreferences['realname'] = [
511 // (not really "private", but still shouldn't be edited without permission)
512 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'realname' )
513 ? 'text' : 'info',
514 'default' => $user->getRealName(),
515 'section' => 'personal/info',
516 'label-message' => 'yourrealname',
517 'help-message' => 'prefs-help-realname',
518 ];
519
520 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
521 new PasswordAuthenticationRequest(), false )->isGood()
522 ) {
523 $defaultPreferences['password'] = [
524 'type' => 'info',
525 'raw' => true,
526 'default' => (string)new \OOUI\ButtonWidget( [
527 'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
528 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
529 ] ),
530 'label' => $context->msg( 'prefs-resetpass' )->text(),
531 ] ),
532 'label-message' => 'yourpassword',
533 // email password reset feature only works for users that have an email set up
534 'help' => $this->options->get( MainConfigNames::AllowRequiringEmailForResets ) &&
535 $user->getEmail()
536 ? $context->msg( 'prefs-help-yourpassword',
537 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
538 : '',
539 'section' => 'personal/info',
540 ];
541 }
542 // Only show prefershttps if secure login is turned on
543 if ( !$this->options->get( MainConfigNames::ForceHTTPS )
544 && $this->options->get( MainConfigNames::SecureLogin )
545 ) {
546 $defaultPreferences['prefershttps'] = [
547 'type' => 'toggle',
548 'label-message' => 'tog-prefershttps',
549 'help-message' => 'prefs-help-prefershttps',
550 'section' => 'personal/info'
551 ];
552 }
553
554 $defaultPreferences['downloaduserdata'] = [
555 'type' => 'info',
556 'raw' => true,
557 'label-message' => 'prefs-user-downloaddata-label',
558 'default' => Html::element(
559 'a',
560 [
561 'href' => $this->options->get( MainConfigNames::ScriptPath ) .
562 '/api.php?action=query&meta=userinfo&uiprop=*',
563 ],
564 $context->msg( 'prefs-user-downloaddata-info' )->text()
565 ),
566 'help-message' => [ 'prefs-user-downloaddata-help-message', urlencode( $user->getTitleKey() ) ],
567 'section' => 'personal/info',
568 ];
569
570 $defaultPreferences['restoreprefs'] = [
571 'type' => 'info',
572 'raw' => true,
573 'label-message' => 'prefs-user-restoreprefs-label',
574 'default' => Html::element(
575 'a',
576 [
577 'href' => SpecialPage::getTitleFor( 'Preferences' )
578 ->getSubpage( 'reset' )->getLocalURL()
579 ],
580 $context->msg( 'prefs-user-restoreprefs-info' )->text()
581 ),
582 'section' => 'personal/info',
583 ];
584
585 $languages = $this->languageNameUtils->getLanguageNames(
586 LanguageNameUtils::AUTONYMS,
587 LanguageNameUtils::SUPPORTED
588 );
589 $languageCode = $this->options->get( MainConfigNames::LanguageCode );
590 if ( !array_key_exists( $languageCode, $languages ) ) {
591 $languages[$languageCode] = $languageCode;
592 // Sort the array again
593 ksort( $languages );
594 }
595
596 $options = [];
597 foreach ( $languages as $code => $name ) {
598 $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
599 $options[$display] = $code;
600 }
601 $defaultPreferences['language'] = [
602 'type' => 'select',
603 'section' => 'personal/i18n',
604 'options' => $options,
605 'label-message' => 'yourlanguage',
606 ];
607
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() )
612 ->escaped()
613 : ''
614 );
615
616 $defaultPreferences['gender'] = [
617 'type' => 'radio',
618 'section' => 'personal/i18n',
619 'options' => [
620 $neutralGenderMessage => 'unknown',
621 $context->msg( 'gender-female' )->escaped() => 'female',
622 $context->msg( 'gender-male' )->escaped() => 'male',
623 ],
624 'label-message' => 'yourgender',
625 'help-message' => 'prefs-help-gender',
626 ];
627
628 // see if there are multiple language variants to choose from
629 if ( !$this->languageConverterFactory->isConversionDisabled() ) {
630
631 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
632 if ( $langCode == $this->contLang->getCode() ) {
633 if ( !$this->languageConverter->hasVariants() ) {
634 continue;
635 }
636
637 $variants = $this->languageConverter->getVariants();
638 $variantArray = [];
639 foreach ( $variants as $v ) {
640 $v = str_replace( '_', '-', strtolower( $v ) );
641 $variantArray[$v] = $lang->getVariantname( $v, false );
642 }
643
644 $options = [];
645 foreach ( $variantArray as $code => $name ) {
646 $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
647 $options[$display] = $code;
648 }
649
650 $defaultPreferences['variant'] = [
651 'label-message' => 'yourvariant',
652 'type' => 'select',
653 'options' => $options,
654 'section' => 'personal/i18n',
655 'help-message' => 'prefs-help-variant',
656 ];
657 } else {
658 $defaultPreferences["variant-$langCode"] = [
659 'type' => 'api',
660 ];
661 }
662 }
663 }
664
665 // show a preview of the old signature first
666 $oldsigWikiText = $this->parser->preSaveTransform(
667 '~~~',
668 $context->getTitle(),
669 $user,
670 ParserOptions::newFromContext( $context )
671 );
672 $oldsigHTML = Parser::stripOuterParagraph(
673 $context->getOutput()->parseAsContent( $oldsigWikiText )
674 );
675 $signatureFieldConfig = [];
676 // Validate existing signature and show a message about it
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 ) {
685 $sigValidation = $this->options->get( MainConfigNames::SignatureValidation );
686 $oldsigHTML .= '<p><strong>' .
687 // Messages used here:
688 // * prefs-signature-invalid-warning
689 // * prefs-signature-invalid-new
690 // * prefs-signature-invalid-disallow
691 $context->msg( "prefs-signature-invalid-$sigValidation" )->parse() .
692 '</strong></p>';
693
694 // On initial page load, show the warnings as well
695 // (when posting, you get normal validation errors instead)
696 foreach ( $signatureErrors as &$sigError ) {
697 $sigError = new \OOUI\HtmlSnippet( $sigError );
698 }
699 if ( !$context->getRequest()->wasPosted() ) {
700 $signatureFieldConfig = [
701 'warnings' => $sigValidation !== 'disallow' ? $signatureErrors : null,
702 'errors' => $sigValidation === 'disallow' ? $signatureErrors : null,
703 ];
704 }
705 }
706 }
707 $defaultPreferences['oldsig'] = [
708 'type' => 'info',
709 // Normally HTMLFormFields do not display warnings, so we need to use 'rawrow'
710 // and provide the entire OOUI\FieldLayout here
711 'rawrow' => true,
712 'default' => new \OOUI\FieldLayout(
713 new \OOUI\LabelWidget( [
714 'label' => new \OOUI\HtmlSnippet( $oldsigHTML ),
715 ] ),
716 [
717 'align' => 'top',
718 'label' => new \OOUI\HtmlSnippet( $context->msg( 'tog-oldsig' )->parse() )
719 ] + $signatureFieldConfig
720 ),
721 'section' => 'personal/signature',
722 ];
723 $defaultPreferences['nickname'] = [
724 'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
725 'maxlength' => $this->options->get( MainConfigNames::MaxSigChars ),
726 'label-message' => 'yournick',
727 'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
728 return $this->validateSignature( $signature, $alldata, $form );
729 },
730 'section' => 'personal/signature',
731 'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
732 return $this->cleanSignature( $signature, $alldata, $form );
733 },
734 ];
735 $defaultPreferences['fancysig'] = [
736 'type' => 'toggle',
737 'label-message' => 'tog-fancysig',
738 // show general help about signature at the bottom of the section
739 'help-message' => 'prefs-help-signature',
740 'section' => 'personal/signature'
741 ];
742
743 // Email preferences
744 if ( $this->options->get( MainConfigNames::EnableEmail ) ) {
745 if ( $canViewPrivateInfo ) {
746 $helpMessages = [];
747 $helpMessages[] = $this->options->get( MainConfigNames::EmailConfirmToEdit )
748 ? 'prefs-help-email-required'
749 : 'prefs-help-email';
750
751 if ( $this->options->get( MainConfigNames::EnableUserEmail ) ) {
752 // additional messages when users can send email to each other
753 $helpMessages[] = 'prefs-help-email-others';
754 }
755
756 $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
757 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
758 $button = new \OOUI\ButtonWidget( [
759 'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
760 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
761 ] ),
762 'label' =>
763 $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
764 ] );
765
766 $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
767 }
768
769 $defaultPreferences['emailaddress'] = [
770 'type' => 'info',
771 'raw' => true,
772 'default' => $emailAddress,
773 'label-message' => 'youremail',
774 'section' => 'personal/email',
775 'help-messages' => $helpMessages,
776 // 'cssclass' chosen below
777 ];
778 }
779
780 $disableEmailPrefs = false;
781
782 if ( $this->options->get( MainConfigNames::AllowRequiringEmailForResets ) ) {
783 $defaultPreferences['requireemail'] = [
784 'type' => 'toggle',
785 'label-message' => 'tog-requireemail',
786 'help-message' => 'prefs-help-requireemail',
787 'section' => 'personal/email',
788 'disabled' => $user->getEmail() ? false : true,
789 ];
790 }
791
792 if ( $this->options->get( MainConfigNames::EmailAuthentication ) ) {
793 if ( $user->getEmail() ) {
794 if ( $user->getEmailAuthenticationTimestamp() ) {
795 // date and time are separate parameters to facilitate localisation.
796 // $time is kept for backward compat reasons.
797 // 'emailauthenticated' is also used in SpecialConfirmemail.php
798 $displayUser = $context->getUser();
799 $emailTimestamp = $user->getEmailAuthenticationTimestamp();
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';
806 } else {
807 $disableEmailPrefs = true;
808 $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
809 new \OOUI\ButtonWidget( [
810 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
811 'label' => $context->msg( 'emailconfirmlink' )->text(),
812 ] );
813 $emailauthenticationclass = "mw-email-not-authenticated";
814 }
815 } else {
816 $disableEmailPrefs = true;
817 $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
818 $emailauthenticationclass = 'mw-email-none';
819 }
820
821 if ( $canViewPrivateInfo ) {
822 $defaultPreferences['emailauthentication'] = [
823 'type' => 'info',
824 'raw' => true,
825 'section' => 'personal/email',
826 'label-message' => 'prefs-emailconfirm-label',
827 'default' => $emailauthenticated,
828 // Apply the same CSS class used on the input to the message:
829 'cssclass' => $emailauthenticationclass,
830 ];
831 }
832 }
833
834 if ( $this->options->get( MainConfigNames::EnableUserEmail ) &&
835 $user->isAllowed( 'sendemail' )
836 ) {
837 $defaultPreferences['disablemail'] = [
838 'id' => 'wpAllowEmail',
839 'type' => 'toggle',
840 'invert' => true,
841 'section' => 'personal/email',
842 'label-message' => 'allowemail',
843 'disabled' => $disableEmailPrefs,
844 ];
845
846 $defaultPreferences['email-allow-new-users'] = [
847 'id' => 'wpAllowEmailFromNewUsers',
848 'type' => 'toggle',
849 'section' => 'personal/email',
850 'label-message' => 'email-allow-new-users-label',
851 'disabled' => $disableEmailPrefs,
852 'disable-if' => [ '!==', 'disablemail', '1' ],
853 ];
854
855 $defaultPreferences['ccmeonemails'] = [
856 'type' => 'toggle',
857 'section' => 'personal/email',
858 'label-message' => 'tog-ccmeonemails',
859 'disabled' => $disableEmailPrefs,
860 ];
861
862 if ( $this->options->get( MainConfigNames::EnableUserEmailMuteList ) ) {
863 $defaultPreferences['email-blacklist'] = [
864 'type' => 'usersmultiselect',
865 'label-message' => 'email-mutelist-label',
866 'section' => 'personal/email',
867 'disabled' => $disableEmailPrefs,
868 'filter' => MultiUsernameFilter::class,
869 ];
870 }
871 }
872
873 if ( $this->options->get( MainConfigNames::EnotifWatchlist ) ) {
874 $defaultPreferences['enotifwatchlistpages'] = [
875 'type' => 'toggle',
876 'section' => 'personal/email',
877 'label-message' => 'tog-enotifwatchlistpages',
878 'disabled' => $disableEmailPrefs,
879 ];
880 }
881 if ( $this->options->get( MainConfigNames::EnotifUserTalk ) ) {
882 $defaultPreferences['enotifusertalkpages'] = [
883 'type' => 'toggle',
884 'section' => 'personal/email',
885 'label-message' => 'tog-enotifusertalkpages',
886 'disabled' => $disableEmailPrefs,
887 ];
888 }
889 if ( $this->options->get( MainConfigNames::EnotifUserTalk ) ||
890 $this->options->get( MainConfigNames::EnotifWatchlist ) ) {
891 if ( $this->options->get( MainConfigNames::EnotifMinorEdits ) ) {
892 $defaultPreferences['enotifminoredits'] = [
893 'type' => 'toggle',
894 'section' => 'personal/email',
895 'label-message' => 'tog-enotifminoredits',
896 'disabled' => $disableEmailPrefs,
897 ];
898 }
899
900 if ( $this->options->get( MainConfigNames::EnotifRevealEditorAddress ) ) {
901 $defaultPreferences['enotifrevealaddr'] = [
902 'type' => 'toggle',
903 'section' => 'personal/email',
904 'label-message' => 'tog-enotifrevealaddr',
905 'disabled' => $disableEmailPrefs,
906 ];
907 }
908 }
909 }
910 }
911
918 protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
919 // Skin selector, if there is at least one valid skin
920 $skinOptions = $this->generateSkinOptions( $user, $context );
921 if ( $skinOptions ) {
922 $defaultPreferences['skin'] = [
923 // @phan-suppress-next-line SecurityCheck-XSS False positive, key is escaped
924 'type' => 'radio',
925 'options' => $skinOptions,
926 'section' => 'rendering/skin',
927 ];
928 $defaultPreferences['skin-responsive'] = [
929 'type' => 'check',
930 'label-message' => 'prefs-skin-responsive',
931 'section' => 'rendering/skin/skin-prefs',
932 'help-message' => 'prefs-help-skin-responsive',
933 ];
934 }
935
936 $allowUserCss = $this->options->get( MainConfigNames::AllowUserCss );
937 $allowUserJs = $this->options->get( MainConfigNames::AllowUserJs );
938 // Create links to user CSS/JS pages for all skins.
939 // This code is basically copied from generateSkinOptions().
940 // @todo Refactor this and the similar code in generateSkinOptions().
941 if ( $allowUserCss || $allowUserJs ) {
942 $linkTools = [];
943 $userName = $user->getName();
944
945 if ( $allowUserCss ) {
946 $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
947 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
948 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
949 }
950
951 if ( $allowUserJs ) {
952 $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
953 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
954 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
955 }
956
957 $defaultPreferences['commoncssjs'] = [
958 'type' => 'info',
959 'raw' => true,
960 'default' => $context->getLanguage()->pipeList( $linkTools ),
961 'label-message' => 'prefs-common-config',
962 'section' => 'rendering/skin',
963 ];
964 }
965 }
966
971 protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
972 $defaultPreferences['imagesize'] = [
973 'type' => 'select',
974 'options' => $this->getImageSizes( $context ),
975 'label-message' => 'imagemaxsize',
976 'section' => 'rendering/files',
977 ];
978 $defaultPreferences['thumbsize'] = [
979 'type' => 'select',
980 'options' => $this->getThumbSizes( $context ),
981 'label-message' => 'thumbsize',
982 'section' => 'rendering/files',
983 ];
984 }
985
992 protected function datetimePreferences(
993 User $user, IContextSource $context, &$defaultPreferences
994 ) {
995 $dateOptions = $this->getDateOptions( $context );
996 if ( $dateOptions ) {
997 $defaultPreferences['date'] = [
998 'type' => 'radio',
999 'options' => $dateOptions,
1000 'section' => 'rendering/dateformat',
1001 ];
1002 }
1003
1004 // Info
1005 $now = wfTimestampNow();
1006 $lang = $context->getLanguage();
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 ) );
1012
1013 $defaultPreferences['nowserver'] = [
1014 'type' => 'info',
1015 'raw' => 1,
1016 'label-message' => 'servertime',
1017 'default' => $nowserver,
1018 'section' => 'rendering/timeoffset',
1019 ];
1020
1021 $defaultPreferences['nowlocal'] = [
1022 'type' => 'info',
1023 'raw' => 1,
1024 'label-message' => 'localtime',
1025 'default' => $nowlocal,
1026 'section' => 'rendering/timeoffset',
1027 ];
1028
1029 $userTimeCorrection = (string)$this->userOptionsManager->getOption( $user, 'timecorrection' );
1030 // This value should already be normalized by UserTimeCorrection, so it should always be valid and not
1031 // in the legacy format. However, let's be sure about that and normalize it again.
1032 // Also, recompute the offset because it can change with DST.
1033 $userTimeCorrectionObj = new UserTimeCorrection(
1034 $userTimeCorrection,
1035 null,
1036 $this->options->get( MainConfigNames::LocalTZoffset )
1037 );
1038
1039 if ( $userTimeCorrectionObj->getCorrectionType() === UserTimeCorrection::OFFSET ) {
1040 $tzDefault = UserTimeCorrection::formatTimezoneOffset( $userTimeCorrectionObj->getTimeOffset() );
1041 } else {
1042 $tzDefault = $userTimeCorrectionObj->toString();
1043 }
1044
1045 $defaultPreferences['timecorrection'] = [
1046 'type' => 'timezone',
1047 'label-message' => 'timezonelegend',
1048 'default' => $tzDefault,
1049 'size' => 20,
1050 'section' => 'rendering/timeoffset',
1051 'id' => 'wpTimeCorrection',
1052 'filter' => TimezoneFilter::class,
1053 ];
1054 }
1055
1061 protected function renderingPreferences(
1062 User $user,
1063 MessageLocalizer $l10n,
1064 &$defaultPreferences
1065 ) {
1066 // Diffs
1067 $defaultPreferences['diffonly'] = [
1068 'type' => 'toggle',
1069 'section' => 'rendering/diffs',
1070 'label-message' => 'tog-diffonly',
1071 ];
1072 $defaultPreferences['norollbackdiff'] = [
1073 'type' => 'toggle',
1074 'section' => 'rendering/diffs',
1075 'label-message' => 'tog-norollbackdiff',
1076 ];
1077
1078 // Page Rendering
1079 if ( $this->options->get( MainConfigNames::AllowUserCssPrefs ) ) {
1080 $defaultPreferences['underline'] = [
1081 'type' => 'select',
1082 'options' => [
1083 $l10n->msg( 'underline-never' )->text() => 0,
1084 $l10n->msg( 'underline-always' )->text() => 1,
1085 $l10n->msg( 'underline-default' )->text() => 2,
1086 ],
1087 'label-message' => 'tog-underline',
1088 'section' => 'rendering/advancedrendering',
1089 ];
1090 }
1091
1092 $defaultPreferences['showhiddencats'] = [
1093 'type' => 'toggle',
1094 'section' => 'rendering/advancedrendering',
1095 'label-message' => 'tog-showhiddencats'
1096 ];
1097
1098 if ( $user->isAllowed( 'rollback' ) ) {
1099 $defaultPreferences['showrollbackconfirmation'] = [
1100 'type' => 'toggle',
1101 'section' => 'rendering/advancedrendering',
1102 'label-message' => 'tog-showrollbackconfirmation',
1103 ];
1104 }
1105 }
1106
1112 protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1113 $defaultPreferences['editsectiononrightclick'] = [
1114 'type' => 'toggle',
1115 'section' => 'editing/advancedediting',
1116 'label-message' => 'tog-editsectiononrightclick',
1117 ];
1118 $defaultPreferences['editondblclick'] = [
1119 'type' => 'toggle',
1120 'section' => 'editing/advancedediting',
1121 'label-message' => 'tog-editondblclick',
1122 ];
1123
1124 if ( $this->options->get( MainConfigNames::AllowUserCssPrefs ) ) {
1125 $defaultPreferences['editfont'] = [
1126 'type' => 'select',
1127 'section' => 'editing/editor',
1128 'label-message' => 'editfont-style',
1129 'options' => [
1130 $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
1131 $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
1132 $l10n->msg( 'editfont-serif' )->text() => 'serif',
1133 ]
1134 ];
1135 }
1136
1137 if ( $user->isAllowed( 'minoredit' ) ) {
1138 $defaultPreferences['minordefault'] = [
1139 'type' => 'toggle',
1140 'section' => 'editing/editor',
1141 'label-message' => 'tog-minordefault',
1142 ];
1143 }
1144
1145 $defaultPreferences['forceeditsummary'] = [
1146 'type' => 'toggle',
1147 'section' => 'editing/editor',
1148 'label-message' => 'tog-forceeditsummary',
1149 ];
1150 $defaultPreferences['useeditwarning'] = [
1151 'type' => 'toggle',
1152 'section' => 'editing/editor',
1153 'label-message' => 'tog-useeditwarning',
1154 ];
1155
1156 $defaultPreferences['previewonfirst'] = [
1157 'type' => 'toggle',
1158 'section' => 'editing/preview',
1159 'label-message' => 'tog-previewonfirst',
1160 ];
1161 $defaultPreferences['previewontop'] = [
1162 'type' => 'toggle',
1163 'section' => 'editing/preview',
1164 'label-message' => 'tog-previewontop',
1165 ];
1166 $defaultPreferences['uselivepreview'] = [
1167 'type' => 'toggle',
1168 'section' => 'editing/preview',
1169 'label-message' => 'tog-uselivepreview',
1170 ];
1171 }
1172
1178 protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1179 $rcMaxAge = $this->options->get( MainConfigNames::RCMaxAge );
1180 $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1181 $defaultPreferences['rcdays'] = [
1182 'type' => 'float',
1183 'label-message' => 'recentchangesdays',
1184 'section' => 'rc/displayrc',
1185 'min' => 1 / 24,
1186 'max' => $rcMax,
1187 'help-message' => [ 'recentchangesdays-max', Message::numParam( $rcMax ) ],
1188 ];
1189 $defaultPreferences['rclimit'] = [
1190 'type' => 'int',
1191 'min' => 1,
1192 'max' => 1000,
1193 'label-message' => 'recentchangescount',
1194 'help-message' => 'prefs-help-recentchangescount',
1195 'section' => 'rc/displayrc',
1196 'filter' => IntvalFilter::class,
1197 ];
1198 $defaultPreferences['usenewrc'] = [
1199 'type' => 'toggle',
1200 'label-message' => 'tog-usenewrc',
1201 'section' => 'rc/advancedrc',
1202 ];
1203 $defaultPreferences['hideminor'] = [
1204 'type' => 'toggle',
1205 'label-message' => 'tog-hideminor',
1206 'section' => 'rc/changesrc',
1207 ];
1208 $defaultPreferences['pst-cssjs'] = [
1209 'type' => 'api',
1210 ];
1211 $defaultPreferences['rcfilters-rc-collapsed'] = [
1212 'type' => 'api',
1213 ];
1214 $defaultPreferences['rcfilters-wl-collapsed'] = [
1215 'type' => 'api',
1216 ];
1217 $defaultPreferences['rcfilters-saved-queries'] = [
1218 'type' => 'api',
1219 ];
1220 $defaultPreferences['rcfilters-wl-saved-queries'] = [
1221 'type' => 'api',
1222 ];
1223 // Override RCFilters preferences for RecentChanges 'limit'
1224 $defaultPreferences['rcfilters-limit'] = [
1225 'type' => 'api',
1226 ];
1227 $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
1228 'type' => 'api',
1229 ];
1230 $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
1231 'type' => 'api',
1232 ];
1233
1234 if ( $this->options->get( MainConfigNames::RCWatchCategoryMembership ) ) {
1235 $defaultPreferences['hidecategorization'] = [
1236 'type' => 'toggle',
1237 'label-message' => 'tog-hidecategorization',
1238 'section' => 'rc/changesrc',
1239 ];
1240 }
1241
1242 if ( $user->useRCPatrol() ) {
1243 $defaultPreferences['hidepatrolled'] = [
1244 'type' => 'toggle',
1245 'section' => 'rc/changesrc',
1246 'label-message' => 'tog-hidepatrolled',
1247 ];
1248 }
1249
1250 if ( $user->useNPPatrol() ) {
1251 $defaultPreferences['newpageshidepatrolled'] = [
1252 'type' => 'toggle',
1253 'section' => 'rc/changesrc',
1254 'label-message' => 'tog-newpageshidepatrolled',
1255 ];
1256 }
1257
1258 if ( $this->options->get( MainConfigNames::RCShowWatchingUsers ) ) {
1259 $defaultPreferences['shownumberswatching'] = [
1260 'type' => 'toggle',
1261 'section' => 'rc/advancedrc',
1262 'label-message' => 'tog-shownumberswatching',
1263 ];
1264 }
1265
1266 $defaultPreferences['rcenhancedfilters-disable'] = [
1267 'type' => 'toggle',
1268 'section' => 'rc/advancedrc',
1269 'label-message' => 'rcfilters-preference-label',
1270 'help-message' => 'rcfilters-preference-help',
1271 ];
1272 }
1273
1279 protected function watchlistPreferences(
1280 User $user, IContextSource $context, &$defaultPreferences
1281 ) {
1282 $watchlistdaysMax = ceil( $this->options->get( MainConfigNames::RCMaxAge ) / ( 3600 * 24 ) );
1283
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' ] ],
1290 ];
1291 foreach ( $editWatchlistModes as $mode => $options ) {
1292 // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1293 $editWatchlistLinks .=
1294 new \OOUI\ButtonWidget( [
1295 'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
1296 'flags' => $options[ 'flags' ],
1297 'label' => new \OOUI\HtmlSnippet(
1298 $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
1299 ),
1300 ] );
1301 }
1302
1303 $defaultPreferences['editwatchlist'] = [
1304 'type' => 'info',
1305 'raw' => true,
1306 'default' => $editWatchlistLinks,
1307 'label-message' => 'prefs-editwatchlist-label',
1308 'section' => 'watchlist/editwatchlist',
1309 ];
1310 }
1311
1312 $defaultPreferences['watchlistdays'] = [
1313 'type' => 'float',
1314 'min' => 1 / 24,
1315 'max' => $watchlistdaysMax,
1316 'section' => 'watchlist/displaywatchlist',
1317 'help-message' => [ 'prefs-watchlist-days-max', Message::numParam( $watchlistdaysMax ) ],
1318 'label-message' => 'prefs-watchlist-days',
1319 ];
1320 $defaultPreferences['wllimit'] = [
1321 'type' => 'int',
1322 'min' => 1,
1323 'max' => 1000,
1324 'label-message' => 'prefs-watchlist-edits',
1325 'help-message' => 'prefs-watchlist-edits-max',
1326 'section' => 'watchlist/displaywatchlist',
1327 'filter' => IntvalFilter::class,
1328 ];
1329 $defaultPreferences['extendwatchlist'] = [
1330 'type' => 'toggle',
1331 'section' => 'watchlist/advancedwatchlist',
1332 'label-message' => 'tog-extendwatchlist',
1333 ];
1334 $defaultPreferences['watchlisthideminor'] = [
1335 'type' => 'toggle',
1336 'section' => 'watchlist/changeswatchlist',
1337 'label-message' => 'tog-watchlisthideminor',
1338 ];
1339 $defaultPreferences['watchlisthidebots'] = [
1340 'type' => 'toggle',
1341 'section' => 'watchlist/changeswatchlist',
1342 'label-message' => 'tog-watchlisthidebots',
1343 ];
1344 $defaultPreferences['watchlisthideown'] = [
1345 'type' => 'toggle',
1346 'section' => 'watchlist/changeswatchlist',
1347 'label-message' => 'tog-watchlisthideown',
1348 ];
1349 $defaultPreferences['watchlisthideanons'] = [
1350 'type' => 'toggle',
1351 'section' => 'watchlist/changeswatchlist',
1352 'label-message' => 'tog-watchlisthideanons',
1353 ];
1354 $defaultPreferences['watchlisthideliu'] = [
1355 'type' => 'toggle',
1356 'section' => 'watchlist/changeswatchlist',
1357 'label-message' => 'tog-watchlisthideliu',
1358 ];
1359
1361 $defaultPreferences['watchlistreloadautomatically'] = [
1362 'type' => 'toggle',
1363 'section' => 'watchlist/advancedwatchlist',
1364 'label-message' => 'tog-watchlistreloadautomatically',
1365 ];
1366 }
1367
1368 $defaultPreferences['watchlistunwatchlinks'] = [
1369 'type' => 'toggle',
1370 'section' => 'watchlist/advancedwatchlist',
1371 'label-message' => 'tog-watchlistunwatchlinks',
1372 ];
1373
1374 if ( $this->options->get( MainConfigNames::RCWatchCategoryMembership ) ) {
1375 $defaultPreferences['watchlisthidecategorization'] = [
1376 'type' => 'toggle',
1377 'section' => 'watchlist/changeswatchlist',
1378 'label-message' => 'tog-watchlisthidecategorization',
1379 ];
1380 }
1381
1382 if ( $user->useRCPatrol() ) {
1383 $defaultPreferences['watchlisthidepatrolled'] = [
1384 'type' => 'toggle',
1385 'section' => 'watchlist/changeswatchlist',
1386 'label-message' => 'tog-watchlisthidepatrolled',
1387 ];
1388 }
1389
1390 $watchTypes = [
1391 'edit' => 'watchdefault',
1392 'move' => 'watchmoves',
1393 'delete' => 'watchdeletion'
1394 ];
1395
1396 // Kinda hacky
1397 if ( $user->isAllowedAny( 'createpage', 'createtalk' ) ) {
1398 $watchTypes['read'] = 'watchcreations';
1399 }
1400
1401 if ( $user->isAllowed( 'rollback' ) ) {
1402 $watchTypes['rollback'] = 'watchrollback';
1403 }
1404
1405 if ( $user->isAllowed( 'upload' ) ) {
1406 $watchTypes['upload'] = 'watchuploads';
1407 }
1408
1409 foreach ( $watchTypes as $action => $pref ) {
1410 if ( $user->isAllowed( $action ) ) {
1411 // Messages:
1412 // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1413 // tog-watchrollback
1414 $defaultPreferences[$pref] = [
1415 'type' => 'toggle',
1416 'section' => 'watchlist/pageswatchlist',
1417 'label-message' => "tog-$pref",
1418 ];
1419 }
1420 }
1421
1422 $defaultPreferences['watchlisttoken'] = [
1423 'type' => 'api',
1424 ];
1425
1426 $tokenButton = new \OOUI\ButtonWidget( [
1427 'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1428 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1429 ] ),
1430 'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1431 ] );
1432 $defaultPreferences['watchlisttoken-info'] = [
1433 'type' => 'info',
1434 'section' => 'watchlist/tokenwatchlist',
1435 'label-message' => 'prefs-watchlist-token',
1436 'help-message' => 'prefs-help-tokenmanagement',
1437 'raw' => true,
1438 'default' => (string)$tokenButton,
1439 ];
1440
1441 $defaultPreferences['wlenhancedfilters-disable'] = [
1442 'type' => 'toggle',
1443 'section' => 'watchlist/advancedwatchlist',
1444 'label-message' => 'rcfilters-watchlist-preference-label',
1445 'help-message' => 'rcfilters-watchlist-preference-help',
1446 ];
1447 }
1448
1453 protected function searchPreferences( $context, &$defaultPreferences ) {
1454 $defaultPreferences['search-special-page'] = [
1455 'type' => 'api',
1456 ];
1457
1458 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1459 $defaultPreferences['searchNs' . $n] = [
1460 'type' => 'api',
1461 ];
1462 }
1463
1464 if ( $this->options->get( MainConfigNames::SearchMatchRedirectPreference ) ) {
1465 $defaultPreferences['search-match-redirect'] = [
1466 'type' => 'toggle',
1467 'section' => 'searchoptions/searchmisc',
1468 'label-message' => 'search-match-redirect-label',
1469 'help-message' => 'search-match-redirect-help',
1470 ];
1471 } else {
1472 $defaultPreferences['search-match-redirect'] = [
1473 'type' => 'api',
1474 ];
1475 }
1476
1477 $defaultPreferences['searchlimit'] = [
1478 'type' => 'int',
1479 'min' => 1,
1480 'max' => 500,
1481 'section' => 'searchoptions/searchmisc',
1482 'label-message' => 'searchlimit-label',
1483 'help-message' => $context->msg( 'searchlimit-help', 500 ),
1484 'filter' => IntvalFilter::class,
1485 ];
1486
1487 // show a preference for thumbnails from namespaces other than NS_FILE,
1488 // only when there they're actually configured to be served
1489 $thumbNamespaces = $this->config->get( 'ThumbnailNamespaces' );
1490 $thumbNamespacesFormatted = array_combine(
1491 $thumbNamespaces,
1492 array_map(
1493 static function ( $namespaceId ) use ( $context ) {
1494 return $namespaceId === NS_MAIN
1495 ? $context->msg( 'blanknamespace' )->escaped()
1496 : $context->getLanguage()->getFormattedNsText( $namespaceId );
1497 },
1498 $thumbNamespaces
1499 )
1500 );
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'] = [
1507 'type' => 'toggle',
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 )
1516 ),
1517 ];
1518 }
1519 }
1520
1521 /*
1522 * Custom skin string comparison function that takes into account current and preferred skins.
1523 *
1524 * @param string $a
1525 * @param string $b
1526 * @param string $currentSkin
1527 * @param array $preferredSkins
1528 * @return int
1529 */
1530 private static function sortSkinNames( $a, $b, $currentSkin, $preferredSkins ) {
1531 // Display the current skin first in the list
1532 if ( strcasecmp( $a, $currentSkin ) === 0 ) {
1533 return -1;
1534 }
1535 if ( strcasecmp( $b, $currentSkin ) === 0 ) {
1536 return 1;
1537 }
1538 // Display preferred skins over other skins
1539 if ( count( $preferredSkins ) ) {
1540 $aPreferred = array_search( $a, $preferredSkins );
1541 $bPreferred = array_search( $b, $preferredSkins );
1542 // Cannot use ! operator because array_search returns the
1543 // index of the array item if found (i.e. 0) and false otherwise
1544 if ( $aPreferred !== false && $bPreferred === false ) {
1545 return -1;
1546 }
1547 if ( $aPreferred === false && $bPreferred !== false ) {
1548 return 1;
1549 }
1550 // When both skins are preferred, default to the ordering
1551 // specified by the preferred skins config array
1552 if ( $aPreferred !== false && $bPreferred !== false ) {
1553 return strcasecmp( $aPreferred, $bPreferred );
1554 }
1555 }
1556 // Use normal string comparison if both strings are not preferred
1557 return strcasecmp( $a, $b );
1558 }
1559
1565 protected function generateSkinOptions( User $user, IContextSource $context ) {
1566 $ret = [];
1567
1568 $mptitle = Title::newMainPage();
1569 $previewtext = $context->msg( 'skin-preview' )->escaped();
1570
1571 // Only show skins that aren't disabled
1572 $validSkinNames = $this->skinFactory->getAllowedSkins();
1573 $allInstalledSkins = $this->skinFactory->getInstalledSkins();
1574
1575 // Display the installed skin the user has specifically requested via useskin=….
1576 $useSkin = $context->getRequest()->getRawVal( 'useskin' );
1577 if ( isset( $allInstalledSkins[$useSkin] )
1578 && $context->msg( "skinname-$useSkin" )->exists()
1579 ) {
1580 $validSkinNames[$useSkin] = $useSkin;
1581 }
1582
1583 // Display the skin if the user has set it as a preference already before it was hidden.
1584 $currentUserSkin = $this->userOptionsManager->getOption( $user, 'skin' );
1585 if ( isset( $allInstalledSkins[$currentUserSkin] )
1586 && $context->msg( "skinname-$currentUserSkin" )->exists()
1587 ) {
1588 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1589 }
1590
1591 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1592 $msg = $context->msg( "skinname-{$skinkey}" );
1593 if ( $msg->exists() ) {
1594 $skinname = htmlspecialchars( $msg->text() );
1595 }
1596 }
1597
1598 $preferredSkins = $this->config->get( MainConfigNames::SkinsPreferred );
1599 // Sort by the internal name, so that the ordering is the same for each display language,
1600 // especially if some skin names are translated to use a different alphabet and some are not.
1601 uksort( $validSkinNames, function ( $a, $b ) use ( $currentUserSkin, $preferredSkins ) {
1602 return $this->sortSkinNames( $a, $b, $currentUserSkin, $preferredSkins );
1603 } );
1604
1605 $defaultSkin = $this->options->get( MainConfigNames::DefaultSkin );
1606 $allowUserCss = $this->options->get( MainConfigNames::AllowUserCss );
1607 $allowUserJs = $this->options->get( MainConfigNames::AllowUserJs );
1608 $foundDefault = false;
1609 foreach ( $validSkinNames as $skinkey => $sn ) {
1610 $linkTools = [];
1611
1612 // Mark the default skin
1613 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1614 $linkTools[] = $context->msg( 'default' )->escaped();
1615 $foundDefault = true;
1616 }
1617
1618 // Create talk page link if relevant message exists.
1619 $talkPageMsg = $context->msg( "$skinkey-prefs-talkpage" );
1620 if ( $talkPageMsg->exists() ) {
1621 $linkTools[] = $talkPageMsg->parse();
1622 }
1623
1624 // Create preview link
1625 $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1626 $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1627
1628 // Create links to user CSS/JS pages
1629 // @todo Refactor this and the similar code in skinPreferences().
1630 if ( $allowUserCss ) {
1631 $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1632 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1633 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1634 }
1635
1636 if ( $allowUserJs ) {
1637 $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1638 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1639 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1640 }
1641
1642 $display = $sn . ' ' . $context->msg( 'parentheses' )
1643 ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1644 ->escaped();
1645 $ret[$display] = $skinkey;
1646 }
1647
1648 if ( !$foundDefault ) {
1649 // If the default skin is not available, things are going to break horribly because the
1650 // default value for skin selector will not be a valid value. Let's just not show it then.
1651 return [];
1652 }
1653
1654 return $ret;
1655 }
1656
1661 protected function getDateOptions( IContextSource $context ) {
1662 $lang = $context->getLanguage();
1663 $dateopts = $lang->getDatePreferences();
1664
1665 $ret = [];
1666
1667 if ( $dateopts ) {
1668 if ( !in_array( 'default', $dateopts ) ) {
1669 $dateopts[] = 'default'; // Make sure default is always valid T21237
1670 }
1671
1672 // FIXME KLUGE: site default might not be valid for user language
1673 global $wgDefaultUserOptions;
1674 if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1675 $wgDefaultUserOptions['date'] = 'default';
1676 }
1677
1678 $epoch = wfTimestampNow();
1679 foreach ( $dateopts as $key ) {
1680 if ( $key == 'default' ) {
1681 $formatted = $context->msg( 'datedefault' )->escaped();
1682 } else {
1683 $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1684 }
1685 $ret[$formatted] = $key;
1686 }
1687 }
1688 return $ret;
1689 }
1690
1695 protected function getImageSizes( MessageLocalizer $l10n ) {
1696 $ret = [];
1697 $pixels = $l10n->msg( 'unit-pixel' )->text();
1698
1699 foreach ( $this->options->get( MainConfigNames::ImageLimits ) as $index => $limits ) {
1700 // Note: A left-to-right marker (U+200E) is inserted, see T144386
1701 $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1702 $ret[$display] = $index;
1703 }
1704
1705 return $ret;
1706 }
1707
1712 protected function getThumbSizes( MessageLocalizer $l10n ) {
1713 $ret = [];
1714 $pixels = $l10n->msg( 'unit-pixel' )->text();
1715
1716 foreach ( $this->options->get( MainConfigNames::ThumbLimits ) as $index => $size ) {
1717 $display = $size . $pixels;
1718 $ret[$display] = $index;
1719 }
1720
1721 return $ret;
1722 }
1723
1730 protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1731 $sigValidation = $this->options->get( MainConfigNames::SignatureValidation );
1732 $maxSigChars = $this->options->get( MainConfigNames::MaxSigChars );
1733 if ( is_string( $signature ) && mb_strlen( $signature ) > $maxSigChars ) {
1734 return $form->msg( 'badsiglength' )->numParams( $maxSigChars )->escaped();
1735 }
1736
1737 if ( $signature === null || $signature === '' ) {
1738 // Make sure leaving the field empty is valid, since that's used as the default (T288151).
1739 // Code using this preference in Parser::getUserSig() handles this case specially.
1740 return true;
1741 }
1742
1743 // Remaining checks only apply to fancy signatures
1744 if ( !( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) ) {
1745 return true;
1746 }
1747
1748 // HERE BE DRAGONS:
1749 //
1750 // If this value is already saved as the user's signature, treat it as valid, even if it
1751 // would be invalid to save now, and even if $wgSignatureValidation is set to 'disallow'.
1752 //
1753 // It can become invalid when we introduce new validation, or when the value just transcludes
1754 // some page containing the real signature and that page is edited (which we can't validate),
1755 // or when someone's username is changed.
1756 //
1757 // Otherwise it would be completely removed when the user opens their preferences page, which
1758 // would be very unfriendly.
1759 $user = $form->getUser();
1760 if (
1761 $signature === $this->userOptionsManager->getOption( $user, 'nickname' ) &&
1762 (bool)$alldata['fancysig'] === $this->userOptionsManager->getBoolOption( $user, 'fancysig' )
1763 ) {
1764 return true;
1765 }
1766
1767 if ( $sigValidation === 'new' || $sigValidation === 'disallow' ) {
1768 // Validate everything
1769 $parserOpts = ParserOptions::newFromContext( $form->getContext() );
1770 $validator = $this->signatureValidatorFactory
1771 ->newSignatureValidator( $user, $form->getContext(), $parserOpts );
1772 $errors = $validator->validateSignature( $signature );
1773 if ( $errors ) {
1774 return $errors;
1775 }
1776 }
1777
1778 // Quick check for mismatched HTML tags in the input.
1779 // Note that this is easily fooled by wikitext templates or bold/italic markup.
1780 // We're only keeping this until Parsoid is integrated and guaranteed to be available.
1781 if ( $this->parser->validateSig( $signature ) === false ) {
1782 return $form->msg( 'badsig' )->escaped();
1783 }
1784
1785 return true;
1786 }
1787
1794 protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
1795 if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1796 $signature = $this->parser->cleanSig( $signature );
1797 } else {
1798 // When no fancy sig used, make sure ~{3,5} get removed.
1799 $signature = Parser::cleanSigInSig( $signature );
1800 }
1801
1802 return $signature;
1803 }
1804
1812 public function getForm(
1813 User $user,
1814 IContextSource $context,
1815 $formClass = PreferencesFormOOUI::class,
1816 array $remove = []
1817 ) {
1818 // We use ButtonWidgets in some of the getPreferences() functions
1819 $context->getOutput()->enableOOUI();
1820
1821 // Note that the $user parameter of getFormDescriptor() is deprecated.
1822 $formDescriptor = $this->getFormDescriptor( $user, $context );
1823 if ( count( $remove ) ) {
1824 $removeKeys = array_fill_keys( $remove, true );
1825 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1826 }
1827
1828 // Remove type=api preferences. They are not intended for rendering in the form.
1829 foreach ( $formDescriptor as $name => $info ) {
1830 if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1831 unset( $formDescriptor[$name] );
1832 }
1833 }
1834
1838 $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1839
1840 // This allows users to opt-in to hidden skins. While this should be discouraged and is not
1841 // discoverable, this allows users to still use hidden skins while preventing new users from
1842 // adopting unsupported skins. If no useskin=… parameter was provided, it will not show up
1843 // in the resulting URL.
1844 $htmlForm->setAction( $context->getTitle()->getLocalURL( [
1845 'useskin' => $context->getRequest()->getRawVal( 'useskin' )
1846 ] ) );
1847
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' );
1854 // Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1855 $htmlForm->setSubmitTooltip( 'preferences-save' );
1856 $htmlForm->setSubmitID( 'prefcontrol' );
1857 $htmlForm->setSubmitCallback(
1858 function ( array $formData, PreferencesFormOOUI $form ) use ( $formDescriptor ) {
1859 return $this->submitForm( $formData, $form, $formDescriptor );
1860 }
1861 );
1862
1863 return $htmlForm;
1864 }
1865
1874 protected function saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor ) {
1875 $user = $form->getModifiedUser();
1876 $hiddenPrefs = $this->options->get( MainConfigNames::HiddenPrefs );
1877 $result = true;
1878
1879 if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' )
1880 ) {
1881 return Status::newFatal( 'mypreferencesprotected' );
1882 }
1883
1884 // Filter input
1885 $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
1886
1887 // Fortunately, the realname field is MUCH simpler
1888 // (not really "private", but still shouldn't be edited without permission)
1889
1890 if ( !in_array( 'realname', $hiddenPrefs )
1891 && $user->isAllowed( 'editmyprivateinfo' )
1892 && array_key_exists( 'realname', $formData )
1893 ) {
1894 $realName = $formData['realname'];
1895 $user->setRealName( $realName );
1896 }
1897
1898 if ( $user->isAllowed( 'editmyoptions' ) ) {
1899 $oldUserOptions = $this->userOptionsManager->getOptions( $user );
1900
1901 foreach ( $this->getSaveBlacklist() as $b ) {
1902 unset( $formData[$b] );
1903 }
1904
1905 // If users have saved a value for a preference which has subsequently been disabled
1906 // via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1907 // is subsequently re-enabled
1908 foreach ( $hiddenPrefs as $pref ) {
1909 // If the user has not set a non-default value here, the default will be returned
1910 // and subsequently discarded
1911 $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref, null, true );
1912 }
1913
1914 // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
1915 if (
1916 isset( $formData['rclimit'] ) &&
1917 intval( $formData[ 'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user, 'rclimit' )
1918 ) {
1919 $formData['rcfilters-limit'] = $formData['rclimit'];
1920 }
1921
1922 // Keep old preferences from interfering due to back-compat code, etc.
1923 $this->userOptionsManager->resetOptions( $user, $form->getContext(), 'unused' );
1924
1925 foreach ( $formData as $key => $value ) {
1926 $this->userOptionsManager->setOption( $user, $key, $value );
1927 }
1928
1929 $this->hookRunner->onPreferencesFormPreSave(
1930 $formData, $form, $user, $result, $oldUserOptions );
1931 }
1932
1933 $user->saveSettings();
1934
1935 return $result;
1936 }
1937
1946 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1947 foreach ( $formDescriptor as $preference => $desc ) {
1948 if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
1949 continue;
1950 }
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();
1958 } else {
1959 throw new UnexpectedValueException(
1960 "Unrecognized filter type for preference '$preference'"
1961 );
1962 }
1963 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1964 }
1965 }
1966
1975 protected function submitForm(
1976 array $formData,
1977 PreferencesFormOOUI $form,
1978 array $formDescriptor
1979 ) {
1980 $res = $this->saveFormData( $formData, $form, $formDescriptor );
1981
1982 if ( $res === true ) {
1983 $context = $form->getContext();
1984 $urlOptions = [];
1985
1986 $urlOptions += $form->getExtraSuccessRedirectParameters();
1987
1988 $url = $form->getTitle()->getFullURL( $urlOptions );
1989
1990 // Set session data for the success message
1991 $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
1992
1993 $context->getOutput()->redirect( $url );
1994 }
1995
1996 return ( $res === true ? Status::newGood() : $res );
1997 }
1998}
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.
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:153
Methods for dealing with language codes.
Base class for multi-variant language conversion.
Base class for language-specific code.
Definition Language.php:56
MediaWiki exception.
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,...
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:55
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 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()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
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)
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)
__construct(ServiceOptions $options, Language $contLang, AuthManager $authManager, LinkRenderer $linkRenderer, NamespaceInfo $nsInfo, PermissionManager $permissionManager, ILanguageConverter $languageConverter, LanguageNameUtils $languageNameUtils, HookContainer $hookContainer, UserOptionsLookup $userOptionsLookup, LanguageConverterFactory $languageConverterFactory=null, Parser $parser=null, SkinFactory $skinFactory=null, UserGroupManager $userGroupManager=null, SignatureValidatorFactory $signatureValidatorFactory=null, Config $config=null)
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.
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.
newMainPage(MessageLocalizer $localizer=null)
makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Represents a title within MediaWiki.
Definition Title.php:82
Provides access to user options.
A service class to control user options.
Utility class to parse the TimeCorrection string value.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
static numParam( $num)
Definition Message.php:1146
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...
Definition Parser.php:107
static stripOuterParagraph( $html)
Strip outer.
Definition Parser.php:6455
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition Parser.php:4780
Form to edit user preferences.
getExtraSuccessRedirectParameters()
Get extra parameters for the query string when redirecting after successful save.
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.
Definition Status.php:46
Represents a "user group membership" – a specific instance of a user belonging to a group.
internal since 1.36
Definition User.php:71
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1680
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:2011
getRealName()
Get the user's real name.
Definition User.php:2095
getRegistration()
Get the timestamp of account creation.
Definition User.php:3256
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.
Definition User.php:2359
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:2385
setRealName(string $str)
Set the user's real name.
Definition User.php:2107
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:2375
getEditCount()
Get the user's edit count.
Definition User.php:2264
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:1772
saveSettings()
Save this user's settings into the database.
Definition User.php:2551
getEmail()
Get the user's e-mail address.
Definition User.php:1998
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Definition User.php:2367
Module of static functions for generating XML.
Definition Xml.php:31
$wgDefaultUserOptions
Config variable stub for the DefaultUserOptions setting, for use by phpdoc and IDEs.
Interface for configuration instances.
Definition Config.php:30
Interface for objects which can provide a MediaWiki context on request.
The shared interface for all language converters.
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.
if(!isset( $args[0])) $lang