34 use InvalidArgumentException;
48 use Psr\Log\LoggerAwareInterface;
49 use Psr\Log\LoggerInterface;
50 use Psr\Log\NullLogger;
56 use UnexpectedValueException;
61 use Wikimedia\Minify\CSSMin;
62 use Wikimedia\Minify\JavaScriptMinifier;
64 use Wikimedia\RequestTimeout\TimeoutException;
65 use Wikimedia\ScopedCallback;
66 use Wikimedia\Timestamp\ConvertibleTimestamp;
67 use Wikimedia\WrappedString;
93 public const CACHE_VERSION = 9;
95 public const FILTER_NOMIN =
'/*@nomin*/';
98 private const RL_DEP_STORE_PREFIX =
'ResourceLoaderModule';
100 private const RL_MODULE_DEP_TTL = BagOStuff::TTL_YEAR;
102 private const MAXAGE_RECOVER = 60;
116 private $hookContainer;
122 private $maxageVersioned;
124 private $maxageUnversioned;
126 private $useFileCache;
131 private $moduleInfos = [];
133 private $testModuleNames = [];
135 private $sources = [];
144 private $depStoreUpdateBuffer = [];
149 private $moduleSkinStyles = [];
176 LoggerInterface $logger =
null,
180 $this->loadScript = $params[
'loadScript'] ??
'/load.php';
181 $this->maxageVersioned = $params[
'maxageVersioned'] ?? 30 * 24 * 60 * 60;
182 $this->maxageUnversioned = $params[
'maxageUnversioned'] ?? 5 * 60;
183 $this->useFileCache = $params[
'useFileCache'] ??
false;
185 $this->config = $config;
186 $this->logger = $logger ?:
new NullLogger();
189 $this->hookContainer = $services->getHookContainer();
190 $this->hookRunner =
new HookRunner( $this->hookContainer );
193 $this->
addSource(
'local', $this->loadScript );
196 $this->
register(
'startup', [
'class' => StartUpModule::class ] );
199 new MessageBlobStore( $this, $this->logger, $services->getMainWANObjectCache() )
210 return $this->config;
218 $this->logger = $logger;
226 return $this->logger;
234 return $this->blobStore;
242 $this->blobStore = $blobStore;
258 $this->moduleSkinStyles = $moduleSkinStyles;
272 public function register( $name, array $info = null ) {
274 $registrations = is_array( $name ) ? $name : [ $name => $info ];
275 foreach ( $registrations as $name => $info ) {
277 if ( isset( $this->moduleInfos[$name] ) ) {
279 $this->logger->warning(
280 'ResourceLoader duplicate registration warning. ' .
281 'Another module has already been registered as ' . $name
286 if ( !self::isValidModuleName( $name ) ) {
287 throw new InvalidArgumentException(
"ResourceLoader module name '$name' is invalid, "
288 .
"see ResourceLoader::isValidModuleName()" );
290 if ( !is_array( $info ) ) {
291 throw new InvalidArgumentException(
292 'Invalid module info for "' . $name .
'": expected array, got ' . gettype( $info )
297 $this->moduleInfos[$name] = $info;
307 $testModules = $extRegistry->
getAttribute(
'QUnitTestModules' );
309 $testModuleNames = [];
310 foreach ( $testModules as $name => &$module ) {
312 if ( isset( $module[
'dependencies'] ) && is_string( $module[
'dependencies'] ) ) {
313 $module[
'dependencies'] = [ $module[
'dependencies'] ];
317 $module[
'dependencies'][] =
'mediawiki.qunit-testrunner';
320 $testModuleNames[] = $name;
324 $testModules = ( include MW_INSTALL_PATH .
'/tests/qunit/QUnitTestResources.php' ) + $testModules;
325 $testModuleNames[] =
'test.MediaWiki';
327 $this->
register( $testModules );
328 $this->testModuleNames = $testModuleNames;
341 public function addSource( $sources, $loadUrl =
null ) {
342 if ( !is_array( $sources ) ) {
343 $sources = [ $sources => $loadUrl ];
345 foreach ( $sources as $id =>
$source ) {
347 if ( isset( $this->sources[$id] ) ) {
348 throw new RuntimeException(
'Cannot register source ' . $id .
' twice' );
353 if ( !isset(
$source[
'loadScript'] ) ) {
354 throw new InvalidArgumentException(
'Each source must have a "loadScript" key' );
367 return array_keys( $this->moduleInfos );
378 return $this->testModuleNames;
389 return isset( $this->moduleInfos[$name] );
404 if ( !isset( $this->modules[$name] ) ) {
405 if ( !isset( $this->moduleInfos[$name] ) ) {
410 $info = $this->moduleInfos[$name];
411 if ( isset( $info[
'factory'] ) ) {
413 $object = call_user_func( $info[
'factory'], $info );
415 $class = $info[
'class'] ?? FileModule::class;
417 $object =
new $class( $info );
419 $object->setConfig( $this->
getConfig() );
420 $object->setLogger( $this->logger );
421 $object->setHookContainer( $this->hookContainer );
422 $object->setName( $name );
423 $object->setDependencyAccessCallbacks(
424 [ $this,
'loadModuleDependenciesInternal' ],
425 [ $this,
'saveModuleDependenciesInternal' ]
427 $object->setSkinStylesOverride( $this->moduleSkinStyles );
428 $this->modules[$name] = $object;
431 return $this->modules[$name];
443 $entitiesByModule = [];
444 foreach ( $moduleNames as $moduleName ) {
445 $entitiesByModule[$moduleName] =
"$moduleName|$vary";
447 $depsByEntity = $this->depStore->retrieveMulti(
448 self::RL_DEP_STORE_PREFIX,
452 foreach ( $moduleNames as $moduleName ) {
453 $module = $this->
getModule( $moduleName );
455 $entity = $entitiesByModule[$moduleName];
456 $deps = $depsByEntity[$entity];
458 $module->setFileDependencies( $context, $paths );
467 $modulesWithMessages = [];
468 foreach ( $moduleNames as $moduleName ) {
469 $module = $this->
getModule( $moduleName );
470 if ( $module && $module->getMessages() ) {
471 $modulesWithMessages[$moduleName] = $module;
477 $blobs = $store->getBlobs( $modulesWithMessages,
$lang );
478 foreach ( $blobs as $moduleName =>
$blob ) {
479 $modulesWithMessages[$moduleName]->setMessageBlob(
$blob,
$lang );
490 $deps = $this->depStore->retrieve( self::RL_DEP_STORE_PREFIX,
"$moduleName|$variant" );
503 $hasPendingUpdate = (bool)$this->depStoreUpdateBuffer;
504 $entity =
"$moduleName|$variant";
506 if ( array_diff( $paths, $priorPaths ) || array_diff( $priorPaths, $paths ) ) {
509 $deps = $this->depStore->newEntityDependencies( $paths, time() );
510 $this->depStoreUpdateBuffer[$entity] = $deps;
512 $this->depStoreUpdateBuffer[$entity] =
null;
522 if ( !$hasPendingUpdate ) {
524 $updatesByEntity = $this->depStoreUpdateBuffer;
525 $this->depStoreUpdateBuffer = [];
531 foreach ( $updatesByEntity as $entity => $update ) {
532 $lockKey = $cache->makeKey(
'rl-deps', $entity );
533 $scopeLocks[$entity] = $cache->getScopedLock( $lockKey, 0 );
534 if ( !$scopeLocks[$entity] ) {
539 if ( $update ===
null ) {
540 $entitiesUnreg[] = $entity;
542 $depsByEntity[$entity] = $update;
546 $ttl = self::RL_MODULE_DEP_TTL;
547 $this->depStore->storeMulti( self::RL_DEP_STORE_PREFIX, $depsByEntity, $ttl );
548 $this->depStore->remove( self::RL_DEP_STORE_PREFIX, $entitiesUnreg );
559 return $this->sources;
571 if ( !isset( $this->sources[
$source] ) ) {
572 throw new UnexpectedValueException(
"Unknown source '$source'" );
574 return $this->sources[
$source];
645 $hash = hash(
'fnv132', $value );
649 \Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
666 $this->logger->warning(
668 $context + [
'exception' => $e ]
682 if ( !$moduleNames ) {
685 $hashes = array_map(
function ( $module ) use ( $context ) {
687 return $this->
getModule( $module )->getVersionHash( $context );
688 }
catch ( TimeoutException $e ) {
690 }
catch ( Exception $e ) {
694 'Calculating version for "{module}" failed: {exception}',
762 if ( $module->getGroup() === Module::GROUP_PRIVATE ) {
764 $this->logger->debug(
"Request for private module '$name' denied" );
765 $this->errors[] =
"Cannot build private module \"$name\"";
777 }
catch ( TimeoutException $e ) {
779 }
catch ( Exception $e ) {
788 $etag =
'W/"' . $versionHash .
'"';
796 if ( $this->useFileCache ) {
811 $warnings = ob_get_contents();
812 if ( strlen( $warnings ) ) {
813 $this->errors[] = $warnings;
818 if ( $fileCache && !$this->errors && $missing === [] &&
820 if ( $fileCache->isCacheWorthy() ) {
822 $fileCache->saveText( $response );
824 $fileCache->incrMissesRecent( $context->
getRequest() );
835 $response = implode(
"\n\n", $this->errors );
836 } elseif ( $this->errors ) {
837 $errorText = implode(
"\n\n", $this->errors );
840 $errorResponse .=
'if (window.console && console.error) { console.error('
846 $response = $errorResponse . $response;
859 $statStart = $_SERVER[
'REQUEST_TIME_FLOAT'];
860 return new ScopedCallback(
static function () use ( $statStart ) {
861 $statTiming = microtime(
true ) - $statStart;
863 $stats->timing(
'resourceloader.responseTime', $statTiming * 1000 );
892 $maxage = self::MAXAGE_RECOVER;
893 } elseif ( $context->
getVersion() ===
null ) {
897 $maxage = $this->maxageUnversioned;
901 $maxage = $this->maxageVersioned;
906 header(
'Content-Type: text/plain; charset=utf-8' );
908 $context->
getImageObj()->sendResponseHeaders( $context );
910 } elseif ( $context->
getOnly() ===
'styles' ) {
911 header(
'Content-Type: text/css; charset=utf-8' );
912 header(
'Access-Control-Allow-Origin: *' );
914 header(
'Content-Type: text/javascript; charset=utf-8' );
918 header(
'ETag: ' . $etag );
921 header(
'Cache-Control: private, no-cache, must-revalidate' );
922 header(
'Pragma: no-cache' );
926 $staleDirective = ( $maxage > self::MAXAGE_RECOVER
927 ?
", stale-while-revalidate=" . min( 60, intval( $maxage / 2 ) )
930 header(
"Cache-Control: public, max-age=$maxage, s-maxage=$maxage" . $staleDirective );
931 header(
'Expires: ' . ConvertibleTimestamp::convert( TS_RFC2822, time() + $maxage ) );
933 foreach ( $extra as
$header ) {
953 if ( $clientKeys !==
false && !$context->
getDebug() && in_array( $etag, $clientKeys ) ) {
967 $this->sendResponseHeaders( $context, $etag,
false );
990 ? $this->maxageUnversioned
991 : $this->maxageVersioned;
993 $minTime = time() - $maxage;
994 $good = $fileCache->
isCacheGood( ConvertibleTimestamp::convert( TS_MW, $minTime ) );
1005 $this->sendResponseHeaders( $context, $etag,
false );
1010 $warnings = ob_get_contents();
1011 if ( strlen( $warnings ) ) {
1012 $response = self::makeComment( $warnings ) . $response;
1017 echo $response .
"\n/* Cached {$ts} */";
1035 $encText = str_replace(
'*/',
'* /', $text );
1036 return "/*\n$encText\n*/\n";
1046 return self::makeComment( self::formatExceptionNoComment( $e ) );
1078 array
$modules, array $missing = []
1080 if (
$modules === [] && $missing === [] ) {
1090 $data = $image->getImageData( $context );
1091 if ( $data ===
false ) {
1093 $this->errors[] =
'Image generation failed';
1099 foreach ( $missing as $name ) {
1100 $states[$name] =
'missing';
1104 $filter = $only ===
'styles' ?
'minify-css' :
'minify-js';
1105 $debug = (bool)$context->
getDebug();
1108 foreach (
$modules as $name => $module ) {
1110 $content = $module->getModuleContent( $context );
1111 $implementKey = $name .
'@' . $module->getVersionHash( $context );
1114 if ( isset(
$content[
'headers'] ) ) {
1115 $this->extraHeaders = array_merge( $this->extraHeaders,
$content[
'headers'] );
1122 if ( is_string( $scripts ) ) {
1124 $strContent = $scripts;
1125 } elseif ( is_array( $scripts ) ) {
1127 $strContent = self::makeLoaderImplementScript(
1142 $strContent = isset( $styles[
'css'] ) ? implode(
'', $styles[
'css'] ) :
'';
1145 $scripts =
$content[
'scripts'] ??
'';
1146 if ( is_string( $scripts ) ) {
1147 if ( $name ===
'site' || $name ===
'user' ) {
1153 $scripts = self::filter(
'minify-js', $scripts );
1159 $strContent = self::makeLoaderImplementScript(
1173 $strContent = self::ensureNewline( $strContent );
1175 $strContent = self::filter( $filter, $strContent, [
1179 'cache' => !$module->shouldEmbedModule( $context )
1183 if ( $only ===
'scripts' ) {
1185 $out .= self::ensureNewline( $strContent );
1187 $out .= $strContent;
1189 }
catch ( TimeoutException $e ) {
1191 }
catch ( Exception $e ) {
1192 $this->outputErrorAndLog( $e,
'Generating module package failed: {exception}' );
1195 $states[$name] =
'error';
1202 if (
$modules && $only ===
'scripts' ) {
1205 foreach (
$modules as $name => $module ) {
1206 $states[$name] =
'ready';
1212 $stateScript = self::makeLoaderStateScript( $context, $states );
1214 $stateScript = self::filter(
'minify-js', $stateScript );
1217 $out = self::ensureNewline( $out ) . $stateScript;
1219 } elseif ( $states ) {
1220 $this->errors[] =
'Problematic modules: '
1233 public static function ensureNewline( $str ) {
1234 $end = substr( $str, -1 );
1235 if ( $end ===
false || $end ===
'' || $end ===
"\n" ) {
1247 public function getModulesByMessage( $messageKey ) {
1249 foreach ( $this->getModuleNames() as $moduleName ) {
1250 $module = $this->getModule( $moduleName );
1251 if ( in_array( $messageKey, $module->getMessages() ) ) {
1252 $moduleNames[] = $moduleName;
1255 return $moduleNames;
1275 private static function makeLoaderImplementScript(
1276 Context $context, $name, $scripts, $styles, $messages, $templates
1279 if ( $scripts->value ===
'' ) {
1282 $scripts =
new XmlJsCode(
"function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1284 } elseif ( is_array( $scripts ) && isset( $scripts[
'files'] ) ) {
1285 $files = $scripts[
'files'];
1286 foreach ( $files as &
$file ) {
1289 if (
$file[
'type'] ===
'script' ) {
1296 $file =
new XmlJsCode(
"function ( require, module, exports ) {\n$content}" );
1302 'main' => $scripts[
'main'],
1305 } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1306 throw new InvalidArgumentException(
'Script must be a string or an array of URLs' );
1319 self::trimArray( $module );
1332 public static function makeMessageSetScript( $messages ) {
1333 return 'mw.messages.set('
1334 . self::encodeJsonForScript( (
object)$messages )
1345 public static function makeCombinedStyles( array $stylePairs ) {
1347 foreach ( $stylePairs as $media => $styles ) {
1350 $styles = (array)$styles;
1351 foreach ( $styles as $style ) {
1352 $style = trim( $style );
1354 if ( $style ===
'' ) {
1361 if ( $media ===
'' || $media ==
'all' ) {
1363 } elseif ( is_string( $media ) ) {
1364 $out[] =
"@media $media {\n" . str_replace(
"\n",
"\n\t",
"\t" . $style ) .
"}";
1381 public static function encodeJsonForScript( $data ) {
1391 $jsonFlags = JSON_UNESCAPED_SLASHES |
1392 JSON_UNESCAPED_UNICODE |
1395 if ( self::inDebugMode() ) {
1396 $jsonFlags |= JSON_PRETTY_PRINT;
1398 return json_encode( $data, $jsonFlags );
1413 public static function makeLoaderStateScript(
1414 Context $context, array $states
1416 return 'mw.loader.state('
1417 . $context->encodeJson( $states )
1421 private static function isEmptyObject( stdClass $obj ) {
1422 foreach ( $obj as $key => $value ) {
1441 private static function trimArray( array &$array ): void {
1442 $i = count( $array );
1444 if ( $array[$i] ===
null
1445 || $array[$i] === []
1446 || ( $array[$i] instanceof
XmlJsCode && $array[$i]->value ===
'{}' )
1447 || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1449 unset( $array[$i] );
1481 public static function makeLoaderRegisterScript(
1488 foreach (
$modules as $i => &$module ) {
1490 $index[$module[0]] = $i;
1493 if ( isset( $module[2] ) ) {
1494 foreach ( $module[2] as &$dependency ) {
1495 if ( isset( $index[$dependency] ) ) {
1497 $dependency = $index[$dependency];
1503 array_walk(
$modules, [ self::class,
'trimArray' ] );
1505 return 'mw.loader.register('
1523 public static function makeLoaderSourcesScript(
1524 Context $context, array $sources
1526 return 'mw.loader.addSource('
1527 . $context->encodeJson( $sources )
1537 public static function makeLoaderConditionalScript( $script ) {
1539 return '(RLQ=window.RLQ||[]).push(function(){' .
1540 trim( $script ) .
'});';
1551 public static function makeInlineCodeWithModule(
$modules, $script ) {
1553 return '(RLQ=window.RLQ||[]).push(['
1554 . self::encodeJsonForScript(
$modules ) .
','
1555 .
'function(){' . trim( $script ) .
'}'
1570 public static function makeInlineScript( $script, $nonce =
null ) {
1571 $js = self::makeLoaderConditionalScript( $script );
1573 if ( $nonce ===
null ) {
1574 wfWarn( __METHOD__ .
" did not get nonce. Will break CSP" );
1575 } elseif ( $nonce !==
false ) {
1579 $escNonce =
' nonce="' . htmlspecialchars( $nonce ) .
'"';
1582 return new WrappedString(
1584 "<script$escNonce>(RLQ=window.RLQ||[]).push(function(){",
1597 public static function makeConfigSetScript( array $configuration ) {
1598 $json = self::encodeJsonForScript( $configuration );
1599 if ( $json ===
false ) {
1601 'JSON serialization of config data failed. ' .
1602 'This usually means the config data is not valid UTF-8.'
1605 return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) .
');';
1607 return "mw.config.set($json);";
1623 public static function makePackedModulesString( array
$modules ) {
1626 $pos = strrpos( $module,
'.' );
1627 $prefix = $pos ===
false ?
'' : substr( $module, 0, $pos );
1628 $suffix = $pos ===
false ? $module : substr( $module, $pos + 1 );
1629 $moduleMap[$prefix][] = $suffix;
1633 foreach ( $moduleMap as $prefix => $suffixes ) {
1634 $p = $prefix ===
'' ?
'' : $prefix .
'.';
1635 $arr[] = $p . implode(
',', $suffixes );
1637 return implode(
'|', $arr );
1651 public static function expandModuleNames(
$modules ) {
1653 $exploded = explode(
'|',
$modules );
1654 foreach ( $exploded as $group ) {
1655 if ( strpos( $group,
',' ) ===
false ) {
1662 $pos = strrpos( $group,
'.' );
1663 if ( $pos ===
false ) {
1665 $retval = array_merge( $retval, explode(
',', $group ) );
1669 $prefix = substr( $group, 0, $pos );
1670 $suffixes = explode(
',', substr( $group, $pos + 1 ) );
1671 foreach ( $suffixes as $suffix ) {
1672 $retval[] =
"$prefix.$suffix";
1688 public static function inDebugMode() {
1689 if ( self::$debugMode ===
null ) {
1691 $resourceLoaderDebug = MediaWikiServices::getInstance()->getMainConfig()->get(
1692 MainConfigNames::ResourceLoaderDebug );
1694 $wgRequest->getCookie(
'resourceLoaderDebug',
'', $resourceLoaderDebug ?
'true' :
'' )
1696 self::$debugMode = Context::debugFromString( $str );
1698 return self::$debugMode;
1711 public static function clearCache() {
1712 self::$debugMode =
null;
1724 public function createLoaderURL(
$source, Context $context,
1725 array $extraQuery = []
1727 $query = self::createLoaderQuery( $context, $extraQuery );
1728 $script = $this->getLoadScript(
$source );
1742 protected static function createLoaderQuery(
1743 Context $context, array $extraQuery = []
1745 return self::makeLoaderQuery(
1746 $context->getModules(),
1747 $context->getLanguage(),
1748 $context->getSkin(),
1749 $context->getUser(),
1750 $context->getVersion(),
1751 $context->getDebug(),
1752 $context->getOnly(),
1753 $context->getRequest()->getBool(
'printable' ),
1775 public static function makeLoaderQuery( array
$modules,
$lang, $skin, $user =
null,
1776 $version =
null, $debug = Context::DEBUG_OFF, $only =
null,
1777 $printable =
false, $handheld =
null, array $extraQuery = []
1780 'modules' => self::makePackedModulesString(
$modules ),
1786 if (
$lang !== Context::DEFAULT_LANG ) {
1787 $query[
'lang'] =
$lang;
1789 if ( $skin !== Context::DEFAULT_SKIN ) {
1790 $query[
'skin'] = $skin;
1792 if ( $debug !== Context::DEBUG_OFF ) {
1793 $query[
'debug'] = strval( $debug );
1795 if ( $user !==
null ) {
1796 $query[
'user'] = $user;
1798 if ( $version !==
null ) {
1799 $query[
'version'] = $version;
1801 if ( $only !==
null ) {
1802 $query[
'only'] = $only;
1805 $query[
'printable'] = 1;
1807 $query += $extraQuery;
1823 public static function isValidModuleName( $moduleName ) {
1824 $len = strlen( $moduleName );
1825 return $len <= 255 && strcspn( $moduleName,
'!,|', 0, $len ) === $len;
1839 public function getLessCompiler( array $vars = [], array $importDirs = [] ) {
1844 if ( !class_exists( Less_Parser::class ) ) {
1845 throw new MWException(
'MediaWiki requires the less.php parser' );
1848 $importDirs[] =
"$IP/resources/src/mediawiki.less";
1850 $parser =
new Less_Parser;
1851 $parser->ModifyVars( $vars );
1853 $parser->SetImportDirs( array_fill_keys( $importDirs,
'' ) );
1854 $parser->SetOption(
'relativeUrls',
false );
1872 public function expandUrl(
string $base,
string $url ): string {
1874 $isProtoRelative = strpos(
$base,
'//' ) === 0;
1875 if ( $isProtoRelative ) {
1876 $base =
"https:$base";
1879 $baseUrl =
new Net_URL2(
$base );
1880 $ret = $baseUrl->resolve( $url );
1881 if ( $isProtoRelative ) {
1882 $ret->setScheme(
false );
1884 return $ret->getURL();
1904 public static function filter( $filter, $data, array $options = [] ) {
1905 if ( strpos( $data, self::FILTER_NOMIN ) !==
false ) {
1909 if ( isset( $options[
'cache'] ) && $options[
'cache'] ===
false ) {
1910 return self::applyFilter( $filter, $data ) ?? $data;
1913 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1916 $key = $cache->makeGlobalKey(
1917 'resourceloader-filter',
1919 self::CACHE_VERSION,
1923 $incKey =
"resourceloader_cache.$filter.hit";
1924 $result = $cache->getWithSetCallback(
1927 function () use ( $filter, $data, &$incKey ) {
1928 $incKey =
"resourceloader_cache.$filter.miss";
1929 return self::applyFilter( $filter, $data );
1932 $stats->increment( $incKey );
1935 return $result ?? $data;
1943 private static function applyFilter( $filter, $data ) {
1944 $data = trim( $data );
1947 $data = ( $filter ===
'minify-css' )
1948 ? CSSMin::minify( $data )
1949 : JavaScriptMinifier::minify( $data );
1950 }
catch ( TimeoutException $e ) {
1952 }
catch ( Exception $e ) {
1971 public static function getUserDefaults(
1973 HookContainer $hookContainer,
1974 UserOptionsLookup $userOptionsLookup
1976 $defaultOptions = $userOptionsLookup->getDefaultOptions();
1977 $keysToExclude = [];
1978 $hookRunner =
new HookRunner( $hookContainer );
1979 $hookRunner->onResourceLoaderExcludeUserOptions( $keysToExclude, $context );
1980 foreach ( $keysToExclude as $excludedKey ) {
1981 unset( $defaultOptions[ $excludedKey ] );
1983 return $defaultOptions;
1994 public static function getSiteConfigSettings(
1995 Context $context,
Config $conf
2000 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2001 $namespaceIds = $contLang->getNamespaceIds();
2002 $caseSensitiveNamespaces = [];
2003 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
2004 foreach ( $nsInfo->getCanonicalNamespaces() as $index => $name ) {
2005 $namespaceIds[$contLang->lc( $name )] = $index;
2006 if ( !$nsInfo->isCapitalized( $index ) ) {
2007 $caseSensitiveNamespaces[] = $index;
2011 $illegalFileChars = $conf->
get( MainConfigNames::IllegalFileChars );
2014 $skin = $context->getSkin();
2018 'debug' => $context->getDebug(),
2020 'stylepath' => $conf->
get( MainConfigNames::StylePath ),
2021 'wgArticlePath' => $conf->
get( MainConfigNames::ArticlePath ),
2022 'wgScriptPath' => $conf->
get( MainConfigNames::ScriptPath ),
2023 'wgScript' => $conf->
get( MainConfigNames::Script ),
2024 'wgSearchType' => $conf->
get( MainConfigNames::SearchType ),
2025 'wgVariantArticlePath' => $conf->
get( MainConfigNames::VariantArticlePath ),
2026 'wgServer' => $conf->
get( MainConfigNames::Server ),
2027 'wgServerName' => $conf->
get( MainConfigNames::ServerName ),
2028 'wgUserLanguage' => $context->getLanguage(),
2029 'wgContentLanguage' => $contLang->getCode(),
2031 'wgFormattedNamespaces' => $contLang->getFormattedNamespaces(),
2032 'wgNamespaceIds' => $namespaceIds,
2033 'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
2034 'wgSiteName' => $conf->
get( MainConfigNames::Sitename ),
2035 'wgDBname' => $conf->
get( MainConfigNames::DBname ),
2037 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
2038 'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
2039 'wgExtensionAssetsPath' => $conf->
get( MainConfigNames::ExtensionAssetsPath ),
2050 'wgActionPaths' => (object)$conf->
get( MainConfigNames::ActionPaths ),
2052 'wgTranslateNumerals' => $conf->
get( MainConfigNames::TranslateNumerals ),
2054 'wgExtraSignatureNamespaces' => $conf->
get( MainConfigNames::ExtraSignatureNamespaces ),
2059 Hooks::runner()->onResourceLoaderGetConfigVars( $vars, $skin, $conf );
2065 class_alias( ResourceLoader::class,
'ResourceLoader' );
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...
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
if(!defined( 'MEDIAWIKI')) if(ini_get( 'mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Class representing a cache/ephemeral data store.
Class for managing the deferral of updates within the scope of a PHP script invocation.
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
The Registry loads JSON files, and uses a Processor to extract information from them.
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.
This class is a collection of static functions that serve two purposes:
static inlineScript( $contents, $nonce=null)
Output an HTML script tag with the given contents.
static header( $code)
Output an HTTP status code header.
Handler class for MWExceptions.
static getRedactedTraceAsString(Throwable $e)
Generate a string representation of a throwable's stack trace.
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
static getPublicLogMessage(Throwable $e)
static getLogMessage(Throwable $e)
Get a message formatting the throwable message and its origin.
Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
static shouldShowExceptionDetails()
A class containing constants representing the names of configuration variables.
Context object that contains information about the state of a specific ResourceLoader web request.
encodeJson( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
getImageObj()
If this is a request for an image, get the Image object.
Functions to get cache objects.
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from configuration)
static getLocalClusterInstance()
Get the main cluster-local cache object.
This is one of the Core classes and should be read at least once by any new developers.
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
ResourceLoader request result caching in the file system.
static useFileCache(RL\Context $context)
Check if an RL request can be cached.
static newFromContext(RL\Context $context)
Construct an ResourceFileCache from a context.
Represents a title within MediaWiki.
static legalChars()
Get a regex character class describing the legal characters in a link.
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
Helper tools for dealing with other locally-hosted wikis.
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.
Module of static functions for generating XML.
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