MediaWiki master
ResourceLoader.php
Go to the documentation of this file.
1<?php
10
11use Exception;
12use InvalidArgumentException;
13use Less_Environment;
14use Less_Parser;
15use LogicException;
33use Psr\Log\LoggerAwareInterface;
34use Psr\Log\LoggerInterface;
35use Psr\Log\NullLogger;
36use RuntimeException;
37use stdClass;
38use Throwable;
39use UnexpectedValueException;
42use Wikimedia\Minify\CSSMin;
43use Wikimedia\Minify\IdentityMinifierState;
44use Wikimedia\Minify\IndexMap;
45use Wikimedia\Minify\IndexMapOffset;
46use Wikimedia\Minify\JavaScriptMapperState;
47use Wikimedia\Minify\JavaScriptMinifier;
48use Wikimedia\Minify\JavaScriptMinifierState;
49use Wikimedia\Minify\MinifierState;
52use Wikimedia\RequestTimeout\TimeoutException;
53use Wikimedia\ScopedCallback;
55use Wikimedia\Timestamp\ConvertibleTimestamp;
56use Wikimedia\Timestamp\TimestampFormat as TS;
57use Wikimedia\WrappedString;
58
79class ResourceLoader implements LoggerAwareInterface {
81 public const CACHE_VERSION = 9;
82
84 private const MAXAGE_RECOVER = 60;
85
87 protected static $debugMode = null;
88
90 private $config;
92 private $blobStore;
94 private $depStore;
96 private $logger;
98 private $hookContainer;
100 private $srvCache;
102 private $statsFactory;
104 private $maxageVersioned;
106 private $maxageUnversioned;
107
109 private $modules = [];
111 private $moduleInfos = [];
113 private $testModuleNames = [];
115 private $sources = [];
117 protected $errors = [];
122 protected $extraHeaders = [];
127 private $moduleSkinStyles = [];
128
149 public function __construct(
150 Config $config,
151 ?LoggerInterface $logger = null,
152 ?DependencyStore $tracker = null,
153 array $params = []
154 ) {
155 $this->maxageVersioned = $params['maxageVersioned'] ?? 30 * 24 * 60 * 60;
156 $this->maxageUnversioned = $params['maxageUnversioned'] ?? 5 * 60;
157
158 $this->config = $config;
159 $this->logger = $logger ?? new NullLogger();
160
161 $services = MediaWikiServices::getInstance();
162 $this->hookContainer = $services->getHookContainer();
163
164 $this->srvCache = $services->getLocalServerObjectCache();
165 $this->statsFactory = $services->getStatsFactory();
166
167 // Add 'local' source first
168 $this->addSource( 'local', $params['loadScript'] ?? '/load.php' );
169
170 // Special module that always exists
171 $this->register( 'startup', [ 'class' => StartUpModule::class ] );
172
173 $this->setMessageBlobStore(
174 new MessageBlobStore( $this, $this->logger, $services->getMainWANObjectCache() )
175 );
176
177 $this->setDependencyStore( $tracker ?? new DependencyStore( new HashBagOStuff() ) );
178 }
179
183 public function getConfig() {
184 return $this->config;
185 }
186
191 public function setLogger( LoggerInterface $logger ): void {
192 $this->logger = $logger;
193 }
194
199 public function getLogger(): LoggerInterface {
200 return $this->logger;
201 }
202
207 public function getMessageBlobStore() {
208 return $this->blobStore;
209 }
210
215 public function setMessageBlobStore( MessageBlobStore $blobStore ) {
216 $this->blobStore = $blobStore;
217 }
218
223 public function setDependencyStore( DependencyStore $tracker ) {
224 $this->depStore = $tracker;
225 }
226
233 return $this->depStore;
234 }
235
240 public function setModuleSkinStyles( array $moduleSkinStyles ) {
241 $this->moduleSkinStyles = $moduleSkinStyles;
242 }
243
255 public function register( $name, ?array $info = null ) {
256 // Allow multiple modules to be registered in one call
257 $registrations = is_array( $name ) ? $name : [ $name => $info ];
258 foreach ( $registrations as $name => $info ) {
259 // Warn on duplicate registrations
260 if ( isset( $this->moduleInfos[$name] ) ) {
261 // A module has already been registered by this name
262 $this->logger->warning(
263 'ResourceLoader duplicate registration warning. ' .
264 'Another module has already been registered as ' . $name
265 );
266 }
267
268 // Check validity
269 if ( !self::isValidModuleName( $name ) ) {
270 throw new InvalidArgumentException( "ResourceLoader module name '$name' is invalid, "
271 . "see ResourceLoader::isValidModuleName()" );
272 }
273 if ( !is_array( $info ) ) {
274 throw new InvalidArgumentException(
275 'Invalid module info for "' . $name . '": expected array, got ' . get_debug_type( $info )
276 );
277 }
278
279 // Attach module
280 $this->moduleInfos[$name] = $info;
281 }
282 }
283
288 public function registerTestModules(): void {
289 $extRegistry = ExtensionRegistry::getInstance();
290 $testModules = $extRegistry->getAttribute( 'QUnitTestModule' );
291
292 $testModuleNames = [];
293 foreach ( $testModules as $name => &$module ) {
294 // Turn any single-module dependency into an array
295 if ( isset( $module['dependencies'] ) && is_string( $module['dependencies'] ) ) {
296 $module['dependencies'] = [ $module['dependencies'] ];
297 }
298
299 // Ensure the testrunner loads before any tests
300 $module['dependencies'][] = 'mediawiki.qunit-testrunner';
301
302 // Keep track of the modules to load on SpecialJavaScriptTest
303 $testModuleNames[] = $name;
304 }
305
306 // Core test modules (their names have further precedence).
307 $testModules = ( include MW_INSTALL_PATH . '/tests/qunit/QUnitTestResources.php' ) + $testModules;
308 $testModuleNames[] = 'test.MediaWiki';
309
310 $this->register( $testModules );
311 $this->testModuleNames = $testModuleNames;
312 }
313
324 public function addSource( $sources, $loadUrl = null ) {
325 if ( !is_array( $sources ) ) {
326 $sources = [ $sources => $loadUrl ];
327 }
328 foreach ( $sources as $id => $source ) {
329 // Disallow duplicates
330 if ( isset( $this->sources[$id] ) ) {
331 throw new RuntimeException( 'Cannot register source ' . $id . ' twice' );
332 }
333
334 // Support: MediaWiki 1.24 and earlier
335 if ( is_array( $source ) ) {
336 if ( !isset( $source['loadScript'] ) ) {
337 throw new InvalidArgumentException( 'Each source must have a "loadScript" key' );
338 }
339 $source = $source['loadScript'];
340 }
341
342 $this->sources[$id] = $source;
343 }
344 }
345
349 public function getModuleNames() {
350 return array_keys( $this->moduleInfos );
351 }
352
360 public function getTestSuiteModuleNames() {
361 return $this->testModuleNames;
362 }
363
371 public function isModuleRegistered( $name ) {
372 return isset( $this->moduleInfos[$name] );
373 }
374
386 public function getModule( $name ) {
387 if ( !isset( $this->modules[$name] ) ) {
388 if ( !isset( $this->moduleInfos[$name] ) ) {
389 // No such module
390 return null;
391 }
392 // Construct the requested module object
393 $info = $this->moduleInfos[$name];
394 if ( isset( $info['factory'] ) ) {
396 $object = $info['factory']( $info );
397 } else {
398 $class = $info['class'] ?? FileModule::class;
400 $object = new $class( $info );
401 }
402 $object->setConfig( $this->getConfig() );
403 $object->setLogger( $this->logger );
404 $object->setHookContainer( $this->hookContainer );
405 $object->setName( $name );
406 $object->setSkinStylesOverride( $this->moduleSkinStyles );
407 $this->modules[$name] = $object;
408 }
409
410 return $this->modules[$name];
411 }
412
419 public function preloadModuleInfo( array $moduleNames, Context $context ) {
420 // Load all tracked indirect file dependencies for the modules
421 $vary = Module::getVary( $context );
422 $entitiesByModule = [];
423 foreach ( $moduleNames as $moduleName ) {
424 $entitiesByModule[$moduleName] = "$moduleName|$vary";
425 }
426 $depsByEntity = $this->depStore->retrieveMulti(
427 $entitiesByModule
428 );
429
430 $modulesWithMessages = [];
431
432 // Inject the indirect file dependencies for all the modules
433 foreach ( $moduleNames as $moduleName ) {
434 $module = $this->getModule( $moduleName );
435 if ( $module ) {
436 $entity = $entitiesByModule[$moduleName];
437 $deps = $depsByEntity[$entity];
438 $paths = $deps['paths'];
439 $module->setFileDependencies( $context, $paths );
440
441 if ( $module->getMessages() ) {
442 $modulesWithMessages[$moduleName] = $module;
443 }
444 }
445 }
446
447 WikiModule::preloadTitleInfo( $context, $moduleNames );
448
449 // Prime in-object cache for message blobs for modules with messages
450 if ( $modulesWithMessages ) {
451 $lang = $context->getLanguage();
452 $store = $this->getMessageBlobStore();
453 $blobs = $store->getBlobs( $modulesWithMessages, $lang );
454 foreach ( $blobs as $moduleName => $blob ) {
455 $modulesWithMessages[$moduleName]->setMessageBlob( $blob, $lang );
456 }
457 }
458 }
459
465 public function getSources() {
466 return $this->sources;
467 }
468
477 public function getLoadScript( $source ) {
478 if ( !isset( $this->sources[$source] ) ) {
479 throw new UnexpectedValueException( "Unknown source '$source'" );
480 }
481 return $this->sources[$source];
482 }
483
487 public const HASH_LENGTH = 5;
488
551 public static function makeHash( $value ) {
552 $hash = hash( 'fnv132', $value );
553 // The base_convert will pad it (if too short),
554 // then substr() will trim it (if too long).
555 return substr(
556 \Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
557 0,
558 self::HASH_LENGTH
559 );
560 }
561
571 public function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
572 MWExceptionHandler::logException( $e );
573 $this->logger->warning(
574 $msg,
575 $context + [ 'exception' => $e ]
576 );
577 $this->errors[] = self::formatExceptionNoComment( $e );
578 }
579
588 public function getCombinedVersion( Context $context, array $moduleNames ) {
589 if ( !$moduleNames ) {
590 return '';
591 }
592 $hashes = [];
593 foreach ( $moduleNames as $module ) {
594 try {
595 $hash = $this->getModule( $module )->getVersionHash( $context );
596 } catch ( TimeoutException $e ) {
597 throw $e;
598 } catch ( Exception $e ) {
599 // If modules fail to compute a version, don't fail the request (T152266)
600 // and still compute versions of other modules.
601 $this->outputErrorAndLog( $e,
602 'Calculating version for "{module}" failed: {exception}',
603 [
604 'module' => $module,
605 ]
606 );
607 $hash = '';
608 }
609 $hashes[] = $hash;
610 }
611 return self::makeHash( implode( '', $hashes ) );
612 }
613
628 public function makeVersionQuery( Context $context, array $modules ) {
629 // As of MediaWiki 1.28, the server and client use the same algorithm for combining
630 // version hashes. There is no technical reason for this to be same, and for years the
631 // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
632 // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
633 // query parameter), then this method must continue to match the JS one.
634 $filtered = [];
635 foreach ( $modules as $name ) {
636 if ( !$this->getModule( $name ) ) {
637 // If a versioned request contains a missing module, the version is a mismatch
638 // as the client considered a module (and version) we don't have.
639 return '';
640 }
641 $filtered[] = $name;
642 }
643 return $this->getCombinedVersion( $context, $filtered );
644 }
645
653 public function respond( Context $context, array $extraHeaders = [] ) {
654 // Buffer output to catch warnings. Normally we'd use ob_clean() on the
655 // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
656 // is used: ob_clean() will clear the GZIP header in that case and it won't come
657 // back for subsequent output, resulting in invalid GZIP. So we have to wrap
658 // the whole thing in our own output buffer to be sure the active buffer
659 // doesn't use ob_gzhandler.
660 // See https://bugs.php.net/bug.php?id=36514
661 ob_start();
662
663 $this->errors = [];
664 $this->extraHeaders = $extraHeaders;
665 $responseTime = $this->measureResponseTime();
666 ProfilingContext::singleton()->init( MW_ENTRY_POINT, 'respond' );
667
668 // Find out which modules are missing and instantiate the others
669 $modules = [];
670 $missing = [];
671 foreach ( $context->getModules() as $name ) {
672 $module = $this->getModule( $name );
673 if ( $module ) {
674 // Do not allow private modules to be loaded from the web.
675 // This is a security issue, see T36907.
676 if ( $module->getGroup() === Module::GROUP_PRIVATE ) {
677 // Not a serious error, just means something is trying to access it (T101806)
678 $this->logger->debug( "Request for private module '$name' denied" );
679 $this->errors[] = "Cannot build private module \"$name\"";
680 continue;
681 }
682 $modules[$name] = $module;
683 } else {
684 $missing[] = $name;
685 }
686 }
687
688 try {
689 // Preload for getCombinedVersion() and for batch makeModuleResponse()
690 $this->preloadModuleInfo( array_keys( $modules ), $context );
691 } catch ( TimeoutException $e ) {
692 throw $e;
693 } catch ( Exception $e ) {
694 $this->outputErrorAndLog( $e, 'Preloading module info failed: {exception}' );
695 }
696
697 // Combine versions to propagate cache invalidation
698 $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
699
700 // See RFC 2616 § 3.11 Entity Tags
701 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
702 $etag = 'W/"' . $versionHash . '"';
703
704 // Try the client-side cache first
705 if ( $this->tryRespondNotModified( $context, $etag ) ) {
706 return; // output handled (buffers cleared)
707 }
708
709 if ( $context->isSourceMap() ) {
710 // In source map mode, a version mismatch should be a 404
711 if ( $context->getVersion() !== null && $versionHash !== $context->getVersion() ) {
712 ob_end_clean();
713 $this->sendSourceMapVersionMismatch( $versionHash );
714 return;
715 }
716 // No source maps for images, only=styles requests, or debug mode
717 if ( $context->getImage()
718 || $context->getOnly() === 'styles'
719 || $context->getDebug()
720 ) {
721 ob_end_clean();
722 $this->sendSourceMapTypeNotImplemented();
723 return;
724 }
725 }
726 // Emit source map header if supported (inverse of the above check)
727 if ( $this->config->get( MainConfigNames::ResourceLoaderEnableSourceMapLinks )
728 && !$context->getImageObj()
729 && !$context->isSourceMap()
730 && $context->shouldIncludeScripts()
731 && !$context->getDebug()
732 ) {
733 $this->extraHeaders[] = 'SourceMap: ' . $this->getSourceMapUrl( $context, $versionHash );
734 }
735
736 // Generate a response
737 $response = $this->makeModuleResponse( $context, $modules, $missing );
738
739 // Capture any PHP warnings from the output buffer and append them to the
740 // error list if we're in debug mode.
741 if ( $context->getDebug() ) {
742 $warnings = ob_get_contents();
743 if ( $warnings !== false && $warnings !== '' ) {
744 $this->errors[] = $warnings;
745 }
746 }
747
748 $this->sendResponseHeaders( $context, $etag, (bool)$this->errors );
749
750 // Remove the output buffer and output the response
751 ob_end_clean();
752
753 if ( $context->getImageObj() && $this->errors ) {
754 // We can't show both the error messages and the response when it's an image.
755 $response = implode( "\n\n", $this->errors );
756 } elseif ( $this->errors ) {
757 $errorText = implode( "\n\n", $this->errors );
758 $errorResponse = self::makeComment( $errorText );
759 if ( $context->shouldIncludeScripts() ) {
760 $errorResponse .= 'if (window.console && console.error) { console.error('
761 . $context->encodeJson( $errorText )
762 . "); }\n";
763 // Append the error info to the response
764 // We used to prepend it, but that would corrupt the source map
765 $response .= $errorResponse;
766 } else {
767 // For styles we can still prepend
768 $response = $errorResponse . $response;
769 }
770 }
771
772 // @phan-suppress-next-line SecurityCheck-XSS
773 echo $response;
774 }
775
779 #[\NoDiscard]
780 protected function measureResponseTime(): ScopedCallback {
781 $requestStart = $_SERVER['REQUEST_TIME_FLOAT'];
782 return new ScopedCallback( function () use ( $requestStart ) {
783 $statTiming = microtime( true ) - $requestStart;
784
785 $this->statsFactory->getTiming( 'resourceloader_response_time_seconds' )
786 ->observe( 1000 * $statTiming );
787 } );
788 }
789
799 protected function sendResponseHeaders(
800 Context $context, $etag, $errors
801 ): void {
802 HeaderCallback::warnIfHeadersSent();
803
804 if ( $errors ) {
805 $maxage = self::MAXAGE_RECOVER;
806 } elseif (
807 $context->getVersion() !== null
808 && $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
809 ) {
810 // If we need to self-correct, set a very short cache expiry
811 // to basically just debounce CDN traffic. This applies to:
812 // - Internal errors, e.g. due to misconfiguration.
813 // - Version mismatch, e.g. due to deployment race (T117587, T47877).
814 $this->logger->debug( 'Client and server registry version out of sync' );
815 $maxage = self::MAXAGE_RECOVER;
816 } elseif ( $context->getVersion() === null ) {
817 // Resources that can't set a version, should have their updates propagate to
818 // clients quickly. This applies to shared resources linked from HTML, such as
819 // the startup module and stylesheets.
820 $maxage = $this->maxageUnversioned;
821 } else {
822 // When a version is set, use a long expiry because changes
823 // will naturally miss the cache by using a different URL.
824 $maxage = $this->maxageVersioned;
825 }
826 if ( $context->getImageObj() ) {
827 // Output different headers if we're outputting textual errors.
828 if ( $errors ) {
829 header( 'Content-Type: text/plain; charset=utf-8' );
830 } else {
831 $context->getImageObj()->sendResponseHeaders( $context );
832 }
833 } elseif ( $context->isSourceMap() ) {
834 header( 'Content-Type: application/json' );
835 } elseif ( $context->getOnly() === 'styles' ) {
836 header( 'Content-Type: text/css; charset=utf-8' );
837 header( 'Access-Control-Allow-Origin: *' );
838 } else {
839 header( 'Content-Type: text/javascript; charset=utf-8' );
840 }
841 // See RFC 2616 § 14.19 ETag
842 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
843 header( 'ETag: ' . $etag );
844 if ( $context->getDebug() ) {
845 // Do not cache debug responses
846 header( 'Cache-Control: private, no-cache, must-revalidate' );
847 } else {
848 // T132418: When a resource expires mid-way a browsing session, prefer to renew it in
849 // the background instead of blocking the next page load (eg. startup module, or CSS).
850 $staleDirective = ( $maxage > self::MAXAGE_RECOVER
851 ? ", stale-while-revalidate=" . min( 60, intval( $maxage / 2 ) )
852 : ''
853 );
854 header( "Cache-Control: public, max-age=$maxage, s-maxage=$maxage" . $staleDirective );
855 header( 'Expires: ' . ConvertibleTimestamp::convert( TS::RFC2822, time() + $maxage ) );
856 }
857
858 foreach ( $this->extraHeaders as $header ) {
859 header( $header );
860 }
861 }
862
873 protected function tryRespondNotModified( Context $context, $etag ) {
874 // See RFC 2616 § 14.26 If-None-Match
875 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
876 $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
877 // Never send 304s in debug mode
878 if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
879 // There's another bug in ob_gzhandler (see also the comment at
880 // the top of this function) that causes it to gzip even empty
881 // responses, meaning it's impossible to produce a truly empty
882 // response (because the gzip header is always there). This is
883 // a problem because 304 responses have to be completely empty
884 // per the HTTP spec, and Firefox behaves buggily when they're not.
885 // See also https://bugs.php.net/bug.php?id=51579
886 // To work around this, we tear down all output buffering before
887 // sending the 304.
888 wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
889
890 HttpStatus::header( 304 );
891 $this->sendResponseHeaders( $context, $etag, false );
892 return true;
893 }
894 return false;
895 }
896
904 private function getSourceMapUrl( Context $context, $version ) {
905 return $this->createLoaderURL( 'local', $context, [
906 'sourcemap' => '1',
907 'version' => $version
908 ] );
909 }
910
916 private function sendSourceMapVersionMismatch( $currentVersion ) {
917 HttpStatus::header( 404 );
918 header( 'Content-Type: text/plain; charset=utf-8' );
919 header( 'X-Content-Type-Options: nosniff' );
920 echo "Can't deliver a source map for the requested version " .
921 "since the version is now '$currentVersion'\n";
922 }
923
928 private function sendSourceMapTypeNotImplemented() {
929 HttpStatus::header( 404 );
930 header( 'Content-Type: text/plain; charset=utf-8' );
931 header( 'X-Content-Type-Options: nosniff' );
932 echo "Can't make a source map for this content type\n";
933 }
934
943 public static function makeComment( $text ) {
944 $encText = str_replace( '*/', '* /', $text );
945 return "/*\n$encText\n*/\n";
946 }
947
955 protected static function formatExceptionNoComment( Throwable $e ) {
956 if ( !MWExceptionRenderer::shouldShowExceptionDetails() ) {
957 return MWExceptionHandler::getPublicLogMessage( $e );
958 }
959
960 // Like MWExceptionHandler::getLogMessage but without $url and $id.
961 // - Long load.php URL would push the actual error message off-screen into
962 // scroll overflow in browser devtools.
963 // - reqId is redundant with X-Request-Id header, plus usually no need to
964 // correlate the reqId since the backtrace is already included below.
965 $type = get_class( $e );
966 $message = $e->getMessage();
967
968 return "$type: $message" .
969 "\nBacktrace:\n" .
970 MWExceptionHandler::getRedactedTraceAsString( $e );
971 }
972
984 public function makeModuleResponse( Context $context,
985 array $modules, array $missing = []
986 ) {
987 if ( $modules === [] && $missing === [] ) {
988 return <<<MESSAGE
989/* This file is the Web entry point for MediaWiki's ResourceLoader:
990 <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
991 no modules were requested. Max made me put this here. */
992MESSAGE;
993 }
994
995 $image = $context->getImageObj();
996 if ( $image ) {
997 $data = $image->getImageData( $context );
998 if ( $data === false ) {
999 $data = '';
1000 $this->errors[] = 'Image generation failed';
1001 }
1002 return $data;
1003 }
1004
1005 $states = [];
1006 foreach ( $missing as $name ) {
1007 $states[$name] = 'missing';
1008 }
1009
1010 $only = $context->getOnly();
1011 $debug = (bool)$context->getDebug();
1012 if ( $context->isSourceMap() && count( $modules ) > 1 ) {
1013 $indexMap = new IndexMap;
1014 } else {
1015 $indexMap = null;
1016 }
1017
1018 $out = '';
1019 foreach ( $modules as $name => $module ) {
1020 try {
1021 [ $response, $offset ] = $this->getOneModuleResponse( $context, $name, $module );
1022 if ( $indexMap ) {
1023 $indexMap->addEncodedMap( $response, $offset );
1024 } else {
1025 $out .= $response;
1026 }
1027 } catch ( TimeoutException $e ) {
1028 throw $e;
1029 } catch ( Exception $e ) {
1030 $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1031
1032 // Respond to client with error-state instead of module implementation
1033 $states[$name] = 'error';
1034 unset( $modules[$name] );
1035 }
1036 }
1037
1038 // Update module states
1039 if ( $context->shouldIncludeScripts() && !$context->getRaw() ) {
1040 if ( $modules && $only === 'scripts' ) {
1041 // Set the state of modules loaded as only scripts to ready as
1042 // they don't have an mw.loader.impl wrapper that sets the state
1043 foreach ( $modules as $name => $module ) {
1044 $states[$name] = 'ready';
1045 }
1046 }
1047
1048 // Set the state of modules we didn't respond to with mw.loader.impl
1049 if ( $states && !$context->isSourceMap() ) {
1050 $stateScript = self::makeLoaderStateScript( $context, $states );
1051 if ( !$debug ) {
1052 $stateScript = self::filter( 'minify-js', $stateScript );
1053 }
1054 // Use a linebreak between module script and state script (T162719)
1055 $out = self::ensureNewline( $out ) . $stateScript;
1056 }
1057 } elseif ( $states ) {
1058 $this->errors[] = 'Problematic modules: '
1059 // Silently ignore invalid UTF-8 injected via 'modules' query
1060 // Don't issue server-side warnings for client errors. (T331641)
1061 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1062 . @$context->encodeJson( $states );
1063 }
1064
1065 if ( $indexMap ) {
1066 return $indexMap->getMap();
1067 }
1068 return $out;
1069 }
1070
1079 private function getOneModuleResponse( Context $context, $name, Module $module ) {
1080 $only = $context->getOnly();
1081 // Important: Do not cache minifications of embedded modules
1082 // This is especially for the private 'user.options' module,
1083 // which varies on every pageview and would explode the cache (T84960)
1084 $shouldCache = !$module->shouldEmbedModule( $context );
1085 if ( $only === 'styles' ) {
1086 $minifier = new IdentityMinifierState;
1087 $this->addOneModuleResponse( $context, $minifier, $name, $module, $this->extraHeaders );
1088 // NOTE: This is not actually "minified". IdentityMinifierState is a no-op wrapper
1089 // to ease code reuse. The filter() call below performs CSS minification.
1090 $styles = $minifier->getMinifiedOutput();
1091 if ( $context->getDebug() ) {
1092 return [ $styles, null ];
1093 }
1094 return [
1095 self::filter( 'minify-css', $styles,
1096 [ 'cache' => $shouldCache ] ),
1097 null
1098 ];
1099 }
1100
1101 $replayMinifier = new ReplayMinifierState;
1102 $this->addOneModuleResponse( $context, $replayMinifier, $name, $module, $this->extraHeaders );
1103
1104 $minifier = new IdentityMinifierState;
1105 $replayMinifier->replayOn( $minifier );
1106 $plainContent = $minifier->getMinifiedOutput();
1107 if ( $context->getDebug() ) {
1108 return [ $plainContent, null ];
1109 }
1110
1111 $isHit = true;
1112 $callback = function () use ( $context, $replayMinifier, &$isHit ) {
1113 $isHit = false;
1114 if ( $context->isSourceMap() ) {
1115 $minifier = ( new JavaScriptMapperState )
1116 ->outputFile( $this->createLoaderURL( 'local', $context, [
1117 'modules' => self::makePackedModulesString( $context->getModules() ),
1118 'only' => $context->getOnly()
1119 ] ) );
1120 } else {
1121 $minifier = new JavaScriptMinifierState;
1122 }
1123 $replayMinifier->replayOn( $minifier );
1124 if ( $context->isSourceMap() ) {
1125 $sourceMap = $minifier->getRawSourceMap();
1126 $generated = $minifier->getMinifiedOutput();
1127 $offset = IndexMapOffset::newFromText( $generated );
1128 return [ $sourceMap, $offset->toArray() ];
1129 } else {
1130 return [ $minifier->getMinifiedOutput(), null ];
1131 }
1132 };
1133
1134 // The below is based on ResourceLoader::filter. Keep together to ease review/maintenance:
1135 // * Handle $shouldCache, skip cache and minify directly if set.
1136 // * Use minify cache, minify on-demand and populate cache as needed.
1137 // * Emit resourceloader_cache_total stats.
1138
1139 if ( $shouldCache ) {
1140 [ $response, $offsetArray ] = $this->srvCache->getWithSetCallback(
1141 $this->srvCache->makeGlobalKey(
1142 'resourceloader-mapped',
1143 self::CACHE_VERSION,
1144 $name,
1145 $context->isSourceMap() ? '1' : '0',
1146 md5( $plainContent )
1147 ),
1148 BagOStuff::TTL_DAY,
1149 $callback
1150 );
1151
1152 $mapType = $context->isSourceMap() ? 'map-js' : 'minify-js';
1153 $this->statsFactory->getCounter( 'resourceloader_cache_total' )
1154 ->setLabel( 'type', $mapType )
1155 ->setLabel( 'status', $isHit ? 'hit' : 'miss' )
1156 ->increment();
1157 } else {
1158 [ $response, $offsetArray ] = $callback();
1159 }
1160 $offset = $offsetArray ? IndexMapOffset::newFromArray( $offsetArray ) : null;
1161
1162 return [ $response, $offset ];
1163 }
1164
1175 private function addOneModuleResponse(
1176 Context $context, MinifierState $minifier, $name, Module $module, &$headers
1177 ) {
1178 $only = $context->getOnly();
1179 $debug = (bool)$context->getDebug();
1180 $content = $module->getModuleContent( $context );
1181 $version = $module->getVersionHash( $context );
1182
1183 if ( $headers !== null && isset( $content['headers'] ) ) {
1184 $headers = array_merge( $headers, $content['headers'] );
1185 }
1186
1187 // Append output
1188 switch ( $only ) {
1189 case 'scripts':
1190 $scripts = $content['scripts'];
1191 if ( !is_array( $scripts ) ) {
1192 // Formerly scripts was usually a string, but now it is
1193 // normalized to an array by buildContent().
1194 throw new InvalidArgumentException( 'scripts must be an array' );
1195 }
1196 if ( isset( $scripts['plainScripts'] ) ) {
1197 // Add plain scripts
1198 $this->addPlainScripts( $minifier, $name, $scripts['plainScripts'] );
1199 } elseif ( isset( $scripts['files'] ) ) {
1200 // Add implement call if any
1201 $this->addImplementScript(
1202 $minifier,
1203 $name,
1204 $version,
1205 $scripts,
1206 [],
1207 null,
1208 [],
1209 $content['deprecationWarning'] ?? null
1210 );
1211 }
1212 break;
1213 case 'styles':
1214 $styles = $content['styles'];
1215 // We no longer separate into media, they are all combined now with
1216 // custom media type groups into @media .. {} sections as part of the css string.
1217 // Module returns either an empty array or a numerical array with css strings.
1218 if ( isset( $styles['css'] ) ) {
1219 $minifier->addOutput( implode( '', $styles['css'] ) );
1220 }
1221 break;
1222 default:
1223 $scripts = $content['scripts'] ?? '';
1224 if ( ( $name === 'site' || $name === 'user' )
1225 && isset( $scripts['plainScripts'] )
1226 ) {
1227 // Legacy scripts that run in the global scope without a closure.
1228 // mw.loader.impl will use eval if scripts is a string.
1229 // Minify manually here, because general response minification is
1230 // not effective due it being a string literal, not a function.
1231 $scripts = self::concatenatePlainScripts( $scripts['plainScripts'] );
1232 if ( !$debug ) {
1233 $scripts = self::filter( 'minify-js', $scripts ); // T107377
1234 }
1235 }
1236 $this->addImplementScript(
1237 $minifier,
1238 $name,
1239 $version,
1240 $scripts,
1241 $content['styles'] ?? [],
1242 isset( $content['messagesBlob'] ) ? new HtmlJsCode( $content['messagesBlob'] ) : null,
1243 $content['templates'] ?? [],
1244 $content['deprecationWarning'] ?? null
1245 );
1246 break;
1247 }
1248 $minifier->ensureNewline();
1249 }
1250
1257 public static function ensureNewline( $str ) {
1258 $end = substr( $str, -1 );
1259 if ( $end === '' || $end === "\n" ) {
1260 return $str;
1261 }
1262 return $str . "\n";
1263 }
1264
1271 public function getModulesByMessage( $messageKey ) {
1272 $moduleNames = [];
1273 foreach ( $this->getModuleNames() as $moduleName ) {
1274 $module = $this->getModule( $moduleName );
1275 if ( in_array( $messageKey, $module->getMessages() ) ) {
1276 $moduleNames[] = $moduleName;
1277 }
1278 }
1279 return $moduleNames;
1280 }
1281
1303 private function addImplementScript( MinifierState $minifier,
1304 $moduleName, $version, $scripts, $styles, $messages, $templates, $deprecationWarning
1305 ) {
1306 $implementKey = "$moduleName@$version";
1307 // Plain functions are used instead of arrow functions to avoid
1308 // defeating lazy compilation on Chrome. (T343407)
1309 $minifier->addOutput( "mw.loader.impl(function(){return[" .
1310 Html::encodeJsVar( $implementKey ) . "," );
1311
1312 // Scripts
1313 if ( is_string( $scripts ) ) {
1314 // user/site script
1315 $minifier->addOutput( Html::encodeJsVar( $scripts ) );
1316 } elseif ( is_array( $scripts ) ) {
1317 if ( isset( $scripts['files'] ) ) {
1318 $minifier->addOutput(
1319 "{\"main\":" .
1320 Html::encodeJsVar( $scripts['main'] ) .
1321 ",\"files\":" );
1322 $this->addFiles( $minifier, $moduleName, $scripts['files'] );
1323 $minifier->addOutput( "}" );
1324 } elseif ( isset( $scripts['plainScripts'] ) ) {
1325 if ( $this->isEmptyFileInfos( $scripts['plainScripts'] ) ) {
1326 $minifier->addOutput( 'null' );
1327 } else {
1328 $minifier->addOutput( "function($,jQuery,require,module){" );
1329 $this->addPlainScripts( $minifier, $moduleName, $scripts['plainScripts'] );
1330 $minifier->addOutput( "}" );
1331 }
1332 } elseif ( $scripts === [] || isset( $scripts[0] ) ) {
1333 // Array of URLs
1334 $minifier->addOutput( Html::encodeJsVar( $scripts ) );
1335 } else {
1336 throw new InvalidArgumentException( 'Invalid script array: ' .
1337 'must contain files, plainScripts or be an array of URLs' );
1338 }
1339 } else {
1340 throw new InvalidArgumentException( 'Script must be a string or array' );
1341 }
1342
1343 // mw.loader.impl requires 'styles', 'messages' and 'templates' to be objects (not
1344 // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1345 // of "{}". Force them to objects.
1346 $extraArgs = [
1347 (object)$styles,
1348 $messages ?? (object)[],
1349 (object)$templates,
1350 $deprecationWarning
1351 ];
1352 self::trimArray( $extraArgs );
1353 foreach ( $extraArgs as $arg ) {
1354 $minifier->addOutput( ',' . Html::encodeJsVar( $arg ) );
1355 }
1356 $minifier->addOutput( "];});" );
1357 }
1358
1369 private function addFiles( MinifierState $minifier, $moduleName, $files ) {
1370 $first = true;
1371 $minifier->addOutput( "{" );
1372 foreach ( $files as $fileName => $file ) {
1373 if ( $first ) {
1374 $first = false;
1375 } else {
1376 $minifier->addOutput( "," );
1377 }
1378 $minifier->addOutput( Html::encodeJsVar( $fileName ) . ':' );
1379 $this->addFileContent( $minifier, $moduleName, 'packageFile', $fileName, $file );
1380 }
1381 $minifier->addOutput( "}" );
1382 }
1383
1393 private function addFileContent( MinifierState $minifier,
1394 $moduleName, $sourceType, $sourceIndex, array $file
1395 ) {
1396 $isScript = ( $file['type'] ?? 'script' ) === 'script';
1398 $filePath = $file['filePath'] ?? $file['virtualFilePath'] ?? null;
1399 if ( $filePath !== null && $filePath->getRemoteBasePath() !== null ) {
1400 $url = $filePath->getRemotePath();
1401 } else {
1402 $ext = $isScript ? 'js' : 'json';
1403 $scriptPath = $this->config->has( MainConfigNames::ScriptPath )
1404 ? $this->config->get( MainConfigNames::ScriptPath ) : '';
1405 $url = "$scriptPath/virtual-resource/$moduleName-$sourceType-$sourceIndex.$ext";
1406 }
1407 $content = $file['content'];
1408 if ( $isScript ) {
1409 if ( $sourceType === 'packageFile' ) {
1410 // Provide CJS `exports` (in addition to CJS2 `module.exports`) to package modules (T284511).
1411 // $/jQuery are simply used as globals instead.
1412 // TODO: Remove $/jQuery param from traditional module closure too (and bump caching)
1413 $minifier->addOutput( "function(require,module,exports){" );
1414 $minifier->addSourceFile( $url, $content, true );
1415 $minifier->ensureNewline();
1416 $minifier->addOutput( "}" );
1417 } else {
1418 $minifier->addSourceFile( $url, $content, true );
1419 $minifier->ensureNewline();
1420 }
1421 } else {
1422 $content = Html::encodeJsVar( $content, true );
1423 $minifier->addSourceFile( $url, $content, true );
1424 }
1425 }
1426
1434 private static function concatenatePlainScripts( $plainScripts ) {
1435 $s = '';
1436 foreach ( $plainScripts as $script ) {
1437 // Make the script safe to concatenate by making sure there is at least one
1438 // trailing new line at the end of the content (T29054, T162719)
1439 $s .= self::ensureNewline( $script['content'] );
1440 }
1441 return $s;
1442 }
1443
1452 private function addPlainScripts( MinifierState $minifier, $moduleName, $plainScripts ) {
1453 foreach ( $plainScripts as $index => $file ) {
1454 $this->addFileContent( $minifier, $moduleName, 'script', $index, $file );
1455 }
1456 }
1457
1464 private function isEmptyFileInfos( $infos ) {
1465 $len = 0;
1466 foreach ( $infos as $info ) {
1467 $len += strlen( $info['content'] ?? '' );
1468 }
1469 return $len === 0;
1470 }
1471
1479 public static function makeCombinedStyles( array $stylePairs ) {
1480 $out = [];
1481 foreach ( $stylePairs as $media => $styles ) {
1482 // FileModule::getStyle can return the styles as a string or an
1483 // array of strings. This is to allow separation in the front-end.
1484 $styles = (array)$styles;
1485 foreach ( $styles as $style ) {
1486 $style = trim( $style );
1487 // Don't output an empty "@media print { }" block (T42498)
1488 if ( $style === '' ) {
1489 continue;
1490 }
1491 // Transform the media type based on request params and config
1492 // The way that this relies on $wgRequest to propagate request params is slightly evil
1493 $media = OutputPage::transformCssMedia( $media );
1494
1495 if ( $media === '' || $media == 'all' ) {
1496 $out[] = $style;
1497 } elseif ( is_string( $media ) ) {
1498 $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1499 }
1500 // else: skip
1501 }
1502 }
1503 return $out;
1504 }
1505
1513 private static function encodeJsonForScript( $data ) {
1514 // Keep output as small as possible by disabling needless escape modes
1515 // that PHP uses by default.
1516 // However, while most module scripts are only served on HTTP responses
1517 // for JavaScript, some modules can also be embedded in the HTML as inline
1518 // scripts. This, and the fact that we sometimes need to export strings
1519 // containing user-generated content and labels that may genuinely contain
1520 // a sequences like "</script>", we need to encode either '/' or '<'.
1521 // By default PHP escapes '/'. Let's escape '<' instead which is less common
1522 // and allows URLs to mostly remain readable.
1523 $jsonFlags = JSON_UNESCAPED_SLASHES |
1524 JSON_UNESCAPED_UNICODE |
1525 JSON_HEX_TAG |
1526 JSON_HEX_AMP;
1527 if ( self::inDebugMode() ) {
1528 $jsonFlags |= JSON_PRETTY_PRINT;
1529 }
1530 return json_encode( $data, $jsonFlags );
1531 }
1532
1541 public static function makeLoaderStateScript(
1542 Context $context, array $states
1543 ) {
1544 return 'mw.loader.state('
1545 // Silently ignore invalid UTF-8 injected via 'modules' query
1546 // Don't issue server-side warnings for client errors. (T331641)
1547 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1548 . @$context->encodeJson( $states )
1549 . ');';
1550 }
1551
1552 private static function isEmptyObject( stdClass $obj ): bool {
1553 foreach ( $obj as $value ) {
1554 return false;
1555 }
1556 return true;
1557 }
1558
1570 private static function trimArray( array &$array ): void {
1571 $i = count( $array );
1572 while ( $i-- ) {
1573 if ( $array[$i] === null
1574 || $array[$i] === []
1575 || ( $array[$i] instanceof HtmlJsCode && $array[$i]->value === '{}' )
1576 || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1577 ) {
1578 unset( $array[$i] );
1579 } else {
1580 break;
1581 }
1582 }
1583 }
1584
1610 public static function makeLoaderRegisterScript(
1611 Context $context, array $modules
1612 ) {
1613 // Optimisation: Transform dependency names into indexes when possible
1614 // to produce smaller output. They are expanded by mw.loader.register on
1615 // the other end.
1616 $index = [];
1617 foreach ( $modules as $i => $module ) {
1618 // Build module name index
1619 $index[$module[0]] = $i;
1620 }
1621 foreach ( $modules as &$module ) {
1622 if ( isset( $module[2] ) ) {
1623 foreach ( $module[2] as &$dependency ) {
1624 if ( isset( $index[$dependency] ) ) {
1625 // Replace module name in dependency list with index
1626 $dependency = $index[$dependency];
1627 }
1628 }
1629 }
1630 self::trimArray( $module );
1631 }
1632
1633 return 'mw.loader.register('
1634 . $context->encodeJson( $modules )
1635 . ');';
1636 }
1637
1651 public static function makeLoaderSourcesScript(
1652 Context $context, array $sources
1653 ) {
1654 return 'mw.loader.addSource('
1655 . $context->encodeJson( $sources )
1656 . ');';
1657 }
1658
1665 public static function makeLoaderConditionalScript( $script ) {
1666 // Adds a function to lazy-created RLQ
1667 return '(RLQ=window.RLQ||[]).push(function(){' .
1668 trim( $script ) . '});';
1669 }
1670
1679 public static function makeInlineCodeWithModule( $modules, $script ) {
1680 // Adds an array to lazy-created RLQ
1681 return '(RLQ=window.RLQ||[]).push(['
1682 . json_encode( $modules ) . ','
1683 . 'function(){' . trim( $script ) . '}'
1684 . ']);';
1685 }
1686
1697 public static function makeInlineScript( $script, $nonce = null ) {
1698 $js = self::makeLoaderConditionalScript( $script );
1699 return new WrappedString(
1700 Html::inlineScript( $js ),
1701 "<script>(RLQ=window.RLQ||[]).push(function(){",
1702 '});</script>'
1703 );
1704 }
1705
1718 public static function makeConfigSetScript( array $configuration ) {
1719 $json = self::encodeJsonForScript( $configuration );
1720 if ( $json === false ) {
1721 $e = new LogicException(
1722 'JSON serialization of config data failed. ' .
1723 'This usually means the config data is not valid UTF-8.'
1724 );
1725 MWExceptionHandler::logException( $e );
1726 return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) . ');';
1727 }
1728 return "mw.config.set($json);";
1729 }
1730
1744 public static function makePackedModulesString( array $modules ) {
1745 $moduleMap = []; // [ prefix => [ suffixes ] ]
1746 foreach ( $modules as $module ) {
1747 $pos = strrpos( $module, '.' );
1748 $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1749 $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1750 $moduleMap[$prefix][] = $suffix;
1751 }
1752
1753 $arr = [];
1754 foreach ( $moduleMap as $prefix => $suffixes ) {
1755 $p = $prefix === '' ? '' : $prefix . '.';
1756 $arr[] = $p . implode( ',', $suffixes );
1757 }
1758 return implode( '|', $arr );
1759 }
1760
1772 public static function expandModuleNames( $modules ) {
1773 $retval = [];
1774 $exploded = explode( '|', $modules );
1775 foreach ( $exploded as $group ) {
1776 if ( !str_contains( $group, ',' ) ) {
1777 // This is not a set of modules in foo.bar,baz notation
1778 // but a single module
1779 $retval[] = $group;
1780 continue;
1781 }
1782 // This is a set of modules in foo.bar,baz notation
1783 $pos = strrpos( $group, '.' );
1784 if ( $pos === false ) {
1785 // Prefixless modules, i.e. without dots
1786 $retval = array_merge( $retval, explode( ',', $group ) );
1787 continue;
1788 }
1789 // We have a prefix and a bunch of suffixes
1790 $prefix = substr( $group, 0, $pos ); // 'foo'
1791 $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // [ 'bar', 'baz' ]
1792 foreach ( $suffixes as $suffix ) {
1793 $retval[] = "$prefix.$suffix";
1794 }
1795 }
1796 return $retval;
1797 }
1798
1809 public static function inDebugMode() {
1810 if ( self::$debugMode === null ) {
1811 global $wgRequest;
1812
1813 $resourceLoaderDebug = MediaWikiServices::getInstance()->getMainConfig()->get(
1814 MainConfigNames::ResourceLoaderDebug );
1815 $str = $wgRequest->getRawVal( 'debug' ) ??
1816 $wgRequest->getCookie( 'resourceLoaderDebug', '', $resourceLoaderDebug ? 'true' : '' );
1817 self::$debugMode = Context::debugFromString( $str );
1818 }
1819 return self::$debugMode;
1820 }
1821
1832 public static function clearCache() {
1833 self::$debugMode = null;
1834 }
1835
1845 public function createLoaderURL( $source, Context $context,
1846 array $extraQuery = []
1847 ) {
1848 $query = self::createLoaderQuery( $context, $extraQuery );
1849 $script = $this->getLoadScript( $source );
1850
1851 return wfAppendQuery( $script, $query );
1852 }
1853
1863 protected static function createLoaderQuery(
1864 Context $context, array $extraQuery = []
1865 ) {
1866 return self::makeLoaderQuery(
1867 $context->getModules(),
1868 $context->getLanguage(),
1869 $context->getSkin(),
1870 $context->getUser(),
1871 $context->getVersion(),
1872 $context->getDebug(),
1873 $context->getOnly(),
1874 $context->getRequest()->getBool( 'printable' ),
1875 null,
1876 $extraQuery
1877 );
1878 }
1879
1896 public static function makeLoaderQuery( array $modules, $lang, $skin, $user = null,
1897 $version = null, $debug = Context::DEBUG_OFF, $only = null,
1898 $printable = false, $handheld = null, array $extraQuery = []
1899 ) {
1900 $query = [
1901 'modules' => self::makePackedModulesString( $modules ),
1902 ];
1903 // Keep urls short by omitting query parameters that
1904 // match the defaults assumed by Context.
1905 // Note: This relies on the defaults either being insignificant or forever constant,
1906 // as otherwise cached urls could change in meaning when the defaults change.
1907 if ( $lang !== Context::DEFAULT_LANG ) {
1908 $query['lang'] = $lang;
1909 }
1910 if ( $skin !== Context::DEFAULT_SKIN ) {
1911 $query['skin'] = $skin;
1912 }
1913 if ( $debug !== Context::DEBUG_OFF ) {
1914 $query['debug'] = strval( $debug );
1915 }
1916 if ( $user !== null ) {
1917 $query['user'] = $user;
1918 }
1919 if ( $version !== null ) {
1920 $query['version'] = $version;
1921 }
1922 if ( $only !== null ) {
1923 $query['only'] = $only;
1924 }
1925 if ( $printable ) {
1926 $query['printable'] = 1;
1927 }
1928 foreach ( $extraQuery as $name => $value ) {
1929 $query[$name] = $value;
1930 }
1931
1932 // Make queries uniform in order
1933 ksort( $query );
1934 return $query;
1935 }
1936
1946 public static function isValidModuleName( $moduleName ) {
1947 $len = strlen( $moduleName );
1948 return ( $len <= 255
1949 && strcspn( $moduleName, '!,|', 0, $len ) === $len )
1950 && ( !str_starts_with( $moduleName, "./" ) && !str_starts_with( $moduleName, "../" ) );
1951 }
1952
1963 public function getLessCompiler( array $vars = [], array $importDirs = [] ) {
1964 // When called from the installer, it is possible that a required PHP extension
1965 // is missing (at least for now; see T49564). If this is the case, throw an
1966 // exception (caught by the installer) to prevent a fatal error later on.
1967 if ( !class_exists( Less_Parser::class ) ) {
1968 throw new RuntimeException( 'MediaWiki requires the less.php parser' );
1969 }
1970
1971 $importDirs[] = MW_INSTALL_PATH . '/resources/src/mediawiki.less';
1972
1973 $parser = new Less_Parser;
1974 $parser->ModifyVars( $vars );
1975 $parser->SetOption( 'relativeUrls', false );
1976 $parser->SetOption( 'math', 'parens-division' );
1977
1978 // SetImportDirs expects an array like [ 'path1' => '', 'path2' => '' ]
1979 $formattedImportDirs = array_fill_keys( $importDirs, '' );
1980
1981 // Add a callback to the import dirs array for path remapping
1982 $codexDevDir = $this->getConfig()->get( MainConfigNames::CodexDevelopmentDir );
1983 $formattedImportDirs[] = static function ( $path ) use ( $codexDevDir ) {
1984 // For each of the Codex import paths, use CodexDevelopmentDir if it's set
1985 $importMap = [
1986 '@wikimedia/codex-icons/' => $codexDevDir !== null ?
1987 "$codexDevDir/packages/codex-icons/dist/" :
1988 MW_INSTALL_PATH . '/resources/lib/codex-icons/',
1989 'mediawiki.skin.codex/' => $codexDevDir !== null ?
1990 "$codexDevDir/packages/codex/dist/" :
1991 MW_INSTALL_PATH . '/resources/lib/codex/',
1992 'mediawiki.skin.codex-design-tokens/' => $codexDevDir !== null ?
1993 "$codexDevDir/packages/codex-design-tokens/dist/" :
1994 MW_INSTALL_PATH . '/resources/lib/codex-design-tokens/',
1995 '@wikimedia/codex-design-tokens/' => static function ( $unused_path ): never {
1996 throw new RuntimeException(
1997 'Importing from @wikimedia/codex-design-tokens is not supported. ' .
1998 "To use the Codex tokens, use `@import 'mediawiki.skin.variables.less';` instead."
1999 );
2000 }
2001 ];
2002 foreach ( $importMap as $importPath => $substPath ) {
2003 if ( str_starts_with( $path, $importPath ) ) {
2004 $restOfPath = substr( $path, strlen( $importPath ) );
2005 if ( is_callable( $substPath ) ) {
2006 // @phan-suppress-next-line PhanUseReturnValueOfNever
2007 $resolvedPath = $substPath( $restOfPath );
2008 } else {
2009 $filePath = $substPath . $restOfPath;
2010
2011 $resolvedPath = null;
2012 if ( file_exists( $filePath ) ) {
2013 $resolvedPath = $filePath;
2014 } elseif ( file_exists( "$filePath.less" ) ) {
2015 $resolvedPath = "$filePath.less";
2016 }
2017 }
2018
2019 if ( $resolvedPath !== null ) {
2020 return [
2021 Less_Environment::normalizePath( $resolvedPath ),
2022 Less_Environment::normalizePath( dirname( $path ) )
2023 ];
2024 } else {
2025 break;
2026 }
2027 }
2028 }
2029 return [ null, null ];
2030 };
2031 $parser->SetImportDirs( $formattedImportDirs );
2032
2033 return $parser;
2034 }
2035
2053 public static function filter( $filter, $data, array $options = [] ) {
2054 if ( isset( $options['cache'] ) && $options['cache'] === false ) {
2055 return self::applyFilter( $filter, $data ) ?? $data;
2056 }
2057
2058 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
2059 // Same as ResourceLoader->srvCache
2060 $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
2061
2062 $key = $cache->makeGlobalKey(
2063 'resourceloader-filter',
2064 $filter,
2065 self::CACHE_VERSION,
2066 md5( $data )
2067 );
2068
2069 $status = 'hit';
2070 $result = $cache->getWithSetCallback(
2071 $key,
2072 BagOStuff::TTL_DAY,
2073 static function () use ( $filter, $data, &$status ) {
2074 $status = 'miss';
2075 return self::applyFilter( $filter, $data );
2076 }
2077 );
2078 $statsFactory->getCounter( 'resourceloader_cache_total' )
2079 ->setLabel( 'type', $filter )
2080 ->setLabel( 'status', $status )
2081 ->increment();
2082
2083 // Use $data on cache failure
2084 return $result ?? $data;
2085 }
2086
2092 private static function applyFilter( $filter, $data ) {
2093 $data = trim( $data );
2094 if ( $data ) {
2095 try {
2096 $data = ( $filter === 'minify-css' )
2097 ? CSSMin::minify( $data )
2098 : JavaScriptMinifier::minify( $data );
2099 } catch ( TimeoutException $e ) {
2100 throw $e;
2101 } catch ( Exception $e ) {
2102 MWExceptionHandler::logException( $e );
2103 return null;
2104 }
2105 }
2106 return $data;
2107 }
2108
2120 public static function getUserDefaults(
2121 Context $context,
2122 HookContainer $hookContainer,
2123 UserOptionsLookup $userOptionsLookup
2124 ): array {
2125 $defaultOptions = $userOptionsLookup->getDefaultOptions();
2126 $keysToExclude = [];
2127 $hookRunner = new HookRunner( $hookContainer );
2128 $hookRunner->onResourceLoaderExcludeUserOptions( $keysToExclude, $context );
2129 foreach ( $keysToExclude as $excludedKey ) {
2130 unset( $defaultOptions[ $excludedKey ] );
2131 }
2132 return $defaultOptions;
2133 }
2134
2143 public static function getSiteConfigSettings(
2144 Context $context, Config $conf
2145 ): array {
2146 $services = MediaWikiServices::getInstance();
2147 // Namespace related preparation
2148 // - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces.
2149 // - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive.
2150 $contLang = $services->getContentLanguage();
2151 $namespaceIds = $contLang->getNamespaceIds();
2152 $caseSensitiveNamespaces = [];
2153 $nsInfo = $services->getNamespaceInfo();
2154 foreach ( $nsInfo->getCanonicalNamespaces() as $index => $name ) {
2155 $namespaceIds[$contLang->lc( $name )] = $index;
2156 if ( !$nsInfo->isCapitalized( $index ) ) {
2157 $caseSensitiveNamespaces[] = $index;
2158 }
2159 }
2160
2161 $illegalFileChars = $conf->get( MainConfigNames::IllegalFileChars );
2162
2163 // Build list of variables
2164 $skin = $context->getSkin();
2165
2166 // Start of supported and stable config vars (for use by extensions/gadgets).
2167 $vars = [
2168 'debug' => $context->getDebug(),
2169 'skin' => $skin,
2170 'stylepath' => $conf->get( MainConfigNames::StylePath ),
2171 'wgArticlePath' => $conf->get( MainConfigNames::ArticlePath ),
2172 'wgScriptPath' => $conf->get( MainConfigNames::ScriptPath ),
2173 'wgScript' => $conf->get( MainConfigNames::Script ),
2174 'wgSearchType' => $conf->get( MainConfigNames::SearchType ),
2175 'wgVariantArticlePath' => $conf->get( MainConfigNames::VariantArticlePath ),
2176 'wgServer' => $conf->get( MainConfigNames::Server ),
2177 'wgServerName' => $conf->get( MainConfigNames::ServerName ),
2178 'wgUserLanguage' => $context->getLanguage(),
2179 'wgContentLanguage' => $contLang->getCode(),
2180 'wgVersion' => MW_VERSION,
2181 'wgFormattedNamespaces' => $contLang->getFormattedNamespaces(),
2182 'wgNamespaceIds' => $namespaceIds,
2183 'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
2184 'wgSiteName' => $conf->get( MainConfigNames::Sitename ),
2185 'wgDBname' => $conf->get( MainConfigNames::DBname ),
2186 'wgWikiID' => WikiMap::getCurrentWikiId(),
2187 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
2188 'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
2189 'wgExtensionAssetsPath' => $conf->get( MainConfigNames::ExtensionAssetsPath ),
2190 ];
2191 // End of stable config vars.
2192
2193 // Internal variables for use by MediaWiki core and/or ResourceLoader.
2194 $vars += [
2195 // @internal For mediawiki.widgets
2196 'wgUrlProtocols' => $services->getUrlUtils()->validProtocols(),
2197 // @internal For mediawiki.page.watch
2198 // Force object to avoid "empty" associative array from
2199 // becoming [] instead of {} in JS (T36604)
2200 'wgActionPaths' => (object)$conf->get( MainConfigNames::ActionPaths ),
2201 // @internal For mediawiki.language
2202 'wgTranslateNumerals' => $conf->get( MainConfigNames::TranslateNumerals ),
2203 // @internal For mediawiki.Title
2204 'wgExtraSignatureNamespaces' => $conf->get( MainConfigNames::ExtraSignatureNamespaces ),
2205 'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
2206 'wgIllegalFileChars' => Title::convertByteClassToUnicodeClass( $illegalFileChars ),
2207 ];
2208
2209 ( new HookRunner( $services->getHookContainer() ) )
2210 ->onResourceLoaderGetConfigVars( $vars, $skin, $conf );
2211
2212 return $vars;
2213 }
2214
2219 public function getErrors() {
2220 return $this->errors;
2221 }
2222}
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:23
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
global $wgRequest
Definition Setup.php:434
const MW_ENTRY_POINT
Definition api.php:21
Handle database storage of comments such as edit summaries and log reasons.
Handler class for MWExceptions.
Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
A wrapper class which causes Html::encodeJsVar() and Html::encodeJsCall() (as well as their Xml::* co...
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
This is one of the Core classes and should be read at least once by any new developers.
Class for tracking request-level classification information for profiling/stats/logging.
Load JSON files, and uses a Processor to extract information.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
Context object that contains information about the state of a specific ResourceLoader web request.
Definition Context.php:32
encodeJson( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition Context.php:456
getImageObj()
If this is a request for an image, get the Image object.
Definition Context.php:339
This class generates message blobs for use by ResourceLoader.
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition Module.php:34
shouldEmbedModule(Context $context)
Check whether this module should be embedded rather than linked.
Definition Module.php:943
ResourceLoader is a loading system for JavaScript and CSS resources.
getLoadScript( $source)
Get the URL to the load.php endpoint for the given ResourceLoader source.
static makeComment( $text)
Generate a CSS or JS comment block.
isModuleRegistered( $name)
Check whether a ResourceLoader module is registered.
preloadModuleInfo(array $moduleNames, Context $context)
Load information stored in the database and dependency tracking store about modules.
setMessageBlobStore(MessageBlobStore $blobStore)
tryRespondNotModified(Context $context, $etag)
Respond with HTTP 304 Not Modified if appropriate.
static formatExceptionNoComment(Throwable $e)
Handle exception display.
measureResponseTime()
Send stats about the time used to build the response.
setDependencyStore(DependencyStore $tracker)
static makeHash( $value)
Create a hash for module versioning purposes.
array $errors
Errors accumulated during a respond() call.
sendResponseHeaders(Context $context, $etag, $errors)
Send main response headers to the client.
getTestSuiteModuleNames()
Get a list of modules with QUnit tests.
makeModuleResponse(Context $context, array $modules, array $missing=[])
Generate code for a response.
getModule( $name)
Get the Module object for a given module name.
__construct(Config $config, ?LoggerInterface $logger=null, ?DependencyStore $tracker=null, array $params=[])
setModuleSkinStyles(array $moduleSkinStyles)
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
respond(Context $context, array $extraHeaders=[])
Output a response to a load request, including the content-type header.
makeVersionQuery(Context $context, array $modules)
Get the expected value of the 'version' query parameter.
string[] $extraHeaders
Buffer for extra response headers during a makeModuleResponse() call.
getCombinedVersion(Context $context, array $moduleNames)
Helper method to get and combine versions of multiple modules.
addSource( $sources, $loadUrl=null)
Add a foreign source of modules.
Represents a title within MediaWiki.
Definition Title.php:69
Provides access to user options.
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
Track per-module dependency file paths that are expensive to mass compute.
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73
Store data in a memory for the current request/process only.
This is the primary interface for validating metrics definitions, caching defined metrics,...
getCounter(string $name)
Makes a new CounterMetric or fetches one from cache.
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'=>[], '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'=> false, '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, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'EnableSpecialMute'=> false, 'EnableUserEmailMuteList'=> false, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, '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, 'ImageLinksSchemaMigrationStage'=> 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',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], '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'=> '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,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, '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',],],], '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, ], '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, 'UseLegacyMediaStyles' => 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, ], ], '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, '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, ], '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, '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' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], '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, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => 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, '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, '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, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => 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', ], '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, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => 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, '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: ], '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' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], '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, ], '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, '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\\JobQueue\\Jobs\\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', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], '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, '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, 'UsePostprocCache' => false, 'ParserOptionsLogUnsafeSampleRate' => 0, ], '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', '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', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ImageLinksSchemaMigrationStage' => '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', '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', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', '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', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ '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', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => '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', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], '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', 'UsePostprocCache' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], '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', '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', ], ], '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', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], '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.', ],]
Interface for configuration instances.
Definition Config.php:18
$source