MediaWiki REL1_33
DefaultPreferencesFactory.php
Go to the documentation of this file.
1<?php
22
24use DateTime;
25use DateTimeZone;
26use Exception;
28use Html;
47use Psr\Log\LoggerAwareTrait;
48use Psr\Log\NullLogger;
49use Skin;
53use UnexpectedValueException;
54use User;
56use 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 public function getSaveBlacklist() {
99 return [
100 'realname',
101 'emailaddress',
102 ];
103 }
104
111 public function getFormDescriptor( User $user, IContextSource $context ) {
112 $preferences = [];
113
114 OutputPage::setupOOUI(
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
376 $languages = Language::fetchLanguageNames( null, 'mwfile' );
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 '~~~',
462 $user,
463 ParserOptions::newFromContext( $context )
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'] = [
790 'class' => \HTMLSelectOrOtherField::class,
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
1238 protected function generateSkinOptions( User $user, IContextSource $context ) {
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
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 = PreferencesFormLegacy::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}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
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
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfCanIPUseHTTPS( $ip)
Determine whether the client at a given source IP is likely to be able to access the wiki via HTTPS.
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
The parent class to generate form fields.
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
Object handling generic submission, CSRF protection, layout and other logic for UI forms.
Definition HTMLForm.php:133
getTitle()
Get the title.
Hooks class.
Definition Hooks.php:34
This class is a collection of static functions that serve two purposes:
Definition Html.php:49
Methods for dealing with language codes.
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Base class for language conversion.
static array $languagesWithVariants
languages supporting variants
Internationalisation code.
Definition Language.php:36
MediaWiki exception.
This is a utility class with only static functions for dealing with namespaces that encodes all the "...
Library for creating and parsing MW-style timestamps.
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
This serves as the entry point to the authentication system.
This is a value object for authentication requests with a username and password.
Class that generates HTML links for pages.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
This is the default implementation of PreferencesFactory.
validateSignature( $signature, $alldata, HTMLForm $form)
getTimeZoneList(Language $language)
Get a list of all time zones.
rcPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
profilePreferences(User $user, IContextSource $context, &$defaultPreferences, $canIPUseHTTPS)
watchlistPreferences(User $user, IContextSource $context, &$defaultPreferences)
renderingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
skinPreferences(User $user, IContextSource $context, &$defaultPreferences)
loadPreferenceValues(User $user, IContextSource $context, &$defaultPreferences)
Loads existing values for a given array of preferences.
editingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
datetimePreferences( $user, IContextSource $context, &$defaultPreferences)
getOptionFromUser( $name, $info, array $userOptions)
Pull option from a user account.
cleanSignature( $signature, $alldata, HTMLForm $form)
getForm(User $user, IContextSource $context, $formClass=PreferencesFormLegacy::class, array $remove=[])
applyFilters(array &$preferences, array $formDescriptor, $verb)
Applies filters to preferences either before or after form usage.
submitForm(array $formData, HTMLForm $form, array $formDescriptor)
Save the form data and reload the page.
filesPreferences(IContextSource $context, &$defaultPreferences)
saveFormData( $formData, HTMLForm $form, array $formDescriptor)
Handle the form submission if everything validated properly.
__construct(Config $config, Language $contLang, AuthManager $authManager, LinkRenderer $linkRenderer)
getTitle()
Get the Title object that we'll be acting on, as specified in the WebRequest.
This class should be covered by a general architecture document which does not exist as of January 20...
Set options of the Parser.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:69
static stripOuterParagraph( $html)
Strip outer.
Definition Parser.php:6339
static cleanSigInSig( $text)
Strip 3, 4 or 5 tildes out of signatures.
Definition Parser.php:4770
Form to edit user preferences.
The main skin class which provides methods and properties for all other skins.
Definition Skin.php:38
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
Definition Skin.php:80
Parent class for all special pages.
static checkStructuredFilterUiEnabled(Config $config, User $user)
Static method to check whether StructuredFilter UI is enabled for the given user.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:40
Represents a title within MediaWiki.
Definition Title.php:40
Represents a "user group membership" – a specific instance of a user belonging to a group.
static getLink( $ugm, IContextSource $context, $format, $userName=null)
Gets a link for a user group, possibly including the expiry date if relevant.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
static getDefaultOptions()
Combine the language default options with any site-specific options and add the default language vari...
Definition User.php:1776
Module of static functions for generating XML.
Definition Xml.php:28
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition Xml.php:41
$res
Definition database.txt:21
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
const NS_USER
Definition Defines.php:75
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1802
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:2818
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition hooks.txt:1834
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. '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:1991
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:181
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:2157
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:1999
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 you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition hooks.txt:2848
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:856
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:2003
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;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:271
return true to allow those checks to and false if checking is done & $user
Definition hooks.txt:1510
returning false will NOT prevent logging $e
Definition hooks.txt:2175
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
Interface for configuration instances.
Definition Config.php:28
Interface for objects which can provide a MediaWiki context on request.
Base interface for user preference flters that work as a middleware between storage and interface.
Definition Filter.php:27
A PreferencesFactory is a MediaWiki service that provides the definitions of preferences for a given ...
Interface for localizing messages in MediaWiki.
msg( $key)
This is the method for getting translated interface messages.
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))
$filter
if(!isset( $args[0])) $lang
switch( $options['output']) $languages
Definition transstat.php:76