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