MediaWiki master
DefaultPreferencesFactory.php
Go to the documentation of this file.
1<?php
8
53use OOUI\ButtonWidget;
54use OOUI\FieldLayout;
55use OOUI\HorizontalLayout;
56use OOUI\HtmlSnippet;
57use OOUI\LabelWidget;
58use OOUI\MessageWidget;
59use Psr\Log\LoggerAwareTrait;
60use Psr\Log\NullLogger;
61use UnexpectedValueException;
63
68 use LoggerAwareTrait;
69
71 protected $options;
72
74 protected $contLang;
75
78
80 protected $authManager;
81
83 protected $linkRenderer;
84
86 protected $nsInfo;
87
90
92 private $languageConverter;
93
95 private $hookRunner;
96
99
101 private $languageConverterFactory;
102
104 private $parserFactory;
105
107 private $skinFactory;
108
110 private $userGroupManager;
111
113 private $signatureValidatorFactory;
114
118 public const CONSTRUCTOR_OPTIONS = [
150 ];
151
169 public function __construct(
176 ILanguageConverter $languageConverter,
178 HookContainer $hookContainer,
179 UserOptionsLookup $userOptionsLookup,
180 ?LanguageConverterFactory $languageConverterFactory = null,
181 ?ParserFactory $parserFactory = null,
182 ?SkinFactory $skinFactory = null,
183 ?UserGroupManager $userGroupManager = null,
184 ?SignatureValidatorFactory $signatureValidatorFactory = null
185 ) {
186 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
187
188 $this->options = $options;
189 $this->contLang = $contLang;
190 $this->authManager = $authManager;
191 $this->linkRenderer = $linkRenderer;
192 $this->nsInfo = $nsInfo;
193
194 // We don't use the PermissionManager anymore, but we need to be careful
195 // removing the parameter since this class is extended by GlobalPreferencesFactory
196 // in the GlobalPreferences extension, and that class uses it
197 $this->permissionManager = $permissionManager;
198
199 $this->logger = new NullLogger();
200 $this->languageConverter = $languageConverter;
201 $this->languageNameUtils = $languageNameUtils;
202 $this->hookRunner = new HookRunner( $hookContainer );
203
204 // Don't break GlobalPreferences, fall back to global state if missing services
205 // or if passed a UserOptionsLookup that isn't UserOptionsManager
206 $services = static function () {
207 // BC hack. Use a closure so this can be unit-tested.
209 };
210 $this->userOptionsManager = ( $userOptionsLookup instanceof UserOptionsManager )
211 ? $userOptionsLookup
212 : $services()->getUserOptionsManager();
213 $this->languageConverterFactory = $languageConverterFactory ?? $services()->getLanguageConverterFactory();
214
215 $this->parserFactory = $parserFactory ?? $services()->getParserFactory();
216 $this->skinFactory = $skinFactory ?? $services()->getSkinFactory();
217 $this->userGroupManager = $userGroupManager ?? $services()->getUserGroupManager();
218 $this->signatureValidatorFactory = $signatureValidatorFactory
219 ?? $services()->getSignatureValidatorFactory();
220 }
221
225 public function getSaveBlacklist() {
226 return [
227 'realname',
228 'emailaddress',
229 ];
230 }
231
237 public function getFormDescriptor( User $user, IContextSource $context ) {
238 $preferences = [];
239
240 OutputPage::setupOOUI(
241 strtolower( $context->getSkin()->getSkinName() ),
242 $context->getLanguage()->getDir()
243 );
244
245 $this->profilePreferences( $user, $context, $preferences );
246 $this->skinPreferences( $user, $context, $preferences );
247 $this->datetimePreferences( $user, $context, $preferences );
248 $this->filesPreferences( $context, $preferences );
249 $this->renderingPreferences( $user, $context, $preferences );
250 $this->editingPreferences( $user, $context, $preferences );
251 $this->rcPreferences( $user, $context, $preferences );
252 $this->watchlistPreferences( $user, $context, $preferences );
253 $this->searchPreferences( $context, $preferences );
254
255 $this->hookRunner->onGetPreferences( $user, $preferences );
256
257 $this->loadPreferenceValues( $user, $context, $preferences );
258 $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
259 return $preferences;
260 }
261
268 public static function simplifyFormDescriptor( array $descriptor ) {
269 foreach ( $descriptor as $name => &$params ) {
270 // Info fields are useless and can use complicated closure to provide
271 // text, skip all of them.
272 if ( ( isset( $params['type'] ) && $params['type'] === 'info' ) ||
273 // Checking old alias for compatibility with unchanged extensions
274 ( isset( $params['class'] ) && $params['class'] === \HTMLInfoField::class ) ||
275 ( isset( $params['class'] ) && $params['class'] === HTMLInfoField::class )
276 ) {
277 unset( $descriptor[$name] );
278 continue;
279 }
280 // Message parsing is the heaviest load when constructing the field,
281 // but we just want to validate data.
282 foreach ( $params as $key => $value ) {
283 switch ( $key ) {
284 // Special case, should be kept.
285 case 'options-message':
286 break;
287 // Special case, should be transferred.
288 case 'options-messages':
289 unset( $params[$key] );
290 $params['options'] = $value;
291 break;
292 default:
293 if ( preg_match( '/-messages?$/', $key ) ) {
294 // Unwanted.
295 unset( $params[$key] );
296 }
297 }
298 }
299 }
300 return $descriptor;
301 }
302
310 private function loadPreferenceValues( User $user, IContextSource $context, &$defaultPreferences ) {
311 // Remove preferences that wikis don't want to use
312 foreach ( $this->options->get( MainConfigNames::HiddenPrefs ) as $pref ) {
313 unset( $defaultPreferences[$pref] );
314 }
315
316 // For validation.
317 $simplified = self::simplifyFormDescriptor( $defaultPreferences );
318 $form = new HTMLForm( $simplified, $context );
319
320 $disable = !$user->isAllowed( 'editmyoptions' );
321
322 $defaultOptions = $this->userOptionsManager->getDefaultOptions( $user );
323 $userOptions = $this->userOptionsManager->getOptions( $user );
324 $this->applyFilters( $userOptions, $defaultPreferences, 'filterForForm' );
325 // Add in defaults from the user
326 foreach ( $simplified as $name => $_ ) {
327 $info = &$defaultPreferences[$name];
328 if ( $disable && !in_array( $name, $this->getSaveBlacklist() ) ) {
329 $info['disabled'] = 'disabled';
330 }
331 if ( isset( $info['default'] ) ) {
332 // Already set, no problem
333 continue;
334 }
335 $field = $form->getField( $name );
336 $globalDefault = $defaultOptions[$name] ?? null;
337 $prefFromUser = static::getPreferenceForField( $name, $field, $userOptions );
338
339 // If it validates, set it as the default
340 // FIXME: That's not how the validate() function works! Values of nested fields
341 // (e.g. CheckMatrix) would be missing.
342 if ( $prefFromUser !== null && // Make sure we're not just pulling nothing
343 $field->validate( $prefFromUser, $this->userOptionsManager->getOptions( $user ) ) === true ) {
344 $info['default'] = $prefFromUser;
345 } elseif ( $field->validate( $globalDefault, $this->userOptionsManager->getOptions( $user ) ) === true ) {
346 $info['default'] = $globalDefault;
347 } else {
348 $globalDefault = json_encode( $globalDefault );
349 throw new UnexpectedValueException(
350 "Default '$globalDefault' is invalid for preference $name of user " . $user->getName()
351 );
352 }
353 }
354
355 return $defaultPreferences;
356 }
357
368 public static function getPreferenceForField( $name, HTMLFormField $field, array $userOptions ) {
369 $val = $userOptions[$name] ?? null;
370
371 if ( $field instanceof HTMLNestedFilterable ) {
372 $val = [];
373 $prefix = $field->mParams['prefix'] ?? $name;
374 // Fetch all possible preference keys of the given field on this wiki.
375 $keys = array_keys( $field->filterDataForSubmit( [] ) );
376 foreach ( $keys as $key ) {
377 if ( $userOptions[$prefix . $key] ?? false ) {
378 $val[] = $key;
379 }
380 }
381 }
382
383 return $val;
384 }
385
395 protected function getOptionFromUser( $name, $info, array $userOptions ) {
396 $val = $userOptions[$name] ?? null;
397
398 // Handling for multiselect preferences
399 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
400 // Checking old alias for compatibility with unchanged extensions
401 ( isset( $info['class'] ) && $info['class'] === \HTMLMultiSelectField::class ) ||
402 ( isset( $info['class'] ) && $info['class'] === HTMLMultiSelectField::class )
403 ) {
404 $options = HTMLFormField::flattenOptions( $info['options-messages'] ?? $info['options'] );
405 $prefix = $info['prefix'] ?? $name;
406 $val = [];
407
408 foreach ( $options as $value ) {
409 if ( $userOptions["$prefix$value"] ?? false ) {
410 $val[] = $value;
411 }
412 }
413 }
414
415 // Handling for checkmatrix preferences
416 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
417 // Checking old alias for compatibility with unchanged extensions
418 ( isset( $info['class'] ) && $info['class'] === \HTMLCheckMatrix::class ) ||
419 ( isset( $info['class'] ) && $info['class'] === HTMLCheckMatrix::class )
420 ) {
421 $columns = HTMLFormField::flattenOptions( $info['columns'] );
422 $rows = HTMLFormField::flattenOptions( $info['rows'] );
423 $prefix = $info['prefix'] ?? $name;
424 $val = [];
425
426 foreach ( $columns as $column ) {
427 foreach ( $rows as $row ) {
428 if ( $userOptions["$prefix$column-$row"] ?? false ) {
429 $val[] = "$column-$row";
430 }
431 }
432 }
433 }
434
435 return $val;
436 }
437
445 protected function profilePreferences(
446 User $user, IContextSource $context, &$defaultPreferences
447 ) {
448 // retrieving user name for GENDER and misc.
449 $userName = $user->getName();
450
451 // Information panel
452 $defaultPreferences['username'] = [
453 'type' => 'info',
454 'label-message' => [ 'username', $userName ],
455 'default' => $userName,
456 'section' => 'personal/info',
457 ];
458
459 $lang = $context->getLanguage();
460
461 // Get groups to which the user belongs, Skip the default * group, seems useless here
462 $userEffectiveGroups = array_diff(
463 $this->userGroupManager->getUserEffectiveGroups( $user ),
464 [ '*' ]
465 );
466 $defaultPreferences['usergroups'] = [
467 'type' => 'info',
468 'label-message' => [ 'prefs-memberingroups',
469 Message::numParam( count( $userEffectiveGroups ) ), $userName ],
470 'default' => function () use ( $user, $userEffectiveGroups, $context ) {
471 return $this->renderUserGroupList( $user, $userEffectiveGroups, $context );
472 },
473 'raw' => true,
474 'section' => 'personal/info',
475 ];
476
477 $userDisabledGroups = $this->userGroupManager->getUserDisabledGroups( $user );
478 if ( $userDisabledGroups ) {
479 $conditionsLink = SpecialPage::getTitleFor(
480 'Listgrouprights', false,
482 )->getFullText();
483 $defaultPreferences['usergroups-disabled'] = [
484 'type' => 'info',
485 'label-message' => [ 'prefs-memberingroupsdisabled',
486 Message::numParam( count( $userDisabledGroups ) ), $userName ],
487 'default' => function () use ( $user, $userDisabledGroups, $context ) {
488 return $this->renderUserGroupList( $user, $userDisabledGroups, $context );
489 },
490 'help-message' => [ 'prefs-memberingroupsdisabled-help',
491 Message::numParam( count( $userDisabledGroups ) ), $user->getName(), $conditionsLink ],
492 'raw' => true,
493 'section' => 'personal/info',
494 ];
495 }
496
497 $contribTitle = SpecialPage::getTitleFor( "Contributions", $userName );
498 $formattedEditCount = $lang->formatNum( $user->getEditCount() );
499 $editCount = $this->linkRenderer->makeLink( $contribTitle, $formattedEditCount );
500
501 $defaultPreferences['editcount'] = [
502 'type' => 'info',
503 'raw' => true,
504 'label-message' => 'prefs-edits',
505 'default' => $editCount,
506 'section' => 'personal/info',
507 ];
508
509 if ( $user->getRegistration() ) {
510 $displayUser = $context->getUser();
511 $userRegistration = $user->getRegistration();
512 $defaultPreferences['registrationdate'] = [
513 'type' => 'info',
514 'label-message' => 'prefs-registration',
515 'default' => $context->msg(
516 'prefs-registration-date-time',
517 $lang->userTimeAndDate( $userRegistration, $displayUser ),
518 $lang->userDate( $userRegistration, $displayUser ),
519 $lang->userTime( $userRegistration, $displayUser )
520 )->text(),
521 'section' => 'personal/info',
522 ];
523 }
524
525 $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' );
526 $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' );
527
528 // Actually changeable stuff
529 $defaultPreferences['realname'] = [
530 // (not really "private", but still shouldn't be edited without permission)
531 'type' => $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'realname' )
532 ? 'text' : 'info',
533 'default' => $user->getRealName(),
534 'section' => 'personal/info',
535 'label-message' => 'yourrealname',
536 'help-message' => 'prefs-help-realname',
537 ];
538
539 if ( $canEditPrivateInfo && $this->authManager->allowsAuthenticationDataChange(
540 new PasswordAuthenticationRequest(), false )->isGood()
541 ) {
542 $defaultPreferences['password'] = [
543 'type' => 'info',
544 'raw' => true,
545 'default' => (string)new ButtonWidget( [
546 'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [
547 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
548 ] ),
549 'label' => $context->msg( 'prefs-resetpass' )->text(),
550 ] ),
551 'label-message' => 'yourpassword',
552 // email password reset feature only works for users that have an email set up
553 'help-raw' => $user->getEmail()
554 ? $context->msg( 'prefs-help-yourpassword',
555 '[[#mw-prefsection-personal-email|{{int:prefs-email}}]]' )->parse()
556 : '',
557 'section' => 'personal/accountsecurity',
558 ];
559 }
560 // Only show prefershttps if secure login is turned on
561 if ( !$this->options->get( MainConfigNames::ForceHTTPS )
562 && $this->options->get( MainConfigNames::SecureLogin )
563 ) {
564 $defaultPreferences['prefershttps'] = [
565 'type' => 'toggle',
566 'label-message' => 'tog-prefershttps',
567 'help-message' => 'prefs-help-prefershttps',
568 'section' => 'personal/info'
569 ];
570 }
571
572 $defaultPreferences['downloaduserdata'] = [
573 'type' => 'info',
574 'raw' => true,
575 'label-message' => 'prefs-user-downloaddata-label',
576 'default' => Html::element(
577 'a',
578 [
579 'href' => $this->options->get( MainConfigNames::ScriptPath ) .
580 '/api.php?action=query&meta=userinfo&uiprop=*&formatversion=2',
581 ],
582 $context->msg( 'prefs-user-downloaddata-info' )->text()
583 ),
584 'help-message' => [ 'prefs-user-downloaddata-help-message', urlencode( $user->getTitleKey() ) ],
585 'section' => 'personal/info',
586 ];
587
588 $defaultPreferences['restoreprefs'] = [
589 'type' => 'info',
590 'raw' => true,
591 'label-message' => 'prefs-user-restoreprefs-label',
592 'default' => Html::element(
593 'a',
594 [
595 'href' => SpecialPage::getTitleFor( 'Preferences' )
596 ->getSubpage( 'reset' )->getLocalURL()
597 ],
598 $context->msg( 'prefs-user-restoreprefs-info' )->text()
599 ),
600 'section' => 'personal/info',
601 ];
602
603 $languages = $this->languageNameUtils->getLanguageNames(
604 LanguageNameUtils::AUTONYMS,
605 LanguageNameUtils::SUPPORTED
606 );
607 $languageCode = $this->options->get( MainConfigNames::LanguageCode );
608 if ( !array_key_exists( $languageCode, $languages ) ) {
609 $languages[$languageCode] = $languageCode;
610 // Sort the array again
611 ksort( $languages );
612 }
613
614 $options = [];
615 foreach ( $languages as $code => $name ) {
616 $options[$code] = $name;
617 }
618
619 $defaultPreferences['language'] = [
620 'type' => 'language',
621 'section' => 'personal/i18n',
622 'useCodex' => true,
623 'label-message' => 'yourlanguage',
624 'languages' => $options,
625 ];
626
627 $neutralGenderMessage = $context->msg( 'gender-notknown' )->escaped() . (
628 !$context->msg( 'gender-unknown' )->isDisabled()
629 ? "<br>" . $context->msg( 'parentheses' )
630 ->params( $context->msg( 'gender-unknown' )->plain() )
631 ->escaped()
632 : ''
633 );
634
635 $defaultPreferences['gender'] = [
636 'type' => 'radio',
637 'section' => 'personal/i18n',
638 'options' => [
639 $neutralGenderMessage => 'unknown',
640 $context->msg( 'gender-female' )->escaped() => 'female',
641 $context->msg( 'gender-male' )->escaped() => 'male',
642 ],
643 'label-message' => 'yourgender',
644 'help-message' => 'prefs-help-gender',
645 ];
646
647 // see if there are multiple language variants to choose from
648 if ( !$this->languageConverterFactory->isConversionDisabled() ) {
649
650 foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
651 if ( $langCode == $this->contLang->getCode() ) {
652 if ( !$this->languageConverter->hasVariants() ) {
653 continue;
654 }
655
656 $variants = $this->languageConverter->getVariants();
657 $variantArray = [];
658 foreach ( $variants as $v ) {
659 $v = str_replace( '_', '-', strtolower( $v ) );
660 $variantArray[$v] = $lang->getVariantname( $v, false );
661 }
662
663 $options = [];
664 foreach ( $variantArray as $code => $name ) {
665 $options[$code] = $name;
666 }
667
668 $defaultPreferences['variant'] = [
669 'label-message' => 'yourvariant',
670 'type' => 'language',
671 'section' => 'personal/i18n',
672 'help-message' => 'prefs-help-variant',
673 'useCodex' => true,
674 'languages' => $options,
675 ];
676 } else {
677 $defaultPreferences["variant-$langCode"] = [
678 'type' => 'api',
679 ];
680 }
681 }
682 }
683
684 // show a preview of the old signature first
685 $oldsigWikiText = $this->parserFactory->getInstance()->preSaveTransform(
686 '~~~',
687 $context->getTitle(),
688 $user,
689 ParserOptions::newFromContext( $context )
690 );
691 $oldsigHTML = Parser::stripOuterParagraph(
692 $context->getOutput()->parseAsContent( $oldsigWikiText )
693 );
694 $signatureFieldConfig = [];
695 // Validate existing signature and show a message about it
696 $signature = $this->userOptionsManager->getOption( $user, 'nickname' );
697 $useFancySig = $this->userOptionsManager->getBoolOption( $user, 'fancysig' );
698 if ( $useFancySig && $signature !== '' ) {
699 $parserOpts = ParserOptions::newFromContext( $context );
700 $validator = $this->signatureValidatorFactory
701 ->newSignatureValidator( $user, $context, $parserOpts );
702 $signatureErrors = $validator->validateSignature( $signature );
703 if ( $signatureErrors ) {
704 $sigValidation = $this->options->get( MainConfigNames::SignatureValidation );
705 $oldsigHTML .= '<p><strong>' .
706 // Messages used here:
707 // * prefs-signature-invalid-warning
708 // * prefs-signature-invalid-new
709 // * prefs-signature-invalid-disallow
710 $context->msg( "prefs-signature-invalid-$sigValidation" )->parse() .
711 '</strong></p>';
712
713 // On initial page load, show the warnings as well
714 // (when posting, you get normal validation errors instead)
715 foreach ( $signatureErrors as &$sigError ) {
716 $sigError = new HtmlSnippet( $sigError );
717 }
718 if ( !$context->getRequest()->wasPosted() ) {
719 $signatureFieldConfig = [
720 'warnings' => $sigValidation !== 'disallow' ? $signatureErrors : null,
721 'errors' => $sigValidation === 'disallow' ? $signatureErrors : null,
722 ];
723 }
724 }
725 }
726 $defaultPreferences['oldsig'] = [
727 'type' => 'info',
728 // Normally HTMLFormFields do not display warnings, so we need to use 'rawrow'
729 // and provide the entire OOUI\FieldLayout here
730 'rawrow' => true,
731 'default' => new FieldLayout(
732 new LabelWidget( [
733 'label' => new HtmlSnippet( $oldsigHTML ),
734 ] ),
735 [
736 'align' => 'top',
737 'label' => new HtmlSnippet( $context->msg( 'tog-oldsig' )->parse() )
738 ] + $signatureFieldConfig
739 ),
740 'section' => 'personal/signature',
741 ];
742 $defaultPreferences['nickname'] = [
743 'type' => $this->authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info',
744 'maxlength' => $this->options->get( MainConfigNames::MaxSigChars ),
745 'label-message' => 'yournick',
746 'validation-callback' => function ( $signature, $alldata, HTMLForm $form ) {
747 return $this->validateSignature( $signature, $alldata, $form );
748 },
749 'section' => 'personal/signature',
750 'filter-callback' => function ( $signature, array $alldata, HTMLForm $form ) {
751 return $this->cleanSignature( $signature, $alldata, $form );
752 },
753 ];
754 $defaultPreferences['fancysig'] = [
755 'type' => 'toggle',
756 'label-message' => 'tog-fancysig',
757 // show general help about signature at the bottom of the section
758 'help-message' => 'prefs-help-signature',
759 'section' => 'personal/signature'
760 ];
761
762 // Email preferences
763 if ( $this->options->get( MainConfigNames::EnableEmail ) ) {
764 if ( $canViewPrivateInfo ) {
765 $helpMessages = [];
766 $helpMessages[] = $this->options->get( MainConfigNames::EmailConfirmToEdit )
767 ? 'prefs-help-email-required'
768 : 'prefs-help-email';
769
770 if ( $this->options->get( MainConfigNames::EnableUserEmail ) ) {
771 // additional messages when users can send email to each other
772 $helpMessages[] = 'prefs-help-email-others';
773 }
774
775 $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
776 if ( $canEditPrivateInfo && $this->authManager->allowsPropertyChange( 'emailaddress' ) ) {
777 $button = new ButtonWidget( [
778 'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [
779 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
780 ] ),
781 'label' =>
782 $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(),
783 ] );
784
785 $emailAddress .= $emailAddress == '' ? $button : ( '<br />' . $button );
786 }
787
788 $defaultPreferences['emailaddress'] = [
789 'type' => 'info',
790 'raw' => true,
791 'default' => $emailAddress,
792 'label-message' => 'youremail',
793 'section' => 'personal/email',
794 'help-messages' => $helpMessages,
795 // 'cssclass' chosen below
796 ];
797 }
798
799 $disableEmailPrefs = false;
800
801 $defaultPreferences['requireemail'] = [
802 'type' => 'toggle',
803 'label-message' => 'tog-requireemail',
804 'help-message' => 'prefs-help-requireemail',
805 'section' => 'personal/email',
806 'disabled' => !$user->getEmail(),
807 ];
808
809 if ( $this->options->get( MainConfigNames::EmailAuthentication ) ) {
810 if ( $user->getEmail() ) {
811 if ( $user->getEmailAuthenticationTimestamp() ) {
812 // date and time are separate parameters to facilitate localisation.
813 // $time is kept for backward compat reasons.
814 // 'emailauthenticated' is also used in SpecialConfirmemail.php
815 $displayUser = $context->getUser();
816 $emailTimestamp = $user->getEmailAuthenticationTimestamp();
817 $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
818 $d = $lang->userDate( $emailTimestamp, $displayUser );
819 $t = $lang->userTime( $emailTimestamp, $displayUser );
820 $emailauthenticated = $context->msg( 'emailauthenticated',
821 $time, $d, $t )->parse() . '<br />';
822 $emailauthenticationclass = 'mw-email-authenticated';
823 } else {
824 $disableEmailPrefs = true;
825 $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
826 new ButtonWidget( [
827 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(),
828 'label' => $context->msg( 'emailconfirmlink' )->text(),
829 ] );
830 $emailauthenticationclass = "mw-email-not-authenticated";
831 }
832 } else {
833 $disableEmailPrefs = true;
834 $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
835 $emailauthenticationclass = 'mw-email-none';
836 }
837
838 if ( $canViewPrivateInfo ) {
839 $defaultPreferences['emailauthentication'] = [
840 'type' => 'info',
841 'raw' => true,
842 'section' => 'personal/email',
843 'label-message' => 'prefs-emailconfirm-label',
844 'default' => $emailauthenticated,
845 // Apply the same CSS class used on the input to the message:
846 'cssclass' => $emailauthenticationclass,
847 ];
848 }
849 }
850
851 if ( $this->options->get( MainConfigNames::EnableUserEmail ) &&
852 $user->isAllowed( 'sendemail' )
853 ) {
854 $defaultPreferences['disablemail'] = [
855 'id' => 'wpAllowEmail',
856 'type' => 'toggle',
857 'invert' => true,
858 'section' => 'personal/email',
859 'label-message' => 'allowemail',
860 'disabled' => $disableEmailPrefs,
861 ];
862
863 $defaultPreferences['email-allow-new-users'] = [
864 'id' => 'wpAllowEmailFromNewUsers',
865 'type' => 'toggle',
866 'section' => 'personal/email',
867 'label-message' => 'email-allow-new-users-label',
868 'help-message' => 'prefs-help-email-allow-new-users',
869 'disabled' => $disableEmailPrefs,
870 'disable-if' => [ '!==', 'disablemail', '1' ],
871 ];
872
873 $defaultPreferences['ccmeonemails'] = [
874 'type' => 'toggle',
875 'section' => 'personal/email',
876 'label-message' => 'tog-ccmeonemails',
877 'disabled' => $disableEmailPrefs,
878 ];
879
880 $defaultPreferences['email-blacklist'] = [
881 'type' => 'usersmultiselect',
882 'label-message' => 'email-mutelist-label',
883 'section' => 'personal/email',
884 'disabled' => $disableEmailPrefs,
885 'filter' => MultiUsernameFilter::class,
886 'excludetemp' => true,
887 ];
888 }
889
890 if ( $this->options->get( MainConfigNames::EnotifWatchlist ) ) {
891 $defaultPreferences['enotifwatchlistpages'] = [
892 'type' => 'toggle',
893 'section' => 'personal/email',
894 'label-message' => 'tog-enotifwatchlistpages',
895 'disabled' => $disableEmailPrefs,
896 ];
897 }
898 if ( $this->options->get( MainConfigNames::EnotifUserTalk ) ) {
899 $defaultPreferences['enotifusertalkpages'] = [
900 'type' => 'toggle',
901 'section' => 'personal/email',
902 'label-message' => 'tog-enotifusertalkpages',
903 'disabled' => $disableEmailPrefs,
904 ];
905 }
906 if ( $this->options->get( MainConfigNames::EnotifUserTalk ) ||
907 $this->options->get( MainConfigNames::EnotifWatchlist ) ) {
908 if ( $this->options->get( MainConfigNames::EnotifMinorEdits ) ) {
909 $defaultPreferences['enotifminoredits'] = [
910 'type' => 'toggle',
911 'section' => 'personal/email',
912 'label-message' => 'tog-enotifminoredits',
913 'disabled' => $disableEmailPrefs,
914 ];
915 }
916
917 if ( $this->options->get( MainConfigNames::EnotifRevealEditorAddress ) ) {
918 $defaultPreferences['enotifrevealaddr'] = [
919 'type' => 'toggle',
920 'section' => 'personal/email',
921 'label-message' => 'tog-enotifrevealaddr',
922 'disabled' => $disableEmailPrefs,
923 ];
924 }
925 }
926 }
927 }
928
935 protected function skinPreferences( User $user, IContextSource $context, &$defaultPreferences ) {
936 // Skin selector, if there is at least one valid skin
937 $validSkinNames = $this->getValidSkinNames( $user, $context );
938 if ( $validSkinNames ) {
939 $defaultPreferences['skin'] = [
940 // @phan-suppress-next-line SecurityCheck-XSS False +ve, label is escaped in generateSkinOptions()
941 'type' => 'radio',
942 'options' => $this->generateSkinOptions( $user, $context, $validSkinNames ),
943 'section' => 'rendering/skin',
944 ];
945 $hideCond = [ 'AND' ];
946 foreach ( $validSkinNames as $skinName => $_ ) {
947 $options = $this->skinFactory->getSkinOptions( $skinName );
948 if ( $options['responsive'] ?? false ) {
949 $hideCond[] = [ '!==', 'skin', $skinName ];
950 }
951 }
952 if ( $hideCond === [ 'AND' ] ) {
953 $hideCond = [];
954 }
955 $defaultPreferences['skin-responsive'] = [
956 'type' => 'check',
957 'label-message' => 'prefs-skin-responsive',
958 'section' => 'rendering/skin/skin-prefs',
959 'help-message' => 'prefs-help-skin-responsive',
960 'hide-if' => $hideCond,
961 ];
962 }
963
964 $allowUserCss = $this->options->get( MainConfigNames::AllowUserCss );
965 $allowUserJs = $this->options->get( MainConfigNames::AllowUserJs );
966 $safeMode = $this->userOptionsManager->getOption( $user, 'forcesafemode' );
967 // Create links to user CSS/JS pages for all skins.
968 // This code is basically copied from generateSkinOptions().
969 // @todo Refactor this and the similar code in generateSkinOptions().
970 if ( $allowUserCss || $allowUserJs ) {
971 if ( $safeMode ) {
972 $defaultPreferences['customcssjs-safemode'] = [
973 'type' => 'info',
974 'raw' => true,
975 'rawrow' => true,
976 'section' => 'rendering/skin',
977 'default' => new FieldLayout(
978 new MessageWidget( [
979 'label' => new HtmlSnippet( $context->msg( 'prefs-custom-cssjs-safemode' )->parse() ),
980 'type' => 'warning',
981 ] )
982 ),
983 ];
984 } else {
985 $linkTools = [];
986 $userName = $user->getName();
987
988 if ( $allowUserCss ) {
989 $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' );
990 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
991 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
992 }
993
994 if ( $allowUserJs ) {
995 $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' );
996 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
997 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
998 }
999
1000 $defaultPreferences['commoncssjs'] = [
1001 'type' => 'info',
1002 'raw' => true,
1003 'default' => $context->getLanguage()->pipeList( $linkTools ),
1004 'label-message' => 'prefs-common-config',
1005 'section' => 'rendering/skin',
1006 ];
1007 }
1008 }
1009 }
1010
1015 protected function filesPreferences( IContextSource $context, &$defaultPreferences ) {
1016 $defaultPreferences['imagesize'] = [
1017 'type' => 'select',
1018 'options' => $this->getImageSizes( $context ),
1019 'label-message' => 'imagemaxsize',
1020 'section' => 'rendering/files',
1021 ];
1023 $this->options->get( MainConfigNames::ThumbLimits ),
1024 $this->userOptionsManager->getDefaultOptions()
1025 );
1026 $defaultPreferences['thumbsize'] = [
1027 'type' => 'select',
1028 'options' => $this->getThumbSizes( $context ),
1029 'label-message' => 'thumbsize',
1030 'help' => $context->msg( 'thumbsize-help' )->params( $sizes[ 1 ] ),
1031 'section' => 'rendering/files',
1032 ];
1033 $user = $context->getUser();
1034 $limits = $this->options->get( MainConfigNames::ThumbLimits );
1035 $currentSize = $limits[ $this->userOptionsManager->getOption( $user, 'thumbsize' ) ];
1036 // Correct legacy values in next save (can be removed once T376152 is resolved)
1037 if ( !in_array( $currentSize, $sizes ) ) {
1038 // using a non-standard preference so map to the closest.
1039 $closestSize = $limits[1];
1040 if ( $currentSize < $limits[0] ) {
1041 $closestSize = $limits[0];
1042 } elseif ( $currentSize > $limits[1] ) {
1043 $closestSize = $limits[2];
1044 }
1045 $defaultPreferences['thumbsize']['default'] = array_search( $closestSize, $limits );
1046 }
1047 }
1048
1055 protected function datetimePreferences(
1056 User $user, IContextSource $context, &$defaultPreferences
1057 ) {
1058 $dateOptions = $this->getDateOptions( $context );
1059 if ( $dateOptions ) {
1060 $defaultPreferences['date'] = [
1061 'type' => 'radio',
1062 'options' => $dateOptions,
1063 'section' => 'rendering/dateformat',
1064 ];
1065 }
1066
1067 // Info
1068 $now = wfTimestampNow();
1069 $lang = $context->getLanguage();
1070 $nowlocal = Html::element( 'span', [ 'id' => 'wpLocalTime' ],
1071 $lang->userTime( $now, $user ) );
1072 $nowserver = $lang->userTime( $now, $user,
1073 [ 'format' => false, 'timecorrection' => false ] ) .
1074 Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
1075
1076 $defaultPreferences['nowserver'] = [
1077 'type' => 'info',
1078 'raw' => 1,
1079 'label-message' => 'servertime',
1080 'default' => $nowserver,
1081 'section' => 'rendering/timeoffset',
1082 ];
1083
1084 $defaultPreferences['nowlocal'] = [
1085 'type' => 'info',
1086 'raw' => 1,
1087 'label-message' => 'localtime',
1088 'default' => $nowlocal,
1089 'section' => 'rendering/timeoffset',
1090 ];
1091
1092 $userTimeCorrection = (string)$this->userOptionsManager->getOption( $user, 'timecorrection' );
1093 // This value should already be normalized by UserTimeCorrection, so it should always be valid and not
1094 // in the legacy format. However, let's be sure about that and normalize it again.
1095 // Also, recompute the offset because it can change with DST.
1096 $userTimeCorrectionObj = new UserTimeCorrection(
1097 $userTimeCorrection,
1098 null,
1099 $this->options->get( MainConfigNames::LocalTZoffset )
1100 );
1101
1102 if ( $userTimeCorrectionObj->getCorrectionType() === UserTimeCorrection::OFFSET ) {
1103 $tzDefault = UserTimeCorrection::formatTimezoneOffset( $userTimeCorrectionObj->getTimeOffset() );
1104 } else {
1105 $tzDefault = $userTimeCorrectionObj->toString();
1106 }
1107
1108 $defaultPreferences['timecorrection'] = [
1109 'type' => 'timezone',
1110 'label-message' => 'timezonelegend',
1111 'default' => $tzDefault,
1112 'size' => 20,
1113 'section' => 'rendering/timeoffset',
1114 'id' => 'wpTimeCorrection',
1115 'filter' => TimezoneFilter::class,
1116 ];
1117 }
1118
1124 protected function renderingPreferences(
1125 User $user,
1126 MessageLocalizer $l10n,
1127 &$defaultPreferences
1128 ) {
1129 // Diffs
1130 $defaultPreferences['diffonly'] = [
1131 'type' => 'toggle',
1132 'section' => 'rendering/diffs',
1133 'label-message' => 'tog-diffonly',
1134 ];
1135 $defaultPreferences['norollbackdiff'] = [
1136 'type' => 'toggle',
1137 'section' => 'rendering/diffs',
1138 'label-message' => 'tog-norollbackdiff',
1139 ];
1140 $defaultPreferences['diff-type'] = [
1141 'type' => 'api',
1142 ];
1143
1144 // Page Rendering
1145 if ( $this->options->get( MainConfigNames::AllowUserCssPrefs ) ) {
1146 $defaultPreferences['underline'] = [
1147 'type' => 'select',
1148 'options' => [
1149 $l10n->msg( 'underline-never' )->text() => 0,
1150 $l10n->msg( 'underline-always' )->text() => 1,
1151 $l10n->msg( 'underline-default' )->text() => 2,
1152 ],
1153 'label-message' => 'tog-underline',
1154 'section' => 'rendering/advancedrendering',
1155 ];
1156 }
1157
1158 $defaultPreferences['showhiddencats'] = [
1159 'type' => 'toggle',
1160 'section' => 'rendering/advancedrendering',
1161 'label-message' => 'tog-showhiddencats'
1162 ];
1163
1164 if ( $user->isAllowed( 'rollback' ) ) {
1165 $defaultPreferences['showrollbackconfirmation'] = [
1166 'type' => 'toggle',
1167 'section' => 'rendering/advancedrendering',
1168 'label-message' => 'tog-showrollbackconfirmation',
1169 ];
1170 }
1171
1172 $defaultPreferences['forcesafemode'] = [
1173 'type' => 'toggle',
1174 'section' => 'rendering/advancedrendering',
1175 'label-message' => 'tog-forcesafemode',
1176 'help-message' => 'prefs-help-forcesafemode'
1177 ];
1178 }
1179
1185 protected function editingPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1186 $defaultPreferences['editsectiononrightclick'] = [
1187 'type' => 'toggle',
1188 'section' => 'editing/advancedediting',
1189 'label-message' => 'tog-editsectiononrightclick',
1190 ];
1191 $defaultPreferences['editondblclick'] = [
1192 'type' => 'toggle',
1193 'section' => 'editing/advancedediting',
1194 'label-message' => 'tog-editondblclick',
1195 ];
1196
1197 if ( $this->options->get( MainConfigNames::AllowUserCssPrefs ) ) {
1198 $defaultPreferences['editfont'] = [
1199 'type' => 'select',
1200 'section' => 'editing/editor',
1201 'label-message' => 'editfont-style',
1202 'options' => [
1203 $l10n->msg( 'editfont-monospace' )->text() => 'monospace',
1204 $l10n->msg( 'editfont-sansserif' )->text() => 'sans-serif',
1205 $l10n->msg( 'editfont-serif' )->text() => 'serif',
1206 ]
1207 ];
1208 }
1209
1210 if ( $user->isAllowed( 'minoredit' ) ) {
1211 $defaultPreferences['minordefault'] = [
1212 'type' => 'toggle',
1213 'section' => 'editing/editor',
1214 'label-message' => 'tog-minordefault',
1215 ];
1216 }
1217
1218 $defaultPreferences['forceeditsummary'] = [
1219 'type' => 'toggle',
1220 'section' => 'editing/editor',
1221 'label-message' => 'tog-forceeditsummary',
1222 ];
1223
1224 // T350653
1225 if ( $this->options->get( MainConfigNames::EnableEditRecovery ) ) {
1226 $defaultPreferences['editrecovery'] = [
1227 'type' => 'toggle',
1228 'section' => 'editing/editor',
1229 'label-message' => 'tog-editrecovery',
1230 'help-message' => [
1231 'tog-editrecovery-help',
1232 'https://meta.wikimedia.org/wiki/Talk:Community_Wishlist_Survey_2023/Edit-recovery_feature',
1233 ],
1234 ];
1235 }
1236
1237 $defaultPreferences['useeditwarning'] = [
1238 'type' => 'toggle',
1239 'section' => 'editing/editor',
1240 'label-message' => 'tog-useeditwarning',
1241 ];
1242
1243 $defaultPreferences['previewonfirst'] = [
1244 'type' => 'toggle',
1245 'section' => 'editing/preview',
1246 'label-message' => 'tog-previewonfirst',
1247 ];
1248 $defaultPreferences['previewontop'] = [
1249 'type' => 'toggle',
1250 'section' => 'editing/preview',
1251 'label-message' => 'tog-previewontop',
1252 ];
1253 $defaultPreferences['uselivepreview'] = [
1254 'type' => 'toggle',
1255 'section' => 'editing/preview',
1256 'label-message' => 'tog-uselivepreview',
1257 ];
1258 }
1259
1265 protected function rcPreferences( User $user, MessageLocalizer $l10n, &$defaultPreferences ) {
1266 $rcMaxAge = $this->options->get( MainConfigNames::RCMaxAge );
1267 $rcMax = ceil( $rcMaxAge / ( 3600 * 24 ) );
1268 $defaultPreferences['rcdays'] = [
1269 'type' => 'float',
1270 'label-message' => 'recentchangesdays',
1271 'section' => 'rc/displayrc',
1272 'min' => 1 / 24,
1273 'max' => $rcMax,
1274 'help-message' => [ 'recentchangesdays-max', Message::numParam( $rcMax ) ],
1275 ];
1276 $defaultPreferences['rclimit'] = [
1277 'type' => 'int',
1278 'min' => 1,
1279 'max' => 1000,
1280 'label-message' => 'recentchangescount',
1281 'help-message' => 'prefs-help-recentchangescount',
1282 'section' => 'rc/displayrc',
1283 'filter' => IntvalFilter::class,
1284 ];
1285 $defaultPreferences['usenewrc'] = [
1286 'type' => 'toggle',
1287 'label-message' => 'tog-usenewrc',
1288 'section' => 'rc/advancedrc',
1289 ];
1290 $defaultPreferences['hideminor'] = [
1291 'type' => 'toggle',
1292 'label-message' => 'tog-hideminor',
1293 'section' => 'rc/changesrc',
1294 ];
1295 $defaultPreferences['pst-cssjs'] = [
1296 'type' => 'api',
1297 ];
1298 $defaultPreferences['rcfilters-rc-collapsed'] = [
1299 'type' => 'api',
1300 ];
1301 $defaultPreferences['rcfilters-wl-collapsed'] = [
1302 'type' => 'api',
1303 ];
1304 $defaultPreferences['rcfilters-saved-queries'] = [
1305 'type' => 'api',
1306 ];
1307 $defaultPreferences['rcfilters-wl-saved-queries'] = [
1308 'type' => 'api',
1309 ];
1310 // Override RCFilters preferences for RecentChanges 'limit'
1311 $defaultPreferences['rcfilters-limit'] = [
1312 'type' => 'api',
1313 ];
1314 $defaultPreferences['rcfilters-saved-queries-versionbackup'] = [
1315 'type' => 'api',
1316 ];
1317 $defaultPreferences['rcfilters-wl-saved-queries-versionbackup'] = [
1318 'type' => 'api',
1319 ];
1320
1321 if ( $this->options->get( MainConfigNames::RCWatchCategoryMembership ) ) {
1322 $defaultPreferences['hidecategorization'] = [
1323 'type' => 'toggle',
1324 'label-message' => 'tog-hidecategorization',
1325 'section' => 'rc/changesrc',
1326 ];
1327 }
1328
1329 if ( $user->useRCPatrol() ) {
1330 $defaultPreferences['hidepatrolled'] = [
1331 'type' => 'toggle',
1332 'section' => 'rc/changesrc',
1333 'label-message' => 'tog-hidepatrolled',
1334 ];
1335 }
1336
1337 if ( $user->useNPPatrol() ) {
1338 $defaultPreferences['newpageshidepatrolled'] = [
1339 'type' => 'toggle',
1340 'section' => 'rc/changesrc',
1341 'label-message' => 'tog-newpageshidepatrolled',
1342 ];
1343 }
1344
1345 if ( $this->options->get( MainConfigNames::RCShowWatchingUsers ) ) {
1346 $defaultPreferences['shownumberswatching'] = [
1347 'type' => 'toggle',
1348 'section' => 'rc/advancedrc',
1349 'label-message' => 'tog-shownumberswatching',
1350 ];
1351 }
1352
1353 $defaultPreferences['rcenhancedfilters-disable'] = [
1354 'type' => 'toggle',
1355 'section' => 'rc/advancedrc',
1356 'label-message' => 'rcfilters-preference-label',
1357 'help-message' => 'rcfilters-preference-help',
1358 ];
1359 }
1360
1366 protected function watchlistPreferences(
1367 User $user, IContextSource $context, &$defaultPreferences
1368 ) {
1369 $watchlistdaysMax = ceil( $this->options->get( MainConfigNames::RCMaxAge ) / ( 3600 * 24 ) );
1370
1371 if ( $user->isAllowed( 'editmywatchlist' ) ) {
1372 $editWatchlistLinks = [];
1373 $editWatchlistModes = [
1374 'edit' => [ 'subpage' => false, 'flags' => [] ],
1375 'raw' => [ 'subpage' => 'raw', 'flags' => [] ],
1376 'clear' => [ 'subpage' => 'clear', 'flags' => [] ],
1377 ];
1378 foreach ( $editWatchlistModes as $mode => $options ) {
1379 // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear
1380 $editWatchlistLinks[] =
1381 new ButtonWidget( [
1382 'href' => SpecialPage::getTitleFor( 'EditWatchlist', $options['subpage'] )->getLinkURL(),
1383 'flags' => $options[ 'flags' ],
1384 'label' => new HtmlSnippet(
1385 $context->msg( "prefs-editwatchlist-{$mode}" )->parse()
1386 ),
1387 ] );
1388 }
1389
1390 $defaultPreferences['editwatchlist'] = [
1391 'type' => 'info',
1392 'raw' => true,
1393 // Improve button spacing when they are wrapped (T353365)
1394 'default' => (string)new HorizontalLayout( [ 'items' => $editWatchlistLinks ] ),
1395 'label-message' => 'prefs-editwatchlist-label',
1396 'section' => 'watchlist/editwatchlist',
1397 ];
1398
1399 // If watchlist labels are enabled, show WatchlistLabels button
1400 if ( $this->options->get( MainConfigNames::EnableWatchlistLabels ) ) {
1401 $watchlistLabelsLink = new ButtonWidget( [
1402 'href' => SpecialPage::getTitleFor( 'WatchlistLabels' )->getLinkURL(),
1403 'label' => new HtmlSnippet(
1404 $context->msg( 'prefs-managewatchlistlabels' )->parse()
1405 ),
1406 ] );
1407
1408 $defaultPreferences['editwatchlistlabels'] = [
1409 'type' => 'info',
1410 'raw' => true,
1411 'default' => (string)$watchlistLabelsLink,
1412 'label-message' => 'prefs-editwatchlistlabels-label',
1413 'section' => 'watchlist/editwatchlist',
1414 ];
1415 }
1416
1417 }
1418
1419 $defaultPreferences['watchlistdays'] = [
1420 'type' => 'float',
1421 'min' => 1 / 24,
1422 'max' => $watchlistdaysMax,
1423 'section' => 'watchlist/displaywatchlist',
1424 'help-message' => [ 'prefs-watchlist-days-max', Message::numParam( $watchlistdaysMax ) ],
1425 'label-message' => 'prefs-watchlist-days',
1426 ];
1427 $defaultPreferences['wllimit'] = [
1428 'type' => 'int',
1429 'min' => 1,
1430 'max' => 1000,
1431 'label-message' => 'prefs-watchlist-edits',
1432 'help-message' => 'prefs-watchlist-edits-max',
1433 'section' => 'watchlist/displaywatchlist',
1434 'filter' => IntvalFilter::class,
1435 ];
1436 $defaultPreferences['extendwatchlist'] = [
1437 'type' => 'toggle',
1438 'section' => 'watchlist/advancedwatchlist',
1439 'label-message' => 'tog-extendwatchlist',
1440 ];
1441 $defaultPreferences['watchlisthideminor'] = [
1442 'type' => 'toggle',
1443 'section' => 'watchlist/changeswatchlist',
1444 'label-message' => 'tog-watchlisthideminor',
1445 ];
1446 $defaultPreferences['watchlisthidebots'] = [
1447 'type' => 'toggle',
1448 'section' => 'watchlist/changeswatchlist',
1449 'label-message' => 'tog-watchlisthidebots',
1450 ];
1451 $defaultPreferences['watchlisthideown'] = [
1452 'type' => 'toggle',
1453 'section' => 'watchlist/changeswatchlist',
1454 'label-message' => 'tog-watchlisthideown',
1455 ];
1456 $defaultPreferences['watchlisthideanons'] = [
1457 'type' => 'toggle',
1458 'section' => 'watchlist/changeswatchlist',
1459 'label-message' => 'tog-watchlisthideanons',
1460 ];
1461 $defaultPreferences['watchlisthideliu'] = [
1462 'type' => 'toggle',
1463 'section' => 'watchlist/changeswatchlist',
1464 'label-message' => 'tog-watchlisthideliu',
1465 ];
1466
1468 $defaultPreferences['watchlistreloadautomatically'] = [
1469 'type' => 'toggle',
1470 'section' => 'watchlist/advancedwatchlist',
1471 'label-message' => 'tog-watchlistreloadautomatically',
1472 ];
1473 }
1474
1475 $defaultPreferences['watchlistunwatchlinks'] = [
1476 'type' => 'toggle',
1477 'section' => 'watchlist/advancedwatchlist',
1478 'label-message' => 'tog-watchlistunwatchlinks',
1479 ];
1480
1481 if ( $this->options->get( MainConfigNames::RCWatchCategoryMembership ) ) {
1482 $defaultPreferences['watchlisthidecategorization'] = [
1483 'type' => 'toggle',
1484 'section' => 'watchlist/changeswatchlist',
1485 'label-message' => 'tog-watchlisthidecategorization',
1486 ];
1487 }
1488
1489 if ( $user->useRCPatrol() ) {
1490 $defaultPreferences['watchlisthidepatrolled'] = [
1491 'type' => 'toggle',
1492 'section' => 'watchlist/changeswatchlist',
1493 'label-message' => 'tog-watchlisthidepatrolled',
1494 ];
1495 }
1496
1497 if ( $this->options->get( MainConfigNames::WatchlistExpiry ) ) {
1498 $defaultPreferences["watchstar-expiry"] = [
1499 'type' => 'select',
1500 'options' => WatchAction::getExpiryOptionsFromMessage( $context ),
1501 'label-message' => "tog-watchstar-expiry",
1502 'section' => 'watchlist/pageswatchlist',
1503 ];
1504 }
1505
1506 $watchTypes = [
1507 'edit' => 'watchdefault',
1508 'move' => 'watchmoves',
1509 ];
1510
1511 // Kinda hacky
1512 if ( $user->isAllowedAny( 'createpage', 'createtalk' ) ) {
1513 $watchTypes['read'] = 'watchcreations';
1514 }
1515
1516 // Move uncommon actions to end of list
1517 $watchTypes += [
1518 'rollback' => 'watchrollback',
1519 'upload' => 'watchuploads',
1520 'delete' => 'watchdeletion',
1521 ];
1522
1523 foreach ( $watchTypes as $action => $pref ) {
1524 if ( $user->isAllowed( $action ) ) {
1525 // Messages:
1526 // tog-watchdefault, tog-watchmoves, tog-watchdeletion,
1527 // tog-watchcreations, tog-watchuploads, tog-watchrollback
1528 $defaultPreferences[$pref] = [
1529 'type' => 'toggle',
1530 'section' => 'watchlist/pageswatchlist',
1531 'label-message' => "tog-$pref",
1532 ];
1533
1534 if ( in_array( $action, [ 'edit', 'read', 'rollback' ] ) &&
1535 $this->options->get( MainConfigNames::WatchlistExpiry )
1536 ) {
1537 $defaultPreferences["$pref-expiry"] = [
1538 'type' => 'select',
1539 'options' => WatchAction::getExpiryOptionsFromMessage( $context ),
1540 'label-message' => "tog-watch-expiry",
1541 'section' => 'watchlist/pageswatchlist',
1542 'hide-if' => [ '!==', $pref, '1' ],
1543 'cssclass' => 'mw-prefs-indent',
1544 ];
1545 }
1546 }
1547 }
1548
1549 $defaultPreferences['watchlisttoken'] = [
1550 'type' => 'api',
1551 ];
1552
1553 // T408235
1554 $defaultPreferences['watchlistlabelonboarding'] = [
1555 'type' => 'api',
1556 ];
1557
1558 $tokenButton = new ButtonWidget( [
1559 'href' => SpecialPage::getTitleFor( 'ResetTokens' )->getLinkURL( [
1560 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText()
1561 ] ),
1562 'label' => $context->msg( 'prefs-watchlist-managetokens' )->text(),
1563 ] );
1564 $defaultPreferences['watchlisttoken-info'] = [
1565 'type' => 'info',
1566 'section' => 'watchlist/tokenwatchlist',
1567 'label-message' => 'prefs-watchlist-token',
1568 'help-message' => 'prefs-help-tokenmanagement',
1569 'raw' => true,
1570 'default' => (string)$tokenButton,
1571 ];
1572
1573 $defaultPreferences['wlenhancedfilters-disable'] = [
1574 'type' => 'toggle',
1575 'section' => 'watchlist/advancedwatchlist',
1576 'label-message' => 'rcfilters-watchlist-preference-label',
1577 'help-message' => 'rcfilters-watchlist-preference-help',
1578 ];
1579 }
1580
1585 protected function searchPreferences( $context, &$defaultPreferences ) {
1586 $defaultPreferences['search-special-page'] = [
1587 'type' => 'api',
1588 ];
1589
1590 foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
1591 $defaultPreferences['searchNs' . $n] = [
1592 'type' => 'api',
1593 ];
1594 }
1595
1596 if ( $this->options->get( MainConfigNames::SearchMatchRedirectPreference ) ) {
1597 $defaultPreferences['search-match-redirect'] = [
1598 'type' => 'toggle',
1599 'section' => 'searchoptions/searchmisc',
1600 'label-message' => 'search-match-redirect-label',
1601 'help-message' => 'search-match-redirect-help',
1602 ];
1603 } else {
1604 $defaultPreferences['search-match-redirect'] = [
1605 'type' => 'api',
1606 ];
1607 }
1608
1609 $defaultPreferences['searchlimit'] = [
1610 'type' => 'int',
1611 'min' => 1,
1612 'max' => 500,
1613 'section' => 'searchoptions/searchmisc',
1614 'label-message' => 'searchlimit-label',
1615 'help-message' => $context->msg( 'searchlimit-help', 500 ),
1616 'filter' => IntvalFilter::class,
1617 ];
1618
1619 // show a preference for thumbnails from namespaces other than NS_FILE,
1620 // only when there they're actually configured to be served
1621 $thumbNamespaces = $this->options->get( MainConfigNames::ThumbnailNamespaces );
1622 $thumbNamespacesFormatted = array_combine(
1623 $thumbNamespaces,
1624 array_map(
1625 static function ( $namespaceId ) use ( $context ) {
1626 return $namespaceId === NS_MAIN
1627 ? $context->msg( 'blanknamespace' )->escaped()
1628 : $context->getLanguage()->getFormattedNsText( $namespaceId );
1629 },
1630 $thumbNamespaces
1631 )
1632 );
1633 $defaultThumbNamespacesFormatted =
1634 array_intersect_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] ) ?? [];
1635 $extraThumbNamespacesFormatted =
1636 array_diff_key( $thumbNamespacesFormatted, [ NS_FILE => 1 ] );
1637 if ( $extraThumbNamespacesFormatted ) {
1638 $defaultPreferences['search-thumbnail-extra-namespaces'] = [
1639 'type' => 'toggle',
1640 'section' => 'searchoptions/searchmisc',
1641 'label-message' => 'search-thumbnail-extra-namespaces-label',
1642 'help-message' => $context->msg(
1643 'search-thumbnail-extra-namespaces-message',
1644 Message::listParam( $extraThumbNamespacesFormatted ),
1645 count( $extraThumbNamespacesFormatted ),
1646 Message::listParam( $defaultThumbNamespacesFormatted ),
1647 count( $defaultThumbNamespacesFormatted )
1648 ),
1649 ];
1650 }
1651 }
1652
1662 private static function sortSkinNames( $a, $b, $currentSkin, $preferredSkins ) {
1663 // Display the current skin first in the list
1664 if ( strcasecmp( $a, $currentSkin ) === 0 ) {
1665 return -1;
1666 }
1667 if ( strcasecmp( $b, $currentSkin ) === 0 ) {
1668 return 1;
1669 }
1670 // Display preferred skins over other skins
1671 if ( count( $preferredSkins ) ) {
1672 $aPreferred = array_search( $a, $preferredSkins );
1673 $bPreferred = array_search( $b, $preferredSkins );
1674 // Cannot use ! operator because array_search returns the
1675 // index of the array item if found (i.e. 0) and false otherwise
1676 if ( $aPreferred !== false && $bPreferred === false ) {
1677 return -1;
1678 }
1679 if ( $aPreferred === false && $bPreferred !== false ) {
1680 return 1;
1681 }
1682 // When both skins are preferred, default to the ordering
1683 // specified by the preferred skins config array
1684 if ( $aPreferred !== false && $bPreferred !== false ) {
1685 return strcasecmp( $aPreferred, $bPreferred );
1686 }
1687 }
1688 // Use normal string comparison if both strings are not preferred
1689 return strcasecmp( $a, $b );
1690 }
1691
1700 private function getValidSkinNames( User $user, IContextSource $context ) {
1701 // Only show skins that aren't disabled
1702 $validSkinNames = $this->skinFactory->getAllowedSkins();
1703 $allInstalledSkins = $this->skinFactory->getInstalledSkins();
1704
1705 // Display the installed skin the user has specifically requested via useskin=….
1706 $useSkin = $context->getRequest()->getRawVal( 'useskin' );
1707 if ( $useSkin !== null && isset( $allInstalledSkins[$useSkin] )
1708 && $context->msg( "skinname-$useSkin" )->exists()
1709 ) {
1710 $validSkinNames[$useSkin] = $useSkin;
1711 }
1712
1713 // Display the skin if the user has set it as a preference already before it was hidden.
1714 $currentUserSkin = $this->userOptionsManager->getOption( $user, 'skin' );
1715 if ( isset( $allInstalledSkins[$currentUserSkin] )
1716 && $context->msg( "skinname-$currentUserSkin" )->exists()
1717 ) {
1718 $validSkinNames[$currentUserSkin] = $currentUserSkin;
1719 }
1720
1721 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1722 $msg = $context->msg( "skinname-{$skinkey}" );
1723 if ( $msg->exists() ) {
1724 $skinname = htmlspecialchars( $msg->text() );
1725 }
1726 }
1727
1728 $preferredSkins = $this->options->get( MainConfigNames::SkinsPreferred );
1729 // Sort by the internal name, so that the ordering is the same for each display language,
1730 // especially if some skin names are translated to use a different alphabet and some are not.
1731 uksort( $validSkinNames, function ( $a, $b ) use ( $currentUserSkin, $preferredSkins ) {
1732 return $this->sortSkinNames( $a, $b, $currentUserSkin, $preferredSkins );
1733 } );
1734
1735 return $validSkinNames;
1736 }
1737
1746 private function renderUserGroupList(
1747 UserIdentity $user,
1748 array $userGroupsToShow,
1749 IContextSource $context
1750 ): string {
1751 $userGroupMemberships = $this->userGroupManager->getUserGroupMemberships( $user );
1752 $userGroups = $userMembers = $userTempGroups = $userTempMembers = [];
1753 foreach ( $userGroupsToShow as $ug ) {
1754 $groupStringOrObject = $userGroupMemberships[$ug] ?? $ug;
1755
1756 $userG = UserGroupMembership::getLinkHTML( $groupStringOrObject, $context );
1757 $userM = UserGroupMembership::getLinkHTML( $groupStringOrObject, $context, $user->getName() );
1758
1759 // Store expiring groups separately, so we can place them before non-expiring
1760 // groups in the list. This is to avoid the ambiguity of something like
1761 // "administrator, bureaucrat (until X date)" -- users might wonder whether the
1762 // expiry date applies to both groups, or just the last one
1763 if ( $groupStringOrObject instanceof UserGroupMembership &&
1764 $groupStringOrObject->getExpiry()
1765 ) {
1766 $userTempGroups[] = $userG;
1767 $userTempMembers[] = $userM;
1768 } else {
1769 $userGroups[] = $userG;
1770 $userMembers[] = $userM;
1771 }
1772 }
1773 sort( $userGroups );
1774 sort( $userMembers );
1775 sort( $userTempGroups );
1776 sort( $userTempMembers );
1777 $userGroups = array_merge( $userTempGroups, $userGroups );
1778 $userMembers = array_merge( $userTempMembers, $userMembers );
1779 $lang = $context->getLanguage();
1780 return $context->msg( 'prefs-memberingroups-type' )
1781 ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) )
1782 ->escaped();
1783 }
1784
1791 protected function generateSkinOptions( User $user, IContextSource $context, array $validSkinNames ) {
1792 $ret = [];
1793
1794 $mptitle = Title::newMainPage();
1795 $previewtext = $context->msg( 'skin-preview' )->escaped();
1796 $defaultSkin = $this->options->get( MainConfigNames::DefaultSkin );
1797 $allowUserCss = $this->options->get( MainConfigNames::AllowUserCss );
1798 $allowUserJs = $this->options->get( MainConfigNames::AllowUserJs );
1799 $safeMode = $this->userOptionsManager->getOption( $user, 'forcesafemode' );
1800 $foundDefault = false;
1801 foreach ( $validSkinNames as $skinkey => $sn ) {
1802 $linkTools = [];
1803
1804 // Mark the default skin
1805 if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) {
1806 $linkTools[] = $context->msg( 'default' )->escaped();
1807 $foundDefault = true;
1808 }
1809
1810 // Create talk page link if relevant message exists.
1811 $talkPageMsg = $context->msg( "$skinkey-prefs-talkpage" );
1812 if ( $talkPageMsg->exists() ) {
1813 $linkTools[] = $talkPageMsg->parse();
1814 }
1815
1816 // Create preview link
1817 $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) );
1818 $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
1819
1820 if ( !$safeMode ) {
1821 // Create links to user CSS/JS pages
1822 // @todo Refactor this and the similar code in skinPreferences().
1823 if ( $allowUserCss ) {
1824 $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1825 $cssLinkText = $context->msg( 'prefs-custom-css' )->text();
1826 $linkTools[] = $this->linkRenderer->makeLink( $cssPage, $cssLinkText );
1827 }
1828
1829 if ( $allowUserJs ) {
1830 $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1831 $jsLinkText = $context->msg( 'prefs-custom-js' )->text();
1832 $linkTools[] = $this->linkRenderer->makeLink( $jsPage, $jsLinkText );
1833 }
1834 }
1835
1836 $display = $sn . ' ' . $context->msg( 'parentheses' )
1837 ->rawParams( $context->getLanguage()->pipeList( $linkTools ) )
1838 ->escaped();
1839 $ret[$display] = $skinkey;
1840 }
1841
1842 if ( !$foundDefault ) {
1843 // If the default skin is not available, things are going to break horribly because the
1844 // default value for skin selector will not be a valid value. Let's just not show it then.
1845 return [];
1846 }
1847
1848 return $ret;
1849 }
1850
1855 protected function getDateOptions( IContextSource $context ) {
1856 $lang = $context->getLanguage();
1857 $dateopts = $lang->getDatePreferences();
1858
1859 $ret = [];
1860
1861 if ( $dateopts ) {
1862 if ( !in_array( 'default', $dateopts ) ) {
1863 $dateopts[] = 'default'; // Make sure default is always valid T21237
1864 }
1865
1866 // FIXME KLUGE: site default might not be valid for user language
1867 global $wgDefaultUserOptions;
1868 if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) {
1869 $wgDefaultUserOptions['date'] = 'default';
1870 }
1871
1872 $epoch = wfTimestampNow();
1873 foreach ( $dateopts as $key ) {
1874 if ( $key == 'default' ) {
1875 $formatted = $context->msg( 'datedefault' )->escaped();
1876 } else {
1877 $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
1878 }
1879 $ret[$formatted] = $key;
1880 }
1881 }
1882 return $ret;
1883 }
1884
1889 protected function getImageSizes( MessageLocalizer $l10n ) {
1890 $ret = [];
1891 $pixels = $l10n->msg( 'unit-pixel' )->text();
1892
1893 foreach ( $this->options->get( MainConfigNames::ImageLimits ) as $index => $limits ) {
1894 // Note: A left-to-right marker (U+200E) is inserted, see T144386
1895 $display = "{$limits[0]}\u{200E}×{$limits[1]}$pixels";
1896 $ret[$display] = $index;
1897 }
1898
1899 return $ret;
1900 }
1901
1917 public static function getNormalizedThumbSizes( array $limits, array $defaultUserOptions ) {
1918 $smallSize = max( 180, min( $limits ) );
1919 $defaultIndex = $defaultUserOptions[ 'thumbsize' ] ?? 0;
1920 $defaultSize = $limits[ $defaultIndex ];
1921 $largeSize = max( $limits );
1922 return [ $smallSize, $defaultSize, $largeSize ];
1923 }
1924
1929 protected function getThumbSizes( MessageLocalizer $l10n ) {
1930 $config = $this->options;
1931 $limits = $config->get( MainConfigNames::ThumbLimits );
1932 $sizes = self::getNormalizedThumbSizes( $limits, $this->userOptionsManager->getDefaultOptions() );
1933
1934 return [
1935 $l10n->msg( 'thumbsize-small' )->text() => array_search( $sizes[ 0 ], $limits ),
1936 $l10n->msg( 'thumbsize-regular' )->text() => array_search( $sizes[ 1 ], $limits ),
1937 $l10n->msg( 'thumbsize-large' )->text() => array_search( $sizes[ 2 ], $limits ),
1938 ];
1939 }
1940
1947 protected function validateSignature( $signature, $alldata, HTMLForm $form ) {
1948 $sigValidation = $this->options->get( MainConfigNames::SignatureValidation );
1949 $maxSigChars = $this->options->get( MainConfigNames::MaxSigChars );
1950 if ( is_string( $signature ) && mb_strlen( $signature ) > $maxSigChars ) {
1951 return $form->msg( 'badsiglength' )->numParams( $maxSigChars )->escaped();
1952 }
1953
1954 if ( $signature === null || $signature === '' ) {
1955 // Make sure leaving the field empty is valid, since that's used as the default (T288151).
1956 // Code using this preference in Parser::getUserSig() handles this case specially.
1957 return true;
1958 }
1959
1960 // Remaining checks only apply to fancy signatures
1961 if ( !( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) ) {
1962 return true;
1963 }
1964
1965 // HERE BE DRAGONS:
1966 //
1967 // If this value is already saved as the user's signature, treat it as valid, even if it
1968 // would be invalid to save now, and even if $wgSignatureValidation is set to 'disallow'.
1969 //
1970 // It can become invalid when we introduce new validation, or when the value just transcludes
1971 // some page containing the real signature and that page is edited (which we can't validate),
1972 // or when someone's username is changed.
1973 //
1974 // Otherwise it would be completely removed when the user opens their preferences page, which
1975 // would be very unfriendly.
1976 $user = $form->getUser();
1977 if (
1978 $signature === $this->userOptionsManager->getOption( $user, 'nickname' ) &&
1979 (bool)$alldata['fancysig'] === $this->userOptionsManager->getBoolOption( $user, 'fancysig' )
1980 ) {
1981 return true;
1982 }
1983
1984 if ( $sigValidation === 'new' || $sigValidation === 'disallow' ) {
1985 // Validate everything
1986 $parserOpts = ParserOptions::newFromContext( $form->getContext() );
1987 $validator = $this->signatureValidatorFactory
1988 ->newSignatureValidator( $user, $form->getContext(), $parserOpts );
1989 $errors = $validator->validateSignature( $signature );
1990 if ( $errors ) {
1991 return $errors;
1992 }
1993 }
1994
1995 // Quick check for mismatched HTML tags in the input.
1996 // Note that this is easily fooled by wikitext templates or bold/italic markup.
1997 // We're only keeping this until Parsoid is integrated and guaranteed to be available.
1998 if ( $this->parserFactory->getInstance()->validateSig( $signature ) === false ) {
1999 return $form->msg( 'badsig' )->escaped();
2000 }
2001
2002 return true;
2003 }
2004
2011 protected function cleanSignature( $signature, $alldata, HTMLForm $form ) {
2012 if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
2013 $signature = $this->parserFactory->getInstance()->cleanSig( $signature );
2014 } else {
2015 // When no fancy sig used, make sure ~{3,5} get removed.
2016 $signature = Parser::cleanSigInSig( $signature );
2017 }
2018
2019 return $signature;
2020 }
2021
2029 public function getForm(
2030 User $user,
2031 IContextSource $context,
2032 $formClass = PreferencesFormOOUI::class,
2033 array $remove = []
2034 ) {
2035 // We use ButtonWidgets in some of the getPreferences() functions
2036 $context->getOutput()->enableOOUI();
2037
2038 // Note that the $user parameter of getFormDescriptor() is deprecated.
2039 $formDescriptor = $this->getFormDescriptor( $user, $context );
2040 if ( count( $remove ) ) {
2041 $removeKeys = array_fill_keys( $remove, true );
2042 $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
2043 }
2044
2045 // Remove type=api preferences. They are not intended for rendering in the form.
2046 foreach ( $formDescriptor as $name => $info ) {
2047 if ( isset( $info['type'] ) && $info['type'] === 'api' ) {
2048 unset( $formDescriptor[$name] );
2049 }
2050 }
2051
2055 $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
2056
2057 // This allows users to opt-in to hidden skins. While this should be discouraged and is not
2058 // discoverable, this allows users to still use hidden skins while preventing new users from
2059 // adopting unsupported skins. If no useskin=… parameter was provided, it will not show up
2060 // in the resulting URL.
2061 $htmlForm->setAction( $context->getTitle()->getLocalURL( [
2062 'useskin' => $context->getRequest()->getRawVal( 'useskin' )
2063 ] ) );
2064
2065 $htmlForm->setModifiedUser( $user );
2066 $htmlForm->setOptionsEditable( $user->isAllowed( 'editmyoptions' ) );
2067 $htmlForm->setPrivateInfoEditable( $user->isAllowed( 'editmyprivateinfo' ) );
2068 $htmlForm->setId( 'mw-prefs-form' );
2069 $htmlForm->setAutocomplete( 'off' );
2070 $htmlForm->setSubmitTextMsg( 'saveprefs' );
2071 // Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
2072 $htmlForm->setSubmitTooltip( 'preferences-save' );
2073 $htmlForm->setSubmitID( 'prefcontrol' );
2074 $htmlForm->setSubmitCallback(
2075 function ( array $formData, PreferencesFormOOUI $form ) use ( $formDescriptor ) {
2076 return $this->submitForm( $formData, $form, $formDescriptor );
2077 }
2078 );
2079
2080 return $htmlForm;
2081 }
2082
2091 protected function saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor ) {
2092 $user = $form->getModifiedUser();
2093 $hiddenPrefs = $this->options->get( MainConfigNames::HiddenPrefs );
2094 $result = true;
2095
2096 if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) {
2097 return Status::newFatal( 'mypreferencesprotected' );
2098 }
2099
2100 // Filter input
2101 $this->applyFilters( $formData, $formDescriptor, 'filterFromForm' );
2102
2103 // Fortunately, the realname field is MUCH simpler
2104 // (not really "private", but still shouldn't be edited without permission)
2105
2106 if ( !in_array( 'realname', $hiddenPrefs )
2107 && $user->isAllowed( 'editmyprivateinfo' )
2108 && array_key_exists( 'realname', $formData )
2109 ) {
2110 $realName = $formData['realname'];
2111 $user->setRealName( $realName );
2112 }
2113
2114 if ( $user->isAllowed( 'editmyoptions' ) ) {
2115 $oldUserOptions = $this->userOptionsManager->getOptions( $user );
2116
2117 foreach ( $this->getSaveBlacklist() as $b ) {
2118 unset( $formData[$b] );
2119 }
2120
2121 // If users have saved a value for a preference which has subsequently been disabled
2122 // via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
2123 // is subsequently re-enabled
2124 foreach ( $hiddenPrefs as $pref ) {
2125 // If the user has not set a non-default value here, the default will be returned
2126 // and subsequently discarded
2127 $formData[$pref] = $this->userOptionsManager->getOption( $user, $pref, null, true );
2128 }
2129
2130 // If the user changed the rclimit preference, also change the rcfilters-rclimit preference
2131 if (
2132 isset( $formData['rclimit'] ) &&
2133 intval( $formData[ 'rclimit' ] ) !== $this->userOptionsManager->getIntOption( $user, 'rclimit' )
2134 ) {
2135 $formData['rcfilters-limit'] = $formData['rclimit'];
2136 }
2137
2138 // Keep old preferences from interfering due to back-compat code, etc.
2139 $optionsToReset = $this->getOptionNamesForReset( $user, $form->getContext(), 'unused' );
2140 $this->userOptionsManager->resetOptionsByName( $user, $optionsToReset );
2141
2142 foreach ( $formData as $key => $value ) {
2143 // If we're creating a new local override, we need to explicitly pass
2144 // GLOBAL_OVERRIDE to setOption(), otherwise the update would be ignored
2145 // due to the conflicting global option.
2146 $except = !empty( $formData[$key . UserOptionsLookup::LOCAL_EXCEPTION_SUFFIX] );
2147 $this->userOptionsManager->setOption( $user, $key, $value,
2148 $except ? UserOptionsManager::GLOBAL_OVERRIDE : UserOptionsManager::GLOBAL_IGNORE );
2149 }
2150
2151 $this->hookRunner->onPreferencesFormPreSave(
2152 $formData, $form, $user, $result, $oldUserOptions );
2153 }
2154
2155 $user->saveSettings();
2156
2157 return $result;
2158 }
2159
2168 protected function applyFilters( array &$preferences, array $formDescriptor, $verb ) {
2169 foreach ( $formDescriptor as $preference => $desc ) {
2170 if ( !isset( $desc['filter'] ) || !isset( $preferences[$preference] ) ) {
2171 continue;
2172 }
2173 $filterDesc = $desc['filter'];
2174 if ( $filterDesc instanceof Filter ) {
2175 $filter = $filterDesc;
2176 } elseif ( class_exists( $filterDesc ) ) {
2177 $filter = new $filterDesc();
2178 } elseif ( is_callable( $filterDesc ) ) {
2179 $filter = $filterDesc();
2180 } else {
2181 throw new UnexpectedValueException(
2182 "Unrecognized filter type for preference '$preference'"
2183 );
2184 }
2185 $preferences[$preference] = $filter->$verb( $preferences[$preference] );
2186 }
2187 }
2188
2197 protected function submitForm(
2198 array $formData,
2199 PreferencesFormOOUI $form,
2200 array $formDescriptor
2201 ) {
2202 $res = $this->saveFormData( $formData, $form, $formDescriptor );
2203
2204 if ( $res === true ) {
2205 $context = $form->getContext();
2206 $urlOptions = [];
2207
2208 $urlOptions += $form->getExtraSuccessRedirectParameters();
2209
2210 $url = $form->getTitle()->createFragmentTarget(
2211 $form->getRequest()->getVal( 'returntoanchor' ) ?? ''
2212 )->getFullURL( $urlOptions );
2213
2214 // Set session data for the success message
2215 $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 );
2216
2217 $context->getOutput()->redirect( $url );
2218 }
2219
2220 return ( $res === true ? Status::newGood() : $res );
2221 }
2222
2229 public function getResetKinds(
2230 User $user, IContextSource $context, $options = null
2231 ): array {
2232 $options ??= $this->userOptionsManager->loadUserOptions( $user );
2233
2234 $prefs = $this->getFormDescriptor( $user, $context );
2235 $mapping = [];
2236
2237 // Pull out the "special" options, so they don't get converted as
2238 // multiselect or checkmatrix.
2239 $specialOptions = array_fill_keys( $this->getSaveBlacklist(), true );
2240 foreach ( $specialOptions as $name => $value ) {
2241 unset( $prefs[$name] );
2242 }
2243
2244 // Multiselect and checkmatrix options are stored in the database with
2245 // one key per option, each having a boolean value. Extract those keys.
2246 $multiselectOptions = [];
2247 foreach ( $prefs as $name => $info ) {
2248 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
2249 // Checking old alias for compatibility with unchanged extensions
2250 ( isset( $info['class'] ) && $info['class'] === \HTMLMultiSelectField::class ) ||
2251 ( isset( $info['class'] ) && $info['class'] === HTMLMultiSelectField::class )
2252 ) {
2253 $opts = HTMLFormField::flattenOptions( $info['options'] ?? $info['options-messages'] );
2254 $prefix = $info['prefix'] ?? $name;
2255
2256 foreach ( $opts as $value ) {
2257 $multiselectOptions["$prefix$value"] = true;
2258 }
2259
2260 unset( $prefs[$name] );
2261 }
2262 }
2263 $checkmatrixOptions = [];
2264 foreach ( $prefs as $name => $info ) {
2265 if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) ||
2266 // Checking old alias for compatibility with unchanged extensions
2267 ( isset( $info['class'] ) && $info['class'] === \HTMLCheckMatrix::class ) ||
2268 ( isset( $info['class'] ) && $info['class'] === HTMLCheckMatrix::class )
2269 ) {
2270 $columns = HTMLFormField::flattenOptions( $info['columns'] );
2271 $rows = HTMLFormField::flattenOptions( $info['rows'] );
2272 $prefix = $info['prefix'] ?? $name;
2273
2274 foreach ( $columns as $column ) {
2275 foreach ( $rows as $row ) {
2276 $checkmatrixOptions["$prefix$column-$row"] = true;
2277 }
2278 }
2279
2280 unset( $prefs[$name] );
2281 }
2282 }
2283
2284 // $value is ignored
2285 foreach ( $options as $key => $value ) {
2286 if ( isset( $prefs[$key] ) ) {
2287 $mapping[$key] = 'registered';
2288 } elseif ( isset( $multiselectOptions[$key] ) ) {
2289 $mapping[$key] = 'registered-multiselect';
2290 } elseif ( isset( $checkmatrixOptions[$key] ) ) {
2291 $mapping[$key] = 'registered-checkmatrix';
2292 } elseif ( isset( $specialOptions[$key] ) ) {
2293 $mapping[$key] = 'special';
2294 } elseif ( str_starts_with( $key, 'userjs-' ) ) {
2295 $mapping[$key] = 'userjs';
2296 } elseif ( str_ends_with( $key, UserOptionsLookup::LOCAL_EXCEPTION_SUFFIX ) ) {
2297 $mapping[$key] = 'local-exception';
2298 } else {
2299 $mapping[$key] = 'unused';
2300 }
2301 }
2302
2303 return $mapping;
2304 }
2305
2306 public function listResetKinds(): array {
2307 return [
2308 'registered',
2309 'registered-multiselect',
2310 'registered-checkmatrix',
2311 'special',
2312 'userjs',
2313 'local-exception',
2314 'unused'
2315 ];
2316 }
2317
2324 public function getOptionNamesForReset( User $user, IContextSource $context, $kinds ) {
2325 $oldOptions = $this->userOptionsManager->loadUserOptions( $user, IDBAccessObject::READ_LATEST );
2326
2327 if ( !is_array( $kinds ) ) {
2328 $kinds = [ $kinds ];
2329 }
2330
2331 if ( in_array( 'all', $kinds ) ) {
2332 return array_keys( $oldOptions );
2333 } else {
2334 $optionKinds = $this->getResetKinds( $user, $context );
2335 $kinds = array_intersect( $kinds, $this->listResetKinds() );
2336 $optionNames = [];
2337
2338 foreach ( $oldOptions as $key => $value ) {
2339 if ( in_array( $optionKinds[$key], $kinds ) ) {
2340 $optionNames[] = $key;
2341 }
2342 }
2343 return $optionNames;
2344 }
2345 }
2346}
const NS_USER
Definition Defines.php:53
const NS_FILE
Definition Defines.php:57
const NS_MAIN
Definition Defines.php:51
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Page addition to a user's watchlist.
AuthManager is the authentication system in MediaWiki and serves entry point for authentication.
This is a value object for authentication requests with a username and password.
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
A checkbox matrix Operates similarly to HTMLMultiSelectField, but instead of using an array of option...
An information field (text blob), not a proper input.
The parent class to generate form fields.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:214
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
An interface for creating language converters.
Base class for multi-variant language conversion.
A service that provides utilities to do with language names and codes.
Base class for language-specific code.
Definition Language.php:65
Class that generates HTML for internal links.
A class containing constants representing the names of configuration variables.
const HiddenPrefs
Name constant for the HiddenPrefs setting, for use with Config::get()
const ForceHTTPS
Name constant for the ForceHTTPS setting, for use with Config::get()
const EnotifWatchlist
Name constant for the EnotifWatchlist setting, for use with Config::get()
const MaxSigChars
Name constant for the MaxSigChars setting, for use with Config::get()
const RCMaxAge
Name constant for the RCMaxAge setting, for use with Config::get()
const DefaultSkin
Name constant for the DefaultSkin setting, for use with Config::get()
const EnotifRevealEditorAddress
Name constant for the EnotifRevealEditorAddress setting, for use with Config::get()
const EnableUserEmail
Name constant for the EnableUserEmail setting, for use with Config::get()
const SkinsPreferred
Name constant for the SkinsPreferred setting, for use with Config::get()
const EnableWatchlistLabels
Name constant for the EnableWatchlistLabels setting, for use with Config::get()
const EnableEditRecovery
Name constant for the EnableEditRecovery setting, for use with Config::get()
const EmailConfirmToEdit
Name constant for the EmailConfirmToEdit setting, for use with Config::get()
const EnableEmail
Name constant for the EnableEmail setting, for use with Config::get()
const LocalTZoffset
Name constant for the LocalTZoffset setting, for use with Config::get()
const RCShowWatchingUsers
Name constant for the RCShowWatchingUsers setting, for use with Config::get()
const WatchlistExpiry
Name constant for the WatchlistExpiry setting, for use with Config::get()
const EnotifUserTalk
Name constant for the EnotifUserTalk setting, for use with Config::get()
const AllowUserJs
Name constant for the AllowUserJs setting, for use with Config::get()
const ImageLimits
Name constant for the ImageLimits setting, for use with Config::get()
const SearchMatchRedirectPreference
Name constant for the SearchMatchRedirectPreference setting, for use with Config::get()
const EnotifMinorEdits
Name constant for the EnotifMinorEdits setting, for use with Config::get()
const ScriptPath
Name constant for the ScriptPath setting, for use with Config::get()
const AllowUserCss
Name constant for the AllowUserCss setting, for use with Config::get()
const ThumbLimits
Name constant for the ThumbLimits setting, for use with Config::get()
const SecureLogin
Name constant for the SecureLogin setting, for use with Config::get()
const LanguageCode
Name constant for the LanguageCode setting, for use with Config::get()
const SignatureValidation
Name constant for the SignatureValidation setting, for use with Config::get()
const AllowUserCssPrefs
Name constant for the AllowUserCssPrefs setting, for use with Config::get()
const RCWatchCategoryMembership
Name constant for the RCWatchCategoryMembership setting, for use with Config::get()
const ThumbnailNamespaces
Name constant for the ThumbnailNamespaces setting, for use with Config::get()
const EmailAuthentication
Name constant for the EmailAuthentication setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
static listParam(array $list, $type=ListType::AND)
Definition Message.php:1355
This is one of the Core classes and should be read at least once by any new developers.
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:139
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
This is the default implementation of PreferencesFactory.
validateSignature( $signature, $alldata, HTMLForm $form)
rcPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
watchlistPreferences(User $user, IContextSource $context, &$defaultPreferences)
listResetKinds()
Return a list of the types of user options currently returned by getResetKinds().
profilePreferences(User $user, IContextSource $context, &$defaultPreferences)
renderingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
getForm(User $user, IContextSource $context, $formClass=PreferencesFormOOUI::class, array $remove=[])
getOptionNamesForReset(User $user, IContextSource $context, $kinds)
skinPreferences(User $user, IContextSource $context, &$defaultPreferences)
static getNormalizedThumbSizes(array $limits, array $defaultUserOptions)
Normalizes thumbnail options (which can support more than 3 values) to min, default and max values fo...
__construct(ServiceOptions $options, Language $contLang, AuthManager $authManager, LinkRenderer $linkRenderer, NamespaceInfo $nsInfo, PermissionManager $permissionManager, ILanguageConverter $languageConverter, LanguageNameUtils $languageNameUtils, HookContainer $hookContainer, UserOptionsLookup $userOptionsLookup, ?LanguageConverterFactory $languageConverterFactory=null, ?ParserFactory $parserFactory=null, ?SkinFactory $skinFactory=null, ?UserGroupManager $userGroupManager=null, ?SignatureValidatorFactory $signatureValidatorFactory=null)
generateSkinOptions(User $user, IContextSource $context, array $validSkinNames)
static simplifyFormDescriptor(array $descriptor)
Simplify form descriptor for validation or something similar.
getResetKinds(User $user, IContextSource $context, $options=null)
editingPreferences(User $user, MessageLocalizer $l10n, &$defaultPreferences)
getOptionFromUser( $name, $info, array $userOptions)
Pull option from a user account.
datetimePreferences(User $user, IContextSource $context, &$defaultPreferences)
cleanSignature( $signature, $alldata, HTMLForm $form)
getSaveBlacklist()
Get the names of preferences that should never be saved (such as 'realname' and 'emailaddress')....
applyFilters(array &$preferences, array $formDescriptor, $verb)
Applies filters to preferences either before or after form usage.
static getPreferenceForField( $name, HTMLFormField $field, array $userOptions)
Get preference values for the 'default' param of html form descriptor, compatible with nested fields.
filesPreferences(IContextSource $context, &$defaultPreferences)
submitForm(array $formData, PreferencesFormOOUI $form, array $formDescriptor)
Save the form data and reload the page.
saveFormData( $formData, PreferencesFormOOUI $form, array $formDescriptor)
Handle the form submission if everything validated properly.
Factory class to create Skin objects.
Parent class for all special pages.
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,...
getExtraSuccessRedirectParameters()
Get extra parameters for the query string when redirecting after successful save.
List all defined user groups and the associated rights.
A special page that lists last changes made to the wiki, limited to user-defined list of titles.
static checkStructuredFilterUiEnabled(UserIdentity $user)
Static method to check whether StructuredFilter UI is enabled for the given user.1....
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:69
Provides access to user options.
A service class to control user options.
Manage user group memberships.
Represents the membership of one user in one user group.
Utility class to parse the TimeCorrection string value.
User class for the MediaWiki software.
Definition User.php:130
isAllowed(string $permission, ?PermissionStatus $status=null)
Checks whether this authority has the given permission in general.
Definition User.php:2150
getRegistration()
Get the timestamp of account creation.
Definition User.php:3099
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition User.php:2158
isAllowedAny(... $permissions)
Checks whether this authority has any of the given permissions in general.Implementations must ensure...
Definition User.php:2141
getEditCount()
Get the user's edit count.
Definition User.php:2077
getRealName()
Get the user's real name.
Definition User.php:1958
getTitleKey()
Get the user's name escaped by underscores.
Definition User.php:1617
getEmailAuthenticationTimestamp()
Get the timestamp of the user's e-mail authentication.
Definition User.php:1875
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition User.php:2168
getEmail()
Get the user's e-mail address.
Definition User.php:1862
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1525
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'DefaultLockManager'=> null, 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'RestrictUserPageEditing' => false, 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'RestrictUserPageEditing' => 'boolean', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'mode' => [ 'type' => 'string', ], ], 'required' => [ 'mode', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
$wgDefaultUserOptions
Config variable stub for the DefaultUserOptions setting, for use by phpdoc and IDEs.
Interface for objects which can provide a MediaWiki context on request.
The shared interface for all language converters.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
Base interface for user preference filters that work as a middleware between storage and interface.
Definition Filter.php:13
A PreferencesFactory is a MediaWiki service that provides the definitions of preferences for a given ...
Interface for objects representing user identity.
Interface for database access objects.
element(SerializerNode $parent, SerializerNode $node, $contents)