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