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 $this->sendResponseHeaders( $context, $etag, (bool)$this->errors );
748
749 // Remove the output buffer and output the response
750 ob_end_clean();
751
752 if ( $context->getImageObj() && $this->errors ) {
753 // We can't show both the error messages and the response when it's an image.
754 $response = implode( "\n\n", $this->errors );
755 } elseif ( $this->errors ) {
756 $errorText = implode( "\n\n", $this->errors );
757 $errorResponse = self::makeComment( $errorText );
758 if ( $context->shouldIncludeScripts() ) {
759 $errorResponse .= 'if (window.console && console.error) { console.error('
760 . $context->encodeJson( $errorText )
761 . "); }\n";
762 // Append the error info to the response
763 // We used to prepend it, but that would corrupt the source map
764 $response .= $errorResponse;
765 } else {
766 // For styles we can still prepend
767 $response = $errorResponse . $response;
768 }
769 }
770
771 // @phan-suppress-next-line SecurityCheck-XSS
772 echo $response;
773 }
774
778 #[\NoDiscard]
779 protected function measureResponseTime(): ScopedCallback {
780 $requestStart = $_SERVER['REQUEST_TIME_FLOAT'];
781 return new ScopedCallback( function () use ( $requestStart ) {
782 $statTiming = microtime( true ) - $requestStart;
783
784 $this->statsFactory->getTiming( 'resourceloader_response_time_seconds' )
785 ->observe( 1000 * $statTiming );
786 } );
787 }
788
798 protected function sendResponseHeaders(
799 Context $context, $etag, $errors
800 ): void {
801 HeaderCallback::warnIfHeadersSent();
802
803 if ( $errors ) {
804 $maxage = self::MAXAGE_RECOVER;
805 } elseif (
806 $context->getVersion() !== null
807 && $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
808 ) {
809 // If we need to self-correct, set a very short cache expiry
810 // to basically just debounce CDN traffic. This applies to:
811 // - Internal errors, e.g. due to misconfiguration.
812 // - Version mismatch, e.g. due to deployment race (T117587, T47877).
813 $this->logger->debug( 'Client and server registry version out of sync' );
814 $maxage = self::MAXAGE_RECOVER;
815 } elseif ( $context->getVersion() === null ) {
816 // Resources that can't set a version, should have their updates propagate to
817 // clients quickly. This applies to shared resources linked from HTML, such as
818 // the startup module and stylesheets.
819 $maxage = $this->maxageUnversioned;
820 } else {
821 // When a version is set, use a long expiry because changes
822 // will naturally miss the cache by using a different URL.
823 $maxage = $this->maxageVersioned;
824 }
825 if ( $context->getImageObj() ) {
826 // Output different headers if we're outputting textual errors.
827 if ( $errors ) {
828 header( 'Content-Type: text/plain; charset=utf-8' );
829 } else {
830 $context->getImageObj()->sendResponseHeaders( $context );
831 }
832 } elseif ( $context->isSourceMap() ) {
833 header( 'Content-Type: application/json' );
834 } elseif ( $context->getOnly() === 'styles' ) {
835 header( 'Content-Type: text/css; charset=utf-8' );
836 header( 'Access-Control-Allow-Origin: *' );
837 } else {
838 header( 'Content-Type: text/javascript; charset=utf-8' );
839 }
840 // See RFC 2616 § 14.19 ETag
841 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
842 header( 'ETag: ' . $etag );
843 if ( $context->getDebug() ) {
844 // Do not cache debug responses
845 header( 'Cache-Control: private, no-cache, must-revalidate' );
846 } else {
847 // T132418: When a resource expires mid-way a browsing session, prefer to renew it in
848 // the background instead of blocking the next page load (eg. startup module, or CSS).
849 $staleDirective = ( $maxage > self::MAXAGE_RECOVER
850 ? ", stale-while-revalidate=" . min( 60, intval( $maxage / 2 ) )
851 : ''
852 );
853 header( "Cache-Control: public, max-age=$maxage, s-maxage=$maxage" . $staleDirective );
854 header( 'Expires: ' . ConvertibleTimestamp::convert( TS::RFC2822, time() + $maxage ) );
855 }
856
857 foreach ( $this->extraHeaders as $header ) {
858 header( $header );
859 }
860 }
861
872 protected function tryRespondNotModified( Context $context, $etag ) {
873 // See RFC 2616 § 14.26 If-None-Match
874 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
875 $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
876 // Never send 304s in debug mode
877 if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
878 // There's another bug in ob_gzhandler (see also the comment at
879 // the top of this function) that causes it to gzip even empty
880 // responses, meaning it's impossible to produce a truly empty
881 // response (because the gzip header is always there). This is
882 // a problem because 304 responses have to be completely empty
883 // per the HTTP spec, and Firefox behaves buggily when they're not.
884 // See also https://bugs.php.net/bug.php?id=51579
885 // To work around this, we tear down all output buffering before
886 // sending the 304.
887 wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
888
889 HttpStatus::header( 304 );
890 $this->sendResponseHeaders( $context, $etag, false );
891 return true;
892 }
893 return false;
894 }
895
903 private function getSourceMapUrl( Context $context, $version ) {
904 return $this->createLoaderURL( 'local', $context, [
905 'sourcemap' => '1',
906 'version' => $version
907 ] );
908 }
909
915 private function sendSourceMapVersionMismatch( $currentVersion ) {
916 HttpStatus::header( 404 );
917 header( 'Content-Type: text/plain; charset=utf-8' );
918 header( 'X-Content-Type-Options: nosniff' );
919 echo "Can't deliver a source map for the requested version " .
920 "since the version is now '$currentVersion'\n";
921 }
922
927 private function sendSourceMapTypeNotImplemented() {
928 HttpStatus::header( 404 );
929 header( 'Content-Type: text/plain; charset=utf-8' );
930 header( 'X-Content-Type-Options: nosniff' );
931 echo "Can't make a source map for this content type\n";
932 }
933
942 public static function makeComment( $text ) {
943 $encText = str_replace( '*/', '* /', $text );
944 return "/*\n$encText\n*/\n";
945 }
946
954 protected static function formatExceptionNoComment( Throwable $e ) {
955 if ( !MWExceptionRenderer::shouldShowExceptionDetails() ) {
956 return MWExceptionHandler::getPublicLogMessage( $e );
957 }
958
959 // Like MWExceptionHandler::getLogMessage but without $url and $id.
960 // - Long load.php URL would push the actual error message off-screen into
961 // scroll overflow in browser devtools.
962 // - reqId is redundant with X-Request-Id header, plus usually no need to
963 // correlate the reqId since the backtrace is already included below.
964 $type = get_class( $e );
965 $message = $e->getMessage();
966
967 return "$type: $message" .
968 "\nBacktrace:\n" .
969 MWExceptionHandler::getRedactedTraceAsString( $e );
970 }
971
983 public function makeModuleResponse( Context $context,
984 array $modules, array $missing = []
985 ) {
986 if ( $modules === [] && $missing === [] ) {
987 return <<<MESSAGE
988/* This file is the Web entry point for MediaWiki's ResourceLoader:
989 <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
990 no modules were requested. Max made me put this here. */
991MESSAGE;
992 }
993
994 $image = $context->getImageObj();
995 if ( $image ) {
996 $data = $image->getImageData( $context );
997 if ( $data === false ) {
998 $data = '';
999 $this->errors[] = 'Image generation failed';
1000 }
1001 return $data;
1002 }
1003
1004 $states = [];
1005 foreach ( $missing as $name ) {
1006 $states[$name] = 'missing';
1007 }
1008
1009 $only = $context->getOnly();
1010 $debug = (bool)$context->getDebug();
1011 if ( $context->isSourceMap() && count( $modules ) > 1 ) {
1012 $indexMap = new IndexMap;
1013 } else {
1014 $indexMap = null;
1015 }
1016
1017 $out = '';
1018 foreach ( $modules as $name => $module ) {
1019 try {
1020 [ $response, $offset ] = $this->getOneModuleResponse( $context, $name, $module );
1021 if ( $indexMap ) {
1022 $indexMap->addEncodedMap( $response, $offset );
1023 } else {
1024 $out .= $response;
1025 }
1026 } catch ( TimeoutException $e ) {
1027 throw $e;
1028 } catch ( Exception $e ) {
1029 $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1030
1031 // Respond to client with error-state instead of module implementation
1032 $states[$name] = 'error';
1033 unset( $modules[$name] );
1034 }
1035 }
1036
1037 // Update module states
1038 if ( $context->shouldIncludeScripts() && !$context->getRaw() ) {
1039 if ( $modules && $only === 'scripts' ) {
1040 // Set the state of modules loaded as only scripts to ready as
1041 // they don't have an mw.loader.impl wrapper that sets the state
1042 foreach ( $modules as $name => $module ) {
1043 $states[$name] = 'ready';
1044 }
1045 }
1046
1047 // Set the state of modules we didn't respond to with mw.loader.impl
1048 if ( $states && !$context->isSourceMap() ) {
1049 $stateScript = self::makeLoaderStateScript( $context, $states );
1050 if ( !$debug ) {
1051 $stateScript = self::filter( 'minify-js', $stateScript );
1052 }
1053 // Use a linebreak between module script and state script (T162719)
1054 $out = self::ensureNewline( $out ) . $stateScript;
1055 }
1056 } elseif ( $states ) {
1057 $this->errors[] = 'Problematic modules: '
1058 // Silently ignore invalid UTF-8 injected via 'modules' query
1059 // Don't issue server-side warnings for client errors. (T331641)
1060 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1061 . @$context->encodeJson( $states );
1062 }
1063
1064 if ( $indexMap ) {
1065 return $indexMap->getMap();
1066 }
1067 return $out;
1068 }
1069
1078 private function getOneModuleResponse( Context $context, $name, Module $module ) {
1079 $only = $context->getOnly();
1080 // Important: Do not cache minifications of embedded modules
1081 // This is especially for the private 'user.options' module,
1082 // which varies on every pageview and would explode the cache (T84960)
1083 $shouldCache = !$module->shouldEmbedModule( $context );
1084 if ( $only === 'styles' ) {
1085 $minifier = new IdentityMinifierState;
1086 $this->addOneModuleResponse( $context, $minifier, $name, $module, $this->extraHeaders );
1087 // NOTE: This is not actually "minified". IdentityMinifierState is a no-op wrapper
1088 // to ease code reuse. The filter() call below performs CSS minification.
1089 $styles = $minifier->getMinifiedOutput();
1090 if ( $context->getDebug() ) {
1091 return [ $styles, null ];
1092 }
1093 return [
1094 self::filter( 'minify-css', $styles,
1095 [ 'cache' => $shouldCache ] ),
1096 null
1097 ];
1098 }
1099
1100 $replayMinifier = new ReplayMinifierState;
1101 $this->addOneModuleResponse( $context, $replayMinifier, $name, $module, $this->extraHeaders );
1102
1103 $minifier = new IdentityMinifierState;
1104 $replayMinifier->replayOn( $minifier );
1105 $plainContent = $minifier->getMinifiedOutput();
1106 if ( $context->getDebug() ) {
1107 return [ $plainContent, null ];
1108 }
1109
1110 $isHit = true;
1111 $callback = function () use ( $context, $replayMinifier, &$isHit ) {
1112 $isHit = false;
1113 if ( $context->isSourceMap() ) {
1114 $minifier = ( new JavaScriptMapperState )
1115 ->outputFile( $this->createLoaderURL( 'local', $context, [
1116 'modules' => self::makePackedModulesString( $context->getModules() ),
1117 'only' => $context->getOnly()
1118 ] ) );
1119 } else {
1120 $minifier = new JavaScriptMinifierState;
1121 }
1122 $replayMinifier->replayOn( $minifier );
1123 if ( $context->isSourceMap() ) {
1124 $sourceMap = $minifier->getRawSourceMap();
1125 $generated = $minifier->getMinifiedOutput();
1126 $offset = IndexMapOffset::newFromText( $generated );
1127 return [ $sourceMap, $offset->toArray() ];
1128 } else {
1129 return [ $minifier->getMinifiedOutput(), null ];
1130 }
1131 };
1132
1133 // The below is based on ResourceLoader::filter. Keep together to ease review/maintenance:
1134 // * Handle $shouldCache, skip cache and minify directly if set.
1135 // * Use minify cache, minify on-demand and populate cache as needed.
1136 // * Emit resourceloader_cache_total stats.
1137
1138 if ( $shouldCache ) {
1139 [ $response, $offsetArray ] = $this->srvCache->getWithSetCallback(
1140 $this->srvCache->makeGlobalKey(
1141 'resourceloader-mapped',
1142 self::CACHE_VERSION,
1143 $name,
1144 $context->isSourceMap() ? '1' : '0',
1145 md5( $plainContent )
1146 ),
1147 BagOStuff::TTL_DAY,
1148 $callback
1149 );
1150
1151 $mapType = $context->isSourceMap() ? 'map-js' : 'minify-js';
1152 $this->statsFactory->getCounter( 'resourceloader_cache_total' )
1153 ->setLabel( 'type', $mapType )
1154 ->setLabel( 'status', $isHit ? 'hit' : 'miss' )
1155 ->increment();
1156 } else {
1157 [ $response, $offsetArray ] = $callback();
1158 }
1159 $offset = $offsetArray ? IndexMapOffset::newFromArray( $offsetArray ) : null;
1160
1161 return [ $response, $offset ];
1162 }
1163
1174 private function addOneModuleResponse(
1175 Context $context, MinifierState $minifier, $name, Module $module, &$headers
1176 ) {
1177 $only = $context->getOnly();
1178 $debug = (bool)$context->getDebug();
1179 $content = $module->getModuleContent( $context );
1180 $version = $module->getVersionHash( $context );
1181
1182 if ( $headers !== null && isset( $content['headers'] ) ) {
1183 $headers = array_merge( $headers, $content['headers'] );
1184 }
1185
1186 // Append output
1187 switch ( $only ) {
1188 case 'scripts':
1189 $scripts = $content['scripts'];
1190 if ( !is_array( $scripts ) ) {
1191 // Formerly scripts was usually a string, but now it is
1192 // normalized to an array by buildContent().
1193 throw new InvalidArgumentException( 'scripts must be an array' );
1194 }
1195 if ( isset( $scripts['plainScripts'] ) ) {
1196 // Add plain scripts
1197 $this->addPlainScripts( $minifier, $name, $scripts['plainScripts'] );
1198 } elseif ( isset( $scripts['files'] ) ) {
1199 // Add implement call if any
1200 $this->addImplementScript(
1201 $minifier,
1202 $name,
1203 $version,
1204 $scripts,
1205 [],
1206 null,
1207 [],
1208 $content['deprecationWarning'] ?? null
1209 );
1210 }
1211 break;
1212 case 'styles':
1213 $styles = $content['styles'];
1214 // We no longer separate into media, they are all combined now with
1215 // custom media type groups into @media .. {} sections as part of the css string.
1216 // Module returns either an empty array or a numerical array with css strings.
1217 if ( isset( $styles['css'] ) ) {
1218 $minifier->addOutput( implode( '', $styles['css'] ) );
1219 }
1220 break;
1221 default:
1222 $scripts = $content['scripts'] ?? '';
1223 if ( ( $name === 'site' || $name === 'user' )
1224 && isset( $scripts['plainScripts'] )
1225 ) {
1226 // Legacy scripts that run in the global scope without a closure.
1227 // mw.loader.impl will use eval if scripts is a string.
1228 // Minify manually here, because general response minification is
1229 // not effective due it being a string literal, not a function.
1230 $scripts = self::concatenatePlainScripts( $scripts['plainScripts'] );
1231 if ( !$debug ) {
1232 $scripts = self::filter( 'minify-js', $scripts ); // T107377
1233 }
1234 }
1235 $this->addImplementScript(
1236 $minifier,
1237 $name,
1238 $version,
1239 $scripts,
1240 $content['styles'] ?? [],
1241 isset( $content['messagesBlob'] ) ? new HtmlJsCode( $content['messagesBlob'] ) : null,
1242 $content['templates'] ?? [],
1243 $content['deprecationWarning'] ?? null
1244 );
1245 break;
1246 }
1247 $minifier->ensureNewline();
1248 }
1249
1256 public static function ensureNewline( $str ) {
1257 $end = substr( $str, -1 );
1258 if ( $end === '' || $end === "\n" ) {
1259 return $str;
1260 }
1261 return $str . "\n";
1262 }
1263
1270 public function getModulesByMessage( $messageKey ) {
1271 $moduleNames = [];
1272 foreach ( $this->getModuleNames() as $moduleName ) {
1273 $module = $this->getModule( $moduleName );
1274 if ( in_array( $messageKey, $module->getMessages() ) ) {
1275 $moduleNames[] = $moduleName;
1276 }
1277 }
1278 return $moduleNames;
1279 }
1280
1302 private function addImplementScript( MinifierState $minifier,
1303 $moduleName, $version, $scripts, $styles, $messages, $templates, $deprecationWarning
1304 ) {
1305 $implementKey = "$moduleName@$version";
1306 // Plain functions are used instead of arrow functions to avoid
1307 // defeating lazy compilation on Chrome. (T343407)
1308 $minifier->addOutput( "mw.loader.impl(function(){return[" .
1309 Html::encodeJsVar( $implementKey ) . "," );
1310
1311 // Scripts
1312 if ( is_string( $scripts ) ) {
1313 // user/site script
1314 $minifier->addOutput( Html::encodeJsVar( $scripts ) );
1315 } elseif ( is_array( $scripts ) ) {
1316 if ( isset( $scripts['files'] ) ) {
1317 $minifier->addOutput(
1318 "{\"main\":" .
1319 Html::encodeJsVar( $scripts['main'] ) .
1320 ",\"files\":" );
1321 $this->addFiles( $minifier, $moduleName, $scripts['files'] );
1322 $minifier->addOutput( "}" );
1323 } elseif ( isset( $scripts['plainScripts'] ) ) {
1324 if ( $this->isEmptyFileInfos( $scripts['plainScripts'] ) ) {
1325 $minifier->addOutput( 'null' );
1326 } else {
1327 $minifier->addOutput( "function($,jQuery,require,module){" );
1328 $this->addPlainScripts( $minifier, $moduleName, $scripts['plainScripts'] );
1329 $minifier->addOutput( "}" );
1330 }
1331 } elseif ( $scripts === [] || isset( $scripts[0] ) ) {
1332 // Array of URLs
1333 $minifier->addOutput( Html::encodeJsVar( $scripts ) );
1334 } else {
1335 throw new InvalidArgumentException( 'Invalid script array: ' .
1336 'must contain files, plainScripts or be an array of URLs' );
1337 }
1338 } else {
1339 throw new InvalidArgumentException( 'Script must be a string or array' );
1340 }
1341
1342 // mw.loader.impl requires 'styles', 'messages' and 'templates' to be objects (not
1343 // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1344 // of "{}". Force them to objects.
1345 $extraArgs = [
1346 (object)$styles,
1347 $messages ?? (object)[],
1348 (object)$templates,
1349 $deprecationWarning
1350 ];
1351 self::trimArray( $extraArgs );
1352 foreach ( $extraArgs as $arg ) {
1353 $minifier->addOutput( ',' . Html::encodeJsVar( $arg ) );
1354 }
1355 $minifier->addOutput( "];});" );
1356 }
1357
1368 private function addFiles( MinifierState $minifier, $moduleName, $files ) {
1369 $first = true;
1370 $minifier->addOutput( "{" );
1371 foreach ( $files as $fileName => $file ) {
1372 if ( $first ) {
1373 $first = false;
1374 } else {
1375 $minifier->addOutput( "," );
1376 }
1377 $minifier->addOutput( Html::encodeJsVar( $fileName ) . ':' );
1378 $this->addFileContent( $minifier, $moduleName, 'packageFile', $fileName, $file );
1379 }
1380 $minifier->addOutput( "}" );
1381 }
1382
1392 private function addFileContent( MinifierState $minifier,
1393 $moduleName, $sourceType, $sourceIndex, array $file
1394 ) {
1395 $isScript = ( $file['type'] ?? 'script' ) === 'script';
1397 $filePath = $file['filePath'] ?? $file['virtualFilePath'] ?? null;
1398 if ( $filePath !== null && $filePath->getRemoteBasePath() !== null ) {
1399 $url = $filePath->getRemotePath();
1400 } else {
1401 $ext = $isScript ? 'js' : 'json';
1402 $scriptPath = $this->config->has( MainConfigNames::ScriptPath )
1403 ? $this->config->get( MainConfigNames::ScriptPath ) : '';
1404 $url = "$scriptPath/virtual-resource/$moduleName-$sourceType-$sourceIndex.$ext";
1405 }
1406 $content = $file['content'];
1407 if ( $isScript ) {
1408 if ( $sourceType === 'packageFile' ) {
1409 // Provide CJS `exports` (in addition to CJS2 `module.exports`) to package modules (T284511).
1410 // $/jQuery are simply used as globals instead.
1411 // TODO: Remove $/jQuery param from traditional module closure too (and bump caching)
1412 $minifier->addOutput( "function(require,module,exports){" );
1413 $minifier->addSourceFile( $url, $content, true );
1414 $minifier->ensureNewline();
1415 $minifier->addOutput( "}" );
1416 } else {
1417 $minifier->addSourceFile( $url, $content, true );
1418 $minifier->ensureNewline();
1419 }
1420 } else {
1421 $content = Html::encodeJsVar( $content, true );
1422 $minifier->addSourceFile( $url, $content, true );
1423 }
1424 }
1425
1433 private static function concatenatePlainScripts( $plainScripts ) {
1434 $s = '';
1435 foreach ( $plainScripts as $script ) {
1436 // Make the script safe to concatenate by making sure there is at least one
1437 // trailing new line at the end of the content (T29054, T162719)
1438 $s .= self::ensureNewline( $script['content'] );
1439 }
1440 return $s;
1441 }
1442
1451 private function addPlainScripts( MinifierState $minifier, $moduleName, $plainScripts ) {
1452 foreach ( $plainScripts as $index => $file ) {
1453 $this->addFileContent( $minifier, $moduleName, 'script', $index, $file );
1454 }
1455 }
1456
1463 private function isEmptyFileInfos( $infos ) {
1464 $len = 0;
1465 foreach ( $infos as $info ) {
1466 $len += strlen( $info['content'] ?? '' );
1467 }
1468 return $len === 0;
1469 }
1470
1479 public static function makeCombinedStyles( array $stylePairs, $request = null ) {
1480 if ( $request === null ) {
1481 wfDeprecated( __METHOD__ . ' with null $request', '1.46' );
1482 }
1483 $out = [];
1484 foreach ( $stylePairs as $media => $styles ) {
1485 // FileModule::getStyle can return the styles as a string or an
1486 // array of strings. This is to allow separation in the front-end.
1487 $styles = (array)$styles;
1488 foreach ( $styles as $style ) {
1489 $style = trim( $style );
1490 // Don't output an empty "@media print { }" block (T42498)
1491 if ( $style === '' ) {
1492 continue;
1493 }
1494 // Transform the media type based on request params and config
1495 // The way that this relies on $wgRequest to propagate request params is slightly evil
1496 $media = OutputPage::transformCssMedia( $media, $request );
1497
1498 if ( $media === '' || $media == 'all' ) {
1499 $out[] = $style;
1500 } elseif ( is_string( $media ) ) {
1501 $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1502 }
1503 // else: skip
1504 }
1505 }
1506 return $out;
1507 }
1508
1517 public static function makeLoaderStateScript(
1518 Context $context, array $states
1519 ) {
1520 return 'mw.loader.state('
1521 // Silently ignore invalid UTF-8 injected via 'modules' query
1522 // Don't issue server-side warnings for client errors. (T331641)
1523 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1524 . @$context->encodeJson( $states )
1525 . ');';
1526 }
1527
1528 private static function isEmptyObject( stdClass $obj ): bool {
1529 foreach ( $obj as $value ) {
1530 return false;
1531 }
1532 return true;
1533 }
1534
1546 private static function trimArray( array &$array ): void {
1547 $i = count( $array );
1548 while ( $i-- ) {
1549 if ( $array[$i] === null
1550 || $array[$i] === []
1551 || ( $array[$i] instanceof HtmlJsCode && $array[$i]->value === '{}' )
1552 || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1553 ) {
1554 unset( $array[$i] );
1555 } else {
1556 break;
1557 }
1558 }
1559 }
1560
1586 public static function makeLoaderRegisterScript(
1587 Context $context, array $modules
1588 ) {
1589 // Optimisation: Transform dependency names into indexes when possible
1590 // to produce smaller output. They are expanded by mw.loader.register on
1591 // the other end.
1592 $index = [];
1593 foreach ( $modules as $i => $module ) {
1594 // Build module name index
1595 $index[$module[0]] = $i;
1596 }
1597 foreach ( $modules as &$module ) {
1598 if ( isset( $module[2] ) ) {
1599 foreach ( $module[2] as &$dependency ) {
1600 if ( isset( $index[$dependency] ) ) {
1601 // Replace module name in dependency list with index
1602 $dependency = $index[$dependency];
1603 }
1604 }
1605 }
1606 self::trimArray( $module );
1607 }
1608
1609 return 'mw.loader.register('
1610 . $context->encodeJson( $modules )
1611 . ');';
1612 }
1613
1627 public static function makeLoaderSourcesScript(
1628 Context $context, array $sources
1629 ) {
1630 return 'mw.loader.addSource('
1631 . $context->encodeJson( $sources )
1632 . ');';
1633 }
1634
1641 public static function makeLoaderConditionalScript( $script ) {
1642 // Adds a function to lazy-created RLQ
1643 return '(RLQ=window.RLQ||[]).push(function(){' .
1644 trim( $script ) . '});';
1645 }
1646
1655 public static function makeInlineCodeWithModule( $modules, $script ) {
1656 // Adds an array to lazy-created RLQ
1657 return '(RLQ=window.RLQ||[]).push(['
1658 . json_encode( $modules ) . ','
1659 . 'function(){' . trim( $script ) . '}'
1660 . ']);';
1661 }
1662
1673 public static function makeInlineScript( $script, $nonce = null ) {
1674 $js = self::makeLoaderConditionalScript( $script );
1675 return new WrappedString(
1676 Html::inlineScript( $js ),
1677 "<script>(RLQ=window.RLQ||[]).push(function(){",
1678 '});</script>'
1679 );
1680 }
1681
1695 public static function makePackedModulesString( array $modules ) {
1696 $moduleMap = []; // [ prefix => [ suffixes ] ]
1697 foreach ( $modules as $module ) {
1698 $pos = strrpos( $module, '.' );
1699 $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1700 $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1701 $moduleMap[$prefix][] = $suffix;
1702 }
1703
1704 $arr = [];
1705 foreach ( $moduleMap as $prefix => $suffixes ) {
1706 $p = $prefix === '' ? '' : $prefix . '.';
1707 $arr[] = $p . implode( ',', $suffixes );
1708 }
1709 return implode( '|', $arr );
1710 }
1711
1723 public static function expandModuleNames( $modules ) {
1724 $retval = [];
1725 $exploded = explode( '|', $modules );
1726 foreach ( $exploded as $group ) {
1727 if ( !str_contains( $group, ',' ) ) {
1728 // This is not a set of modules in foo.bar,baz notation
1729 // but a single module
1730 $retval[] = $group;
1731 continue;
1732 }
1733 // This is a set of modules in foo.bar,baz notation
1734 $pos = strrpos( $group, '.' );
1735 if ( $pos === false ) {
1736 // Prefixless modules, i.e. without dots
1737 $retval = array_merge( $retval, explode( ',', $group ) );
1738 continue;
1739 }
1740 // We have a prefix and a bunch of suffixes
1741 $prefix = substr( $group, 0, $pos ); // 'foo'
1742 $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // [ 'bar', 'baz' ]
1743 foreach ( $suffixes as $suffix ) {
1744 $retval[] = "$prefix.$suffix";
1745 }
1746 }
1747 return $retval;
1748 }
1749
1761 public static function inDebugMode() {
1762 wfDeprecated( __METHOD__, '1.47' );
1763 if ( self::$debugMode === null ) {
1764 $resourceLoaderDebug = MediaWikiServices::getInstance()->getMainConfig()->get(
1765 MainConfigNames::ResourceLoaderDebug );
1766 $request = RequestContext::getMain()->getRequest();
1767 $str = $request->getRawVal( 'debug' ) ??
1768 $request->getCookie( 'resourceLoaderDebug', '', $resourceLoaderDebug ? 'true' : '' );
1769 self::$debugMode = Context::debugFromString( $str );
1770 }
1771 return self::$debugMode;
1772 }
1773
1784 public static function clearCache() {
1785 self::$debugMode = null;
1786 }
1787
1797 public function createLoaderURL( $source, Context $context,
1798 array $extraQuery = []
1799 ) {
1800 $query = self::createLoaderQuery( $context, $extraQuery );
1801 $script = $this->getLoadScript( $source );
1802
1803 return wfAppendQuery( $script, $query );
1804 }
1805
1815 protected static function createLoaderQuery(
1816 Context $context, array $extraQuery = []
1817 ) {
1818 return self::makeLoaderQuery(
1819 $context->getModules(),
1820 $context->getLanguage(),
1821 $context->getSkin(),
1822 $context->getUser(),
1823 $context->getVersion(),
1824 $context->getDebug(),
1825 $context->getOnly(),
1826 $context->getRequest()->getBool( 'printable' ),
1827 null,
1828 $extraQuery
1829 );
1830 }
1831
1848 public static function makeLoaderQuery( array $modules, $lang, $skin, $user = null,
1849 $version = null, $debug = Context::DEBUG_OFF, $only = null,
1850 $printable = false, $handheld = null, array $extraQuery = []
1851 ) {
1852 $query = [
1853 'modules' => self::makePackedModulesString( $modules ),
1854 ];
1855 // Keep urls short by omitting query parameters that
1856 // match the defaults assumed by Context.
1857 // Note: This relies on the defaults either being insignificant or forever constant,
1858 // as otherwise cached urls could change in meaning when the defaults change.
1859 if ( $lang !== Context::DEFAULT_LANG ) {
1860 $query['lang'] = $lang;
1861 }
1862 if ( $skin !== Context::DEFAULT_SKIN ) {
1863 $query['skin'] = $skin;
1864 }
1865 if ( $debug !== Context::DEBUG_OFF ) {
1866 $query['debug'] = strval( $debug );
1867 }
1868 if ( $user !== null ) {
1869 $query['user'] = $user;
1870 }
1871 if ( $version !== null ) {
1872 $query['version'] = $version;
1873 }
1874 if ( $only !== null ) {
1875 $query['only'] = $only;
1876 }
1877 if ( $printable ) {
1878 $query['printable'] = 1;
1879 }
1880 foreach ( $extraQuery as $name => $value ) {
1881 $query[$name] = $value;
1882 }
1883
1884 // Make queries uniform in order
1885 ksort( $query );
1886 return $query;
1887 }
1888
1898 public static function isValidModuleName( $moduleName ) {
1899 $len = strlen( $moduleName );
1900 return ( $len <= 255
1901 && strcspn( $moduleName, '!,|', 0, $len ) === $len )
1902 && ( !str_starts_with( $moduleName, "./" ) && !str_starts_with( $moduleName, "../" ) );
1903 }
1904
1915 public function getLessCompiler( array $vars = [], array $importDirs = [] ) {
1916 // When called from the installer, it is possible that a required PHP extension
1917 // is missing (at least for now; see T49564). If this is the case, throw an
1918 // exception (caught by the installer) to prevent a fatal error later on.
1919 if ( !class_exists( Less_Parser::class ) ) {
1920 throw new RuntimeException( 'MediaWiki requires the less.php parser' );
1921 }
1922
1923 $importDirs[] = MW_INSTALL_PATH . '/resources/src/mediawiki.less';
1924
1925 $parser = new Less_Parser;
1926 $parser->ModifyVars( $vars );
1927 $parser->SetOption( 'relativeUrls', false );
1928 $parser->SetOption( 'math', 'parens-division' );
1929
1930 // SetImportDirs expects an array like [ 'path1' => '', 'path2' => '' ]
1931 $formattedImportDirs = array_fill_keys( $importDirs, '' );
1932
1933 // Add a callback to the import dirs array for path remapping
1934 $codexDevDir = $this->getConfig()->get( MainConfigNames::CodexDevelopmentDir );
1935 $formattedImportDirs[] = static function ( $path ) use ( $codexDevDir ) {
1936 // For each of the Codex import paths, use CodexDevelopmentDir if it's set
1937 $importMap = [
1938 '@wikimedia/codex-icons/' => $codexDevDir !== null ?
1939 "$codexDevDir/packages/codex-icons/dist/" :
1940 MW_INSTALL_PATH . '/resources/lib/codex-icons/',
1941 'mediawiki.skin.codex/' => $codexDevDir !== null ?
1942 "$codexDevDir/packages/codex/dist/" :
1943 MW_INSTALL_PATH . '/resources/lib/codex/',
1944 'mediawiki.skin.codex-design-tokens/' => $codexDevDir !== null ?
1945 "$codexDevDir/packages/codex-design-tokens/dist/" :
1946 MW_INSTALL_PATH . '/resources/lib/codex-design-tokens/',
1947 '@wikimedia/codex-design-tokens/' => static function ( $unused_path ): never {
1948 throw new RuntimeException(
1949 'Importing from @wikimedia/codex-design-tokens is not supported. ' .
1950 "To use the Codex tokens, use `@import 'mediawiki.skin.variables.less';` instead."
1951 );
1952 }
1953 ];
1954 foreach ( $importMap as $importPath => $substPath ) {
1955 if ( str_starts_with( $path, $importPath ) ) {
1956 $restOfPath = substr( $path, strlen( $importPath ) );
1957 if ( is_callable( $substPath ) ) {
1958 // @phan-suppress-next-line PhanUseReturnValueOfNever
1959 $resolvedPath = $substPath( $restOfPath );
1960 } else {
1961 $filePath = $substPath . $restOfPath;
1962
1963 $resolvedPath = null;
1964 if ( file_exists( $filePath ) ) {
1965 $resolvedPath = $filePath;
1966 } elseif ( file_exists( "$filePath.less" ) ) {
1967 $resolvedPath = "$filePath.less";
1968 }
1969 }
1970
1971 if ( $resolvedPath !== null ) {
1972 return [
1973 Less_Environment::normalizePath( $resolvedPath ),
1974 Less_Environment::normalizePath( dirname( $path ) )
1975 ];
1976 } else {
1977 break;
1978 }
1979 }
1980 }
1981 return [ null, null ];
1982 };
1983 $parser->SetImportDirs( $formattedImportDirs );
1984
1985 return $parser;
1986 }
1987
2005 public static function filter( $filter, $data, array $options = [] ) {
2006 if ( isset( $options['cache'] ) && $options['cache'] === false ) {
2007 return self::applyFilter( $filter, $data ) ?? $data;
2008 }
2009
2010 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
2011 // Same as ResourceLoader->srvCache
2012 $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
2013
2014 $key = $cache->makeGlobalKey(
2015 'resourceloader-filter',
2016 $filter,
2017 self::CACHE_VERSION,
2018 md5( $data )
2019 );
2020
2021 $status = 'hit';
2022 $result = $cache->getWithSetCallback(
2023 $key,
2024 BagOStuff::TTL_DAY,
2025 static function () use ( $filter, $data, &$status ) {
2026 $status = 'miss';
2027 return self::applyFilter( $filter, $data );
2028 }
2029 );
2030 $statsFactory->getCounter( 'resourceloader_cache_total' )
2031 ->setLabel( 'type', $filter )
2032 ->setLabel( 'status', $status )
2033 ->increment();
2034
2035 // Use $data on cache failure
2036 return $result ?? $data;
2037 }
2038
2044 private static function applyFilter( $filter, $data ) {
2045 $data = trim( $data );
2046 if ( $data ) {
2047 try {
2048 $data = ( $filter === 'minify-css' )
2049 ? CSSMin::minify( $data )
2050 : JavaScriptMinifier::minify( $data );
2051 } catch ( TimeoutException $e ) {
2052 throw $e;
2053 } catch ( Exception $e ) {
2054 MWExceptionHandler::logException( $e );
2055 return null;
2056 }
2057 }
2058 return $data;
2059 }
2060
2072 public static function getUserDefaults(
2073 Context $context,
2074 HookContainer $hookContainer,
2075 UserOptionsLookup $userOptionsLookup
2076 ): array {
2077 $defaultOptions = $userOptionsLookup->getDefaultOptions();
2078 $keysToExclude = [];
2079 $hookRunner = new HookRunner( $hookContainer );
2080 $hookRunner->onResourceLoaderExcludeUserOptions( $keysToExclude, $context );
2081 foreach ( $keysToExclude as $excludedKey ) {
2082 unset( $defaultOptions[ $excludedKey ] );
2083 }
2084 return $defaultOptions;
2085 }
2086
2095 public static function getSiteConfigSettings(
2096 Context $context, Config $conf
2097 ): array {
2098 $services = MediaWikiServices::getInstance();
2099 // Namespace related preparation
2100 // - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces.
2101 // - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive.
2102 $contLang = $services->getContentLanguage();
2103 $namespaceIds = $contLang->getNamespaceIds();
2104 $caseSensitiveNamespaces = [];
2105 $nsInfo = $services->getNamespaceInfo();
2106 foreach ( $nsInfo->getCanonicalNamespaces() as $index => $name ) {
2107 $namespaceIds[$contLang->lc( $name )] = $index;
2108 if ( !$nsInfo->isCapitalized( $index ) ) {
2109 $caseSensitiveNamespaces[] = $index;
2110 }
2111 }
2112
2113 $illegalFileChars = $conf->get( MainConfigNames::IllegalFileChars );
2114
2115 // Build list of variables
2116 $skin = $context->getSkin();
2117
2118 // Start of supported and stable config vars (for use by extensions/gadgets).
2119 $vars = [
2120 'debug' => $context->getDebug(),
2121 'skin' => $skin,
2122 'stylepath' => $conf->get( MainConfigNames::StylePath ),
2123 'wgArticlePath' => $conf->get( MainConfigNames::ArticlePath ),
2124 'wgScriptPath' => $conf->get( MainConfigNames::ScriptPath ),
2125 'wgScript' => $conf->get( MainConfigNames::Script ),
2126 'wgSearchType' => $conf->get( MainConfigNames::SearchType ),
2127 'wgVariantArticlePath' => $conf->get( MainConfigNames::VariantArticlePath ),
2128 'wgServer' => $conf->get( MainConfigNames::Server ),
2129 'wgServerName' => $conf->get( MainConfigNames::ServerName ),
2130 'wgUserLanguage' => $context->getLanguage(),
2131 'wgContentLanguage' => $contLang->getCode(),
2132 'wgVersion' => MW_VERSION,
2133 'wgFormattedNamespaces' => $contLang->getFormattedNamespaces(),
2134 'wgNamespaceIds' => $namespaceIds,
2135 'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
2136 'wgSiteName' => $conf->get( MainConfigNames::Sitename ),
2137 'wgDBname' => $conf->get( MainConfigNames::DBname ),
2138 'wgWikiID' => WikiMap::getCurrentWikiId(),
2139 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
2140 'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
2141 'wgExtensionAssetsPath' => $conf->get( MainConfigNames::ExtensionAssetsPath ),
2142 ];
2143 // End of stable config vars.
2144
2145 // Internal variables for use by MediaWiki core and/or ResourceLoader.
2146 $vars += [
2147 // @internal For mediawiki.widgets
2148 'wgUrlProtocols' => $services->getUrlUtils()->validProtocols(),
2149 // @internal For mediawiki.page.watch
2150 // Force object to avoid "empty" associative array from
2151 // becoming [] instead of {} in JS (T36604)
2152 'wgActionPaths' => (object)$conf->get( MainConfigNames::ActionPaths ),
2153 // @internal For mediawiki.language
2154 'wgTranslateNumerals' => $conf->get( MainConfigNames::TranslateNumerals ),
2155 // @internal For mediawiki.Title
2156 'wgExtraSignatureNamespaces' => $conf->get( MainConfigNames::ExtraSignatureNamespaces ),
2157 'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
2158 'wgIllegalFileChars' => Title::convertByteClassToUnicodeClass( $illegalFileChars ),
2159 ];
2160
2161 ( new HookRunner( $services->getHookContainer() ) )
2162 ->onResourceLoaderGetConfigVars( $vars, $skin, $conf );
2163
2164 return $vars;
2165 }
2166
2171 public function getErrors() {
2172 return $this->errors;
2173 }
2174}
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:32
encodeJson( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition Context.php:456
getImageObj()
If this is a request for an image, get the Image object.
Definition Context.php:339
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:943
ResourceLoader is a loading system for JavaScript and CSS resources.
getLoadScript( $source)
Get the URL to the load.php endpoint for the given ResourceLoader source.
static makeComment( $text)
Generate a CSS or JS comment block.
isModuleRegistered( $name)
Check whether a ResourceLoader module is registered.
preloadModuleInfo(array $moduleNames, Context $context)
Load information stored in the database and dependency tracking store about modules.
setMessageBlobStore(MessageBlobStore $blobStore)
tryRespondNotModified(Context $context, $etag)
Respond with HTTP 304 Not Modified if appropriate.
static formatExceptionNoComment(Throwable $e)
Handle exception display.
measureResponseTime()
Send stats about the time used to build the response.
setDependencyStore(DependencyStore $tracker)
static makeHash( $value)
Create a hash for module versioning purposes.
array $errors
Errors accumulated during a respond() call.
sendResponseHeaders(Context $context, $etag, $errors)
Send main response headers to the client.
getTestSuiteModuleNames()
Get a list of modules with QUnit tests.
makeModuleResponse(Context $context, array $modules, array $missing=[])
Generate code for a response.
getModule( $name)
Get the Module object for a given module name.
__construct(Config $config, ?LoggerInterface $logger=null, ?DependencyStore $tracker=null, array $params=[])
setModuleSkinStyles(array $moduleSkinStyles)
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
respond(Context $context, array $extraHeaders=[])
Output a response to a load request, including the content-type header.
makeVersionQuery(Context $context, array $modules)
Get the expected value of the 'version' query parameter.
string[] $extraHeaders
Buffer for extra response headers during a makeModuleResponse() call.
getCombinedVersion(Context $context, array $moduleNames)
Helper method to get and combine versions of multiple modules.
addSource( $sources, $loadUrl=null)
Add a foreign source of modules.
Represents a title within MediaWiki.
Definition Title.php:69
Provides access to user options.
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73
Store data in a memory for the current request/process only.
This is the primary interface for validating metrics definitions, caching defined metrics,...
getCounter(string $name)
Makes a new CounterMetric or fetches one from cache.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], '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, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, '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, ], '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' => [ ], '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, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], '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, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\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', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, '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, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'NamespacesWithoutAutoSummaries' => 'array', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', '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', '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', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], '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', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], '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