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