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