MediaWiki  master
ResourceLoader.php
Go to the documentation of this file.
1 <?php
27 use Psr\Log\LoggerAwareInterface;
28 use Psr\Log\LoggerInterface;
29 use Psr\Log\NullLogger;
33 use Wikimedia\Timestamp\ConvertibleTimestamp;
34 use Wikimedia\WrappedString;
35 
56 class ResourceLoader implements LoggerAwareInterface {
58  protected $config;
60  protected $blobStore;
62  protected $depStore;
63 
65  private $logger;
66 
68  private $hookContainer;
69 
71  private $hookRunner;
72 
74  protected $modules = [];
76  protected $moduleInfos = [];
82  protected $testModuleNames = [];
84  protected $testSuiteModuleNames = [];
86  protected $sources = [];
88  protected $errors = [];
90  protected $extraHeaders = [];
91 
93  private $depStoreUpdateBuffer = [];
94 
98  private $moduleSkinStyles = [];
99 
101  protected static $debugMode = null;
102 
104  public const CACHE_VERSION = 8;
105 
107  private const RL_DEP_STORE_PREFIX = 'ResourceLoaderModule';
109  private const RL_MODULE_DEP_TTL = BagOStuff::TTL_WEEK;
110 
112  public const FILTER_NOMIN = '/*@nomin*/';
113 
120  public function preloadModuleInfo( array $moduleNames, ResourceLoaderContext $context ) {
121  // Load all tracked indirect file dependencies for the modules
122  $vary = ResourceLoaderModule::getVary( $context );
123  $entitiesByModule = [];
124  foreach ( $moduleNames as $moduleName ) {
125  $entitiesByModule[$moduleName] = "$moduleName|$vary";
126  }
127  $depsByEntity = $this->depStore->retrieveMulti(
128  self::RL_DEP_STORE_PREFIX,
129  $entitiesByModule
130  );
131  // Inject the indirect file dependencies for all the modules
132  foreach ( $moduleNames as $moduleName ) {
133  $module = $this->getModule( $moduleName );
134  if ( $module ) {
135  $entity = $entitiesByModule[$moduleName];
136  $deps = $depsByEntity[$entity];
137  $paths = ResourceLoaderModule::expandRelativePaths( $deps['paths'] );
138  $module->setFileDependencies( $context, $paths );
139  }
140  }
141 
142  // Batched version of ResourceLoaderWikiModule::getTitleInfo
143  $dbr = wfGetDB( DB_REPLICA );
144  ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $moduleNames );
145 
146  // Prime in-object cache for message blobs for modules with messages
147  $modulesWithMessages = [];
148  foreach ( $moduleNames as $moduleName ) {
149  $module = $this->getModule( $moduleName );
150  if ( $module && $module->getMessages() ) {
151  $modulesWithMessages[$moduleName] = $module;
152  }
153  }
154  // Prime in-object cache for message blobs for modules with messages
155  $lang = $context->getLanguage();
156  $store = $this->getMessageBlobStore();
157  $blobs = $store->getBlobs( $modulesWithMessages, $lang );
158  foreach ( $blobs as $moduleName => $blob ) {
159  $modulesWithMessages[$moduleName]->setMessageBlob( $blob, $lang );
160  }
161  }
162 
180  public static function filter( $filter, $data, array $options = [] ) {
181  if ( strpos( $data, self::FILTER_NOMIN ) !== false ) {
182  return $data;
183  }
184 
185  if ( isset( $options['cache'] ) && $options['cache'] === false ) {
186  return self::applyFilter( $filter, $data ) ?? $data;
187  }
188 
189  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
191 
192  $key = $cache->makeGlobalKey(
193  'resourceloader-filter',
194  $filter,
195  self::CACHE_VERSION,
196  md5( $data )
197  );
198 
199  $result = $cache->get( $key );
200  if ( $result === false ) {
201  $stats->increment( "resourceloader_cache.$filter.miss" );
202  $result = self::applyFilter( $filter, $data );
203  $cache->set( $key, $result, 24 * 3600 );
204  } else {
205  $stats->increment( "resourceloader_cache.$filter.hit" );
206  }
207  if ( $result === null ) {
208  // Cached failure
209  $result = $data;
210  }
211 
212  return $result;
213  }
214 
220  private static function applyFilter( $filter, $data ) {
221  $data = trim( $data );
222  if ( $data ) {
223  try {
224  $data = ( $filter === 'minify-css' )
225  ? CSSMin::minify( $data )
226  : JavaScriptMinifier::minify( $data );
227  } catch ( Exception $e ) {
229  return null;
230  }
231  }
232  return $data;
233  }
234 
241  public function __construct(
242  Config $config = null,
243  LoggerInterface $logger = null,
245  ) {
246  $this->logger = $logger ?: new NullLogger();
247  $services = MediaWikiServices::getInstance();
248 
249  if ( !$config ) {
250  wfDeprecated( __METHOD__ . ' without a Config instance', '1.34' );
251  $config = $services->getMainConfig();
252  }
253  $this->config = $config;
254 
255  $this->hookContainer = $services->getHookContainer();
256  $this->hookRunner = new HookRunner( $this->hookContainer );
257 
258  // Add 'local' source first
259  $this->addSource( 'local', $config->get( 'LoadScript' ) );
260 
261  // Special module that always exists
262  $this->register( 'startup', [ 'class' => ResourceLoaderStartUpModule::class ] );
263 
264  $this->setMessageBlobStore(
265  new MessageBlobStore( $this, $this->logger, $services->getMainWANObjectCache() )
266  );
267 
269  $this->setDependencyStore( $tracker );
270  }
271 
275  public function getConfig() {
276  return $this->config;
277  }
278 
283  public function setLogger( LoggerInterface $logger ) {
284  $this->logger = $logger;
285  }
286 
291  public function getLogger() {
292  return $this->logger;
293  }
294 
299  public function getMessageBlobStore() {
300  return $this->blobStore;
301  }
302 
308  $this->blobStore = $blobStore;
309  }
310 
316  $this->depStore = $tracker;
317  }
318 
323  public function setModuleSkinStyles( array $moduleSkinStyles ) {
324  $this->moduleSkinStyles = $moduleSkinStyles;
325  }
326 
338  public function register( $name, array $info = null ) {
339  // Allow multiple modules to be registered in one call
340  $registrations = is_array( $name ) ? $name : [ $name => $info ];
341  foreach ( $registrations as $name => $info ) {
342  // Warn on duplicate registrations
343  if ( isset( $this->moduleInfos[$name] ) ) {
344  // A module has already been registered by this name
345  $this->logger->warning(
346  'ResourceLoader duplicate registration warning. ' .
347  'Another module has already been registered as ' . $name
348  );
349  }
350 
351  // Check validity
352  if ( !self::isValidModuleName( $name ) ) {
353  throw new InvalidArgumentException( "ResourceLoader module name '$name' is invalid, "
354  . "see ResourceLoader::isValidModuleName()" );
355  }
356  if ( !is_array( $info ) ) {
357  throw new InvalidArgumentException(
358  'Invalid module info for "' . $name . '": expected array, got ' . gettype( $info )
359  );
360  }
361 
362  // Attach module
363  $this->moduleInfos[$name] = $info;
364 
365  // Last-minute changes
366  // Apply custom skin-defined styles to existing modules.
367  if ( $this->isFileModule( $name ) ) {
368  foreach ( $this->moduleSkinStyles as $skinName => $skinStyles ) {
369  // If this module already defines skinStyles for this skin, ignore ResourceModuleSkinStyles.
370  if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
371  continue;
372  }
373 
374  // If $name is preceded with a '+', the defined style files will be added to 'default'
375  // skinStyles, otherwise 'default' will be ignored as it normally would be.
376  if ( isset( $skinStyles[$name] ) ) {
377  $paths = (array)$skinStyles[$name];
378  $styleFiles = [];
379  } elseif ( isset( $skinStyles['+' . $name] ) ) {
380  $paths = (array)$skinStyles['+' . $name];
381  $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
382  (array)$this->moduleInfos[$name]['skinStyles']['default'] :
383  [];
384  } else {
385  continue;
386  }
387 
388  // Add new file paths, remapping them to refer to our directories and not use settings
389  // from the module we're modifying, which come from the base definition.
390  list( $localBasePath, $remoteBasePath ) =
392 
393  foreach ( $paths as $path ) {
394  $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
395  }
396 
397  $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
398  }
399  }
400  }
401  }
402 
407  public function registerTestModules() : void {
408  global $IP;
409 
410  if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
411  throw new MWException( 'Attempt to register JavaScript test modules '
412  . 'but <code>$wgEnableJavaScriptTest</code> is false. '
413  . 'Edit your <code>LocalSettings.php</code> to enable it.' );
414  }
415 
416  // This has a 'qunit' key for compat with the below hook.
417  $testModulesMeta = [ 'qunit' => [] ];
418 
419  $this->hookRunner->onResourceLoaderTestModules( $testModulesMeta, $this );
420  $extRegistry = ExtensionRegistry::getInstance();
421  // In case of conflict, the deprecated hook has precedence.
422  $testModules = $testModulesMeta['qunit']
423  + $extRegistry->getAttribute( 'QUnitTestModules' );
424 
426  foreach ( $testModules as $name => &$module ) {
427  // Turn any single-module dependency into an array
428  if ( isset( $module['dependencies'] ) && is_string( $module['dependencies'] ) ) {
429  $module['dependencies'] = [ $module['dependencies'] ];
430  }
431 
432  // Ensure the testrunner loads before any test suites
433  $module['dependencies'][] = 'mediawiki.qunit-testrunner';
434 
435  // Keep track of the test suites to load on SpecialJavaScriptTest
436  $testSuiteModuleNames[] = $name;
437  }
438 
439  // Core test suites (their names have further precedence).
440  $testModules = ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules;
441  $testSuiteModuleNames[] = 'test.MediaWiki';
442 
443  $this->register( $testModules );
444  $this->testSuiteModuleNames = $testSuiteModuleNames;
445  }
446 
457  public function addSource( $sources, $loadUrl = null ) {
458  if ( !is_array( $sources ) ) {
459  $sources = [ $sources => $loadUrl ];
460  }
461  foreach ( $sources as $id => $source ) {
462  // Disallow duplicates
463  if ( isset( $this->sources[$id] ) ) {
464  throw new RuntimeException( 'Cannot register source ' . $id . ' twice' );
465  }
466 
467  // Support: MediaWiki 1.24 and earlier
468  if ( is_array( $source ) ) {
469  if ( !isset( $source['loadScript'] ) ) {
470  throw new InvalidArgumentException( 'Each source must have a "loadScript" key' );
471  }
472  $source = $source['loadScript'];
473  }
474 
475  $this->sources[$id] = $source;
476  }
477  }
478 
482  public function getModuleNames() {
483  return array_keys( $this->moduleInfos );
484  }
485 
493  public function getTestSuiteModuleNames() {
495  }
496 
504  public function isModuleRegistered( $name ) {
505  return isset( $this->moduleInfos[$name] );
506  }
507 
519  public function getModule( $name ) {
520  if ( !isset( $this->modules[$name] ) ) {
521  if ( !isset( $this->moduleInfos[$name] ) ) {
522  // No such module
523  return null;
524  }
525  // Construct the requested module object
526  $info = $this->moduleInfos[$name];
527  if ( isset( $info['factory'] ) ) {
529  $object = call_user_func( $info['factory'], $info );
530  } else {
531  $class = $info['class'] ?? ResourceLoaderFileModule::class;
533  $object = new $class( $info );
534  }
535  $object->setConfig( $this->getConfig() );
536  $object->setLogger( $this->logger );
537  $object->setHookContainer( $this->hookContainer );
538  $object->setName( $name );
539  $object->setDependencyAccessCallbacks(
540  [ $this, 'loadModuleDependenciesInternal' ],
541  [ $this, 'saveModuleDependenciesInternal' ]
542  );
543  $this->modules[$name] = $object;
544  }
545 
546  return $this->modules[$name];
547  }
548 
555  public function loadModuleDependenciesInternal( $moduleName, $variant ) {
556  $deps = $this->depStore->retrieve( self::RL_DEP_STORE_PREFIX, "$moduleName|$variant" );
557 
558  return ResourceLoaderModule::expandRelativePaths( $deps['paths'] );
559  }
560 
568  public function saveModuleDependenciesInternal( $moduleName, $variant, $paths, $priorPaths ) {
569  $hasPendingUpdate = (bool)$this->depStoreUpdateBuffer;
570  $entity = "$moduleName|$variant";
571 
572  if ( array_diff( $paths, $priorPaths ) || array_diff( $priorPaths, $paths ) ) {
573  // Dependency store needs to be updated with the new path list
574  if ( $paths ) {
575  $deps = $this->depStore->newEntityDependencies( $paths, time() );
576  $this->depStoreUpdateBuffer[$entity] = $deps;
577  } else {
578  $this->depStoreUpdateBuffer[$entity] = null;
579  }
580  } elseif ( $priorPaths ) {
581  // Dependency store needs to store the existing path list for longer
582  $this->depStoreUpdateBuffer[$entity] = '*';
583  }
584 
585  // Use a DeferrableUpdate to flush the buffered dependency updates...
586  if ( !$hasPendingUpdate ) {
588  $updatesByEntity = $this->depStoreUpdateBuffer;
589  $this->depStoreUpdateBuffer = []; // consume
591 
592  $scopeLocks = [];
593  $depsByEntity = [];
594  $entitiesUnreg = [];
595  $entitiesRenew = [];
596  foreach ( $updatesByEntity as $entity => $update ) {
597  $lockKey = $cache->makeKey( 'rl-deps', $entity );
598  $scopeLocks[$entity] = $cache->getScopedLock( $lockKey, 0 );
599  if ( !$scopeLocks[$entity] ) {
600  // avoid duplicate write request slams (T124649)
601  // the lock must be specific to the current wiki (T247028)
602  continue;
603  } elseif ( $update === null ) {
604  $entitiesUnreg[] = $entity;
605  } elseif ( $update === '*' ) {
606  $entitiesRenew[] = $entity;
607  } else {
608  $depsByEntity[$entity] = $update;
609  }
610  }
611 
612  $ttl = self::RL_MODULE_DEP_TTL;
613  $this->depStore->storeMulti( self::RL_DEP_STORE_PREFIX, $depsByEntity, $ttl );
614  $this->depStore->remove( self::RL_DEP_STORE_PREFIX, $entitiesUnreg );
615  $this->depStore->renew( self::RL_DEP_STORE_PREFIX, $entitiesRenew, $ttl );
616  } );
617  }
618  }
619 
626  protected function isFileModule( $name ) {
627  if ( !isset( $this->moduleInfos[$name] ) ) {
628  return false;
629  }
630  $info = $this->moduleInfos[$name];
631  return !isset( $info['factory'] ) && (
632  // The implied default for 'class' is ResourceLoaderFileModule
633  !isset( $info['class'] ) ||
634  // Explicit default
635  $info['class'] === ResourceLoaderFileModule::class ||
636  is_subclass_of( $info['class'], ResourceLoaderFileModule::class )
637  );
638  }
639 
645  public function getSources() {
646  return $this->sources;
647  }
648 
657  public function getLoadScript( $source ) {
658  if ( !isset( $this->sources[$source] ) ) {
659  throw new UnexpectedValueException( "Unknown source '$source'" );
660  }
661  return $this->sources[$source];
662  }
663 
667  public const HASH_LENGTH = 5;
668 
731  public static function makeHash( $value ) {
732  $hash = hash( 'fnv132', $value );
733  // The base_convert will pad it (if too short),
734  // then substr() will trim it (if too long).
735  return substr(
736  Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
737  0,
738  self::HASH_LENGTH
739  );
740  }
741 
751  public function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
753  $this->logger->warning(
754  $msg,
755  $context + [ 'exception' => $e ]
756  );
757  $this->errors[] = self::formatExceptionNoComment( $e );
758  }
759 
768  public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
769  if ( !$moduleNames ) {
770  return '';
771  }
772  $hashes = array_map( function ( $module ) use ( $context ) {
773  try {
774  return $this->getModule( $module )->getVersionHash( $context );
775  } catch ( Exception $e ) {
776  // If modules fail to compute a version, don't fail the request (T152266)
777  // and still compute versions of other modules.
778  $this->outputErrorAndLog( $e,
779  'Calculating version for "{module}" failed: {exception}',
780  [
781  'module' => $module,
782  ]
783  );
784  return '';
785  }
786  }, $moduleNames );
787  return self::makeHash( implode( '', $hashes ) );
788  }
789 
804  public function makeVersionQuery( ResourceLoaderContext $context, array $modules = null ) {
805  if ( $modules === null ) {
806  wfDeprecated( __METHOD__ . ' without $modules', '1.34' );
807  $modules = $context->getModules();
808  }
809  // As of MediaWiki 1.28, the server and client use the same algorithm for combining
810  // version hashes. There is no technical reason for this to be same, and for years the
811  // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
812  // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
813  // query parameter), then this method must continue to match the JS one.
814  $filtered = [];
815  foreach ( $modules as $name ) {
816  if ( !$this->getModule( $name ) ) {
817  // If a versioned request contains a missing module, the version is a mismatch
818  // as the client considered a module (and version) we don't have.
819  return '';
820  }
821  $filtered[] = $name;
822  }
823  return $this->getCombinedVersion( $context, $filtered );
824  }
825 
831  public function respond( ResourceLoaderContext $context ) {
832  // Buffer output to catch warnings. Normally we'd use ob_clean() on the
833  // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
834  // is used: ob_clean() will clear the GZIP header in that case and it won't come
835  // back for subsequent output, resulting in invalid GZIP. So we have to wrap
836  // the whole thing in our own output buffer to be sure the active buffer
837  // doesn't use ob_gzhandler.
838  // See https://bugs.php.net/bug.php?id=36514
839  ob_start();
840 
841  $this->measureResponseTime( RequestContext::getMain()->getTiming() );
842 
843  // Find out which modules are missing and instantiate the others
844  $modules = [];
845  $missing = [];
846  foreach ( $context->getModules() as $name ) {
847  $module = $this->getModule( $name );
848  if ( $module ) {
849  // Do not allow private modules to be loaded from the web.
850  // This is a security issue, see T36907.
851  if ( $module->getGroup() === 'private' ) {
852  // Not a serious error, just means something is trying to access it (T101806)
853  $this->logger->debug( "Request for private module '$name' denied" );
854  $this->errors[] = "Cannot build private module \"$name\"";
855  continue;
856  }
857  $modules[$name] = $module;
858  } else {
859  $missing[] = $name;
860  }
861  }
862 
863  try {
864  // Preload for getCombinedVersion() and for batch makeModuleResponse()
865  $this->preloadModuleInfo( array_keys( $modules ), $context );
866  } catch ( Exception $e ) {
867  $this->outputErrorAndLog( $e, 'Preloading module info failed: {exception}' );
868  }
869 
870  // Combine versions to propagate cache invalidation
871  $versionHash = '';
872  try {
873  $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
874  } catch ( Exception $e ) {
875  $this->outputErrorAndLog( $e, 'Calculating version hash failed: {exception}' );
876  }
877 
878  // See RFC 2616 § 3.11 Entity Tags
879  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
880  $etag = 'W/"' . $versionHash . '"';
881 
882  // Try the client-side cache first
883  if ( $this->tryRespondNotModified( $context, $etag ) ) {
884  return; // output handled (buffers cleared)
885  }
886 
887  // Use file cache if enabled and available...
888  if ( $this->config->get( 'UseFileCache' ) ) {
889  $fileCache = ResourceFileCache::newFromContext( $context );
890  if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
891  return; // output handled
892  }
893  } else {
894  $fileCache = null;
895  }
896 
897  // Generate a response
898  $response = $this->makeModuleResponse( $context, $modules, $missing );
899 
900  // Capture any PHP warnings from the output buffer and append them to the
901  // error list if we're in debug mode.
902  if ( $context->getDebug() ) {
903  $warnings = ob_get_contents();
904  if ( strlen( $warnings ) ) {
905  $this->errors[] = $warnings;
906  }
907  }
908 
909  // Consider saving the response to file cache (unless there are errors).
910  if ( $fileCache &&
911  !$this->errors &&
912  $missing === [] &&
914  ) {
915  if ( $fileCache->isCacheWorthy() ) {
916  // There were enough hits, save the response to the cache
917  $fileCache->saveText( $response );
918  } else {
919  $fileCache->incrMissesRecent( $context->getRequest() );
920  }
921  }
922 
923  $this->sendResponseHeaders( $context, $etag, (bool)$this->errors, $this->extraHeaders );
924 
925  // Remove the output buffer and output the response
926  ob_end_clean();
927 
928  if ( $context->getImageObj() && $this->errors ) {
929  // We can't show both the error messages and the response when it's an image.
930  $response = implode( "\n\n", $this->errors );
931  } elseif ( $this->errors ) {
932  $errorText = implode( "\n\n", $this->errors );
933  $errorResponse = self::makeComment( $errorText );
934  if ( $context->shouldIncludeScripts() ) {
935  $errorResponse .= 'if (window.console && console.error) { console.error('
936  . $context->encodeJson( $errorText )
937  . "); }\n";
938  }
939 
940  // Prepend error info to the response
941  $response = $errorResponse . $response;
942  }
943 
944  $this->errors = [];
945  echo $response;
946  }
947 
948  protected function measureResponseTime( Timing $timing ) {
949  DeferredUpdates::addCallableUpdate( function () use ( $timing ) {
950  $measure = $timing->measure( 'responseTime', 'requestStart', 'requestShutdown' );
951  if ( $measure !== false ) {
952  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
953  $stats->timing( 'resourceloader.responseTime', $measure['duration'] * 1000 );
954  }
955  } );
956  }
957 
968  protected function sendResponseHeaders(
969  ResourceLoaderContext $context, $etag, $errors, array $extra = []
970  ) : void {
971  HeaderCallback::warnIfHeadersSent();
972  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
973  // Use a short cache expiry so that updates propagate to clients quickly, if:
974  // - No version specified (shared resources, e.g. stylesheets)
975  // - There were errors (recover quickly)
976  // - Version mismatch (T117587, T47877)
977  if ( $context->getVersion() === null
978  || $errors
979  || $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
980  ) {
981  $maxage = $rlMaxage['unversioned'];
982  // If a version was specified we can use a longer expiry time since changing
983  // version numbers causes cache misses
984  } else {
985  $maxage = $rlMaxage['versioned'];
986  }
987  if ( $context->getImageObj() ) {
988  // Output different headers if we're outputting textual errors.
989  if ( $errors ) {
990  header( 'Content-Type: text/plain; charset=utf-8' );
991  } else {
992  $context->getImageObj()->sendResponseHeaders( $context );
993  }
994  } elseif ( $context->getOnly() === 'styles' ) {
995  header( 'Content-Type: text/css; charset=utf-8' );
996  header( 'Access-Control-Allow-Origin: *' );
997  } else {
998  header( 'Content-Type: text/javascript; charset=utf-8' );
999  }
1000  // See RFC 2616 § 14.19 ETag
1001  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
1002  header( 'ETag: ' . $etag );
1003  if ( $context->getDebug() ) {
1004  // Do not cache debug responses
1005  header( 'Cache-Control: private, no-cache, must-revalidate' );
1006  header( 'Pragma: no-cache' );
1007  } else {
1008  header( "Cache-Control: public, max-age=$maxage, s-maxage=$maxage" );
1009  header( 'Expires: ' . ConvertibleTimestamp::convert( TS_RFC2822, time() + $maxage ) );
1010  }
1011  foreach ( $extra as $header ) {
1012  header( $header );
1013  }
1014  }
1015 
1026  protected function tryRespondNotModified( ResourceLoaderContext $context, $etag ) {
1027  // See RFC 2616 § 14.26 If-None-Match
1028  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
1029  $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
1030  // Never send 304s in debug mode
1031  if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
1032  // There's another bug in ob_gzhandler (see also the comment at
1033  // the top of this function) that causes it to gzip even empty
1034  // responses, meaning it's impossible to produce a truly empty
1035  // response (because the gzip header is always there). This is
1036  // a problem because 304 responses have to be completely empty
1037  // per the HTTP spec, and Firefox behaves buggily when they're not.
1038  // See also https://bugs.php.net/bug.php?id=51579
1039  // To work around this, we tear down all output buffering before
1040  // sending the 304.
1041  wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
1042 
1043  HttpStatus::header( 304 );
1044 
1045  $this->sendResponseHeaders( $context, $etag, false );
1046  return true;
1047  }
1048  return false;
1049  }
1050 
1059  protected function tryRespondFromFileCache(
1060  ResourceFileCache $fileCache,
1061  ResourceLoaderContext $context,
1062  $etag
1063  ) {
1064  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
1065  // Buffer output to catch warnings.
1066  ob_start();
1067  // Get the maximum age the cache can be
1068  $maxage = $context->getVersion() === null
1069  ? $rlMaxage['unversioned']
1070  : $rlMaxage['versioned'];
1071  // Minimum timestamp the cache file must have
1072  $minTime = time() - $maxage;
1073  $good = $fileCache->isCacheGood( ConvertibleTimestamp::convert( TS_MW, $minTime ) );
1074  if ( !$good ) {
1075  try { // RL always hits the DB on file cache miss...
1076  wfGetDB( DB_REPLICA );
1077  } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
1078  $good = $fileCache->isCacheGood(); // cache existence check
1079  }
1080  }
1081  if ( $good ) {
1082  $ts = $fileCache->cacheTimestamp();
1083  // Send content type and cache headers
1084  $this->sendResponseHeaders( $context, $etag, false );
1085  $response = $fileCache->fetchText();
1086  // Capture any PHP warnings from the output buffer and append them to the
1087  // response in a comment if we're in debug mode.
1088  if ( $context->getDebug() ) {
1089  $warnings = ob_get_contents();
1090  if ( strlen( $warnings ) ) {
1091  $response = self::makeComment( $warnings ) . $response;
1092  }
1093  }
1094  // Remove the output buffer and output the response
1095  ob_end_clean();
1096  echo $response . "\n/* Cached {$ts} */";
1097  return true; // cache hit
1098  }
1099  // Clear buffer
1100  ob_end_clean();
1101 
1102  return false; // cache miss
1103  }
1104 
1113  public static function makeComment( $text ) {
1114  $encText = str_replace( '*/', '* /', $text );
1115  return "/*\n$encText\n*/\n";
1116  }
1117 
1124  public static function formatException( Throwable $e ) {
1125  return self::makeComment( self::formatExceptionNoComment( $e ) );
1126  }
1127 
1135  protected static function formatExceptionNoComment( Throwable $e ) {
1136  global $wgShowExceptionDetails;
1137 
1138  if ( !$wgShowExceptionDetails ) {
1140  }
1141 
1142  return MWExceptionHandler::getLogMessage( $e ) .
1143  "\nBacktrace:\n" .
1145  }
1146 
1158  public function makeModuleResponse( ResourceLoaderContext $context,
1159  array $modules, array $missing = []
1160  ) {
1161  $out = '';
1162  $states = [];
1163 
1164  if ( $modules === [] && $missing === [] ) {
1165  return <<<MESSAGE
1166 /* This file is the Web entry point for MediaWiki's ResourceLoader:
1167  <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
1168  no modules were requested. Max made me put this here. */
1169 MESSAGE;
1170  }
1171 
1172  $image = $context->getImageObj();
1173  if ( $image ) {
1174  $data = $image->getImageData( $context );
1175  if ( $data === false ) {
1176  $data = '';
1177  $this->errors[] = 'Image generation failed';
1178  }
1179  return $data;
1180  }
1181 
1182  foreach ( $missing as $name ) {
1183  $states[$name] = 'missing';
1184  }
1185 
1186  $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
1187 
1188  foreach ( $modules as $name => $module ) {
1189  try {
1190  $content = $module->getModuleContent( $context );
1191  $implementKey = $name . '@' . $module->getVersionHash( $context );
1192  $strContent = '';
1193 
1194  if ( isset( $content['headers'] ) ) {
1195  $this->extraHeaders = array_merge( $this->extraHeaders, $content['headers'] );
1196  }
1197 
1198  // Append output
1199  switch ( $context->getOnly() ) {
1200  case 'scripts':
1201  $scripts = $content['scripts'];
1202  if ( is_string( $scripts ) ) {
1203  // Load scripts raw...
1204  $strContent = $scripts;
1205  } elseif ( is_array( $scripts ) ) {
1206  // ...except when $scripts is an array of URLs or an associative array
1207  $strContent = self::makeLoaderImplementScript(
1208  $context,
1209  $implementKey,
1210  $scripts,
1211  [],
1212  [],
1213  []
1214  );
1215  }
1216  break;
1217  case 'styles':
1218  $styles = $content['styles'];
1219  // We no longer separate into media, they are all combined now with
1220  // custom media type groups into @media .. {} sections as part of the css string.
1221  // Module returns either an empty array or a numerical array with css strings.
1222  $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
1223  break;
1224  default:
1225  $scripts = $content['scripts'] ?? '';
1226  if ( is_string( $scripts ) ) {
1227  if ( $name === 'site' || $name === 'user' ) {
1228  // Legacy scripts that run in the global scope without a closure.
1229  // mw.loader.implement will use globalEval if scripts is a string.
1230  // Minify manually here, because general response minification is
1231  // not effective due it being a string literal, not a function.
1232  if ( !$context->getDebug() ) {
1233  $scripts = self::filter( 'minify-js', $scripts ); // T107377
1234  }
1235  } else {
1236  $scripts = new XmlJsCode( $scripts );
1237  }
1238  }
1239  $strContent = self::makeLoaderImplementScript(
1240  $context,
1241  $implementKey,
1242  $scripts,
1243  $content['styles'] ?? [],
1244  isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
1245  $content['templates'] ?? []
1246  );
1247  break;
1248  }
1249 
1250  if ( !$context->getDebug() ) {
1251  $strContent = self::filter( $filter, $strContent );
1252  } else {
1253  // In debug mode, separate each response by a new line.
1254  // For example, between 'mw.loader.implement();' statements.
1255  $strContent = $this->ensureNewline( $strContent );
1256  }
1257 
1258  if ( $context->getOnly() === 'scripts' ) {
1259  // Use a linebreak between module scripts (T162719)
1260  $out .= $this->ensureNewline( $strContent );
1261  } else {
1262  $out .= $strContent;
1263  }
1264 
1265  } catch ( Exception $e ) {
1266  $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1267 
1268  // Respond to client with error-state instead of module implementation
1269  $states[$name] = 'error';
1270  unset( $modules[$name] );
1271  }
1272  }
1273 
1274  // Update module states
1275  if ( $context->shouldIncludeScripts() && !$context->getRaw() ) {
1276  if ( $modules && $context->getOnly() === 'scripts' ) {
1277  // Set the state of modules loaded as only scripts to ready as
1278  // they don't have an mw.loader.implement wrapper that sets the state
1279  foreach ( $modules as $name => $module ) {
1280  $states[$name] = 'ready';
1281  }
1282  }
1283 
1284  // Set the state of modules we didn't respond to with mw.loader.implement
1285  if ( $states ) {
1286  $stateScript = self::makeLoaderStateScript( $context, $states );
1287  if ( !$context->getDebug() ) {
1288  $stateScript = self::filter( 'minify-js', $stateScript );
1289  }
1290  // Use a linebreak between module script and state script (T162719)
1291  $out = $this->ensureNewline( $out ) . $stateScript;
1292  }
1293  } elseif ( $states ) {
1294  $this->errors[] = 'Problematic modules: '
1295  . $context->encodeJson( $states );
1296  }
1297 
1298  return $out;
1299  }
1300 
1306  private function ensureNewline( $str ) {
1307  $end = substr( $str, -1 );
1308  if ( $end === false || $end === '' || $end === "\n" ) {
1309  return $str;
1310  }
1311  return $str . "\n";
1312  }
1313 
1320  public function getModulesByMessage( $messageKey ) {
1321  $moduleNames = [];
1322  foreach ( $this->getModuleNames() as $moduleName ) {
1323  $module = $this->getModule( $moduleName );
1324  if ( in_array( $messageKey, $module->getMessages() ) ) {
1325  $moduleNames[] = $moduleName;
1326  }
1327  }
1328  return $moduleNames;
1329  }
1330 
1348  private static function makeLoaderImplementScript(
1349  ResourceLoaderContext $context, $name, $scripts, $styles, $messages, $templates
1350  ) {
1351  if ( $scripts instanceof XmlJsCode ) {
1352  if ( $scripts->value === '' ) {
1353  $scripts = null;
1354  } elseif ( $context->getDebug() ) {
1355  $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1356  } else {
1357  $scripts = new XmlJsCode( 'function($,jQuery,require,module){' . $scripts->value . '}' );
1358  }
1359  } elseif ( is_array( $scripts ) && isset( $scripts['files'] ) ) {
1360  $files = $scripts['files'];
1361  foreach ( $files as $path => &$file ) {
1362  // $file is changed (by reference) from a descriptor array to the content of the file
1363  // All of these essentially do $file = $file['content'];, some just have wrapping around it
1364  if ( $file['type'] === 'script' ) {
1365  // Multi-file modules only get two parameters ($ and jQuery are being phased out)
1366  if ( $context->getDebug() ) {
1367  $file = new XmlJsCode( "function ( require, module ) {\n{$file['content']}\n}" );
1368  } else {
1369  $file = new XmlJsCode( 'function(require,module){' . $file['content'] . '}' );
1370  }
1371  } else {
1372  $file = $file['content'];
1373  }
1374  }
1375  $scripts = XmlJsCode::encodeObject( [
1376  'main' => $scripts['main'],
1377  'files' => XmlJsCode::encodeObject( $files, $context->getDebug() )
1378  ], $context->getDebug() );
1379  } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1380  throw new InvalidArgumentException( 'Script must be a string or an array of URLs' );
1381  }
1382 
1383  // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
1384  // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1385  // of "{}". Force them to objects.
1386  $module = [
1387  $name,
1388  $scripts,
1389  (object)$styles,
1390  (object)$messages,
1391  (object)$templates
1392  ];
1393  self::trimArray( $module );
1394 
1395  return Xml::encodeJsCall( 'mw.loader.implement', $module, $context->getDebug() );
1396  }
1397 
1404  public static function makeMessageSetScript( $messages ) {
1405  return 'mw.messages.set('
1406  . self::encodeJsonForScript( (object)$messages )
1407  . ');';
1408  }
1409 
1417  public static function makeCombinedStyles( array $stylePairs ) {
1418  $out = [];
1419  foreach ( $stylePairs as $media => $styles ) {
1420  // ResourceLoaderFileModule::getStyle can return the styles
1421  // as a string or an array of strings. This is to allow separation in
1422  // the front-end.
1423  $styles = (array)$styles;
1424  foreach ( $styles as $style ) {
1425  $style = trim( $style );
1426  // Don't output an empty "@media print { }" block (T42498)
1427  if ( $style !== '' ) {
1428  // Transform the media type based on request params and config
1429  // The way that this relies on $wgRequest to propagate request params is slightly evil
1430  $media = OutputPage::transformCssMedia( $media );
1431 
1432  if ( $media === '' || $media == 'all' ) {
1433  $out[] = $style;
1434  } elseif ( is_string( $media ) ) {
1435  $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1436  }
1437  // else: skip
1438  }
1439  }
1440  }
1441  return $out;
1442  }
1443 
1453  public static function encodeJsonForScript( $data ) {
1454  // Keep output as small as possible by disabling needless escape modes
1455  // that PHP uses by default.
1456  // However, while most module scripts are only served on HTTP responses
1457  // for JavaScript, some modules can also be embedded in the HTML as inline
1458  // scripts. This, and the fact that we sometimes need to export strings
1459  // containing user-generated content and labels that may genuinely contain
1460  // a sequences like "</script>", we need to encode either '/' or '<'.
1461  // By default PHP escapes '/'. Let's escape '<' instead which is less common
1462  // and allows URLs to mostly remain readable.
1463  $jsonFlags = JSON_UNESCAPED_SLASHES |
1464  JSON_UNESCAPED_UNICODE |
1465  JSON_HEX_TAG |
1466  JSON_HEX_AMP;
1467  if ( self::inDebugMode() ) {
1468  $jsonFlags |= JSON_PRETTY_PRINT;
1469  }
1470  return json_encode( $data, $jsonFlags );
1471  }
1472 
1485  public static function makeLoaderStateScript(
1486  ResourceLoaderContext $context, array $states
1487  ) {
1488  return 'mw.loader.state('
1489  . $context->encodeJson( $states )
1490  . ');';
1491  }
1492 
1493  private static function isEmptyObject( stdClass $obj ) {
1494  foreach ( $obj as $key => $value ) {
1495  return false;
1496  }
1497  return true;
1498  }
1499 
1513  private static function trimArray( array &$array ) : void {
1514  $i = count( $array );
1515  while ( $i-- ) {
1516  if ( $array[$i] === null
1517  || $array[$i] === []
1518  || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
1519  || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1520  ) {
1521  unset( $array[$i] );
1522  } else {
1523  break;
1524  }
1525  }
1526  }
1527 
1555  public static function makeLoaderRegisterScript(
1556  ResourceLoaderContext $context, array $modules
1557  ) {
1558  // Optimisation: Transform dependency names into indexes when possible
1559  // to produce smaller output. They are expanded by mw.loader.register on
1560  // the other end using resolveIndexedDependencies().
1561  $index = [];
1562  foreach ( $modules as $i => &$module ) {
1563  // Build module name index
1564  $index[$module[0]] = $i;
1565  }
1566  foreach ( $modules as &$module ) {
1567  if ( isset( $module[2] ) ) {
1568  foreach ( $module[2] as &$dependency ) {
1569  if ( isset( $index[$dependency] ) ) {
1570  // Replace module name in dependency list with index
1571  $dependency = $index[$dependency];
1572  }
1573  }
1574  }
1575  }
1576 
1577  array_walk( $modules, [ self::class, 'trimArray' ] );
1578 
1579  return 'mw.loader.register('
1580  . $context->encodeJson( $modules )
1581  . ');';
1582  }
1583 
1597  public static function makeLoaderSourcesScript(
1598  ResourceLoaderContext $context, array $sources
1599  ) {
1600  return 'mw.loader.addSource('
1601  . $context->encodeJson( $sources )
1602  . ');';
1603  }
1604 
1611  public static function makeLoaderConditionalScript( $script ) {
1612  // Adds a function to lazy-created RLQ
1613  return '(RLQ=window.RLQ||[]).push(function(){' .
1614  trim( $script ) . '});';
1615  }
1616 
1625  public static function makeInlineCodeWithModule( $modules, $script ) {
1626  // Adds an array to lazy-created RLQ
1627  return '(RLQ=window.RLQ||[]).push(['
1629  . 'function(){' . trim( $script ) . '}'
1630  . ']);';
1631  }
1632 
1644  public static function makeInlineScript( $script, $nonce = null ) {
1645  $js = self::makeLoaderConditionalScript( $script );
1646  $escNonce = '';
1647  if ( $nonce === null ) {
1648  wfWarn( __METHOD__ . " did not get nonce. Will break CSP" );
1649  } elseif ( $nonce !== false ) {
1650  // If it was false, CSP is disabled, so no nonce attribute.
1651  // Nonce should be only base64 characters, so should be safe,
1652  // but better to be safely escaped than sorry.
1653  $escNonce = ' nonce="' . htmlspecialchars( $nonce ) . '"';
1654  }
1655 
1656  return new WrappedString(
1657  Html::inlineScript( $js, $nonce ),
1658  "<script$escNonce>(RLQ=window.RLQ||[]).push(function(){",
1659  '});</script>'
1660  );
1661  }
1662 
1671  public static function makeConfigSetScript( array $configuration ) {
1672  $json = self::encodeJsonForScript( $configuration );
1673  if ( $json === false ) {
1674  $e = new Exception(
1675  'JSON serialization of config data failed. ' .
1676  'This usually means the config data is not valid UTF-8.'
1677  );
1679  return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) . ');';
1680  }
1681  return "mw.config.set($json);";
1682  }
1683 
1697  public static function makePackedModulesString( array $modules ) {
1698  $moduleMap = []; // [ prefix => [ suffixes ] ]
1699  foreach ( $modules as $module ) {
1700  $pos = strrpos( $module, '.' );
1701  $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1702  $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1703  $moduleMap[$prefix][] = $suffix;
1704  }
1705 
1706  $arr = [];
1707  foreach ( $moduleMap as $prefix => $suffixes ) {
1708  $p = $prefix === '' ? '' : $prefix . '.';
1709  $arr[] = $p . implode( ',', $suffixes );
1710  }
1711  return implode( '|', $arr );
1712  }
1713 
1725  public static function expandModuleNames( $modules ) {
1726  $retval = [];
1727  $exploded = explode( '|', $modules );
1728  foreach ( $exploded as $group ) {
1729  if ( strpos( $group, ',' ) === false ) {
1730  // This is not a set of modules in foo.bar,baz notation
1731  // but a single module
1732  $retval[] = $group;
1733  } else {
1734  // This is a set of modules in foo.bar,baz notation
1735  $pos = strrpos( $group, '.' );
1736  if ( $pos === false ) {
1737  // Prefixless modules, i.e. without dots
1738  $retval = array_merge( $retval, explode( ',', $group ) );
1739  } else {
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  }
1748  }
1749  return $retval;
1750  }
1751 
1762  public static function inDebugMode() {
1763  if ( self::$debugMode === null ) {
1765  self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
1766  $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
1767  );
1768  }
1769  return self::$debugMode;
1770  }
1771 
1782  public static function clearCache() {
1783  self::$debugMode = null;
1784  }
1785 
1795  public function createLoaderURL( $source, ResourceLoaderContext $context,
1796  array $extraQuery = []
1797  ) {
1798  $query = self::createLoaderQuery( $context, $extraQuery );
1799  $script = $this->getLoadScript( $source );
1800 
1801  return wfAppendQuery( $script, $query );
1802  }
1803 
1813  protected static function createLoaderQuery(
1814  ResourceLoaderContext $context, array $extraQuery = []
1815  ) {
1816  return self::makeLoaderQuery(
1817  $context->getModules(),
1818  $context->getLanguage(),
1819  $context->getSkin(),
1820  $context->getUser(),
1821  $context->getVersion(),
1822  $context->getDebug(),
1823  $context->getOnly(),
1824  $context->getRequest()->getBool( 'printable' ),
1825  $context->getRequest()->getBool( 'handheld' ),
1826  $extraQuery
1827  );
1828  }
1829 
1846  public static function makeLoaderQuery( array $modules, $lang, $skin, $user = null,
1847  $version = null, $debug = false, $only = null, $printable = false,
1848  $handheld = false, array $extraQuery = []
1849  ) {
1850  $query = [
1851  'modules' => self::makePackedModulesString( $modules ),
1852  ];
1853  // Keep urls short by omitting query parameters that
1854  // match the defaults assumed by ResourceLoaderContext.
1855  // Note: This relies on the defaults either being insignificant or forever constant,
1856  // as otherwise cached urls could change in meaning when the defaults change.
1858  $query['lang'] = $lang;
1859  }
1860  if ( $skin !== ResourceLoaderContext::DEFAULT_SKIN ) {
1861  $query['skin'] = $skin;
1862  }
1863  if ( $debug === true ) {
1864  $query['debug'] = 'true';
1865  }
1866  if ( $user !== null ) {
1867  $query['user'] = $user;
1868  }
1869  if ( $version !== null ) {
1870  $query['version'] = $version;
1871  }
1872  if ( $only !== null ) {
1873  $query['only'] = $only;
1874  }
1875  if ( $printable ) {
1876  $query['printable'] = 1;
1877  }
1878  if ( $handheld ) {
1879  $query['handheld'] = 1;
1880  }
1881  $query += $extraQuery;
1882 
1883  // Make queries uniform in order
1884  ksort( $query );
1885  return $query;
1886  }
1887 
1897  public static function isValidModuleName( $moduleName ) {
1898  $len = strlen( $moduleName );
1899  return $len <= 255 && strcspn( $moduleName, '!,|', 0, $len ) === $len;
1900  }
1901 
1913  public function getLessCompiler( array $vars = [], array $importDirs = [] ) {
1914  global $IP;
1915  // When called from the installer, it is possible that a required PHP extension
1916  // is missing (at least for now; see T49564). If this is the case, throw an
1917  // exception (caught by the installer) to prevent a fatal error later on.
1918  if ( !class_exists( 'Less_Parser' ) ) {
1919  throw new MWException( 'MediaWiki requires the less.php parser' );
1920  }
1921 
1922  $importDirs[] = "$IP/resources/src/mediawiki.less";
1923 
1924  $parser = new Less_Parser;
1925  $parser->ModifyVars( $vars );
1926  // SetImportDirs expects an array like [ 'path1' => '', 'path2' => '' ]
1927  $parser->SetImportDirs( array_fill_keys( $importDirs, '' ) );
1928  $parser->SetOption( 'relativeUrls', false );
1929 
1930  return $parser;
1931  }
1932 
1941  public static function getSiteConfigSettings(
1942  ResourceLoaderContext $context, Config $conf
1943  ) : array {
1944  // Namespace related preparation
1945  // - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces.
1946  // - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive.
1947  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1948  $namespaceIds = $contLang->getNamespaceIds();
1949  $caseSensitiveNamespaces = [];
1950  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1951  foreach ( $nsInfo->getCanonicalNamespaces() as $index => $name ) {
1952  $namespaceIds[$contLang->lc( $name )] = $index;
1953  if ( !$nsInfo->isCapitalized( $index ) ) {
1954  $caseSensitiveNamespaces[] = $index;
1955  }
1956  }
1957 
1958  $illegalFileChars = $conf->get( 'IllegalFileChars' );
1959 
1960  // Build list of variables
1961  $skin = $context->getSkin();
1962 
1963  // Start of supported and stable config vars (for use by extensions/gadgets).
1964  $vars = [
1965  'debug' => $context->getDebug(),
1966  'skin' => $skin,
1967  'stylepath' => $conf->get( 'StylePath' ),
1968  'wgArticlePath' => $conf->get( 'ArticlePath' ),
1969  'wgScriptPath' => $conf->get( 'ScriptPath' ),
1970  'wgScript' => $conf->get( 'Script' ),
1971  'wgSearchType' => $conf->get( 'SearchType' ),
1972  'wgVariantArticlePath' => $conf->get( 'VariantArticlePath' ),
1973  'wgServer' => $conf->get( 'Server' ),
1974  'wgServerName' => $conf->get( 'ServerName' ),
1975  'wgUserLanguage' => $context->getLanguage(),
1976  'wgContentLanguage' => $contLang->getCode(),
1977  'wgVersion' => MW_VERSION,
1978  'wgFormattedNamespaces' => $contLang->getFormattedNamespaces(),
1979  'wgNamespaceIds' => $namespaceIds,
1980  'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
1981  'wgSiteName' => $conf->get( 'Sitename' ),
1982  'wgDBname' => $conf->get( 'DBname' ),
1983  'wgWikiID' => WikiMap::getCurrentWikiId(),
1984  'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
1985  'wgCommentByteLimit' => null,
1986  'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
1987  'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
1988  ];
1989  // End of stable config vars.
1990 
1991  // Internal variables for use by MediaWiki core and/or ResourceLoader.
1992  $vars += [
1993  // @internal For mediawiki.widgets
1994  'wgUrlProtocols' => wfUrlProtocols(),
1995  // @internal For mediawiki.page.watch
1996  // Force object to avoid "empty" associative array from
1997  // becoming [] instead of {} in JS (T36604)
1998  'wgActionPaths' => (object)$conf->get( 'ActionPaths' ),
1999  // @internal For mediawiki.language
2000  'wgTranslateNumerals' => $conf->get( 'TranslateNumerals' ),
2001  // @internal For mediawiki.Title
2002  'wgExtraSignatureNamespaces' => $conf->get( 'ExtraSignatureNamespaces' ),
2003  // @internal For mediawiki.cookie
2004  'wgCookiePrefix' => $conf->get( 'CookiePrefix' ),
2005  'wgCookieDomain' => $conf->get( 'CookieDomain' ),
2006  'wgCookiePath' => $conf->get( 'CookiePath' ),
2007  'wgCookieExpiration' => $conf->get( 'CookieExpiration' ),
2008  // @internal For mediawiki.Title
2009  'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
2010  'wgIllegalFileChars' => Title::convertByteClassToUnicodeClass( $illegalFileChars ),
2011  // @internal For mediawiki.ForeignUpload
2012  'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
2013  'wgEnableUploads' => $conf->get( 'EnableUploads' ),
2014  ];
2015 
2016  Hooks::runner()->onResourceLoaderGetConfigVars( $vars, $skin, $conf );
2017 
2018  return $vars;
2019  }
2020 }
ResourceLoader\filter
static filter( $filter, $data, array $options=[])
Run JavaScript or CSS data through a filter, caching the filtered result for future calls.
Definition: ResourceLoader.php:180
ResourceLoader\makeLoaderConditionalScript
static makeLoaderConditionalScript( $script)
Wrap JavaScript code to run after the startup module.
Definition: ResourceLoader.php:1609
ResourceLoaderContext
Context object that contains information about the state of a specific ResourceLoader web request.
Definition: ResourceLoaderContext.php:33
ResourceLoader\addSource
addSource( $sources, $loadUrl=null)
Add a foreign source of modules.
Definition: ResourceLoader.php:457
ResourceLoader\getMessageBlobStore
getMessageBlobStore()
Definition: ResourceLoader.php:299
wfResetOutputBuffers
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
Definition: GlobalFunctions.php:1652
ResourceLoader\sendResponseHeaders
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors, array $extra=[])
Send main response headers to the client.
Definition: ResourceLoader.php:968
ResourceLoader\$errors
array $errors
Errors accumulated during current respond() call.
Definition: ResourceLoader.php:88
ResourceLoader\getLessCompiler
getLessCompiler(array $vars=[], array $importDirs=[])
Return a LESS compiler that is set up for use with MediaWiki.
Definition: ResourceLoader.php:1911
CSSMin\minify
static minify( $css)
Removes whitespace from CSS data.
Definition: CSSMin.php:512
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:84
ResourceLoader\makeConfigSetScript
static makeConfigSetScript(array $configuration)
Return JS code which will set the MediaWiki configuration array to the given value.
Definition: ResourceLoader.php:1669
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:272
ResourceLoader\$depStore
DependencyStore $depStore
Definition: ResourceLoader.php:62
ResourceFileCache\newFromContext
static newFromContext(ResourceLoaderContext $context)
Construct an ResourceFileCache from a context.
Definition: ResourceFileCache.php:40
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:32
ResourceLoader\$depStoreUpdateBuffer
array $depStoreUpdateBuffer
Map of (module-variant => buffered DependencyStore updates)
Definition: ResourceLoader.php:93
FileCacheBase\isCacheGood
isCacheGood( $timestamp='')
Check if up to date cache file exists.
Definition: FileCacheBase.php:119
ResourceLoader\formatException
static formatException(Throwable $e)
Handle exception display.
Definition: ResourceLoader.php:1124
ResourceLoader\encodeJsonForScript
static encodeJsonForScript( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition: ResourceLoader.php:1451
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:166
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
ResourceLoader\makeVersionQuery
makeVersionQuery(ResourceLoaderContext $context, array $modules=null)
Get the expected value of the 'version' query parameter.
Definition: ResourceLoader.php:804
ResourceLoader\makeInlineScript
static makeInlineScript( $script, $nonce=null)
Make an HTML script that runs given JS code after startup and base modules.
Definition: ResourceLoader.php:1642
MWExceptionHandler\getPublicLogMessage
static getPublicLogMessage(Throwable $e)
Definition: MWExceptionHandler.php:510
ResourceLoaderContext\getModules
getModules()
Definition: ResourceLoaderContext.php:152
ResourceLoader\getSiteConfigSettings
static getSiteConfigSettings(ResourceLoaderContext $context, Config $conf)
Get site configuration settings to expose to JavaScript on all pages via mw.config.
Definition: ResourceLoader.php:1939
ResourceLoader\getLogger
getLogger()
Definition: ResourceLoader.php:291
MW_VERSION
const MW_VERSION
The running version of MediaWiki.
Definition: Defines.php:35
ResourceLoader\$hookContainer
HookContainer $hookContainer
Definition: ResourceLoader.php:68
ResourceLoader\isFileModule
isFileModule( $name)
Whether the module is a ResourceLoaderFileModule or subclass thereof.
Definition: ResourceLoader.php:626
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
ResourceLoader\getTestSuiteModuleNames
getTestSuiteModuleNames()
Get a list of module names with QUnit test suites.
Definition: ResourceLoader.php:493
ResourceLoader\$testSuiteModuleNames
string[] $testSuiteModuleNames
List of module names that contain QUnit test suites.
Definition: ResourceLoader.php:84
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
ResourceLoader\makeLoaderRegisterScript
static makeLoaderRegisterScript(ResourceLoaderContext $context, array $modules)
Format JS code which calls mw.loader.register() with the given parameters.
Definition: ResourceLoader.php:1553
ResourceLoader\HookRunner
Definition: HookRunner.php:18
ResourceLoaderModule\getVary
static getVary(ResourceLoaderContext $context)
Get vary string.
Definition: ResourceLoaderModule.php:1028
ResourceLoaderContext\getOnly
getOnly()
Definition: ResourceLoaderContext.php:245
WikiMap\getCurrentWikiId
static getCurrentWikiId()
Definition: WikiMap.php:303
Title\convertByteClassToUnicodeClass
static convertByteClassToUnicodeClass( $byteClass)
Utility method for converting a character sequence from bytes to Unicode.
Definition: Title.php:749
ResourceLoader\isEmptyObject
static isEmptyObject(stdClass $obj)
Definition: ResourceLoader.php:1491
ResourceLoader\$extraHeaders
string[] $extraHeaders
Extra HTTP response headers from modules loaded in makeModuleResponse()
Definition: ResourceLoader.php:90
ResourceLoader\preloadModuleInfo
preloadModuleInfo(array $moduleNames, ResourceLoaderContext $context)
Load information stored in the database and dependency tracking store about modules.
Definition: ResourceLoader.php:120
OutputPage\transformCssMedia
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
Definition: OutputPage.php:4010
$dbr
$dbr
Definition: testCompression.php:54
$debug
$debug
Definition: mcc.php:31
wfAppendQuery
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Definition: GlobalFunctions.php:442
XmlJsCode
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
Definition: XmlJsCode.php:40
ExtensionRegistry\getInstance
static getInstance()
Definition: ExtensionRegistry.php:136
MWExceptionHandler\logException
static logException(Throwable $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log a throwable to the exception log (if enabled).
Definition: MWExceptionHandler.php:665
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:686
ResourceLoader\setMessageBlobStore
setMessageBlobStore(MessageBlobStore $blobStore)
Definition: ResourceLoader.php:307
Config
Interface for configuration instances.
Definition: Config.php:30
MWExceptionHandler\getRedactedTraceAsString
static getRedactedTraceAsString(Throwable $e)
Generate a string representation of a throwable's stack trace.
Definition: MWExceptionHandler.php:364
ResourceLoader\clearCache
static clearCache()
Reset static members used for caching.
Definition: ResourceLoader.php:1780
MWException
MediaWiki exception.
Definition: MWException.php:29
ResourceLoader\ensureNewline
ensureNewline( $str)
Ensure the string is either empty or ends in a line break.
Definition: ResourceLoader.php:1304
ResourceLoader\$moduleInfos
array[] $moduleInfos
Map of (module name => associative info array)
Definition: ResourceLoader.php:76
ResourceLoader\makeModuleResponse
makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing=[])
Generate code for a response.
Definition: ResourceLoader.php:1158
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1033
ResourceLoaderContext\getRequest
getRequest()
Definition: ResourceLoaderContext.php:138
ResourceLoaderWikiModule\preloadTitleInfo
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Definition: ResourceLoaderWikiModule.php:463
Config\get
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
$blob
$blob
Definition: testCompression.php:70
Timing
An interface to help developers measure the performance of their applications.
Definition: Timing.php:45
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2466
ResourceLoader\makeHash
static makeHash( $value)
Create a hash for module versioning purposes.
Definition: ResourceLoader.php:731
ResourceLoaderContext\getDebug
getDebug()
Definition: ResourceLoaderContext.php:238
Timing\measure
measure( $measureName, $startMark='requestStart', $endMark=null)
This method stores the duration between two marks along with the associated name (a "measure").
Definition: Timing.php:124
ResourceLoader\getSources
getSources()
Get the list of sources.
Definition: ResourceLoader.php:645
ResourceLoader\makeLoaderStateScript
static makeLoaderStateScript(ResourceLoaderContext $context, array $states)
Returns a JS call to mw.loader.state, which sets the state of modules to a given value:
Definition: ResourceLoader.php:1483
ResourceLoader\makeInlineCodeWithModule
static makeInlineCodeWithModule( $modules, $script)
Wrap JavaScript code to run after a required module.
Definition: ResourceLoader.php:1623
ResourceLoader\makeComment
static makeComment( $text)
Generate a CSS or JS comment block.
Definition: ResourceLoader.php:1113
ResourceLoaderModule\expandRelativePaths
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
Definition: ResourceLoaderModule.php:561
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ResourceLoaderContext\getLanguage
getLanguage()
Definition: ResourceLoaderContext.php:156
ResourceLoader\$logger
LoggerInterface $logger
Definition: ResourceLoader.php:65
ResourceLoaderContext\getVersion
getVersion()
Definition: ResourceLoaderContext.php:254
FileCacheBase\fetchText
fetchText()
Get the uncompressed text from the cache.
Definition: FileCacheBase.php:146
ResourceLoader\makePackedModulesString
static makePackedModulesString(array $modules)
Convert an array of module names to a packed query string.
Definition: ResourceLoader.php:1695
ResourceLoader\setDependencyStore
setDependencyStore(DependencyStore $tracker)
Definition: ResourceLoader.php:315
ResourceLoaderFileModule\extractBasePaths
static extractBasePaths(array $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
Definition: ResourceLoaderFileModule.php:301
ResourceLoader\getModulesByMessage
getModulesByMessage( $messageKey)
Get names of modules that use a certain message.
Definition: ResourceLoader.php:1318
ResourceLoader\$modules
ResourceLoaderModule[] $modules
Map of (module name => ResourceLoaderModule)
Definition: ResourceLoader.php:74
ResourceLoader\setModuleSkinStyles
setModuleSkinStyles(array $moduleSkinStyles)
Definition: ResourceLoader.php:323
wfUrlProtocols
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
Definition: GlobalFunctions.php:722
ResourceLoader\makeLoaderQuery
static makeLoaderQuery(array $modules, $lang, $skin, $user=null, $version=null, $debug=false, $only=null, $printable=false, $handheld=false, array $extraQuery=[])
Build a query array (array representation of query string) for load.php.
Definition: ResourceLoader.php:1844
ResourceLoaderContext\DEFAULT_SKIN
const DEFAULT_SKIN
Definition: ResourceLoaderContext.php:35
ResourceLoader\getModule
getModule( $name)
Get the ResourceLoaderModule object for a given module name.
Definition: ResourceLoader.php:519
ResourceLoader\$debugMode
static bool $debugMode
Definition: ResourceLoader.php:101
$content
$content
Definition: router.php:76
ResourceFileCache\useFileCache
static useFileCache(ResourceLoaderContext $context)
Check if an RL request can be cached.
Definition: ResourceFileCache.php:66
ResourceLoader\getLoadScript
getLoadScript( $source)
Get the URL to the load.php endpoint for the given ResourceLoader source.
Definition: ResourceLoader.php:657
Html\inlineScript
static inlineScript( $contents, $nonce=null)
Output an HTML script tag with the given contents.
Definition: Html.php:575
$header
$header
Definition: updateCredits.php:37
XmlJsCode\encodeObject
static encodeObject( $obj, $pretty=false)
Encode an object containing XmlJsCode objects.
Definition: XmlJsCode.php:59
ResourceLoader\makeCombinedStyles
static makeCombinedStyles(array $stylePairs)
Combines an associative array mapping media type to CSS into a single stylesheet with "@media" blocks...
Definition: ResourceLoader.php:1415
ResourceLoaderContext\DEFAULT_LANG
const DEFAULT_LANG
Definition: ResourceLoaderContext.php:34
MediaWiki\HeaderCallback
Definition: HeaderCallback.php:8
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
ResourceLoader\applyFilter
static applyFilter( $filter, $data)
Definition: ResourceLoader.php:220
ResourceLoader\$hookRunner
HookRunner $hookRunner
Definition: ResourceLoader.php:71
ResourceLoader\HASH_LENGTH
const HASH_LENGTH
Definition: ResourceLoader.php:667
ResourceLoader\$testModuleNames
array $testModuleNames
Associative array mapping framework ids to a list of names of test suite modules like [ 'qunit' => [ ...
Definition: ResourceLoader.php:82
ResourceLoader\isModuleRegistered
isModuleRegistered( $name)
Check whether a ResourceLoader module is registered.
Definition: ResourceLoader.php:504
ResourceLoader\expandModuleNames
static expandModuleNames( $modules)
Expand a string of the form jquery.foo,bar|jquery.ui.baz,quux to an array of module names like ‘[ 'jq...
Definition: ResourceLoader.php:1723
Wikimedia\DependencyStore\KeyValueDependencyStore
Lightweight class for tracking path dependencies lists via an object cache instance.
Definition: KeyValueDependencyStore.php:36
ResourceLoader\$blobStore
MessageBlobStore $blobStore
Definition: ResourceLoader.php:60
ResourceLoader\getCombinedVersion
getCombinedVersion(ResourceLoaderContext $context, array $moduleNames)
Helper method to get and combine versions of multiple modules.
Definition: ResourceLoader.php:768
ResourceLoaderContext\getSkin
getSkin()
Definition: ResourceLoaderContext.php:188
ResourceLoaderContext\shouldIncludeScripts
shouldIncludeScripts()
Definition: ResourceLoaderContext.php:334
ResourceLoader\respond
respond(ResourceLoaderContext $context)
Output a response to a load request, including the content-type header.
Definition: ResourceLoader.php:831
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:454
ResourceLoader\$config
Config $config
Definition: ResourceLoader.php:58
ResourceLoader\trimArray
static trimArray(array &$array)
Remove empty values from the end of an array.
Definition: ResourceLoader.php:1511
ResourceLoaderContext\getUser
getUser()
Definition: ResourceLoaderContext.php:195
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:48
ResourceLoader\__construct
__construct(Config $config=null, LoggerInterface $logger=null, DependencyStore $tracker=null)
Register core modules and runs registration hooks.
Definition: ResourceLoader.php:241
ResourceLoaderModule
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition: ResourceLoaderModule.php:39
ResourceLoader
ResourceLoader is a loading system for JavaScript and CSS resources.
Definition: ResourceLoader.php:56
ResourceLoader\makeLoaderImplementScript
static makeLoaderImplementScript(ResourceLoaderContext $context, $name, $scripts, $styles, $messages, $templates)
Return JS code that calls mw.loader.implement with given module properties.
Definition: ResourceLoader.php:1346
$cache
$cache
Definition: mcc.php:33
HttpStatus\header
static header( $code)
Output an HTTP status code header.
Definition: HttpStatus.php:96
ResourceLoader\createLoaderURL
createLoaderURL( $source, ResourceLoaderContext $context, array $extraQuery=[])
Build a load.php URL.
Definition: ResourceLoader.php:1793
WebRequest\GETHEADER_LIST
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
Definition: WebRequest.php:72
ResourceLoader\inDebugMode
static inDebugMode()
Determine whether debug mode is on.
Definition: ResourceLoader.php:1760
ResourceLoader\tryRespondFromFileCache
tryRespondFromFileCache(ResourceFileCache $fileCache, ResourceLoaderContext $context, $etag)
Send out code for a response from file cache if possible.
Definition: ResourceLoader.php:1059
ResourceLoader\loadModuleDependenciesInternal
loadModuleDependenciesInternal( $moduleName, $variant)
Definition: ResourceLoader.php:555
$path
$path
Definition: NoLocalSettings.php:25
$wgShowExceptionDetails
$wgShowExceptionDetails
If set to true, uncaught exceptions will print the exception message and a complete stack trace to ou...
Definition: DefaultSettings.php:6755
ResourceLoader\$moduleSkinStyles
array $moduleSkinStyles
Styles that are skin-specific and supplement or replace the default skinStyles of a FileModule.
Definition: ResourceLoader.php:98
JavaScriptMinifier\minify
static minify( $s)
Returns minified JavaScript code.
Definition: JavaScriptMinifier.php:97
ResourceLoader\makeMessageSetScript
static makeMessageSetScript( $messages)
Returns JS code which, when called, will register a given list of messages.
Definition: ResourceLoader.php:1402
Wikimedia\Rdbms\DBConnectionError
@newable
Definition: DBConnectionError.php:27
Wikimedia
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
MessageBlobStore
This class generates message blobs for use by ResourceLoader.
Definition: MessageBlobStore.php:38
$source
$source
Definition: mwdoc-filter.php:34
ResourceLoader\registerTestModules
registerTestModules()
Definition: ResourceLoader.php:407
ResourceLoader\makeLoaderSourcesScript
static makeLoaderSourcesScript(ResourceLoaderContext $context, array $sources)
Format JS code which calls mw.loader.addSource() with the given parameters.
Definition: ResourceLoader.php:1595
ResourceLoader\tryRespondNotModified
tryRespondNotModified(ResourceLoaderContext $context, $etag)
Respond with HTTP 304 Not Modified if appropiate.
Definition: ResourceLoader.php:1026
$hashes
$hashes
Definition: testCompression.php:71
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
ResourceLoader\$sources
array $sources
Map of (source => path); E.g.
Definition: ResourceLoader.php:86
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1080
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:735
ResourceLoaderContext\getImageObj
getImageObj()
If this is a request for an image, get the ResourceLoaderImage object.
Definition: ResourceLoaderContext.php:289
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:645
ResourceLoader\formatExceptionNoComment
static formatExceptionNoComment(Throwable $e)
Handle exception display.
Definition: ResourceLoader.php:1135
ResourceLoader\getConfig
getConfig()
Definition: ResourceLoader.php:275
ResourceLoader\createLoaderQuery
static createLoaderQuery(ResourceLoaderContext $context, array $extraQuery=[])
Helper for createLoaderURL()
Definition: ResourceLoader.php:1811
ResourceLoader\saveModuleDependenciesInternal
saveModuleDependenciesInternal( $moduleName, $variant, $paths, $priorPaths)
Definition: ResourceLoader.php:568
Wikimedia\DependencyStore\DependencyStore
Class for tracking per-entity dependency path lists that are expensive to mass compute.
Definition: DependencyStore.php:28
$tracker
if(count( $args)< 1) $tracker
Definition: trackBlobs.php:37
$IP
$IP
Definition: WebStart.php:49
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
Definition: DeferredUpdates.php:145
ResourceFileCache
ResourceLoader request result caching in the file system.
Definition: ResourceFileCache.php:29
$wgResourceLoaderDebug
$wgResourceLoaderDebug
The default debug mode (on/off) for of ResourceLoader requests.
Definition: DefaultSettings.php:4141
ResourceLoaderContext\getRaw
getRaw()
Definition: ResourceLoaderContext.php:258
ResourceLoaderContext\encodeJson
encodeJson( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition: ResourceLoaderContext.php:405
ResourceLoader\outputErrorAndLog
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
Definition: ResourceLoader.php:751
MWExceptionHandler\getLogMessage
static getLogMessage(Throwable $e)
Get a message formatting the throwable message and its origin.
Definition: MWExceptionHandler.php:475
ResourceLoader\isValidModuleName
static isValidModuleName( $moduleName)
Check a module name for validity.
Definition: ResourceLoader.php:1895
ResourceLoader\getModuleNames
getModuleNames()
Definition: ResourceLoader.php:482
ResourceLoader\setLogger
setLogger(LoggerInterface $logger)
Definition: ResourceLoader.php:283
FileCacheBase\cacheTimestamp
cacheTimestamp()
Get the last-modified timestamp of the cache file.
Definition: FileCacheBase.php:105
ResourceLoader\measureResponseTime
measureResponseTime(Timing $timing)
Definition: ResourceLoader.php:948
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:254