MediaWiki  master
DefaultPreferencesFactory.php
Go to the documentation of this file.
1 <?php
22 
23 use Config;
27 use Hooks;
28 use Html;
44 use Parser;
49 use Skin;
51 use Status;
52 use Title;
54 use User;
56 use Xml;
57 
63 
65  protected $config;
66 
68  protected $contLang;
69 
71  protected $authManager;
72 
74  protected $linkRenderer;
75 
82  public function __construct(
87  ) {
88  $this->config = $config;
89  $this->contLang = $contLang;
90  $this->authManager = $authManager;
91  $this->linkRenderer = $linkRenderer;
92  $this->logger = new NullLogger();
93  }
94 
98  public function getSaveBlacklist() {
99  return [
100  'realname',
101  'emailaddress',
102  ];
103  }
104 
112  $preferences = [];
113 
115  strtolower( $context->getSkin()->getSkinName() ),
116  $context->getLanguage()->getDir()
117  );
118 
119  $canIPUseHTTPS = wfCanIPUseHTTPS( $context->getRequest()->getIP() );
120  $this->profilePreferences( $user, $context, $preferences, $canIPUseHTTPS );
121  $this->skinPreferences( $user, $context, $preferences );
122  $this->datetimePreferences( $user, $context, $preferences );
123  $this->filesPreferences( $context, $preferences );
124  $this->renderingPreferences( $context, $preferences );
125  $this->editingPreferences( $user, $context, $preferences );
126  $this->rcPreferences( $user, $context, $preferences );
127  $this->watchlistPreferences( $user, $context, $preferences );
128  $this->searchPreferences( $preferences );
129 
130  Hooks::run( 'GetPreferences', [ $user, &$preferences ] );
131 
132  $this->loadPreferenceValues( $user, $context, $preferences );
133  $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
134  return $preferences;
135  }
136 
145  private function loadPreferenceValues(
146  User $user, IContextSource $context, &$defaultPreferences
147  ) {
148  # # Remove preferences that wikis don't want to use
149  foreach ( $this->config->get( 'HiddenPrefs' ) as $pref ) {
150  if ( isset( $defaultPreferences[$pref] ) ) {
151  unset( $defaultPreferences[$pref] );
152  }
153  }
154 
155  # # Make sure that form fields have their parent set. See T43337.
156  $dummyForm = new HTMLForm( [], $context );
157 
158  $disable = !$user->isAllowed( 'editmyoptions' );
159 
160  $defaultOptions = User::getDefaultOptions();
161  $userOptions = $user->getOptions();
162  $this->applyFilters( $userOptions, $defaultPreferences, 'filterForForm' );
163  # # Prod in defaults from the user
164  foreach ( $defaultPreferences as $name => &$info ) {
165  $prefFromUser = $this->getOptionFromUser( $name, $info, $userOptions );
166  if ( $disable && !in_array( $name, $this->getSaveBlacklist() ) ) {
167  $info['disabled'] = 'disabled';
168  }
169  $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation
170  $globalDefault = $defaultOptions[$name] ?? null;
171 
172  // If it validates, set it as the default
173  if ( isset( $info['default'] ) ) {
174  // Already set, no problem
175  continue;
176  } elseif ( !is_null( $prefFromUser ) && // Make sure we're not just pulling nothing
177  $field->validate( $prefFromUser, $user->getOptions() ) === true ) {
178  $info['default'] = $prefFromUser;
179  } elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) {
180  $info['default'] = $globalDefault;
181  } else {
182  throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
183  }
184  }
185 
186  return $defaultPreferences;
187  }
188 
197  protected function getOptionFromUser( $name, $info, array $userOptions ) {
198  $val = $userOptions[$name] ?? null;
199 
200  // Handling for multiselect preferences
201  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
202  ( isset( $info['class'] ) && $info['class'] == \HTMLMultiSelectField::class ) ) {
203  $options = HTMLFormField::flattenOptions( $info['options'] );
204  $prefix = $info['prefix'] ?? $name;
205  $val = [];
206 
207  foreach ( $options as $value ) {
208  if ( $userOptions["$prefix$value"] ?? false ) {
209  $val[] = $value;
210  }
211  }
212  }
213 
214  // Handling for checkmatrix preferences
215  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
216  ( isset( $info['class'] ) && $info['class'] == \HTMLCheckMatrix::class ) ) {
217  $columns = HTMLFormField::flattenOptions( $info['columns'] );
218  $rows = HTMLFormField::flattenOptions( $info['rows'] );
219  $prefix = $info['prefix'] ?? $name;
220  $val = [];
221 
222  foreach ( $columns as $column ) {
223  foreach ( $rows as $row ) {
224  if ( $userOptions["$prefix$column-$row"] ?? false ) {
225  $val[] = "$column-$row";
226  }
227  }
228  }
229  }
230 
231  return $val;
232  }
233 
243  protected function profilePreferences(
244  User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS
245  ) {
246  // retrieving user name for GENDER and misc.
247  $userName = $user->getName();
248 
249  # # User info #####################################
250  // Information panel
251  $defaultPreferences['username'] = [
252  'type' => 'info',
253  'label-message' => [ 'username', $userName ],
254  'default' => $userName,
255  'section' => 'personal/info',
256  ];
257 
258  $lang = $context->getLanguage();
259 
260  # Get groups to which the user belongs
261  $userEffectiveGroups = $user->getEffectiveGroups();
262  $userGroupMemberships = $user->getGroupMemberships();
263  $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
264  foreach ( $userEffectiveGroups as $ueg ) {
265  if ( $ueg == '*' ) {
266  // Skip the default * group, seems useless here
267  continue;
268  }
269 
270  $groupStringOrObject = $userGroupMemberships[$ueg] ?? $ueg;
271 
272  $userG = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html' );
273  $userM = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html',
274  $userName );
275 
276  // Store expiring groups separately, so we can place them before non-expiring
277  // groups in the list. This is to avoid the ambiguity of something like
278  // "administrator, bureaucrat (until X date)" -- users might wonder whether the
279  // expiry date applies to both groups, or just the last one
280  if ( $groupStringOrObject instanceof UserGroupMembership &&
281  $groupStringOrObject->getExpiry()
282  ) {
283  $userTempGroups[] = $userG;
284  $userTempMembers[] = $userM;
285  } else {
286  $userGroups[] = $userG;
287  $userMembers[] = $userM;
288  }
289  }
290  sort( $userGroups );
291  sort( $userMembers );
292  sort( $userTempGroups );
293  sort( $userTempMembers );
294  $userGroups = array_merge( $userTempGroups, $userGroups );
295  $userMembers = array_merge( $userTempMembers, $userMembers );
296 
297  $defaultPreferences['usergroups'] = [
298  'type' => 'info',
299  'label' => $context->msg( 'prefs-memberingroups' )->numParams(
300  count( $userGroups ) )->params( $userName )->parse(),
301  'default' => $context->msg( 'prefs-memberingroups-type' )
302  ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
303  ->escaped(),
304  'raw' => true,
305  'section' => 'personal/info',
306  ];
307 
308  $contribTitle = SpecialPage::getTitleFor( "Contributions", $userName );
309  $formattedEditCount = $lang->formatNum( $user->getEditCount() );
310  $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
311 
312  $defaultPreferences['editcount'] = [
313  'type' => 'info',
314  'raw' => true,
315  'label-message' => 'prefs-edits',
316  'default' => $editCount,
317  'section' => 'personal/info',
318  ];
319 
320  if ( $user->getRegistration() ) {
321  $displayUser = $context->getUser();
322  $userRegistration = $user->getRegistration();
323  $defaultPreferences['registrationdate'] = [
324  'type' => 'info',
325  'label-message' => 'prefs-registration',
326  'default' => $context->msg(
327  'prefs-registration-date-time',
328  $lang->userTimeAndDate( $userRegistration, $displayUser ),
329  $lang->userDate( $userRegistration, $displayUser ),
330  $lang->userTime( $userRegistration, $displayUser )
331  )->text(),
332  'section' => 'personal/info',
333  ];
334  }
335 
336  $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
337  $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
338 
339  // Actually changeable stuff
340  $defaultPreferences['realname'] = [
341  // (not really "private", but still shouldn't be edited without permission)
342  'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'realname' )
343  ? 'text' : 'info',
344  'default' => $user->getRealName(),
345  'section' => 'personal/info',
346  'label-message' => 'yourrealname',
347  'help-message' => 'prefs-help-realname',
348  ];
349 
350  if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
351  new PasswordAuthenticationRequest(), false )->isGood()
352  ) {
353  $defaultPreferences['password'] = [
354  'type' => 'info',
355  'raw' => true,
356  'default' => (string)new \OOUI\ButtonWidget( [
357  'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
358  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
359  ] ),
360  'label' => $context->msg( 'prefs-resetpass' )->text(),
361  ] ),
362  'label-message' => 'yourpassword',
363  'section' => 'personal/info',
364  ];
365  }
366  // Only show prefershttps if secure login is turned on
367  if ( $this->config->get( 'SecureLogin' ) && $canIPUseHTTPS ) {
368  $defaultPreferences['prefershttps'] = [
369  'type' => 'toggle',
370  'label-message' => 'tog-prefershttps',
371  'help-message' => 'prefs-help-prefershttps',
372  'section' => 'personal/info'
373  ];
374  }
375 
376  // Language
377  $languages = Language::fetchLanguageNames( null, 'mwfile' );
378  $languageCode = $this->config->get( 'LanguageCode' );
379  if ( !array_key_exists( $languageCode, $languages ) ) {
380  $languages[$languageCode] = $languageCode;
381  // Sort the array again
382  ksort( $languages );
383  }
384 
385  $options = [];
386  foreach ( $languages as $code => $name ) {
387  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
388  $options[$display] = $code;
389  }
390  $defaultPreferences['language'] = [
391  'type' => 'select',
392  'section' => 'personal/i18n',
393  'options' => $options,
394  'label-message' => 'yourlanguage',
395  ];
396 
397  $defaultPreferences['gender'] = [
398  'type' => 'radio',
399  'section' => 'personal/i18n',
400  'options' => [
401  $context->msg( 'parentheses' )
402  ->params( $context->msg( 'gender-unknown' )->plain() )
403  ->escaped() => 'unknown',
404  $context->msg( 'gender-female' )->escaped() => 'female',
405  $context->msg( 'gender-male' )->escaped() => 'male',
406  ],
407  'label-message' => 'yourgender',
408  'help-message' => 'prefs-help-gender',
409  ];
410 
411  // see if there are multiple language variants to choose from
412  if ( !$this->config->get( 'DisableLangConversion' ) ) {
413  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
414  if ( $langCode == $this->contLang->getCode() ) {
415  if ( !$this->contLang->hasVariants() ) {
416  continue;
417  }
418 
419  $variants = $this->contLang->getVariants();
420  $variantArray = [];
421  foreach ( $variants as $v ) {
422  $v = str_replace( '_', '-', strtolower( $v ) );
423  $variantArray[$v] = $lang->getVariantname( $v, false );
424  }
425 
426  $options = [];
427  foreach ( $variantArray as $code => $name ) {
428  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
429  $options[$display] = $code;
430  }
431 
432  $defaultPreferences['variant'] = [
433  'label-message' => 'yourvariant',
434  'type' => 'select',
435  'options' => $options,
436  'section' => 'personal/i18n',
437  'help-message' => 'prefs-help-variant',
438  ];
439  } else {
440  $defaultPreferences["variant-$langCode"] = [
441  'type' => 'api',
442  ];
443  }
444  }
445  }
446 
447  // Stuff from Language::getExtraUserToggles()
448  // FIXME is this dead code? $extraUserToggles doesn't seem to be defined for any language
449  $toggles = $this->contLang->getExtraUserToggles();
450 
451  foreach ( $toggles as $toggle ) {
452  $defaultPreferences[$toggle] = [
453  'type' => 'toggle',
454  'section' => 'personal/i18n',
455  'label-message' => "tog-$toggle",
456  ];
457  }
458 
459  // show a preview of the old signature first
460  $oldsigWikiText = MediaWikiServices::getInstance()->getParser()->preSaveTransform(
461  '~~~',
462  $context->getTitle(),
463  $user,
465  );
466  $oldsigHTML = Parser::stripOuterParagraph(
467  $context->getOutput()->parseAsContent( $oldsigWikiText )
468  );
469  $defaultPreferences['oldsig'] = [
470  'type' => 'info',
471  'raw' => true,
472  'label-message' => 'tog-oldsig',
473  'default' => $oldsigHTML,
474  'section' => 'personal/signature',
475  ];
476  $defaultPreferences['nickname'] = [
477  'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
478  'maxlength' => $this->config->get( 'MaxSigChars' ),
479  'label-message' => 'yournick',
480  'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
481  return $this->validateSignature( $signature, $alldata, $form );
482  },
483  'section' => 'personal/signature',
484  'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
485  return $this->cleanSignature( $signature, $alldata, $form );
486  },
487  ];
488  $defaultPreferences['fancysig'] = [
489  'type' => 'toggle',
490  'label-message' => 'tog-fancysig',
491  // show general help about signature at the bottom of the section
492  'help-message' => 'prefs-help-signature',
493  'section' => 'personal/signature'
494  ];
495 
496  # # Email stuff
497 
498  if ( $this->config->get( 'EnableEmail' ) ) {
499  if ( $canViewPrivateInfo ) {
500  $helpMessages[] = $this->config->get( 'EmailConfirmToEdit' )
501  ? 'prefs-help-email-required'
502  : 'prefs-help-email';
503 
504  if ( $this->config->get( 'EnableUserEmail' ) ) {
505  // additional messages when users can send email to each other
506  $helpMessages[] = 'prefs-help-email-others';
507  }
508 
509  $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
510  if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
511  $button = new \OOUI\ButtonWidget( [
512  'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
513  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
514  ] ),
515  'label' =>
516  $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
517  ] );
518 
519  $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
520  }
521 
522  $defaultPreferences['emailaddress'] = [
523  'type' => 'info',
524  'raw' => true,
525  'default' => $emailAddress,
526  'label-message' => 'youremail',
527  'section' => 'personal/email',
528  'help-messages' => $helpMessages,
529  # 'cssclass' chosen below
530  ];
531  }
532 
533  $disableEmailPrefs = false;
534 
535  if ( $this->config->get( 'EmailAuthentication' ) ) {
536  $emailauthenticationclass = 'mw-email-not-authenticated';
537  if ( $user->getEmail() ) {
538  if ( $user->getEmailAuthenticationTimestamp() ) {
539  // date and time are separate parameters to facilitate localisation.
540  // $time is kept for backward compat reasons.
541  // 'emailauthenticated' is also used in SpecialConfirmemail.php
542  $displayUser = $context->getUser();
543  $emailTimestamp = $user->getEmailAuthenticationTimestamp();
544  $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
545  $d = $lang->userDate( $emailTimestamp, $displayUser );
546  $t = $lang->userTime( $emailTimestamp, $displayUser );
547  $emailauthenticated = $context->msg( 'emailauthenticated',
548  $time, $d, $t )->parse() . '<br />';
549  $disableEmailPrefs = false;
550  $emailauthenticationclass = 'mw-email-authenticated';
551  } else {
552  $disableEmailPrefs = true;
553  $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
554  new \OOUI\ButtonWidget( [
555  'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
556  'label' => $context->msg( 'emailconfirmlink' )->text(),
557  ] );
558  $emailauthenticationclass = "mw-email-not-authenticated";
559  }
560  } else {
561  $disableEmailPrefs = true;
562  $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
563  $emailauthenticationclass = 'mw-email-none';
564  }
565 
566  if ( $canViewPrivateInfo ) {
567  $defaultPreferences['emailauthentication'] = [
568  'type' => 'info',
569  'raw' => true,
570  'section' => 'personal/email',
571  'label-message' => 'prefs-emailconfirm-label',
572  'default' => $emailauthenticated,
573  # Apply the same CSS class used on the input to the message:
574  'cssclass' => $emailauthenticationclass,
575  ];
576  }
577  }
578 
579  if ( $this->config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
580  $defaultPreferences['disablemail'] = [
581  'id' => 'wpAllowEmail',
582  'type' => 'toggle',
583  'invert' => true,
584  'section' => 'personal/email',
585  'label-message' => 'allowemail',
586  'disabled' => $disableEmailPrefs,
587  ];
588 
589  $defaultPreferences['email-allow-new-users'] = [
590  'id' => 'wpAllowEmailFromNewUsers',
591  'type' => 'toggle',
592  'section' => 'personal/email',
593  'label-message' => 'email-allow-new-users-label',
594  'disabled' => $disableEmailPrefs,
595  ];
596 
597  $defaultPreferences['ccmeonemails'] = [
598  'type' => 'toggle',
599  'section' => 'personal/email',
600  'label-message' => 'tog-ccmeonemails',
601  'disabled' => $disableEmailPrefs,
602  ];
603 
604  if ( $this->config->get( 'EnableUserEmailBlacklist' ) ) {
605  $defaultPreferences['email-blacklist'] = [
606  'type' => 'usersmultiselect',
607  'label-message' => 'email-blacklist-label',
608  'section' => 'personal/email',
609  'disabled' => $disableEmailPrefs,
610  'filter' => MultiUsernameFilter::class,
611  ];
612  }
613  }
614 
615  if ( $this->config->get( 'EnotifWatchlist' ) ) {
616  $defaultPreferences['enotifwatchlistpages'] = [
617  'type' => 'toggle',
618  'section' => 'personal/email',
619  'label-message' => 'tog-enotifwatchlistpages',
620  'disabled' => $disableEmailPrefs,
621  ];
622  }
623  if ( $this->config->get( 'EnotifUserTalk' ) ) {
624  $defaultPreferences['enotifusertalkpages'] = [
625  'type' => 'toggle',
626  'section' => 'personal/email',
627  'label-message' => 'tog-enotifusertalkpages',
628  'disabled' => $disableEmailPrefs,
629  ];
630  }
631  if ( $this->config->get( 'EnotifUserTalk' ) || $this->config->get( 'EnotifWatchlist' ) ) {
632  if ( $this->config->get( 'EnotifMinorEdits' ) ) {
633  $defaultPreferences['enotifminoredits'] = [
634  'type' => 'toggle',
635  'section' => 'personal/email',
636  'label-message' => 'tog-enotifminoredits',
637  'disabled' => $disableEmailPrefs,
638  ];
639  }
640 
641  if ( $this->config->get( 'EnotifRevealEditorAddress' ) ) {
642  $defaultPreferences['enotifrevealaddr'] = [
643  'type' => 'toggle',
644  'section' => 'personal/email',
645  'label-message' => 'tog-enotifrevealaddr',
646  'disabled' => $disableEmailPrefs,
647  ];
648  }
649  }
650  }
651  }
652 
659  protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
660  # # Skin #####################################
661 
662  // Skin selector, if there is at least one valid skin
663  $skinOptions = $this->generateSkinOptions( $user, $context );
664  if ( $skinOptions ) {
665  $defaultPreferences['skin'] = [
666  'type' => 'radio',
667  'options' => $skinOptions,
668  'section' => 'rendering/skin',
669  ];
670  }
671 
672  $allowUserCss = $this->config->get( 'AllowUserCss' );
673  $allowUserJs = $this->config->get( 'AllowUserJs' );
674  # Create links to user CSS/JS pages for all skins
675  # This code is basically copied from generateSkinOptions(). It'd
676  # be nice to somehow merge this back in there to avoid redundancy.
677  if ( $allowUserCss || $allowUserJs ) {
678  $linkTools = [];
679  $userName = $user->getName();
680 
681  if ( $allowUserCss ) {
682  $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
683  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
684  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
685  }
686 
687  if ( $allowUserJs ) {
688  $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
689  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
690  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
691  }
692 
693  $defaultPreferences['commoncssjs'] = [
694  'type' => 'info',
695  'raw' => true,
696  'default' => $context->getLanguage()->pipeList( $linkTools ),
697  'label-message' => 'prefs-common-config',
698  'section' => 'rendering/skin',
699  ];
700  }
701  }
702 
707  protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
708  # # Files #####################################
709  $defaultPreferences['imagesize'] = [
710  'type' => 'select',
711  'options' => $this->getImageSizes( $context ),
712  'label-message' => 'imagemaxsize',
713  'section' => 'rendering/files',
714  ];
715  $defaultPreferences['thumbsize'] = [
716  'type' => 'select',
717  'options' => $this->getThumbSizes( $context ),
718  'label-message' => 'thumbsize',
719  'section' => 'rendering/files',
720  ];
721  }
722 
729  protected function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) {
730  # # Date and time #####################################
731  $dateOptions = $this->getDateOptions( $context );
732  if ( $dateOptions ) {
733  $defaultPreferences['date'] = [
734  'type' => 'radio',
735  'options' => $dateOptions,
736  'section' => 'rendering/dateformat',
737  ];
738  }
739 
740  // Info
741  $now = wfTimestampNow();
742  $lang = $context->getLanguage();
743  $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ],
744  $lang->userTime( $now, $user ) );
745  $nowserver = $lang->userTime( $now, $user,
746  [ 'format' => false, 'timecorrection' => false ] ) .
747  Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
748 
749  $defaultPreferences['nowserver'] = [
750  'type' => 'info',
751  'raw' => 1,
752  'label-message' => 'servertime',
753  'default' => $nowserver,
754  'section' => 'rendering/timeoffset',
755  ];
756 
757  $defaultPreferences['nowlocal'] = [
758  'type' => 'info',
759  'raw' => 1,
760  'label-message' => 'localtime',
761  'default' => $nowlocal,
762  'section' => 'rendering/timeoffset',
763  ];
764 
765  // Grab existing pref.
766  $tzOffset = $user->getOption( 'timecorrection' );
767  $tz = explode( '|', $tzOffset, 3 );
768 
769  $tzOptions = $this->getTimezoneOptions( $context );
770 
771  $tzSetting = $tzOffset;
772  if ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
773  !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
774  ) {
775  // Timezone offset can vary with DST
776  try {
777  $userTZ = new DateTimeZone( $tz[2] );
778  $minDiff = floor( $userTZ->getOffset( new DateTime( 'now' ) ) / 60 );
779  $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
780  } catch ( Exception $e ) {
781  // User has an invalid time zone set. Fall back to just using the offset
782  $tz[0] = 'Offset';
783  }
784  }
785  if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
786  $minDiff = $tz[1];
787  $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
788  }
789 
790  $defaultPreferences['timecorrection'] = [
792  'label-message' => 'timezonelegend',
793  'options' => $tzOptions,
794  'default' => $tzSetting,
795  'size' => 20,
796  'section' => 'rendering/timeoffset',
797  'id' => 'wpTimeCorrection',
798  'filter' => TimezoneFilter::class,
799  'placeholder-message' => 'timezone-useoffset-placeholder',
800  ];
801  }
802 
807  protected function renderingPreferences( MessageLocalizer $l10n, &$defaultPreferences ) {
808  # # Diffs ####################################
809  $defaultPreferences['diffonly'] = [
810  'type' => 'toggle',
811  'section' => 'rendering/diffs',
812  'label-message' => 'tog-diffonly',
813  ];
814  $defaultPreferences['norollbackdiff'] = [
815  'type' => 'toggle',
816  'section' => 'rendering/diffs',
817  'label-message' => 'tog-norollbackdiff',
818  ];
819 
820  # # Page Rendering ##############################
821  if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
822  $defaultPreferences['underline'] = [
823  'type' => 'select',
824  'options' => [
825  $l10n->msg( 'underline-never' )->text() => 0,
826  $l10n->msg( 'underline-always' )->text() => 1,
827  $l10n->msg( 'underline-default' )->text() => 2,
828  ],
829  'label-message' => 'tog-underline',
830  'section' => 'rendering/advancedrendering',
831  ];
832  }
833 
834  $stubThresholdValues = [ 50, 100, 500, 1000, 2000, 5000, 10000 ];
835  $stubThresholdOptions = [ $l10n->msg( 'stub-threshold-disabled' )->text() => 0 ];
836  foreach ( $stubThresholdValues as $value ) {
837  $stubThresholdOptions[$l10n->msg( 'size-bytes', $value )->text()] = $value;
838  }
839 
840  $defaultPreferences['stubthreshold'] = [
841  'type' => 'select',
842  'section' => 'rendering/advancedrendering',
843  'options' => $stubThresholdOptions,
844  // This is not a raw HTML message; label-raw is needed for the manual <a></a>
845  'label-raw' => $l10n->msg( 'stub-threshold' )->rawParams(
846  '<a href="#" class="stub">' .
847  $l10n->msg( 'stub-threshold-sample-link' )->parse() .
848  '</a>' )->parse(),
849  ];
850 
851  $defaultPreferences['showhiddencats'] = [
852  'type' => 'toggle',
853  'section' => 'rendering/advancedrendering',
854  'label-message' => 'tog-showhiddencats'
855  ];
856 
857  $defaultPreferences['numberheadings'] = [
858  'type' => 'toggle',
859  'section' => 'rendering/advancedrendering',
860  'label-message' => 'tog-numberheadings',
861  ];
862  }
863 
869  protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
870  # # Editing #####################################
871  $defaultPreferences['editsectiononrightclick'] = [
872  'type' => 'toggle',
873  'section' => 'editing/advancedediting',
874  'label-message' => 'tog-editsectiononrightclick',
875  ];
876  $defaultPreferences['editondblclick'] = [
877  'type' => 'toggle',
878  'section' => 'editing/advancedediting',
879  'label-message' => 'tog-editondblclick',
880  ];
881 
882  if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
883  $defaultPreferences['editfont'] = [
884  'type' => 'select',
885  'section' => 'editing/editor',
886  'label-message' => 'editfont-style',
887  'options' => [
888  $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
889  $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
890  $l10n->msg( 'editfont-serif' )->text() => 'serif',
891  ]
892  ];
893  }
894 
895  if ( $user->isAllowed( 'minoredit' ) ) {
896  $defaultPreferences['minordefault'] = [
897  'type' => 'toggle',
898  'section' => 'editing/editor',
899  'label-message' => 'tog-minordefault',
900  ];
901  }
902 
903  $defaultPreferences['forceeditsummary'] = [
904  'type' => 'toggle',
905  'section' => 'editing/editor',
906  'label-message' => 'tog-forceeditsummary',
907  ];
908  $defaultPreferences['useeditwarning'] = [
909  'type' => 'toggle',
910  'section' => 'editing/editor',
911  'label-message' => 'tog-useeditwarning',
912  ];
913 
914  $defaultPreferences['previewonfirst'] = [
915  'type' => 'toggle',
916  'section' => 'editing/preview',
917  'label-message' => 'tog-previewonfirst',
918  ];
919  $defaultPreferences['previewontop'] = [
920  'type' => 'toggle',
921  'section' => 'editing/preview',
922  'label-message' => 'tog-previewontop',
923  ];
924  $defaultPreferences['uselivepreview'] = [
925  'type' => 'toggle',
926  'section' => 'editing/preview',
927  'label-message' => 'tog-uselivepreview',
928  ];
929  }
930 
936  protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
937  $rcMaxAge = $this->config->get( 'RCMaxAge' );
938  # # RecentChanges #####################################
939  $defaultPreferences['rcdays'] = [
940  'type' => 'float',
941  'label-message' => 'recentchangesdays',
942  'section' => 'rc/displayrc',
943  'min' => 1 / 24,
944  'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
945  'help' => $l10n->msg( 'recentchangesdays-max' )->numParams(
946  ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped()
947  ];
948  $defaultPreferences['rclimit'] = [
949  'type' => 'int',
950  'min' => 1,
951  'max' => 1000,
952  'label-message' => 'recentchangescount',
953  'help-message' => 'prefs-help-recentchangescount',
954  'section' => 'rc/displayrc',
955  'filter' => IntvalFilter::class,
956  ];
957  $defaultPreferences['usenewrc'] = [
958  'type' => 'toggle',
959  'label-message' => 'tog-usenewrc',
960  'section' => 'rc/advancedrc',
961  ];
962  $defaultPreferences['hideminor'] = [
963  'type' => 'toggle',
964  'label-message' => 'tog-hideminor',
965  'section' => 'rc/changesrc',
966  ];
967  $defaultPreferences['rcfilters-rc-collapsed'] = [
968  'type' => 'api',
969  ];
970  $defaultPreferences['rcfilters-wl-collapsed'] = [
971  'type' => 'api',
972  ];
973  $defaultPreferences['rcfilters-saved-queries'] = [
974  'type' => 'api',
975  ];
976  $defaultPreferences['rcfilters-wl-saved-queries'] = [
977  'type' => 'api',
978  ];
979  // Override RCFilters preferences for RecentChanges 'limit'
980  $defaultPreferences['rcfilters-limit'] = [
981  'type' => 'api',
982  ];
983  $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
984  'type' => 'api',
985  ];
986  $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
987  'type' => 'api',
988  ];
989 
990  if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
991  $defaultPreferences['hidecategorization'] = [
992  'type' => 'toggle',
993  'label-message' => 'tog-hidecategorization',
994  'section' => 'rc/changesrc',
995  ];
996  }
997 
998  if ( $user->useRCPatrol() ) {
999  $defaultPreferences['hidepatrolled'] = [
1000  'type' => 'toggle',
1001  'section' => 'rc/changesrc',
1002  'label-message' => 'tog-hidepatrolled',
1003  ];
1004  }
1005 
1006  if ( $user->useNPPatrol() ) {
1007  $defaultPreferences['newpageshidepatrolled'] = [
1008  'type' => 'toggle',
1009  'section' => 'rc/changesrc',
1010  'label-message' => 'tog-newpageshidepatrolled',
1011  ];
1012  }
1013 
1014  if ( $this->config->get( 'RCShowWatchingUsers' ) ) {
1015  $defaultPreferences['shownumberswatching'] = [
1016  'type' => 'toggle',
1017  'section' => 'rc/advancedrc',
1018  'label-message' => 'tog-shownumberswatching',
1019  ];
1020  }
1021 
1022  $defaultPreferences['rcenhancedfilters-disable'] = [
1023  'type' => 'toggle',
1024  'section' => 'rc/advancedrc',
1025  'label-message' => 'rcfilters-preference-label',
1026  'help-message' => 'rcfilters-preference-help',
1027  ];
1028  }
1029 
1035  protected function watchlistPreferences(
1036  User $user, IContextSource $context, &$defaultPreferences
1037  ) {
1038  $watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
1039 
1040  # # Watchlist #####################################
1041  if ( $user->isAllowed( 'editmywatchlist' ) ) {
1042  $editWatchlistLinks = '';
1043  $editWatchlistLinksOld = [];
1044  $editWatchlistModes = [
1045  'edit' => [ 'subpage' => false, 'flags' => [] ],
1046  'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
1047  'clear' => [ 'subpage' => 'clear', 'flags' => [ 'destructive' ] ],
1048  ];
1049  foreach ( $editWatchlistModes as $mode => $options ) {
1050  // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1051  $editWatchlistLinks .=
1052  new \OOUI\ButtonWidget( [
1053  'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
1054  'flags' => $options[ 'flags' ],
1055  'label' => new \OOUI\HtmlSnippet(
1056  $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
1057  ),
1058  ] );
1059  }
1060 
1061  $defaultPreferences['editwatchlist'] = [
1062  'type' => 'info',
1063  'raw' => true,
1064  'default' => $editWatchlistLinks,
1065  'label-message' => 'prefs-editwatchlist-label',
1066  'section' => 'watchlist/editwatchlist',
1067  ];
1068  }
1069 
1070  $defaultPreferences['watchlistdays'] = [
1071  'type' => 'float',
1072  'min' => 1 / 24,
1073  'max' => $watchlistdaysMax,
1074  'section' => 'watchlist/displaywatchlist',
1075  'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams(
1076  $watchlistdaysMax )->escaped(),
1077  'label-message' => 'prefs-watchlist-days',
1078  ];
1079  $defaultPreferences['wllimit'] = [
1080  'type' => 'int',
1081  'min' => 1,
1082  'max' => 1000,
1083  'label-message' => 'prefs-watchlist-edits',
1084  'help' => $context->msg( 'prefs-watchlist-edits-max' )->escaped(),
1085  'section' => 'watchlist/displaywatchlist',
1086  'filter' => IntvalFilter::class,
1087  ];
1088  $defaultPreferences['extendwatchlist'] = [
1089  'type' => 'toggle',
1090  'section' => 'watchlist/advancedwatchlist',
1091  'label-message' => 'tog-extendwatchlist',
1092  ];
1093  $defaultPreferences['watchlisthideminor'] = [
1094  'type' => 'toggle',
1095  'section' => 'watchlist/changeswatchlist',
1096  'label-message' => 'tog-watchlisthideminor',
1097  ];
1098  $defaultPreferences['watchlisthidebots'] = [
1099  'type' => 'toggle',
1100  'section' => 'watchlist/changeswatchlist',
1101  'label-message' => 'tog-watchlisthidebots',
1102  ];
1103  $defaultPreferences['watchlisthideown'] = [
1104  'type' => 'toggle',
1105  'section' => 'watchlist/changeswatchlist',
1106  'label-message' => 'tog-watchlisthideown',
1107  ];
1108  $defaultPreferences['watchlisthideanons'] = [
1109  'type' => 'toggle',
1110  'section' => 'watchlist/changeswatchlist',
1111  'label-message' => 'tog-watchlisthideanons',
1112  ];
1113  $defaultPreferences['watchlisthideliu'] = [
1114  'type' => 'toggle',
1115  'section' => 'watchlist/changeswatchlist',
1116  'label-message' => 'tog-watchlisthideliu',
1117  ];
1118 
1120  $this->config,
1121  $user
1122  ) ) {
1123  $defaultPreferences['watchlistreloadautomatically'] = [
1124  'type' => 'toggle',
1125  'section' => 'watchlist/advancedwatchlist',
1126  'label-message' => 'tog-watchlistreloadautomatically',
1127  ];
1128  }
1129 
1130  $defaultPreferences['watchlistunwatchlinks'] = [
1131  'type' => 'toggle',
1132  'section' => 'watchlist/advancedwatchlist',
1133  'label-message' => 'tog-watchlistunwatchlinks',
1134  ];
1135 
1136  if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
1137  $defaultPreferences['watchlisthidecategorization'] = [
1138  'type' => 'toggle',
1139  'section' => 'watchlist/changeswatchlist',
1140  'label-message' => 'tog-watchlisthidecategorization',
1141  ];
1142  }
1143 
1144  if ( $user->useRCPatrol() ) {
1145  $defaultPreferences['watchlisthidepatrolled'] = [
1146  'type' => 'toggle',
1147  'section' => 'watchlist/changeswatchlist',
1148  'label-message' => 'tog-watchlisthidepatrolled',
1149  ];
1150  }
1151 
1152  $watchTypes = [
1153  'edit' => 'watchdefault',
1154  'move' => 'watchmoves',
1155  'delete' => 'watchdeletion'
1156  ];
1157 
1158  // Kinda hacky
1159  if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
1160  $watchTypes['read'] = 'watchcreations';
1161  }
1162 
1163  if ( $user->isAllowed( 'rollback' ) ) {
1164  $watchTypes['rollback'] = 'watchrollback';
1165  }
1166 
1167  if ( $user->isAllowed( 'upload' ) ) {
1168  $watchTypes['upload'] = 'watchuploads';
1169  }
1170 
1171  foreach ( $watchTypes as $action => $pref ) {
1172  if ( $user->isAllowed( $action ) ) {
1173  // Messages:
1174  // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1175  // tog-watchrollback
1176  $defaultPreferences[$pref] = [
1177  'type' => 'toggle',
1178  'section' => 'watchlist/pageswatchlist',
1179  'label-message' => "tog-$pref",
1180  ];
1181  }
1182  }
1183 
1184  $defaultPreferences['watchlisttoken'] = [
1185  'type' => 'api',
1186  ];
1187 
1188  $tokenButton = new \OOUI\ButtonWidget( [
1189  'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1190  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1191  ] ),
1192  'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1193  ] );
1194  $defaultPreferences['watchlisttoken-info'] = [
1195  'type' => 'info',
1196  'section' => 'watchlist/tokenwatchlist',
1197  'label-message' => 'prefs-watchlist-token',
1198  'help-message' => 'prefs-help-tokenmanagement',
1199  'raw' => true,
1200  'default' => (string)$tokenButton,
1201  ];
1202 
1203  $defaultPreferences['wlenhancedfilters-disable'] = [
1204  'type' => 'toggle',
1205  'section' => 'watchlist/advancedwatchlist',
1206  'label-message' => 'rcfilters-watchlist-preference-label',
1207  'help-message' => 'rcfilters-watchlist-preference-help',
1208  ];
1209  }
1210 
1214  protected function searchPreferences( &$defaultPreferences ) {
1215  foreach ( MWNamespace::getValidNamespaces() as $n ) {
1216  $defaultPreferences['searchNs' . $n] = [
1217  'type' => 'api',
1218  ];
1219  }
1220  }
1221 
1228  $ret = [];
1229 
1230  $mptitle = Title::newMainPage();
1231  $previewtext = $context->msg( 'skin-preview' )->escaped();
1232 
1233  # Only show skins that aren't disabled in $wgSkipSkins
1234  $validSkinNames = Skin::getAllowedSkins();
1235 
1236  foreach ( $validSkinNames as $skinkey => &$skinname ) {
1237  $msg = $context->msg( "skinname-{$skinkey}" );
1238  if ( $msg->exists() ) {
1239  $skinname = htmlspecialchars( $msg->text() );
1240  }
1241  }
1242 
1243  $defaultSkin = $this->config->get( 'DefaultSkin' );
1244  $allowUserCss = $this->config->get( 'AllowUserCss' );
1245  $allowUserJs = $this->config->get( 'AllowUserJs' );
1246 
1247  # Sort by the internal name, so that the ordering is the same for each display language,
1248  # especially if some skin names are translated to use a different alphabet and some are not.
1249  uksort( $validSkinNames, function ( $a, $b ) use ( $defaultSkin ) {
1250  # Display the default first in the list by comparing it as lesser than any other.
1251  if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1252  return -1;
1253  }
1254  if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1255  return 1;
1256  }
1257  return strcasecmp( $a, $b );
1258  } );
1259 
1260  $foundDefault = false;
1261  foreach ( $validSkinNames as $skinkey => $sn ) {
1262  $linkTools = [];
1263 
1264  # Mark the default skin
1265  if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1266  $linkTools[] = $context->msg( 'default' )->escaped();
1267  $foundDefault = true;
1268  }
1269 
1270  # Create preview link
1271  $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1272  $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1273 
1274  # Create links to user CSS/JS pages
1275  if ( $allowUserCss ) {
1276  $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1277  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1278  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1279  }
1280 
1281  if ( $allowUserJs ) {
1282  $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1283  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1284  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1285  }
1286 
1287  $display = $sn . ' ' . $context->msg( 'parentheses' )
1288  ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1289  ->escaped();
1290  $ret[$display] = $skinkey;
1291  }
1292 
1293  if ( !$foundDefault ) {
1294  // If the default skin is not available, things are going to break horribly because the
1295  // default value for skin selector will not be a valid value. Let's just not show it then.
1296  return [];
1297  }
1298 
1299  return $ret;
1300  }
1301 
1306  protected function getDateOptions( IContextSource $context ) {
1307  $lang = $context->getLanguage();
1308  $dateopts = $lang->getDatePreferences();
1309 
1310  $ret = [];
1311 
1312  if ( $dateopts ) {
1313  if ( !in_array( 'default', $dateopts ) ) {
1314  $dateopts[] = 'default'; // Make sure default is always valid T21237
1315  }
1316 
1317  // FIXME KLUGE: site default might not be valid for user language
1318  global $wgDefaultUserOptions;
1319  if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1320  $wgDefaultUserOptions['date'] = 'default';
1321  }
1322 
1323  $epoch = wfTimestampNow();
1324  foreach ( $dateopts as $key ) {
1325  if ( $key == 'default' ) {
1326  $formatted = $context->msg( 'datedefault' )->escaped();
1327  } else {
1328  $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1329  }
1330  $ret[$formatted] = $key;
1331  }
1332  }
1333  return $ret;
1334  }
1335 
1340  protected function getImageSizes( MessageLocalizer $l10n ) {
1341  $ret = [];
1342  $pixels = $l10n->msg( 'unit-pixel' )->text();
1343 
1344  foreach ( $this->config->get( 'ImageLimits' ) as $index => $limits ) {
1345  // Note: A left-to-right marker (U+200E) is inserted, see T144386
1346  $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1347  $ret[$display] = $index;
1348  }
1349 
1350  return $ret;
1351  }
1352 
1357  protected function getThumbSizes( MessageLocalizer $l10n ) {
1358  $ret = [];
1359  $pixels = $l10n->msg( 'unit-pixel' )->text();
1360 
1361  foreach ( $this->config->get( 'ThumbLimits' ) as $index => $size ) {
1362  $display = $size . $pixels;
1363  $ret[$display] = $index;
1364  }
1365 
1366  return $ret;
1367  }
1368 
1375  protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1376  $maxSigChars = $this->config->get( 'MaxSigChars' );
1377  if ( mb_strlen( $signature ) > $maxSigChars ) {
1378  return Xml::element( 'span', [ 'class' => 'error' ],
1379  $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
1380  } elseif ( isset( $alldata['fancysig'] ) &&
1381  $alldata['fancysig'] &&
1382  MediaWikiServices::getInstance()->getParser()->validateSig( $signature ) === false
1383  ) {
1384  return Xml::element(
1385  'span',
1386  [ 'class' => 'error' ],
1387  $form->msg( 'badsig' )->text()
1388  );
1389  } else {
1390  return true;
1391  }
1392  }
1393 
1400  protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
1401  $parser = MediaWikiServices::getInstance()->getParser();
1402  if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1403  $signature = $parser->cleanSig( $signature );
1404  } else {
1405  // When no fancy sig used, make sure ~{3,5} get removed.
1406  $signature = Parser::cleanSigInSig( $signature );
1407  }
1408 
1409  return $signature;
1410  }
1411 
1419  public function getForm(
1420  User $user,
1422  $formClass = PreferencesFormLegacy::class,
1423  array $remove = []
1424  ) {
1425  // We use ButtonWidgets in some of the getPreferences() functions
1426  $context->getOutput()->enableOOUI();
1427 
1428  $formDescriptor = $this->getFormDescriptor( $user, $context );
1429  if ( count( $remove ) ) {
1430  $removeKeys = array_flip( $remove );
1431  $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1432  }
1433 
1434  // Remove type=api preferences. They are not intended for rendering in the form.
1435  foreach ( $formDescriptor as $name => $info ) {
1436  if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1437  unset( $formDescriptor[$name] );
1438  }
1439  }
1440 
1444  $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1445 
1446  $htmlForm->setModifiedUser( $user );
1447  $htmlForm->setId( 'mw-prefs-form' );
1448  $htmlForm->setAutocomplete( 'off' );
1449  $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() );
1450  # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1451  $htmlForm->setSubmitTooltip( 'preferences-save' );
1452  $htmlForm->setSubmitID( 'prefcontrol' );
1453  $htmlForm->setSubmitCallback(
1454  function ( array $formData, HTMLForm $form ) use ( $formDescriptor ) {
1455  return $this->submitForm( $formData, $form, $formDescriptor );
1456  }
1457  );
1458 
1459  return $htmlForm;
1460  }
1461 
1467  $opt = [];
1468 
1469  $localTZoffset = $this->config->get( 'LocalTZoffset' );
1470  $timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
1471 
1472  $timestamp = MWTimestamp::getLocalInstance();
1473  // Check that the LocalTZoffset is the same as the local time zone offset
1474  if ( $localTZoffset == $timestamp->format( 'Z' ) / 60 ) {
1475  $timezoneName = $timestamp->getTimezone()->getName();
1476  // Localize timezone
1477  if ( isset( $timeZoneList[$timezoneName] ) ) {
1478  $timezoneName = $timeZoneList[$timezoneName]['name'];
1479  }
1480  $server_tz_msg = $context->msg(
1481  'timezoneuseserverdefault',
1482  $timezoneName
1483  )->text();
1484  } else {
1485  $tzstring = sprintf(
1486  '%+03d:%02d',
1487  floor( $localTZoffset / 60 ),
1488  abs( $localTZoffset ) % 60
1489  );
1490  $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
1491  }
1492  $opt[$server_tz_msg] = "System|$localTZoffset";
1493  $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
1494  $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
1495 
1496  foreach ( $timeZoneList as $timeZoneInfo ) {
1497  $region = $timeZoneInfo['region'];
1498  if ( !isset( $opt[$region] ) ) {
1499  $opt[$region] = [];
1500  }
1501  $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
1502  }
1503  return $opt;
1504  }
1505 
1514  protected function saveFormData( $formData, HTMLForm $form, array $formDescriptor ) {
1516  $user = $form->getModifiedUser();
1517  $hiddenPrefs = $this->config->get( 'HiddenPrefs' );
1518  $result = true;
1519 
1520  if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
1521  return Status::newFatal( 'mypreferencesprotected' );
1522  }
1523 
1524  // Filter input
1525  $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
1526 
1527  // Fortunately, the realname field is MUCH simpler
1528  // (not really "private", but still shouldn't be edited without permission)
1529 
1530  if ( !in_array( 'realname', $hiddenPrefs )
1531  && $user->isAllowed( 'editmyprivateinfo' )
1532  && array_key_exists( 'realname', $formData )
1533  ) {
1534  $realName = $formData['realname'];
1535  $user->setRealName( $realName );
1536  }
1537 
1538  if ( $user->isAllowed( 'editmyoptions' ) ) {
1539  $oldUserOptions = $user->getOptions();
1540 
1541  foreach ( $this->getSaveBlacklist() as $b ) {
1542  unset( $formData[$b] );
1543  }
1544 
1545  # If users have saved a value for a preference which has subsequently been disabled
1546  # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1547  # is subsequently re-enabled
1548  foreach ( $hiddenPrefs as $pref ) {
1549  # If the user has not set a non-default value here, the default will be returned
1550  # and subsequently discarded
1551  $formData[$pref] = $user->getOption( $pref, null, true );
1552  }
1553 
1554  // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
1555  if (
1556  isset( $formData['rclimit'] ) &&
1557  intval( $formData[ 'rclimit' ] ) !== $user->getIntOption( 'rclimit' )
1558  ) {
1559  $formData['rcfilters-limit'] = $formData['rclimit'];
1560  }
1561 
1562  // Keep old preferences from interfering due to back-compat code, etc.
1563  $user->resetOptions( 'unused', $form->getContext() );
1564 
1565  foreach ( $formData as $key => $value ) {
1566  $user->setOption( $key, $value );
1567  }
1568 
1569  Hooks::run(
1570  'PreferencesFormPreSave',
1571  [ $formData, $form, $user, &$result, $oldUserOptions ]
1572  );
1573  }
1574 
1575  AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] );
1576  $user->saveSettings();
1577 
1578  return $result;
1579  }
1580 
1589  protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1590  foreach ( $formDescriptor as $preference => $desc ) {
1591  if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
1592  continue;
1593  }
1594  $filterDesc = $desc['filter'];
1595  if ( $filterDesc instanceof Filter ) {
1596  $filter = $filterDesc;
1597  } elseif ( class_exists( $filterDesc ) ) {
1598  $filter = new $filterDesc();
1599  } elseif ( is_callable( $filterDesc ) ) {
1600  $filter = $filterDesc();
1601  } else {
1602  throw new UnexpectedValueException(
1603  "Unrecognized filter type for preference '$preference'"
1604  );
1605  }
1606  $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1607  }
1608  }
1609 
1618  protected function submitForm( array $formData, HTMLForm $form, array $formDescriptor ) {
1619  $res = $this->saveFormData( $formData, $form, $formDescriptor );
1620 
1621  if ( $res === true ) {
1622  $context = $form->getContext();
1623  $urlOptions = [];
1624 
1625  if ( $res === 'eauth' ) {
1626  $urlOptions['eauth'] = 1;
1627  }
1628 
1629  $urlOptions += $form->getExtraSuccessRedirectParameters();
1630 
1631  $url = $form->getTitle()->getFullURL( $urlOptions );
1632 
1633  // Set session data for the success message
1634  $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
1635 
1636  $context->getOutput()->redirect( $url );
1637  }
1638 
1639  return ( $res === true ? Status::newGood() : $res );
1640  }
1641 
1650  protected function getTimeZoneList( Language $language ) {
1651  $identifiers = DateTimeZone::listIdentifiers();
1652  if ( $identifiers === false ) {
1653  return [];
1654  }
1655  sort( $identifiers );
1656 
1657  $tzRegions = [
1658  'Africa' => wfMessage( 'timezoneregion-africa' )->inLanguage( $language )->text(),
1659  'America' => wfMessage( 'timezoneregion-america' )->inLanguage( $language )->text(),
1660  'Antarctica' => wfMessage( 'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1661  'Arctic' => wfMessage( 'timezoneregion-arctic' )->inLanguage( $language )->text(),
1662  'Asia' => wfMessage( 'timezoneregion-asia' )->inLanguage( $language )->text(),
1663  'Atlantic' => wfMessage( 'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1664  'Australia' => wfMessage( 'timezoneregion-australia' )->inLanguage( $language )->text(),
1665  'Europe' => wfMessage( 'timezoneregion-europe' )->inLanguage( $language )->text(),
1666  'Indian' => wfMessage( 'timezoneregion-indian' )->inLanguage( $language )->text(),
1667  'Pacific' => wfMessage( 'timezoneregion-pacific' )->inLanguage( $language )->text(),
1668  ];
1669  asort( $tzRegions );
1670 
1671  $timeZoneList = [];
1672 
1673  $now = new DateTime();
1674 
1675  foreach ( $identifiers as $identifier ) {
1676  $parts = explode( '/', $identifier, 2 );
1677 
1678  // DateTimeZone::listIdentifiers() returns a number of
1679  // backwards-compatibility entries. This filters them out of the
1680  // list presented to the user.
1681  if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1682  continue;
1683  }
1684 
1685  // Localize region
1686  $parts[0] = $tzRegions[$parts[0]];
1687 
1688  $dateTimeZone = new DateTimeZone( $identifier );
1689  $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1690 
1691  $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
1692  $value = "ZoneInfo|$minDiff|$identifier";
1693 
1694  $timeZoneList[$identifier] = [
1695  'name' => $display,
1696  'timecorrection' => $value,
1697  'region' => $parts[0],
1698  ];
1699  }
1700 
1701  return $timeZoneList;
1702  }
1703 }
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:3065
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:2626
static fetchLanguageNames( $inLanguage=self::AS_AUTONYMS, $include='mw')
Get an array of language names, indexed by code.
Definition: Language.php:843
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()
Create a new Title for the Main Page.
Definition: Title.php:597
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:1996
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:2173
getEffectiveGroups( $recache=false)
Get the list of implicit group memberships this user has.
Definition: User.php:3629
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:2075
getTimeZoneList(Language $language)
Get a list of all time zones.
validateSignature( $signature, $alldata, HTMLForm $form)
if(!isset( $args[0])) $lang
getRealName()
Get the user&#39;s real name.
Definition: User.php:3157
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 flters that work as a middleware between storage and interface...
Definition: Filter.php:27
getForm(User $user, IContextSource $context, $formClass=PreferencesFormLegacy::class, array $remove=[])
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4758
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1813
static getInstance()
Returns the global default instance of the top level service locator.
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:1813
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:2469
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:1994
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6333
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:47
cleanSignature( $signature, $alldata, HTMLForm $form)
static loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent=null)
Initialise a new Object for the field.
Definition: HTMLForm.php:480
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4946
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3616
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
static callLegacyAuthPlugin( $method, array $params, $return=null)
Call a legacy AuthPlugin method, if necessary.
$res
Definition: database.txt:21
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:1589
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
renderingPreferences(MessageLocalizer $l10n, &$defaultPreferences)
getContext()
Get the base IContextSource object.
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 & $options
Definition: hooks.txt:1996
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3869
datetimePreferences( $user, IContextSource $context, &$defaultPreferences)
$filter
This serves as the entry point to the authentication system.
Definition: AuthManager.php:83
static checkStructuredFilterUiEnabled(Config $config, User $user)
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:82
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:785
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:3882
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)
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:573
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
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3212
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:3075
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:795
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3705
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:3891
__construct(Config $config, Language $contLang, AuthManager $authManager, LinkRenderer $linkRenderer)
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:276
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS...
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1768
static array $languagesWithVariants
languages supporting variants
static getValidNamespaces()
Returns an array of the namespaces (by integer id) that exist on the wiki.
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
Definition: Skin.php:80
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:1487
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>...