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