MediaWiki REL1_37
DefaultPreferencesFactory.php
Go to the documentation of this file.
1<?php
22
23use DateTime;
24use DateTimeZone;
25use Exception;
26use Html;
27use HTMLForm;
31use Language;
32use LanguageCode;
47use Message;
49use MWException;
50use MWTimestamp;
52use OutputPage;
53use Parser;
56use Psr\Log\LoggerAwareTrait;
57use Psr\Log\NullLogger;
58use SkinFactory;
59use SpecialPage;
60use Status;
61use Title;
62use UnexpectedValueException;
63use User;
65use Xml;
66
71 use LoggerAwareTrait;
72
74 protected $options;
75
77 protected $contLang;
78
81
83 protected $authManager;
84
86 protected $linkRenderer;
87
89 protected $nsInfo;
90
93
96
98 private $hookRunner;
99
102
105
107 private $parser;
108
111
114
118 public const CONSTRUCTOR_OPTIONS = [
119 'AllowRequiringEmailForResets',
120 'AllowUserCss',
121 'AllowUserCssPrefs',
122 'AllowUserJs',
123 'DefaultSkin',
124 'EmailAuthentication',
125 'EmailConfirmToEdit',
126 'EnableEmail',
127 'EnableUserEmail',
128 'EnableUserEmailMuteList',
129 'EnotifMinorEdits',
130 'EnotifRevealEditorAddress',
131 'EnotifUserTalk',
132 'EnotifWatchlist',
133 'ForceHTTPS',
134 'HiddenPrefs',
135 'ImageLimits',
136 'LanguageCode',
137 'LocalTZoffset',
138 'MaxSigChars',
139 'RCMaxAge',
140 'RCShowWatchingUsers',
141 'RCWatchCategoryMembership',
142 'SearchMatchRedirectPreference',
143 'SecureLogin',
144 'ScriptPath',
145 'SignatureValidation',
146 'ThumbLimits',
147 ];
148
165 public function __construct(
174 HookContainer $hookContainer,
177 Parser $parser = null,
180 ) {
181 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
182
183 $this->options = $options;
184 $this->contLang = $contLang;
185 $this->authManager = $authManager;
186 $this->linkRenderer = $linkRenderer;
187 $this->nsInfo = $nsInfo;
188
189 // We don't use the PermissionManager anymore, but we need to be careful
190 // removing the parameter since this class is extended by GlobalPreferencesFactory
191 // in the GlobalPreferences extension, and that class uses it
192 $this->permissionManager = $permissionManager;
193
194 $this->logger = new NullLogger();
195 $this->languageConverter = $languageConverter;
196 $this->languageNameUtils = $languageNameUtils;
197 $this->hookRunner = new HookRunner( $hookContainer );
198
199 // Don't break GlobalPreferences, fall back to global state if missing services
200 // or if passed a UserOptionsLookup that isn't UserOptionsManager
201 $services = static function () {
202 // BC hack. Use a closure so this can be unit-tested.
204 };
205 $this->userOptionsManager = ( $userOptionsLookup instanceof UserOptionsManager )
207 : $services()->getUserOptionsManager();
208 $this->languageConverterFactory = $languageConverterFactory ?? $services()->getLanguageConverterFactory();
209 $this->parser = $parser ?? $services()->getParser();
210 $this->skinFactory = $skinFactory ?? $services()->getSkinFactory();
211 $this->userGroupManager = $userGroupManager ?? $services()->getUserGroupManager();
212 }
213
217 public function getSaveBlacklist() {
218 return [
219 'realname',
220 'emailaddress',
221 ];
222 }
223
230 public function getFormDescriptor( User $user, IContextSource $context ) {
231 $preferences = [];
232
233 OutputPage::setupOOUI(
234 strtolower( $context->getSkin()->getSkinName() ),
235 $context->getLanguage()->getDir()
236 );
237
238 $this->profilePreferences( $user, $context, $preferences );
239 $this->skinPreferences( $user, $context, $preferences );
240 $this->datetimePreferences( $user, $context, $preferences );
241 $this->filesPreferences( $context, $preferences );
242 $this->renderingPreferences( $user, $context, $preferences );
243 $this->editingPreferences( $user, $context, $preferences );
244 $this->rcPreferences( $user, $context, $preferences );
245 $this->watchlistPreferences( $user, $context, $preferences );
246 $this->searchPreferences( $preferences );
247
248 $this->hookRunner->onGetPreferences( $user, $preferences );
249
250 $this->loadPreferenceValues( $user, $context, $preferences );
251 $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
252 return $preferences;
253 }
254
263 private function loadPreferenceValues( User $user, IContextSource $context, &$defaultPreferences ) {
264 // Remove preferences that wikis don't want to use
265 foreach ( $this->options->get( 'HiddenPrefs' ) as $pref ) {
266 unset( $defaultPreferences[$pref] );
267 }
268
269 // Make sure that form fields have their parent set. See T43337.
270 $dummyForm = new HTMLForm( [], $context );
271
272 $disable = !$user->isAllowed( 'editmyoptions' );
273
274 $defaultOptions = $this->userOptionsManager->getDefaultOptions();
275 $userOptions = $this->userOptionsManager->getOptions( $user );
276 $this->applyFilters( $userOptions, $defaultPreferences, 'filterForForm' );
277 // Add in defaults from the user
278 foreach ( $defaultPreferences as $name => &$info ) {
279 $prefFromUser = $this->getOptionFromUser( $name, $info, $userOptions );
280 if ( $disable && !in_array( $name, $this->getSaveBlacklist() ) ) {
281 $info['disabled'] = 'disabled';
282 }
283 $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation
284 $globalDefault = $defaultOptions[$name] ?? null;
285
286 // If it validates, set it as the default
287 if ( isset( $info['default'] ) ) {
288 // Already set, no problem
289 continue;
290 }
291 if ( $prefFromUser !== null && // Make sure we're not just pulling nothing
292 $field->validate( $prefFromUser, $this->userOptionsManager->getOptions( $user ) ) === true ) {
293 $info['default'] = $prefFromUser;
294 } elseif ( $field->validate( $globalDefault, $this->userOptionsManager->getOptions( $user ) ) === true ) {
295 $info['default'] = $globalDefault;
296 } else {
297 $globalDefault = json_encode( $globalDefault );
298 throw new MWException(
299 "Default '$globalDefault' is invalid for preference $name of user " . $user->getName()
300 );
301 }
302 }
303
304 return $defaultPreferences;
305 }
306
315 protected function getOptionFromUser( $name, $info, array $userOptions ) {
316 $val = $userOptions[$name] ?? null;
317
318 // Handling for multiselect preferences
319 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
320 ( isset( $info['class'] ) && $info['class'] == \HTMLMultiSelectField::class ) ) {
321 $options = HTMLFormField::flattenOptions( $info['options-messages'] ?? $info['options'] );
322 $prefix = $info['prefix'] ?? $name;
323 $val = [];
324
325 foreach ( $options as $value ) {
326 if ( $userOptions["$prefix$value"] ?? false ) {
327 $val[] = $value;
328 }
329 }
330 }
331
332 // Handling for checkmatrix preferences
333 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
334 ( isset( $info['class'] ) && $info['class'] == \HTMLCheckMatrix::class ) ) {
335 $columns = HTMLFormField::flattenOptions( $info['columns'] );
336 $rows = HTMLFormField::flattenOptions( $info['rows'] );
337 $prefix = $info['prefix'] ?? $name;
338 $val = [];
339
340 foreach ( $columns as $column ) {
341 foreach ( $rows as $row ) {
342 if ( $userOptions["$prefix$column-$row"] ?? false ) {
343 $val[] = "$column-$row";
344 }
345 }
346 }
347 }
348
349 return $val;
350 }
351
359 protected function profilePreferences(
360 User $user, IContextSource $context, &$defaultPreferences
361 ) {
362 // retrieving user name for GENDER and misc.
363 $userName = $user->getName();
364
365 // Information panel
366 $defaultPreferences['username'] = [
367 'type' => 'info',
368 'label-message' => [ 'username', $userName ],
369 'default' => $userName,
370 'section' => 'personal/info',
371 ];
372
373 $lang = $context->getLanguage();
374
375 // Get groups to which the user belongs, Skip the default * group, seems useless here
376 $userEffectiveGroups = array_diff(
377 $this->userGroupManager->getUserEffectiveGroups( $user ),
378 [ '*' ]
379 );
380 $defaultPreferences['usergroups'] = [
381 'type' => 'info',
382 'label-message' => [ 'prefs-memberingroups',
383 \Message::numParam( count( $userEffectiveGroups ) ), $userName ],
384 'default' => function () use ( $user, $userEffectiveGroups, $context, $lang, $userName ) {
385 $userGroupMemberships = $this->userGroupManager->getUserGroupMemberships( $user );
386 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
387 foreach ( $userEffectiveGroups as $ueg ) {
388 $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
389
390 $userG = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html' );
391 $userM = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html',
392 $userName );
393
394 // Store expiring groups separately, so we can place them before non-expiring
395 // groups in the list. This is to avoid the ambiguity of something like
396 // "administrator, bureaucrat (until X date)" -- users might wonder whether the
397 // expiry date applies to both groups, or just the last one
398 if ( $groupStringOrObject instanceof UserGroupMembership &&
399 $groupStringOrObject->getExpiry()
400 ) {
401 $userTempGroups[] = $userG;
402 $userTempMembers[] = $userM;
403 } else {
404 $userGroups[] = $userG;
405 $userMembers[] = $userM;
406 }
407 }
408 sort( $userGroups );
409 sort( $userMembers );
410 sort( $userTempGroups );
411 sort( $userTempMembers );
412 $userGroups = array_merge( $userTempGroups, $userGroups );
413 $userMembers = array_merge( $userTempMembers, $userMembers );
414 return $context->msg( 'prefs-memberingroups-type' )
415 ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
416 ->escaped();
417 },
418 'raw' => true,
419 'section' => 'personal/info',
420 ];
421
422 $contribTitle = SpecialPage::getTitleFor( "Contributions", $userName );
423 $formattedEditCount = $lang->formatNum( $user->getEditCount() );
424 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
425
426 $defaultPreferences['editcount'] = [
427 'type' => 'info',
428 'raw' => true,
429 'label-message' => 'prefs-edits',
430 'default' => $editCount,
431 'section' => 'personal/info',
432 ];
433
434 if ( $user->getRegistration() ) {
435 $displayUser = $context->getUser();
436 $userRegistration = $user->getRegistration();
437 $defaultPreferences['registrationdate'] = [
438 'type' => 'info',
439 'label-message' => 'prefs-registration',
440 'default' => $context->msg(
441 'prefs-registration-date-time',
442 $lang->userTimeAndDate( $userRegistration, $displayUser ),
443 $lang->userDate( $userRegistration, $displayUser ),
444 $lang->userTime( $userRegistration, $displayUser )
445 )->text(),
446 'section' => 'personal/info',
447 ];
448 }
449
450 $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
451 $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
452
453 // Actually changeable stuff
454 $defaultPreferences['realname'] = [
455 // (not really "private", but still shouldn't be edited without permission)
456 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'realname' )
457 ? 'text' : 'info',
458 'default' => $user->getRealName(),
459 'section' => 'personal/info',
460 'label-message' => 'yourrealname',
461 'help-message' => 'prefs-help-realname',
462 ];
463
464 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
465 new PasswordAuthenticationRequest(), false )->isGood()
466 ) {
467 $defaultPreferences['password'] = [
468 'type' => 'info',
469 'raw' => true,
470 'default' => (string)new \OOUI\ButtonWidget( [
471 'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
472 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
473 ] ),
474 'label' => $context->msg( 'prefs-resetpass' )->text(),
475 ] ),
476 'label-message' => 'yourpassword',
477 // email password reset feature only works for users that have an email set up
478 'help' => $this->options->get( 'AllowRequiringEmailForResets' ) && $user->getEmail()
479 ? $context->msg( 'prefs-help-yourpassword',
480 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
481 : '',
482 'section' => 'personal/info',
483 ];
484 }
485 // Only show prefershttps if secure login is turned on
486 if ( !$this->options->get( 'ForceHTTPS' )
487 && $this->options->get( 'SecureLogin' )
488 ) {
489 $defaultPreferences['prefershttps'] = [
490 'type' => 'toggle',
491 'label-message' => 'tog-prefershttps',
492 'help-message' => 'prefs-help-prefershttps',
493 'section' => 'personal/info'
494 ];
495 }
496
497 $defaultPreferences['downloaduserdata'] = [
498 'type' => 'info',
499 'raw' => true,
500 'label-message' => 'prefs-user-downloaddata-label',
501 'default' => HTML::Element(
502 'a',
503 [
504 'href' => $this->options->get( 'ScriptPath' ) .
505 '/api.php?action=query&meta=userinfo&uiprop=*',
506 ],
507 $context->msg( 'prefs-user-downloaddata-info' )->text()
508 ),
509 'help-message' => [ 'prefs-user-downloaddata-help-message', urlencode( $user->getTitleKey() ) ],
510 'section' => 'personal/info',
511 ];
512
513 $languages = $this->languageNameUtils->getLanguageNames( null, 'mwfile' );
514 $languageCode = $this->options->get( 'LanguageCode' );
515 if ( !array_key_exists( $languageCode, $languages ) ) {
516 $languages[$languageCode] = $languageCode;
517 // Sort the array again
518 ksort( $languages );
519 }
520
521 $options = [];
522 foreach ( $languages as $code => $name ) {
523 $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
524 $options[$display] = $code;
525 }
526 $defaultPreferences['language'] = [
527 'type' => 'select',
528 'section' => 'personal/i18n',
529 'options' => $options,
530 'label-message' => 'yourlanguage',
531 ];
532
533 $neutralGenderMessage = $context->msg( 'gender-notknown' )->escaped() . (
534 !$context->msg( 'gender-unknown' )->isDisabled()
535 ? "<br>" . $context->msg( 'parentheses' )
536 ->params( $context->msg( 'gender-unknown' )->plain() )
537 ->escaped()
538 : ''
539 );
540
541 $defaultPreferences['gender'] = [
542 'type' => 'radio',
543 'section' => 'personal/i18n',
544 'options' => [
545 $neutralGenderMessage => 'unknown',
546 $context->msg( 'gender-female' )->escaped() => 'female',
547 $context->msg( 'gender-male' )->escaped() => 'male',
548 ],
549 'label-message' => 'yourgender',
550 'help-message' => 'prefs-help-gender',
551 ];
552
553 // see if there are multiple language variants to choose from
554 if ( !$this->languageConverterFactory->isConversionDisabled() ) {
555
556 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
557 if ( $langCode == $this->contLang->getCode() ) {
558 if ( !$this->languageConverter->hasVariants() ) {
559 continue;
560 }
561
562 $variants = $this->languageConverter->getVariants();
563 $variantArray = [];
564 foreach ( $variants as $v ) {
565 $v = str_replace( '_', '-', strtolower( $v ) );
566 $variantArray[$v] = $lang->getVariantname( $v, false );
567 }
568
569 $options = [];
570 foreach ( $variantArray as $code => $name ) {
571 $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
572 $options[$display] = $code;
573 }
574
575 $defaultPreferences['variant'] = [
576 'label-message' => 'yourvariant',
577 'type' => 'select',
578 'options' => $options,
579 'section' => 'personal/i18n',
580 'help-message' => 'prefs-help-variant',
581 ];
582 } else {
583 $defaultPreferences["variant-$langCode"] = [
584 'type' => 'api',
585 ];
586 }
587 }
588 }
589
590 // show a preview of the old signature first
591 $oldsigWikiText = $this->parser->preSaveTransform(
592 '~~~',
593 $context->getTitle(),
594 $user,
595 ParserOptions::newFromContext( $context )
596 );
597 $oldsigHTML = Parser::stripOuterParagraph(
598 $context->getOutput()->parseAsContent( $oldsigWikiText )
599 );
600 $signatureFieldConfig = [];
601 // Validate existing signature and show a message about it
602 $signature = $this->userOptionsManager->getOption( $user, 'nickname' );
603 $useFancySig = $this->userOptionsManager->getBoolOption( $user, 'fancysig' );
604 if ( $useFancySig && $signature !== '' ) {
605 $validator = new SignatureValidator(
606 $user,
607 $context,
608 ParserOptions::newFromContext( $context )
609 );
610 $signatureErrors = $validator->validateSignature( $signature );
611 if ( $signatureErrors ) {
612 $sigValidation = $this->options->get( 'SignatureValidation' );
613 $oldsigHTML .= '<p><strong>' .
614 // Messages used here:
615 // * prefs-signature-invalid-warning
616 // * prefs-signature-invalid-new
617 // * prefs-signature-invalid-disallow
618 $context->msg( "prefs-signature-invalid-$sigValidation" )->parse() .
619 '</strong></p>';
620
621 // On initial page load, show the warnings as well
622 // (when posting, you get normal validation errors instead)
623 foreach ( $signatureErrors as &$sigError ) {
624 $sigError = new \OOUI\HtmlSnippet( $sigError );
625 }
626 if ( !$context->getRequest()->wasPosted() ) {
627 $signatureFieldConfig = [
628 'warnings' => $sigValidation !== 'disallow' ? $signatureErrors : null,
629 'errors' => $sigValidation === 'disallow' ? $signatureErrors : null,
630 ];
631 }
632 }
633 }
634 $defaultPreferences['oldsig'] = [
635 'type' => 'info',
636 // Normally HTMLFormFields do not display warnings, so we need to use 'rawrow'
637 // and provide the entire OOUI\FieldLayout here
638 'rawrow' => true,
639 'default' => new \OOUI\FieldLayout(
640 new \OOUI\LabelWidget( [
641 'label' => new \OOUI\HtmlSnippet( $oldsigHTML ),
642 ] ),
643 [
644 'align' => 'top',
645 'label' => new \OOUI\HtmlSnippet( $context->msg( 'tog-oldsig' )->parse() )
646 ] + $signatureFieldConfig
647 ),
648 'section' => 'personal/signature',
649 ];
650 $defaultPreferences['nickname'] = [
651 'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
652 'maxlength' => $this->options->get( 'MaxSigChars' ),
653 'label-message' => 'yournick',
654 'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
655 return $this->validateSignature( $signature, $alldata, $form );
656 },
657 'section' => 'personal/signature',
658 'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
659 return $this->cleanSignature( $signature, $alldata, $form );
660 },
661 ];
662 $defaultPreferences['fancysig'] = [
663 'type' => 'toggle',
664 'label-message' => 'tog-fancysig',
665 // show general help about signature at the bottom of the section
666 'help-message' => 'prefs-help-signature',
667 'section' => 'personal/signature'
668 ];
669
670 // Email preferences
671 if ( $this->options->get( 'EnableEmail' ) ) {
672 if ( $canViewPrivateInfo ) {
673 $helpMessages = [];
674 $helpMessages[] = $this->options->get( 'EmailConfirmToEdit' )
675 ? 'prefs-help-email-required'
676 : 'prefs-help-email';
677
678 if ( $this->options->get( 'EnableUserEmail' ) ) {
679 // additional messages when users can send email to each other
680 $helpMessages[] = 'prefs-help-email-others';
681 }
682
683 $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
684 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
685 $button = new \OOUI\ButtonWidget( [
686 'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
687 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
688 ] ),
689 'label' =>
690 $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
691 ] );
692
693 $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
694 }
695
696 $defaultPreferences['emailaddress'] = [
697 'type' => 'info',
698 'raw' => true,
699 'default' => $emailAddress,
700 'label-message' => 'youremail',
701 'section' => 'personal/email',
702 'help-messages' => $helpMessages,
703 // 'cssclass' chosen below
704 ];
705 }
706
707 $disableEmailPrefs = false;
708
709 if ( $this->options->get( 'AllowRequiringEmailForResets' ) ) {
710 $defaultPreferences['requireemail'] = [
711 'type' => 'toggle',
712 'label-message' => 'tog-requireemail',
713 'help-message' => 'prefs-help-requireemail',
714 'section' => 'personal/email',
715 'disabled' => $user->getEmail() ? false : true,
716 ];
717 }
718
719 if ( $this->options->get( 'EmailAuthentication' ) ) {
720 if ( $user->getEmail() ) {
721 if ( $user->getEmailAuthenticationTimestamp() ) {
722 // date and time are separate parameters to facilitate localisation.
723 // $time is kept for backward compat reasons.
724 // 'emailauthenticated' is also used in SpecialConfirmemail.php
725 $displayUser = $context->getUser();
726 $emailTimestamp = $user->getEmailAuthenticationTimestamp();
727 $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
728 $d = $lang->userDate( $emailTimestamp, $displayUser );
729 $t = $lang->userTime( $emailTimestamp, $displayUser );
730 $emailauthenticated = $context->msg( 'emailauthenticated',
731 $time, $d, $t )->parse() . '<br />';
732 $emailauthenticationclass = 'mw-email-authenticated';
733 } else {
734 $disableEmailPrefs = true;
735 $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
736 new \OOUI\ButtonWidget( [
737 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
738 'label' => $context->msg( 'emailconfirmlink' )->text(),
739 ] );
740 $emailauthenticationclass = "mw-email-not-authenticated";
741 }
742 } else {
743 $disableEmailPrefs = true;
744 $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
745 $emailauthenticationclass = 'mw-email-none';
746 }
747
748 if ( $canViewPrivateInfo ) {
749 $defaultPreferences['emailauthentication'] = [
750 'type' => 'info',
751 'raw' => true,
752 'section' => 'personal/email',
753 'label-message' => 'prefs-emailconfirm-label',
754 'default' => $emailauthenticated,
755 // Apply the same CSS class used on the input to the message:
756 'cssclass' => $emailauthenticationclass,
757 ];
758 }
759 }
760
761 if ( $this->options->get( 'EnableUserEmail' ) &&
762 $user->isAllowed( 'sendemail' )
763 ) {
764 $defaultPreferences['disablemail'] = [
765 'id' => 'wpAllowEmail',
766 'type' => 'toggle',
767 'invert' => true,
768 'section' => 'personal/email',
769 'label-message' => 'allowemail',
770 'disabled' => $disableEmailPrefs,
771 ];
772
773 $defaultPreferences['email-allow-new-users'] = [
774 'id' => 'wpAllowEmailFromNewUsers',
775 'type' => 'toggle',
776 'section' => 'personal/email',
777 'label-message' => 'email-allow-new-users-label',
778 'disabled' => $disableEmailPrefs,
779 ];
780
781 $defaultPreferences['ccmeonemails'] = [
782 'type' => 'toggle',
783 'section' => 'personal/email',
784 'label-message' => 'tog-ccmeonemails',
785 'disabled' => $disableEmailPrefs,
786 ];
787
788 if ( $this->options->get( 'EnableUserEmailMuteList' ) ) {
789 $defaultPreferences['email-blacklist'] = [
790 'type' => 'usersmultiselect',
791 'label-message' => 'email-mutelist-label',
792 'section' => 'personal/email',
793 'disabled' => $disableEmailPrefs,
794 'filter' => MultiUsernameFilter::class,
795 ];
796 }
797 }
798
799 if ( $this->options->get( 'EnotifWatchlist' ) ) {
800 $defaultPreferences['enotifwatchlistpages'] = [
801 'type' => 'toggle',
802 'section' => 'personal/email',
803 'label-message' => 'tog-enotifwatchlistpages',
804 'disabled' => $disableEmailPrefs,
805 ];
806 }
807 if ( $this->options->get( 'EnotifUserTalk' ) ) {
808 $defaultPreferences['enotifusertalkpages'] = [
809 'type' => 'toggle',
810 'section' => 'personal/email',
811 'label-message' => 'tog-enotifusertalkpages',
812 'disabled' => $disableEmailPrefs,
813 ];
814 }
815 if ( $this->options->get( 'EnotifUserTalk' ) ||
816 $this->options->get( 'EnotifWatchlist' ) ) {
817 if ( $this->options->get( 'EnotifMinorEdits' ) ) {
818 $defaultPreferences['enotifminoredits'] = [
819 'type' => 'toggle',
820 'section' => 'personal/email',
821 'label-message' => 'tog-enotifminoredits',
822 'disabled' => $disableEmailPrefs,
823 ];
824 }
825
826 if ( $this->options->get( 'EnotifRevealEditorAddress' ) ) {
827 $defaultPreferences['enotifrevealaddr'] = [
828 'type' => 'toggle',
829 'section' => 'personal/email',
830 'label-message' => 'tog-enotifrevealaddr',
831 'disabled' => $disableEmailPrefs,
832 ];
833 }
834 }
835 }
836 }
837
844 protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
845 // Skin selector, if there is at least one valid skin
846 $skinOptions = $this->generateSkinOptions( $user, $context );
847 if ( $skinOptions ) {
848 $defaultPreferences['skin'] = [
849 // @phan-suppress-next-line SecurityCheck-XSS False positive, key is escaped
850 'type' => 'radio',
851 'options' => $skinOptions,
852 'section' => 'rendering/skin',
853 ];
854 $defaultPreferences['skin-responsive'] = [
855 'type' => 'check',
856 'label-message' => 'prefs-skin-responsive',
857 'section' => 'rendering/skin/skin-prefs',
858 'help-message' => 'prefs-help-skin-responsive',
859 ];
860 }
861
862 $allowUserCss = $this->options->get( 'AllowUserCss' );
863 $allowUserJs = $this->options->get( 'AllowUserJs' );
864 // Create links to user CSS/JS pages for all skins.
865 // This code is basically copied from generateSkinOptions().
866 // @todo Refactor this and the similar code in generateSkinOptions().
867 if ( $allowUserCss || $allowUserJs ) {
868 $linkTools = [];
869 $userName = $user->getName();
870
871 if ( $allowUserCss ) {
872 $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
873 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
874 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
875 }
876
877 if ( $allowUserJs ) {
878 $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
879 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
880 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
881 }
882
883 $defaultPreferences['commoncssjs'] = [
884 'type' => 'info',
885 'raw' => true,
886 'default' => $context->getLanguage()->pipeList( $linkTools ),
887 'label-message' => 'prefs-common-config',
888 'section' => 'rendering/skin',
889 ];
890 }
891 }
892
897 protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
898 $defaultPreferences['imagesize'] = [
899 'type' => 'select',
900 'options' => $this->getImageSizes( $context ),
901 'label-message' => 'imagemaxsize',
902 'section' => 'rendering/files',
903 ];
904 $defaultPreferences['thumbsize'] = [
905 'type' => 'select',
906 'options' => $this->getThumbSizes( $context ),
907 'label-message' => 'thumbsize',
908 'section' => 'rendering/files',
909 ];
910 }
911
918 protected function datetimePreferences(
919 User $user, IContextSource $context, &$defaultPreferences
920 ) {
921 $dateOptions = $this->getDateOptions( $context );
922 if ( $dateOptions ) {
923 $defaultPreferences['date'] = [
924 'type' => 'radio',
925 'options' => $dateOptions,
926 'section' => 'rendering/dateformat',
927 ];
928 }
929
930 // Info
931 $now = wfTimestampNow();
932 $lang = $context->getLanguage();
933 $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ],
934 $lang->userTime( $now, $user ) );
935 $nowserver = $lang->userTime( $now, $user,
936 [ 'format' => false, 'timecorrection' => false ] ) .
937 Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
938
939 $defaultPreferences['nowserver'] = [
940 'type' => 'info',
941 'raw' => 1,
942 'label-message' => 'servertime',
943 'default' => $nowserver,
944 'section' => 'rendering/timeoffset',
945 ];
946
947 $defaultPreferences['nowlocal'] = [
948 'type' => 'info',
949 'raw' => 1,
950 'label-message' => 'localtime',
951 'default' => $nowlocal,
952 'section' => 'rendering/timeoffset',
953 ];
954
955 // Grab existing pref.
956 $tzOffset = $this->userOptionsManager->getOption( $user, 'timecorrection' );
957 $tz = explode( '|', $tzOffset, 3 );
958
959 $tzOptions = $this->getTimezoneOptions( $context );
960
961 $tzSetting = $tzOffset;
962 if ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
963 !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
964 ) {
965 // Timezone offset can vary with DST
966 try {
967 $userTZ = new DateTimeZone( $tz[2] );
968 $minDiff = floor( $userTZ->getOffset( new DateTime( 'now' ) ) / 60 );
969 $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
970 } catch ( Exception $e ) {
971 // User has an invalid time zone set. Fall back to just using the offset
972 $tz[0] = 'Offset';
973 }
974 }
975 if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
976 $minDiff = $tz[1];
977 $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
978 }
979
980 $defaultPreferences['timecorrection'] = [
981 'class' => \HTMLSelectOrOtherField::class,
982 'label-message' => 'timezonelegend',
983 'options' => $tzOptions,
984 'default' => $tzSetting,
985 'size' => 20,
986 'section' => 'rendering/timeoffset',
987 'id' => 'wpTimeCorrection',
988 'filter' => TimezoneFilter::class,
989 'placeholder-message' => 'timezone-useoffset-placeholder',
990 ];
991 }
992
998 protected function renderingPreferences(
999 User $user,
1000 MessageLocalizer $l10n,
1001 &$defaultPreferences
1002 ) {
1003 // Diffs
1004 $defaultPreferences['diffonly'] = [
1005 'type' => 'toggle',
1006 'section' => 'rendering/diffs',
1007 'label-message' => 'tog-diffonly',
1008 ];
1009 $defaultPreferences['norollbackdiff'] = [
1010 'type' => 'toggle',
1011 'section' => 'rendering/diffs',
1012 'label-message' => 'tog-norollbackdiff',
1013 ];
1014
1015 // Page Rendering
1016 if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
1017 $defaultPreferences['underline'] = [
1018 'type' => 'select',
1019 'options' => [
1020 $l10n->msg( 'underline-never' )->text() => 0,
1021 $l10n->msg( 'underline-always' )->text() => 1,
1022 $l10n->msg( 'underline-default' )->text() => 2,
1023 ],
1024 'label-message' => 'tog-underline',
1025 'section' => 'rendering/advancedrendering',
1026 ];
1027 }
1028
1029 $defaultPreferences['showhiddencats'] = [
1030 'type' => 'toggle',
1031 'section' => 'rendering/advancedrendering',
1032 'label-message' => 'tog-showhiddencats'
1033 ];
1034
1035 $defaultPreferences['numberheadings'] = [
1036 'type' => 'toggle',
1037 'section' => 'rendering/advancedrendering',
1038 'label-message' => 'tog-numberheadings',
1039 ];
1040
1041 if ( $user->isAllowed( 'rollback' ) ) {
1042 $defaultPreferences['showrollbackconfirmation'] = [
1043 'type' => 'toggle',
1044 'section' => 'rendering/advancedrendering',
1045 'label-message' => 'tog-showrollbackconfirmation',
1046 ];
1047 }
1048 }
1049
1055 protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1056 $defaultPreferences['editsectiononrightclick'] = [
1057 'type' => 'toggle',
1058 'section' => 'editing/advancedediting',
1059 'label-message' => 'tog-editsectiononrightclick',
1060 ];
1061 $defaultPreferences['editondblclick'] = [
1062 'type' => 'toggle',
1063 'section' => 'editing/advancedediting',
1064 'label-message' => 'tog-editondblclick',
1065 ];
1066
1067 if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
1068 $defaultPreferences['editfont'] = [
1069 'type' => 'select',
1070 'section' => 'editing/editor',
1071 'label-message' => 'editfont-style',
1072 'options' => [
1073 $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
1074 $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
1075 $l10n->msg( 'editfont-serif' )->text() => 'serif',
1076 ]
1077 ];
1078 }
1079
1080 if ( $user->isAllowed( 'minoredit' ) ) {
1081 $defaultPreferences['minordefault'] = [
1082 'type' => 'toggle',
1083 'section' => 'editing/editor',
1084 'label-message' => 'tog-minordefault',
1085 ];
1086 }
1087
1088 $defaultPreferences['forceeditsummary'] = [
1089 'type' => 'toggle',
1090 'section' => 'editing/editor',
1091 'label-message' => 'tog-forceeditsummary',
1092 ];
1093 $defaultPreferences['useeditwarning'] = [
1094 'type' => 'toggle',
1095 'section' => 'editing/editor',
1096 'label-message' => 'tog-useeditwarning',
1097 ];
1098
1099 $defaultPreferences['previewonfirst'] = [
1100 'type' => 'toggle',
1101 'section' => 'editing/preview',
1102 'label-message' => 'tog-previewonfirst',
1103 ];
1104 $defaultPreferences['previewontop'] = [
1105 'type' => 'toggle',
1106 'section' => 'editing/preview',
1107 'label-message' => 'tog-previewontop',
1108 ];
1109 $defaultPreferences['uselivepreview'] = [
1110 'type' => 'toggle',
1111 'section' => 'editing/preview',
1112 'label-message' => 'tog-uselivepreview',
1113 ];
1114 }
1115
1121 protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1122 $rcMaxAge = $this->options->get( 'RCMaxAge' );
1123 $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1124 $defaultPreferences['rcdays'] = [
1125 'type' => 'float',
1126 'label-message' => 'recentchangesdays',
1127 'section' => 'rc/displayrc',
1128 'min' => 1 / 24,
1129 'max' => $rcMax,
1130 'help-message' => [ 'recentchangesdays-max', Message::numParam( $rcMax ) ],
1131 ];
1132 $defaultPreferences['rclimit'] = [
1133 'type' => 'int',
1134 'min' => 1,
1135 'max' => 1000,
1136 'label-message' => 'recentchangescount',
1137 'help-message' => 'prefs-help-recentchangescount',
1138 'section' => 'rc/displayrc',
1139 'filter' => IntvalFilter::class,
1140 ];
1141 $defaultPreferences['usenewrc'] = [
1142 'type' => 'toggle',
1143 'label-message' => 'tog-usenewrc',
1144 'section' => 'rc/advancedrc',
1145 ];
1146 $defaultPreferences['hideminor'] = [
1147 'type' => 'toggle',
1148 'label-message' => 'tog-hideminor',
1149 'section' => 'rc/changesrc',
1150 ];
1151 $defaultPreferences['pst-cssjs'] = [
1152 'type' => 'api',
1153 ];
1154 $defaultPreferences['rcfilters-rc-collapsed'] = [
1155 'type' => 'api',
1156 ];
1157 $defaultPreferences['rcfilters-wl-collapsed'] = [
1158 'type' => 'api',
1159 ];
1160 $defaultPreferences['rcfilters-saved-queries'] = [
1161 'type' => 'api',
1162 ];
1163 $defaultPreferences['rcfilters-wl-saved-queries'] = [
1164 'type' => 'api',
1165 ];
1166 // Override RCFilters preferences for RecentChanges 'limit'
1167 $defaultPreferences['rcfilters-limit'] = [
1168 'type' => 'api',
1169 ];
1170 $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
1171 'type' => 'api',
1172 ];
1173 $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
1174 'type' => 'api',
1175 ];
1176
1177 if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
1178 $defaultPreferences['hidecategorization'] = [
1179 'type' => 'toggle',
1180 'label-message' => 'tog-hidecategorization',
1181 'section' => 'rc/changesrc',
1182 ];
1183 }
1184
1185 if ( $user->useRCPatrol() ) {
1186 $defaultPreferences['hidepatrolled'] = [
1187 'type' => 'toggle',
1188 'section' => 'rc/changesrc',
1189 'label-message' => 'tog-hidepatrolled',
1190 ];
1191 }
1192
1193 if ( $user->useNPPatrol() ) {
1194 $defaultPreferences['newpageshidepatrolled'] = [
1195 'type' => 'toggle',
1196 'section' => 'rc/changesrc',
1197 'label-message' => 'tog-newpageshidepatrolled',
1198 ];
1199 }
1200
1201 if ( $this->options->get( 'RCShowWatchingUsers' ) ) {
1202 $defaultPreferences['shownumberswatching'] = [
1203 'type' => 'toggle',
1204 'section' => 'rc/advancedrc',
1205 'label-message' => 'tog-shownumberswatching',
1206 ];
1207 }
1208
1209 $defaultPreferences['rcenhancedfilters-disable'] = [
1210 'type' => 'toggle',
1211 'section' => 'rc/advancedrc',
1212 'label-message' => 'rcfilters-preference-label',
1213 'help-message' => 'rcfilters-preference-help',
1214 ];
1215 }
1216
1222 protected function watchlistPreferences(
1223 User $user, IContextSource $context, &$defaultPreferences
1224 ) {
1225 $watchlistdaysMax = ceil( $this->options->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
1226
1227 if ( $user->isAllowed( 'editmywatchlist' ) ) {
1228 $editWatchlistLinks = '';
1229 $editWatchlistModes = [
1230 'edit' => [ 'subpage' => false, 'flags' => [] ],
1231 'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
1232 'clear' => [ 'subpage' => 'clear', 'flags' => [ 'destructive' ] ],
1233 ];
1234 foreach ( $editWatchlistModes as $mode => $options ) {
1235 // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1236 $editWatchlistLinks .=
1237 new \OOUI\ButtonWidget( [
1238 'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
1239 'flags' => $options[ 'flags' ],
1240 'label' => new \OOUI\HtmlSnippet(
1241 $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
1242 ),
1243 ] );
1244 }
1245
1246 $defaultPreferences['editwatchlist'] = [
1247 'type' => 'info',
1248 'raw' => true,
1249 'default' => $editWatchlistLinks,
1250 'label-message' => 'prefs-editwatchlist-label',
1251 'section' => 'watchlist/editwatchlist',
1252 ];
1253 }
1254
1255 $defaultPreferences['watchlistdays'] = [
1256 'type' => 'float',
1257 'min' => 1 / 24,
1258 'max' => $watchlistdaysMax,
1259 'section' => 'watchlist/displaywatchlist',
1260 'help-message' => [ 'prefs-watchlist-days-max', Message::numParam( $watchlistdaysMax ) ],
1261 'label-message' => 'prefs-watchlist-days',
1262 ];
1263 $defaultPreferences['wllimit'] = [
1264 'type' => 'int',
1265 'min' => 1,
1266 'max' => 1000,
1267 'label-message' => 'prefs-watchlist-edits',
1268 'help-message' => 'prefs-watchlist-edits-max',
1269 'section' => 'watchlist/displaywatchlist',
1270 'filter' => IntvalFilter::class,
1271 ];
1272 $defaultPreferences['extendwatchlist'] = [
1273 'type' => 'toggle',
1274 'section' => 'watchlist/advancedwatchlist',
1275 'label-message' => 'tog-extendwatchlist',
1276 ];
1277 $defaultPreferences['watchlisthideminor'] = [
1278 'type' => 'toggle',
1279 'section' => 'watchlist/changeswatchlist',
1280 'label-message' => 'tog-watchlisthideminor',
1281 ];
1282 $defaultPreferences['watchlisthidebots'] = [
1283 'type' => 'toggle',
1284 'section' => 'watchlist/changeswatchlist',
1285 'label-message' => 'tog-watchlisthidebots',
1286 ];
1287 $defaultPreferences['watchlisthideown'] = [
1288 'type' => 'toggle',
1289 'section' => 'watchlist/changeswatchlist',
1290 'label-message' => 'tog-watchlisthideown',
1291 ];
1292 $defaultPreferences['watchlisthideanons'] = [
1293 'type' => 'toggle',
1294 'section' => 'watchlist/changeswatchlist',
1295 'label-message' => 'tog-watchlisthideanons',
1296 ];
1297 $defaultPreferences['watchlisthideliu'] = [
1298 'type' => 'toggle',
1299 'section' => 'watchlist/changeswatchlist',
1300 'label-message' => 'tog-watchlisthideliu',
1301 ];
1302
1304 $defaultPreferences['watchlistreloadautomatically'] = [
1305 'type' => 'toggle',
1306 'section' => 'watchlist/advancedwatchlist',
1307 'label-message' => 'tog-watchlistreloadautomatically',
1308 ];
1309 }
1310
1311 $defaultPreferences['watchlistunwatchlinks'] = [
1312 'type' => 'toggle',
1313 'section' => 'watchlist/advancedwatchlist',
1314 'label-message' => 'tog-watchlistunwatchlinks',
1315 ];
1316
1317 if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
1318 $defaultPreferences['watchlisthidecategorization'] = [
1319 'type' => 'toggle',
1320 'section' => 'watchlist/changeswatchlist',
1321 'label-message' => 'tog-watchlisthidecategorization',
1322 ];
1323 }
1324
1325 if ( $user->useRCPatrol() ) {
1326 $defaultPreferences['watchlisthidepatrolled'] = [
1327 'type' => 'toggle',
1328 'section' => 'watchlist/changeswatchlist',
1329 'label-message' => 'tog-watchlisthidepatrolled',
1330 ];
1331 }
1332
1333 $watchTypes = [
1334 'edit' => 'watchdefault',
1335 'move' => 'watchmoves',
1336 'delete' => 'watchdeletion'
1337 ];
1338
1339 // Kinda hacky
1340 if ( $user->isAllowedAny( 'createpage', 'createtalk' ) ) {
1341 $watchTypes['read'] = 'watchcreations';
1342 }
1343
1344 if ( $user->isAllowed( 'rollback' ) ) {
1345 $watchTypes['rollback'] = 'watchrollback';
1346 }
1347
1348 if ( $user->isAllowed( 'upload' ) ) {
1349 $watchTypes['upload'] = 'watchuploads';
1350 }
1351
1352 foreach ( $watchTypes as $action => $pref ) {
1353 if ( $user->isAllowed( $action ) ) {
1354 // Messages:
1355 // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1356 // tog-watchrollback
1357 $defaultPreferences[$pref] = [
1358 'type' => 'toggle',
1359 'section' => 'watchlist/pageswatchlist',
1360 'label-message' => "tog-$pref",
1361 ];
1362 }
1363 }
1364
1365 $defaultPreferences['watchlisttoken'] = [
1366 'type' => 'api',
1367 ];
1368
1369 $tokenButton = new \OOUI\ButtonWidget( [
1370 'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1371 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1372 ] ),
1373 'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1374 ] );
1375 $defaultPreferences['watchlisttoken-info'] = [
1376 'type' => 'info',
1377 'section' => 'watchlist/tokenwatchlist',
1378 'label-message' => 'prefs-watchlist-token',
1379 'help-message' => 'prefs-help-tokenmanagement',
1380 'raw' => true,
1381 'default' => (string)$tokenButton,
1382 ];
1383
1384 $defaultPreferences['wlenhancedfilters-disable'] = [
1385 'type' => 'toggle',
1386 'section' => 'watchlist/advancedwatchlist',
1387 'label-message' => 'rcfilters-watchlist-preference-label',
1388 'help-message' => 'rcfilters-watchlist-preference-help',
1389 ];
1390 }
1391
1395 protected function searchPreferences( &$defaultPreferences ) {
1396 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1397 $defaultPreferences['searchNs' . $n] = [
1398 'type' => 'api',
1399 ];
1400 }
1401
1402 if ( $this->options->get( 'SearchMatchRedirectPreference' ) ) {
1403 $defaultPreferences['search-match-redirect'] = [
1404 'type' => 'toggle',
1405 'section' => 'searchoptions/searchmisc',
1406 'label-message' => 'search-match-redirect-label',
1407 'help-message' => 'search-match-redirect-help',
1408 ];
1409 } else {
1410 $defaultPreferences['search-match-redirect'] = [
1411 'type' => 'api',
1412 ];
1413 }
1414 }
1415
1421 protected function generateSkinOptions( User $user, IContextSource $context ) {
1422 $ret = [];
1423
1424 $mptitle = Title::newMainPage();
1425 $previewtext = $context->msg( 'skin-preview' )->escaped();
1426
1427 // Only show skins that aren't disabled
1428 $validSkinNames = $this->skinFactory->getAllowedSkins();
1429 $allInstalledSkins = $this->skinFactory->getSkinNames();
1430
1431 // Display the installed skin the user has specifically requested via useskin=….
1432 $useSkin = $context->getRequest()->getRawVal( 'useskin' );
1433 if ( isset( $allInstalledSkins[$useSkin] )
1434 && $context->msg( "skinname-$useSkin" )->exists()
1435 ) {
1436 $validSkinNames[$useSkin] = $useSkin;
1437 }
1438
1439 // Display the skin if the user has set it as a preference already before it was hidden.
1440 $currentUserSkin = $this->userOptionsManager->getOption( $user, 'skin' );
1441 if ( isset( $allInstalledSkins[$currentUserSkin] )
1442 && $context->msg( "skinname-$currentUserSkin" )->exists()
1443 ) {
1444 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1445 }
1446
1447 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1448 $msg = $context->msg( "skinname-{$skinkey}" );
1449 if ( $msg->exists() ) {
1450 $skinname = htmlspecialchars( $msg->text() );
1451 }
1452 }
1453
1454 $defaultSkin = $this->options->get( 'DefaultSkin' );
1455 $allowUserCss = $this->options->get( 'AllowUserCss' );
1456 $allowUserJs = $this->options->get( 'AllowUserJs' );
1457
1458 // Sort by the internal name, so that the ordering is the same for each display language,
1459 // especially if some skin names are translated to use a different alphabet and some are not.
1460 uksort( $validSkinNames, static function ( $a, $b ) use ( $defaultSkin ) {
1461 // Display the default first in the list by comparing it as lesser than any other.
1462 if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1463 return -1;
1464 }
1465 if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1466 return 1;
1467 }
1468 return strcasecmp( $a, $b );
1469 } );
1470
1471 $foundDefault = false;
1472 foreach ( $validSkinNames as $skinkey => $sn ) {
1473 $linkTools = [];
1474
1475 // Mark the default skin
1476 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1477 $linkTools[] = $context->msg( 'default' )->escaped();
1478 $foundDefault = true;
1479 }
1480
1481 // Create preview link
1482 $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1483 $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1484
1485 // Create links to user CSS/JS pages
1486 // @todo Refactor this and the similar code in skinPreferences().
1487 if ( $allowUserCss ) {
1488 $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1489 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1490 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1491 }
1492
1493 if ( $allowUserJs ) {
1494 $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1495 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1496 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1497 }
1498
1499 $display = $sn . ' ' . $context->msg( 'parentheses' )
1500 ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1501 ->escaped();
1502 $ret[$display] = $skinkey;
1503 }
1504
1505 if ( !$foundDefault ) {
1506 // If the default skin is not available, things are going to break horribly because the
1507 // default value for skin selector will not be a valid value. Let's just not show it then.
1508 return [];
1509 }
1510
1511 return $ret;
1512 }
1513
1518 protected function getDateOptions( IContextSource $context ) {
1519 $lang = $context->getLanguage();
1520 $dateopts = $lang->getDatePreferences();
1521
1522 $ret = [];
1523
1524 if ( $dateopts ) {
1525 if ( !in_array( 'default', $dateopts ) ) {
1526 $dateopts[] = 'default'; // Make sure default is always valid T21237
1527 }
1528
1529 // FIXME KLUGE: site default might not be valid for user language
1530 global $wgDefaultUserOptions;
1531 if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1532 $wgDefaultUserOptions['date'] = 'default';
1533 }
1534
1535 $epoch = wfTimestampNow();
1536 foreach ( $dateopts as $key ) {
1537 if ( $key == 'default' ) {
1538 $formatted = $context->msg( 'datedefault' )->escaped();
1539 } else {
1540 $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1541 }
1542 $ret[$formatted] = $key;
1543 }
1544 }
1545 return $ret;
1546 }
1547
1552 protected function getImageSizes( MessageLocalizer $l10n ) {
1553 $ret = [];
1554 $pixels = $l10n->msg( 'unit-pixel' )->text();
1555
1556 foreach ( $this->options->get( 'ImageLimits' ) as $index => $limits ) {
1557 // Note: A left-to-right marker (U+200E) is inserted, see T144386
1558 $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1559 $ret[$display] = $index;
1560 }
1561
1562 return $ret;
1563 }
1564
1569 protected function getThumbSizes( MessageLocalizer $l10n ) {
1570 $ret = [];
1571 $pixels = $l10n->msg( 'unit-pixel' )->text();
1572
1573 foreach ( $this->options->get( 'ThumbLimits' ) as $index => $size ) {
1574 $display = $size . $pixels;
1575 $ret[$display] = $index;
1576 }
1577
1578 return $ret;
1579 }
1580
1587 protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1588 $sigValidation = $this->options->get( 'SignatureValidation' );
1589 $maxSigChars = $this->options->get( 'MaxSigChars' );
1590 if ( is_string( $signature ) && mb_strlen( $signature ) > $maxSigChars ) {
1591 return $form->msg( 'badsiglength' )->numParams( $maxSigChars )->escaped();
1592 }
1593
1594 if ( $signature === null || $signature === '' ) {
1595 // Make sure leaving the field empty is valid, since that's used as the default (T288151).
1596 // Code using this preference in Parser::getUserSig() handles this case specially.
1597 return true;
1598 }
1599
1600 // Remaining checks only apply to fancy signatures
1601 if ( !( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) ) {
1602 return true;
1603 }
1604
1605 // HERE BE DRAGONS:
1606 //
1607 // If this value is already saved as the user's signature, treat it as valid, even if it
1608 // would be invalid to save now, and even if $wgSignatureValidation is set to 'disallow'.
1609 //
1610 // It can become invalid when we introduce new validation, or when the value just transcludes
1611 // some page containing the real signature and that page is edited (which we can't validate),
1612 // or when someone's username is changed.
1613 //
1614 // Otherwise it would be completely removed when the user opens their preferences page, which
1615 // would be very unfriendly.
1616 $user = $form->getUser();
1617 if (
1618 $signature === $this->userOptionsManager->getOption( $user, 'nickname' ) &&
1619 (bool)$alldata['fancysig'] === $this->userOptionsManager->getBoolOption( $user, 'fancysig' )
1620 ) {
1621 return true;
1622 }
1623
1624 if ( $sigValidation === 'new' || $sigValidation === 'disallow' ) {
1625 // Validate everything
1626 $validator = new SignatureValidator(
1627 $user,
1628 $form->getContext(),
1629 ParserOptions::newFromContext( $form->getContext() )
1630 );
1631 $errors = $validator->validateSignature( $signature );
1632 if ( $errors ) {
1633 return $errors;
1634 }
1635 }
1636
1637 // Quick check for mismatched HTML tags in the input.
1638 // Note that this is easily fooled by wikitext templates or bold/italic markup.
1639 // We're only keeping this until Parsoid is integrated and guaranteed to be available.
1640 if ( $this->parser->validateSig( $signature ) === false ) {
1641 return $form->msg( 'badsig' )->escaped();
1642 }
1643
1644 return true;
1645 }
1646
1653 protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
1654 if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1655 $signature = $this->parser->cleanSig( $signature );
1656 } else {
1657 // When no fancy sig used, make sure ~{3,5} get removed.
1658 $signature = Parser::cleanSigInSig( $signature );
1659 }
1660
1661 return $signature;
1662 }
1663
1671 public function getForm(
1672 User $user,
1673 IContextSource $context,
1674 $formClass = PreferencesFormOOUI::class,
1675 array $remove = []
1676 ) {
1677 // We use ButtonWidgets in some of the getPreferences() functions
1678 $context->getOutput()->enableOOUI();
1679
1680 // Note that the $user parameter of getFormDescriptor() is deprecated.
1681 $formDescriptor = $this->getFormDescriptor( $user, $context );
1682 if ( count( $remove ) ) {
1683 $removeKeys = array_fill_keys( $remove, true );
1684 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1685 }
1686
1687 // Remove type=api preferences. They are not intended for rendering in the form.
1688 foreach ( $formDescriptor as $name => $info ) {
1689 if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1690 unset( $formDescriptor[$name] );
1691 }
1692 }
1693
1697 $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1698
1699 // This allows users to opt-in to hidden skins. While this should be discouraged and is not
1700 // discoverable, this allows users to still use hidden skins while preventing new users from
1701 // adopting unsupported skins. If no useskin=… parameter was provided, it will not show up
1702 // in the resulting URL.
1703 $htmlForm->setAction( $context->getTitle()->getLocalURL( [
1704 'useskin' => $context->getRequest()->getRawVal( 'useskin' )
1705 ] ) );
1706
1707 $htmlForm->setModifiedUser( $user );
1708 $htmlForm->setOptionsEditable( $user->isAllowed( 'editmyoptions' ) );
1709 $htmlForm->setPrivateInfoEditable( $user->isAllowed( 'editmyprivateinfo' ) );
1710 $htmlForm->setId( 'mw-prefs-form' );
1711 $htmlForm->setAutocomplete( 'off' );
1712 $htmlForm->setSubmitTextMsg( 'saveprefs' );
1713 // Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1714 $htmlForm->setSubmitTooltip( 'preferences-save' );
1715 $htmlForm->setSubmitID( 'prefcontrol' );
1716 $htmlForm->setSubmitCallback(
1717 function ( array $formData, PreferencesFormOOUI $form ) use ( $formDescriptor ) {
1718 return $this->submitForm( $formData, $form, $formDescriptor );
1719 }
1720 );
1721
1722 return $htmlForm;
1723 }
1724
1729 protected function getTimezoneOptions( IContextSource $context ) {
1730 $opt = [];
1731
1732 $localTZoffset = $this->options->get( 'LocalTZoffset' );
1733 $timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
1734
1735 $timestamp = MWTimestamp::getLocalInstance();
1736 // Check that the LocalTZoffset is the same as the local time zone offset
1737 if ( $localTZoffset === $timestamp->format( 'Z' ) / 60 ) {
1738 $timezoneName = $timestamp->getTimezone()->getName();
1739 // Localize timezone
1740 if ( isset( $timeZoneList[$timezoneName] ) ) {
1741 $timezoneName = $timeZoneList[$timezoneName]['name'];
1742 }
1743 $server_tz_msg = $context->msg(
1744 'timezoneuseserverdefault',
1745 $timezoneName
1746 )->text();
1747 } else {
1748 $tzstring = sprintf(
1749 '%+03d:%02d',
1750 floor( $localTZoffset / 60 ),
1751 abs( $localTZoffset ) % 60
1752 );
1753 $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
1754 }
1755 $opt[$server_tz_msg] = "System|$localTZoffset";
1756 $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
1757 $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
1758
1759 foreach ( $timeZoneList as $timeZoneInfo ) {
1760 $region = $timeZoneInfo['region'];
1761 if ( !isset( $opt[$region] ) ) {
1762 $opt[$region] = [];
1763 }
1764 $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
1765 }
1766 return $opt;
1767 }
1768
1777 protected function saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor ) {
1778 $user = $form->getModifiedUser();
1779 $hiddenPrefs = $this->options->get( 'HiddenPrefs' );
1780 $result = true;
1781
1782 if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' )
1783 ) {
1784 return Status::newFatal( 'mypreferencesprotected' );
1785 }
1786
1787 // Filter input
1788 $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
1789
1790 // Fortunately, the realname field is MUCH simpler
1791 // (not really "private", but still shouldn't be edited without permission)
1792
1793 if ( !in_array( 'realname', $hiddenPrefs )
1794 && $user->isAllowed( 'editmyprivateinfo' )
1795 && array_key_exists( 'realname', $formData )
1796 ) {
1797 $realName = $formData['realname'];
1798 $user->setRealName( $realName );
1799 }
1800
1801 if ( $user->isAllowed( 'editmyoptions' ) ) {
1802 $oldUserOptions = $this->userOptionsManager->getOptions( $user );
1803
1804 foreach ( $this->getSaveBlacklist() as $b ) {
1805 unset( $formData[$b] );
1806 }
1807
1808 // If users have saved a value for a preference which has subsequently been disabled
1809 // via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1810 // is subsequently re-enabled
1811 foreach ( $hiddenPrefs as $pref ) {
1812 // If the user has not set a non-default value here, the default will be returned
1813 // and subsequently discarded
1814 $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref, null, true );
1815 }
1816
1817 // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
1818 if (
1819 isset( $formData['rclimit'] ) &&
1820 intval( $formData[ 'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user, 'rclimit' )
1821 ) {
1822 $formData['rcfilters-limit'] = $formData['rclimit'];
1823 }
1824
1825 // Keep old preferences from interfering due to back-compat code, etc.
1826 $this->userOptionsManager->resetOptions( $user, $form->getContext(), 'unused' );
1827
1828 foreach ( $formData as $key => $value ) {
1829 $this->userOptionsManager->setOption( $user, $key, $value );
1830 }
1831
1832 $this->hookRunner->onPreferencesFormPreSave(
1833 $formData, $form, $user, $result, $oldUserOptions );
1834 }
1835
1836 $user->saveSettings();
1837
1838 return $result;
1839 }
1840
1849 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1850 foreach ( $formDescriptor as $preference => $desc ) {
1851 if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
1852 continue;
1853 }
1854 $filterDesc = $desc['filter'];
1855 if ( $filterDesc instanceof Filter ) {
1856 $filter = $filterDesc;
1857 } elseif ( class_exists( $filterDesc ) ) {
1858 $filter = new $filterDesc();
1859 } elseif ( is_callable( $filterDesc ) ) {
1860 $filter = $filterDesc();
1861 } else {
1862 throw new UnexpectedValueException(
1863 "Unrecognized filter type for preference '$preference'"
1864 );
1865 }
1866 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1867 }
1868 }
1869
1878 protected function submitForm(
1879 array $formData,
1880 PreferencesFormOOUI $form,
1881 array $formDescriptor
1882 ) {
1883 $res = $this->saveFormData( $formData, $form, $formDescriptor );
1884
1885 if ( $res === true ) {
1886 $context = $form->getContext();
1887 $urlOptions = [];
1888
1889 $urlOptions += $form->getExtraSuccessRedirectParameters();
1890
1891 $url = $form->getTitle()->getFullURL( $urlOptions );
1892
1893 // Set session data for the success message
1894 $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
1895
1896 $context->getOutput()->redirect( $url );
1897 }
1898
1899 return ( $res === true ? Status::newGood() : $res );
1900 }
1901
1910 protected function getTimeZoneList( Language $language ) {
1911 $identifiers = DateTimeZone::listIdentifiers();
1912 // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3162
1913 if ( $identifiers === false ) {
1914 return [];
1915 }
1916 sort( $identifiers );
1917
1918 $tzRegions = [
1919 'Africa' => wfMessage( 'timezoneregion-africa' )->inLanguage( $language )->text(),
1920 'America' => wfMessage( 'timezoneregion-america' )->inLanguage( $language )->text(),
1921 'Antarctica' => wfMessage( 'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1922 'Arctic' => wfMessage( 'timezoneregion-arctic' )->inLanguage( $language )->text(),
1923 'Asia' => wfMessage( 'timezoneregion-asia' )->inLanguage( $language )->text(),
1924 'Atlantic' => wfMessage( 'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1925 'Australia' => wfMessage( 'timezoneregion-australia' )->inLanguage( $language )->text(),
1926 'Europe' => wfMessage( 'timezoneregion-europe' )->inLanguage( $language )->text(),
1927 'Indian' => wfMessage( 'timezoneregion-indian' )->inLanguage( $language )->text(),
1928 'Pacific' => wfMessage( 'timezoneregion-pacific' )->inLanguage( $language )->text(),
1929 ];
1930 asort( $tzRegions );
1931
1932 $timeZoneList = [];
1933
1934 $now = new DateTime();
1935
1936 foreach ( $identifiers as $identifier ) {
1937 $parts = explode( '/', $identifier, 2 );
1938
1939 // DateTimeZone::listIdentifiers() returns a number of
1940 // backwards-compatibility entries. This filters them out of the
1941 // list presented to the user.
1942 if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1943 continue;
1944 }
1945
1946 // Localize region
1947 $parts[0] = $tzRegions[$parts[0]];
1948
1949 $dateTimeZone = new DateTimeZone( $identifier );
1950 $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1951
1952 $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
1953 $value = "ZoneInfo|$minDiff|$identifier";
1954
1955 $timeZoneList[$identifier] = [
1956 'name' => $display,
1957 'timecorrection' => $value,
1958 'region' => $parts[0],
1959 ];
1960 }
1961
1962 return $timeZoneList;
1963 }
1964}
UserOptionsLookup $userOptionsLookup
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
const NS_USER
Definition Defines.php:66
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
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:143
This class is a collection of static functions that serve two purposes:
Definition Html.php:49
Methods for dealing with language codes.
Base class for multi-variant language conversion.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:42
MediaWiki exception.
Library for creating and parsing MW-style timestamps.
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...
An interface for creating language converters.
A service that provides utilities to do with language names and codes.
Class that generates HTML links for pages.
MediaWikiServices is the service locator for the application scope of MediaWiki.
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)
getTimeZoneList(Language $language)
Get a list of all time zones.
rcPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
watchlistPreferences(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)
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)
loadPreferenceValues(User $user, IContextSource $context, &$defaultPreferences)
Loads existing values for a given array of preferences.
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.
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.
Provides access to user options.
A service class to control user options.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:138
static numParam( $num)
Definition Message.php:1101
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:91
static stripOuterParagraph( $html)
Strip outer.
Definition Parser.php:6336
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition Parser.php:4775
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( $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:44
Represents a title within MediaWiki.
Definition Title.php:48
Represents a "user group membership" – a specific instance of a user belonging to a group.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:69
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2116
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:2446
getRealName()
Get the user's real name.
Definition User.php:2527
getRegistration()
Get the timestamp of account creation.
Definition User.php:4012
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.
Definition User.php:3025
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:3050
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:3041
getEditCount()
Get the user's edit count.
Definition User.php:2918
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:2208
getEmail()
Get the user's e-mail address.
Definition User.php:2433
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Definition User.php:3033
Module of static functions for generating XML.
Definition Xml.php:28
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition Xml.php:41
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