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 );
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 
337  public function register( $name, array $info = null ) {
338  // Allow multiple modules to be registered in one call
339  $registrations = is_array( $name ) ? $name : [ $name => $info ];
340  foreach ( $registrations as $name => $info ) {
341  // Warn on duplicate registrations
342  if ( isset( $this->moduleInfos[$name] ) ) {
343  // A module has already been registered by this name
344  $this->logger->warning(
345  'ResourceLoader duplicate registration warning. ' .
346  'Another module has already been registered as ' . $name
347  );
348  }
349 
350  // Check validity
351  if ( !self::isValidModuleName( $name ) ) {
352  throw new InvalidArgumentException( "ResourceLoader module name '$name' is invalid, "
353  . "see ResourceLoader::isValidModuleName()" );
354  }
355  if ( !is_array( $info ) ) {
356  throw new InvalidArgumentException(
357  'Invalid module info for "' . $name . '": expected array, got ' . gettype( $info )
358  );
359  }
360 
361  // Attach module
362  $this->moduleInfos[$name] = $info;
363 
364  // Last-minute changes
365  // Apply custom skin-defined styles to existing modules.
366  if ( $this->isFileModule( $name ) ) {
367  foreach ( $this->moduleSkinStyles as $skinName => $skinStyles ) {
368  // If this module already defines skinStyles for this skin, ignore ResourceModuleSkinStyles.
369  if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
370  continue;
371  }
372 
373  // If $name is preceded with a '+', the defined style files will be added to 'default'
374  // skinStyles, otherwise 'default' will be ignored as it normally would be.
375  if ( isset( $skinStyles[$name] ) ) {
376  $paths = (array)$skinStyles[$name];
377  $styleFiles = [];
378  } elseif ( isset( $skinStyles['+' . $name] ) ) {
379  $paths = (array)$skinStyles['+' . $name];
380  $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
381  (array)$this->moduleInfos[$name]['skinStyles']['default'] :
382  [];
383  } else {
384  continue;
385  }
386 
387  // Add new file paths, remapping them to refer to our directories and not use settings
388  // from the module we're modifying, which come from the base definition.
389  list( $localBasePath, $remoteBasePath ) =
391 
392  foreach ( $paths as $path ) {
393  $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
394  }
395 
396  $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
397  }
398  }
399  }
400  }
401 
406  public function registerTestModules() : void {
407  global $IP;
408 
409  if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
410  throw new MWException( 'Attempt to register JavaScript test modules '
411  . 'but <code>$wgEnableJavaScriptTest</code> is false. '
412  . 'Edit your <code>LocalSettings.php</code> to enable it.' );
413  }
414 
415  // This has a 'qunit' key for compat with the below hook.
416  $testModulesMeta = [ 'qunit' => [] ];
417 
418  $this->hookRunner->onResourceLoaderTestModules( $testModulesMeta, $this );
419  $extRegistry = ExtensionRegistry::getInstance();
420  // In case of conflict, the deprecated hook has precedence.
421  $testModules = $testModulesMeta['qunit']
422  + $extRegistry->getAttribute( 'QUnitTestModules' );
423 
425  foreach ( $testModules as $name => &$module ) {
426  // Turn any single-module dependency into an array
427  if ( isset( $module['dependencies'] ) && is_string( $module['dependencies'] ) ) {
428  $module['dependencies'] = [ $module['dependencies'] ];
429  }
430 
431  // Ensure the testrunner loads before any test suites
432  $module['dependencies'][] = 'mediawiki.qunit-testrunner';
433 
434  // Keep track of the test suites to load on SpecialJavaScriptTest
435  $testSuiteModuleNames[] = $name;
436  }
437 
438  // Core test suites (their names have further precedence).
439  $testModules = ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules;
440  $testSuiteModuleNames[] = 'test.MediaWiki';
441 
442  $this->register( $testModules );
443  $this->testSuiteModuleNames = $testSuiteModuleNames;
444  }
445 
456  public function addSource( $sources, $loadUrl = null ) {
457  if ( !is_array( $sources ) ) {
458  $sources = [ $sources => $loadUrl ];
459  }
460  foreach ( $sources as $id => $source ) {
461  // Disallow duplicates
462  if ( isset( $this->sources[$id] ) ) {
463  throw new RuntimeException( 'Cannot register source ' . $id . ' twice' );
464  }
465 
466  // Support: MediaWiki 1.24 and earlier
467  if ( is_array( $source ) ) {
468  if ( !isset( $source['loadScript'] ) ) {
469  throw new InvalidArgumentException( 'Each source must have a "loadScript" key' );
470  }
471  $source = $source['loadScript'];
472  }
473 
474  $this->sources[$id] = $source;
475  }
476  }
477 
483  public function getModuleNames() {
484  return array_keys( $this->moduleInfos );
485  }
486 
494  public function getTestSuiteModuleNames() {
496  }
497 
505  public function isModuleRegistered( $name ) {
506  return isset( $this->moduleInfos[$name] );
507  }
508 
520  public function getModule( $name ) {
521  if ( !isset( $this->modules[$name] ) ) {
522  if ( !isset( $this->moduleInfos[$name] ) ) {
523  // No such module
524  return null;
525  }
526  // Construct the requested module object
527  $info = $this->moduleInfos[$name];
528  if ( isset( $info['factory'] ) ) {
530  $object = call_user_func( $info['factory'], $info );
531  } else {
532  $class = $info['class'] ?? ResourceLoaderFileModule::class;
534  $object = new $class( $info );
535  }
536  $object->setConfig( $this->getConfig() );
537  $object->setLogger( $this->logger );
538  $object->setHookContainer( $this->hookContainer );
539  $object->setName( $name );
540  $object->setDependencyAccessCallbacks(
541  [ $this, 'loadModuleDependenciesInternal' ],
542  [ $this, 'saveModuleDependenciesInternal' ]
543  );
544  $this->modules[$name] = $object;
545  }
546 
547  return $this->modules[$name];
548  }
549 
556  public function loadModuleDependenciesInternal( $moduleName, $variant ) {
557  $deps = $this->depStore->retrieve( self::RL_DEP_STORE_PREFIX, "$moduleName|$variant" );
558 
559  return ResourceLoaderModule::expandRelativePaths( $deps['paths'] );
560  }
561 
569  public function saveModuleDependenciesInternal( $moduleName, $variant, $paths, $priorPaths ) {
570  $hasPendingUpdate = (bool)$this->depStoreUpdateBuffer;
571  $entity = "$moduleName|$variant";
572 
573  if ( array_diff( $paths, $priorPaths ) || array_diff( $priorPaths, $paths ) ) {
574  // Dependency store needs to be updated with the new path list
575  if ( $paths ) {
576  $deps = $this->depStore->newEntityDependencies( $paths, time() );
577  $this->depStoreUpdateBuffer[$entity] = $deps;
578  } else {
579  $this->depStoreUpdateBuffer[$entity] = null;
580  }
581  } elseif ( $priorPaths ) {
582  // Dependency store needs to store the existing path list for longer
583  $this->depStoreUpdateBuffer[$entity] = '*';
584  }
585 
586  // Use a DeferrableUpdate to flush the buffered dependency updates...
587  if ( !$hasPendingUpdate ) {
589  $updatesByEntity = $this->depStoreUpdateBuffer;
590  $this->depStoreUpdateBuffer = []; // consume
592 
593  $scopeLocks = [];
594  $depsByEntity = [];
595  $entitiesUnreg = [];
596  $entitiesRenew = [];
597  foreach ( $updatesByEntity as $entity => $update ) {
598  $lockKey = $cache->makeKey( 'rl-deps', $entity );
599  $scopeLocks[$entity] = $cache->getScopedLock( $lockKey, 0 );
600  if ( !$scopeLocks[$entity] ) {
601  // avoid duplicate write request slams (T124649)
602  // the lock must be specific to the current wiki (T247028)
603  continue;
604  } elseif ( $update === null ) {
605  $entitiesUnreg[] = $entity;
606  } elseif ( $update === '*' ) {
607  $entitiesRenew[] = $entity;
608  } else {
609  $depsByEntity[$entity] = $update;
610  }
611  }
612 
613  $ttl = self::RL_MODULE_DEP_TTL;
614  $this->depStore->storeMulti( self::RL_DEP_STORE_PREFIX, $depsByEntity, $ttl );
615  $this->depStore->remove( self::RL_DEP_STORE_PREFIX, $entitiesUnreg );
616  $this->depStore->renew( self::RL_DEP_STORE_PREFIX, $entitiesRenew, $ttl );
617  } );
618  }
619  }
620 
627  protected function isFileModule( $name ) {
628  if ( !isset( $this->moduleInfos[$name] ) ) {
629  return false;
630  }
631  $info = $this->moduleInfos[$name];
632  return !isset( $info['factory'] ) && (
633  // The implied default for 'class' is ResourceLoaderFileModule
634  !isset( $info['class'] ) ||
635  // Explicit default
636  $info['class'] === ResourceLoaderFileModule::class ||
637  is_subclass_of( $info['class'], ResourceLoaderFileModule::class )
638  );
639  }
640 
646  public function getSources() {
647  return $this->sources;
648  }
649 
658  public function getLoadScript( $source ) {
659  if ( !isset( $this->sources[$source] ) ) {
660  throw new UnexpectedValueException( "Unknown source '$source'" );
661  }
662  return $this->sources[$source];
663  }
664 
668  public const HASH_LENGTH = 5;
669 
732  public static function makeHash( $value ) {
733  $hash = hash( 'fnv132', $value );
734  // The base_convert will pad it (if too short),
735  // then substr() will trim it (if too long).
736  return substr(
737  Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
738  0,
739  self::HASH_LENGTH
740  );
741  }
742 
752  public function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
754  $this->logger->warning(
755  $msg,
756  $context + [ 'exception' => $e ]
757  );
758  $this->errors[] = self::formatExceptionNoComment( $e );
759  }
760 
769  public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
770  if ( !$moduleNames ) {
771  return '';
772  }
773  $hashes = array_map( function ( $module ) use ( $context ) {
774  try {
775  return $this->getModule( $module )->getVersionHash( $context );
776  } catch ( Exception $e ) {
777  // If modules fail to compute a version, don't fail the request (T152266)
778  // and still compute versions of other modules.
779  $this->outputErrorAndLog( $e,
780  'Calculating version for "{module}" failed: {exception}',
781  [
782  'module' => $module,
783  ]
784  );
785  return '';
786  }
787  }, $moduleNames );
788  return self::makeHash( implode( '', $hashes ) );
789  }
790 
805  public function makeVersionQuery( ResourceLoaderContext $context, array $modules = null ) {
806  if ( $modules === null ) {
807  wfDeprecated( __METHOD__ . ' without $modules', '1.34' );
808  $modules = $context->getModules();
809  }
810  // As of MediaWiki 1.28, the server and client use the same algorithm for combining
811  // version hashes. There is no technical reason for this to be same, and for years the
812  // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
813  // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
814  // query parameter), then this method must continue to match the JS one.
815  $filtered = [];
816  foreach ( $modules as $name ) {
817  if ( !$this->getModule( $name ) ) {
818  // If a versioned request contains a missing module, the version is a mismatch
819  // as the client considered a module (and version) we don't have.
820  return '';
821  }
822  $filtered[] = $name;
823  }
824  return $this->getCombinedVersion( $context, $filtered );
825  }
826 
832  public function respond( ResourceLoaderContext $context ) {
833  // Buffer output to catch warnings. Normally we'd use ob_clean() on the
834  // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
835  // is used: ob_clean() will clear the GZIP header in that case and it won't come
836  // back for subsequent output, resulting in invalid GZIP. So we have to wrap
837  // the whole thing in our own output buffer to be sure the active buffer
838  // doesn't use ob_gzhandler.
839  // See https://bugs.php.net/bug.php?id=36514
840  ob_start();
841 
842  $this->measureResponseTime( RequestContext::getMain()->getTiming() );
843 
844  // Find out which modules are missing and instantiate the others
845  $modules = [];
846  $missing = [];
847  foreach ( $context->getModules() as $name ) {
848  $module = $this->getModule( $name );
849  if ( $module ) {
850  // Do not allow private modules to be loaded from the web.
851  // This is a security issue, see T36907.
852  if ( $module->getGroup() === 'private' ) {
853  // Not a serious error, just means something is trying to access it (T101806)
854  $this->logger->debug( "Request for private module '$name' denied" );
855  $this->errors[] = "Cannot build private module \"$name\"";
856  continue;
857  }
858  $modules[$name] = $module;
859  } else {
860  $missing[] = $name;
861  }
862  }
863 
864  try {
865  // Preload for getCombinedVersion() and for batch makeModuleResponse()
866  $this->preloadModuleInfo( array_keys( $modules ), $context );
867  } catch ( Exception $e ) {
868  $this->outputErrorAndLog( $e, 'Preloading module info failed: {exception}' );
869  }
870 
871  // Combine versions to propagate cache invalidation
872  $versionHash = '';
873  try {
874  $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
875  } catch ( Exception $e ) {
876  $this->outputErrorAndLog( $e, 'Calculating version hash failed: {exception}' );
877  }
878 
879  // See RFC 2616 § 3.11 Entity Tags
880  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
881  $etag = 'W/"' . $versionHash . '"';
882 
883  // Try the client-side cache first
884  if ( $this->tryRespondNotModified( $context, $etag ) ) {
885  return; // output handled (buffers cleared)
886  }
887 
888  // Use file cache if enabled and available...
889  if ( $this->config->get( 'UseFileCache' ) ) {
890  $fileCache = ResourceFileCache::newFromContext( $context );
891  if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
892  return; // output handled
893  }
894  } else {
895  $fileCache = null;
896  }
897 
898  // Generate a response
899  $response = $this->makeModuleResponse( $context, $modules, $missing );
900 
901  // Capture any PHP warnings from the output buffer and append them to the
902  // error list if we're in debug mode.
903  if ( $context->getDebug() ) {
904  $warnings = ob_get_contents();
905  if ( strlen( $warnings ) ) {
906  $this->errors[] = $warnings;
907  }
908  }
909 
910  // Consider saving the response to file cache (unless there are errors).
911  if ( $fileCache &&
912  !$this->errors &&
913  $missing === [] &&
915  ) {
916  if ( $fileCache->isCacheWorthy() ) {
917  // There were enough hits, save the response to the cache
918  $fileCache->saveText( $response );
919  } else {
920  $fileCache->incrMissesRecent( $context->getRequest() );
921  }
922  }
923 
924  $this->sendResponseHeaders( $context, $etag, (bool)$this->errors, $this->extraHeaders );
925 
926  // Remove the output buffer and output the response
927  ob_end_clean();
928 
929  if ( $context->getImageObj() && $this->errors ) {
930  // We can't show both the error messages and the response when it's an image.
931  $response = implode( "\n\n", $this->errors );
932  } elseif ( $this->errors ) {
933  $errorText = implode( "\n\n", $this->errors );
934  $errorResponse = self::makeComment( $errorText );
935  if ( $context->shouldIncludeScripts() ) {
936  $errorResponse .= 'if (window.console && console.error) { console.error('
937  . $context->encodeJson( $errorText )
938  . "); }\n";
939  }
940 
941  // Prepend error info to the response
942  $response = $errorResponse . $response;
943  }
944 
945  $this->errors = [];
946  echo $response;
947  }
948 
949  protected function measureResponseTime( Timing $timing ) {
950  DeferredUpdates::addCallableUpdate( function () use ( $timing ) {
951  $measure = $timing->measure( 'responseTime', 'requestStart', 'requestShutdown' );
952  if ( $measure !== false ) {
953  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
954  $stats->timing( 'resourceloader.responseTime', $measure['duration'] * 1000 );
955  }
956  } );
957  }
958 
969  protected function sendResponseHeaders(
970  ResourceLoaderContext $context, $etag, $errors, array $extra = []
971  ) : void {
972  HeaderCallback::warnIfHeadersSent();
973  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
974  // Use a short cache expiry so that updates propagate to clients quickly, if:
975  // - No version specified (shared resources, e.g. stylesheets)
976  // - There were errors (recover quickly)
977  // - Version mismatch (T117587, T47877)
978  if ( $context->getVersion() === null
979  || $errors
980  || $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
981  ) {
982  $maxage = $rlMaxage['unversioned'];
983  // If a version was specified we can use a longer expiry time since changing
984  // version numbers causes cache misses
985  } else {
986  $maxage = $rlMaxage['versioned'];
987  }
988  if ( $context->getImageObj() ) {
989  // Output different headers if we're outputting textual errors.
990  if ( $errors ) {
991  header( 'Content-Type: text/plain; charset=utf-8' );
992  } else {
993  $context->getImageObj()->sendResponseHeaders( $context );
994  }
995  } elseif ( $context->getOnly() === 'styles' ) {
996  header( 'Content-Type: text/css; charset=utf-8' );
997  header( 'Access-Control-Allow-Origin: *' );
998  } else {
999  header( 'Content-Type: text/javascript; charset=utf-8' );
1000  }
1001  // See RFC 2616 § 14.19 ETag
1002  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
1003  header( 'ETag: ' . $etag );
1004  if ( $context->getDebug() ) {
1005  // Do not cache debug responses
1006  header( 'Cache-Control: private, no-cache, must-revalidate' );
1007  header( 'Pragma: no-cache' );
1008  } else {
1009  header( "Cache-Control: public, max-age=$maxage, s-maxage=$maxage" );
1010  header( 'Expires: ' . ConvertibleTimestamp::convert( TS_RFC2822, time() + $maxage ) );
1011  }
1012  foreach ( $extra as $header ) {
1013  header( $header );
1014  }
1015  }
1016 
1027  protected function tryRespondNotModified( ResourceLoaderContext $context, $etag ) {
1028  // See RFC 2616 § 14.26 If-None-Match
1029  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
1030  $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
1031  // Never send 304s in debug mode
1032  if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
1033  // There's another bug in ob_gzhandler (see also the comment at
1034  // the top of this function) that causes it to gzip even empty
1035  // responses, meaning it's impossible to produce a truly empty
1036  // response (because the gzip header is always there). This is
1037  // a problem because 304 responses have to be completely empty
1038  // per the HTTP spec, and Firefox behaves buggily when they're not.
1039  // See also https://bugs.php.net/bug.php?id=51579
1040  // To work around this, we tear down all output buffering before
1041  // sending the 304.
1042  wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
1043 
1044  HttpStatus::header( 304 );
1045 
1046  $this->sendResponseHeaders( $context, $etag, false );
1047  return true;
1048  }
1049  return false;
1050  }
1051 
1060  protected function tryRespondFromFileCache(
1061  ResourceFileCache $fileCache,
1062  ResourceLoaderContext $context,
1063  $etag
1064  ) {
1065  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
1066  // Buffer output to catch warnings.
1067  ob_start();
1068  // Get the maximum age the cache can be
1069  $maxage = $context->getVersion() === null
1070  ? $rlMaxage['unversioned']
1071  : $rlMaxage['versioned'];
1072  // Minimum timestamp the cache file must have
1073  $minTime = time() - $maxage;
1074  $good = $fileCache->isCacheGood( ConvertibleTimestamp::convert( TS_MW, $minTime ) );
1075  if ( !$good ) {
1076  try { // RL always hits the DB on file cache miss...
1077  wfGetDB( DB_REPLICA );
1078  } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
1079  $good = $fileCache->isCacheGood(); // cache existence check
1080  }
1081  }
1082  if ( $good ) {
1083  $ts = $fileCache->cacheTimestamp();
1084  // Send content type and cache headers
1085  $this->sendResponseHeaders( $context, $etag, false );
1086  $response = $fileCache->fetchText();
1087  // Capture any PHP warnings from the output buffer and append them to the
1088  // response in a comment if we're in debug mode.
1089  if ( $context->getDebug() ) {
1090  $warnings = ob_get_contents();
1091  if ( strlen( $warnings ) ) {
1092  $response = self::makeComment( $warnings ) . $response;
1093  }
1094  }
1095  // Remove the output buffer and output the response
1096  ob_end_clean();
1097  echo $response . "\n/* Cached {$ts} */";
1098  return true; // cache hit
1099  }
1100  // Clear buffer
1101  ob_end_clean();
1102 
1103  return false; // cache miss
1104  }
1105 
1114  public static function makeComment( $text ) {
1115  $encText = str_replace( '*/', '* /', $text );
1116  return "/*\n$encText\n*/\n";
1117  }
1118 
1125  public static function formatException( Throwable $e ) {
1126  return self::makeComment( self::formatExceptionNoComment( $e ) );
1127  }
1128 
1136  protected static function formatExceptionNoComment( Throwable $e ) {
1137  global $wgShowExceptionDetails;
1138 
1139  if ( !$wgShowExceptionDetails ) {
1141  }
1142 
1143  return MWExceptionHandler::getLogMessage( $e ) .
1144  "\nBacktrace:\n" .
1146  }
1147 
1159  public function makeModuleResponse( ResourceLoaderContext $context,
1160  array $modules, array $missing = []
1161  ) {
1162  $out = '';
1163  $states = [];
1164 
1165  if ( $modules === [] && $missing === [] ) {
1166  return <<<MESSAGE
1167 /* This file is the Web entry point for MediaWiki's ResourceLoader:
1168  <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
1169  no modules were requested. Max made me put this here. */
1170 MESSAGE;
1171  }
1172 
1173  $image = $context->getImageObj();
1174  if ( $image ) {
1175  $data = $image->getImageData( $context );
1176  if ( $data === false ) {
1177  $data = '';
1178  $this->errors[] = 'Image generation failed';
1179  }
1180  return $data;
1181  }
1182 
1183  foreach ( $missing as $name ) {
1184  $states[$name] = 'missing';
1185  }
1186 
1187  $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
1188 
1189  foreach ( $modules as $name => $module ) {
1190  try {
1191  $content = $module->getModuleContent( $context );
1192  $implementKey = $name . '@' . $module->getVersionHash( $context );
1193  $strContent = '';
1194 
1195  if ( isset( $content['headers'] ) ) {
1196  $this->extraHeaders = array_merge( $this->extraHeaders, $content['headers'] );
1197  }
1198 
1199  // Append output
1200  switch ( $context->getOnly() ) {
1201  case 'scripts':
1202  $scripts = $content['scripts'];
1203  if ( is_string( $scripts ) ) {
1204  // Load scripts raw...
1205  $strContent = $scripts;
1206  } elseif ( is_array( $scripts ) ) {
1207  // ...except when $scripts is an array of URLs or an associative array
1208  $strContent = self::makeLoaderImplementScript(
1209  $context,
1210  $implementKey,
1211  $scripts,
1212  [],
1213  [],
1214  []
1215  );
1216  }
1217  break;
1218  case 'styles':
1219  $styles = $content['styles'];
1220  // We no longer separate into media, they are all combined now with
1221  // custom media type groups into @media .. {} sections as part of the css string.
1222  // Module returns either an empty array or a numerical array with css strings.
1223  $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
1224  break;
1225  default:
1226  $scripts = $content['scripts'] ?? '';
1227  if ( is_string( $scripts ) ) {
1228  if ( $name === 'site' || $name === 'user' ) {
1229  // Legacy scripts that run in the global scope without a closure.
1230  // mw.loader.implement will use globalEval if scripts is a string.
1231  // Minify manually here, because general response minification is
1232  // not effective due it being a string literal, not a function.
1233  if ( !$context->getDebug() ) {
1234  $scripts = self::filter( 'minify-js', $scripts ); // T107377
1235  }
1236  } else {
1237  $scripts = new XmlJsCode( $scripts );
1238  }
1239  }
1240  $strContent = self::makeLoaderImplementScript(
1241  $context,
1242  $implementKey,
1243  $scripts,
1244  $content['styles'] ?? [],
1245  isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
1246  $content['templates'] ?? []
1247  );
1248  break;
1249  }
1250 
1251  if ( !$context->getDebug() ) {
1252  $strContent = self::filter( $filter, $strContent );
1253  } else {
1254  // In debug mode, separate each response by a new line.
1255  // For example, between 'mw.loader.implement();' statements.
1256  $strContent = $this->ensureNewline( $strContent );
1257  }
1258 
1259  if ( $context->getOnly() === 'scripts' ) {
1260  // Use a linebreak between module scripts (T162719)
1261  $out .= $this->ensureNewline( $strContent );
1262  } else {
1263  $out .= $strContent;
1264  }
1265 
1266  } catch ( Exception $e ) {
1267  $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1268 
1269  // Respond to client with error-state instead of module implementation
1270  $states[$name] = 'error';
1271  unset( $modules[$name] );
1272  }
1273  }
1274 
1275  // Update module states
1276  if ( $context->shouldIncludeScripts() && !$context->getRaw() ) {
1277  if ( $modules && $context->getOnly() === 'scripts' ) {
1278  // Set the state of modules loaded as only scripts to ready as
1279  // they don't have an mw.loader.implement wrapper that sets the state
1280  foreach ( $modules as $name => $module ) {
1281  $states[$name] = 'ready';
1282  }
1283  }
1284 
1285  // Set the state of modules we didn't respond to with mw.loader.implement
1286  if ( $states ) {
1287  $stateScript = self::makeLoaderStateScript( $context, $states );
1288  if ( !$context->getDebug() ) {
1289  $stateScript = self::filter( 'minify-js', $stateScript );
1290  }
1291  // Use a linebreak between module script and state script (T162719)
1292  $out = $this->ensureNewline( $out ) . $stateScript;
1293  }
1294  } elseif ( $states ) {
1295  $this->errors[] = 'Problematic modules: '
1296  . $context->encodeJson( $states );
1297  }
1298 
1299  return $out;
1300  }
1301 
1307  private function ensureNewline( $str ) {
1308  $end = substr( $str, -1 );
1309  if ( $end === false || $end === '' || $end === "\n" ) {
1310  return $str;
1311  }
1312  return $str . "\n";
1313  }
1314 
1321  public function getModulesByMessage( $messageKey ) {
1322  $moduleNames = [];
1323  foreach ( $this->getModuleNames() as $moduleName ) {
1324  $module = $this->getModule( $moduleName );
1325  if ( in_array( $messageKey, $module->getMessages() ) ) {
1326  $moduleNames[] = $moduleName;
1327  }
1328  }
1329  return $moduleNames;
1330  }
1331 
1349  private static function makeLoaderImplementScript(
1350  ResourceLoaderContext $context, $name, $scripts, $styles, $messages, $templates
1351  ) {
1352  if ( $scripts instanceof XmlJsCode ) {
1353  if ( $scripts->value === '' ) {
1354  $scripts = null;
1355  } elseif ( $context->getDebug() ) {
1356  $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1357  } else {
1358  $scripts = new XmlJsCode( 'function($,jQuery,require,module){' . $scripts->value . '}' );
1359  }
1360  } elseif ( is_array( $scripts ) && isset( $scripts['files'] ) ) {
1361  $files = $scripts['files'];
1362  foreach ( $files as $path => &$file ) {
1363  // $file is changed (by reference) from a descriptor array to the content of the file
1364  // All of these essentially do $file = $file['content'];, some just have wrapping around it
1365  if ( $file['type'] === 'script' ) {
1366  // Multi-file modules only get two parameters ($ and jQuery are being phased out)
1367  if ( $context->getDebug() ) {
1368  $file = new XmlJsCode( "function ( require, module ) {\n{$file['content']}\n}" );
1369  } else {
1370  $file = new XmlJsCode( 'function(require,module){' . $file['content'] . '}' );
1371  }
1372  } else {
1373  $file = $file['content'];
1374  }
1375  }
1376  $scripts = XmlJsCode::encodeObject( [
1377  'main' => $scripts['main'],
1378  'files' => XmlJsCode::encodeObject( $files, $context->getDebug() )
1379  ], $context->getDebug() );
1380  } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1381  throw new InvalidArgumentException( 'Script must be a string or an array of URLs' );
1382  }
1383 
1384  // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
1385  // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1386  // of "{}". Force them to objects.
1387  $module = [
1388  $name,
1389  $scripts,
1390  (object)$styles,
1391  (object)$messages,
1392  (object)$templates
1393  ];
1394  self::trimArray( $module );
1395 
1396  return Xml::encodeJsCall( 'mw.loader.implement', $module, $context->getDebug() );
1397  }
1398 
1405  public static function makeMessageSetScript( $messages ) {
1406  return 'mw.messages.set('
1407  . self::encodeJsonForScript( (object)$messages )
1408  . ');';
1409  }
1410 
1418  public static function makeCombinedStyles( array $stylePairs ) {
1419  $out = [];
1420  foreach ( $stylePairs as $media => $styles ) {
1421  // ResourceLoaderFileModule::getStyle can return the styles
1422  // as a string or an array of strings. This is to allow separation in
1423  // the front-end.
1424  $styles = (array)$styles;
1425  foreach ( $styles as $style ) {
1426  $style = trim( $style );
1427  // Don't output an empty "@media print { }" block (T42498)
1428  if ( $style !== '' ) {
1429  // Transform the media type based on request params and config
1430  // The way that this relies on $wgRequest to propagate request params is slightly evil
1431  $media = OutputPage::transformCssMedia( $media );
1432 
1433  if ( $media === '' || $media == 'all' ) {
1434  $out[] = $style;
1435  } elseif ( is_string( $media ) ) {
1436  $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1437  }
1438  // else: skip
1439  }
1440  }
1441  }
1442  return $out;
1443  }
1444 
1454  public static function encodeJsonForScript( $data ) {
1455  // Keep output as small as possible by disabling needless escape modes
1456  // that PHP uses by default.
1457  // However, while most module scripts are only served on HTTP responses
1458  // for JavaScript, some modules can also be embedded in the HTML as inline
1459  // scripts. This, and the fact that we sometimes need to export strings
1460  // containing user-generated content and labels that may genuinely contain
1461  // a sequences like "</script>", we need to encode either '/' or '<'.
1462  // By default PHP escapes '/'. Let's escape '<' instead which is less common
1463  // and allows URLs to mostly remain readable.
1464  $jsonFlags = JSON_UNESCAPED_SLASHES |
1465  JSON_UNESCAPED_UNICODE |
1466  JSON_HEX_TAG |
1467  JSON_HEX_AMP;
1468  if ( self::inDebugMode() ) {
1469  $jsonFlags |= JSON_PRETTY_PRINT;
1470  }
1471  return json_encode( $data, $jsonFlags );
1472  }
1473 
1486  public static function makeLoaderStateScript(
1487  ResourceLoaderContext $context, array $states
1488  ) {
1489  return 'mw.loader.state('
1490  . $context->encodeJson( $states )
1491  . ');';
1492  }
1493 
1494  private static function isEmptyObject( stdClass $obj ) {
1495  foreach ( $obj as $key => $value ) {
1496  return false;
1497  }
1498  return true;
1499  }
1500 
1514  private static function trimArray( array &$array ) : void {
1515  $i = count( $array );
1516  while ( $i-- ) {
1517  if ( $array[$i] === null
1518  || $array[$i] === []
1519  || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
1520  || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1521  ) {
1522  unset( $array[$i] );
1523  } else {
1524  break;
1525  }
1526  }
1527  }
1528 
1556  public static function makeLoaderRegisterScript(
1557  ResourceLoaderContext $context, array $modules
1558  ) {
1559  // Optimisation: Transform dependency names into indexes when possible
1560  // to produce smaller output. They are expanded by mw.loader.register on
1561  // the other end using resolveIndexedDependencies().
1562  $index = [];
1563  foreach ( $modules as $i => &$module ) {
1564  // Build module name index
1565  $index[$module[0]] = $i;
1566  }
1567  foreach ( $modules as &$module ) {
1568  if ( isset( $module[2] ) ) {
1569  foreach ( $module[2] as &$dependency ) {
1570  if ( isset( $index[$dependency] ) ) {
1571  // Replace module name in dependency list with index
1572  $dependency = $index[$dependency];
1573  }
1574  }
1575  }
1576  }
1577 
1578  array_walk( $modules, [ self::class, 'trimArray' ] );
1579 
1580  return 'mw.loader.register('
1581  . $context->encodeJson( $modules )
1582  . ');';
1583  }
1584 
1598  public static function makeLoaderSourcesScript(
1599  ResourceLoaderContext $context, array $sources
1600  ) {
1601  return 'mw.loader.addSource('
1602  . $context->encodeJson( $sources )
1603  . ');';
1604  }
1605 
1612  public static function makeLoaderConditionalScript( $script ) {
1613  // Adds a function to lazy-created RLQ
1614  return '(RLQ=window.RLQ||[]).push(function(){' .
1615  trim( $script ) . '});';
1616  }
1617 
1626  public static function makeInlineCodeWithModule( $modules, $script ) {
1627  // Adds an array to lazy-created RLQ
1628  return '(RLQ=window.RLQ||[]).push(['
1630  . 'function(){' . trim( $script ) . '}'
1631  . ']);';
1632  }
1633 
1645  public static function makeInlineScript( $script, $nonce = null ) {
1646  $js = self::makeLoaderConditionalScript( $script );
1647  $escNonce = '';
1648  if ( $nonce === null ) {
1649  wfWarn( __METHOD__ . " did not get nonce. Will break CSP" );
1650  } elseif ( $nonce !== false ) {
1651  // If it was false, CSP is disabled, so no nonce attribute.
1652  // Nonce should be only base64 characters, so should be safe,
1653  // but better to be safely escaped than sorry.
1654  $escNonce = ' nonce="' . htmlspecialchars( $nonce ) . '"';
1655  }
1656 
1657  return new WrappedString(
1658  Html::inlineScript( $js, $nonce ),
1659  "<script$escNonce>(RLQ=window.RLQ||[]).push(function(){",
1660  '});</script>'
1661  );
1662  }
1663 
1672  public static function makeConfigSetScript( array $configuration ) {
1673  $json = self::encodeJsonForScript( $configuration );
1674  if ( $json === false ) {
1675  $e = new Exception(
1676  'JSON serialization of config data failed. ' .
1677  'This usually means the config data is not valid UTF-8.'
1678  );
1680  return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) . ');';
1681  }
1682  return "mw.config.set($json);";
1683  }
1684 
1698  public static function makePackedModulesString( array $modules ) {
1699  $moduleMap = []; // [ prefix => [ suffixes ] ]
1700  foreach ( $modules as $module ) {
1701  $pos = strrpos( $module, '.' );
1702  $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1703  $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1704  $moduleMap[$prefix][] = $suffix;
1705  }
1706 
1707  $arr = [];
1708  foreach ( $moduleMap as $prefix => $suffixes ) {
1709  $p = $prefix === '' ? '' : $prefix . '.';
1710  $arr[] = $p . implode( ',', $suffixes );
1711  }
1712  return implode( '|', $arr );
1713  }
1714 
1726  public static function expandModuleNames( $modules ) {
1727  $retval = [];
1728  $exploded = explode( '|', $modules );
1729  foreach ( $exploded as $group ) {
1730  if ( strpos( $group, ',' ) === false ) {
1731  // This is not a set of modules in foo.bar,baz notation
1732  // but a single module
1733  $retval[] = $group;
1734  } else {
1735  // This is a set of modules in foo.bar,baz notation
1736  $pos = strrpos( $group, '.' );
1737  if ( $pos === false ) {
1738  // Prefixless modules, i.e. without dots
1739  $retval = array_merge( $retval, explode( ',', $group ) );
1740  } else {
1741  // We have a prefix and a bunch of suffixes
1742  $prefix = substr( $group, 0, $pos ); // 'foo'
1743  $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // [ 'bar', 'baz' ]
1744  foreach ( $suffixes as $suffix ) {
1745  $retval[] = "$prefix.$suffix";
1746  }
1747  }
1748  }
1749  }
1750  return $retval;
1751  }
1752 
1763  public static function inDebugMode() {
1764  if ( self::$debugMode === null ) {
1766  self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
1767  $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
1768  );
1769  }
1770  return self::$debugMode;
1771  }
1772 
1783  public static function clearCache() {
1784  self::$debugMode = null;
1785  }
1786 
1796  public function createLoaderURL( $source, ResourceLoaderContext $context,
1797  array $extraQuery = []
1798  ) {
1799  $query = self::createLoaderQuery( $context, $extraQuery );
1800  $script = $this->getLoadScript( $source );
1801 
1802  return wfAppendQuery( $script, $query );
1803  }
1804 
1814  protected static function createLoaderQuery(
1815  ResourceLoaderContext $context, array $extraQuery = []
1816  ) {
1817  return self::makeLoaderQuery(
1818  $context->getModules(),
1819  $context->getLanguage(),
1820  $context->getSkin(),
1821  $context->getUser(),
1822  $context->getVersion(),
1823  $context->getDebug(),
1824  $context->getOnly(),
1825  $context->getRequest()->getBool( 'printable' ),
1826  $context->getRequest()->getBool( 'handheld' ),
1827  $extraQuery
1828  );
1829  }
1830 
1847  public static function makeLoaderQuery( array $modules, $lang, $skin, $user = null,
1848  $version = null, $debug = false, $only = null, $printable = false,
1849  $handheld = false, array $extraQuery = []
1850  ) {
1851  $query = [
1852  'modules' => self::makePackedModulesString( $modules ),
1853  ];
1854  // Keep urls short by omitting query parameters that
1855  // match the defaults assumed by ResourceLoaderContext.
1856  // Note: This relies on the defaults either being insignificant or forever constant,
1857  // as otherwise cached urls could change in meaning when the defaults change.
1859  $query['lang'] = $lang;
1860  }
1861  if ( $skin !== ResourceLoaderContext::DEFAULT_SKIN ) {
1862  $query['skin'] = $skin;
1863  }
1864  if ( $debug === true ) {
1865  $query['debug'] = 'true';
1866  }
1867  if ( $user !== null ) {
1868  $query['user'] = $user;
1869  }
1870  if ( $version !== null ) {
1871  $query['version'] = $version;
1872  }
1873  if ( $only !== null ) {
1874  $query['only'] = $only;
1875  }
1876  if ( $printable ) {
1877  $query['printable'] = 1;
1878  }
1879  if ( $handheld ) {
1880  $query['handheld'] = 1;
1881  }
1882  $query += $extraQuery;
1883 
1884  // Make queries uniform in order
1885  ksort( $query );
1886  return $query;
1887  }
1888 
1898  public static function isValidModuleName( $moduleName ) {
1899  return strcspn( $moduleName, '!,|', 0, 255 ) === strlen( $moduleName );
1900  }
1901 
1912  public function getLessCompiler( $vars = [] ) {
1913  global $IP;
1914  // When called from the installer, it is possible that a required PHP extension
1915  // is missing (at least for now; see T49564). If this is the case, throw an
1916  // exception (caught by the installer) to prevent a fatal error later on.
1917  if ( !class_exists( 'Less_Parser' ) ) {
1918  throw new MWException( 'MediaWiki requires the less.php parser' );
1919  }
1920 
1921  $parser = new Less_Parser;
1922  $parser->ModifyVars( $vars );
1923  $parser->SetImportDirs( [
1924  "$IP/resources/src/mediawiki.less/" => '',
1925  ] );
1926  $parser->SetOption( 'relativeUrls', false );
1927 
1928  return $parser;
1929  }
1930 
1939  public static function getSiteConfigSettings(
1940  ResourceLoaderContext $context, Config $conf
1941  ) : array {
1942  // Namespace related preparation
1943  // - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces.
1944  // - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive.
1945  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
1946  $namespaceIds = $contLang->getNamespaceIds();
1947  $caseSensitiveNamespaces = [];
1948  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
1949  foreach ( $nsInfo->getCanonicalNamespaces() as $index => $name ) {
1950  $namespaceIds[$contLang->lc( $name )] = $index;
1951  if ( !$nsInfo->isCapitalized( $index ) ) {
1952  $caseSensitiveNamespaces[] = $index;
1953  }
1954  }
1955 
1956  $illegalFileChars = $conf->get( 'IllegalFileChars' );
1957 
1958  // Build list of variables
1959  $skin = $context->getSkin();
1960 
1961  // Start of supported and stable config vars (for use by extensions/gadgets).
1962  $vars = [
1963  'debug' => $context->getDebug(),
1964  'skin' => $skin,
1965  'stylepath' => $conf->get( 'StylePath' ),
1966  'wgArticlePath' => $conf->get( 'ArticlePath' ),
1967  'wgScriptPath' => $conf->get( 'ScriptPath' ),
1968  'wgScript' => $conf->get( 'Script' ),
1969  'wgSearchType' => $conf->get( 'SearchType' ),
1970  'wgVariantArticlePath' => $conf->get( 'VariantArticlePath' ),
1971  'wgServer' => $conf->get( 'Server' ),
1972  'wgServerName' => $conf->get( 'ServerName' ),
1973  'wgUserLanguage' => $context->getLanguage(),
1974  'wgContentLanguage' => $contLang->getCode(),
1975  'wgVersion' => MW_VERSION,
1976  'wgFormattedNamespaces' => $contLang->getFormattedNamespaces(),
1977  'wgNamespaceIds' => $namespaceIds,
1978  'wgContentNamespaces' => $nsInfo->getContentNamespaces(),
1979  'wgSiteName' => $conf->get( 'Sitename' ),
1980  'wgDBname' => $conf->get( 'DBname' ),
1981  'wgWikiID' => WikiMap::getCurrentWikiId(),
1982  'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
1983  'wgCommentByteLimit' => null,
1984  'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
1985  'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
1986  ];
1987  // End of stable config vars.
1988 
1989  // Internal variables for use by MediaWiki core and/or ResourceLoader.
1990  $vars += [
1991  // @internal For mediawiki.widgets
1992  'wgUrlProtocols' => wfUrlProtocols(),
1993  // @internal For mediawiki.page.watch
1994  // Force object to avoid "empty" associative array from
1995  // becoming [] instead of {} in JS (T36604)
1996  'wgActionPaths' => (object)$conf->get( 'ActionPaths' ),
1997  // @internal For mediawiki.language
1998  'wgTranslateNumerals' => $conf->get( 'TranslateNumerals' ),
1999  // @internal For mediawiki.Title
2000  'wgExtraSignatureNamespaces' => $conf->get( 'ExtraSignatureNamespaces' ),
2001  // @internal For mediawiki.cookie
2002  'wgCookiePrefix' => $conf->get( 'CookiePrefix' ),
2003  'wgCookieDomain' => $conf->get( 'CookieDomain' ),
2004  'wgCookiePath' => $conf->get( 'CookiePath' ),
2005  'wgCookieExpiration' => $conf->get( 'CookieExpiration' ),
2006  // @internal For mediawiki.Title
2007  'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
2008  'wgIllegalFileChars' => Title::convertByteClassToUnicodeClass( $illegalFileChars ),
2009  // @internal For mediawiki.ForeignUpload
2010  'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
2011  'wgEnableUploads' => $conf->get( 'EnableUploads' ),
2012  ];
2013 
2014  Hooks::runner()->onResourceLoaderGetConfigVars( $vars, $skin, $conf );
2015 
2016  return $vars;
2017  }
2018 }
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:1610
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:456
ResourceLoader\getMessageBlobStore
getMessageBlobStore()
Definition: ResourceLoader.php:299
wfResetOutputBuffers
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
Definition: GlobalFunctions.php:1638
ResourceLoader\sendResponseHeaders
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors, array $extra=[])
Send main response headers to the client.
Definition: ResourceLoader.php:969
ResourceLoader\$errors
array $errors
Errors accumulated during current respond() call.
Definition: ResourceLoader.php:88
CSSMin\minify
static minify( $css)
Removes whitespace from CSS data.
Definition: CSSMin.php:476
ResourceLoader\makeConfigSetScript
static makeConfigSetScript(array $configuration)
Return JS code which will set the MediaWiki configuration array to the given value.
Definition: ResourceLoader.php:1670
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:31
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:1125
ResourceLoader\encodeJsonForScript
static encodeJsonForScript( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition: ResourceLoader.php:1452
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:152
$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:805
ResourceLoader\makeInlineScript
static makeInlineScript( $script, $nonce=null)
Make an HTML script that runs given JS code after startup and base modules.
Definition: ResourceLoader.php:1643
MWExceptionHandler\getPublicLogMessage
static getPublicLogMessage(Throwable $e)
Definition: MWExceptionHandler.php:501
ResourceLoaderContext\getModules
getModules()
Definition: ResourceLoaderContext.php:150
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:1937
ResourceLoader\getLogger
getLogger()
Definition: ResourceLoader.php:291
MW_VERSION
const MW_VERSION
The running version of MediaWiki.
Definition: Defines.php:39
ResourceLoader\$hookContainer
HookContainer $hookContainer
Definition: ResourceLoader.php:68
ResourceLoader\isFileModule
isFileModule( $name)
Whether the module is a ResourceLoaderFileModule or subclass thereof.
Definition: ResourceLoader.php:627
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:494
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:1554
ResourceLoaderModule\getVary
static getVary(ResourceLoaderContext $context)
Get vary string.
Definition: ResourceLoaderModule.php:993
ResourceLoaderContext\getOnly
getOnly()
Definition: ResourceLoaderContext.php:243
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:714
ResourceLoader\isEmptyObject
static isEmptyObject(stdClass $obj)
Definition: ResourceLoader.php:1492
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
ResourceLoader\getLessCompiler
getLessCompiler( $vars=[])
Return a LESS compiler that is set up for use with MediaWiki.
Definition: ResourceLoader.php:1910
OutputPage\transformCssMedia
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
Definition: OutputPage.php:3998
$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:438
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:656
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:681
ResourceLoader\setMessageBlobStore
setMessageBlobStore(MessageBlobStore $blobStore)
Definition: ResourceLoader.php:307
Config
Interface for configuration instances.
Definition: Config.php:28
MWExceptionHandler\getRedactedTraceAsString
static getRedactedTraceAsString(Throwable $e)
Generate a string representation of a throwable's stack trace.
Definition: MWExceptionHandler.php:352
ResourceLoader\clearCache
static clearCache()
Reset static members used for caching.
Definition: ResourceLoader.php:1781
MWException
MediaWiki exception.
Definition: MWException.php:26
ResourceLoader\ensureNewline
ensureNewline( $str)
Ensure the string is either empty or ends in a line break.
Definition: ResourceLoader.php:1305
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:1159
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1029
ResourceLoaderContext\getRequest
getRequest()
Definition: ResourceLoaderContext.php:136
ResourceLoaderWikiModule\preloadTitleInfo
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Definition: ResourceLoaderWikiModule.php:459
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:2464
ResourceLoader\makeHash
static makeHash( $value)
Create a hash for module versioning purposes.
Definition: ResourceLoader.php:732
ResourceLoaderContext\getDebug
getDebug()
Definition: ResourceLoaderContext.php:236
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:646
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:1484
ResourceLoader\makeInlineCodeWithModule
static makeInlineCodeWithModule( $modules, $script)
Wrap JavaScript code to run after a required module.
Definition: ResourceLoader.php:1624
ResourceLoader\makeComment
static makeComment( $text)
Generate a CSS or JS comment block.
Definition: ResourceLoader.php:1114
ResourceLoaderModule\expandRelativePaths
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
Definition: ResourceLoaderModule.php:543
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ResourceLoaderContext\getLanguage
getLanguage()
Definition: ResourceLoaderContext.php:154
ResourceLoader\$logger
LoggerInterface $logger
Definition: ResourceLoader.php:65
ResourceLoaderContext\getVersion
getVersion()
Definition: ResourceLoaderContext.php:252
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:1696
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:300
ResourceLoader\getModulesByMessage
getModulesByMessage( $messageKey)
Get names of modules that use a certain message.
Definition: ResourceLoader.php:1319
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:718
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:1845
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:520
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:658
Html\inlineScript
static inlineScript( $contents, $nonce=null)
Output an HTML script tag with the given contents.
Definition: Html.php:572
$header
$header
Definition: updateCredits.php:41
XmlJsCode\encodeObject
static encodeObject( $obj, $pretty=false)
Encode an object containing XmlJsCode objects.
Definition: XmlJsCode.php:58
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:1416
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:668
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:90
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:505
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:1724
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:769
ResourceLoaderContext\getSkin
getSkin()
Definition: ResourceLoaderContext.php:186
ResourceLoaderContext\shouldIncludeScripts
shouldIncludeScripts()
Definition: ResourceLoaderContext.php:332
ResourceLoader\respond
respond(ResourceLoaderContext $context)
Output a response to a load request, including the content-type header.
Definition: ResourceLoader.php:832
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:451
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:1512
ResourceLoaderContext\getUser
getUser()
Definition: ResourceLoaderContext.php:193
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:37
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:38
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:1347
$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:1794
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:1761
ResourceLoader\tryRespondFromFileCache
tryRespondFromFileCache(ResourceFileCache $fileCache, ResourceLoaderContext $context, $etag)
Send out code for a response from file cache if possible.
Definition: ResourceLoader.php:1060
ResourceLoader\loadModuleDependenciesInternal
loadModuleDependenciesInternal( $moduleName, $variant)
Definition: ResourceLoader.php:556
$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:6726
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:1403
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
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:406
ResourceLoader\makeLoaderSourcesScript
static makeLoaderSourcesScript(ResourceLoaderContext $context, array $sources)
Format JS code which calls mw.loader.addSource() with the given parameters.
Definition: ResourceLoader.php:1596
ResourceLoader\tryRespondNotModified
tryRespondNotModified(ResourceLoaderContext $context, $etag)
Respond with HTTP 304 Not Modified if appropiate.
Definition: ResourceLoader.php:1027
$hashes
$hashes
Definition: testCompression.php:71
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:44
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:1076
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:571
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:700
ResourceLoaderContext\getImageObj
getImageObj()
If this is a request for an image, get the ResourceLoaderImage object.
Definition: ResourceLoaderContext.php:287
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:644
ResourceLoader\formatExceptionNoComment
static formatExceptionNoComment(Throwable $e)
Handle exception display.
Definition: ResourceLoader.php:1136
ResourceLoader\getConfig
getConfig()
Definition: ResourceLoader.php:275
ResourceLoader\createLoaderQuery
static createLoaderQuery(ResourceLoaderContext $context, array $extraQuery=[])
Helper for createLoaderURL()
Definition: ResourceLoader.php:1812
ResourceLoader\saveModuleDependenciesInternal
saveModuleDependenciesInternal( $moduleName, $variant, $paths, $priorPaths)
Definition: ResourceLoader.php:569
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 a callable update.
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:4126
ResourceLoaderContext\getRaw
getRaw()
Definition: ResourceLoaderContext.php:256
ResourceLoaderContext\encodeJson
encodeJson( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition: ResourceLoaderContext.php:403
ResourceLoader\outputErrorAndLog
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
Definition: ResourceLoader.php:752
MWExceptionHandler\getLogMessage
static getLogMessage(Throwable $e)
Get a message formatting the throwable message and its origin.
Definition: MWExceptionHandler.php:462
ResourceLoader\isValidModuleName
static isValidModuleName( $moduleName)
Check a module name for validity.
Definition: ResourceLoader.php:1896
ResourceLoader\getModuleNames
getModuleNames()
Get a list of module names.
Definition: ResourceLoader.php:483
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:949
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:254