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