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