MediaWiki master
ResourceLoader.php
Go to the documentation of this file.
1<?php
10
11use Exception;
12use InvalidArgumentException;
13use Less_Environment;
14use Less_Parser;
33use Psr\Log\LoggerAwareInterface;
34use Psr\Log\LoggerInterface;
35use Psr\Log\NullLogger;
36use RuntimeException;
37use stdClass;
38use Throwable;
39use UnexpectedValueException;
41use Wikimedia\Minify\CSSMin;
42use Wikimedia\Minify\IdentityMinifierState;
43use Wikimedia\Minify\IndexMap;
44use Wikimedia\Minify\IndexMapOffset;
45use Wikimedia\Minify\JavaScriptMapperState;
46use Wikimedia\Minify\JavaScriptMinifier;
47use Wikimedia\Minify\JavaScriptMinifierState;
48use Wikimedia\Minify\MinifierState;
51use Wikimedia\RequestTimeout\TimeoutException;
52use Wikimedia\ScopedCallback;
54use Wikimedia\Timestamp\ConvertibleTimestamp;
55use Wikimedia\Timestamp\TimestampFormat as TS;
56use Wikimedia\WrappedString;
57
78class ResourceLoader implements LoggerAwareInterface {
80 public const CACHE_VERSION = 9;
81
83 private const MAXAGE_RECOVER = 60;
84
86 protected static $debugMode = null;
87
89 private $config;
91 private $blobStore;
93 private $depStore;
95 private $logger;
97 private $hookContainer;
99 private $srvCache;
101 private $statsFactory;
103 private $maxageVersioned;
105 private $maxageUnversioned;
106
108 private $modules = [];
110 private $moduleInfos = [];
112 private $testModuleNames = [];
114 private $sources = [];
116 protected $errors = [];
121 protected $extraHeaders = [];
126 private $moduleSkinStyles = [];
127
148 public function __construct(
149 Config $config,
150 ?LoggerInterface $logger = null,
151 ?DependencyStore $tracker = null,
152 array $params = []
153 ) {
154 $this->maxageVersioned = $params['maxageVersioned'] ?? 30 * 24 * 60 * 60;
155 $this->maxageUnversioned = $params['maxageUnversioned'] ?? 5 * 60;
156
157 $this->config = $config;
158 $this->logger = $logger ?? new NullLogger();
159
160 $services = MediaWikiServices::getInstance();
161 $this->hookContainer = $services->getHookContainer();
162
163 $this->srvCache = $services->getLocalServerObjectCache();
164 $this->statsFactory = $services->getStatsFactory();
165
166 // Add 'local' source first
167 $this->addSource( 'local', $params['loadScript'] ?? '/load.php' );
168
169 // Special module that always exists
170 $this->register( 'startup', [ 'class' => StartUpModule::class ] );
171
172 $this->setMessageBlobStore(
173 new MessageBlobStore( $this, $this->logger, $services->getMainWANObjectCache() )
174 );
175
176 $this->setDependencyStore( $tracker ?? new DependencyStore( new HashBagOStuff() ) );
177 }
178
182 public function getConfig() {
183 return $this->config;
184 }
185
190 public function setLogger( LoggerInterface $logger ): void {
191 $this->logger = $logger;
192 }
193
198 public function getLogger(): LoggerInterface {
199 return $this->logger;
200 }
201
206 public function getMessageBlobStore() {
207 return $this->blobStore;
208 }
209
214 public function setMessageBlobStore( MessageBlobStore $blobStore ) {
215 $this->blobStore = $blobStore;
216 }
217
222 public function setDependencyStore( DependencyStore $tracker ) {
223 $this->depStore = $tracker;
224 }
225
232 return $this->depStore;
233 }
234
239 public function setModuleSkinStyles( array $moduleSkinStyles ) {
240 $this->moduleSkinStyles = $moduleSkinStyles;
241 }
242
254 public function register( $name, ?array $info = null ) {
255 // Allow multiple modules to be registered in one call
256 $registrations = is_array( $name ) ? $name : [ $name => $info ];
257 foreach ( $registrations as $name => $info ) {
258 // Warn on duplicate registrations
259 if ( isset( $this->moduleInfos[$name] ) ) {
260 // A module has already been registered by this name
261 $this->logger->warning(
262 'ResourceLoader duplicate registration warning. ' .
263 'Another module has already been registered as ' . $name
264 );
265 }
266
267 // Check validity
268 if ( !self::isValidModuleName( $name ) ) {
269 throw new InvalidArgumentException( "ResourceLoader module name '$name' is invalid, "
270 . "see ResourceLoader::isValidModuleName()" );
271 }
272 if ( !is_array( $info ) ) {
273 throw new InvalidArgumentException(
274 'Invalid module info for "' . $name . '": expected array, got ' . get_debug_type( $info )
275 );
276 }
277
278 // Attach module
279 $this->moduleInfos[$name] = $info;
280 }
281 }
282
287 public function registerTestModules(): void {
288 $extRegistry = ExtensionRegistry::getInstance();
289 $testModules = $extRegistry->getAttribute( 'QUnitTestModule' );
290
291 $testModuleNames = [];
292 foreach ( $testModules as $name => &$module ) {
293 // Turn any single-module dependency into an array
294 if ( isset( $module['dependencies'] ) && is_string( $module['dependencies'] ) ) {
295 $module['dependencies'] = [ $module['dependencies'] ];
296 }
297
298 // Ensure the testrunner loads before any tests
299 $module['dependencies'][] = 'mediawiki.qunit-testrunner';
300
301 // Keep track of the modules to load on SpecialJavaScriptTest
302 $testModuleNames[] = $name;
303 }
304
305 // Core test modules (their names have further precedence).
306 $testModules = ( include MW_INSTALL_PATH . '/tests/qunit/QUnitTestResources.php' ) + $testModules;
307 $testModuleNames[] = 'test.MediaWiki';
308
309 $this->register( $testModules );
310 $this->testModuleNames = $testModuleNames;
311 }
312
323 public function addSource( $sources, $loadUrl = null ) {
324 if ( !is_array( $sources ) ) {
325 $sources = [ $sources => $loadUrl ];
326 }
327 foreach ( $sources as $id => $source ) {
328 // Disallow duplicates
329 if ( isset( $this->sources[$id] ) ) {
330 throw new RuntimeException( 'Cannot register source ' . $id . ' twice' );
331 }
332
333 // Support: MediaWiki 1.24 and earlier
334 if ( is_array( $source ) ) {
335 if ( !isset( $source['loadScript'] ) ) {
336 throw new InvalidArgumentException( 'Each source must have a "loadScript" key' );
337 }
338 $source = $source['loadScript'];
339 }
340
341 $this->sources[$id] = $source;
342 }
343 }
344
348 public function getModuleNames() {
349 return array_keys( $this->moduleInfos );
350 }
351
359 public function getTestSuiteModuleNames() {
360 return $this->testModuleNames;
361 }
362
370 public function isModuleRegistered( $name ) {
371 return isset( $this->moduleInfos[$name] );
372 }
373
385 public function getModule( $name ) {
386 if ( !isset( $this->modules[$name] ) ) {
387 if ( !isset( $this->moduleInfos[$name] ) ) {
388 // No such module
389 return null;
390 }
391 // Construct the requested module object
392 $info = $this->moduleInfos[$name];
393 if ( isset( $info['factory'] ) ) {
395 $object = $info['factory']( $info );
396 } else {
397 $class = $info['class'] ?? FileModule::class;
399 $object = new $class( $info );
400 }
401 $object->setConfig( $this->getConfig() );
402 $object->setLogger( $this->logger );
403 $object->setHookContainer( $this->hookContainer );
404 $object->setName( $name );
405 $object->setSkinStylesOverride( $this->moduleSkinStyles );
406 $this->modules[$name] = $object;
407 }
408
409 return $this->modules[$name];
410 }
411
418 public function preloadModuleInfo( array $moduleNames, Context $context ) {
419 // Load all tracked indirect file dependencies for the modules
420 $vary = Module::getVary( $context );
421 $entitiesByModule = [];
422 foreach ( $moduleNames as $moduleName ) {
423 $entitiesByModule[$moduleName] = "$moduleName|$vary";
424 }
425 $depsByEntity = $this->depStore->retrieveMulti(
426 $entitiesByModule
427 );
428
429 $modulesWithMessages = [];
430
431 // Inject the indirect file dependencies for all the modules
432 foreach ( $moduleNames as $moduleName ) {
433 $module = $this->getModule( $moduleName );
434 if ( $module ) {
435 $entity = $entitiesByModule[$moduleName];
436 $deps = $depsByEntity[$entity];
437 $paths = $deps['paths'];
438 $module->setFileDependencies( $context, $paths );
439
440 if ( $module->getMessages() ) {
441 $modulesWithMessages[$moduleName] = $module;
442 }
443 }
444 }
445
446 WikiModule::preloadTitleInfo( $context, $moduleNames );
447
448 // Prime in-object cache for message blobs for modules with messages
449 if ( $modulesWithMessages ) {
450 $lang = $context->getLanguage();
451 $store = $this->getMessageBlobStore();
452 $blobs = $store->getBlobs( $modulesWithMessages, $lang );
453 foreach ( $blobs as $moduleName => $blob ) {
454 $modulesWithMessages[$moduleName]->setMessageBlob( $blob, $lang );
455 }
456 }
457 }
458
464 public function getSources() {
465 return $this->sources;
466 }
467
476 public function getLoadScript( $source ) {
477 if ( !isset( $this->sources[$source] ) ) {
478 throw new UnexpectedValueException( "Unknown source '$source'" );
479 }
480 return $this->sources[$source];
481 }
482
486 public const HASH_LENGTH = 5;
487
550 public static function makeHash( $value ) {
551 $hash = hash( 'fnv132', $value );
552 // The base_convert will pad it (if too short),
553 // then substr() will trim it (if too long).
554 return substr(
555 \Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
556 0,
557 self::HASH_LENGTH
558 );
559 }
560
570 public function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
571 MWExceptionHandler::logException( $e );
572 $this->logger->warning(
573 $msg,
574 $context + [ 'exception' => $e ]
575 );
576 $this->errors[] = self::formatExceptionNoComment( $e );
577 }
578
587 public function getCombinedVersion( Context $context, array $moduleNames ) {
588 if ( !$moduleNames ) {
589 return '';
590 }
591 $hashes = [];
592 foreach ( $moduleNames as $module ) {
593 try {
594 $hash = $this->getModule( $module )->getVersionHash( $context );
595 } catch ( TimeoutException $e ) {
596 throw $e;
597 } catch ( Exception $e ) {
598 // If modules fail to compute a version, don't fail the request (T152266)
599 // and still compute versions of other modules.
600 $this->outputErrorAndLog( $e,
601 'Calculating version for "{module}" failed: {exception}',
602 [
603 'module' => $module,
604 ]
605 );
606 $hash = '';
607 }
608 $hashes[] = $hash;
609 }
610 return self::makeHash( implode( '', $hashes ) );
611 }
612
627 public function makeVersionQuery( Context $context, array $modules ) {
628 // As of MediaWiki 1.28, the server and client use the same algorithm for combining
629 // version hashes. There is no technical reason for this to be same, and for years the
630 // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
631 // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
632 // query parameter), then this method must continue to match the JS one.
633 $filtered = [];
634 foreach ( $modules as $name ) {
635 if ( !$this->getModule( $name ) ) {
636 // If a versioned request contains a missing module, the version is a mismatch
637 // as the client considered a module (and version) we don't have.
638 return '';
639 }
640 $filtered[] = $name;
641 }
642 return $this->getCombinedVersion( $context, $filtered );
643 }
644
652 public function respond( Context $context, array $extraHeaders = [] ) {
653 // Buffer output to catch warnings. Normally we'd use ob_clean() on the
654 // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
655 // is used: ob_clean() will clear the GZIP header in that case and it won't come
656 // back for subsequent output, resulting in invalid GZIP. So we have to wrap
657 // the whole thing in our own output buffer to be sure the active buffer
658 // doesn't use ob_gzhandler.
659 // See https://bugs.php.net/bug.php?id=36514
660 ob_start();
661
662 $this->errors = [];
663 $this->extraHeaders = $extraHeaders;
664 $responseTime = $this->measureResponseTime();
665 ProfilingContext::singleton()->init( MW_ENTRY_POINT, 'respond' );
666
667 // Find out which modules are missing and instantiate the others
668 $modules = [];
669 $missing = [];
670 foreach ( $context->getModules() as $name ) {
671 $module = $this->getModule( $name );
672 if ( $module ) {
673 // Do not allow private modules to be loaded from the web.
674 // This is a security issue, see T36907.
675 if ( $module->getGroup() === Module::GROUP_PRIVATE ) {
676 // Not a serious error, just means something is trying to access it (T101806)
677 $this->logger->debug( "Request for private module '$name' denied" );
678 $this->errors[] = "Cannot build private module \"$name\"";
679 continue;
680 }
681 $modules[$name] = $module;
682 } else {
683 $missing[] = $name;
684 }
685 }
686
687 try {
688 // Preload for getCombinedVersion() and for batch makeModuleResponse()
689 $this->preloadModuleInfo( array_keys( $modules ), $context );
690 } catch ( TimeoutException $e ) {
691 throw $e;
692 } catch ( Exception $e ) {
693 $this->outputErrorAndLog( $e, 'Preloading module info failed: {exception}' );
694 }
695
696 // Combine versions to propagate cache invalidation
697 $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
698
699 // See RFC 2616 § 3.11 Entity Tags
700 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
701 $etag = 'W/"' . $versionHash . '"';
702
703 // Try the client-side cache first
704 if ( $this->tryRespondNotModified( $context, $etag ) ) {
705 return; // output handled (buffers cleared)
706 }
707
708 if ( $context->isSourceMap() ) {
709 // In source map mode, a version mismatch should be a 404
710 if ( $context->getVersion() !== null && $versionHash !== $context->getVersion() ) {
711 ob_end_clean();
712 $this->sendSourceMapVersionMismatch( $versionHash );
713 return;
714 }
715 // No source maps for images, only=styles requests, or debug mode
716 if ( $context->getImage()
717 || $context->getOnly() === 'styles'
718 || $context->getDebug()
719 ) {
720 ob_end_clean();
721 $this->sendSourceMapTypeNotImplemented();
722 return;
723 }
724 }
725 // Emit source map header if supported (inverse of the above check)
726 if ( $this->config->get( MainConfigNames::ResourceLoaderEnableSourceMapLinks )
727 && !$context->getImageObj()
728 && !$context->isSourceMap()
729 && $context->shouldIncludeScripts()
730 && !$context->getDebug()
731 ) {
732 $this->extraHeaders[] = 'SourceMap: ' . $this->getSourceMapUrl( $context, $versionHash );
733 }
734
735 // Generate a response
736 $response = $this->makeModuleResponse( $context, $modules, $missing );
737
738 // Capture any PHP warnings from the output buffer and append them to the
739 // error list if we're in debug mode.
740 if ( $context->getDebug() ) {
741 $warnings = ob_get_contents();
742 if ( $warnings !== false && $warnings !== '' ) {
743 $this->errors[] = $warnings;
744 }
745 }
746
747 // Use an alternate E-Tag so that HTTP caches self-correct after an error (T431583).
748 //
749 // Usually when a module is broken, ResourceLoader sends a partial response with the rest
750 // of the batch. The mw.loader client isolates dependency trees such that errors often go unnoticed.
751 // The combined version hash skips broken modules and so the future response with the fixed
752 // module naturally has different E-Tag and the cache self-corrects. But, if
753 // Module::getVersionHash suceeeds and only Module::getVersionHash fails, then the error
754 // response could be renewed via HTTP 304 after the error is fixed. This prevents that.
755 if ( $this->errors ) {
756 $etag = 'W/"' . $versionHash . '_with_errors"';
757 }
758
759 $this->sendResponseHeaders( $context, $etag, (bool)$this->errors );
760
761 // Remove the output buffer and output the response
762 ob_end_clean();
763
764 if ( $context->getImageObj() && $this->errors ) {
765 // We can't show both the error messages and the response when it's an image.
766 $response = implode( "\n\n", $this->errors );
767 } elseif ( $this->errors ) {
768 $errorText = implode( "\n\n", $this->errors );
769 $errorResponse = self::makeComment( $errorText );
770 if ( $context->shouldIncludeScripts() ) {
771 $errorResponse .= 'if (window.console && console.error) { console.error('
772 . $context->encodeJson( $errorText )
773 . "); }\n";
774 // Append the error info to the response
775 // We used to prepend it, but that would corrupt the source map
776 $response .= $errorResponse;
777 } else {
778 // For styles we can still prepend
779 $response = $errorResponse . $response;
780 }
781 }
782
783 // @phan-suppress-next-line SecurityCheck-XSS
784 echo $response;
785 }
786
790 #[\NoDiscard]
791 protected function measureResponseTime(): ScopedCallback {
792 $requestStart = $_SERVER['REQUEST_TIME_FLOAT'];
793 return new ScopedCallback( function () use ( $requestStart ) {
794 $statTiming = microtime( true ) - $requestStart;
795
796 $this->statsFactory->getTiming( 'resourceloader_response_time_seconds' )
797 ->observe( 1000 * $statTiming );
798 } );
799 }
800
810 protected function sendResponseHeaders(
811 Context $context, $etag, $errors
812 ): void {
813 HeaderCallback::warnIfHeadersSent();
814
815 if ( $errors ) {
816 $maxage = self::MAXAGE_RECOVER;
817 } elseif (
818 $context->getVersion() !== null
819 && $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
820 ) {
821 // If we need to self-correct, set a very short cache expiry
822 // to basically just debounce CDN traffic. This applies to:
823 // - Internal errors, e.g. due to misconfiguration.
824 // - Version mismatch, e.g. due to deployment race (T117587, T47877).
825 $this->logger->debug( 'Client and server registry version out of sync' );
826 $maxage = self::MAXAGE_RECOVER;
827 } elseif ( $context->getVersion() === null ) {
828 // Resources that can't set a version, should have their updates propagate to
829 // clients quickly. This applies to shared resources linked from HTML, such as
830 // the startup module and stylesheets.
831 $maxage = $this->maxageUnversioned;
832 } else {
833 // When a version is set, use a long expiry because changes
834 // will naturally miss the cache by using a different URL.
835 $maxage = $this->maxageVersioned;
836 }
837 if ( $context->getImageObj() ) {
838 // Output different headers if we're outputting textual errors.
839 if ( $errors ) {
840 header( 'Content-Type: text/plain; charset=utf-8' );
841 } else {
842 $context->getImageObj()->sendResponseHeaders( $context );
843 }
844 } elseif ( $context->isSourceMap() ) {
845 header( 'Content-Type: application/json' );
846 } elseif ( $context->getOnly() === 'styles' ) {
847 header( 'Content-Type: text/css; charset=utf-8' );
848 header( 'Access-Control-Allow-Origin: *' );
849 } else {
850 header( 'Content-Type: text/javascript; charset=utf-8' );
851 }
852 // See RFC 2616 § 14.19 ETag
853 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
854 header( 'ETag: ' . $etag );
855 if ( $context->getDebug() ) {
856 // Do not cache debug responses
857 header( 'Cache-Control: private, no-cache, must-revalidate' );
858 } else {
859 // T132418: When a resource expires mid-way a browsing session, prefer to renew it in
860 // the background instead of blocking the next page load (eg. startup module, or CSS).
861 $staleDirective = ( $maxage > self::MAXAGE_RECOVER
862 ? ", stale-while-revalidate=" . min( 60, intval( $maxage / 2 ) )
863 : ''
864 );
865 header( "Cache-Control: public, max-age=$maxage, s-maxage=$maxage" . $staleDirective );
866 header( 'Expires: ' . ConvertibleTimestamp::convert( TS::RFC2822, time() + $maxage ) );
867 }
868
869 foreach ( $this->extraHeaders as $header ) {
870 header( $header );
871 }
872 }
873
884 protected function tryRespondNotModified( Context $context, $etag ) {
885 // See RFC 2616 § 14.26 If-None-Match
886 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
887 $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
888 // Never send 304s in debug mode
889 if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
890 // There's another bug in ob_gzhandler (see also the comment at
891 // the top of this function) that causes it to gzip even empty
892 // responses, meaning it's impossible to produce a truly empty
893 // response (because the gzip header is always there). This is
894 // a problem because 304 responses have to be completely empty
895 // per the HTTP spec, and Firefox behaves buggily when they're not.
896 // See also https://bugs.php.net/bug.php?id=51579
897 // To work around this, we tear down all output buffering before
898 // sending the 304.
899 wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
900
901 HttpStatus::header( 304 );
902 $this->sendResponseHeaders( $context, $etag, false );
903 return true;
904 }
905 return false;
906 }
907
915 private function getSourceMapUrl( Context $context, $version ) {
916 return $this->createLoaderURL( 'local', $context, [
917 'sourcemap' => '1',
918 'version' => $version
919 ] );
920 }
921
927 private function sendSourceMapVersionMismatch( $currentVersion ) {
928 HttpStatus::header( 404 );
929 header( 'Content-Type: text/plain; charset=utf-8' );
930 header( 'X-Content-Type-Options: nosniff' );
931 echo "Can't deliver a source map for the requested version " .
932 "since the version is now '$currentVersion'\n";
933 }
934
939 private function sendSourceMapTypeNotImplemented() {
940 HttpStatus::header( 404 );
941 header( 'Content-Type: text/plain; charset=utf-8' );
942 header( 'X-Content-Type-Options: nosniff' );
943 echo "Can't make a source map for this content type\n";
944 }
945
954 public static function makeComment( $text ) {
955 $encText = str_replace( '*/', '* /', $text );
956 return "/*\n$encText\n*/\n";
957 }
958
966 protected static function formatExceptionNoComment( Throwable $e ) {
967 if ( !MWExceptionRenderer::shouldShowExceptionDetails() ) {
968 return MWExceptionHandler::getPublicLogMessage( $e );
969 }
970
971 // Like MWExceptionHandler::getLogMessage but without $url and $id.
972 // - Long load.php URL would push the actual error message off-screen into
973 // scroll overflow in browser devtools.
974 // - reqId is redundant with X-Request-Id header, plus usually no need to
975 // correlate the reqId since the backtrace is already included below.
976 $type = get_class( $e );
977 $message = $e->getMessage();
978
979 return "$type: $message" .
980 "\nBacktrace:\n" .
981 MWExceptionHandler::getRedactedTraceAsString( $e );
982 }
983
995 public function makeModuleResponse( Context $context,
996 array $modules, array $missing = []
997 ) {
998 if ( $modules === [] && $missing === [] ) {
999 return <<<MESSAGE
1000/* This file is the Web entry point for MediaWiki's ResourceLoader:
1001 <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
1002 no modules were requested. Max made me put this here. */
1003MESSAGE;
1004 }
1005
1006 $image = $context->getImageObj();
1007 if ( $image ) {
1008 $data = $image->getImageData( $context );
1009 if ( $data === false ) {
1010 $data = '';
1011 $this->errors[] = 'Image generation failed';
1012 }
1013 return $data;
1014 }
1015
1016 $states = [];
1017 foreach ( $missing as $name ) {
1018 $states[$name] = 'missing';
1019 }
1020
1021 $only = $context->getOnly();
1022 $debug = (bool)$context->getDebug();
1023 if ( $context->isSourceMap() && count( $modules ) > 1 ) {
1024 $indexMap = new IndexMap;
1025 } else {
1026 $indexMap = null;
1027 }
1028
1029 $out = '';
1030 foreach ( $modules as $name => $module ) {
1031 try {
1032 [ $response, $offset ] = $this->getOneModuleResponse( $context, $name, $module );
1033 if ( $indexMap ) {
1034 $indexMap->addEncodedMap( $response, $offset );
1035 } else {
1036 $out .= $response;
1037 }
1038 } catch ( TimeoutException $e ) {
1039 throw $e;
1040 } catch ( Exception $e ) {
1041 $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1042
1043 // Respond to client with error-state instead of module implementation
1044 $states[$name] = 'error';
1045 unset( $modules[$name] );
1046 }
1047 }
1048
1049 // Update module states
1050 if ( $context->shouldIncludeScripts() && !$context->getRaw() ) {
1051 if ( $modules && $only === 'scripts' ) {
1052 // Set the state of modules loaded as only scripts to ready as
1053 // they don't have an mw.loader.impl wrapper that sets the state
1054 foreach ( $modules as $name => $module ) {
1055 $states[$name] = 'ready';
1056 }
1057 }
1058
1059 // Set the state of modules we didn't respond to with mw.loader.impl
1060 if ( $states && !$context->isSourceMap() ) {
1061 $stateScript = self::makeLoaderStateScript( $context, $states );
1062 if ( !$debug ) {
1063 $stateScript = self::filter( 'minify-js', $stateScript );
1064 }
1065 // Use a linebreak between module script and state script (T162719)
1066 $out = self::ensureNewline( $out ) . $stateScript;
1067 }
1068 } elseif ( $states ) {
1069 $this->errors[] = 'Problematic modules: '
1070 // Silently ignore invalid UTF-8 injected via 'modules' query
1071 // Don't issue server-side warnings for client errors. (T331641)
1072 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1073 . @$context->encodeJson( $states );
1074 }
1075
1076 if ( $indexMap ) {
1077 return $indexMap->getMap();
1078 }
1079 return $out;
1080 }
1081
1090 private function getOneModuleResponse( Context $context, $name, Module $module ) {
1091 $only = $context->getOnly();
1092 // Important: Do not cache minifications of embedded modules
1093 // This is especially for the private 'user.options' module,
1094 // which varies on every pageview and would explode the cache (T84960)
1095 $shouldCache = !$module->shouldEmbedModule( $context );
1096 if ( $only === 'styles' ) {
1097 $minifier = new IdentityMinifierState;
1098 $this->addOneModuleResponse( $context, $minifier, $name, $module, $this->extraHeaders );
1099 // NOTE: This is not actually "minified". IdentityMinifierState is a no-op wrapper
1100 // to ease code reuse. The filter() call below performs CSS minification.
1101 $styles = $minifier->getMinifiedOutput();
1102 if ( $context->getDebug() ) {
1103 return [ $styles, null ];
1104 }
1105 return [
1106 self::filter( 'minify-css', $styles,
1107 [ 'cache' => $shouldCache ] ),
1108 null
1109 ];
1110 }
1111
1112 $replayMinifier = new ReplayMinifierState;
1113 $this->addOneModuleResponse( $context, $replayMinifier, $name, $module, $this->extraHeaders );
1114
1115 $minifier = new IdentityMinifierState;
1116 $replayMinifier->replayOn( $minifier );
1117 $plainContent = $minifier->getMinifiedOutput();
1118 if ( $context->getDebug() ) {
1119 return [ $plainContent, null ];
1120 }
1121
1122 $isHit = true;
1123 $callback = function () use ( $context, $replayMinifier, &$isHit ) {
1124 $isHit = false;
1125 if ( $context->isSourceMap() ) {
1126 $minifier = ( new JavaScriptMapperState )
1127 ->outputFile( $this->createLoaderURL( 'local', $context, [
1128 'modules' => self::makePackedModulesString( $context->getModules() ),
1129 'only' => $context->getOnly()
1130 ] ) );
1131 } else {
1132 $minifier = new JavaScriptMinifierState;
1133 }
1134 $replayMinifier->replayOn( $minifier );
1135 if ( $context->isSourceMap() ) {
1136 $sourceMap = $minifier->getRawSourceMap();
1137 $generated = $minifier->getMinifiedOutput();
1138 $offset = IndexMapOffset::newFromText( $generated );
1139 return [ $sourceMap, $offset->toArray() ];
1140 } else {
1141 return [ $minifier->getMinifiedOutput(), null ];
1142 }
1143 };
1144
1145 // The below is based on ResourceLoader::filter. Keep together to ease review/maintenance:
1146 // * Handle $shouldCache, skip cache and minify directly if set.
1147 // * Use minify cache, minify on-demand and populate cache as needed.
1148 // * Emit resourceloader_cache_total stats.
1149
1150 if ( $shouldCache ) {
1151 [ $response, $offsetArray ] = $this->srvCache->getWithSetCallback(
1152 $this->srvCache->makeGlobalKey(
1153 'resourceloader-mapped',
1154 self::CACHE_VERSION,
1155 $name,
1156 $context->isSourceMap() ? '1' : '0',
1157 md5( $plainContent )
1158 ),
1159 BagOStuff::TTL_DAY,
1160 $callback
1161 );
1162
1163 $mapType = $context->isSourceMap() ? 'map-js' : 'minify-js';
1164 $this->statsFactory->getCounter( 'resourceloader_cache_total' )
1165 ->setLabel( 'type', $mapType )
1166 ->setLabel( 'status', $isHit ? 'hit' : 'miss' )
1167 ->increment();
1168 } else {
1169 [ $response, $offsetArray ] = $callback();
1170 }
1171 $offset = $offsetArray ? IndexMapOffset::newFromArray( $offsetArray ) : null;
1172
1173 return [ $response, $offset ];
1174 }
1175
1186 private function addOneModuleResponse(
1187 Context $context, MinifierState $minifier, $name, Module $module, &$headers
1188 ) {
1189 $only = $context->getOnly();
1190 $debug = (bool)$context->getDebug();
1191 $content = $module->getModuleContent( $context );
1192 $version = $module->getVersionHash( $context );
1193
1194 if ( $headers !== null && isset( $content['headers'] ) ) {
1195 $headers = array_merge( $headers, $content['headers'] );
1196 }
1197
1198 // Append output
1199 switch ( $only ) {
1200 case 'scripts':
1201 $scripts = $content['scripts'];
1202 if ( !is_array( $scripts ) ) {
1203 // Formerly scripts was usually a string, but now it is
1204 // normalized to an array by buildContent().
1205 throw new InvalidArgumentException( 'scripts must be an array' );
1206 }
1207 if ( isset( $scripts['plainScripts'] ) ) {
1208 // Add plain scripts
1209 $this->addPlainScripts( $minifier, $name, $scripts['plainScripts'] );
1210 } elseif ( isset( $scripts['files'] ) ) {
1211 // Add implement call if any
1212 $this->addImplementScript(
1213 $minifier,
1214 $name,
1215 $version,
1216 $scripts,
1217 [],
1218 null,
1219 [],
1220 $content['deprecationWarning'] ?? null
1221 );
1222 }
1223 break;
1224 case 'styles':
1225 $styles = $content['styles'];
1226 // We no longer separate into media, they are all combined now with
1227 // custom media type groups into @media .. {} sections as part of the css string.
1228 // Module returns either an empty array or a numerical array with css strings.
1229 if ( isset( $styles['css'] ) ) {
1230 $minifier->addOutput( implode( '', $styles['css'] ) );
1231 }
1232 break;
1233 default:
1234 $scripts = $content['scripts'] ?? '';
1235 if ( ( $name === 'site' || $name === 'user' )
1236 && isset( $scripts['plainScripts'] )
1237 ) {
1238 // Legacy scripts that run in the global scope without a closure.
1239 // mw.loader.impl will use eval if scripts is a string.
1240 // Minify manually here, because general response minification is
1241 // not effective due it being a string literal, not a function.
1242 $scripts = self::concatenatePlainScripts( $scripts['plainScripts'] );
1243 if ( !$debug ) {
1244 $scripts = self::filter( 'minify-js', $scripts ); // T107377
1245 }
1246 }
1247 $this->addImplementScript(
1248 $minifier,
1249 $name,
1250 $version,
1251 $scripts,
1252 $content['styles'] ?? [],
1253 isset( $content['messagesBlob'] ) ? new HtmlJsCode( $content['messagesBlob'] ) : null,
1254 $content['templates'] ?? [],
1255 $content['deprecationWarning'] ?? null
1256 );
1257 break;
1258 }
1259 $minifier->ensureNewline();
1260 }
1261
1268 public static function ensureNewline( $str ) {
1269 $end = substr( $str, -1 );
1270 if ( $end === '' || $end === "\n" ) {
1271 return $str;
1272 }
1273 return $str . "\n";
1274 }
1275
1282 public function getModulesByMessage( $messageKey ) {
1283 $moduleNames = [];
1284 foreach ( $this->getModuleNames() as $moduleName ) {
1285 $module = $this->getModule( $moduleName );
1286 if ( in_array( $messageKey, $module->getMessages() ) ) {
1287 $moduleNames[] = $moduleName;
1288 }
1289 }
1290 return $moduleNames;
1291 }
1292
1314 private function addImplementScript( MinifierState $minifier,
1315 $moduleName, $version, $scripts, $styles, $messages, $templates, $deprecationWarning
1316 ) {
1317 $implementKey = "$moduleName@$version";
1318 // Plain functions are used instead of arrow functions to avoid
1319 // defeating lazy compilation on Chrome. (T343407)
1320 $minifier->addOutput( "mw.loader.impl(function(){return[" .
1321 Html::encodeJsVar( $implementKey ) . "," );
1322
1323 // Scripts
1324 if ( is_string( $scripts ) ) {
1325 // user/site script
1326 $minifier->addOutput( Html::encodeJsVar( $scripts ) );
1327 } elseif ( is_array( $scripts ) ) {
1328 if ( isset( $scripts['files'] ) ) {
1329 $minifier->addOutput(
1330 "{\"main\":" .
1331 Html::encodeJsVar( $scripts['main'] ) .
1332 ",\"files\":" );
1333 $this->addFiles( $minifier, $moduleName, $scripts['files'] );
1334 $minifier->addOutput( "}" );
1335 } elseif ( isset( $scripts['plainScripts'] ) ) {
1336 if ( $this->isEmptyFileInfos( $scripts['plainScripts'] ) ) {
1337 $minifier->addOutput( 'null' );
1338 } else {
1339 $minifier->addOutput( "function($,jQuery,require,module){" );
1340 $this->addPlainScripts( $minifier, $moduleName, $scripts['plainScripts'] );
1341 $minifier->addOutput( "}" );
1342 }
1343 } elseif ( $scripts === [] || isset( $scripts[0] ) ) {
1344 // Array of URLs
1345 $minifier->addOutput( Html::encodeJsVar( $scripts ) );
1346 } else {
1347 throw new InvalidArgumentException( 'Invalid script array: ' .
1348 'must contain files, plainScripts or be an array of URLs' );
1349 }
1350 } else {
1351 throw new InvalidArgumentException( 'Script must be a string or array' );
1352 }
1353
1354 // mw.loader.impl requires 'styles', 'messages' and 'templates' to be objects (not
1355 // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1356 // of "{}". Force them to objects.
1357 $extraArgs = [
1358 (object)$styles,
1359 $messages ?? (object)[],
1360 (object)$templates,
1361 $deprecationWarning
1362 ];
1363 self::trimArray( $extraArgs );
1364 foreach ( $extraArgs as $arg ) {
1365 $minifier->addOutput( ',' . Html::encodeJsVar( $arg ) );
1366 }
1367 $minifier->addOutput( "];});" );
1368 }
1369
1380 private function addFiles( MinifierState $minifier, $moduleName, $files ) {
1381 $first = true;
1382 $minifier->addOutput( "{" );
1383 foreach ( $files as $fileName => $file ) {
1384 if ( $first ) {
1385 $first = false;
1386 } else {
1387 $minifier->addOutput( "," );
1388 }
1389 $minifier->addOutput( Html::encodeJsVar( $fileName ) . ':' );
1390 $this->addFileContent( $minifier, $moduleName, 'packageFile', $fileName, $file );
1391 }
1392 $minifier->addOutput( "}" );
1393 }
1394
1404 private function addFileContent( MinifierState $minifier,
1405 $moduleName, $sourceType, $sourceIndex, array $file
1406 ) {
1407 $isScript = ( $file['type'] ?? 'script' ) === 'script';
1409 $filePath = $file['filePath'] ?? $file['virtualFilePath'] ?? null;
1410 if ( $filePath !== null && $filePath->getRemoteBasePath() !== null ) {
1411 $url = $filePath->getRemotePath();
1412 } else {
1413 $ext = $isScript ? 'js' : 'json';
1414 $scriptPath = $this->config->has( MainConfigNames::ScriptPath )
1415 ? $this->config->get( MainConfigNames::ScriptPath ) : '';
1416 $url = "$scriptPath/virtual-resource/$moduleName-$sourceType-$sourceIndex.$ext";
1417 }
1418 $content = $file['content'];
1419 if ( $isScript ) {
1420 if ( $sourceType === 'packageFile' ) {
1421 // Provide CJS `exports` (in addition to CJS2 `module.exports`) to package modules (T284511).
1422 // $/jQuery are simply used as globals instead.
1423 // TODO: Remove $/jQuery param from traditional module closure too (and bump caching)
1424 $minifier->addOutput( "function(require,module,exports){" );
1425 $minifier->addSourceFile( $url, $content, true );
1426 $minifier->ensureNewline();
1427 $minifier->addOutput( "}" );
1428 } else {
1429 $minifier->addSourceFile( $url, $content, true );
1430 $minifier->ensureNewline();
1431 }
1432 } else {
1433 $content = Html::encodeJsVar( $content, true );
1434 $minifier->addSourceFile( $url, $content, true );
1435 }
1436 }
1437
1445 private static function concatenatePlainScripts( $plainScripts ) {
1446 $s = '';
1447 foreach ( $plainScripts as $script ) {
1448 // Make the script safe to concatenate by making sure there is at least one
1449 // trailing new line at the end of the content (T29054, T162719)
1450 $s .= self::ensureNewline( $script['content'] );
1451 }
1452 return $s;
1453 }
1454
1463 private function addPlainScripts( MinifierState $minifier, $moduleName, $plainScripts ) {
1464 foreach ( $plainScripts as $index => $file ) {
1465 $this->addFileContent( $minifier, $moduleName, 'script', $index, $file );
1466 }
1467 }
1468
1475 private function isEmptyFileInfos( $infos ) {
1476 $len = 0;
1477 foreach ( $infos as $info ) {
1478 $len += strlen( $info['content'] ?? '' );
1479 }
1480 return $len === 0;
1481 }
1482
1491 public static function makeCombinedStyles( array $stylePairs, WebRequest $request ) {
1492 $out = [];
1493 foreach ( $stylePairs as $media => $styles ) {
1494 // FileModule::getStyle can return the styles as a string or an
1495 // array of strings. This is to allow separation in the front-end.
1496 $styles = (array)$styles;
1497 foreach ( $styles as $style ) {
1498 $style = trim( $style );
1499 // Don't output an empty "@media print { }" block (T42498)
1500 if ( $style === '' ) {
1501 continue;
1502 }
1503 // Transform the media type based on request params and config
1504 // The way that this relies on $wgRequest to propagate request params is slightly evil
1505 $media = OutputPage::transformCssMedia( $media, $request );
1506
1507 if ( $media === '' || $media == 'all' ) {
1508 $out[] = $style;
1509 } elseif ( is_string( $media ) ) {
1510 $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1511 }
1512 // else: skip
1513 }
1514 }
1515 return $out;
1516 }
1517
1526 public static function makeLoaderStateScript(
1527 Context $context, array $states
1528 ) {
1529 return 'mw.loader.state('
1530 // Silently ignore invalid UTF-8 injected via 'modules' query
1531 // Don't issue server-side warnings for client errors. (T331641)
1532 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1533 . @$context->encodeJson( $states )
1534 . ');';
1535 }
1536
1537 private static function isEmptyObject( stdClass $obj ): bool {
1538 foreach ( $obj as $value ) {
1539 return false;
1540 }
1541 return true;
1542 }
1543
1555 private static function trimArray( array &$array ): void {
1556 $i = count( $array );
1557 while ( $i-- ) {
1558 if ( $array[$i] === null
1559 || $array[$i] === []
1560 || ( $array[$i] instanceof HtmlJsCode && $array[$i]->value === '{}' )
1561 || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1562 ) {
1563 unset( $array[$i] );
1564 } else {
1565 break;
1566 }
1567 }
1568 }
1569
1595 public static function makeLoaderRegisterScript(
1596 Context $context, array $modules
1597 ) {
1598 // Optimisation: Transform dependency names into indexes when possible
1599 // to produce smaller output. They are expanded by mw.loader.register on
1600 // the other end.
1601 $index = [];
1602 foreach ( $modules as $i => $module ) {
1603 // Build module name index
1604 $index[$module[0]] = $i;
1605 }
1606 foreach ( $modules as &$module ) {
1607 if ( isset( $module[2] ) ) {
1608 foreach ( $module[2] as &$dependency ) {
1609 if ( isset( $index[$dependency] ) ) {
1610 // Replace module name in dependency list with index
1611 $dependency = $index[$dependency];
1612 }
1613 }
1614 }
1615 self::trimArray( $module );
1616 }
1617
1618 return 'mw.loader.register('
1619 . $context->encodeJson( $modules )
1620 . ');';
1621 }
1622
1636 public static function makeLoaderSourcesScript(
1637 Context $context, array $sources
1638 ) {
1639 return 'mw.loader.addSource('
1640 . $context->encodeJson( $sources )
1641 . ');';
1642 }
1643
1650 public static function makeLoaderConditionalScript( $script ) {
1651 // Adds a function to lazy-created RLQ
1652 return '(RLQ=window.RLQ||[]).push(function(){' .
1653 trim( $script ) . '});';
1654 }
1655
1664 public static function makeInlineCodeWithModule( $modules, $script ) {
1665 // Adds an array to lazy-created RLQ
1666 return '(RLQ=window.RLQ||[]).push(['
1667 . json_encode( $modules ) . ','
1668 . 'function(){' . trim( $script ) . '}'
1669 . ']);';
1670 }
1671
1682 public static function makeInlineScript( $script, $nonce = null ) {
1683 $js = self::makeLoaderConditionalScript( $script );
1684 return new WrappedString(
1685 Html::inlineScript( $js ),
1686 "<script>(RLQ=window.RLQ||[]).push(function(){",
1687 '});</script>'
1688 );
1689 }
1690
1704 public static function makePackedModulesString( array $modules ) {
1705 $moduleMap = []; // [ prefix => [ suffixes ] ]
1706 foreach ( $modules as $module ) {
1707 $pos = strrpos( $module, '.' );
1708 $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1709 $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1710 $moduleMap[$prefix][] = $suffix;
1711 }
1712
1713 $arr = [];
1714 foreach ( $moduleMap as $prefix => $suffixes ) {
1715 $p = $prefix === '' ? '' : $prefix . '.';
1716 $arr[] = $p . implode( ',', $suffixes );
1717 }
1718 return implode( '|', $arr );
1719 }
1720
1732 public static function expandModuleNames( $modules ) {
1733 $retval = [];
1734 $exploded = explode( '|', $modules );
1735 foreach ( $exploded as $group ) {
1736 if ( !str_contains( $group, ',' ) ) {
1737 // This is not a set of modules in foo.bar,baz notation
1738 // but a single module
1739 $retval[] = $group;
1740 continue;
1741 }
1742 // This is a set of modules in foo.bar,baz notation
1743 $pos = strrpos( $group, '.' );
1744 if ( $pos === false ) {
1745 // Prefixless modules, i.e. without dots
1746 $retval = array_merge( $retval, explode( ',', $group ) );
1747 continue;
1748 }
1749 // We have a prefix and a bunch of suffixes
1750 $prefix = substr( $group, 0, $pos ); // 'foo'
1751 $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // [ 'bar', 'baz' ]
1752 foreach ( $suffixes as $suffix ) {
1753 $retval[] = "$prefix.$suffix";
1754 }
1755 }
1756 return $retval;
1757 }
1758
1770 public static function inDebugMode() {
1771 wfDeprecated( __METHOD__, '1.47' );
1772 if ( self::$debugMode === null ) {
1773 $resourceLoaderDebug = MediaWikiServices::getInstance()->getMainConfig()->get(
1774 MainConfigNames::ResourceLoaderDebug );
1775 $request = RequestContext::getMain()->getRequest();
1776 $str = $request->getRawVal( 'debug' ) ??
1777 $request->getCookie( 'resourceLoaderDebug', '', $resourceLoaderDebug ? 'true' : '' );
1778 self::$debugMode = Context::debugFromString( $str );
1779 }
1780 return self::$debugMode;
1781 }
1782
1793 public static function clearCache() {
1794 self::$debugMode = null;
1795 }
1796
1806 public function createLoaderURL( $source, Context $context,
1807 array $extraQuery = []
1808 ) {
1809 $query = self::createLoaderQuery( $context, $extraQuery );
1810 $script = $this->getLoadScript( $source );
1811
1812 return wfAppendQuery( $script, $query );
1813 }
1814
1824 protected static function createLoaderQuery(
1825 Context $context, array $extraQuery = []
1826 ) {
1827 return self::makeLoaderQuery(
1828 $context->getModules(),
1829 $context->getLanguage(),
1830 $context->getSkin(),
1831 $context->getUser(),
1832 $context->getVersion(),
1833 $context->getDebug(),
1834 $context->getOnly(),
1835 $context->getRequest()->getBool( 'printable' ),
1836 null,
1837 $extraQuery
1838 );
1839 }
1840
1857 public static function makeLoaderQuery( array $modules, $lang, $skin, $user = null,
1858 $version = null, $debug = Context::DEBUG_OFF, $only = null,
1859 $printable = false, $handheld = null, array $extraQuery = []
1860 ) {
1861 $query = [
1862 'modules' => self::makePackedModulesString( $modules ),
1863 ];
1864 // Keep urls short by omitting query parameters that
1865 // match the defaults assumed by Context.
1866 // Note: This relies on the defaults either being insignificant or forever constant,
1867 // as otherwise cached urls could change in meaning when the defaults change.
1868 if ( $lang !== Context::DEFAULT_LANG ) {
1869 $query['lang'] = $lang;
1870 }
1871 if ( $skin !== Context::DEFAULT_SKIN ) {
1872 $query['skin'] = $skin;
1873 }
1874 if ( $debug !== Context::DEBUG_OFF ) {
1875 $query['debug'] = strval( $debug );
1876 }
1877 if ( $user !== null ) {
1878 $query['user'] = $user;
1879 }
1880 if ( $version !== null ) {
1881 $query['version'] = $version;
1882 }
1883 if ( $only !== null ) {
1884 $query['only'] = $only;
1885 }
1886 if ( $printable ) {
1887 $query['printable'] = 1;
1888 }
1889 foreach ( $extraQuery as $name => $value ) {
1890 $query[$name] = $value;
1891 }
1892
1893 // Make queries uniform in order
1894 ksort( $query );
1895 return $query;
1896 }
1897
1907 public static function isValidModuleName( $moduleName ) {
1908 $len = strlen( $moduleName );
1909 return ( $len <= 255
1910 && strcspn( $moduleName, '!,|', 0, $len ) === $len )
1911 && ( !str_starts_with( $moduleName, "./" ) && !str_starts_with( $moduleName, "../" ) );
1912 }
1913
1924 public function getLessCompiler( array $vars = [], array $importDirs = [] ) {
1925 // When called from the installer, it is possible that a required PHP extension
1926 // is missing (at least for now; see T49564). If this is the case, throw an
1927 // exception (caught by the installer) to prevent a fatal error later on.
1928 if ( !class_exists( Less_Parser::class ) ) {
1929 throw new RuntimeException( 'MediaWiki requires the less.php parser' );
1930 }
1931
1932 $importDirs[] = MW_INSTALL_PATH . '/resources/src/mediawiki.less';
1933
1934 $parser = new Less_Parser;
1935 $parser->ModifyVars( $vars );
1936 $parser->SetOption( 'relativeUrls', false );
1937 $parser->SetOption( 'math', 'parens-division' );
1938
1939 // SetImportDirs expects an array like [ 'path1' => '', 'path2' => '' ]
1940 $formattedImportDirs = array_fill_keys( $importDirs, '' );
1941
1942 // Add a callback to the import dirs array for path remapping
1943 $codexDevDir = $this->getConfig()->get( MainConfigNames::CodexDevelopmentDir );
1944 $formattedImportDirs[] = static function ( $path ) use ( $codexDevDir ) {
1945 // For each of the Codex import paths, use CodexDevelopmentDir if it's set
1946 $importMap = [
1947 '@wikimedia/codex-icons/' => $codexDevDir !== null ?
1948 "$codexDevDir/packages/codex-icons/dist/" :
1949 MW_INSTALL_PATH . '/resources/lib/codex-icons/',
1950 'mediawiki.skin.codex/' => $codexDevDir !== null ?
1951 "$codexDevDir/packages/codex/dist/" :
1952 MW_INSTALL_PATH . '/resources/lib/codex/',
1953 'mediawiki.skin.codex-design-tokens/' => $codexDevDir !== null ?
1954 "$codexDevDir/packages/codex-design-tokens/dist/" :
1955 MW_INSTALL_PATH . '/resources/lib/codex-design-tokens/',
1956 '@wikimedia/codex-design-tokens/' => static function ( $unused_path ): never {
1957 throw new RuntimeException(
1958 'Importing from @wikimedia/codex-design-tokens is not supported. ' .
1959 "To use the Codex tokens, use `@import 'mediawiki.skin.variables.less';` instead."
1960 );
1961 }
1962 ];
1963 foreach ( $importMap as $importPath => $substPath ) {
1964 if ( str_starts_with( $path, $importPath ) ) {
1965 $restOfPath = substr( $path, strlen( $importPath ) );
1966 if ( is_callable( $substPath ) ) {
1967 // @phan-suppress-next-line PhanUseReturnValueOfNever
1968 $resolvedPath = $substPath( $restOfPath );
1969 } else {
1970 $filePath = $substPath . $restOfPath;
1971
1972 $resolvedPath = null;
1973 if ( file_exists( $filePath ) ) {
1974 $resolvedPath = $filePath;
1975 } elseif ( file_exists( "$filePath.less" ) ) {
1976 $resolvedPath = "$filePath.less";
1977 }
1978 }
1979
1980 if ( $resolvedPath !== null ) {
1981 return [
1982 Less_Environment::normalizePath( $resolvedPath ),
1983 Less_Environment::normalizePath( dirname( $path ) )
1984 ];
1985 } else {
1986 break;
1987 }
1988 }
1989 }
1990 return [ null, null ];
1991 };
1992 $parser->SetImportDirs( $formattedImportDirs );
1993
1994 return $parser;
1995 }
1996
2014 public static function filter( $filter, $data, array $options = [] ) {
2015 if ( isset( $options['cache'] ) && $options['cache'] === false ) {
2016 return self::applyFilter( $filter, $data ) ?? $data;
2017 }
2018
2019 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
2020 // Same as ResourceLoader->srvCache
2021 $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
2022
2023 $key = $cache->makeGlobalKey(
2024 'resourceloader-filter',
2025 $filter,
2026 self::CACHE_VERSION,
2027 md5( $data )
2028 );
2029
2030 $status = 'hit';
2031 $result = $cache->getWithSetCallback(
2032 $key,
2033 BagOStuff::TTL_DAY,
2034 static function () use ( $filter, $data, &$status ) {
2035 $status = 'miss';
2036 return self::applyFilter( $filter, $data );
2037 }
2038 );
2039 $statsFactory->getCounter( 'resourceloader_cache_total' )
2040 ->setLabel( 'type', $filter )
2041 ->setLabel( 'status', $status )
2042 ->increment();
2043
2044 // Use $data on cache failure
2045 return $result ?? $data;
2046 }
2047
2053 private static function applyFilter( $filter, $data ) {
2054 $data = trim( $data );
2055 if ( $data ) {
2056 try {
2057 $data = ( $filter === 'minify-css' )
2058 ? CSSMin::minify( $data )
2059 : JavaScriptMinifier::minify( $data );
2060 } catch ( TimeoutException $e ) {
2061 throw $e;
2062 } catch ( Exception $e ) {
2063 MWExceptionHandler::logException( $e );
2064 return null;
2065 }
2066 }
2067 return $data;
2068 }
2069
2081 public static function getUserDefaults(
2082 Context $context,
2083 HookContainer $hookContainer,
2084 UserOptionsLookup $userOptionsLookup
2085 ): array {
2086 $defaultOptions = $userOptionsLookup->getDefaultOptions();
2087 $keysToExclude = [];
2088 $hookRunner = new HookRunner( $hookContainer );
2089 $hookRunner->onResourceLoaderExcludeUserOptions( $keysToExclude, $context );
2090 foreach ( $keysToExclude as $excludedKey ) {
2091 unset( $defaultOptions[ $excludedKey ] );
2092 }
2093 return $defaultOptions;
2094 }
2095
2104 public static function getSiteConfigSettings(
2105 Context $context, Config $conf
2106 ): array {
2107 $services = MediaWikiServices::getInstance();
2108 // Namespace related preparation
2109 // - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces.
2110 // - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive.
2111 $contLang = $services->getContentLanguage();
2112 $namespaceIds = $contLang->getNamespaceIds();
2113 $caseSensitiveNamespaces = [];
2114 $nsInfo = $services->getNamespaceInfo();
2115 foreach ( $nsInfo->getCanonicalNamespaces() as $index => $name ) {
2116 $namespaceIds[$contLang->lc( $name )] = $index;
2117 if ( !$nsInfo->isCapitalized( $index ) ) {
2118 $caseSensitiveNamespaces[] = $index;
2119 }
2120 }
2121
2122 $illegalFileChars = $conf->get( MainConfigNames::IllegalFileChars );
2123
2124 // Build list of variables
2125 $skin = $context->getSkin();
2126
2127 // Start of supported and stable config vars (for use by extensions/gadgets).
2128 $vars = [
2129 'debug' => $context->getDebug(),
2130 'skin' => $skin,
2131 'stylepath' => $conf->get( MainConfigNames::StylePath ),
2132 'wgArticlePath' => $conf->get( MainConfigNames::ArticlePath ),
2133 'wgScriptPath' => $conf->get( MainConfigNames::ScriptPath ),
2134 'wgScript' => $conf->get( MainConfigNames::Script ),
2135 'wgSearchType' => $conf->get( MainConfigNames::SearchType ),
2136 'wgVariantArticlePath' => $conf->get( MainConfigNames::VariantArticlePath ),
2137 'wgServer' => $conf->get( MainConfigNames::Server ),
2138 'wgServerName' => $conf->get( MainConfigNames::ServerName ),
2139 'wgUserLanguage' => $context->getLanguage(),
2140 'wgContentLanguage' => $contLang->getCode(),
2141 'wgVersion' => MW_VERSION,
2142 'wgFormattedNamespaces' => $contLang->getFormattedNamespaces(),
2143 'wgNamespaceIds' => $namespaceIds,
2144 'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
2145 'wgSiteName' => $conf->get( MainConfigNames::Sitename ),
2146 'wgDBname' => $conf->get( MainConfigNames::DBname ),
2147 'wgWikiID' => WikiMap::getCurrentWikiId(),
2148 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
2149 'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
2150 'wgExtensionAssetsPath' => $conf->get( MainConfigNames::ExtensionAssetsPath ),
2151 ];
2152 // End of stable config vars.
2153
2154 // Internal variables for use by MediaWiki core and/or ResourceLoader.
2155 $vars += [
2156 // @internal For mediawiki.widgets
2157 'wgUrlProtocols' => $services->getUrlUtils()->validProtocols(),
2158 // @internal For mediawiki.page.watch
2159 // Force object to avoid "empty" associative array from
2160 // becoming [] instead of {} in JS (T36604)
2161 'wgActionPaths' => (object)$conf->get( MainConfigNames::ActionPaths ),
2162 // @internal For mediawiki.language
2163 'wgTranslateNumerals' => $conf->get( MainConfigNames::TranslateNumerals ),
2164 // @internal For mediawiki.Title
2165 'wgExtraSignatureNamespaces' => $conf->get( MainConfigNames::ExtraSignatureNamespaces ),
2166 'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
2167 'wgIllegalFileChars' => Title::convertByteClassToUnicodeClass( $illegalFileChars ),
2168 ];
2169
2170 ( new HookRunner( $services->getHookContainer() ) )
2171 ->onResourceLoaderGetConfigVars( $vars, $skin, $conf );
2172
2173 return $vars;
2174 }
2175
2180 public function getErrors() {
2181 return $this->errors;
2182 }
2183}
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...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
const MW_ENTRY_POINT
Definition api.php:21
Handle database storage of comments such as edit summaries and log reasons.
Group all the pieces relevant to the context of a request into one instance.
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:44
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:35
encodeJson( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition Context.php:464
getImageObj()
If this is a request for an image, get the Image object.
Definition Context.php:347
Track per-module dependency file paths that are expensive to mass compute.
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:946
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
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, 'EnableChunkedUploads'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'DefaultLockManager'=> null, 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> true, '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 -l $lang -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'MaxAnimatedWebPArea'=> 12500000, 'WebPThumbnailType'=>['webp', 'image/webp',], 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'RestTermsOfServiceUrl'=> null, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'RemoteVirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'SplitParsoidParserCache'=> true, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'EnableSectionShare'=> false, 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'NamespacesWithoutAutoSummaries' => [ ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'autocreateaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => true, 'logout' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'createpreviouslyrenamedaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'RestrictUserPageEditing' => false, 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editmywatchlist' => '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', 'managesessions' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchstarPopover' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\RecentChanges\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], 'UseParsoidLinksUpdate' => null, 'UseParsoidMessages' => null, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'WebPThumbnailType' => 'array', 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'RestTermsOfServiceUrl' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'RemoteVirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'SplitParsoidParserCache' => 'boolean', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'RestrictUserPageEditing' => 'boolean', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', 'UseParsoidLinksUpdate' => [ 'boolean', 'null', ], 'UseParsoidMessages' => [ 'boolean', 'null', ], ], 'mergeStrategy' => [ 'WebPThumbnailType' => 'replace', 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], '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', ], 'skipRedirects' => [ 'type' => 'bool', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'availability' => [ 'type' => 'string', ], ], 'required' => [ 'availability', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Interface for configuration instances.
Definition Config.php:18
$source