MediaWiki  REL1_31
DefaultPreferencesFactory.php
Go to the documentation of this file.
1 <?php
22 
24 use Config;
25 use DateTime;
26 use DateTimeZone;
27 use Exception;
28 use Hooks;
29 use Html;
45 use Parser;
48 use Psr\Log\LoggerAwareTrait;
49 use Psr\Log\NullLogger;
50 use Skin;
52 use Status;
53 use Title;
54 use User;
56 use Xml;
57 
62  use LoggerAwareTrait;
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  protected function getSaveFilters() {
99  // Wrap intval() so that we can pass it multiple parameters and treat all filters the same.
100  $intvalFilter = function ( $value, $alldata ) {
101  return intval( $value );
102  };
103  return [
104  'timecorrection' => [ $this, 'filterTimezoneInput' ],
105  'rclimit' => $intvalFilter,
106  'wllimit' => $intvalFilter,
107  'searchlimit' => $intvalFilter,
108  ];
109  }
110 
114  public function getSaveBlacklist() {
115  return [
116  'realname',
117  'emailaddress',
118  ];
119  }
120 
128  $preferences = [];
129 
130  $canIPUseHTTPS = wfCanIPUseHTTPS( $context->getRequest()->getIP() );
131  $this->profilePreferences( $user, $context, $preferences, $canIPUseHTTPS );
132  $this->skinPreferences( $user, $context, $preferences );
133  $this->datetimePreferences( $user, $context, $preferences );
134  $this->filesPreferences( $context, $preferences );
135  $this->renderingPreferences( $context, $preferences );
136  $this->editingPreferences( $user, $context, $preferences );
137  $this->rcPreferences( $user, $context, $preferences );
138  $this->watchlistPreferences( $user, $context, $preferences );
139  $this->searchPreferences( $preferences );
140 
141  Hooks::run( 'GetPreferences', [ $user, &$preferences ] );
142 
143  $this->loadPreferenceValues( $user, $context, $preferences );
144  $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
145  return $preferences;
146  }
147 
156  private function loadPreferenceValues(
157  User $user, IContextSource $context, &$defaultPreferences
158  ) {
159  # # Remove preferences that wikis don't want to use
160  foreach ( $this->config->get( 'HiddenPrefs' ) as $pref ) {
161  if ( isset( $defaultPreferences[$pref] ) ) {
162  unset( $defaultPreferences[$pref] );
163  }
164  }
165 
166  # # Make sure that form fields have their parent set. See T43337.
167  $dummyForm = new HTMLForm( [], $context );
168 
169  $disable = !$user->isAllowed( 'editmyoptions' );
170 
171  $defaultOptions = User::getDefaultOptions();
172  # # Prod in defaults from the user
173  foreach ( $defaultPreferences as $name => &$info ) {
174  $prefFromUser = $this->getOptionFromUser( $name, $info, $user );
175  if ( $disable && !in_array( $name, $this->getSaveBlacklist() ) ) {
176  $info['disabled'] = 'disabled';
177  }
178  $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation
179  $globalDefault = isset( $defaultOptions[$name] )
180  ? $defaultOptions[$name]
181  : null;
182 
183  // If it validates, set it as the default
184  if ( isset( $info['default'] ) ) {
185  // Already set, no problem
186  continue;
187  } elseif ( !is_null( $prefFromUser ) && // Make sure we're not just pulling nothing
188  $field->validate( $prefFromUser, $user->getOptions() ) === true ) {
189  $info['default'] = $prefFromUser;
190  } elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) {
191  $info['default'] = $globalDefault;
192  } else {
193  throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
194  }
195  }
196 
197  return $defaultPreferences;
198  }
199 
208  protected function getOptionFromUser( $name, $info, User $user ) {
209  $val = $user->getOption( $name );
210 
211  // Handling for multiselect preferences
212  if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
213  ( isset( $info['class'] ) && $info['class'] == \HTMLMultiSelectField::class ) ) {
214  $options = HTMLFormField::flattenOptions( $info['options'] );
215  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
216  $val = [];
217 
218  foreach ( $options as $value ) {
219  if ( $user->getOption( "$prefix$value" ) ) {
220  $val[] = $value;
221  }
222  }
223  }
224 
225  // Handling for checkmatrix preferences
226  if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
227  ( isset( $info['class'] ) && $info['class'] == \HTMLCheckMatrix::class ) ) {
228  $columns = HTMLFormField::flattenOptions( $info['columns'] );
229  $rows = HTMLFormField::flattenOptions( $info['rows'] );
230  $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
231  $val = [];
232 
233  foreach ( $columns as $column ) {
234  foreach ( $rows as $row ) {
235  if ( $user->getOption( "$prefix$column-$row" ) ) {
236  $val[] = "$column-$row";
237  }
238  }
239  }
240  }
241 
242  return $val;
243  }
244 
254  protected function profilePreferences(
255  User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS
256  ) {
257  // retrieving user name for GENDER and misc.
258  $userName = $user->getName();
259 
260  # # User info #####################################
261  // Information panel
262  $defaultPreferences['username'] = [
263  'type' => 'info',
264  'label-message' => [ 'username', $userName ],
265  'default' => $userName,
266  'section' => 'personal/info',
267  ];
268 
270 
271  # Get groups to which the user belongs
272  $userEffectiveGroups = $user->getEffectiveGroups();
273  $userGroupMemberships = $user->getGroupMemberships();
274  $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
275  foreach ( $userEffectiveGroups as $ueg ) {
276  if ( $ueg == '*' ) {
277  // Skip the default * group, seems useless here
278  continue;
279  }
280 
281  if ( isset( $userGroupMemberships[$ueg] ) ) {
282  $groupStringOrObject = $userGroupMemberships[$ueg];
283  } else {
284  $groupStringOrObject = $ueg;
285  }
286 
287  $userG = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html' );
288  $userM = UserGroupMembership::getLink( $groupStringOrObject, $context, 'html',
289  $userName );
290 
291  // Store expiring groups separately, so we can place them before non-expiring
292  // groups in the list. This is to avoid the ambiguity of something like
293  // "administrator, bureaucrat (until X date)" -- users might wonder whether the
294  // expiry date applies to both groups, or just the last one
295  if ( $groupStringOrObject instanceof UserGroupMembership &&
296  $groupStringOrObject->getExpiry()
297  ) {
298  $userTempGroups[] = $userG;
299  $userTempMembers[] = $userM;
300  } else {
301  $userGroups[] = $userG;
302  $userMembers[] = $userM;
303  }
304  }
305  sort( $userGroups );
306  sort( $userMembers );
307  sort( $userTempGroups );
308  sort( $userTempMembers );
309  $userGroups = array_merge( $userTempGroups, $userGroups );
310  $userMembers = array_merge( $userTempMembers, $userMembers );
311 
312  $defaultPreferences['usergroups'] = [
313  'type' => 'info',
314  'label' => $context->msg( 'prefs-memberingroups' )->numParams(
315  count( $userGroups ) )->params( $userName )->parse(),
316  'default' => $context->msg( 'prefs-memberingroups-type' )
317  ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
318  ->escaped(),
319  'raw' => true,
320  'section' => 'personal/info',
321  ];
322 
323  $contribTitle = SpecialPage::getTitleFor( "Contributions", $userName );
324  $formattedEditCount = $lang->formatNum( $user->getEditCount() );
325  $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
326 
327  $defaultPreferences['editcount'] = [
328  'type' => 'info',
329  'raw' => true,
330  'label-message' => 'prefs-edits',
331  'default' => $editCount,
332  'section' => 'personal/info',
333  ];
334 
335  if ( $user->getRegistration() ) {
336  $displayUser = $context->getUser();
337  $userRegistration = $user->getRegistration();
338  $defaultPreferences['registrationdate'] = [
339  'type' => 'info',
340  'label-message' => 'prefs-registration',
341  'default' => $context->msg(
342  'prefs-registration-date-time',
343  $lang->userTimeAndDate( $userRegistration, $displayUser ),
344  $lang->userDate( $userRegistration, $displayUser ),
345  $lang->userTime( $userRegistration, $displayUser )
346  )->parse(),
347  'section' => 'personal/info',
348  ];
349  }
350 
351  $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
352  $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
353 
354  // Actually changeable stuff
355  $defaultPreferences['realname'] = [
356  // (not really "private", but still shouldn't be edited without permission)
357  'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'realname' )
358  ? 'text' : 'info',
359  'default' => $user->getRealName(),
360  'section' => 'personal/info',
361  'label-message' => 'yourrealname',
362  'help-message' => 'prefs-help-realname',
363  ];
364 
365  if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
366  new PasswordAuthenticationRequest(), false )->isGood()
367  ) {
368  $link = $this->linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ),
369  $context->msg( 'prefs-resetpass' )->text(), [],
370  [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
371  $defaultPreferences['password'] = [
372  'type' => 'info',
373  'raw' => true,
374  'default' => $link,
375  'label-message' => 'yourpassword',
376  'section' => 'personal/info',
377  ];
378  }
379  // Only show prefershttps if secure login is turned on
380  if ( !$this->config->get( 'ForceHTTPS' )
381  && $this->config->get( 'SecureLogin' )
382  && $canIPUseHTTPS
383  ) {
384  $defaultPreferences['prefershttps'] = [
385  'type' => 'toggle',
386  'label-message' => 'tog-prefershttps',
387  'help-message' => 'prefs-help-prefershttps',
388  'section' => 'personal/info'
389  ];
390  }
391 
392  // Language
394  $languageCode = $this->config->get( 'LanguageCode' );
395  if ( !array_key_exists( $languageCode, $languages ) ) {
396  $languages[$languageCode] = $languageCode;
397  }
398  ksort( $languages );
399 
400  $options = [];
401  foreach ( $languages as $code => $name ) {
402  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
403  $options[$display] = $code;
404  }
405  $defaultPreferences['language'] = [
406  'type' => 'select',
407  'section' => 'personal/i18n',
408  'options' => $options,
409  'label-message' => 'yourlanguage',
410  ];
411 
412  $defaultPreferences['gender'] = [
413  'type' => 'radio',
414  'section' => 'personal/i18n',
415  'options' => [
416  $context->msg( 'parentheses' )
417  ->params( $context->msg( 'gender-unknown' )->plain() )
418  ->escaped() => 'unknown',
419  $context->msg( 'gender-female' )->escaped() => 'female',
420  $context->msg( 'gender-male' )->escaped() => 'male',
421  ],
422  'label-message' => 'yourgender',
423  'help-message' => 'prefs-help-gender',
424  ];
425 
426  // see if there are multiple language variants to choose from
427  if ( !$this->config->get( 'DisableLangConversion' ) ) {
428  foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
429  if ( $langCode == $this->contLang->getCode() ) {
430  $variants = $this->contLang->getVariants();
431 
432  if ( count( $variants ) <= 1 ) {
433  continue;
434  }
435 
436  $variantArray = [];
437  foreach ( $variants as $v ) {
438  $v = str_replace( '_', '-', strtolower( $v ) );
439  $variantArray[$v] = $lang->getVariantname( $v, false );
440  }
441 
442  $options = [];
443  foreach ( $variantArray as $code => $name ) {
444  $display = LanguageCode::bcp47( $code ) . ' - ' . $name;
445  $options[$display] = $code;
446  }
447 
448  $defaultPreferences['variant'] = [
449  'label-message' => 'yourvariant',
450  'type' => 'select',
451  'options' => $options,
452  'section' => 'personal/i18n',
453  'help-message' => 'prefs-help-variant',
454  ];
455  } else {
456  $defaultPreferences["variant-$langCode"] = [
457  'type' => 'api',
458  ];
459  }
460  }
461  }
462 
463  // Stuff from Language::getExtraUserToggles()
464  // FIXME is this dead code? $extraUserToggles doesn't seem to be defined for any language
465  $toggles = $this->contLang->getExtraUserToggles();
466 
467  foreach ( $toggles as $toggle ) {
468  $defaultPreferences[$toggle] = [
469  'type' => 'toggle',
470  'section' => 'personal/i18n',
471  'label-message' => "tog-$toggle",
472  ];
473  }
474 
475  // show a preview of the old signature first
476  $oldsigWikiText = MediaWikiServices::getInstance()->getParser()->preSaveTransform(
477  '~~~',
478  $context->getTitle(),
479  $user,
481  );
482  $oldsigHTML = $context->getOutput()->parseInline( $oldsigWikiText, true, true );
483  $defaultPreferences['oldsig'] = [
484  'type' => 'info',
485  'raw' => true,
486  'label-message' => 'tog-oldsig',
487  'default' => $oldsigHTML,
488  'section' => 'personal/signature',
489  ];
490  $defaultPreferences['nickname'] = [
491  'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
492  'maxlength' => $this->config->get( 'MaxSigChars' ),
493  'label-message' => 'yournick',
494  'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
495  return $this->validateSignature( $signature, $alldata, $form );
496  },
497  'section' => 'personal/signature',
498  'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
499  return $this->cleanSignature( $signature, $alldata, $form );
500  },
501  ];
502  $defaultPreferences['fancysig'] = [
503  'type' => 'toggle',
504  'label-message' => 'tog-fancysig',
505  // show general help about signature at the bottom of the section
506  'help-message' => 'prefs-help-signature',
507  'section' => 'personal/signature'
508  ];
509 
510  # # Email stuff
511 
512  if ( $this->config->get( 'EnableEmail' ) ) {
513  if ( $canViewPrivateInfo ) {
514  $helpMessages[] = $this->config->get( 'EmailConfirmToEdit' )
515  ? 'prefs-help-email-required'
516  : 'prefs-help-email';
517 
518  if ( $this->config->get( 'EnableUserEmail' ) ) {
519  // additional messages when users can send email to each other
520  $helpMessages[] = 'prefs-help-email-others';
521  }
522 
523  $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
524  if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
525  $link = $this->linkRenderer->makeLink(
526  SpecialPage::getTitleFor( 'ChangeEmail' ),
527  $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
528  [],
529  [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] );
530 
531  $emailAddress .= $emailAddress == '' ? $link : (
532  $context->msg( 'word-separator' )->escaped()
533  . $context->msg( 'parentheses' )->rawParams( $link )->escaped()
534  );
535  }
536 
537  $defaultPreferences['emailaddress'] = [
538  'type' => 'info',
539  'raw' => true,
540  'default' => $emailAddress,
541  'label-message' => 'youremail',
542  'section' => 'personal/email',
543  'help-messages' => $helpMessages,
544  # 'cssclass' chosen below
545  ];
546  }
547 
548  $disableEmailPrefs = false;
549 
550  if ( $this->config->get( 'EmailAuthentication' ) ) {
551  $emailauthenticationclass = 'mw-email-not-authenticated';
552  if ( $user->getEmail() ) {
553  if ( $user->getEmailAuthenticationTimestamp() ) {
554  // date and time are separate parameters to facilitate localisation.
555  // $time is kept for backward compat reasons.
556  // 'emailauthenticated' is also used in SpecialConfirmemail.php
557  $displayUser = $context->getUser();
558  $emailTimestamp = $user->getEmailAuthenticationTimestamp();
559  $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
560  $d = $lang->userDate( $emailTimestamp, $displayUser );
561  $t = $lang->userTime( $emailTimestamp, $displayUser );
562  $emailauthenticated = $context->msg( 'emailauthenticated',
563  $time, $d, $t )->parse() . '<br />';
564  $disableEmailPrefs = false;
565  $emailauthenticationclass = 'mw-email-authenticated';
566  } else {
567  $disableEmailPrefs = true;
568  $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
569  $this->linkRenderer->makeKnownLink(
570  SpecialPage::getTitleFor( 'Confirmemail' ),
571  $context->msg( 'emailconfirmlink' )->text()
572  ) . '<br />';
573  $emailauthenticationclass = "mw-email-not-authenticated";
574  }
575  } else {
576  $disableEmailPrefs = true;
577  $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
578  $emailauthenticationclass = 'mw-email-none';
579  }
580 
581  if ( $canViewPrivateInfo ) {
582  $defaultPreferences['emailauthentication'] = [
583  'type' => 'info',
584  'raw' => true,
585  'section' => 'personal/email',
586  'label-message' => 'prefs-emailconfirm-label',
587  'default' => $emailauthenticated,
588  # Apply the same CSS class used on the input to the message:
589  'cssclass' => $emailauthenticationclass,
590  ];
591  }
592  }
593 
594  if ( $this->config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) {
595  $defaultPreferences['disablemail'] = [
596  'id' => 'wpAllowEmail',
597  'type' => 'toggle',
598  'invert' => true,
599  'section' => 'personal/email',
600  'label-message' => 'allowemail',
601  'disabled' => $disableEmailPrefs,
602  ];
603 
604  $defaultPreferences['email-allow-new-users'] = [
605  'id' => 'wpAllowEmailFromNewUsers',
606  'type' => 'toggle',
607  'section' => 'personal/email',
608  'label-message' => 'email-allow-new-users-label',
609  'disabled' => $disableEmailPrefs,
610  ];
611 
612  $defaultPreferences['ccmeonemails'] = [
613  'type' => 'toggle',
614  'section' => 'personal/email',
615  'label-message' => 'tog-ccmeonemails',
616  'disabled' => $disableEmailPrefs,
617  ];
618 
619  if ( $this->config->get( 'EnableUserEmailBlacklist' ) ) {
620  $lookup = CentralIdLookup::factory();
621  $ids = $user->getOption( 'email-blacklist', [] );
622  $names = $ids ? $lookup->namesFromCentralIds( $ids, $user ) : [];
623 
624  $defaultPreferences['email-blacklist'] = [
625  'type' => 'usersmultiselect',
626  'label-message' => 'email-blacklist-label',
627  'section' => 'personal/email',
628  'default' => implode( "\n", $names ),
629  'disabled' => $disableEmailPrefs,
630  ];
631  }
632  }
633 
634  if ( $this->config->get( 'EnotifWatchlist' ) ) {
635  $defaultPreferences['enotifwatchlistpages'] = [
636  'type' => 'toggle',
637  'section' => 'personal/email',
638  'label-message' => 'tog-enotifwatchlistpages',
639  'disabled' => $disableEmailPrefs,
640  ];
641  }
642  if ( $this->config->get( 'EnotifUserTalk' ) ) {
643  $defaultPreferences['enotifusertalkpages'] = [
644  'type' => 'toggle',
645  'section' => 'personal/email',
646  'label-message' => 'tog-enotifusertalkpages',
647  'disabled' => $disableEmailPrefs,
648  ];
649  }
650  if ( $this->config->get( 'EnotifUserTalk' ) || $this->config->get( 'EnotifWatchlist' ) ) {
651  if ( $this->config->get( 'EnotifMinorEdits' ) ) {
652  $defaultPreferences['enotifminoredits'] = [
653  'type' => 'toggle',
654  'section' => 'personal/email',
655  'label-message' => 'tog-enotifminoredits',
656  'disabled' => $disableEmailPrefs,
657  ];
658  }
659 
660  if ( $this->config->get( 'EnotifRevealEditorAddress' ) ) {
661  $defaultPreferences['enotifrevealaddr'] = [
662  'type' => 'toggle',
663  'section' => 'personal/email',
664  'label-message' => 'tog-enotifrevealaddr',
665  'disabled' => $disableEmailPrefs,
666  ];
667  }
668  }
669  }
670  }
671 
678  protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
679  # # Skin #####################################
680 
681  // Skin selector, if there is at least one valid skin
682  $skinOptions = $this->generateSkinOptions( $user, $context );
683  if ( $skinOptions ) {
684  $defaultPreferences['skin'] = [
685  'type' => 'radio',
686  'options' => $skinOptions,
687  'section' => 'rendering/skin',
688  ];
689  }
690 
691  $allowUserCss = $this->config->get( 'AllowUserCss' );
692  $allowUserJs = $this->config->get( 'AllowUserJs' );
693  # Create links to user CSS/JS pages for all skins
694  # This code is basically copied from generateSkinOptions(). It'd
695  # be nice to somehow merge this back in there to avoid redundancy.
696  if ( $allowUserCss || $allowUserJs ) {
697  $linkTools = [];
698  $userName = $user->getName();
699 
700  if ( $allowUserCss ) {
701  $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
702  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
703  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
704  }
705 
706  if ( $allowUserJs ) {
707  $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
708  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
709  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
710  }
711 
712  $defaultPreferences['commoncssjs'] = [
713  'type' => 'info',
714  'raw' => true,
715  'default' => $context->getLanguage()->pipeList( $linkTools ),
716  'label-message' => 'prefs-common-config',
717  'section' => 'rendering/skin',
718  ];
719  }
720  }
721 
726  protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
727  # # Files #####################################
728  $defaultPreferences['imagesize'] = [
729  'type' => 'select',
730  'options' => $this->getImageSizes( $context ),
731  'label-message' => 'imagemaxsize',
732  'section' => 'rendering/files',
733  ];
734  $defaultPreferences['thumbsize'] = [
735  'type' => 'select',
736  'options' => $this->getThumbSizes( $context ),
737  'label-message' => 'thumbsize',
738  'section' => 'rendering/files',
739  ];
740  }
741 
748  protected function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) {
749  # # Date and time #####################################
750  $dateOptions = $this->getDateOptions( $context );
751  if ( $dateOptions ) {
752  $defaultPreferences['date'] = [
753  'type' => 'radio',
754  'options' => $dateOptions,
755  'section' => 'rendering/dateformat',
756  ];
757  }
758 
759  // Info
760  $now = wfTimestampNow();
762  $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ],
763  $lang->userTime( $now, $user ) );
764  $nowserver = $lang->userTime( $now, $user,
765  [ 'format' => false, 'timecorrection' => false ] ) .
766  Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
767 
768  $defaultPreferences['nowserver'] = [
769  'type' => 'info',
770  'raw' => 1,
771  'label-message' => 'servertime',
772  'default' => $nowserver,
773  'section' => 'rendering/timeoffset',
774  ];
775 
776  $defaultPreferences['nowlocal'] = [
777  'type' => 'info',
778  'raw' => 1,
779  'label-message' => 'localtime',
780  'default' => $nowlocal,
781  'section' => 'rendering/timeoffset',
782  ];
783 
784  // Grab existing pref.
785  $tzOffset = $user->getOption( 'timecorrection' );
786  $tz = explode( '|', $tzOffset, 3 );
787 
788  $tzOptions = $this->getTimezoneOptions( $context );
789 
790  $tzSetting = $tzOffset;
791  if ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
792  !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) )
793  ) {
794  // Timezone offset can vary with DST
795  try {
796  $userTZ = new DateTimeZone( $tz[2] );
797  $minDiff = floor( $userTZ->getOffset( new DateTime( 'now' ) ) / 60 );
798  $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
799  } catch ( Exception $e ) {
800  // User has an invalid time zone set. Fall back to just using the offset
801  $tz[0] = 'Offset';
802  }
803  }
804  if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
805  $minDiff = $tz[1];
806  $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
807  }
808 
809  $defaultPreferences['timecorrection'] = [
811  'label-message' => 'timezonelegend',
812  'options' => $tzOptions,
813  'default' => $tzSetting,
814  'size' => 20,
815  'section' => 'rendering/timeoffset',
816  ];
817  }
818 
823  protected function renderingPreferences( MessageLocalizer $l10n, &$defaultPreferences ) {
824  # # Diffs ####################################
825  $defaultPreferences['diffonly'] = [
826  'type' => 'toggle',
827  'section' => 'rendering/diffs',
828  'label-message' => 'tog-diffonly',
829  ];
830  $defaultPreferences['norollbackdiff'] = [
831  'type' => 'toggle',
832  'section' => 'rendering/diffs',
833  'label-message' => 'tog-norollbackdiff',
834  ];
835 
836  # # Page Rendering ##############################
837  if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
838  $defaultPreferences['underline'] = [
839  'type' => 'select',
840  'options' => [
841  $l10n->msg( 'underline-never' )->text() => 0,
842  $l10n->msg( 'underline-always' )->text() => 1,
843  $l10n->msg( 'underline-default' )->text() => 2,
844  ],
845  'label-message' => 'tog-underline',
846  'section' => 'rendering/advancedrendering',
847  ];
848  }
849 
850  $stubThresholdValues = [ 50, 100, 500, 1000, 2000, 5000, 10000 ];
851  $stubThresholdOptions = [ $l10n->msg( 'stub-threshold-disabled' )->text() => 0 ];
852  foreach ( $stubThresholdValues as $value ) {
853  $stubThresholdOptions[$l10n->msg( 'size-bytes', $value )->text()] = $value;
854  }
855 
856  $defaultPreferences['stubthreshold'] = [
857  'type' => 'select',
858  'section' => 'rendering/advancedrendering',
859  'options' => $stubThresholdOptions,
860  // This is not a raw HTML message; label-raw is needed for the manual <a></a>
861  'label-raw' => $l10n->msg( 'stub-threshold' )->rawParams(
862  '<a href="#" class="stub">' .
863  $l10n->msg( 'stub-threshold-sample-link' )->parse() .
864  '</a>' )->parse(),
865  ];
866 
867  $defaultPreferences['showhiddencats'] = [
868  'type' => 'toggle',
869  'section' => 'rendering/advancedrendering',
870  'label-message' => 'tog-showhiddencats'
871  ];
872 
873  $defaultPreferences['numberheadings'] = [
874  'type' => 'toggle',
875  'section' => 'rendering/advancedrendering',
876  'label-message' => 'tog-numberheadings',
877  ];
878  }
879 
885  protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
886  # # Editing #####################################
887  $defaultPreferences['editsectiononrightclick'] = [
888  'type' => 'toggle',
889  'section' => 'editing/advancedediting',
890  'label-message' => 'tog-editsectiononrightclick',
891  ];
892  $defaultPreferences['editondblclick'] = [
893  'type' => 'toggle',
894  'section' => 'editing/advancedediting',
895  'label-message' => 'tog-editondblclick',
896  ];
897 
898  if ( $this->config->get( 'AllowUserCssPrefs' ) ) {
899  $defaultPreferences['editfont'] = [
900  'type' => 'select',
901  'section' => 'editing/editor',
902  'label-message' => 'editfont-style',
903  'options' => [
904  $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
905  $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
906  $l10n->msg( 'editfont-serif' )->text() => 'serif',
907  ]
908  ];
909  }
910 
911  if ( $user->isAllowed( 'minoredit' ) ) {
912  $defaultPreferences['minordefault'] = [
913  'type' => 'toggle',
914  'section' => 'editing/editor',
915  'label-message' => 'tog-minordefault',
916  ];
917  }
918 
919  $defaultPreferences['forceeditsummary'] = [
920  'type' => 'toggle',
921  'section' => 'editing/editor',
922  'label-message' => 'tog-forceeditsummary',
923  ];
924  $defaultPreferences['useeditwarning'] = [
925  'type' => 'toggle',
926  'section' => 'editing/editor',
927  'label-message' => 'tog-useeditwarning',
928  ];
929  $defaultPreferences['showtoolbar'] = [
930  'type' => 'toggle',
931  'section' => 'editing/editor',
932  'label-message' => 'tog-showtoolbar',
933  ];
934 
935  $defaultPreferences['previewonfirst'] = [
936  'type' => 'toggle',
937  'section' => 'editing/preview',
938  'label-message' => 'tog-previewonfirst',
939  ];
940  $defaultPreferences['previewontop'] = [
941  'type' => 'toggle',
942  'section' => 'editing/preview',
943  'label-message' => 'tog-previewontop',
944  ];
945  $defaultPreferences['uselivepreview'] = [
946  'type' => 'toggle',
947  'section' => 'editing/preview',
948  'label-message' => 'tog-uselivepreview',
949  ];
950  }
951 
957  protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
958  $rcMaxAge = $this->config->get( 'RCMaxAge' );
959  # # RecentChanges #####################################
960  $defaultPreferences['rcdays'] = [
961  'type' => 'float',
962  'label-message' => 'recentchangesdays',
963  'section' => 'rc/displayrc',
964  'min' => 1,
965  'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ),
966  'help' => $l10n->msg( 'recentchangesdays-max' )->numParams(
967  ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped()
968  ];
969  $defaultPreferences['rclimit'] = [
970  'type' => 'int',
971  'min' => 0,
972  'max' => 1000,
973  'label-message' => 'recentchangescount',
974  'help-message' => 'prefs-help-recentchangescount',
975  'section' => 'rc/displayrc',
976  ];
977  $defaultPreferences['usenewrc'] = [
978  'type' => 'toggle',
979  'label-message' => 'tog-usenewrc',
980  'section' => 'rc/advancedrc',
981  ];
982  $defaultPreferences['hideminor'] = [
983  'type' => 'toggle',
984  'label-message' => 'tog-hideminor',
985  'section' => 'rc/advancedrc',
986  ];
987  $defaultPreferences['rcfilters-saved-queries'] = [
988  'type' => 'api',
989  ];
990  $defaultPreferences['rcfilters-wl-saved-queries'] = [
991  'type' => 'api',
992  ];
993  // Override RCFilters preferences for RecentChanges 'limit'
994  $defaultPreferences['rcfilters-limit'] = [
995  'type' => 'api',
996  ];
997  $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
998  'type' => 'api',
999  ];
1000  $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
1001  'type' => 'api',
1002  ];
1003 
1004  if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
1005  $defaultPreferences['hidecategorization'] = [
1006  'type' => 'toggle',
1007  'label-message' => 'tog-hidecategorization',
1008  'section' => 'rc/advancedrc',
1009  ];
1010  }
1011 
1012  if ( $user->useRCPatrol() ) {
1013  $defaultPreferences['hidepatrolled'] = [
1014  'type' => 'toggle',
1015  'section' => 'rc/advancedrc',
1016  'label-message' => 'tog-hidepatrolled',
1017  ];
1018  }
1019 
1020  if ( $user->useNPPatrol() ) {
1021  $defaultPreferences['newpageshidepatrolled'] = [
1022  'type' => 'toggle',
1023  'section' => 'rc/advancedrc',
1024  'label-message' => 'tog-newpageshidepatrolled',
1025  ];
1026  }
1027 
1028  if ( $this->config->get( 'RCShowWatchingUsers' ) ) {
1029  $defaultPreferences['shownumberswatching'] = [
1030  'type' => 'toggle',
1031  'section' => 'rc/advancedrc',
1032  'label-message' => 'tog-shownumberswatching',
1033  ];
1034  }
1035 
1036  if ( $this->config->get( 'StructuredChangeFiltersShowPreference' ) ) {
1037  $defaultPreferences['rcenhancedfilters-disable'] = [
1038  'type' => 'toggle',
1039  'section' => 'rc/opt-out',
1040  'label-message' => 'rcfilters-preference-label',
1041  'help-message' => 'rcfilters-preference-help',
1042  ];
1043  }
1044  }
1045 
1051  protected function watchlistPreferences(
1052  User $user, IContextSource $context, &$defaultPreferences
1053  ) {
1054  $watchlistdaysMax = ceil( $this->config->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
1055 
1056  # # Watchlist #####################################
1057  if ( $user->isAllowed( 'editmywatchlist' ) ) {
1058  $editWatchlistLinks = [];
1059  $editWatchlistModes = [
1060  'edit' => [ 'EditWatchlist', false ],
1061  'raw' => [ 'EditWatchlist', 'raw' ],
1062  'clear' => [ 'EditWatchlist', 'clear' ],
1063  ];
1064  foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) {
1065  // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1066  $editWatchlistLinks[] = $this->linkRenderer->makeKnownLink(
1067  SpecialPage::getTitleFor( $mode[0], $mode[1] ),
1068  new HtmlArmor( $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() )
1069  );
1070  }
1071 
1072  $defaultPreferences['editwatchlist'] = [
1073  'type' => 'info',
1074  'raw' => true,
1075  'default' => $context->getLanguage()->pipeList( $editWatchlistLinks ),
1076  'label-message' => 'prefs-editwatchlist-label',
1077  'section' => 'watchlist/editwatchlist',
1078  ];
1079  }
1080 
1081  $defaultPreferences['watchlistdays'] = [
1082  'type' => 'float',
1083  'min' => 0,
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' => 0,
1093  'max' => 1000,
1094  'label-message' => 'prefs-watchlist-edits',
1095  'help' => $context->msg( 'prefs-watchlist-edits-max' )->escaped(),
1096  'section' => 'watchlist/displaywatchlist',
1097  ];
1098  $defaultPreferences['extendwatchlist'] = [
1099  'type' => 'toggle',
1100  'section' => 'watchlist/advancedwatchlist',
1101  'label-message' => 'tog-extendwatchlist',
1102  ];
1103  $defaultPreferences['watchlisthideminor'] = [
1104  'type' => 'toggle',
1105  'section' => 'watchlist/advancedwatchlist',
1106  'label-message' => 'tog-watchlisthideminor',
1107  ];
1108  $defaultPreferences['watchlisthidebots'] = [
1109  'type' => 'toggle',
1110  'section' => 'watchlist/advancedwatchlist',
1111  'label-message' => 'tog-watchlisthidebots',
1112  ];
1113  $defaultPreferences['watchlisthideown'] = [
1114  'type' => 'toggle',
1115  'section' => 'watchlist/advancedwatchlist',
1116  'label-message' => 'tog-watchlisthideown',
1117  ];
1118  $defaultPreferences['watchlisthideanons'] = [
1119  'type' => 'toggle',
1120  'section' => 'watchlist/advancedwatchlist',
1121  'label-message' => 'tog-watchlisthideanons',
1122  ];
1123  $defaultPreferences['watchlisthideliu'] = [
1124  'type' => 'toggle',
1125  'section' => 'watchlist/advancedwatchlist',
1126  'label-message' => 'tog-watchlisthideliu',
1127  ];
1128 
1130  $this->config,
1131  $user
1132  ) ) {
1133  $defaultPreferences['watchlistreloadautomatically'] = [
1134  'type' => 'toggle',
1135  'section' => 'watchlist/advancedwatchlist',
1136  'label-message' => 'tog-watchlistreloadautomatically',
1137  ];
1138  }
1139 
1140  $defaultPreferences['watchlistunwatchlinks'] = [
1141  'type' => 'toggle',
1142  'section' => 'watchlist/advancedwatchlist',
1143  'label-message' => 'tog-watchlistunwatchlinks',
1144  ];
1145 
1146  if ( $this->config->get( 'RCWatchCategoryMembership' ) ) {
1147  $defaultPreferences['watchlisthidecategorization'] = [
1148  'type' => 'toggle',
1149  'section' => 'watchlist/advancedwatchlist',
1150  'label-message' => 'tog-watchlisthidecategorization',
1151  ];
1152  }
1153 
1154  if ( $user->useRCPatrol() ) {
1155  $defaultPreferences['watchlisthidepatrolled'] = [
1156  'type' => 'toggle',
1157  'section' => 'watchlist/advancedwatchlist',
1158  'label-message' => 'tog-watchlisthidepatrolled',
1159  ];
1160  }
1161 
1162  $watchTypes = [
1163  'edit' => 'watchdefault',
1164  'move' => 'watchmoves',
1165  'delete' => 'watchdeletion'
1166  ];
1167 
1168  // Kinda hacky
1169  if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
1170  $watchTypes['read'] = 'watchcreations';
1171  }
1172 
1173  if ( $user->isAllowed( 'rollback' ) ) {
1174  $watchTypes['rollback'] = 'watchrollback';
1175  }
1176 
1177  if ( $user->isAllowed( 'upload' ) ) {
1178  $watchTypes['upload'] = 'watchuploads';
1179  }
1180 
1181  foreach ( $watchTypes as $action => $pref ) {
1182  if ( $user->isAllowed( $action ) ) {
1183  // Messages:
1184  // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads
1185  // tog-watchrollback
1186  $defaultPreferences[$pref] = [
1187  'type' => 'toggle',
1188  'section' => 'watchlist/advancedwatchlist',
1189  'label-message' => "tog-$pref",
1190  ];
1191  }
1192  }
1193 
1194  if ( $this->config->get( 'EnableAPI' ) ) {
1195  $defaultPreferences['watchlisttoken'] = [
1196  'type' => 'api',
1197  ];
1198  $defaultPreferences['watchlisttoken-info'] = [
1199  'type' => 'info',
1200  'section' => 'watchlist/tokenwatchlist',
1201  'label-message' => 'prefs-watchlist-token',
1202  'default' => $user->getTokenFromOption( 'watchlisttoken' ),
1203  'help-message' => 'prefs-help-watchlist-token2',
1204  ];
1205  }
1206  }
1207 
1211  protected function searchPreferences( &$defaultPreferences ) {
1212  foreach ( MWNamespace::getValidNamespaces() as $n ) {
1213  $defaultPreferences['searchNs' . $n] = [
1214  'type' => 'api',
1215  ];
1216  }
1217  }
1218 
1225  $ret = [];
1226 
1227  $mptitle = Title::newMainPage();
1228  $previewtext = $context->msg( 'skin-preview' )->escaped();
1229 
1230  # Only show skins that aren't disabled in $wgSkipSkins
1231  $validSkinNames = Skin::getAllowedSkins();
1232 
1233  foreach ( $validSkinNames as $skinkey => &$skinname ) {
1234  $msg = $context->msg( "skinname-{$skinkey}" );
1235  if ( $msg->exists() ) {
1236  $skinname = htmlspecialchars( $msg->text() );
1237  }
1238  }
1239 
1240  $defaultSkin = $this->config->get( 'DefaultSkin' );
1241  $allowUserCss = $this->config->get( 'AllowUserCss' );
1242  $allowUserJs = $this->config->get( 'AllowUserJs' );
1243 
1244  # Sort by the internal name, so that the ordering is the same for each display language,
1245  # especially if some skin names are translated to use a different alphabet and some are not.
1246  uksort( $validSkinNames, function ( $a, $b ) use ( $defaultSkin ) {
1247  # Display the default first in the list by comparing it as lesser than any other.
1248  if ( strcasecmp( $a, $defaultSkin ) === 0 ) {
1249  return -1;
1250  }
1251  if ( strcasecmp( $b, $defaultSkin ) === 0 ) {
1252  return 1;
1253  }
1254  return strcasecmp( $a, $b );
1255  } );
1256 
1257  $foundDefault = false;
1258  foreach ( $validSkinNames as $skinkey => $sn ) {
1259  $linkTools = [];
1260 
1261  # Mark the default skin
1262  if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1263  $linkTools[] = $context->msg( 'default' )->escaped();
1264  $foundDefault = true;
1265  }
1266 
1267  # Create preview link
1268  $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1269  $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1270 
1271  # Create links to user CSS/JS pages
1272  if ( $allowUserCss ) {
1273  $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1274  $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1275  $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1276  }
1277 
1278  if ( $allowUserJs ) {
1279  $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1280  $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1281  $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1282  }
1283 
1284  $display = $sn . ' ' . $context->msg( 'parentheses' )
1285  ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1286  ->escaped();
1287  $ret[$display] = $skinkey;
1288  }
1289 
1290  if ( !$foundDefault ) {
1291  // If the default skin is not available, things are going to break horribly because the
1292  // default value for skin selector will not be a valid value. Let's just not show it then.
1293  return [];
1294  }
1295 
1296  return $ret;
1297  }
1298 
1303  protected function getDateOptions( IContextSource $context ) {
1304  $lang = $context->getLanguage();
1305  $dateopts = $lang->getDatePreferences();
1306 
1307  $ret = [];
1308 
1309  if ( $dateopts ) {
1310  if ( !in_array( 'default', $dateopts ) ) {
1311  $dateopts[] = 'default'; // Make sure default is always valid T21237
1312  }
1313 
1314  // FIXME KLUGE: site default might not be valid for user language
1316  if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1317  $wgDefaultUserOptions['date'] = 'default';
1318  }
1319 
1320  $epoch = wfTimestampNow();
1321  foreach ( $dateopts as $key ) {
1322  if ( $key == 'default' ) {
1323  $formatted = $context->msg( 'datedefault' )->escaped();
1324  } else {
1325  $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1326  }
1327  $ret[$formatted] = $key;
1328  }
1329  }
1330  return $ret;
1331  }
1332 
1337  protected function getImageSizes( MessageLocalizer $l10n ) {
1338  $ret = [];
1339  $pixels = $l10n->msg( 'unit-pixel' )->text();
1340 
1341  foreach ( $this->config->get( 'ImageLimits' ) as $index => $limits ) {
1342  // Note: A left-to-right marker (\u200e) is inserted, see T144386
1343  $display = "{$limits[0]}" . json_decode( '"\u200e"' ) . "×{$limits[1]}" . $pixels;
1344  $ret[$display] = $index;
1345  }
1346 
1347  return $ret;
1348  }
1349 
1354  protected function getThumbSizes( MessageLocalizer $l10n ) {
1355  $ret = [];
1356  $pixels = $l10n->msg( 'unit-pixel' )->text();
1357 
1358  foreach ( $this->config->get( 'ThumbLimits' ) as $index => $size ) {
1359  $display = $size . $pixels;
1360  $ret[$display] = $index;
1361  }
1362 
1363  return $ret;
1364  }
1365 
1372  protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1373  $maxSigChars = $this->config->get( 'MaxSigChars' );
1374  if ( mb_strlen( $signature ) > $maxSigChars ) {
1375  return Xml::element( 'span', [ 'class' => 'error' ],
1376  $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() );
1377  } elseif ( isset( $alldata['fancysig'] ) &&
1378  $alldata['fancysig'] &&
1379  MediaWikiServices::getInstance()->getParser()->validateSig( $signature ) === false
1380  ) {
1381  return Xml::element(
1382  'span',
1383  [ 'class' => 'error' ],
1384  $form->msg( 'badsig' )->text()
1385  );
1386  } else {
1387  return true;
1388  }
1389  }
1390 
1397  protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
1398  $parser = MediaWikiServices::getInstance()->getParser();
1399  if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
1400  $signature = $parser->cleanSig( $signature );
1401  } else {
1402  // When no fancy sig used, make sure ~{3,5} get removed.
1403  $signature = Parser::cleanSigInSig( $signature );
1404  }
1405 
1406  return $signature;
1407  }
1408 
1416  public function getForm(
1417  User $user,
1419  $formClass = PreferencesForm::class,
1420  array $remove = []
1421  ) {
1422  $formDescriptor = $this->getFormDescriptor( $user, $context );
1423  if ( count( $remove ) ) {
1424  $removeKeys = array_flip( $remove );
1425  $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
1426  }
1427 
1428  // Remove type=api preferences. They are not intended for rendering in the form.
1429  foreach ( $formDescriptor as $name => $info ) {
1430  if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
1431  unset( $formDescriptor[$name] );
1432  }
1433  }
1434 
1438  $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
1439 
1440  $htmlForm->setModifiedUser( $user );
1441  $htmlForm->setId( 'mw-prefs-form' );
1442  $htmlForm->setAutocomplete( 'off' );
1443  $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() );
1444  # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
1445  $htmlForm->setSubmitTooltip( 'preferences-save' );
1446  $htmlForm->setSubmitID( 'prefcontrol' );
1447  $htmlForm->setSubmitCallback( function ( array $formData, PreferencesForm $form ) {
1448  return $this->submitForm( $formData, $form );
1449  } );
1450 
1451  return $htmlForm;
1452  }
1453 
1459  $opt = [];
1460 
1461  $localTZoffset = $this->config->get( 'LocalTZoffset' );
1462  $timeZoneList = $this->getTimeZoneList( $context->getLanguage() );
1463 
1464  $timestamp = MWTimestamp::getLocalInstance();
1465  // Check that the LocalTZoffset is the same as the local time zone offset
1466  if ( $localTZoffset == $timestamp->format( 'Z' ) / 60 ) {
1467  $timezoneName = $timestamp->getTimezone()->getName();
1468  // Localize timezone
1469  if ( isset( $timeZoneList[$timezoneName] ) ) {
1470  $timezoneName = $timeZoneList[$timezoneName]['name'];
1471  }
1472  $server_tz_msg = $context->msg(
1473  'timezoneuseserverdefault',
1474  $timezoneName
1475  )->text();
1476  } else {
1477  $tzstring = sprintf(
1478  '%+03d:%02d',
1479  floor( $localTZoffset / 60 ),
1480  abs( $localTZoffset ) % 60
1481  );
1482  $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
1483  }
1484  $opt[$server_tz_msg] = "System|$localTZoffset";
1485  $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
1486  $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
1487 
1488  foreach ( $timeZoneList as $timeZoneInfo ) {
1489  $region = $timeZoneInfo['region'];
1490  if ( !isset( $opt[$region] ) ) {
1491  $opt[$region] = [];
1492  }
1493  $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection'];
1494  }
1495  return $opt;
1496  }
1497 
1503  protected function filterTimezoneInput( $tz, array $alldata ) {
1504  $data = explode( '|', $tz, 3 );
1505  switch ( $data[0] ) {
1506  case 'ZoneInfo':
1507  $valid = false;
1508 
1509  if ( count( $data ) === 3 ) {
1510  // Make sure this timezone exists
1511  try {
1512  new DateTimeZone( $data[2] );
1513  // If the constructor didn't throw, we know it's valid
1514  $valid = true;
1515  } catch ( Exception $e ) {
1516  // Not a valid timezone
1517  }
1518  }
1519 
1520  if ( !$valid ) {
1521  // If the supplied timezone doesn't exist, fall back to the encoded offset
1522  return 'Offset|' . intval( $tz[1] );
1523  }
1524  return $tz;
1525  case 'System':
1526  return $tz;
1527  default:
1528  $data = explode( ':', $tz, 2 );
1529  if ( count( $data ) == 2 ) {
1530  $data[0] = intval( $data[0] );
1531  $data[1] = intval( $data[1] );
1532  $minDiff = abs( $data[0] ) * 60 + $data[1];
1533  if ( $data[0] < 0 ) {
1534  $minDiff = - $minDiff;
1535  }
1536  } else {
1537  $minDiff = intval( $data[0] ) * 60;
1538  }
1539 
1540  # Max is +14:00 and min is -12:00, see:
1541  # https://en.wikipedia.org/wiki/Timezone
1542  $minDiff = min( $minDiff, 840 ); # 14:00
1543  $minDiff = max( $minDiff, -720 ); # -12:00
1544  return 'Offset|' . $minDiff;
1545  }
1546  }
1547 
1555  protected function saveFormData( $formData, PreferencesForm $form ) {
1556  $user = $form->getModifiedUser();
1557  $hiddenPrefs = $this->config->get( 'HiddenPrefs' );
1558  $result = true;
1559 
1560  if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
1561  return Status::newFatal( 'mypreferencesprotected' );
1562  }
1563 
1564  // Filter input
1565  foreach ( array_keys( $formData ) as $name ) {
1566  $filters = $this->getSaveFilters();
1567  if ( isset( $filters[$name] ) ) {
1568  $formData[$name] = call_user_func( $filters[$name], $formData[$name], $formData );
1569  }
1570  }
1571 
1572  // Fortunately, the realname field is MUCH simpler
1573  // (not really "private", but still shouldn't be edited without permission)
1574 
1575  if ( !in_array( 'realname', $hiddenPrefs )
1576  && $user->isAllowed( 'editmyprivateinfo' )
1577  && array_key_exists( 'realname', $formData )
1578  ) {
1579  $realName = $formData['realname'];
1580  $user->setRealName( $realName );
1581  }
1582 
1583  if ( $user->isAllowed( 'editmyoptions' ) ) {
1584  $oldUserOptions = $user->getOptions();
1585 
1586  foreach ( $this->getSaveBlacklist() as $b ) {
1587  unset( $formData[$b] );
1588  }
1589 
1590  # If users have saved a value for a preference which has subsequently been disabled
1591  # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
1592  # is subsequently re-enabled
1593  foreach ( $hiddenPrefs as $pref ) {
1594  # If the user has not set a non-default value here, the default will be returned
1595  # and subsequently discarded
1596  $formData[$pref] = $user->getOption( $pref, null, true );
1597  }
1598 
1599  // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
1600  if (
1601  isset( $formData['rclimit'] ) &&
1602  intval( $formData[ 'rclimit' ] ) !== $user->getIntOption( 'rclimit' )
1603  ) {
1604  $formData['rcfilters-limit'] = $formData['rclimit'];
1605  }
1606 
1607  // Keep old preferences from interfering due to back-compat code, etc.
1608  $user->resetOptions( 'unused', $form->getContext() );
1609 
1610  foreach ( $formData as $key => $value ) {
1611  $user->setOption( $key, $value );
1612  }
1613 
1614  Hooks::run(
1615  'PreferencesFormPreSave',
1616  [ $formData, $form, $user, &$result, $oldUserOptions ]
1617  );
1618  }
1619 
1620  AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] );
1621  $user->saveSettings();
1622 
1623  return $result;
1624  }
1625 
1635  public function legacySaveFormData( $formData, PreferencesForm $form ) {
1636  return $this->saveFormData( $formData, $form );
1637  }
1638 
1646  protected function submitForm( array $formData, PreferencesForm $form ) {
1647  $res = $this->saveFormData( $formData, $form );
1648 
1649  if ( $res ) {
1650  $urlOptions = [];
1651 
1652  if ( $res === 'eauth' ) {
1653  $urlOptions['eauth'] = 1;
1654  }
1655 
1656  $urlOptions += $form->getExtraSuccessRedirectParameters();
1657 
1658  $url = $form->getTitle()->getFullURL( $urlOptions );
1659 
1660  $context = $form->getContext();
1661  // Set session data for the success message
1662  $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
1663 
1664  $context->getOutput()->redirect( $url );
1665  }
1666 
1667  return Status::newGood();
1668  }
1669 
1679  public function legacySubmitForm( array $formData, PreferencesForm $form ) {
1680  return $this->submitForm( $formData, $form );
1681  }
1682 
1691  protected function getTimeZoneList( Language $language ) {
1692  $identifiers = DateTimeZone::listIdentifiers();
1693  if ( $identifiers === false ) {
1694  return [];
1695  }
1696  sort( $identifiers );
1697 
1698  $tzRegions = [
1699  'Africa' => wfMessage( 'timezoneregion-africa' )->inLanguage( $language )->text(),
1700  'America' => wfMessage( 'timezoneregion-america' )->inLanguage( $language )->text(),
1701  'Antarctica' => wfMessage( 'timezoneregion-antarctica' )->inLanguage( $language )->text(),
1702  'Arctic' => wfMessage( 'timezoneregion-arctic' )->inLanguage( $language )->text(),
1703  'Asia' => wfMessage( 'timezoneregion-asia' )->inLanguage( $language )->text(),
1704  'Atlantic' => wfMessage( 'timezoneregion-atlantic' )->inLanguage( $language )->text(),
1705  'Australia' => wfMessage( 'timezoneregion-australia' )->inLanguage( $language )->text(),
1706  'Europe' => wfMessage( 'timezoneregion-europe' )->inLanguage( $language )->text(),
1707  'Indian' => wfMessage( 'timezoneregion-indian' )->inLanguage( $language )->text(),
1708  'Pacific' => wfMessage( 'timezoneregion-pacific' )->inLanguage( $language )->text(),
1709  ];
1710  asort( $tzRegions );
1711 
1712  $timeZoneList = [];
1713 
1714  $now = new DateTime();
1715 
1716  foreach ( $identifiers as $identifier ) {
1717  $parts = explode( '/', $identifier, 2 );
1718 
1719  // DateTimeZone::listIdentifiers() returns a number of
1720  // backwards-compatibility entries. This filters them out of the
1721  // list presented to the user.
1722  if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) {
1723  continue;
1724  }
1725 
1726  // Localize region
1727  $parts[0] = $tzRegions[$parts[0]];
1728 
1729  $dateTimeZone = new DateTimeZone( $identifier );
1730  $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 );
1731 
1732  $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] );
1733  $value = "ZoneInfo|$minDiff|$identifier";
1734 
1735  $timeZoneList[$identifier] = [
1736  'name' => $display,
1737  'timecorrection' => $value,
1738  'region' => $parts[0],
1739  ];
1740  }
1741 
1742  return $timeZoneList;
1743  }
1744 }
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1795
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:247
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:40
MWTimestamp
Library for creating and parsing MW-style timestamps.
Definition: MWTimestamp.php:32
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
MediaWiki\Preferences\DefaultPreferencesFactory\__construct
__construct(Config $config, Language $contLang, AuthManager $authManager, LinkRenderer $linkRenderer)
Definition: DefaultPreferencesFactory.php:82
MediaWiki\$action
String $action
Cache what action this request is.
Definition: MediaWiki.php:48
MediaWiki\Preferences\DefaultPreferencesFactory\watchlistPreferences
watchlistPreferences(User $user, IContextSource $context, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:1051
wfCanIPUseHTTPS
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
Definition: GlobalFunctions.php:3308
MediaWiki\Preferences\DefaultPreferencesFactory\submitForm
submitForm(array $formData, PreferencesForm $form)
Save the form data and reload the page.
Definition: DefaultPreferencesFactory.php:1646
MWNamespace\getValidNamespaces
static getValidNamespaces()
Returns an array of the namespaces (by integer id) that exist on the wiki.
Definition: MWNamespace.php:290
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
$rows
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:2783
wfMessage
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 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
array
the array() calling protocol came about after MediaWiki 1.4rc1.
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
PreferencesForm\getExtraSuccessRedirectParameters
getExtraSuccessRedirectParameters()
Get extra parameters for the query string when redirecting after successful save.
Definition: PreferencesForm.php:56
$opt
$opt
Definition: postprocess-phan.php:119
$languages
switch( $options['output']) $languages
Definition: transstat.php:76
ContextSource\msg
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:168
Title\newMainPage
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:586
UserGroupMembership\getExpiry
getExpiry()
Definition: UserGroupMembership.php:75
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
MediaWiki\Preferences\DefaultPreferencesFactory\getSaveFilters
getSaveFilters()
Definition: DefaultPreferencesFactory.php:98
$ret
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:2005
MediaWiki\Preferences\DefaultPreferencesFactory\editingPreferences
editingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:885
MediaWiki\Preferences\DefaultPreferencesFactory\getTimezoneOptions
getTimezoneOptions(IContextSource $context)
Definition: DefaultPreferencesFactory.php:1458
$wgDefaultUserOptions
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
Definition: DefaultSettings.php:4856
MediaWiki\Preferences\DefaultPreferencesFactory\profilePreferences
profilePreferences(User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS)
Definition: DefaultPreferencesFactory.php:254
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
MediaWiki\Preferences\DefaultPreferencesFactory\renderingPreferences
renderingPreferences(MessageLocalizer $l10n, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:823
$res
$res
Definition: database.txt:21
User
User
Definition: All_system_messages.txt:425
$result
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. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. '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. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) '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 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) '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! 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:1993
MediaWiki\Preferences\DefaultPreferencesFactory\getOptionFromUser
getOptionFromUser( $name, $info, User $user)
Pull option from a user account.
Definition: DefaultPreferencesFactory.php:208
php
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:37
MessageLocalizer
Interface for localizing messages in MediaWiki.
Definition: MessageLocalizer.php:28
User\getDefaultOptions
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition: User.php:1722
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
MediaWiki\Auth\PasswordAuthenticationRequest
This is a value object for authentication requests with a username and password.
Definition: PasswordAuthenticationRequest.php:29
MediaWiki\Preferences\DefaultPreferencesFactory\$authManager
AuthManager $authManager
Definition: DefaultPreferencesFactory.php:71
Config
Interface for configuration instances.
Definition: Config.php:28
MediaWiki\Preferences\DefaultPreferencesFactory\getThumbSizes
getThumbSizes(MessageLocalizer $l10n)
Definition: DefaultPreferencesFactory.php:1354
MWException
MediaWiki exception.
Definition: MWException.php:26
MediaWiki\Preferences
Definition: DefaultPreferencesFactory.php:21
PreferencesForm\getModifiedUser
getModifiedUser()
Definition: PreferencesForm.php:42
MediaWiki\Preferences\DefaultPreferencesFactory\getImageSizes
getImageSizes(MessageLocalizer $l10n)
Definition: DefaultPreferencesFactory.php:1337
MediaWiki\Preferences\DefaultPreferencesFactory\getTimeZoneList
getTimeZoneList(Language $language)
Get a list of all time zones.
Definition: DefaultPreferencesFactory.php:1691
Language\fetchLanguageNames
static fetchLanguageNames( $inLanguage=null, $include='mw')
Get an array of language names, indexed by code.
Definition: Language.php:803
UserGroupMembership\getLink
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
Definition: UserGroupMembership.php:375
MediaWiki\Preferences\DefaultPreferencesFactory\$linkRenderer
LinkRenderer $linkRenderer
Definition: DefaultPreferencesFactory.php:74
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2603
HTMLFormField
The parent class to generate form fields.
Definition: HTMLFormField.php:7
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
MediaWiki\Preferences\DefaultPreferencesFactory\rcPreferences
rcPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:957
MWNamespace
This is a utility class with only static functions for dealing with namespaces that encodes all the "...
Definition: MWNamespace.php:32
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3021
MessageLocalizer\msg
msg( $key)
This is the method for getting translated interface messages.
MediaWiki\Preferences\DefaultPreferencesFactory\cleanSignature
cleanSignature( $signature, $alldata, HTMLForm $form)
Definition: DefaultPreferencesFactory.php:1397
PreferencesForm
Form to edit user preferences.
Definition: PreferencesForm.php:26
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2009
MediaWiki\Preferences\DefaultPreferencesFactory\legacySaveFormData
legacySaveFormData( $formData, PreferencesForm $form)
DO NOT USE.
Definition: DefaultPreferencesFactory.php:1635
MediaWiki\Preferences\DefaultPreferencesFactory\$contLang
Language $contLang
The wiki's content language, equivalent to $wgContLang.
Definition: DefaultPreferencesFactory.php:68
MediaWiki\Preferences\DefaultPreferencesFactory\filesPreferences
filesPreferences(IContextSource $context, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:726
HTMLForm\loadInputFromParameters
static loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent=null)
Initialise a new Object for the field.
Definition: HTMLForm.php:477
MediaWiki\Auth\AuthManager\callLegacyAuthPlugin
static callLegacyAuthPlugin( $method, array $params, $return=null)
Call a legacy AuthPlugin method, if necessary.
Definition: AuthManager.php:239
MediaWiki\Preferences\DefaultPreferencesFactory\legacySubmitForm
legacySubmitForm(array $formData, PreferencesForm $form)
DO NOT USE.
Definition: DefaultPreferencesFactory.php:1679
MediaWiki\Preferences\DefaultPreferencesFactory\searchPreferences
searchPreferences(&$defaultPreferences)
Definition: DefaultPreferencesFactory.php:1211
$options
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:2001
MediaWiki\Preferences\DefaultPreferencesFactory\saveFormData
saveFormData( $formData, PreferencesForm $form)
Handle the form submission if everything validated properly.
Definition: DefaultPreferencesFactory.php:1555
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:774
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:109
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:562
$value
$value
Definition: styleTest.css.php:45
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:1005
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:36
MediaWiki\Preferences\PreferencesFactory
A PreferencesFactory is a MediaWiki service that provides the definitions of preferences for a given ...
Definition: PreferencesFactory.php:51
MediaWiki\Preferences\DefaultPreferencesFactory\getForm
getForm(User $user, IContextSource $context, $formClass=PreferencesForm::class, array $remove=[])
Definition: DefaultPreferencesFactory.php:1416
MediaWiki\Preferences\DefaultPreferencesFactory\getDateOptions
getDateOptions(IContextSource $context)
Definition: DefaultPreferencesFactory.php:1303
IContextSource\getUser
getUser()
Parser
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition: Parser.php:70
IContextSource\getTitle
getTitle()
MediaWiki\Preferences\DefaultPreferencesFactory\$config
Config $config
Definition: DefaultPreferencesFactory.php:65
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:83
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
Title
Represents a title within MediaWiki.
Definition: Title.php:39
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
LanguageConverter\$languagesWithVariants
static array $languagesWithVariants
languages supporting variants
Definition: LanguageConverter.php:40
MediaWiki\Preferences\DefaultPreferencesFactory\getSaveBlacklist
getSaveBlacklist()
@inheritDoc
Definition: DefaultPreferencesFactory.php:114
$code
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:865
MediaWiki\Preferences\DefaultPreferencesFactory\filterTimezoneInput
filterTimezoneInput( $tz, array $alldata)
Definition: DefaultPreferencesFactory.php:1503
MediaWiki\Preferences\DefaultPreferencesFactory\loadPreferenceValues
loadPreferenceValues(User $user, IContextSource $context, &$defaultPreferences)
Loads existing values for a given array of preferences.
Definition: DefaultPreferencesFactory.php:156
CentralIdLookup
The CentralIdLookup service allows for connecting local users with cluster-wide IDs.
Definition: CentralIdLookup.php:30
MediaWiki\Preferences\DefaultPreferencesFactory\datetimePreferences
datetimePreferences( $user, IContextSource $context, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:748
as
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:22
LanguageCode\bcp47
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Definition: LanguageCode.php:94
NS_USER
const NS_USER
Definition: Defines.php:76
HTMLForm\getTitle
getTitle()
Get the title.
Definition: HTMLForm.php:1571
MediaWiki\Preferences\DefaultPreferencesFactory
This is the default implementation of PreferencesFactory.
Definition: DefaultPreferencesFactory.php:61
IContextSource\getRequest
getRequest()
LanguageCode
Methods for dealing with language codes.
Definition: LanguageCode.php:29
HTMLFormField\flattenOptions
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Definition: HTMLFormField.php:1122
SpecialWatchlist\checkStructuredFilterUiEnabled
static checkStructuredFilterUiEnabled(Config $config, User $user)
Static method to check whether StructuredFilter UI is enabled for the given user.
Definition: SpecialWatchlist.php:113
class
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:56
$t
$t
Definition: testCompression.php:69
MediaWikiServices
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:25
Skin\getAllowedSkins
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
Definition: Skin.php:78
Skin
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:36
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:38
MediaWiki\Preferences\DefaultPreferencesFactory\validateSignature
validateSignature( $signature, $alldata, HTMLForm $form)
Definition: DefaultPreferencesFactory.php:1372
Parser\cleanSigInSig
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4658
CentralIdLookup\factory
static factory( $providerId=null)
Fetch a CentralIdLookup.
Definition: CentralIdLookup.php:46
MediaWiki\Preferences\DefaultPreferencesFactory\generateSkinOptions
generateSkinOptions(User $user, IContextSource $context)
Definition: DefaultPreferencesFactory.php:1224
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
IContextSource\getOutput
getOutput()
LanguageConverter
Base class for language conversion.
Definition: LanguageConverter.php:34
MWTimestamp\getLocalInstance
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Definition: MWTimestamp.php:204
Xml
Module of static functions for generating XML.
Definition: Xml.php:26
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
Language
Internationalisation code.
Definition: Language.php:35
MediaWiki\Preferences\DefaultPreferencesFactory\skinPreferences
skinPreferences(User $user, IContextSource $context, &$defaultPreferences)
Definition: DefaultPreferencesFactory.php:678
MediaWiki\Preferences\DefaultPreferencesFactory\getFormDescriptor
getFormDescriptor(User $user, IContextSource $context)
Definition: DefaultPreferencesFactory.php:127
Hooks
Hooks class.
Definition: Hooks.php:34
UserGroupMembership
Represents a "user group membership" – a specific instance of a user belonging to a group.
Definition: UserGroupMembership.php:37
IContextSource\getLanguage
getLanguage()
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
Html
This class is a collection of static functions that serve two purposes:
Definition: Html.php:48
HTMLForm
Object handling generic submission, CSRF protection, layout and other logic for UI forms.
Definition: HTMLForm.php:130