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