27use Psr\Log\LoggerAwareInterface;
28use Psr\Log\LoggerInterface;
29use Psr\Log\NullLogger;
33use Wikimedia\Timestamp\ConvertibleTimestamp;
34use Wikimedia\WrappedString;
104 public const CACHE_VERSION = 8;
107 private const RL_DEP_STORE_PREFIX =
'ResourceLoaderModule';
109 private const RL_MODULE_DEP_TTL = BagOStuff::TTL_WEEK;
112 public const FILTER_NOMIN =
'/*@nomin*/';
123 $entitiesByModule = [];
124 foreach ( $moduleNames as $moduleName ) {
125 $entitiesByModule[$moduleName] =
"$moduleName|$vary";
127 $depsByEntity = $this->depStore->retrieveMulti(
128 self::RL_DEP_STORE_PREFIX,
132 foreach ( $moduleNames as $moduleName ) {
133 $module = $this->
getModule( $moduleName );
135 $entity = $entitiesByModule[$moduleName];
136 $deps = $depsByEntity[$entity];
138 $module->setFileDependencies( $context, $paths );
144 ResourceLoaderWikiModule::preloadTitleInfo( $context,
$dbr, $moduleNames );
147 $modulesWithMessages = [];
148 foreach ( $moduleNames as $moduleName ) {
149 $module = $this->
getModule( $moduleName );
150 if ( $module && $module->getMessages() ) {
151 $modulesWithMessages[$moduleName] = $module;
157 $blobs = $store->getBlobs( $modulesWithMessages,
$lang );
158 foreach ( $blobs as $moduleName =>
$blob ) {
159 $modulesWithMessages[$moduleName]->setMessageBlob(
$blob,
$lang );
180 public static function filter( $filter, $data, array $options = [] ) {
181 if ( strpos( $data, self::FILTER_NOMIN ) !==
false ) {
185 if ( isset( $options[
'cache'] ) && $options[
'cache'] ===
false ) {
189 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
192 $key =
$cache->makeGlobalKey(
193 'resourceloader-filter',
199 $result =
$cache->get( $key );
200 if ( $result ===
false ) {
201 $stats->increment(
"resourceloader_cache.$filter.miss" );
203 $cache->set( $key, $result, 24 * 3600 );
205 $stats->increment(
"resourceloader_cache.$filter.hit" );
207 if ( $result ===
null ) {
221 $data = trim( $data );
224 $data = ( $filter ===
'minify-css' )
227 }
catch ( Exception $e ) {
228 MWExceptionHandler::logException( $e );
243 LoggerInterface
$logger =
null,
246 $this->logger =
$logger ?:
new NullLogger();
247 $services = MediaWikiServices::getInstance();
250 wfDeprecated( __METHOD__ .
' without a Config instance',
'1.34' );
251 $config = $services->getMainConfig();
255 $this->hookContainer = $services->getHookContainer();
256 $this->hookRunner =
new HookRunner( $this->hookContainer );
259 $this->
addSource(
'local', $config->
get(
'LoadScript' ) );
262 $this->
register(
'startup', [
'class' => ResourceLoaderStartUpModule::class ] );
265 new MessageBlobStore( $this, $this->logger, $services->getMainWANObjectCache() )
337 public function register( $name, array $info = null ) {
339 $registrations = is_array( $name ) ? $name : [ $name => $info ];
340 foreach ( $registrations as $name => $info ) {
342 if ( isset( $this->moduleInfos[$name] ) ) {
344 $this->logger->warning(
345 'ResourceLoader duplicate registration warning. ' .
346 'Another module has already been registered as ' . $name
351 if ( !self::isValidModuleName( $name ) ) {
352 throw new InvalidArgumentException(
"ResourceLoader module name '$name' is invalid, "
353 .
"see ResourceLoader::isValidModuleName()" );
355 if ( !is_array( $info ) ) {
356 throw new InvalidArgumentException(
357 'Invalid module info for "' . $name .
'": expected array, got ' . gettype( $info )
362 $this->moduleInfos[$name] = $info;
367 foreach ( $this->moduleSkinStyles as $skinName => $skinStyles ) {
369 if ( isset( $this->moduleInfos[$name][
'skinStyles'][$skinName] ) ) {
375 if ( isset( $skinStyles[$name] ) ) {
376 $paths = (array)$skinStyles[$name];
378 } elseif ( isset( $skinStyles[
'+' . $name] ) ) {
379 $paths = (array)$skinStyles[
'+' . $name];
380 $styleFiles = isset( $this->moduleInfos[$name][
'skinStyles'][
'default'] ) ?
381 (array)$this->moduleInfos[$name][
'skinStyles'][
'default'] :
389 list( $localBasePath, $remoteBasePath ) =
392 foreach ( $paths as
$path ) {
396 $this->moduleInfos[$name][
'skinStyles'][$skinName] = $styleFiles;
409 if ( $this->config->get(
'EnableJavaScriptTest' ) !==
true ) {
410 throw new MWException(
'Attempt to register JavaScript test modules '
411 .
'but <code>$wgEnableJavaScriptTest</code> is false. '
412 .
'Edit your <code>LocalSettings.php</code> to enable it.' );
416 $testModulesMeta = [
'qunit' => [] ];
418 $this->hookRunner->onResourceLoaderTestModules( $testModulesMeta, $this );
419 $extRegistry = ExtensionRegistry::getInstance();
421 $testModules = $testModulesMeta[
'qunit']
422 + $extRegistry->getAttribute(
'QUnitTestModules' );
425 foreach ( $testModules as $name => &$module ) {
427 if ( isset( $module[
'dependencies'] ) && is_string( $module[
'dependencies'] ) ) {
428 $module[
'dependencies'] = [ $module[
'dependencies'] ];
432 $module[
'dependencies'][] =
'mediawiki.qunit-testrunner';
439 $testModules = ( include
"$IP/tests/qunit/QUnitTestResources.php" ) + $testModules;
442 $this->
register( $testModules );
456 public function addSource( $sources, $loadUrl =
null ) {
457 if ( !is_array( $sources ) ) {
458 $sources = [ $sources => $loadUrl ];
460 foreach ( $sources as $id =>
$source ) {
462 if ( isset( $this->sources[$id] ) ) {
463 throw new RuntimeException(
'Cannot register source ' . $id .
' twice' );
468 if ( !isset(
$source[
'loadScript'] ) ) {
469 throw new InvalidArgumentException(
'Each source must have a "loadScript" key' );
484 return array_keys( $this->moduleInfos );
495 return $this->testSuiteModuleNames;
506 return isset( $this->moduleInfos[$name] );
521 if ( !isset( $this->modules[$name] ) ) {
522 if ( !isset( $this->moduleInfos[$name] ) ) {
527 $info = $this->moduleInfos[$name];
528 if ( isset( $info[
'factory'] ) ) {
530 $object = call_user_func( $info[
'factory'], $info );
532 $class = $info[
'class'] ?? ResourceLoaderFileModule::class;
534 $object =
new $class( $info );
536 $object->setConfig( $this->getConfig() );
537 $object->setLogger( $this->logger );
538 $object->setHookContainer( $this->hookContainer );
539 $object->setName( $name );
540 $object->setDependencyAccessCallbacks(
541 [ $this,
'loadModuleDependenciesInternal' ],
542 [ $this,
'saveModuleDependenciesInternal' ]
544 $this->modules[$name] = $object;
547 return $this->modules[$name];
557 $deps = $this->depStore->retrieve( self::RL_DEP_STORE_PREFIX,
"$moduleName|$variant" );
570 $hasPendingUpdate = (bool)$this->depStoreUpdateBuffer;
571 $entity =
"$moduleName|$variant";
573 if ( array_diff( $paths, $priorPaths ) || array_diff( $priorPaths, $paths ) ) {
576 $deps = $this->depStore->newEntityDependencies( $paths, time() );
577 $this->depStoreUpdateBuffer[$entity] = $deps;
579 $this->depStoreUpdateBuffer[$entity] =
null;
581 } elseif ( $priorPaths ) {
583 $this->depStoreUpdateBuffer[$entity] =
'*';
587 if ( !$hasPendingUpdate ) {
588 DeferredUpdates::addCallableUpdate(
function () {
589 $updatesByEntity = $this->depStoreUpdateBuffer;
590 $this->depStoreUpdateBuffer = [];
591 $cache = ObjectCache::getLocalClusterInstance();
597 foreach ( $updatesByEntity as $entity => $update ) {
598 $lockKey =
$cache->makeKey(
'rl-deps', $entity );
599 $scopeLocks[$entity] =
$cache->getScopedLock( $lockKey, 0 );
600 if ( !$scopeLocks[$entity] ) {
604 } elseif ( $update ===
null ) {
605 $entitiesUnreg[] = $entity;
606 } elseif ( $update ===
'*' ) {
607 $entitiesRenew[] = $entity;
609 $depsByEntity[$entity] = $update;
613 $ttl = self::RL_MODULE_DEP_TTL;
614 $this->depStore->storeMulti( self::RL_DEP_STORE_PREFIX, $depsByEntity, $ttl );
615 $this->depStore->remove( self::RL_DEP_STORE_PREFIX, $entitiesUnreg );
616 $this->depStore->renew( self::RL_DEP_STORE_PREFIX, $entitiesRenew, $ttl );
628 if ( !isset( $this->moduleInfos[$name] ) ) {
631 $info = $this->moduleInfos[$name];
632 return !isset( $info[
'factory'] ) && (
634 !isset( $info[
'class'] ) ||
636 $info[
'class'] === ResourceLoaderFileModule::class ||
637 is_subclass_of( $info[
'class'], ResourceLoaderFileModule::class )
647 return $this->sources;
659 if ( !isset( $this->sources[
$source] ) ) {
660 throw new UnexpectedValueException(
"Unknown source '$source'" );
662 return $this->sources[
$source];
668 public const HASH_LENGTH = 5;
733 $hash = hash(
'fnv132', $value );
737 Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
753 MWExceptionHandler::logException( $e );
754 $this->logger->warning(
756 $context + [
'exception' => $e ]
758 $this->errors[] = self::formatExceptionNoComment( $e );
770 if ( !$moduleNames ) {
773 $hashes = array_map(
function ( $module ) use ( $context ) {
775 return $this->getModule( $module )->getVersionHash( $context );
776 }
catch ( Exception $e ) {
779 $this->outputErrorAndLog( $e,
780 'Calculating version for "{module}" failed: {exception}',
788 return self::makeHash( implode(
'',
$hashes ) );
807 wfDeprecated( __METHOD__ .
' without $modules',
'1.34' );
817 if ( !$this->getModule( $name ) ) {
824 return $this->getCombinedVersion( $context, $filtered );
842 $this->measureResponseTime( RequestContext::getMain()->getTiming() );
848 $module = $this->getModule( $name );
852 if ( $module->getGroup() ===
'private' ) {
854 $this->logger->debug(
"Request for private module '$name' denied" );
855 $this->errors[] =
"Cannot build private module \"$name\"";
866 $this->preloadModuleInfo( array_keys(
$modules ), $context );
867 }
catch ( Exception $e ) {
868 $this->outputErrorAndLog( $e,
'Preloading module info failed: {exception}' );
874 $versionHash = $this->getCombinedVersion( $context, array_keys(
$modules ) );
875 }
catch ( Exception $e ) {
876 $this->outputErrorAndLog( $e,
'Calculating version hash failed: {exception}' );
881 $etag =
'W/"' . $versionHash .
'"';
884 if ( $this->tryRespondNotModified( $context, $etag ) ) {
889 if ( $this->config->get(
'UseFileCache' ) ) {
891 if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
899 $response = $this->makeModuleResponse( $context,
$modules, $missing );
904 $warnings = ob_get_contents();
905 if ( strlen( $warnings ) ) {
906 $this->errors[] = $warnings;
916 if ( $fileCache->isCacheWorthy() ) {
918 $fileCache->saveText( $response );
920 $fileCache->incrMissesRecent( $context->
getRequest() );
924 $this->sendResponseHeaders( $context, $etag, (
bool)$this->errors, $this->extraHeaders );
931 $response = implode(
"\n\n", $this->errors );
932 } elseif ( $this->errors ) {
933 $errorText = implode(
"\n\n", $this->errors );
934 $errorResponse = self::makeComment( $errorText );
936 $errorResponse .=
'if (window.console && console.error) { console.error('
942 $response = $errorResponse . $response;
950 DeferredUpdates::addCallableUpdate(
function () use ( $timing ) {
951 $measure = $timing->
measure(
'responseTime',
'requestStart',
'requestShutdown' );
952 if ( $measure !==
false ) {
953 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
954 $stats->timing(
'resourceloader.responseTime', $measure[
'duration'] * 1000 );
973 $rlMaxage = $this->config->get(
'ResourceLoaderMaxage' );
982 $maxage = $rlMaxage[
'unversioned'];
986 $maxage = $rlMaxage[
'versioned'];
991 header(
'Content-Type: text/plain; charset=utf-8' );
993 $context->
getImageObj()->sendResponseHeaders( $context );
995 } elseif ( $context->
getOnly() ===
'styles' ) {
996 header(
'Content-Type: text/css; charset=utf-8' );
997 header(
'Access-Control-Allow-Origin: *' );
999 header(
'Content-Type: text/javascript; charset=utf-8' );
1003 header(
'ETag: ' . $etag );
1006 header(
'Cache-Control: private, no-cache, must-revalidate' );
1007 header(
'Pragma: no-cache' );
1009 header(
"Cache-Control: public, max-age=$maxage, s-maxage=$maxage" );
1010 header(
'Expires: ' . ConvertibleTimestamp::convert( TS_RFC2822, time() + $maxage ) );
1012 foreach ( $extra as
$header ) {
1030 $clientKeys = $context->
getRequest()->getHeader(
'If-None-Match', WebRequest::GETHEADER_LIST );
1032 if ( $clientKeys !==
false && !$context->
getDebug() && in_array( $etag, $clientKeys ) ) {
1044 HttpStatus::header( 304 );
1046 $this->sendResponseHeaders( $context, $etag,
false );
1065 $rlMaxage = $this->config->get(
'ResourceLoaderMaxage' );
1070 ? $rlMaxage[
'unversioned']
1071 : $rlMaxage[
'versioned'];
1073 $minTime = time() - $maxage;
1074 $good = $fileCache->
isCacheGood( ConvertibleTimestamp::convert( TS_MW, $minTime ) );
1085 $this->sendResponseHeaders( $context, $etag,
false );
1090 $warnings = ob_get_contents();
1091 if ( strlen( $warnings ) ) {
1092 $response = self::makeComment( $warnings ) . $response;
1097 echo $response .
"\n/* Cached {$ts} */";
1115 $encText = str_replace(
'*/',
'* /', $text );
1116 return "/*\n$encText\n*/\n";
1126 return self::makeComment( self::formatExceptionNoComment( $e ) );
1140 return MWExceptionHandler::getPublicLogMessage( $e );
1143 return MWExceptionHandler::getLogMessage( $e ) .
1145 MWExceptionHandler::getRedactedTraceAsString( $e );
1160 array
$modules, array $missing = []
1165 if (
$modules === [] && $missing === [] ) {
1175 $data = $image->getImageData( $context );
1176 if ( $data ===
false ) {
1178 $this->errors[] =
'Image generation failed';
1183 foreach ( $missing as $name ) {
1184 $states[$name] =
'missing';
1187 $filter = $context->
getOnly() ===
'styles' ?
'minify-css' :
'minify-js';
1189 foreach (
$modules as $name => $module ) {
1191 $content = $module->getModuleContent( $context );
1192 $implementKey = $name .
'@' . $module->getVersionHash( $context );
1195 if ( isset(
$content[
'headers'] ) ) {
1196 $this->extraHeaders = array_merge( $this->extraHeaders,
$content[
'headers'] );
1200 switch ( $context->
getOnly() ) {
1203 if ( is_string( $scripts ) ) {
1205 $strContent = $scripts;
1206 } elseif ( is_array( $scripts ) ) {
1208 $strContent = self::makeLoaderImplementScript(
1223 $strContent = isset( $styles[
'css'] ) ? implode(
'', $styles[
'css'] ) :
'';
1226 $scripts =
$content[
'scripts'] ??
'';
1227 if ( is_string( $scripts ) ) {
1228 if ( $name ===
'site' || $name ===
'user' ) {
1234 $scripts = self::filter(
'minify-js', $scripts );
1240 $strContent = self::makeLoaderImplementScript(
1252 $strContent = self::filter( $filter, $strContent );
1256 $strContent = $this->ensureNewline( $strContent );
1259 if ( $context->
getOnly() ===
'scripts' ) {
1261 $out .= $this->ensureNewline( $strContent );
1263 $out .= $strContent;
1266 }
catch ( Exception $e ) {
1267 $this->outputErrorAndLog( $e,
'Generating module package failed: {exception}' );
1270 $states[$name] =
'error';
1280 foreach (
$modules as $name => $module ) {
1281 $states[$name] =
'ready';
1287 $stateScript = self::makeLoaderStateScript( $context, $states );
1289 $stateScript = self::filter(
'minify-js', $stateScript );
1292 $out = $this->ensureNewline( $out ) . $stateScript;
1294 } elseif ( $states ) {
1295 $this->errors[] =
'Problematic modules: '
1307 private function ensureNewline( $str ) {
1308 $end = substr( $str, -1 );
1309 if ( $end ===
false || $end ===
'' || $end ===
"\n" ) {
1321 public function getModulesByMessage( $messageKey ) {
1323 foreach ( $this->getModuleNames() as $moduleName ) {
1324 $module = $this->getModule( $moduleName );
1325 if ( in_array( $messageKey, $module->getMessages() ) ) {
1326 $moduleNames[] = $moduleName;
1329 return $moduleNames;
1349 private static function makeLoaderImplementScript(
1353 if ( $scripts->value ===
'' ) {
1355 } elseif ( $context->
getDebug() ) {
1356 $scripts =
new XmlJsCode(
"function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1358 $scripts =
new XmlJsCode(
'function($,jQuery,require,module){' . $scripts->value .
'}' );
1360 } elseif ( is_array( $scripts ) && isset( $scripts[
'files'] ) ) {
1361 $files = $scripts[
'files'];
1365 if (
$file[
'type'] ===
'script' ) {
1368 $file =
new XmlJsCode(
"function ( require, module ) {\n{$file['content']}\n}" );
1377 'main' => $scripts[
'main'],
1380 } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1381 throw new InvalidArgumentException(
'Script must be a string or an array of URLs' );
1394 self::trimArray( $module );
1405 public static function makeMessageSetScript( $messages ) {
1406 return 'mw.messages.set('
1407 . self::encodeJsonForScript( (
object)$messages )
1418 public static function makeCombinedStyles( array $stylePairs ) {
1420 foreach ( $stylePairs as $media => $styles ) {
1424 $styles = (array)$styles;
1425 foreach ( $styles as $style ) {
1426 $style = trim( $style );
1428 if ( $style !==
'' ) {
1433 if ( $media ===
'' || $media ==
'all' ) {
1435 } elseif ( is_string( $media ) ) {
1436 $out[] =
"@media $media {\n" . str_replace(
"\n",
"\n\t",
"\t" . $style ) .
"}";
1454 public static function encodeJsonForScript( $data ) {
1464 $jsonFlags = JSON_UNESCAPED_SLASHES |
1465 JSON_UNESCAPED_UNICODE |
1468 if ( self::inDebugMode() ) {
1469 $jsonFlags |= JSON_PRETTY_PRINT;
1471 return json_encode( $data, $jsonFlags );
1486 public static function makeLoaderStateScript(
1489 return 'mw.loader.state('
1494 private static function isEmptyObject( stdClass $obj ) {
1495 foreach ( $obj as $key => $value ) {
1514 private static function trimArray( array &$array ) : void {
1515 $i = count( $array );
1517 if ( $array[$i] ===
null
1518 || $array[$i] === []
1519 || ( $array[$i] instanceof
XmlJsCode && $array[$i]->value ===
'{}' )
1520 || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1522 unset( $array[$i] );
1556 public static function makeLoaderRegisterScript(
1563 foreach (
$modules as $i => &$module ) {
1565 $index[$module[0]] = $i;
1568 if ( isset( $module[2] ) ) {
1569 foreach ( $module[2] as &$dependency ) {
1570 if ( isset( $index[$dependency] ) ) {
1572 $dependency = $index[$dependency];
1578 array_walk(
$modules, [ self::class,
'trimArray' ] );
1580 return 'mw.loader.register('
1598 public static function makeLoaderSourcesScript(
1601 return 'mw.loader.addSource('
1612 public static function makeLoaderConditionalScript( $script ) {
1614 return '(RLQ=window.RLQ||[]).push(function(){' .
1615 trim( $script ) .
'});';
1626 public static function makeInlineCodeWithModule(
$modules, $script ) {
1628 return '(RLQ=window.RLQ||[]).push(['
1629 . self::encodeJsonForScript(
$modules ) .
','
1630 .
'function(){' . trim( $script ) .
'}'
1645 public static function makeInlineScript( $script, $nonce =
null ) {
1646 $js = self::makeLoaderConditionalScript( $script );
1648 if ( $nonce ===
null ) {
1649 wfWarn( __METHOD__ .
" did not get nonce. Will break CSP" );
1650 } elseif ( $nonce !==
false ) {
1654 $escNonce =
' nonce="' . htmlspecialchars( $nonce ) .
'"';
1657 return new WrappedString(
1658 Html::inlineScript( $js, $nonce ),
1659 "<script$escNonce>(RLQ=window.RLQ||[]).push(function(){",
1672 public static function makeConfigSetScript( array $configuration ) {
1673 $json = self::encodeJsonForScript( $configuration );
1674 if ( $json ===
false ) {
1676 'JSON serialization of config data failed. ' .
1677 'This usually means the config data is not valid UTF-8.'
1680 return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) .
');';
1682 return "mw.config.set($json);";
1698 public static function makePackedModulesString( array
$modules ) {
1701 $pos = strrpos( $module,
'.' );
1702 $prefix = $pos ===
false ?
'' : substr( $module, 0, $pos );
1703 $suffix = $pos ===
false ? $module : substr( $module, $pos + 1 );
1704 $moduleMap[$prefix][] = $suffix;
1708 foreach ( $moduleMap as $prefix => $suffixes ) {
1709 $p = $prefix ===
'' ?
'' : $prefix .
'.';
1710 $arr[] = $p . implode(
',', $suffixes );
1712 return implode(
'|', $arr );
1726 public static function expandModuleNames(
$modules ) {
1728 $exploded = explode(
'|',
$modules );
1729 foreach ( $exploded as $group ) {
1730 if ( strpos( $group,
',' ) ===
false ) {
1736 $pos = strrpos( $group,
'.' );
1737 if ( $pos ===
false ) {
1739 $retval = array_merge( $retval, explode(
',', $group ) );
1742 $prefix = substr( $group, 0, $pos );
1743 $suffixes = explode(
',', substr( $group, $pos + 1 ) );
1744 foreach ( $suffixes as $suffix ) {
1745 $retval[] =
"$prefix.$suffix";
1763 public static function inDebugMode() {
1764 if ( self::$debugMode ===
null ) {
1766 self::$debugMode =
$wgRequest->getFuzzyBool(
'debug',
1770 return self::$debugMode;
1783 public static function clearCache() {
1784 self::$debugMode =
null;
1797 array $extraQuery = []
1799 $query = self::createLoaderQuery( $context, $extraQuery );
1800 $script = $this->getLoadScript(
$source );
1814 protected static function createLoaderQuery(
1817 return self::makeLoaderQuery(
1825 $context->
getRequest()->getBool(
'printable' ),
1826 $context->
getRequest()->getBool(
'handheld' ),
1847 public static function makeLoaderQuery( array
$modules,
$lang, $skin, $user =
null,
1848 $version =
null,
$debug =
false, $only =
null, $printable =
false,
1849 $handheld =
false, array $extraQuery = []
1852 'modules' => self::makePackedModulesString(
$modules ),
1858 if (
$lang !== ResourceLoaderContext::DEFAULT_LANG ) {
1859 $query[
'lang'] =
$lang;
1861 if ( $skin !== ResourceLoaderContext::DEFAULT_SKIN ) {
1862 $query[
'skin'] = $skin;
1865 $query[
'debug'] =
'true';
1867 if ( $user !==
null ) {
1868 $query[
'user'] = $user;
1870 if ( $version !==
null ) {
1871 $query[
'version'] = $version;
1873 if ( $only !==
null ) {
1874 $query[
'only'] = $only;
1877 $query[
'printable'] = 1;
1880 $query[
'handheld'] = 1;
1882 $query += $extraQuery;
1898 public static function isValidModuleName( $moduleName ) {
1899 $len = strlen( $moduleName );
1900 return $len <= 255 && strcspn( $moduleName,
'!,|', 0, $len ) === $len;
1913 public function getLessCompiler( $vars = [] ) {
1918 if ( !class_exists(
'Less_Parser' ) ) {
1919 throw new MWException(
'MediaWiki requires the less.php parser' );
1922 $parser =
new Less_Parser;
1923 $parser->ModifyVars( $vars );
1924 $parser->SetImportDirs( [
1925 "$IP/resources/src/mediawiki.less/" =>
'',
1927 $parser->SetOption(
'relativeUrls',
false );
1940 public static function getSiteConfigSettings(
1947 $namespaceIds = $contLang->getNamespaceIds();
1948 $caseSensitiveNamespaces = [];
1950 foreach ( $nsInfo->getCanonicalNamespaces() as $index => $name ) {
1951 $namespaceIds[$contLang->lc( $name )] = $index;
1952 if ( !$nsInfo->isCapitalized( $index ) ) {
1953 $caseSensitiveNamespaces[] = $index;
1957 $illegalFileChars = $conf->
get(
'IllegalFileChars' );
1966 'stylepath' => $conf->
get(
'StylePath' ),
1967 'wgArticlePath' => $conf->
get(
'ArticlePath' ),
1968 'wgScriptPath' => $conf->
get(
'ScriptPath' ),
1969 'wgScript' => $conf->
get(
'Script' ),
1970 'wgSearchType' => $conf->
get(
'SearchType' ),
1971 'wgVariantArticlePath' => $conf->
get(
'VariantArticlePath' ),
1972 'wgServer' => $conf->
get(
'Server' ),
1973 'wgServerName' => $conf->
get(
'ServerName' ),
1975 'wgContentLanguage' => $contLang->getCode(),
1977 'wgFormattedNamespaces' => $contLang->getFormattedNamespaces(),
1978 'wgNamespaceIds' => $namespaceIds,
1979 'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
1980 'wgSiteName' => $conf->
get(
'Sitename' ),
1981 'wgDBname' => $conf->
get(
'DBname' ),
1983 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
1984 'wgCommentByteLimit' =>
null,
1986 'wgExtensionAssetsPath' => $conf->
get(
'ExtensionAssetsPath' ),
1997 'wgActionPaths' => (object)$conf->
get(
'ActionPaths' ),
1999 'wgTranslateNumerals' => $conf->
get(
'TranslateNumerals' ),
2001 'wgExtraSignatureNamespaces' => $conf->
get(
'ExtraSignatureNamespaces' ),
2003 'wgCookiePrefix' => $conf->
get(
'CookiePrefix' ),
2004 'wgCookieDomain' => $conf->
get(
'CookieDomain' ),
2005 'wgCookiePath' => $conf->
get(
'CookiePath' ),
2006 'wgCookieExpiration' => $conf->
get(
'CookieExpiration' ),
2011 'wgForeignUploadTargets' => $conf->
get(
'ForeignUploadTargets' ),
2012 'wgEnableUploads' => $conf->
get(
'EnableUploads' ),
2015 Hooks::runner()->onResourceLoaderGetConfigVars( $vars, $skin, $conf );
$wgResourceLoaderDebug
The default debug mode (on/off) for of ResourceLoader requests.
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
const MW_VERSION
The running version of MediaWiki.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
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 $function is deprecated.
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
if(! $wgDBerrorLogTZ) $wgRequest
static minify( $css)
Removes whitespace from CSS data.
isCacheGood( $timestamp='')
Check if up to date cache file exists.
fetchText()
Get the uncompressed text from the cache.
cacheTimestamp()
Get the last-modified timestamp of the cache file.
Simple store for keeping values in an associative array for the current process.
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
static minify( $s)
Returns minified JavaScript code.
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
This class generates message blobs for use by ResourceLoader.
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
ResourceLoader request result caching in the file system.
static useFileCache(ResourceLoaderContext $context)
Check if an RL request can be cached.
static newFromContext(ResourceLoaderContext $context)
Construct an ResourceFileCache from a context.
Context object that contains information about the state of a specific ResourceLoader web request.
getImageObj()
If this is a request for an image, get the ResourceLoaderImage object.
encodeJson( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
static getVary(ResourceLoaderContext $context)
Get vary string.
ResourceLoader is a loading system for JavaScript and CSS resources.
setDependencyStore(DependencyStore $tracker)
addSource( $sources, $loadUrl=null)
Add a foreign source of modules.
static formatException(Throwable $e)
Handle exception display.
makeVersionQuery(ResourceLoaderContext $context, array $modules=null)
Get the expected value of the 'version' query parameter.
tryRespondFromFileCache(ResourceFileCache $fileCache, ResourceLoaderContext $context, $etag)
Send out code for a response from file cache if possible.
isFileModule( $name)
Whether the module is a ResourceLoaderFileModule or subclass thereof.
getModuleNames()
Get a list of module names.
setMessageBlobStore(MessageBlobStore $blobStore)
makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing=[])
Generate code for a response.
setLogger(LoggerInterface $logger)
HookContainer $hookContainer
getLoadScript( $source)
Get the URL to the load.php endpoint for the given ResourceLoader source.
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors, array $extra=[])
Send main response headers to the client.
array[] $moduleInfos
Map of (module name => associative info array)
tryRespondNotModified(ResourceLoaderContext $context, $etag)
Respond with HTTP 304 Not Modified if appropiate.
__construct(Config $config=null, LoggerInterface $logger=null, DependencyStore $tracker=null)
Register core modules and runs registration hooks.
getSources()
Get the list of sources.
loadModuleDependenciesInternal( $moduleName, $variant)
DependencyStore $depStore
string[] $extraHeaders
Extra HTTP response headers from modules loaded in makeModuleResponse()
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
array $testModuleNames
Associative array mapping framework ids to a list of names of test suite modules like [ 'qunit' => [ ...
getTestSuiteModuleNames()
Get a list of module names with QUnit test suites.
saveModuleDependenciesInternal( $moduleName, $variant, $paths, $priorPaths)
string[] $testSuiteModuleNames
List of module names that contain QUnit test suites.
getModule( $name)
Get the ResourceLoaderModule object for a given module name.
array $sources
Map of (source => path); E.g.
isModuleRegistered( $name)
Check whether a ResourceLoader module is registered.
static applyFilter( $filter, $data)
measureResponseTime(Timing $timing)
setModuleSkinStyles(array $moduleSkinStyles)
ResourceLoaderModule[] $modules
Map of (module name => ResourceLoaderModule)
static formatExceptionNoComment(Throwable $e)
Handle exception display.
array $moduleSkinStyles
Styles that are skin-specific and supplement or replace the default skinStyles of a FileModule.
array $depStoreUpdateBuffer
Map of (module-variant => buffered DependencyStore updates)
MessageBlobStore $blobStore
array $errors
Errors accumulated during current respond() call.
static makeComment( $text)
Generate a CSS or JS comment block.
getCombinedVersion(ResourceLoaderContext $context, array $moduleNames)
Helper method to get and combine versions of multiple modules.
respond(ResourceLoaderContext $context)
Output a response to a load request, including the content-type header.
static makeHash( $value)
Create a hash for module versioning purposes.
static filter( $filter, $data, array $options=[])
Run JavaScript or CSS data through a filter, caching the filtered result for future calls.
preloadModuleInfo(array $moduleNames, ResourceLoaderContext $context)
Load information stored in the database and dependency tracking store about modules.
An interface to help developers measure the performance of their applications.
measure( $measureName, $startMark='requestStart', $endMark=null)
This method stores the duration between two marks along with the associated name (a "measure").
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
static getCurrentWikiId()
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
static encodeObject( $obj, $pretty=false)
Encode an object containing XmlJsCode objects.
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
if(!isset( $args[0])) $lang
if(count( $args)< 1) $tracker