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