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( $user, $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 
377  $languageCode = $this->config->get( 'LanguageCode' );
378  if ( !array_key_exists( $languageCode, $languages ) ) {
379  $languages[$languageCode] = $languageCode;
380  // Sort the array again
381  ksort( $languages );
382  }
383 
384  $options = [];
385  foreach ( $languages as $code => $name ) {
386  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
387  $options[$display] = $code;
388  }
389  $defaultPreferences['language'] = [
390  'type' => 'select',
391  'section' => 'personal/i18n',
392  'options' => $options,
393  'label-message' => 'yourlanguage',
394  ];
395 
396  $defaultPreferences['gender'] = [
397  'type' => 'radio',
398  'section' => 'personal/i18n',
399  'options' => [
400  $context->msg( 'parentheses' )
401  ->params( $context->msg( 'gender-unknown' )->plain() )
402  ->escaped() => 'unknown',
403  $context->msg( 'gender-female' )->escaped() => 'female',
404  $context->msg( 'gender-male' )->escaped() => 'male',
405  ],
406  'label-message' => 'yourgender',
407  'help-message' => 'prefs-help-gender',
408  ];
409 
410  // see if there are multiple language variants to choose from
411  if ( !$this->config->get( 'DisableLangConversion' ) ) {
412  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
413  if ( $langCode == $this->contLang->getCode() ) {
414  if ( !$this->contLang->hasVariants() ) {
415  continue;
416  }
417 
418  $variants = $this->contLang->getVariants();
419  $variantArray = [];
420  foreach ( $variants as $v ) {
421  $v = str_replace( '_', '-', strtolower( $v ) );
422  $variantArray[$v] = $lang->getVariantname( $v, false );
423  }
424 
425  $options = [];
426  foreach ( $variantArray as $code => $name ) {
427  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
428  $options[$display] = $code;
429  }
430 
431  $defaultPreferences['variant'] = [
432  'label-message' => 'yourvariant',
433  'type' => 'select',
434  'options' => $options,
435  'section' => 'personal/i18n',
436  'help-message' => 'prefs-help-variant',
437  ];
438  } else {
439  $defaultPreferences["variant-$langCode"] = [
440  'type' => 'api',
441  ];
442  }
443  }
444  }
445 
446  // Stuff from Language::getExtraUserToggles()
447  // FIXME is this dead code? $extraUserToggles doesn't seem to be defined for any language
448  $toggles = $this->contLang->getExtraUserToggles();
449 
450  foreach ( $toggles as $toggle ) {
451  $defaultPreferences[$toggle] = [
452  'type' => 'toggle',
453  'section' => 'personal/i18n',
454  'label-message' => "tog-$toggle",
455  ];
456  }
457 
458  // show a preview of the old signature first
459  $oldsigWikiText = MediaWikiServices::getInstance()->getParser()->preSaveTransform(
460  '~~~',
461  $context->getTitle(),
462  $user,
464  );
465  $oldsigHTML = Parser::stripOuterParagraph(
466  $context->getOutput()->parseAsContent( $oldsigWikiText )
467  );
468  $defaultPreferences['oldsig'] = [
469  'type' => 'info',
470  'raw' => true,
471  'label-message' => 'tog-oldsig',
472  'default' => $oldsigHTML,
473  'section' => 'personal/signature',
474  ];
475  $defaultPreferences['nickname'] = [
476  'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
477  'maxlength' => $this->config->get( 'MaxSigChars' ),
478  'label-message' => 'yournick',
479  'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
480  return $this->validateSignature( $signature, $alldata, $form );
481  },
482  'section' => 'personal/signature',
483  'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
484  return $this->cleanSignature( $signature, $alldata, $form );
485  },
486  ];
487  $defaultPreferences['fancysig'] = [
488  'type' => 'toggle',
489  'label-message' => 'tog-fancysig',
490  // show general help about signature at the bottom of the section
491  'help-message' => 'prefs-help-signature',
492  'section' => 'personal/signature'
493  ];
494 
495  # # Email stuff
496 
497  if ( $this->config->get( 'EnableEmail' ) ) {
498  if ( $canViewPrivateInfo ) {
499  $helpMessages[] = $this->config->get( 'EmailConfirmToEdit' )
500  ? 'prefs-help-email-required'
501  : 'prefs-help-email';
502 
503  if ( $this->config->get( 'EnableUserEmail' ) ) {
504  // additional messages when users can send email to each other
505  $helpMessages[] = 'prefs-help-email-others';
506  }
507 
508  $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
509  if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
510  $button = new \OOUI\ButtonWidget( [
511  'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
512  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
513  ] ),
514  'label' =>
515  $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
516  ] );
517 
518  $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
519  }
520 
521  $defaultPreferences['emailaddress'] = [
522  'type' => 'info',
523  'raw' => true,
524  'default' => $emailAddress,
525  'label-message' => 'youremail',
526  'section' => 'personal/email',
527  'help-messages' => $helpMessages,
528  # 'cssclass' chosen below
529  ];
530  }
531 
532  $disableEmailPrefs = false;
533 
534  if ( $this->config->get( 'EmailAuthentication' ) ) {
535  $emailauthenticationclass = 'mw-email-not-authenticated';
536  if ( $user->getEmail() ) {
537  if ( $user->getEmailAuthenticationTimestamp() ) {
538  // date and time are separate parameters to facilitate localisation.
539  // $time is kept for backward compat reasons.
540  // 'emailauthenticated' is also used in SpecialConfirmemail.php
541  $displayUser = $context->getUser();
542  $emailTimestamp = $user->getEmailAuthenticationTimestamp();
543  $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
544  $d = $lang->userDate( $emailTimestamp, $displayUser );
545  $t = $lang->userTime( $emailTimestamp, $displayUser );
546  $emailauthenticated = $context->msg( 'emailauthenticated',
547  $time, $d, $t )->parse() . '<br />';
548  $disableEmailPrefs = false;
549  $emailauthenticationclass = 'mw-email-authenticated';
550  } else {
551  $disableEmailPrefs = true;
552  $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
553  new \OOUI\ButtonWidget( [
554  'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
555  'label' => $context->msg( 'emailconfirmlink' )->text(),
556  ] );
557  $emailauthenticationclass = "mw-email-not-authenticated";
558  }
559  } else {
560  $disableEmailPrefs = true;
561  $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
562  $emailauthenticationclass = 'mw-email-none';
563  }
564 
565  if ( $canViewPrivateInfo ) {
566  $defaultPreferences['emailauthentication'] = [
567  'type' => 'info',
568  'raw' => true,
569  'section' => 'personal/email',
570  'label-message' => 'prefs-emailconfirm-label',
571  'default' => $emailauthenticated,
572  # Apply the same CSS class used on the input to the message:
573  'cssclass' => $emailauthenticationclass,
574  ];
575  }
576  }
577 
578  if ( $this->config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
579  $defaultPreferences['disablemail'] = [
580  'id' => 'wpAllowEmail',
581  'type' => 'toggle',
582  'invert' => true,
583  'section' => 'personal/email',
584  'label-message' => 'allowemail',
585  'disabled' => $disableEmailPrefs,
586  ];
587 
588  $defaultPreferences['email-allow-new-users'] = [
589  'id' => 'wpAllowEmailFromNewUsers',
590  'type' => 'toggle',
591  'section' => 'personal/email',
592  'label-message' => 'email-allow-new-users-label',
593  'disabled' => $disableEmailPrefs,
594  ];
595 
596  $defaultPreferences['ccmeonemails'] = [
597  'type' => 'toggle',
598  'section' => 'personal/email',
599  'label-message' => 'tog-ccmeonemails',
600  'disabled' => $disableEmailPrefs,
601  ];
602 
603  if ( $this->config->get( 'EnableUserEmailBlacklist' ) ) {
604  $defaultPreferences['email-blacklist'] = [
605  'type' => 'usersmultiselect',
606  'label-message' => 'email-blacklist-label',
607  'section' => 'personal/email',
608  'disabled' => $disableEmailPrefs,
609  'filter' => MultiUsernameFilter::class,
610  ];
611  }
612  }
613 
614  if ( $this->config->get( 'EnotifWatchlist' ) ) {
615  $defaultPreferences['enotifwatchlistpages'] = [
616  'type' => 'toggle',
617  'section' => 'personal/email',
618  'label-message' => 'tog-enotifwatchlistpages',
619  'disabled' => $disableEmailPrefs,
620  ];
621  }
622  if ( $this->config->get( 'EnotifUserTalk' ) ) {
623  $defaultPreferences['enotifusertalkpages'] = [
624  'type' => 'toggle',
625  'section' => 'personal/email',
626  'label-message' => 'tog-enotifusertalkpages',
627  'disabled' => $disableEmailPrefs,
628  ];
629  }
630  if ( $this->config->get( 'EnotifUserTalk' ) || $this->config->get( 'EnotifWatchlist' ) ) {
631  if ( $this->config->get( 'EnotifMinorEdits' ) ) {
632  $defaultPreferences['enotifminoredits'] = [
633  'type' => 'toggle',
634  'section' => 'personal/email',
635  'label-message' => 'tog-enotifminoredits',
636  'disabled' => $disableEmailPrefs,
637  ];
638  }
639 
640  if ( $this->config->get( 'EnotifRevealEditorAddress' ) ) {
641  $defaultPreferences['enotifrevealaddr'] = [
642  'type' => 'toggle',
643  'section' => 'personal/email',
644  'label-message' => 'tog-enotifrevealaddr',
645  'disabled' => $disableEmailPrefs,
646  ];
647  }
648  }
649  }
650  }
651 
658  protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
659  # # Skin #####################################
660 
661  // Skin selector, if there is at least one valid skin
662  $skinOptions = $this->generateSkinOptions( $user, $context );
663  if ( $skinOptions ) {
664  $defaultPreferences['skin'] = [
665  'type' => 'radio',
666  'options' => $skinOptions,
667  'section' => 'rendering/skin',
668  ];
669  }
670 
671  $allowUserCss = $this->config->get( 'AllowUserCss' );
672  $allowUserJs = $this->config->get( 'AllowUserJs' );
673  # Create links to user CSS/JS pages for all skins
674  # This code is basically copied from generateSkinOptions(). It'd
675  # be nice to somehow merge this back in there to avoid redundancy.
676  if ( $allowUserCss || $allowUserJs ) {
677  $linkTools = [];
678  $userName = $user->getName();
679 
680  if ( $allowUserCss ) {
681  $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
682  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
683  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
684  }
685 
686  if ( $allowUserJs ) {
687  $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
688  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
689  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
690  }
691 
692  $defaultPreferences['commoncssjs'] = [
693  'type' => 'info',
694  'raw' => true,
695  'default' => $context->getLanguage()->pipeList( $linkTools ),
696  'label-message' => 'prefs-common-config',
697  'section' => 'rendering/skin',
698  ];
699  }
700  }
701 
706  protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
707  # # Files #####################################
708  $defaultPreferences['imagesize'] = [
709  'type' => 'select',
710  'options' => $this->getImageSizes( $context ),
711  'label-message' => 'imagemaxsize',
712  'section' => 'rendering/files',
713  ];
714  $defaultPreferences['thumbsize'] = [
715  'type' => 'select',
716  'options' => $this->getThumbSizes( $context ),
717  'label-message' => 'thumbsize',
718  'section' => 'rendering/files',
719  ];
720  }
721 
728  protected function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) {
729  # # Date and time #####################################
730  $dateOptions = $this->getDateOptions( $context );
731  if ( $dateOptions ) {
732  $defaultPreferences['date'] = [
733  'type' => 'radio',
734  'options' => $dateOptions,
735  'section' => 'rendering/dateformat',
736  ];
737  }
738 
739  // Info
740  $now = wfTimestampNow();
741  $lang = $context->getLanguage();
742  $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ],
743  $lang->userTime( $now, $user ) );
744  $nowserver = $lang->userTime( $now, $user,
745  [ 'format' => false, 'timecorrection' => false ] ) .
746  Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
747 
748  $defaultPreferences['nowserver'] = [
749  'type' => 'info',
750  'raw' => 1,
751  'label-message' => 'servertime',
752  'default' => $nowserver,
753  'section' => 'rendering/timeoffset',
754  ];
755 
756  $defaultPreferences['nowlocal'] = [
757  'type' => 'info',
758  'raw' => 1,
759  'label-message' => 'localtime',
760  'default' => $nowlocal,
761  'section' => 'rendering/timeoffset',
762  ];
763 
764  // Grab existing pref.
765  $tzOffset = $user->getOption( 'timecorrection' );
766  $tz = explode( '|', $tzOffset, 3 );
767 
768  $tzOptions = $this->getTimezoneOptions( $context );
769 
770  $tzSetting = $tzOffset;
771  if ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
772  !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
773  ) {
774  // Timezone offset can vary with DST
775  try {
776  $userTZ = new DateTimeZone( $tz[2] );
777  $minDiff = floor( $userTZ->getOffset( new DateTime( 'now' ) ) / 60 );
778  $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
779  } catch ( Exception $e ) {
780  // User has an invalid time zone set. Fall back to just using the offset
781  $tz[0] = 'Offset';
782  }
783  }
784  if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
785  $minDiff = $tz[1];
786  $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
787  }
788 
789  $defaultPreferences['timecorrection'] = [
791  'label-message' => 'timezonelegend',
792  'options' => $tzOptions,
793  'default' => $tzSetting,
794  'size' => 20,
795  'section' => 'rendering/timeoffset',
796  'id' => 'wpTimeCorrection',
797  'filter' => TimezoneFilter::class,
798  'placeholder-message' => 'timezone-useoffset-placeholder',
799  ];
800  }
801 
807  protected function renderingPreferences(
808  User $user,
809  MessageLocalizer $l10n,
810  &$defaultPreferences
811  ) {
812  # # Diffs ####################################
813  $defaultPreferences['diffonly'] = [
814  'type' => 'toggle',
815  'section' => 'rendering/diffs',
816  'label-message' => 'tog-diffonly',
817  ];
818  $defaultPreferences['norollbackdiff'] = [
819  'type' => 'toggle',
820  'section' => 'rendering/diffs',
821  'label-message' => 'tog-norollbackdiff',
822  ];
823 
824  # # Page Rendering ##############################
825  if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
826  $defaultPreferences['underline'] = [
827  'type' => 'select',
828  'options' => [
829  $l10n->msg( 'underline-never' )->text() => 0,
830  $l10n->msg( 'underline-always' )->text() => 1,
831  $l10n->msg( 'underline-default' )->text() => 2,
832  ],
833  'label-message' => 'tog-underline',
834  'section' => 'rendering/advancedrendering',
835  ];
836  }
837 
838  $stubThresholdValues = [ 50, 100, 500, 1000, 2000, 5000, 10000 ];
839  $stubThresholdOptions = [ $l10n->msg( 'stub-threshold-disabled' )->text() => 0 ];
840  foreach ( $stubThresholdValues as $value ) {
841  $stubThresholdOptions[$l10n->msg( 'size-bytes', $value )->text()] = $value;
842  }
843 
844  $defaultPreferences['stubthreshold'] = [
845  'type' => 'select',
846  'section' => 'rendering/advancedrendering',
847  'options' => $stubThresholdOptions,
848  // This is not a raw HTML message; label-raw is needed for the manual <a></a>
849  'label-raw' => $l10n->msg( 'stub-threshold' )->rawParams(
850  '<a class="stub">' .
851  $l10n->msg( 'stub-threshold-sample-link' )->parse() .
852  '</a>' )->parse(),
853  ];
854 
855  $defaultPreferences['showhiddencats'] = [
856  'type' => 'toggle',
857  'section' => 'rendering/advancedrendering',
858  'label-message' => 'tog-showhiddencats'
859  ];
860 
861  $defaultPreferences['numberheadings'] = [
862  'type' => 'toggle',
863  'section' => 'rendering/advancedrendering',
864  'label-message' => 'tog-numberheadings',
865  ];
866 
867  if ( $user->isAllowed( 'rollback' ) ) {
868  $defaultPreferences['showrollbackconfirmation'] = [
869  'type' => 'toggle',
870  'section' => 'rendering/advancedrendering',
871  'label-message' => 'tog-showrollbackconfirmation',
872  ];
873  }
874  }
875 
881  protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
882  # # Editing #####################################
883  $defaultPreferences['editsectiononrightclick'] = [
884  'type' => 'toggle',
885  'section' => 'editing/advancedediting',
886  'label-message' => 'tog-editsectiononrightclick',
887  ];
888  $defaultPreferences['editondblclick'] = [
889  'type' => 'toggle',
890  'section' => 'editing/advancedediting',
891  'label-message' => 'tog-editondblclick',
892  ];
893 
894  if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
895  $defaultPreferences['editfont'] = [
896  'type' => 'select',
897  'section' => 'editing/editor',
898  'label-message' => 'editfont-style',
899  'options' => [
900  $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
901  $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
902  $l10n->msg( 'editfont-serif' )->text() => 'serif',
903  ]
904  ];
905  }
906 
907  if ( $user->isAllowed( 'minoredit' ) ) {
908  $defaultPreferences['minordefault'] = [
909  'type' => 'toggle',
910  'section' => 'editing/editor',
911  'label-message' => 'tog-minordefault',
912  ];
913  }
914 
915  $defaultPreferences['forceeditsummary'] = [
916  'type' => 'toggle',
917  'section' => 'editing/editor',
918  'label-message' => 'tog-forceeditsummary',
919  ];
920  $defaultPreferences['useeditwarning'] = [
921  'type' => 'toggle',
922  'section' => 'editing/editor',
923  'label-message' => 'tog-useeditwarning',
924  ];
925 
926  $defaultPreferences['previewonfirst'] = [
927  'type' => 'toggle',
928  'section' => 'editing/preview',
929  'label-message' => 'tog-previewonfirst',
930  ];
931  $defaultPreferences['previewontop'] = [
932  'type' => 'toggle',
933  'section' => 'editing/preview',
934  'label-message' => 'tog-previewontop',
935  ];
936  $defaultPreferences['uselivepreview'] = [
937  'type' => 'toggle',
938  'section' => 'editing/preview',
939  'label-message' => 'tog-uselivepreview',
940  ];
941  }
942 
948  protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
949  $rcMaxAge = $this->config->get( 'RCMaxAge' );
950  # # RecentChanges #####################################
951  $defaultPreferences['rcdays'] = [
952  'type' => 'float',
953  'label-message' => 'recentchangesdays',
954  'section' => 'rc/displayrc',
955  'min' => 1 / 24,
956  'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
957  'help' => $l10n->msg( 'recentchangesdays-max' )->numParams(
958  ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped()
959  ];
960  $defaultPreferences['rclimit'] = [
961  'type' => 'int',
962  'min' => 1,
963  'max' => 1000,
964  'label-message' => 'recentchangescount',
965  'help-message' => 'prefs-help-recentchangescount',
966  'section' => 'rc/displayrc',
967  'filter' => IntvalFilter::class,
968  ];
969  $defaultPreferences['usenewrc'] = [
970  'type' => 'toggle',
971  'label-message' => 'tog-usenewrc',
972  'section' => 'rc/advancedrc',
973  ];
974  $defaultPreferences['hideminor'] = [
975  'type' => 'toggle',
976  'label-message' => 'tog-hideminor',
977  'section' => 'rc/changesrc',
978  ];
979  $defaultPreferences['rcfilters-rc-collapsed'] = [
980  'type' => 'api',
981  ];
982  $defaultPreferences['rcfilters-wl-collapsed'] = [
983  'type' => 'api',
984  ];
985  $defaultPreferences['rcfilters-saved-queries'] = [
986  'type' => 'api',
987  ];
988  $defaultPreferences['rcfilters-wl-saved-queries'] = [
989  'type' => 'api',
990  ];
991  // Override RCFilters preferences for RecentChanges 'limit'
992  $defaultPreferences['rcfilters-limit'] = [
993  'type' => 'api',
994  ];
995  $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
996  'type' => 'api',
997  ];
998  $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
999  'type' => 'api',
1000  ];
1001 
1002  if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
1003  $defaultPreferences['hidecategorization'] = [
1004  'type' => 'toggle',
1005  'label-message' => 'tog-hidecategorization',
1006  'section' => 'rc/changesrc',
1007  ];
1008  }
1009 
1010  if ( $user->useRCPatrol() ) {
1011  $defaultPreferences['hidepatrolled'] = [
1012  'type' => 'toggle',
1013  'section' => 'rc/changesrc',
1014  'label-message' => 'tog-hidepatrolled',
1015  ];
1016  }
1017 
1018  if ( $user->useNPPatrol() ) {
1019  $defaultPreferences['newpageshidepatrolled'] = [
1020  'type' => 'toggle',
1021  'section' => 'rc/changesrc',
1022  'label-message' => 'tog-newpageshidepatrolled',
1023  ];
1024  }
1025 
1026  if ( $this->config->get( 'RCShowWatchingUsers' ) ) {
1027  $defaultPreferences['shownumberswatching'] = [
1028  'type' => 'toggle',
1029  'section' => 'rc/advancedrc',
1030  'label-message' => 'tog-shownumberswatching',
1031  ];
1032  }
1033 
1034  $defaultPreferences['rcenhancedfilters-disable'] = [
1035  'type' => 'toggle',
1036  'section' => 'rc/advancedrc',
1037  'label-message' => 'rcfilters-preference-label',
1038  'help-message' => 'rcfilters-preference-help',
1039  ];
1040  }
1041 
1047  protected function watchlistPreferences(
1048  User $user, IContextSource $context, &$defaultPreferences
1049  ) {
1050  $watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
1051 
1052  # # Watchlist #####################################
1053  if ( $user->isAllowed( 'editmywatchlist' ) ) {
1054  $editWatchlistLinks = '';
1055  $editWatchlistModes = [
1056  'edit' => [ 'subpage' => false, 'flags' => [] ],
1057  'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
1058  'clear' => [ 'subpage' => 'clear', 'flags' => [ 'destructive' ] ],
1059  ];
1060  foreach ( $editWatchlistModes as $mode => $options ) {
1061  // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1062  $editWatchlistLinks .=
1063  new \OOUI\ButtonWidget( [
1064  'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
1065  'flags' => $options[ 'flags' ],
1066  'label' => new \OOUI\HtmlSnippet(
1067  $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
1068  ),
1069  ] );
1070  }
1071 
1072  $defaultPreferences['editwatchlist'] = [
1073  'type' => 'info',
1074  'raw' => true,
1075  'default' => $editWatchlistLinks,
1076  'label-message' => 'prefs-editwatchlist-label',
1077  'section' => 'watchlist/editwatchlist',
1078  ];
1079  }
1080 
1081  $defaultPreferences['watchlistdays'] = [
1082  'type' => 'float',
1083  'min' => 1 / 24,
1084  'max' => $watchlistdaysMax,
1085  'section' => 'watchlist/displaywatchlist',
1086  'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams(
1087  $watchlistdaysMax )->escaped(),
1088  'label-message' => 'prefs-watchlist-days',
1089  ];
1090  $defaultPreferences['wllimit'] = [
1091  'type' => 'int',
1092  'min' => 1,
1093  'max' => 1000,
1094  'label-message' => 'prefs-watchlist-edits',
1095  'help' => $context->msg( 'prefs-watchlist-edits-max' )->escaped(),
1096  'section' => 'watchlist/displaywatchlist',
1097  'filter' => IntvalFilter::class,
1098  ];
1099  $defaultPreferences['extendwatchlist'] = [
1100  'type' => 'toggle',
1101  'section' => 'watchlist/advancedwatchlist',
1102  'label-message' => 'tog-extendwatchlist',
1103  ];
1104  $defaultPreferences['watchlisthideminor'] = [
1105  'type' => 'toggle',
1106  'section' => 'watchlist/changeswatchlist',
1107  'label-message' => 'tog-watchlisthideminor',
1108  ];
1109  $defaultPreferences['watchlisthidebots'] = [
1110  'type' => 'toggle',
1111  'section' => 'watchlist/changeswatchlist',
1112  'label-message' => 'tog-watchlisthidebots',
1113  ];
1114  $defaultPreferences['watchlisthideown'] = [
1115  'type' => 'toggle',
1116  'section' => 'watchlist/changeswatchlist',
1117  'label-message' => 'tog-watchlisthideown',
1118  ];
1119  $defaultPreferences['watchlisthideanons'] = [
1120  'type' => 'toggle',
1121  'section' => 'watchlist/changeswatchlist',
1122  'label-message' => 'tog-watchlisthideanons',
1123  ];
1124  $defaultPreferences['watchlisthideliu'] = [
1125  'type' => 'toggle',
1126  'section' => 'watchlist/changeswatchlist',
1127  'label-message' => 'tog-watchlisthideliu',
1128  ];
1129 
1131  $this->config,
1132  $user
1133  ) ) {
1134  $defaultPreferences['watchlistreloadautomatically'] = [
1135  'type' => 'toggle',
1136  'section' => 'watchlist/advancedwatchlist',
1137  'label-message' => 'tog-watchlistreloadautomatically',
1138  ];
1139  }
1140 
1141  $defaultPreferences['watchlistunwatchlinks'] = [
1142  'type' => 'toggle',
1143  'section' => 'watchlist/advancedwatchlist',
1144  'label-message' => 'tog-watchlistunwatchlinks',
1145  ];
1146 
1147  if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
1148  $defaultPreferences['watchlisthidecategorization'] = [
1149  'type' => 'toggle',
1150  'section' => 'watchlist/changeswatchlist',
1151  'label-message' => 'tog-watchlisthidecategorization',
1152  ];
1153  }
1154 
1155  if ( $user->useRCPatrol() ) {
1156  $defaultPreferences['watchlisthidepatrolled'] = [
1157  'type' => 'toggle',
1158  'section' => 'watchlist/changeswatchlist',
1159  'label-message' => 'tog-watchlisthidepatrolled',
1160  ];
1161  }
1162 
1163  $watchTypes = [
1164  'edit' => 'watchdefault',
1165  'move' => 'watchmoves',
1166  'delete' => 'watchdeletion'
1167  ];
1168 
1169  // Kinda hacky
1170  if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
1171  $watchTypes['read'] = 'watchcreations';
1172  }
1173 
1174  if ( $user->isAllowed( 'rollback' ) ) {
1175  $watchTypes['rollback'] = 'watchrollback';
1176  }
1177 
1178  if ( $user->isAllowed( 'upload' ) ) {
1179  $watchTypes['upload'] = 'watchuploads';
1180  }
1181 
1182  foreach ( $watchTypes as $action => $pref ) {
1183  if ( $user->isAllowed( $action ) ) {
1184  // Messages:
1185  // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1186  // tog-watchrollback
1187  $defaultPreferences[$pref] = [
1188  'type' => 'toggle',
1189  'section' => 'watchlist/pageswatchlist',
1190  'label-message' => "tog-$pref",
1191  ];
1192  }
1193  }
1194 
1195  $defaultPreferences['watchlisttoken'] = [
1196  'type' => 'api',
1197  ];
1198 
1199  $tokenButton = new \OOUI\ButtonWidget( [
1200  'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1201  'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1202  ] ),
1203  'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1204  ] );
1205  $defaultPreferences['watchlisttoken-info'] = [
1206  'type' => 'info',
1207  'section' => 'watchlist/tokenwatchlist',
1208  'label-message' => 'prefs-watchlist-token',
1209  'help-message' => 'prefs-help-tokenmanagement',
1210  'raw' => true,
1211  'default' => (string)$tokenButton,
1212  ];
1213 
1214  $defaultPreferences['wlenhancedfilters-disable'] = [
1215  'type' => 'toggle',
1216  'section' => 'watchlist/advancedwatchlist',
1217  'label-message' => 'rcfilters-watchlist-preference-label',
1218  'help-message' => 'rcfilters-watchlist-preference-help',
1219  ];
1220  }
1221 
1225  protected function searchPreferences( &$defaultPreferences ) {
1226  foreach ( MWNamespace::getValidNamespaces() as $n ) {
1227  $defaultPreferences['searchNs' . $n] = [
1228  'type' => 'api',
1229  ];
1230  }
1231  }
1232 
1239  $ret = [];
1240 
1241  $mptitle = Title::newMainPage();
1242  $previewtext = $context->msg( 'skin-preview' )->escaped();
1243 
1244  # Only show skins that aren't disabled in $wgSkipSkins
1245  $validSkinNames = Skin::getAllowedSkins();
1246 
1247  foreach ( $validSkinNames as $skinkey => &$skinname ) {
1248  $msg = $context->msg( "skinname-{$skinkey}" );
1249  if ( $msg->exists() ) {
1250  $skinname = htmlspecialchars( $msg->text() );
1251  }
1252  }
1253 
1254  $defaultSkin = $this->config->get( 'DefaultSkin' );
1255  $allowUserCss = $this->config->get( 'AllowUserCss' );
1256  $allowUserJs = $this->config->get( 'AllowUserJs' );
1257 
1258  # Sort by the internal name, so that the ordering is the same for each display language,
1259  # especially if some skin names are translated to use a different alphabet and some are not.
1260  uksort( $validSkinNames, function ( $a, $b ) use ( $defaultSkin ) {
1261  # Display the default first in the list by comparing it as lesser than any other.
1262  if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1263  return -1;
1264  }
1265  if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1266  return 1;
1267  }
1268  return strcasecmp( $a, $b );
1269  } );
1270 
1271  $foundDefault = false;
1272  foreach ( $validSkinNames as $skinkey => $sn ) {
1273  $linkTools = [];
1274 
1275  # Mark the default skin
1276  if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1277  $linkTools[] = $context->msg( 'default' )->escaped();
1278  $foundDefault = true;
1279  }
1280 
1281  # Create preview link
1282  $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1283  $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1284 
1285  # Create links to user CSS/JS pages
1286  if ( $allowUserCss ) {
1287  $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1288  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1289  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1290  }
1291 
1292  if ( $allowUserJs ) {
1293  $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1294  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1295  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1296  }
1297 
1298  $display = $sn . ' ' . $context->msg( 'parentheses' )
1299  ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1300  ->escaped();
1301  $ret[$display] = $skinkey;
1302  }
1303 
1304  if ( !$foundDefault ) {
1305  // If the default skin is not available, things are going to break horribly because the
1306  // default value for skin selector will not be a valid value. Let's just not show it then.
1307  return [];
1308  }
1309 
1310  return $ret;
1311  }
1312 
1317  protected function getDateOptions( IContextSource $context ) {
1318  $lang = $context->getLanguage();
1319  $dateopts = $lang->getDatePreferences();
1320 
1321  $ret = [];
1322 
1323  if ( $dateopts ) {
1324  if ( !in_array( 'default', $dateopts ) ) {
1325  $dateopts[] = 'default'; // Make sure default is always valid T21237
1326  }
1327 
1328  // FIXME KLUGE: site default might not be valid for user language
1329  global $wgDefaultUserOptions;
1330  if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1331  $wgDefaultUserOptions['date'] = 'default';
1332  }
1333 
1334  $epoch = wfTimestampNow();
1335  foreach ( $dateopts as $key ) {
1336  if ( $key == 'default' ) {
1337  $formatted = $context->msg( 'datedefault' )->escaped();
1338  } else {
1339  $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1340  }
1341  $ret[$formatted] = $key;
1342  }
1343  }
1344  return $ret;
1345  }
1346 
1351  protected function getImageSizes( MessageLocalizer $l10n ) {
1352  $ret = [];
1353  $pixels = $l10n->msg( 'unit-pixel' )->text();
1354 
1355  foreach ( $this->config->get( 'ImageLimits' ) as $index => $limits ) {
1356  // Note: A left-to-right marker (U+200E) is inserted, see T144386
1357  $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1358  $ret[$display] = $index;
1359  }
1360 
1361  return $ret;
1362  }
1363 
1368  protected function getThumbSizes( MessageLocalizer $l10n ) {
1369  $ret = [];
1370  $pixels = $l10n->msg( 'unit-pixel' )->text();
1371 
1372  foreach ( $this->config->get( 'ThumbLimits' ) as $index => $size ) {
1373  $display = $size . $pixels;
1374  $ret[$display] = $index;
1375  }
1376 
1377  return $ret;
1378  }
1379 
1386  protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1387  $maxSigChars = $this->config->get( 'MaxSigChars' );
1388  if ( mb_strlen( $signature ) > $maxSigChars ) {
1389  return Xml::element( 'span', [ 'class' => 'error' ],
1390  $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
1391  } elseif ( isset( $alldata['fancysig'] ) &&
1392  $alldata['fancysig'] &&
1393  MediaWikiServices::getInstance()->getParser()->validateSig( $signature ) === false
1394  ) {
1395  return Xml::element(
1396  'span',
1397  [ 'class' => 'error' ],
1398  $form->msg( 'badsig' )->text()
1399  );
1400  } else {
1401  return true;
1402  }
1403  }
1404 
1411  protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
1412  $parser = MediaWikiServices::getInstance()->getParser();
1413  if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1414  $signature = $parser->cleanSig( $signature );
1415  } else {
1416  // When no fancy sig used, make sure ~{3,5} get removed.
1417  $signature = Parser::cleanSigInSig( $signature );
1418  }
1419 
1420  return $signature;
1421  }
1422 
1430  public function getForm(
1431  User $user,
1433  $formClass = PreferencesFormOOUI::class,
1434  array $remove = []
1435  ) {
1436  // We use ButtonWidgets in some of the getPreferences() functions
1437  $context->getOutput()->enableOOUI();
1438 
1439  $formDescriptor = $this->getFormDescriptor( $user, $context );
1440  if ( count( $remove ) ) {
1441  $removeKeys = array_flip( $remove );
1442  $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1443  }
1444 
1445  // Remove type=api preferences. They are not intended for rendering in the form.
1446  foreach ( $formDescriptor as $name => $info ) {
1447  if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1448  unset( $formDescriptor[$name] );
1449  }
1450  }
1451 
1455  $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1456 
1457  $htmlForm->setModifiedUser( $user );
1458  $htmlForm->setId( 'mw-prefs-form' );
1459  $htmlForm->setAutocomplete( 'off' );
1460  $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() );
1461  # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1462  $htmlForm->setSubmitTooltip( 'preferences-save' );
1463  $htmlForm->setSubmitID( 'prefcontrol' );
1464  $htmlForm->setSubmitCallback(
1465  function ( array $formData, HTMLForm $form ) use ( $formDescriptor ) {
1466  return $this->submitForm( $formData, $form, $formDescriptor );
1467  }
1468  );
1469 
1470  return $htmlForm;
1471  }
1472 
1478  $opt = [];
1479 
1480  $localTZoffset = $this->config->get( 'LocalTZoffset' );
1481  $timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
1482 
1483  $timestamp = MWTimestamp::getLocalInstance();
1484  // Check that the LocalTZoffset is the same as the local time zone offset
1485  if ( $localTZoffset == $timestamp->format( 'Z' ) / 60 ) {
1486  $timezoneName = $timestamp->getTimezone()->getName();
1487  // Localize timezone
1488  if ( isset( $timeZoneList[$timezoneName] ) ) {
1489  $timezoneName = $timeZoneList[$timezoneName]['name'];
1490  }
1491  $server_tz_msg = $context->msg(
1492  'timezoneuseserverdefault',
1493  $timezoneName
1494  )->text();
1495  } else {
1496  $tzstring = sprintf(
1497  '%+03d:%02d',
1498  floor( $localTZoffset / 60 ),
1499  abs( $localTZoffset ) % 60
1500  );
1501  $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
1502  }
1503  $opt[$server_tz_msg] = "System|$localTZoffset";
1504  $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
1505  $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
1506 
1507  foreach ( $timeZoneList as $timeZoneInfo ) {
1508  $region = $timeZoneInfo['region'];
1509  if ( !isset( $opt[$region] ) ) {
1510  $opt[$region] = [];
1511  }
1512  $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
1513  }
1514  return $opt;
1515  }
1516 
1525  protected function saveFormData( $formData, HTMLForm $form, array $formDescriptor ) {
1527  $user = $form->getModifiedUser();
1528  $hiddenPrefs = $this->config->get( 'HiddenPrefs' );
1529  $result = true;
1530 
1531  if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
1532  return Status::newFatal( 'mypreferencesprotected' );
1533  }
1534 
1535  // Filter input
1536  $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
1537 
1538  // Fortunately, the realname field is MUCH simpler
1539  // (not really "private", but still shouldn't be edited without permission)
1540 
1541  if ( !in_array( 'realname', $hiddenPrefs )
1542  && $user->isAllowed( 'editmyprivateinfo' )
1543  && array_key_exists( 'realname', $formData )
1544  ) {
1545  $realName = $formData['realname'];
1546  $user->setRealName( $realName );
1547  }
1548 
1549  if ( $user->isAllowed( 'editmyoptions' ) ) {
1550  $oldUserOptions = $user->getOptions();
1551 
1552  foreach ( $this->getSaveBlacklist() as $b ) {
1553  unset( $formData[$b] );
1554  }
1555 
1556  # If users have saved a value for a preference which has subsequently been disabled
1557  # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1558  # is subsequently re-enabled
1559  foreach ( $hiddenPrefs as $pref ) {
1560  # If the user has not set a non-default value here, the default will be returned
1561  # and subsequently discarded
1562  $formData[$pref] = $user->getOption( $pref, null, true );
1563  }
1564 
1565  // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
1566  if (
1567  isset( $formData['rclimit'] ) &&
1568  intval( $formData[ 'rclimit' ] ) !== $user->getIntOption( 'rclimit' )
1569  ) {
1570  $formData['rcfilters-limit'] = $formData['rclimit'];
1571  }
1572 
1573  // Keep old preferences from interfering due to back-compat code, etc.
1574  $user->resetOptions( 'unused', $form->getContext() );
1575 
1576  foreach ( $formData as $key => $value ) {
1577  $user->setOption( $key, $value );
1578  }
1579 
1580  Hooks::run(
1581  'PreferencesFormPreSave',
1582  [ $formData, $form, $user, &$result, $oldUserOptions ]
1583  );
1584  }
1585 
1586  $user->saveSettings();
1587 
1588  return $result;
1589  }
1590 
1599  protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
1600  foreach ( $formDescriptor as $preference => $desc ) {
1601  if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
1602  continue;
1603  }
1604  $filterDesc = $desc['filter'];
1605  if ( $filterDesc instanceof Filter ) {
1606  $filter = $filterDesc;
1607  } elseif ( class_exists( $filterDesc ) ) {
1608  $filter = new $filterDesc();
1609  } elseif ( is_callable( $filterDesc ) ) {
1610  $filter = $filterDesc();
1611  } else {
1612  throw new UnexpectedValueException(
1613  "Unrecognized filter type for preference '$preference'"
1614  );
1615  }
1616  $preferences[$preference] = $filter->$verb( $preferences[$preference] );
1617  }
1618  }
1619 
1628  protected function submitForm( array $formData, HTMLForm $form, array $formDescriptor ) {
1629  $res = $this->saveFormData( $formData, $form, $formDescriptor );
1630 
1631  if ( $res === true ) {
1632  $context = $form->getContext();
1633  $urlOptions = [];
1634 
1635  if ( $res === 'eauth' ) {
1636  $urlOptions['eauth'] = 1;
1637  }
1638 
1639  $urlOptions += $form->getExtraSuccessRedirectParameters();
1640 
1641  $url = $form->getTitle()->getFullURL( $urlOptions );
1642 
1643  // Set session data for the success message
1644  $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
1645 
1646  $context->getOutput()->redirect( $url );
1647  }
1648 
1649  return ( $res === true ? Status::newGood() : $res );
1650  }
1651 
1660  protected function getTimeZoneList( Language $language ) {
1661  $identifiers = DateTimeZone::listIdentifiers();
1662  if ( $identifiers === false ) {
1663  return [];
1664  }
1665  sort( $identifiers );
1666 
1667  $tzRegions = [
1668  'Africa' => wfMessage( 'timezoneregion-africa' )->inLanguage( $language )->text(),
1669  'America' => wfMessage( 'timezoneregion-america' )->inLanguage( $language )->text(),
1670  'Antarctica' => wfMessage( 'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1671  'Arctic' => wfMessage( 'timezoneregion-arctic' )->inLanguage( $language )->text(),
1672  'Asia' => wfMessage( 'timezoneregion-asia' )->inLanguage( $language )->text(),
1673  'Atlantic' => wfMessage( 'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1674  'Australia' => wfMessage( 'timezoneregion-australia' )->inLanguage( $language )->text(),
1675  'Europe' => wfMessage( 'timezoneregion-europe' )->inLanguage( $language )->text(),
1676  'Indian' => wfMessage( 'timezoneregion-indian' )->inLanguage( $language )->text(),
1677  'Pacific' => wfMessage( 'timezoneregion-pacific' )->inLanguage( $language )->text(),
1678  ];
1679  asort( $tzRegions );
1680 
1681  $timeZoneList = [];
1682 
1683  $now = new DateTime();
1684 
1685  foreach ( $identifiers as $identifier ) {
1686  $parts = explode( '/', $identifier, 2 );
1687 
1688  // DateTimeZone::listIdentifiers() returns a number of
1689  // backwards-compatibility entries. This filters them out of the
1690  // list presented to the user.
1691  if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1692  continue;
1693  }
1694 
1695  // Localize region
1696  $parts[0] = $tzRegions[$parts[0]];
1697 
1698  $dateTimeZone = new DateTimeZone( $identifier );
1699  $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1700 
1701  $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
1702  $value = "ZoneInfo|$minDiff|$identifier";
1703 
1704  $timeZoneList[$identifier] = [
1705  'name' => $display,
1706  'timecorrection' => $value,
1707  'region' => $parts[0],
1708  ];
1709  }
1710 
1711  return $timeZoneList;
1712  }
1713 }
getEmail()
Get the user&#39;s e-mail address.
Definition: User.php:3043
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:836
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:3607
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
getRealName()
Get the user&#39;s real name.
Definition: User.php:3133
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
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4771
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 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:2443
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:6345
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:48
cleanSignature( $signature, $alldata, HTMLForm $form)
static loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent=null)
Initialise a new Object for the field.
Definition: HTMLForm.php:493
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
getRegistration()
Get the timestamp of account creation.
Definition: User.php:4938
getGroupMemberships()
Get the list of explicit group memberships this user has, stored as UserGroupMembership objects...
Definition: User.php:3594
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
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:1626
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
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:1982
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3847
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:84
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 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:3860
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: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
getOptions( $flags=0)
Get all user&#39;s options.
Definition: User.php:3188
getEmailAuthenticationTimestamp()
Get the timestamp of the user&#39;s e-mail authentication.
Definition: User.php:3053
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
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3683
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:3869
__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: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:1767
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: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>...