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(
516  );
517  $languageCode = $this->options->get( 'LanguageCode' );
518  if ( !array_key_exists( $languageCode, $languages ) ) {
519  $languages[$languageCode] = $languageCode;
520  // Sort the array again
521  ksort( $languages );
522  }
523 
524  $options = [];
525  foreach ( $languages as $code => $name ) {
526  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
527  $options[$display] = $code;
528  }
529  $defaultPreferences['language'] = [
530  'type' => 'select',
531  'section' => 'personal/i18n',
532  'options' => $options,
533  'label-message' => 'yourlanguage',
534  ];
535 
536  $neutralGenderMessage = $context->msg( 'gender-notknown' )->escaped() . (
537  !$context->msg( 'gender-unknown' )->isDisabled()
538  ? "<br>" . $context->msg( 'parentheses' )
539  ->params( $context->msg( 'gender-unknown' )->plain() )
540  ->escaped()
541  : ''
542  );
543 
544  $defaultPreferences['gender'] = [
545  'type' => 'radio',
546  'section' => 'personal/i18n',
547  'options' => [
548  $neutralGenderMessage => 'unknown',
549  $context->msg( 'gender-female' )->escaped() => 'female',
550  $context->msg( 'gender-male' )->escaped() => 'male',
551  ],
552  'label-message' => 'yourgender',
553  'help-message' => 'prefs-help-gender',
554  ];
555 
556  // see if there are multiple language variants to choose from
557  if ( !$this->languageConverterFactory->isConversionDisabled() ) {
558 
559  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
560  if ( $langCode == $this->contLang->getCode() ) {
561  if ( !$this->languageConverter->hasVariants() ) {
562  continue;
563  }
564 
565  $variants = $this->languageConverter->getVariants();
566  $variantArray = [];
567  foreach ( $variants as $v ) {
568  $v = str_replace( '_', '-', strtolower( $v ) );
569  $variantArray[$v] = $lang->getVariantname( $v, false );
570  }
571 
572  $options = [];
573  foreach ( $variantArray as $code => $name ) {
574  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
575  $options[$display] = $code;
576  }
577 
578  $defaultPreferences['variant'] = [
579  'label-message' => 'yourvariant',
580  'type' => 'select',
581  'options' => $options,
582  'section' => 'personal/i18n',
583  'help-message' => 'prefs-help-variant',
584  ];
585  } else {
586  $defaultPreferences["variant-$langCode"] = [
587  'type' => 'api',
588  ];
589  }
590  }
591  }
592 
593  // show a preview of the old signature first
594  $oldsigWikiText = $this->parser->preSaveTransform(
595  '~~~',
596  $context->getTitle(),
597  $user,
599  );
600  $oldsigHTML = Parser::stripOuterParagraph(
601  $context->getOutput()->parseAsContent( $oldsigWikiText )
602  );
603  $signatureFieldConfig = [];
604  // Validate existing signature and show a message about it
605  $signature = $this->userOptionsManager->getOption( $user, 'nickname' );
606  $useFancySig = $this->userOptionsManager->getBoolOption( $user, 'fancysig' );
607  if ( $useFancySig && $signature !== '' ) {
608  $validator = new SignatureValidator(
609  $user,
610  $context,
612  );
613  $signatureErrors = $validator->validateSignature( $signature );
614  if ( $signatureErrors ) {
615  $sigValidation = $this->options->get( 'SignatureValidation' );
616  $oldsigHTML .= '<p><strong>' .
617  // Messages used here:
618  // * prefs-signature-invalid-warning
619  // * prefs-signature-invalid-new
620  // * prefs-signature-invalid-disallow
621  $context->msg( "prefs-signature-invalid-$sigValidation" )->parse() .
622  '</strong></p>';
623 
624  // On initial page load, show the warnings as well
625  // (when posting, you get normal validation errors instead)
626  foreach ( $signatureErrors as &$sigError ) {
627  $sigError = new \OOUI\HtmlSnippet( $sigError );
628  }
629  if ( !$context->getRequest()->wasPosted() ) {
630  $signatureFieldConfig = [
631  'warnings' => $sigValidation !== 'disallow' ? $signatureErrors : null,
632  'errors' => $sigValidation === 'disallow' ? $signatureErrors : null,
633  ];
634  }
635  }
636  }
637  $defaultPreferences['oldsig'] = [
638  'type' => 'info',
639  // Normally HTMLFormFields do not display warnings, so we need to use 'rawrow'
640  // and provide the entire OOUI\FieldLayout here
641  'rawrow' => true,
642  'default' => new \OOUI\FieldLayout(
643  new \OOUI\LabelWidget( [
644  'label' => new \OOUI\HtmlSnippet( $oldsigHTML ),
645  ] ),
646  [
647  'align' => 'top',
648  'label' => new \OOUI\HtmlSnippet( $context->msg( 'tog-oldsig' )->parse() )
649  ] + $signatureFieldConfig
650  ),
651  'section' => 'personal/signature',
652  ];
653  $defaultPreferences['nickname'] = [
654  'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
655  'maxlength' => $this->options->get( 'MaxSigChars' ),
656  'label-message' => 'yournick',
657  'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
658  return $this->validateSignature( $signature, $alldata, $form );
659  },
660  'section' => 'personal/signature',
661  'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
662  return $this->cleanSignature( $signature, $alldata, $form );
663  },
664  ];
665  $defaultPreferences['fancysig'] = [
666  'type' => 'toggle',
667  'label-message' => 'tog-fancysig',
668  // show general help about signature at the bottom of the section
669  'help-message' => 'prefs-help-signature',
670  'section' => 'personal/signature'
671  ];
672 
673  // Email preferences
674  if ( $this->options->get( 'EnableEmail' ) ) {
675  if ( $canViewPrivateInfo ) {
676  $helpMessages = [];
677  $helpMessages[] = $this->options->get( 'EmailConfirmToEdit' )
678  ? 'prefs-help-email-required'
679  : 'prefs-help-email';
680 
681  if ( $this->options->get( 'EnableUserEmail' ) ) {
682  // additional messages when users can send email to each other
683  $helpMessages[] = 'prefs-help-email-others';
684  }
685 
686  $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
687  if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
688  $button = new \OOUI\ButtonWidget( [
689  'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
690  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
691  ] ),
692  'label' =>
693  $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
694  ] );
695 
696  $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
697  }
698 
699  $defaultPreferences['emailaddress'] = [
700  'type' => 'info',
701  'raw' => true,
702  'default' => $emailAddress,
703  'label-message' => 'youremail',
704  'section' => 'personal/email',
705  'help-messages' => $helpMessages,
706  // 'cssclass' chosen below
707  ];
708  }
709 
710  $disableEmailPrefs = false;
711 
712  if ( $this->options->get( 'AllowRequiringEmailForResets' ) ) {
713  $defaultPreferences['requireemail'] = [
714  'type' => 'toggle',
715  'label-message' => 'tog-requireemail',
716  'help-message' => 'prefs-help-requireemail',
717  'section' => 'personal/email',
718  'disabled' => $user->getEmail() ? false : true,
719  ];
720  }
721 
722  if ( $this->options->get( 'EmailAuthentication' ) ) {
723  if ( $user->getEmail() ) {
724  if ( $user->getEmailAuthenticationTimestamp() ) {
725  // date and time are separate parameters to facilitate localisation.
726  // $time is kept for backward compat reasons.
727  // 'emailauthenticated' is also used in SpecialConfirmemail.php
728  $displayUser = $context->getUser();
729  $emailTimestamp = $user->getEmailAuthenticationTimestamp();
730  $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
731  $d = $lang->userDate( $emailTimestamp, $displayUser );
732  $t = $lang->userTime( $emailTimestamp, $displayUser );
733  $emailauthenticated = $context->msg( 'emailauthenticated',
734  $time, $d, $t )->parse() . '<br />';
735  $emailauthenticationclass = 'mw-email-authenticated';
736  } else {
737  $disableEmailPrefs = true;
738  $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
739  new \OOUI\ButtonWidget( [
740  'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
741  'label' => $context->msg( 'emailconfirmlink' )->text(),
742  ] );
743  $emailauthenticationclass = "mw-email-not-authenticated";
744  }
745  } else {
746  $disableEmailPrefs = true;
747  $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
748  $emailauthenticationclass = 'mw-email-none';
749  }
750 
751  if ( $canViewPrivateInfo ) {
752  $defaultPreferences['emailauthentication'] = [
753  'type' => 'info',
754  'raw' => true,
755  'section' => 'personal/email',
756  'label-message' => 'prefs-emailconfirm-label',
757  'default' => $emailauthenticated,
758  // Apply the same CSS class used on the input to the message:
759  'cssclass' => $emailauthenticationclass,
760  ];
761  }
762  }
763 
764  if ( $this->options->get( 'EnableUserEmail' ) &&
765  $user->isAllowed( 'sendemail' )
766  ) {
767  $defaultPreferences['disablemail'] = [
768  'id' => 'wpAllowEmail',
769  'type' => 'toggle',
770  'invert' => true,
771  'section' => 'personal/email',
772  'label-message' => 'allowemail',
773  'disabled' => $disableEmailPrefs,
774  ];
775 
776  $defaultPreferences['email-allow-new-users'] = [
777  'id' => 'wpAllowEmailFromNewUsers',
778  'type' => 'toggle',
779  'section' => 'personal/email',
780  'label-message' => 'email-allow-new-users-label',
781  'disabled' => $disableEmailPrefs,
782  ];
783 
784  $defaultPreferences['ccmeonemails'] = [
785  'type' => 'toggle',
786  'section' => 'personal/email',
787  'label-message' => 'tog-ccmeonemails',
788  'disabled' => $disableEmailPrefs,
789  ];
790 
791  if ( $this->options->get( 'EnableUserEmailMuteList' ) ) {
792  $defaultPreferences['email-blacklist'] = [
793  'type' => 'usersmultiselect',
794  'label-message' => 'email-mutelist-label',
795  'section' => 'personal/email',
796  'disabled' => $disableEmailPrefs,
797  'filter' => MultiUsernameFilter::class,
798  ];
799  }
800  }
801 
802  if ( $this->options->get( 'EnotifWatchlist' ) ) {
803  $defaultPreferences['enotifwatchlistpages'] = [
804  'type' => 'toggle',
805  'section' => 'personal/email',
806  'label-message' => 'tog-enotifwatchlistpages',
807  'disabled' => $disableEmailPrefs,
808  ];
809  }
810  if ( $this->options->get( 'EnotifUserTalk' ) ) {
811  $defaultPreferences['enotifusertalkpages'] = [
812  'type' => 'toggle',
813  'section' => 'personal/email',
814  'label-message' => 'tog-enotifusertalkpages',
815  'disabled' => $disableEmailPrefs,
816  ];
817  }
818  if ( $this->options->get( 'EnotifUserTalk' ) ||
819  $this->options->get( 'EnotifWatchlist' ) ) {
820  if ( $this->options->get( 'EnotifMinorEdits' ) ) {
821  $defaultPreferences['enotifminoredits'] = [
822  'type' => 'toggle',
823  'section' => 'personal/email',
824  'label-message' => 'tog-enotifminoredits',
825  'disabled' => $disableEmailPrefs,
826  ];
827  }
828 
829  if ( $this->options->get( 'EnotifRevealEditorAddress' ) ) {
830  $defaultPreferences['enotifrevealaddr'] = [
831  'type' => 'toggle',
832  'section' => 'personal/email',
833  'label-message' => 'tog-enotifrevealaddr',
834  'disabled' => $disableEmailPrefs,
835  ];
836  }
837  }
838  }
839  }
840 
847  protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
848  // Skin selector, if there is at least one valid skin
849  $skinOptions = $this->generateSkinOptions( $user, $context );
850  if ( $skinOptions ) {
851  $defaultPreferences['skin'] = [
852  // @phan-suppress-next-line SecurityCheck-XSS False positive, key is escaped
853  'type' => 'radio',
854  'options' => $skinOptions,
855  'section' => 'rendering/skin',
856  ];
857  $defaultPreferences['skin-responsive'] = [
858  'type' => 'check',
859  'label-message' => 'prefs-skin-responsive',
860  'section' => 'rendering/skin/skin-prefs',
861  'help-message' => 'prefs-help-skin-responsive',
862  ];
863  }
864 
865  $allowUserCss = $this->options->get( 'AllowUserCss' );
866  $allowUserJs = $this->options->get( 'AllowUserJs' );
867  // Create links to user CSS/JS pages for all skins.
868  // This code is basically copied from generateSkinOptions().
869  // @todo Refactor this and the similar code in generateSkinOptions().
870  if ( $allowUserCss || $allowUserJs ) {
871  $linkTools = [];
872  $userName = $user->getName();
873 
874  if ( $allowUserCss ) {
875  $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
876  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
877  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
878  }
879 
880  if ( $allowUserJs ) {
881  $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
882  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
883  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
884  }
885 
886  $defaultPreferences['commoncssjs'] = [
887  'type' => 'info',
888  'raw' => true,
889  'default' => $context->getLanguage()->pipeList( $linkTools ),
890  'label-message' => 'prefs-common-config',
891  'section' => 'rendering/skin',
892  ];
893  }
894  }
895 
900  protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
901  $defaultPreferences['imagesize'] = [
902  'type' => 'select',
903  'options' => $this->getImageSizes( $context ),
904  'label-message' => 'imagemaxsize',
905  'section' => 'rendering/files',
906  ];
907  $defaultPreferences['thumbsize'] = [
908  'type' => 'select',
909  'options' => $this->getThumbSizes( $context ),
910  'label-message' => 'thumbsize',
911  'section' => 'rendering/files',
912  ];
913  }
914 
921  protected function datetimePreferences(
922  User $user, IContextSource $context, &$defaultPreferences
923  ) {
924  $dateOptions = $this->getDateOptions( $context );
925  if ( $dateOptions ) {
926  $defaultPreferences['date'] = [
927  'type' => 'radio',
928  'options' => $dateOptions,
929  'section' => 'rendering/dateformat',
930  ];
931  }
932 
933  // Info
934  $now = wfTimestampNow();
936  $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ],
937  $lang->userTime( $now, $user ) );
938  $nowserver = $lang->userTime( $now, $user,
939  [ 'format' => false, 'timecorrection' => false ] ) .
940  Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
941 
942  $defaultPreferences['nowserver'] = [
943  'type' => 'info',
944  'raw' => 1,
945  'label-message' => 'servertime',
946  'default' => $nowserver,
947  'section' => 'rendering/timeoffset',
948  ];
949 
950  $defaultPreferences['nowlocal'] = [
951  'type' => 'info',
952  'raw' => 1,
953  'label-message' => 'localtime',
954  'default' => $nowlocal,
955  'section' => 'rendering/timeoffset',
956  ];
957 
958  // Grab existing pref.
959  $tzOffset = $this->userOptionsManager->getOption( $user, 'timecorrection' );
960  $tz = explode( '|', $tzOffset, 3 );
961 
962  $tzOptions = $this->getTimezoneOptions( $context );
963 
964  $tzSetting = $tzOffset;
965  if ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
966  !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
967  ) {
968  // Timezone offset can vary with DST
969  try {
970  $userTZ = new DateTimeZone( $tz[2] );
971  $minDiff = floor( $userTZ->getOffset( new DateTime( 'now' ) ) / 60 );
972  $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
973  } catch ( Exception $e ) {
974  // User has an invalid time zone set. Fall back to just using the offset
975  $tz[0] = 'Offset';
976  }
977  }
978  if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
979  $minDiff = $tz[1];
980  $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
981  }
982 
983  $defaultPreferences['timecorrection'] = [
984  'class' => \HTMLSelectOrOtherField::class,
985  'label-message' => 'timezonelegend',
986  'options' => $tzOptions,
987  'default' => $tzSetting,
988  'size' => 20,
989  'section' => 'rendering/timeoffset',
990  'id' => 'wpTimeCorrection',
991  'filter' => TimezoneFilter::class,
992  'placeholder-message' => 'timezone-useoffset-placeholder',
993  ];
994  }
995 
1001  protected function renderingPreferences(
1002  User $user,
1003  MessageLocalizer $l10n,
1004  &$defaultPreferences
1005  ) {
1006  // Diffs
1007  $defaultPreferences['diffonly'] = [
1008  'type' => 'toggle',
1009  'section' => 'rendering/diffs',
1010  'label-message' => 'tog-diffonly',
1011  ];
1012  $defaultPreferences['norollbackdiff'] = [
1013  'type' => 'toggle',
1014  'section' => 'rendering/diffs',
1015  'label-message' => 'tog-norollbackdiff',
1016  ];
1017 
1018  // Page Rendering
1019  if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
1020  $defaultPreferences['underline'] = [
1021  'type' => 'select',
1022  'options' => [
1023  $l10n->msg( 'underline-never' )->text() => 0,
1024  $l10n->msg( 'underline-always' )->text() => 1,
1025  $l10n->msg( 'underline-default' )->text() => 2,
1026  ],
1027  'label-message' => 'tog-underline',
1028  'section' => 'rendering/advancedrendering',
1029  ];
1030  }
1031 
1032  $defaultPreferences['showhiddencats'] = [
1033  'type' => 'toggle',
1034  'section' => 'rendering/advancedrendering',
1035  'label-message' => 'tog-showhiddencats'
1036  ];
1037 
1038  if ( $user->isAllowed( '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 ( $user->isAllowed( '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  $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1121  $defaultPreferences['rcdays'] = [
1122  'type' => 'float',
1123  'label-message' => 'recentchangesdays',
1124  'section' => 'rc/displayrc',
1125  'min' => 1 / 24,
1126  'max' => $rcMax,
1127  'help-message' => [ 'recentchangesdays-max', Message::numParam( $rcMax ) ],
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 ( $user->isAllowed( '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-message' => [ 'prefs-watchlist-days-max', Message::numParam( $watchlistdaysMax ) ],
1258  'label-message' => 'prefs-watchlist-days',
1259  ];
1260  $defaultPreferences['wllimit'] = [
1261  'type' => 'int',
1262  'min' => 1,
1263  'max' => 1000,
1264  'label-message' => 'prefs-watchlist-edits',
1265  'help-message' => 'prefs-watchlist-edits-max',
1266  'section' => 'watchlist/displaywatchlist',
1267  'filter' => IntvalFilter::class,
1268  ];
1269  $defaultPreferences['extendwatchlist'] = [
1270  'type' => 'toggle',
1271  'section' => 'watchlist/advancedwatchlist',
1272  'label-message' => 'tog-extendwatchlist',
1273  ];
1274  $defaultPreferences['watchlisthideminor'] = [
1275  'type' => 'toggle',
1276  'section' => 'watchlist/changeswatchlist',
1277  'label-message' => 'tog-watchlisthideminor',
1278  ];
1279  $defaultPreferences['watchlisthidebots'] = [
1280  'type' => 'toggle',
1281  'section' => 'watchlist/changeswatchlist',
1282  'label-message' => 'tog-watchlisthidebots',
1283  ];
1284  $defaultPreferences['watchlisthideown'] = [
1285  'type' => 'toggle',
1286  'section' => 'watchlist/changeswatchlist',
1287  'label-message' => 'tog-watchlisthideown',
1288  ];
1289  $defaultPreferences['watchlisthideanons'] = [
1290  'type' => 'toggle',
1291  'section' => 'watchlist/changeswatchlist',
1292  'label-message' => 'tog-watchlisthideanons',
1293  ];
1294  $defaultPreferences['watchlisthideliu'] = [
1295  'type' => 'toggle',
1296  'section' => 'watchlist/changeswatchlist',
1297  'label-message' => 'tog-watchlisthideliu',
1298  ];
1299 
1301  $defaultPreferences['watchlistreloadautomatically'] = [
1302  'type' => 'toggle',
1303  'section' => 'watchlist/advancedwatchlist',
1304  'label-message' => 'tog-watchlistreloadautomatically',
1305  ];
1306  }
1307 
1308  $defaultPreferences['watchlistunwatchlinks'] = [
1309  'type' => 'toggle',
1310  'section' => 'watchlist/advancedwatchlist',
1311  'label-message' => 'tog-watchlistunwatchlinks',
1312  ];
1313 
1314  if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
1315  $defaultPreferences['watchlisthidecategorization'] = [
1316  'type' => 'toggle',
1317  'section' => 'watchlist/changeswatchlist',
1318  'label-message' => 'tog-watchlisthidecategorization',
1319  ];
1320  }
1321 
1322  if ( $user->useRCPatrol() ) {
1323  $defaultPreferences['watchlisthidepatrolled'] = [
1324  'type' => 'toggle',
1325  'section' => 'watchlist/changeswatchlist',
1326  'label-message' => 'tog-watchlisthidepatrolled',
1327  ];
1328  }
1329 
1330  $watchTypes = [
1331  'edit' => 'watchdefault',
1332  'move' => 'watchmoves',
1333  'delete' => 'watchdeletion'
1334  ];
1335 
1336  // Kinda hacky
1337  if ( $user->isAllowedAny( 'createpage', 'createtalk' ) ) {
1338  $watchTypes['read'] = 'watchcreations';
1339  }
1340 
1341  if ( $user->isAllowed( 'rollback' ) ) {
1342  $watchTypes['rollback'] = 'watchrollback';
1343  }
1344 
1345  if ( $user->isAllowed( 'upload' ) ) {
1346  $watchTypes['upload'] = 'watchuploads';
1347  }
1348 
1349  foreach ( $watchTypes as $action => $pref ) {
1350  if ( $user->isAllowed( $action ) ) {
1351  // Messages:
1352  // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1353  // tog-watchrollback
1354  $defaultPreferences[$pref] = [
1355  'type' => 'toggle',
1356  'section' => 'watchlist/pageswatchlist',
1357  'label-message' => "tog-$pref",
1358  ];
1359  }
1360  }
1361 
1362  $defaultPreferences['watchlisttoken'] = [
1363  'type' => 'api',
1364  ];
1365 
1366  $tokenButton = new \OOUI\ButtonWidget( [
1367  'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1368  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1369  ] ),
1370  'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1371  ] );
1372  $defaultPreferences['watchlisttoken-info'] = [
1373  'type' => 'info',
1374  'section' => 'watchlist/tokenwatchlist',
1375  'label-message' => 'prefs-watchlist-token',
1376  'help-message' => 'prefs-help-tokenmanagement',
1377  'raw' => true,
1378  'default' => (string)$tokenButton,
1379  ];
1380 
1381  $defaultPreferences['wlenhancedfilters-disable'] = [
1382  'type' => 'toggle',
1383  'section' => 'watchlist/advancedwatchlist',
1384  'label-message' => 'rcfilters-watchlist-preference-label',
1385  'help-message' => 'rcfilters-watchlist-preference-help',
1386  ];
1387  }
1388 
1392  protected function searchPreferences( &$defaultPreferences ) {
1393  foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1394  $defaultPreferences['searchNs' . $n] = [
1395  'type' => 'api',
1396  ];
1397  }
1398 
1399  if ( $this->options->get( 'SearchMatchRedirectPreference' ) ) {
1400  $defaultPreferences['search-match-redirect'] = [
1401  'type' => 'toggle',
1402  'section' => 'searchoptions/searchmisc',
1403  'label-message' => 'search-match-redirect-label',
1404  'help-message' => 'search-match-redirect-help',
1405  ];
1406  } else {
1407  $defaultPreferences['search-match-redirect'] = [
1408  'type' => 'api',
1409  ];
1410  }
1411  }
1412 
1418  protected function generateSkinOptions( User $user, IContextSource $context ) {
1419  $ret = [];
1420 
1421  $mptitle = Title::newMainPage();
1422  $previewtext = $context->msg( 'skin-preview' )->escaped();
1423 
1424  // Only show skins that aren't disabled
1425  $validSkinNames = $this->skinFactory->getAllowedSkins();
1426  $allInstalledSkins = $this->skinFactory->getInstalledSkins();
1427 
1428  // Display the installed skin the user has specifically requested via useskin=….
1429  $useSkin = $context->getRequest()->getRawVal( 'useskin' );
1430  if ( isset( $allInstalledSkins[$useSkin] )
1431  && $context->msg( "skinname-$useSkin" )->exists()
1432  ) {
1433  $validSkinNames[$useSkin] = $useSkin;
1434  }
1435 
1436  // Display the skin if the user has set it as a preference already before it was hidden.
1437  $currentUserSkin = $this->userOptionsManager->getOption( $user, 'skin' );
1438  if ( isset( $allInstalledSkins[$currentUserSkin] )
1439  && $context->msg( "skinname-$currentUserSkin" )->exists()
1440  ) {
1441  $validSkinNames[$currentUserSkin] = $currentUserSkin;
1442  }
1443 
1444  foreach ( $validSkinNames as $skinkey => &$skinname ) {
1445  $msg = $context->msg( "skinname-{$skinkey}" );
1446  if ( $msg->exists() ) {
1447  $skinname = htmlspecialchars( $msg->text() );
1448  }
1449  }
1450 
1451  $defaultSkin = $this->options->get( 'DefaultSkin' );
1452  $allowUserCss = $this->options->get( 'AllowUserCss' );
1453  $allowUserJs = $this->options->get( 'AllowUserJs' );
1454 
1455  // Sort by the internal name, so that the ordering is the same for each display language,
1456  // especially if some skin names are translated to use a different alphabet and some are not.
1457  uksort( $validSkinNames, static function ( $a, $b ) use ( $defaultSkin ) {
1458  // Display the default first in the list by comparing it as lesser than any other.
1459  if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1460  return -1;
1461  }
1462  if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1463  return 1;
1464  }
1465  return strcasecmp( $a, $b );
1466  } );
1467 
1468  $foundDefault = false;
1469  foreach ( $validSkinNames as $skinkey => $sn ) {
1470  $linkTools = [];
1471 
1472  // Mark the default skin
1473  if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1474  $linkTools[] = $context->msg( 'default' )->escaped();
1475  $foundDefault = true;
1476  }
1477 
1478  // Create preview link
1479  $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1480  $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1481 
1482  // Create links to user CSS/JS pages
1483  // @todo Refactor this and the similar code in skinPreferences().
1484  if ( $allowUserCss ) {
1485  $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1486  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1487  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1488  }
1489 
1490  if ( $allowUserJs ) {
1491  $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1492  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1493  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1494  }
1495 
1496  $display = $sn . ' ' . $context->msg( 'parentheses' )
1497  ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1498  ->escaped();
1499  $ret[$display] = $skinkey;
1500  }
1501 
1502  if ( !$foundDefault ) {
1503  // If the default skin is not available, things are going to break horribly because the
1504  // default value for skin selector will not be a valid value. Let's just not show it then.
1505  return [];
1506  }
1507 
1508  return $ret;
1509  }
1510 
1515  protected function getDateOptions( IContextSource $context ) {
1516  $lang = $context->getLanguage();
1517  $dateopts = $lang->getDatePreferences();
1518 
1519  $ret = [];
1520 
1521  if ( $dateopts ) {
1522  if ( !in_array( 'default', $dateopts ) ) {
1523  $dateopts[] = 'default'; // Make sure default is always valid T21237
1524  }
1525 
1526  // FIXME KLUGE: site default might not be valid for user language
1527  global $wgDefaultUserOptions;
1528  if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1529  $wgDefaultUserOptions['date'] = 'default';
1530  }
1531 
1532  $epoch = wfTimestampNow();
1533  foreach ( $dateopts as $key ) {
1534  if ( $key == 'default' ) {
1535  $formatted = $context->msg( 'datedefault' )->escaped();
1536  } else {
1537  $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1538  }
1539  $ret[$formatted] = $key;
1540  }
1541  }
1542  return $ret;
1543  }
1544 
1549  protected function getImageSizes( MessageLocalizer $l10n ) {
1550  $ret = [];
1551  $pixels = $l10n->msg( 'unit-pixel' )->text();
1552 
1553  foreach ( $this->options->get( 'ImageLimits' ) as $index => $limits ) {
1554  // Note: A left-to-right marker (U+200E) is inserted, see T144386
1555  $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1556  $ret[$display] = $index;
1557  }
1558 
1559  return $ret;
1560  }
1561 
1566  protected function getThumbSizes( MessageLocalizer $l10n ) {
1567  $ret = [];
1568  $pixels = $l10n->msg( 'unit-pixel' )->text();
1569 
1570  foreach ( $this->options->get( 'ThumbLimits' ) as $index => $size ) {
1571  $display = $size . $pixels;
1572  $ret[$display] = $index;
1573  }
1574 
1575  return $ret;
1576  }
1577 
1584  protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1585  $sigValidation = $this->options->get( 'SignatureValidation' );
1586  $maxSigChars = $this->options->get( 'MaxSigChars' );
1587  if ( mb_strlen( $signature ) > $maxSigChars ) {
1588  return $form->msg( 'badsiglength' )->numParams( $maxSigChars )->escaped();
1589  }
1590 
1591  if ( $signature === '' ) {
1592  // Make sure leaving the field empty is valid, since that's used as the default (T288151).
1593  // Code using this preference in Parser::getUserSig() handles this case specially.
1594  return true;
1595  }
1596 
1597  // Remaining checks only apply to fancy signatures
1598  if ( !( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) ) {
1599  return true;
1600  }
1601 
1602  // HERE BE DRAGONS:
1603  //
1604  // If this value is already saved as the user's signature, treat it as valid, even if it
1605  // would be invalid to save now, and even if $wgSignatureValidation is set to 'disallow'.
1606  //
1607  // It can become invalid when we introduce new validation, or when the value just transcludes
1608  // some page containing the real signature and that page is edited (which we can't validate),
1609  // or when someone's username is changed.
1610  //
1611  // Otherwise it would be completely removed when the user opens their preferences page, which
1612  // would be very unfriendly.
1613  $user = $form->getUser();
1614  if (
1615  $signature === $this->userOptionsManager->getOption( $user, 'nickname' ) &&
1616  (bool)$alldata['fancysig'] === $this->userOptionsManager->getBoolOption( $user, 'fancysig' )
1617  ) {
1618  return true;
1619  }
1620 
1621  if ( $sigValidation === 'new' || $sigValidation === 'disallow' ) {
1622  // Validate everything
1623  $validator = new SignatureValidator(
1624  $user,
1625  $form->getContext(),
1627  );
1628  $errors = $validator->validateSignature( $signature );
1629  if ( $errors ) {
1630  return $errors;
1631  }
1632  }
1633 
1634  // Quick check for mismatched HTML tags in the input.
1635  // Note that this is easily fooled by wikitext templates or bold/italic markup.
1636  // We're only keeping this until Parsoid is integrated and guaranteed to be available.
1637  if ( $this->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  if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1652  $signature = $this->parser->cleanSig( $signature );
1653  } else {
1654  // When no fancy sig used, make sure ~{3,5} get removed.
1655  $signature = Parser::cleanSigInSig( $signature );
1656  }
1657 
1658  return $signature;
1659  }
1660 
1668  public function getForm(
1669  User $user,
1671  $formClass = PreferencesFormOOUI::class,
1672  array $remove = []
1673  ) {
1674  // We use ButtonWidgets in some of the getPreferences() functions
1675  $context->getOutput()->enableOOUI();
1676 
1677  // Note that the $user parameter of getFormDescriptor() is deprecated.
1678  $formDescriptor = $this->getFormDescriptor( $user, $context );
1679  if ( count( $remove ) ) {
1680  $removeKeys = array_fill_keys( $remove, true );
1681  $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1682  }
1683 
1684  // Remove type=api preferences. They are not intended for rendering in the form.
1685  foreach ( $formDescriptor as $name => $info ) {
1686  if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1687  unset( $formDescriptor[$name] );
1688  }
1689  }
1690 
1694  $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1695 
1696  // This allows users to opt-in to hidden skins. While this should be discouraged and is not
1697  // discoverable, this allows users to still use hidden skins while preventing new users from
1698  // adopting unsupported skins. If no useskin=… parameter was provided, it will not show up
1699  // in the resulting URL.
1700  $htmlForm->setAction( $context->getTitle()->getLocalURL( [
1701  'useskin' => $context->getRequest()->getRawVal( 'useskin' )
1702  ] ) );
1703 
1704  $htmlForm->setModifiedUser( $user );
1705  $htmlForm->setOptionsEditable( $user->isAllowed( 'editmyoptions' ) );
1706  $htmlForm->setPrivateInfoEditable( $user->isAllowed( 'editmyprivateinfo' ) );
1707  $htmlForm->setId( 'mw-prefs-form' );
1708  $htmlForm->setAutocomplete( 'off' );
1709  $htmlForm->setSubmitTextMsg( 'saveprefs' );
1710  // Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1711  $htmlForm->setSubmitTooltip( 'preferences-save' );
1712  $htmlForm->setSubmitID( 'prefcontrol' );
1713  $htmlForm->setSubmitCallback(
1714  function ( array $formData, PreferencesFormOOUI $form ) use ( $formDescriptor ) {
1715  return $this->submitForm( $formData, $form, $formDescriptor );
1716  }
1717  );
1718 
1719  return $htmlForm;
1720  }
1721 
1727  $opt = [];
1728 
1729  $localTZoffset = $this->options->get( 'LocalTZoffset' );
1730  $timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
1731 
1732  $timestamp = MWTimestamp::getLocalInstance();
1733  // Check that the LocalTZoffset is the same as the local time zone offset
1734  if ( $localTZoffset === $timestamp->format( 'Z' ) / 60 ) {
1735  $timezoneName = $timestamp->getTimezone()->getName();
1736  // Localize timezone
1737  if ( isset( $timeZoneList[$timezoneName] ) ) {
1738  $timezoneName = $timeZoneList[$timezoneName]['name'];
1739  }
1740  $server_tz_msg = $context->msg(
1741  'timezoneuseserverdefault',
1742  $timezoneName
1743  )->text();
1744  } else {
1745  $tzstring = sprintf(
1746  '%+03d:%02d',
1747  floor( $localTZoffset / 60 ),
1748  abs( $localTZoffset ) % 60
1749  );
1750  $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
1751  }
1752  $opt[$server_tz_msg] = "System|$localTZoffset";
1753  $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
1754  $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
1755 
1756  foreach ( $timeZoneList as $timeZoneInfo ) {
1757  $region = $timeZoneInfo['region'];
1758  if ( !isset( $opt[$region] ) ) {
1759  $opt[$region] = [];
1760  }
1761  $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
1762  }
1763  return $opt;
1764  }
1765 
1774  protected function saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor ) {
1775  $user = $form->getModifiedUser();
1776  $hiddenPrefs = $this->options->get( 'HiddenPrefs' );
1777  $result = true;
1778 
1779  if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' )
1780  ) {
1781  return Status::newFatal( 'mypreferencesprotected' );
1782  }
1783 
1784  // Filter input
1785  $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
1786 
1787  // Fortunately, the realname field is MUCH simpler
1788  // (not really "private", but still shouldn't be edited without permission)
1789 
1790  if ( !in_array( 'realname', $hiddenPrefs )
1791  && $user->isAllowed( 'editmyprivateinfo' )
1792  && array_key_exists( 'realname', $formData )
1793  ) {
1794  $realName = $formData['realname'];
1795  $user->setRealName( $realName );
1796  }
1797 
1798  if ( $user->isAllowed( 'editmyoptions' ) ) {
1799  $oldUserOptions = $this->userOptionsManager->getOptions( $user );
1800 
1801  foreach ( $this->getSaveBlacklist() as $b ) {
1802  unset( $formData[$b] );
1803  }
1804 
1805  // If users have saved a value for a preference which has subsequently been disabled
1806  // via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1807  // is subsequently re-enabled
1808  foreach ( $hiddenPrefs as $pref ) {
1809  // If the user has not set a non-default value here, the default will be returned
1810  // and subsequently discarded
1811  $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref, null, true );
1812  }
1813 
1814  // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
1815  if (
1816  isset( $formData['rclimit'] ) &&
1817  intval( $formData[ 'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user, 'rclimit' )
1818  ) {
1819  $formData['rcfilters-limit'] = $formData['rclimit'];
1820  }
1821 
1822  // Keep old preferences from interfering due to back-compat code, etc.
1823  $this->userOptionsManager->resetOptions( $user, $form->getContext(), 'unused' );
1824 
1825  foreach ( $formData as $key => $value ) {
1826  $this->userOptionsManager->setOption( $user, $key, $value );
1827  }
1828 
1829  $this->hookRunner->onPreferencesFormPreSave(
1830  $formData, $form, $user, $result, $oldUserOptions );
1831  }
1832 
1833  $user->saveSettings();
1834 
1835  return $result;
1836  }
1837 
1846  protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1847  foreach ( $formDescriptor as $preference => $desc ) {
1848  if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
1849  continue;
1850  }
1851  $filterDesc = $desc['filter'];
1852  if ( $filterDesc instanceof Filter ) {
1853  $filter = $filterDesc;
1854  } elseif ( class_exists( $filterDesc ) ) {
1855  $filter = new $filterDesc();
1856  } elseif ( is_callable( $filterDesc ) ) {
1857  $filter = $filterDesc();
1858  } else {
1859  throw new UnexpectedValueException(
1860  "Unrecognized filter type for preference '$preference'"
1861  );
1862  }
1863  $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1864  }
1865  }
1866 
1875  protected function submitForm(
1876  array $formData,
1877  PreferencesFormOOUI $form,
1878  array $formDescriptor
1879  ) {
1880  $res = $this->saveFormData( $formData, $form, $formDescriptor );
1881 
1882  if ( $res === true ) {
1883  $context = $form->getContext();
1884  $urlOptions = [];
1885 
1886  $urlOptions += $form->getExtraSuccessRedirectParameters();
1887 
1888  $url = $form->getTitle()->getFullURL( $urlOptions );
1889 
1890  // Set session data for the success message
1891  $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
1892 
1893  $context->getOutput()->redirect( $url );
1894  }
1895 
1896  return ( $res === true ? Status::newGood() : $res );
1897  }
1898 
1907  protected function getTimeZoneList( Language $language ) {
1908  $identifiers = DateTimeZone::listIdentifiers();
1909  // @phan-suppress-next-line PhanTypeComparisonFromArray See phan issue #3162
1910  if ( $identifiers === false ) {
1911  return [];
1912  }
1913  sort( $identifiers );
1914 
1915  $tzRegions = [
1916  'Africa' => wfMessage( 'timezoneregion-africa' )->inLanguage( $language )->text(),
1917  'America' => wfMessage( 'timezoneregion-america' )->inLanguage( $language )->text(),
1918  'Antarctica' => wfMessage( 'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1919  'Arctic' => wfMessage( 'timezoneregion-arctic' )->inLanguage( $language )->text(),
1920  'Asia' => wfMessage( 'timezoneregion-asia' )->inLanguage( $language )->text(),
1921  'Atlantic' => wfMessage( 'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1922  'Australia' => wfMessage( 'timezoneregion-australia' )->inLanguage( $language )->text(),
1923  'Europe' => wfMessage( 'timezoneregion-europe' )->inLanguage( $language )->text(),
1924  'Indian' => wfMessage( 'timezoneregion-indian' )->inLanguage( $language )->text(),
1925  'Pacific' => wfMessage( 'timezoneregion-pacific' )->inLanguage( $language )->text(),
1926  ];
1927  asort( $tzRegions );
1928 
1929  $timeZoneList = [];
1930 
1931  $now = new DateTime();
1932 
1933  foreach ( $identifiers as $identifier ) {
1934  $parts = explode( '/', $identifier, 2 );
1935 
1936  // DateTimeZone::listIdentifiers() returns a number of
1937  // backwards-compatibility entries. This filters them out of the
1938  // list presented to the user.
1939  if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1940  continue;
1941  }
1942 
1943  // Localize region
1944  $parts[0] = $tzRegions[$parts[0]];
1945 
1946  $dateTimeZone = new DateTimeZone( $identifier );
1947  $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1948 
1949  $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
1950  $value = "ZoneInfo|$minDiff|$identifier";
1951 
1952  $timeZoneList[$identifier] = [
1953  'name' => $display,
1954  'timecorrection' => $value,
1955  'region' => $parts[0],
1956  ];
1957  }
1958 
1959  return $timeZoneList;
1960  }
1961 }
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:1127
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:1219
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:135
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:202
$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:1668
MediaWiki\Preferences\DefaultPreferencesFactory\$skinFactory
SkinFactory $skinFactory
Definition: DefaultPreferencesFactory.php:110
User\getEditCount
getEditCount()
Get the user's edit count.
Definition: User.php:2480
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:1846
UserGroupMembership\getExpiry
getExpiry()
Definition: UserGroupMembership.php:78
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:1052
MediaWiki\Preferences\DefaultPreferencesFactory\getTimezoneOptions
getTimezoneOptions(IContextSource $context)
Definition: DefaultPreferencesFactory.php:1726
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:5669
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:1183
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:1774
User\useNPPatrol
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:2600
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:728
User\getEmailAuthenticationTimestamp
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition: User.php:2242
$res
$res
Definition: testCompression.php:57
User\useRCPatrol
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:2591
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:43
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:263
User\getEmail
getEmail()
Get the user's e-mail address.
Definition: User.php:2229
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:1566
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:1549
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:1907
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:104
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:42
MediaWiki\Preferences\DefaultPreferencesFactory\rcPreferences
rcPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:1118
MediaWiki\Preferences\DefaultPreferencesFactory\$hookRunner
HookRunner $hookRunner
Definition: DefaultPreferencesFactory.php:98
MediaWiki\Preferences\DefaultPreferencesFactory\cleanSignature
cleanSignature( $signature, $alldata, HTMLForm $form)
Definition: DefaultPreferencesFactory.php:1650
MediaWiki\Preferences\DefaultPreferencesFactory\renderingPreferences
renderingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:1001
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1694
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:900
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:1392
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:1875
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:692
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:52
MediaWiki\Preferences\DefaultPreferencesFactory\datetimePreferences
datetimePreferences(User $user, IContextSource $context, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:921
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:2575
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:1065
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:2323
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:4193
MediaWiki\Preferences\DefaultPreferencesFactory\getDateOptions
getDateOptions(IContextSource $context)
Definition: DefaultPreferencesFactory.php:1515
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:92
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:3462
User\isAllowed
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Definition: User.php:2583
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:45
Title
Represents a title within MediaWiki.
Definition: Title.php:47
LanguageConverter\$languagesWithVariants
static array $languagesWithVariants
languages supporting variants
Definition: LanguageConverter.php:44
Parser\stripOuterParagraph
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6333
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:41
MediaWiki\Languages\LanguageNameUtils\SUPPORTED
const SUPPORTED
Return in getLanguageName(s) only the languages for which we have at least some localisation.
Definition: LanguageNameUtils.php:62
User\getTitleKey
getTitleKey()
Get the user's name escaped by underscores.
Definition: User.php:2004
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:139
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:1141
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:554
$t
$t
Definition: testCompression.php:74
MediaWiki\Languages\LanguageNameUtils\AUTONYMS
const AUTONYMS
Return autonyms in getLanguageName(s).
Definition: LanguageNameUtils.php:47
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:40
MediaWiki\Preferences\DefaultPreferencesFactory\validateSignature
validateSignature( $signature, $alldata, HTMLForm $form)
Definition: DefaultPreferencesFactory.php:1584
Parser\cleanSigInSig
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4750
MediaWiki\Preferences\DefaultPreferencesFactory\generateSkinOptions
generateSkinOptions(User $user, IContextSource $context)
Definition: DefaultPreferencesFactory.php:1418
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:67
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:1912
Xml
Module of static functions for generating XML.
Definition: Xml.php:29
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:847
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:35
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