MediaWiki  master
DefaultPreferencesFactory.php
Go to the documentation of this file.
1 <?php
22 
23 use Config;
27 use Hooks;
28 use Html;
45 use Parser;
50 use Skin;
52 use Status;
53 use Title;
55 use User;
57 use Xml;
58 
64 
66  protected $options;
67 
69  protected $contLang;
70 
72  protected $authManager;
73 
75  protected $linkRenderer;
76 
78  protected $nsInfo;
79 
86  public static $constructorOptions = [
87  'AllowUserCss',
88  'AllowUserCssPrefs',
89  'AllowUserJs',
90  'DefaultSkin',
91  'DisableLangConversion',
92  'EmailAuthentication',
93  'EmailConfirmToEdit',
94  'EnableEmail',
95  'EnableUserEmail',
96  'EnableUserEmailBlacklist',
97  'EnotifMinorEdits',
98  'EnotifRevealEditorAddress',
99  'EnotifUserTalk',
100  'EnotifWatchlist',
101  'HiddenPrefs',
102  'ImageLimits',
103  'LanguageCode',
104  'LocalTZoffset',
105  'MaxSigChars',
106  'RCMaxAge',
107  'RCShowWatchingUsers',
108  'RCWatchCategoryMembership',
109  'SecureLogin',
110  'ThumbLimits',
111  ];
112 
122  public function __construct(
123  $options,
128  ) {
129  if ( $options instanceof Config ) {
130  wfDeprecated( __METHOD__ . ' with Config parameter', '1.34' );
131  $options = new ServiceOptions( self::$constructorOptions, $options );
132  }
133 
134  $options->assertRequiredOptions( self::$constructorOptions );
135 
136  if ( !$nsInfo ) {
137  wfDeprecated( __METHOD__ . ' with no NamespaceInfo argument', '1.34' );
138  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
139  }
140  $this->options = $options;
141  $this->contLang = $contLang;
142  $this->authManager = $authManager;
143  $this->linkRenderer = $linkRenderer;
144  $this->nsInfo = $nsInfo;
145  $this->logger = new NullLogger();
146  }
147 
151  public function getSaveBlacklist() {
152  return [
153  'realname',
154  'emailaddress',
155  ];
156  }
157 
165  $preferences = [];
166 
168  strtolower( $context->getSkin()->getSkinName() ),
169  $context->getLanguage()->getDir()
170  );
171 
172  $canIPUseHTTPS = wfCanIPUseHTTPS( $context->getRequest()->getIP() );
173  $this->profilePreferences( $user, $context, $preferences, $canIPUseHTTPS );
174  $this->skinPreferences( $user, $context, $preferences );
175  $this->datetimePreferences( $user, $context, $preferences );
176  $this->filesPreferences( $context, $preferences );
177  $this->renderingPreferences( $user, $context, $preferences );
178  $this->editingPreferences( $user, $context, $preferences );
179  $this->rcPreferences( $user, $context, $preferences );
180  $this->watchlistPreferences( $user, $context, $preferences );
181  $this->searchPreferences( $preferences );
182 
183  Hooks::run( 'GetPreferences', [ $user, &$preferences ] );
184 
185  $this->loadPreferenceValues( $user, $context, $preferences );
186  $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
187  return $preferences;
188  }
189 
198  private function loadPreferenceValues(
199  User $user, IContextSource $context, &$defaultPreferences
200  ) {
201  # # Remove preferences that wikis don't want to use
202  foreach ( $this->options->get( 'HiddenPrefs' ) as $pref ) {
203  if ( isset( $defaultPreferences[$pref] ) ) {
204  unset( $defaultPreferences[$pref] );
205  }
206  }
207 
208  # # Make sure that form fields have their parent set. See T43337.
209  $dummyForm = new HTMLForm( [], $context );
210 
211  $disable = !$user->isAllowed( 'editmyoptions' );
212 
213  $defaultOptions = User::getDefaultOptions();
214  $userOptions = $user->getOptions();
215  $this->applyFilters( $userOptions, $defaultPreferences, 'filterForForm' );
216  # # Prod in defaults from the user
217  foreach ( $defaultPreferences as $name => &$info ) {
218  $prefFromUser = $this->getOptionFromUser( $name, $info, $userOptions );
219  if ( $disable && !in_array( $name, $this->getSaveBlacklist() ) ) {
220  $info['disabled'] = 'disabled';
221  }
222  $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation
223  $globalDefault = $defaultOptions[$name] ?? null;
224 
225  // If it validates, set it as the default
226  if ( isset( $info['default'] ) ) {
227  // Already set, no problem
228  continue;
229  } elseif ( !is_null( $prefFromUser ) && // Make sure we're not just pulling nothing
230  $field->validate( $prefFromUser, $user->getOptions() ) === true ) {
231  $info['default'] = $prefFromUser;
232  } elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) {
233  $info['default'] = $globalDefault;
234  } else {
235  throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
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  // retrieving user name for GENDER and misc.
300  $userName = $user->getName();
301 
302  # # User info #####################################
303  // Information panel
304  $defaultPreferences['username'] = [
305  'type' => 'info',
306  'label-message' => [ 'username', $userName ],
307  'default' => $userName,
308  'section' => 'personal/info',
309  ];
310 
311  $lang = $context->getLanguage();
312 
313  # Get groups to which the user belongs
314  $userEffectiveGroups = $user->getEffectiveGroups();
315  $userGroupMemberships = $user->getGroupMemberships();
316  $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
317  foreach ( $userEffectiveGroups as $ueg ) {
318  if ( $ueg == '*' ) {
319  // Skip the default * group, seems useless here
320  continue;
321  }
322 
323  $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
324 
325  $userG = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html' );
326  $userM = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html',
327  $userName );
328 
329  // Store expiring groups separately, so we can place them before non-expiring
330  // groups in the list. This is to avoid the ambiguity of something like
331  // "administrator, bureaucrat (until X date)" -- users might wonder whether the
332  // expiry date applies to both groups, or just the last one
333  if ( $groupStringOrObject instanceof UserGroupMembership &&
334  $groupStringOrObject->getExpiry()
335  ) {
336  $userTempGroups[] = $userG;
337  $userTempMembers[] = $userM;
338  } else {
339  $userGroups[] = $userG;
340  $userMembers[] = $userM;
341  }
342  }
343  sort( $userGroups );
344  sort( $userMembers );
345  sort( $userTempGroups );
346  sort( $userTempMembers );
347  $userGroups = array_merge( $userTempGroups, $userGroups );
348  $userMembers = array_merge( $userTempMembers, $userMembers );
349 
350  $defaultPreferences['usergroups'] = [
351  'type' => 'info',
352  'label' => $context->msg( 'prefs-memberingroups' )->numParams(
353  count( $userGroups ) )->params( $userName )->parse(),
354  'default' => $context->msg( 'prefs-memberingroups-type' )
355  ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
356  ->escaped(),
357  'raw' => true,
358  'section' => 'personal/info',
359  ];
360 
361  $contribTitle = SpecialPage::getTitleFor( "Contributions", $userName );
362  $formattedEditCount = $lang->formatNum( $user->getEditCount() );
363  $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
364 
365  $defaultPreferences['editcount'] = [
366  'type' => 'info',
367  'raw' => true,
368  'label-message' => 'prefs-edits',
369  'default' => $editCount,
370  'section' => 'personal/info',
371  ];
372 
373  if ( $user->getRegistration() ) {
374  $displayUser = $context->getUser();
375  $userRegistration = $user->getRegistration();
376  $defaultPreferences['registrationdate'] = [
377  'type' => 'info',
378  'label-message' => 'prefs-registration',
379  'default' => $context->msg(
380  'prefs-registration-date-time',
381  $lang->userTimeAndDate( $userRegistration, $displayUser ),
382  $lang->userDate( $userRegistration, $displayUser ),
383  $lang->userTime( $userRegistration, $displayUser )
384  )->text(),
385  'section' => 'personal/info',
386  ];
387  }
388 
389  $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
390  $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
391 
392  // Actually changeable stuff
393  $defaultPreferences['realname'] = [
394  // (not really "private", but still shouldn't be edited without permission)
395  'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'realname' )
396  ? 'text' : 'info',
397  'default' => $user->getRealName(),
398  'section' => 'personal/info',
399  'label-message' => 'yourrealname',
400  'help-message' => 'prefs-help-realname',
401  ];
402 
403  if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
404  new PasswordAuthenticationRequest(), false )->isGood()
405  ) {
406  $defaultPreferences['password'] = [
407  'type' => 'info',
408  'raw' => true,
409  'default' => (string)new \OOUI\ButtonWidget( [
410  'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
411  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
412  ] ),
413  'label' => $context->msg( 'prefs-resetpass' )->text(),
414  ] ),
415  'label-message' => 'yourpassword',
416  'section' => 'personal/info',
417  ];
418  }
419  // Only show prefershttps if secure login is turned on
420  if ( $this->options->get( 'SecureLogin' ) && $canIPUseHTTPS ) {
421  $defaultPreferences['prefershttps'] = [
422  'type' => 'toggle',
423  'label-message' => 'tog-prefershttps',
424  'help-message' => 'prefs-help-prefershttps',
425  'section' => 'personal/info'
426  ];
427  }
428 
430  $languageCode = $this->options->get( 'LanguageCode' );
431  if ( !array_key_exists( $languageCode, $languages ) ) {
432  $languages[$languageCode] = $languageCode;
433  // Sort the array again
434  ksort( $languages );
435  }
436 
437  $options = [];
438  foreach ( $languages as $code => $name ) {
439  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
440  $options[$display] = $code;
441  }
442  $defaultPreferences['language'] = [
443  'type' => 'select',
444  'section' => 'personal/i18n',
445  'options' => $options,
446  'label-message' => 'yourlanguage',
447  ];
448 
449  $defaultPreferences['gender'] = [
450  'type' => 'radio',
451  'section' => 'personal/i18n',
452  'options' => [
453  $context->msg( 'parentheses' )
454  ->params( $context->msg( 'gender-unknown' )->plain() )
455  ->escaped() => 'unknown',
456  $context->msg( 'gender-female' )->escaped() => 'female',
457  $context->msg( 'gender-male' )->escaped() => 'male',
458  ],
459  'label-message' => 'yourgender',
460  'help-message' => 'prefs-help-gender',
461  ];
462 
463  // see if there are multiple language variants to choose from
464  if ( !$this->options->get( 'DisableLangConversion' ) ) {
465  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
466  if ( $langCode == $this->contLang->getCode() ) {
467  if ( !$this->contLang->hasVariants() ) {
468  continue;
469  }
470 
471  $variants = $this->contLang->getVariants();
472  $variantArray = [];
473  foreach ( $variants as $v ) {
474  $v = str_replace( '_', '-', strtolower( $v ) );
475  $variantArray[$v] = $lang->getVariantname( $v, false );
476  }
477 
478  $options = [];
479  foreach ( $variantArray as $code => $name ) {
480  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
481  $options[$display] = $code;
482  }
483 
484  $defaultPreferences['variant'] = [
485  'label-message' => 'yourvariant',
486  'type' => 'select',
487  'options' => $options,
488  'section' => 'personal/i18n',
489  'help-message' => 'prefs-help-variant',
490  ];
491  } else {
492  $defaultPreferences["variant-$langCode"] = [
493  'type' => 'api',
494  ];
495  }
496  }
497  }
498 
499  // show a preview of the old signature first
500  $oldsigWikiText = MediaWikiServices::getInstance()->getParser()->preSaveTransform(
501  '~~~',
502  $context->getTitle(),
503  $user,
505  );
506  $oldsigHTML = Parser::stripOuterParagraph(
507  $context->getOutput()->parseAsContent( $oldsigWikiText )
508  );
509  $defaultPreferences['oldsig'] = [
510  'type' => 'info',
511  'raw' => true,
512  'label-message' => 'tog-oldsig',
513  'default' => $oldsigHTML,
514  'section' => 'personal/signature',
515  ];
516  $defaultPreferences['nickname'] = [
517  'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
518  'maxlength' => $this->options->get( 'MaxSigChars' ),
519  'label-message' => 'yournick',
520  'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
521  return $this->validateSignature( $signature, $alldata, $form );
522  },
523  'section' => 'personal/signature',
524  'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
525  return $this->cleanSignature( $signature, $alldata, $form );
526  },
527  ];
528  $defaultPreferences['fancysig'] = [
529  'type' => 'toggle',
530  'label-message' => 'tog-fancysig',
531  // show general help about signature at the bottom of the section
532  'help-message' => 'prefs-help-signature',
533  'section' => 'personal/signature'
534  ];
535 
536  # # Email stuff
537 
538  if ( $this->options->get( 'EnableEmail' ) ) {
539  if ( $canViewPrivateInfo ) {
540  $helpMessages[] = $this->options->get( 'EmailConfirmToEdit' )
541  ? 'prefs-help-email-required'
542  : 'prefs-help-email';
543 
544  if ( $this->options->get( 'EnableUserEmail' ) ) {
545  // additional messages when users can send email to each other
546  $helpMessages[] = 'prefs-help-email-others';
547  }
548 
549  $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
550  if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
551  $button = new \OOUI\ButtonWidget( [
552  'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
553  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
554  ] ),
555  'label' =>
556  $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
557  ] );
558 
559  $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
560  }
561 
562  $defaultPreferences['emailaddress'] = [
563  'type' => 'info',
564  'raw' => true,
565  'default' => $emailAddress,
566  'label-message' => 'youremail',
567  'section' => 'personal/email',
568  'help-messages' => $helpMessages,
569  # 'cssclass' chosen below
570  ];
571  }
572 
573  $disableEmailPrefs = false;
574 
575  if ( $this->options->get( 'EmailAuthentication' ) ) {
576  $emailauthenticationclass = 'mw-email-not-authenticated';
577  if ( $user->getEmail() ) {
578  if ( $user->getEmailAuthenticationTimestamp() ) {
579  // date and time are separate parameters to facilitate localisation.
580  // $time is kept for backward compat reasons.
581  // 'emailauthenticated' is also used in SpecialConfirmemail.php
582  $displayUser = $context->getUser();
583  $emailTimestamp = $user->getEmailAuthenticationTimestamp();
584  $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
585  $d = $lang->userDate( $emailTimestamp, $displayUser );
586  $t = $lang->userTime( $emailTimestamp, $displayUser );
587  $emailauthenticated = $context->msg( 'emailauthenticated',
588  $time, $d, $t )->parse() . '<br />';
589  $disableEmailPrefs = false;
590  $emailauthenticationclass = 'mw-email-authenticated';
591  } else {
592  $disableEmailPrefs = true;
593  $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
594  new \OOUI\ButtonWidget( [
595  'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
596  'label' => $context->msg( 'emailconfirmlink' )->text(),
597  ] );
598  $emailauthenticationclass = "mw-email-not-authenticated";
599  }
600  } else {
601  $disableEmailPrefs = true;
602  $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
603  $emailauthenticationclass = 'mw-email-none';
604  }
605 
606  if ( $canViewPrivateInfo ) {
607  $defaultPreferences['emailauthentication'] = [
608  'type' => 'info',
609  'raw' => true,
610  'section' => 'personal/email',
611  'label-message' => 'prefs-emailconfirm-label',
612  'default' => $emailauthenticated,
613  # Apply the same CSS class used on the input to the message:
614  'cssclass' => $emailauthenticationclass,
615  ];
616  }
617  }
618 
619  if ( $this->options->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
620  $defaultPreferences['disablemail'] = [
621  'id' => 'wpAllowEmail',
622  'type' => 'toggle',
623  'invert' => true,
624  'section' => 'personal/email',
625  'label-message' => 'allowemail',
626  'disabled' => $disableEmailPrefs,
627  ];
628 
629  $defaultPreferences['email-allow-new-users'] = [
630  'id' => 'wpAllowEmailFromNewUsers',
631  'type' => 'toggle',
632  'section' => 'personal/email',
633  'label-message' => 'email-allow-new-users-label',
634  'disabled' => $disableEmailPrefs,
635  ];
636 
637  $defaultPreferences['ccmeonemails'] = [
638  'type' => 'toggle',
639  'section' => 'personal/email',
640  'label-message' => 'tog-ccmeonemails',
641  'disabled' => $disableEmailPrefs,
642  ];
643 
644  if ( $this->options->get( 'EnableUserEmailBlacklist' ) ) {
645  $defaultPreferences['email-blacklist'] = [
646  'type' => 'usersmultiselect',
647  'label-message' => 'email-blacklist-label',
648  'section' => 'personal/email',
649  'disabled' => $disableEmailPrefs,
650  'filter' => MultiUsernameFilter::class,
651  ];
652  }
653  }
654 
655  if ( $this->options->get( 'EnotifWatchlist' ) ) {
656  $defaultPreferences['enotifwatchlistpages'] = [
657  'type' => 'toggle',
658  'section' => 'personal/email',
659  'label-message' => 'tog-enotifwatchlistpages',
660  'disabled' => $disableEmailPrefs,
661  ];
662  }
663  if ( $this->options->get( 'EnotifUserTalk' ) ) {
664  $defaultPreferences['enotifusertalkpages'] = [
665  'type' => 'toggle',
666  'section' => 'personal/email',
667  'label-message' => 'tog-enotifusertalkpages',
668  'disabled' => $disableEmailPrefs,
669  ];
670  }
671  if ( $this->options->get( 'EnotifUserTalk' ) ||
672  $this->options->get( 'EnotifWatchlist' ) ) {
673  if ( $this->options->get( 'EnotifMinorEdits' ) ) {
674  $defaultPreferences['enotifminoredits'] = [
675  'type' => 'toggle',
676  'section' => 'personal/email',
677  'label-message' => 'tog-enotifminoredits',
678  'disabled' => $disableEmailPrefs,
679  ];
680  }
681 
682  if ( $this->options->get( 'EnotifRevealEditorAddress' ) ) {
683  $defaultPreferences['enotifrevealaddr'] = [
684  'type' => 'toggle',
685  'section' => 'personal/email',
686  'label-message' => 'tog-enotifrevealaddr',
687  'disabled' => $disableEmailPrefs,
688  ];
689  }
690  }
691  }
692  }
693 
700  protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
701  # # Skin #####################################
702 
703  // Skin selector, if there is at least one valid skin
704  $skinOptions = $this->generateSkinOptions( $user, $context );
705  if ( $skinOptions ) {
706  $defaultPreferences['skin'] = [
707  'type' => 'radio',
708  'options' => $skinOptions,
709  'section' => 'rendering/skin',
710  ];
711  }
712 
713  $allowUserCss = $this->options->get( 'AllowUserCss' );
714  $allowUserJs = $this->options->get( 'AllowUserJs' );
715  # Create links to user CSS/JS pages for all skins
716  # This code is basically copied from generateSkinOptions(). It'd
717  # be nice to somehow merge this back in there to avoid redundancy.
718  if ( $allowUserCss || $allowUserJs ) {
719  $linkTools = [];
720  $userName = $user->getName();
721 
722  if ( $allowUserCss ) {
723  $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
724  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
725  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
726  }
727 
728  if ( $allowUserJs ) {
729  $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
730  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
731  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
732  }
733 
734  $defaultPreferences['commoncssjs'] = [
735  'type' => 'info',
736  'raw' => true,
737  'default' => $context->getLanguage()->pipeList( $linkTools ),
738  'label-message' => 'prefs-common-config',
739  'section' => 'rendering/skin',
740  ];
741  }
742  }
743 
748  protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
749  # # Files #####################################
750  $defaultPreferences['imagesize'] = [
751  'type' => 'select',
752  'options' => $this->getImageSizes( $context ),
753  'label-message' => 'imagemaxsize',
754  'section' => 'rendering/files',
755  ];
756  $defaultPreferences['thumbsize'] = [
757  'type' => 'select',
758  'options' => $this->getThumbSizes( $context ),
759  'label-message' => 'thumbsize',
760  'section' => 'rendering/files',
761  ];
762  }
763 
770  protected function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) {
771  # # Date and time #####################################
772  $dateOptions = $this->getDateOptions( $context );
773  if ( $dateOptions ) {
774  $defaultPreferences['date'] = [
775  'type' => 'radio',
776  'options' => $dateOptions,
777  'section' => 'rendering/dateformat',
778  ];
779  }
780 
781  // Info
782  $now = wfTimestampNow();
783  $lang = $context->getLanguage();
784  $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ],
785  $lang->userTime( $now, $user ) );
786  $nowserver = $lang->userTime( $now, $user,
787  [ 'format' => false, 'timecorrection' => false ] ) .
788  Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
789 
790  $defaultPreferences['nowserver'] = [
791  'type' => 'info',
792  'raw' => 1,
793  'label-message' => 'servertime',
794  'default' => $nowserver,
795  'section' => 'rendering/timeoffset',
796  ];
797 
798  $defaultPreferences['nowlocal'] = [
799  'type' => 'info',
800  'raw' => 1,
801  'label-message' => 'localtime',
802  'default' => $nowlocal,
803  'section' => 'rendering/timeoffset',
804  ];
805 
806  // Grab existing pref.
807  $tzOffset = $user->getOption( 'timecorrection' );
808  $tz = explode( '|', $tzOffset, 3 );
809 
810  $tzOptions = $this->getTimezoneOptions( $context );
811 
812  $tzSetting = $tzOffset;
813  if ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
814  !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
815  ) {
816  // Timezone offset can vary with DST
817  try {
818  $userTZ = new DateTimeZone( $tz[2] );
819  $minDiff = floor( $userTZ->getOffset( new DateTime( 'now' ) ) / 60 );
820  $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
821  } catch ( Exception $e ) {
822  // User has an invalid time zone set. Fall back to just using the offset
823  $tz[0] = 'Offset';
824  }
825  }
826  if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
827  $minDiff = $tz[1];
828  $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
829  }
830 
831  $defaultPreferences['timecorrection'] = [
833  'label-message' => 'timezonelegend',
834  'options' => $tzOptions,
835  'default' => $tzSetting,
836  'size' => 20,
837  'section' => 'rendering/timeoffset',
838  'id' => 'wpTimeCorrection',
839  'filter' => TimezoneFilter::class,
840  'placeholder-message' => 'timezone-useoffset-placeholder',
841  ];
842  }
843 
849  protected function renderingPreferences(
850  User $user,
851  MessageLocalizer $l10n,
852  &$defaultPreferences
853  ) {
854  # # Diffs ####################################
855  $defaultPreferences['diffonly'] = [
856  'type' => 'toggle',
857  'section' => 'rendering/diffs',
858  'label-message' => 'tog-diffonly',
859  ];
860  $defaultPreferences['norollbackdiff'] = [
861  'type' => 'toggle',
862  'section' => 'rendering/diffs',
863  'label-message' => 'tog-norollbackdiff',
864  ];
865 
866  # # Page Rendering ##############################
867  if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
868  $defaultPreferences['underline'] = [
869  'type' => 'select',
870  'options' => [
871  $l10n->msg( 'underline-never' )->text() => 0,
872  $l10n->msg( 'underline-always' )->text() => 1,
873  $l10n->msg( 'underline-default' )->text() => 2,
874  ],
875  'label-message' => 'tog-underline',
876  'section' => 'rendering/advancedrendering',
877  ];
878  }
879 
880  $stubThresholdValues = [ 50, 100, 500, 1000, 2000, 5000, 10000 ];
881  $stubThresholdOptions = [ $l10n->msg( 'stub-threshold-disabled' )->text() => 0 ];
882  foreach ( $stubThresholdValues as $value ) {
883  $stubThresholdOptions[$l10n->msg( 'size-bytes', $value )->text()] = $value;
884  }
885 
886  $defaultPreferences['stubthreshold'] = [
887  'type' => 'select',
888  'section' => 'rendering/advancedrendering',
889  'options' => $stubThresholdOptions,
890  // This is not a raw HTML message; label-raw is needed for the manual <a></a>
891  'label-raw' => $l10n->msg( 'stub-threshold' )->rawParams(
892  '<a class="stub">' .
893  $l10n->msg( 'stub-threshold-sample-link' )->parse() .
894  '</a>' )->parse(),
895  ];
896 
897  $defaultPreferences['showhiddencats'] = [
898  'type' => 'toggle',
899  'section' => 'rendering/advancedrendering',
900  'label-message' => 'tog-showhiddencats'
901  ];
902 
903  $defaultPreferences['numberheadings'] = [
904  'type' => 'toggle',
905  'section' => 'rendering/advancedrendering',
906  'label-message' => 'tog-numberheadings',
907  ];
908 
909  if ( $user->isAllowed( 'rollback' ) ) {
910  $defaultPreferences['showrollbackconfirmation'] = [
911  'type' => 'toggle',
912  'section' => 'rendering/advancedrendering',
913  'label-message' => 'tog-showrollbackconfirmation',
914  ];
915  }
916  }
917 
923  protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
924  # # Editing #####################################
925  $defaultPreferences['editsectiononrightclick'] = [
926  'type' => 'toggle',
927  'section' => 'editing/advancedediting',
928  'label-message' => 'tog-editsectiononrightclick',
929  ];
930  $defaultPreferences['editondblclick'] = [
931  'type' => 'toggle',
932  'section' => 'editing/advancedediting',
933  'label-message' => 'tog-editondblclick',
934  ];
935 
936  if ( $this->options->get( 'AllowUserCssPrefs' ) ) {
937  $defaultPreferences['editfont'] = [
938  'type' => 'select',
939  'section' => 'editing/editor',
940  'label-message' => 'editfont-style',
941  'options' => [
942  $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
943  $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
944  $l10n->msg( 'editfont-serif' )->text() => 'serif',
945  ]
946  ];
947  }
948 
949  if ( $user->isAllowed( 'minoredit' ) ) {
950  $defaultPreferences['minordefault'] = [
951  'type' => 'toggle',
952  'section' => 'editing/editor',
953  'label-message' => 'tog-minordefault',
954  ];
955  }
956 
957  $defaultPreferences['forceeditsummary'] = [
958  'type' => 'toggle',
959  'section' => 'editing/editor',
960  'label-message' => 'tog-forceeditsummary',
961  ];
962  $defaultPreferences['useeditwarning'] = [
963  'type' => 'toggle',
964  'section' => 'editing/editor',
965  'label-message' => 'tog-useeditwarning',
966  ];
967 
968  $defaultPreferences['previewonfirst'] = [
969  'type' => 'toggle',
970  'section' => 'editing/preview',
971  'label-message' => 'tog-previewonfirst',
972  ];
973  $defaultPreferences['previewontop'] = [
974  'type' => 'toggle',
975  'section' => 'editing/preview',
976  'label-message' => 'tog-previewontop',
977  ];
978  $defaultPreferences['uselivepreview'] = [
979  'type' => 'toggle',
980  'section' => 'editing/preview',
981  'label-message' => 'tog-uselivepreview',
982  ];
983  }
984 
990  protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
991  $rcMaxAge = $this->options->get( 'RCMaxAge' );
992  # # RecentChanges #####################################
993  $defaultPreferences['rcdays'] = [
994  'type' => 'float',
995  'label-message' => 'recentchangesdays',
996  'section' => 'rc/displayrc',
997  'min' => 1 / 24,
998  'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
999  'help' => $l10n->msg( 'recentchangesdays-max' )->numParams(
1000  ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped()
1001  ];
1002  $defaultPreferences['rclimit'] = [
1003  'type' => 'int',
1004  'min' => 1,
1005  'max' => 1000,
1006  'label-message' => 'recentchangescount',
1007  'help-message' => 'prefs-help-recentchangescount',
1008  'section' => 'rc/displayrc',
1009  'filter' => IntvalFilter::class,
1010  ];
1011  $defaultPreferences['usenewrc'] = [
1012  'type' => 'toggle',
1013  'label-message' => 'tog-usenewrc',
1014  'section' => 'rc/advancedrc',
1015  ];
1016  $defaultPreferences['hideminor'] = [
1017  'type' => 'toggle',
1018  'label-message' => 'tog-hideminor',
1019  'section' => 'rc/changesrc',
1020  ];
1021  $defaultPreferences['rcfilters-rc-collapsed'] = [
1022  'type' => 'api',
1023  ];
1024  $defaultPreferences['rcfilters-wl-collapsed'] = [
1025  'type' => 'api',
1026  ];
1027  $defaultPreferences['rcfilters-saved-queries'] = [
1028  'type' => 'api',
1029  ];
1030  $defaultPreferences['rcfilters-wl-saved-queries'] = [
1031  'type' => 'api',
1032  ];
1033  // Override RCFilters preferences for RecentChanges 'limit'
1034  $defaultPreferences['rcfilters-limit'] = [
1035  'type' => 'api',
1036  ];
1037  $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
1038  'type' => 'api',
1039  ];
1040  $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
1041  'type' => 'api',
1042  ];
1043 
1044  if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
1045  $defaultPreferences['hidecategorization'] = [
1046  'type' => 'toggle',
1047  'label-message' => 'tog-hidecategorization',
1048  'section' => 'rc/changesrc',
1049  ];
1050  }
1051 
1052  if ( $user->useRCPatrol() ) {
1053  $defaultPreferences['hidepatrolled'] = [
1054  'type' => 'toggle',
1055  'section' => 'rc/changesrc',
1056  'label-message' => 'tog-hidepatrolled',
1057  ];
1058  }
1059 
1060  if ( $user->useNPPatrol() ) {
1061  $defaultPreferences['newpageshidepatrolled'] = [
1062  'type' => 'toggle',
1063  'section' => 'rc/changesrc',
1064  'label-message' => 'tog-newpageshidepatrolled',
1065  ];
1066  }
1067 
1068  if ( $this->options->get( 'RCShowWatchingUsers' ) ) {
1069  $defaultPreferences['shownumberswatching'] = [
1070  'type' => 'toggle',
1071  'section' => 'rc/advancedrc',
1072  'label-message' => 'tog-shownumberswatching',
1073  ];
1074  }
1075 
1076  $defaultPreferences['rcenhancedfilters-disable'] = [
1077  'type' => 'toggle',
1078  'section' => 'rc/advancedrc',
1079  'label-message' => 'rcfilters-preference-label',
1080  'help-message' => 'rcfilters-preference-help',
1081  ];
1082  }
1083 
1089  protected function watchlistPreferences(
1090  User $user, IContextSource $context, &$defaultPreferences
1091  ) {
1092  $watchlistdaysMax = ceil( $this->options->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
1093 
1094  # # Watchlist #####################################
1095  if ( $user->isAllowed( 'editmywatchlist' ) ) {
1096  $editWatchlistLinks = '';
1097  $editWatchlistModes = [
1098  'edit' => [ 'subpage' => false, 'flags' => [] ],
1099  'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
1100  'clear' => [ 'subpage' => 'clear', 'flags' => [ 'destructive' ] ],
1101  ];
1102  foreach ( $editWatchlistModes as $mode => $options ) {
1103  // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1104  $editWatchlistLinks .=
1105  new \OOUI\ButtonWidget( [
1106  'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
1107  'flags' => $options[ 'flags' ],
1108  'label' => new \OOUI\HtmlSnippet(
1109  $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
1110  ),
1111  ] );
1112  }
1113 
1114  $defaultPreferences['editwatchlist'] = [
1115  'type' => 'info',
1116  'raw' => true,
1117  'default' => $editWatchlistLinks,
1118  'label-message' => 'prefs-editwatchlist-label',
1119  'section' => 'watchlist/editwatchlist',
1120  ];
1121  }
1122 
1123  $defaultPreferences['watchlistdays'] = [
1124  'type' => 'float',
1125  'min' => 1 / 24,
1126  'max' => $watchlistdaysMax,
1127  'section' => 'watchlist/displaywatchlist',
1128  'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams(
1129  $watchlistdaysMax )->escaped(),
1130  'label-message' => 'prefs-watchlist-days',
1131  ];
1132  $defaultPreferences['wllimit'] = [
1133  'type' => 'int',
1134  'min' => 1,
1135  'max' => 1000,
1136  'label-message' => 'prefs-watchlist-edits',
1137  'help' => $context->msg( 'prefs-watchlist-edits-max' )->escaped(),
1138  'section' => 'watchlist/displaywatchlist',
1139  'filter' => IntvalFilter::class,
1140  ];
1141  $defaultPreferences['extendwatchlist'] = [
1142  'type' => 'toggle',
1143  'section' => 'watchlist/advancedwatchlist',
1144  'label-message' => 'tog-extendwatchlist',
1145  ];
1146  $defaultPreferences['watchlisthideminor'] = [
1147  'type' => 'toggle',
1148  'section' => 'watchlist/changeswatchlist',
1149  'label-message' => 'tog-watchlisthideminor',
1150  ];
1151  $defaultPreferences['watchlisthidebots'] = [
1152  'type' => 'toggle',
1153  'section' => 'watchlist/changeswatchlist',
1154  'label-message' => 'tog-watchlisthidebots',
1155  ];
1156  $defaultPreferences['watchlisthideown'] = [
1157  'type' => 'toggle',
1158  'section' => 'watchlist/changeswatchlist',
1159  'label-message' => 'tog-watchlisthideown',
1160  ];
1161  $defaultPreferences['watchlisthideanons'] = [
1162  'type' => 'toggle',
1163  'section' => 'watchlist/changeswatchlist',
1164  'label-message' => 'tog-watchlisthideanons',
1165  ];
1166  $defaultPreferences['watchlisthideliu'] = [
1167  'type' => 'toggle',
1168  'section' => 'watchlist/changeswatchlist',
1169  'label-message' => 'tog-watchlisthideliu',
1170  ];
1171 
1173  $defaultPreferences['watchlistreloadautomatically'] = [
1174  'type' => 'toggle',
1175  'section' => 'watchlist/advancedwatchlist',
1176  'label-message' => 'tog-watchlistreloadautomatically',
1177  ];
1178  }
1179 
1180  $defaultPreferences['watchlistunwatchlinks'] = [
1181  'type' => 'toggle',
1182  'section' => 'watchlist/advancedwatchlist',
1183  'label-message' => 'tog-watchlistunwatchlinks',
1184  ];
1185 
1186  if ( $this->options->get( 'RCWatchCategoryMembership' ) ) {
1187  $defaultPreferences['watchlisthidecategorization'] = [
1188  'type' => 'toggle',
1189  'section' => 'watchlist/changeswatchlist',
1190  'label-message' => 'tog-watchlisthidecategorization',
1191  ];
1192  }
1193 
1194  if ( $user->useRCPatrol() ) {
1195  $defaultPreferences['watchlisthidepatrolled'] = [
1196  'type' => 'toggle',
1197  'section' => 'watchlist/changeswatchlist',
1198  'label-message' => 'tog-watchlisthidepatrolled',
1199  ];
1200  }
1201 
1202  $watchTypes = [
1203  'edit' => 'watchdefault',
1204  'move' => 'watchmoves',
1205  'delete' => 'watchdeletion'
1206  ];
1207 
1208  // Kinda hacky
1209  if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
1210  $watchTypes['read'] = 'watchcreations';
1211  }
1212 
1213  if ( $user->isAllowed( 'rollback' ) ) {
1214  $watchTypes['rollback'] = 'watchrollback';
1215  }
1216 
1217  if ( $user->isAllowed( 'upload' ) ) {
1218  $watchTypes['upload'] = 'watchuploads';
1219  }
1220 
1221  foreach ( $watchTypes as $action => $pref ) {
1222  if ( $user->isAllowed( $action ) ) {
1223  // Messages:
1224  // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1225  // tog-watchrollback
1226  $defaultPreferences[$pref] = [
1227  'type' => 'toggle',
1228  'section' => 'watchlist/pageswatchlist',
1229  'label-message' => "tog-$pref",
1230  ];
1231  }
1232  }
1233 
1234  $defaultPreferences['watchlisttoken'] = [
1235  'type' => 'api',
1236  ];
1237 
1238  $tokenButton = new \OOUI\ButtonWidget( [
1239  'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1240  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1241  ] ),
1242  'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1243  ] );
1244  $defaultPreferences['watchlisttoken-info'] = [
1245  'type' => 'info',
1246  'section' => 'watchlist/tokenwatchlist',
1247  'label-message' => 'prefs-watchlist-token',
1248  'help-message' => 'prefs-help-tokenmanagement',
1249  'raw' => true,
1250  'default' => (string)$tokenButton,
1251  ];
1252 
1253  $defaultPreferences['wlenhancedfilters-disable'] = [
1254  'type' => 'toggle',
1255  'section' => 'watchlist/advancedwatchlist',
1256  'label-message' => 'rcfilters-watchlist-preference-label',
1257  'help-message' => 'rcfilters-watchlist-preference-help',
1258  ];
1259  }
1260 
1264  protected function searchPreferences( &$defaultPreferences ) {
1265  foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1266  $defaultPreferences['searchNs' . $n] = [
1267  'type' => 'api',
1268  ];
1269  }
1270  }
1271 
1278  $ret = [];
1279 
1280  $mptitle = Title::newMainPage();
1281  $previewtext = $context->msg( 'skin-preview' )->escaped();
1282 
1283  # Only show skins that aren't disabled in $wgSkipSkins
1284  $validSkinNames = Skin::getAllowedSkins();
1285  $allInstalledSkins = Skin::getSkinNames();
1286 
1287  // Display the installed skin the user has specifically requested via useskin=….
1288  $useSkin = $context->getRequest()->getRawVal( 'useskin' );
1289  if ( isset( $allInstalledSkins[$useSkin] )
1290  && $context->msg( "skinname-$useSkin" )->exists()
1291  ) {
1292  $validSkinNames[$useSkin] = $useSkin;
1293  }
1294 
1295  // Display the skin if the user has set it as a preference already before it was hidden.
1296  $currentUserSkin = $user->getOption( 'skin' );
1297  if ( isset( $allInstalledSkins[$currentUserSkin] )
1298  && $context->msg( "skinname-$useSkin" )->exists()
1299  ) {
1300  $validSkinNames[$currentUserSkin] = $currentUserSkin;
1301  }
1302 
1303  foreach ( $validSkinNames as $skinkey => &$skinname ) {
1304  $msg = $context->msg( "skinname-{$skinkey}" );
1305  if ( $msg->exists() ) {
1306  $skinname = htmlspecialchars( $msg->text() );
1307  }
1308  }
1309 
1310  $defaultSkin = $this->options->get( 'DefaultSkin' );
1311  $allowUserCss = $this->options->get( 'AllowUserCss' );
1312  $allowUserJs = $this->options->get( 'AllowUserJs' );
1313 
1314  # Sort by the internal name, so that the ordering is the same for each display language,
1315  # especially if some skin names are translated to use a different alphabet and some are not.
1316  uksort( $validSkinNames, function ( $a, $b ) use ( $defaultSkin ) {
1317  # Display the default first in the list by comparing it as lesser than any other.
1318  if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1319  return -1;
1320  }
1321  if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1322  return 1;
1323  }
1324  return strcasecmp( $a, $b );
1325  } );
1326 
1327  $foundDefault = false;
1328  foreach ( $validSkinNames as $skinkey => $sn ) {
1329  $linkTools = [];
1330 
1331  # Mark the default skin
1332  if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1333  $linkTools[] = $context->msg( 'default' )->escaped();
1334  $foundDefault = true;
1335  }
1336 
1337  # Create preview link
1338  $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1339  $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1340 
1341  # Create links to user CSS/JS pages
1342  if ( $allowUserCss ) {
1343  $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1344  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1345  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1346  }
1347 
1348  if ( $allowUserJs ) {
1349  $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1350  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1351  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1352  }
1353 
1354  $display = $sn . ' ' . $context->msg( 'parentheses' )
1355  ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1356  ->escaped();
1357  $ret[$display] = $skinkey;
1358  }
1359 
1360  if ( !$foundDefault ) {
1361  // If the default skin is not available, things are going to break horribly because the
1362  // default value for skin selector will not be a valid value. Let's just not show it then.
1363  return [];
1364  }
1365 
1366  return $ret;
1367  }
1368 
1373  protected function getDateOptions( IContextSource $context ) {
1374  $lang = $context->getLanguage();
1375  $dateopts = $lang->getDatePreferences();
1376 
1377  $ret = [];
1378 
1379  if ( $dateopts ) {
1380  if ( !in_array( 'default', $dateopts ) ) {
1381  $dateopts[] = 'default'; // Make sure default is always valid T21237
1382  }
1383 
1384  // FIXME KLUGE: site default might not be valid for user language
1385  global $wgDefaultUserOptions;
1386  if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1387  $wgDefaultUserOptions['date'] = 'default';
1388  }
1389 
1390  $epoch = wfTimestampNow();
1391  foreach ( $dateopts as $key ) {
1392  if ( $key == 'default' ) {
1393  $formatted = $context->msg( 'datedefault' )->escaped();
1394  } else {
1395  $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1396  }
1397  $ret[$formatted] = $key;
1398  }
1399  }
1400  return $ret;
1401  }
1402 
1407  protected function getImageSizes( MessageLocalizer $l10n ) {
1408  $ret = [];
1409  $pixels = $l10n->msg( 'unit-pixel' )->text();
1410 
1411  foreach ( $this->options->get( 'ImageLimits' ) as $index => $limits ) {
1412  // Note: A left-to-right marker (U+200E) is inserted, see T144386
1413  $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1414  $ret[$display] = $index;
1415  }
1416 
1417  return $ret;
1418  }
1419 
1424  protected function getThumbSizes( MessageLocalizer $l10n ) {
1425  $ret = [];
1426  $pixels = $l10n->msg( 'unit-pixel' )->text();
1427 
1428  foreach ( $this->options->get( 'ThumbLimits' ) as $index => $size ) {
1429  $display = $size . $pixels;
1430  $ret[$display] = $index;
1431  }
1432 
1433  return $ret;
1434  }
1435 
1442  protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1443  $maxSigChars = $this->options->get( 'MaxSigChars' );
1444  if ( mb_strlen( $signature ) > $maxSigChars ) {
1445  return Xml::element( 'span', [ 'class' => 'error' ],
1446  $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
1447  } elseif ( isset( $alldata['fancysig'] ) &&
1448  $alldata['fancysig'] &&
1449  MediaWikiServices::getInstance()->getParser()->validateSig( $signature ) === false
1450  ) {
1451  return Xml::element(
1452  'span',
1453  [ 'class' => 'error' ],
1454  $form->msg( 'badsig' )->text()
1455  );
1456  } else {
1457  return true;
1458  }
1459  }
1460 
1467  protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
1468  $parser = MediaWikiServices::getInstance()->getParser();
1469  if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1470  $signature = $parser->cleanSig( $signature );
1471  } else {
1472  // When no fancy sig used, make sure ~{3,5} get removed.
1473  $signature = Parser::cleanSigInSig( $signature );
1474  }
1475 
1476  return $signature;
1477  }
1478 
1486  public function getForm(
1487  User $user,
1489  $formClass = PreferencesFormOOUI::class,
1490  array $remove = []
1491  ) {
1492  // We use ButtonWidgets in some of the getPreferences() functions
1493  $context->getOutput()->enableOOUI();
1494 
1495  $formDescriptor = $this->getFormDescriptor( $user, $context );
1496  if ( count( $remove ) ) {
1497  $removeKeys = array_flip( $remove );
1498  $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1499  }
1500 
1501  // Remove type=api preferences. They are not intended for rendering in the form.
1502  foreach ( $formDescriptor as $name => $info ) {
1503  if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1504  unset( $formDescriptor[$name] );
1505  }
1506  }
1507 
1511  $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1512 
1513  // This allows users to opt-in to hidden skins. While this should be discouraged and is not
1514  // discoverable, this allows users to still use hidden skins while preventing new users from
1515  // adopting unsupported skins. If no useskin=… parameter was provided, it will not show up
1516  // in the resulting URL.
1517  $htmlForm->setAction( $context->getTitle()->getLocalURL( [
1518  'useskin' => $context->getRequest()->getRawVal( 'useskin' )
1519  ] ) );
1520 
1521  $htmlForm->setModifiedUser( $user );
1522  $htmlForm->setId( 'mw-prefs-form' );
1523  $htmlForm->setAutocomplete( 'off' );
1524  $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() );
1525  # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1526  $htmlForm->setSubmitTooltip( 'preferences-save' );
1527  $htmlForm->setSubmitID( 'prefcontrol' );
1528  $htmlForm->setSubmitCallback(
1529  function ( array $formData, HTMLForm $form ) use ( $formDescriptor ) {
1530  return $this->submitForm( $formData, $form, $formDescriptor );
1531  }
1532  );
1533 
1534  return $htmlForm;
1535  }
1536 
1542  $opt = [];
1543 
1544  $localTZoffset = $this->options->get( 'LocalTZoffset' );
1545  $timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
1546 
1547  $timestamp = MWTimestamp::getLocalInstance();
1548  // Check that the LocalTZoffset is the same as the local time zone offset
1549  if ( $localTZoffset == $timestamp->format( 'Z' ) / 60 ) {
1550  $timezoneName = $timestamp->getTimezone()->getName();
1551  // Localize timezone
1552  if ( isset( $timeZoneList[$timezoneName] ) ) {
1553  $timezoneName = $timeZoneList[$timezoneName]['name'];
1554  }
1555  $server_tz_msg = $context->msg(
1556  'timezoneuseserverdefault',
1557  $timezoneName
1558  )->text();
1559  } else {
1560  $tzstring = sprintf(
1561  '%+03d:%02d',
1562  floor( $localTZoffset / 60 ),
1563  abs( $localTZoffset ) % 60
1564  );
1565  $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
1566  }
1567  $opt[$server_tz_msg] = "System|$localTZoffset";
1568  $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
1569  $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
1570 
1571  foreach ( $timeZoneList as $timeZoneInfo ) {
1572  $region = $timeZoneInfo['region'];
1573  if ( !isset( $opt[$region] ) ) {
1574  $opt[$region] = [];
1575  }
1576  $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
1577  }
1578  return $opt;
1579  }
1580 
1589  protected function saveFormData( $formData, HTMLForm $form, array $formDescriptor ) {
1591  $user = $form->getModifiedUser();
1592  $hiddenPrefs = $this->options->get( 'HiddenPrefs' );
1593  $result = true;
1594 
1595  if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
1596  return Status::newFatal( 'mypreferencesprotected' );
1597  }
1598 
1599  // Filter input
1600  $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
1601 
1602  // Fortunately, the realname field is MUCH simpler
1603  // (not really "private", but still shouldn't be edited without permission)
1604 
1605  if ( !in_array( 'realname', $hiddenPrefs )
1606  && $user->isAllowed( 'editmyprivateinfo' )
1607  && array_key_exists( 'realname', $formData )
1608  ) {
1609  $realName = $formData['realname'];
1610  $user->setRealName( $realName );
1611  }
1612 
1613  if ( $user->isAllowed( 'editmyoptions' ) ) {
1614  $oldUserOptions = $user->getOptions();
1615 
1616  foreach ( $this->getSaveBlacklist() as $b ) {
1617  unset( $formData[$b] );
1618  }
1619 
1620  # If users have saved a value for a preference which has subsequently been disabled
1621  # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1622  # is subsequently re-enabled
1623  foreach ( $hiddenPrefs as $pref ) {
1624  # If the user has not set a non-default value here, the default will be returned
1625  # and subsequently discarded
1626  $formData[$pref] = $user->getOption( $pref, null, true );
1627  }
1628 
1629  // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
1630  if (
1631  isset( $formData['rclimit'] ) &&
1632  intval( $formData[ 'rclimit' ] ) !== $user->getIntOption( 'rclimit' )
1633  ) {
1634  $formData['rcfilters-limit'] = $formData['rclimit'];
1635  }
1636 
1637  // Keep old preferences from interfering due to back-compat code, etc.
1638  $user->resetOptions( 'unused', $form->getContext() );
1639 
1640  foreach ( $formData as $key => $value ) {
1641  $user->setOption( $key, $value );
1642  }
1643 
1644  Hooks::run(
1645  'PreferencesFormPreSave',
1646  [ $formData, $form, $user, &$result, $oldUserOptions ]
1647  );
1648  }
1649 
1650  $user->saveSettings();
1651 
1652  return $result;
1653  }
1654 
1663  protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1664  foreach ( $formDescriptor as $preference => $desc ) {
1665  if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
1666  continue;
1667  }
1668  $filterDesc = $desc['filter'];
1669  if ( $filterDesc instanceof Filter ) {
1670  $filter = $filterDesc;
1671  } elseif ( class_exists( $filterDesc ) ) {
1672  $filter = new $filterDesc();
1673  } elseif ( is_callable( $filterDesc ) ) {
1674  $filter = $filterDesc();
1675  } else {
1676  throw new UnexpectedValueException(
1677  "Unrecognized filter type for preference '$preference'"
1678  );
1679  }
1680  $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1681  }
1682  }
1683 
1692  protected function submitForm( array $formData, HTMLForm $form, array $formDescriptor ) {
1693  $res = $this->saveFormData( $formData, $form, $formDescriptor );
1694 
1695  if ( $res === true ) {
1696  $context = $form->getContext();
1697  $urlOptions = [];
1698 
1699  if ( $res === 'eauth' ) {
1700  $urlOptions['eauth'] = 1;
1701  }
1702 
1703  $urlOptions += $form->getExtraSuccessRedirectParameters();
1704 
1705  $url = $form->getTitle()->getFullURL( $urlOptions );
1706 
1707  // Set session data for the success message
1708  $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
1709 
1710  $context->getOutput()->redirect( $url );
1711  }
1712 
1713  return ( $res === true ? Status::newGood() : $res );
1714  }
1715 
1724  protected function getTimeZoneList( Language $language ) {
1725  $identifiers = DateTimeZone::listIdentifiers();
1726  if ( $identifiers === false ) {
1727  return [];
1728  }
1729  sort( $identifiers );
1730 
1731  $tzRegions = [
1732  'Africa' => wfMessage( 'timezoneregion-africa' )->inLanguage( $language )->text(),
1733  'America' => wfMessage( 'timezoneregion-america' )->inLanguage( $language )->text(),
1734  'Antarctica' => wfMessage( 'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1735  'Arctic' => wfMessage( 'timezoneregion-arctic' )->inLanguage( $language )->text(),
1736  'Asia' => wfMessage( 'timezoneregion-asia' )->inLanguage( $language )->text(),
1737  'Atlantic' => wfMessage( 'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1738  'Australia' => wfMessage( 'timezoneregion-australia' )->inLanguage( $language )->text(),
1739  'Europe' => wfMessage( 'timezoneregion-europe' )->inLanguage( $language )->text(),
1740  'Indian' => wfMessage( 'timezoneregion-indian' )->inLanguage( $language )->text(),
1741  'Pacific' => wfMessage( 'timezoneregion-pacific' )->inLanguage( $language )->text(),
1742  ];
1743  asort( $tzRegions );
1744 
1745  $timeZoneList = [];
1746 
1747  $now = new DateTime();
1748 
1749  foreach ( $identifiers as $identifier ) {
1750  $parts = explode( '/', $identifier, 2 );
1751 
1752  // DateTimeZone::listIdentifiers() returns a number of
1753  // backwards-compatibility entries. This filters them out of the
1754  // list presented to the user.
1755  if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1756  continue;
1757  }
1758 
1759  // Localize region
1760  $parts[0] = $tzRegions[$parts[0]];
1761 
1762  $dateTimeZone = new DateTimeZone( $identifier );
1763  $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1764 
1765  $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
1766  $value = "ZoneInfo|$minDiff|$identifier";
1767 
1768  $timeZoneList[$identifier] = [
1769  'name' => $display,
1770  'timecorrection' => $value,
1771  'region' => $parts[0],
1772  ];
1773  }
1774 
1775  return $timeZoneList;
1776  }
1777 }
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:2911
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
saveFormData( $formData, HTMLForm $form, array $formDescriptor)
Handle the form submission if everything validated properly.
editingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2633
static fetchLanguageNames( $inLanguage=self::AS_AUTONYMS, $include='mw')
Get an array of language names, indexed by code.
Definition: Language.php:840
generateSkinOptions(User $user, IContextSource $context)
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:653
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1982
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3477
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead & $formDescriptor
Definition: hooks.txt:2061
getTimeZoneList(Language $language)
Get a list of all time zones.
validateSignature( $signature, $alldata, HTMLForm $form)
if(!isset( $args[0])) $lang
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3028
static checkStructuredFilterUiEnabled( $user)
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:57
getRealName()
Get the user&#39;s real name.
Definition: User.php:3001
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
getFormDescriptor(User $user, IContextSource $context)
$value
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
string $action
Cache what action this request is.
Definition: MediaWiki.php:48
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
Base interface for user preference filters that work as a middleware between storage and interface...
Definition: Filter.php:27
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4834
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1799
static getInstance()
Returns the global default instance of the top level service locator.
static array $constructorOptions
TODO Make this a const when we drop HHVM support (T192166)
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition: hooks.txt:1799
applyFilters(array &$preferences, array $formDescriptor, $verb)
Applies filters to preferences either before or after form usage.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2311
This is the default implementation of PreferencesFactory.
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1980
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6408
A class for passing options to services.
profilePreferences(User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS)
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
cleanSignature( $signature, $alldata, HTMLForm $form)
static loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent=null)
Initialise a new Object for the field.
Definition: HTMLForm.php:490
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4823
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3464
Class that generates HTML links for pages.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
IContextSource $context
Definition: MediaWiki.php:38
Interface for configuration instances.
Definition: Config.php:28
$res
Definition: database.txt:21
__construct( $options, Language $contLang, AuthManager $authManager, LinkRenderer $linkRenderer, NamespaceInfo $nsInfo=null)
Do not call this directly.
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
getOptionFromUser( $name, $info, array $userOptions)
Pull option from a user account.
getTitle()
Get the title.
Definition: HTMLForm.php:1595
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
getContext()
Get the base IContextSource object.
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3730
datetimePreferences( $user, IContextSource $context, &$defaultPreferences)
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
$filter
This serves as the entry point to the authentication system.
Definition: AuthManager.php:85
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:780
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:3743
This is a value object for authentication requests with a username and password.
loadPreferenceValues(User $user, IContextSource $context, &$defaultPreferences)
Loads existing values for a given array of preferences.
rcPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys, without regard for order.
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
renderingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
msg( $key)
This is the method for getting translated interface messages.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place. Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3056
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:2921
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:797
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3553
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
Language $contLang
The wiki&#39;s content language.
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:3752
filesPreferences(IContextSource $context, &$defaultPreferences)
skinPreferences(User $user, IContextSource $context, &$defaultPreferences)
watchlistPreferences(User $user, IContextSource $context, &$defaultPreferences)
A PreferencesFactory is a MediaWiki service that provides the definitions of preferences for a given ...
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
getForm(User $user, IContextSource $context, $formClass=PreferencesFormOOUI::class, array $remove=[])
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1750
static array $languagesWithVariants
languages supporting variants
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
Definition: Skin.php:81
submitForm(array $formData, HTMLForm $form, array $formDescriptor)
Save the form data and reload the page.
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
switch( $options['output']) $languages
Definition: transstat.php:76
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...