MediaWiki  1.34.4
ResourceLoader.php
Go to the documentation of this file.
1 <?php
24 use Psr\Log\LoggerAwareInterface;
25 use Psr\Log\LoggerInterface;
26 use Psr\Log\NullLogger;
28 use Wikimedia\WrappedString;
29 
44 class ResourceLoader implements LoggerAwareInterface {
46  protected $config;
48  protected $blobStore;
49 
51  private $logger;
52 
54  protected $modules = [];
56  protected $moduleInfos = [];
62  protected $testModuleNames = [];
64  protected $testSuiteModuleNames = [];
65 
67  protected $sources = [];
69  protected $errors = [];
71  protected $extraHeaders = [];
72 
74  protected static $debugMode = null;
75 
77  const CACHE_VERSION = 8;
78 
80  const FILTER_NOMIN = '/*@nomin*/';
81 
96  public function preloadModuleInfo( array $moduleNames, ResourceLoaderContext $context ) {
97  if ( !$moduleNames ) {
98  // Or else Database*::select() will explode, plus it's cheaper!
99  return;
100  }
101  $dbr = wfGetDB( DB_REPLICA );
102  $lang = $context->getLanguage();
103 
104  // Batched version of ResourceLoaderModule::getFileDependencies
106  $res = $dbr->select( 'module_deps', [ 'md_module', 'md_deps' ], [
107  'md_module' => $moduleNames,
108  'md_skin' => $vary,
109  ], __METHOD__
110  );
111 
112  // Prime in-object cache for file dependencies
113  $modulesWithDeps = [];
114  foreach ( $res as $row ) {
115  $module = $this->getModule( $row->md_module );
116  if ( $module ) {
117  $module->setFileDependencies( $context, ResourceLoaderModule::expandRelativePaths(
118  json_decode( $row->md_deps, true )
119  ) );
120  $modulesWithDeps[] = $row->md_module;
121  }
122  }
123  // Register the absence of a dependency row too
124  foreach ( array_diff( $moduleNames, $modulesWithDeps ) as $name ) {
125  $module = $this->getModule( $name );
126  if ( $module ) {
127  $this->getModule( $name )->setFileDependencies( $context, [] );
128  }
129  }
130 
131  // Batched version of ResourceLoaderWikiModule::getTitleInfo
133 
134  // Prime in-object cache for message blobs for modules with messages
135  $modules = [];
136  foreach ( $moduleNames as $name ) {
137  $module = $this->getModule( $name );
138  if ( $module && $module->getMessages() ) {
139  $modules[$name] = $module;
140  }
141  }
142  $store = $this->getMessageBlobStore();
143  $blobs = $store->getBlobs( $modules, $lang );
144  foreach ( $blobs as $name => $blob ) {
145  $modules[$name]->setMessageBlob( $blob, $lang );
146  }
147  }
148 
166  public static function filter( $filter, $data, array $options = [] ) {
167  if ( strpos( $data, self::FILTER_NOMIN ) !== false ) {
168  return $data;
169  }
170 
171  if ( isset( $options['cache'] ) && $options['cache'] === false ) {
172  return self::applyFilter( $filter, $data );
173  }
174 
175  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
177 
178  $key = $cache->makeGlobalKey(
179  'resourceloader-filter',
180  $filter,
181  self::CACHE_VERSION,
182  md5( $data )
183  );
184 
185  $result = $cache->get( $key );
186  if ( $result === false ) {
187  $stats->increment( "resourceloader_cache.$filter.miss" );
188  $result = self::applyFilter( $filter, $data );
189  $cache->set( $key, $result, 24 * 3600 );
190  } else {
191  $stats->increment( "resourceloader_cache.$filter.hit" );
192  }
193  if ( $result === null ) {
194  // Cached failure
195  $result = $data;
196  }
197 
198  return $result;
199  }
200 
201  private static function applyFilter( $filter, $data ) {
202  $data = trim( $data );
203  if ( $data ) {
204  try {
205  $data = ( $filter === 'minify-css' )
206  ? CSSMin::minify( $data )
207  : JavaScriptMinifier::minify( $data );
208  } catch ( Exception $e ) {
210  return null;
211  }
212  }
213  return $data;
214  }
215 
221  public function __construct( Config $config = null, LoggerInterface $logger = null ) {
222  $this->logger = $logger ?: new NullLogger();
223 
224  if ( !$config ) {
225  wfDeprecated( __METHOD__ . ' without a Config instance', '1.34' );
226  $config = MediaWikiServices::getInstance()->getMainConfig();
227  }
228  $this->config = $config;
229 
230  // Add 'local' source first
231  $this->addSource( 'local', $config->get( 'LoadScript' ) );
232 
233  // Special module that always exists
234  $this->register( 'startup', [ 'class' => ResourceLoaderStartUpModule::class ] );
235 
236  $this->setMessageBlobStore( new MessageBlobStore( $this, $this->logger ) );
237  }
238 
242  public function getConfig() {
243  return $this->config;
244  }
245 
250  public function setLogger( LoggerInterface $logger ) {
251  $this->logger = $logger;
252  }
253 
258  public function getLogger() {
259  return $this->logger;
260  }
261 
266  public function getMessageBlobStore() {
267  return $this->blobStore;
268  }
269 
275  $this->blobStore = $blobStore;
276  }
277 
289  public function register( $name, $info = null ) {
290  $moduleSkinStyles = $this->config->get( 'ResourceModuleSkinStyles' );
291 
292  // Allow multiple modules to be registered in one call
293  $registrations = is_array( $name ) ? $name : [ $name => $info ];
294  foreach ( $registrations as $name => $info ) {
295  // Warn on duplicate registrations
296  if ( isset( $this->moduleInfos[$name] ) ) {
297  // A module has already been registered by this name
298  $this->logger->warning(
299  'ResourceLoader duplicate registration warning. ' .
300  'Another module has already been registered as ' . $name
301  );
302  }
303 
304  // Check validity
305  if ( !self::isValidModuleName( $name ) ) {
306  throw new MWException( "ResourceLoader module name '$name' is invalid, "
307  . "see ResourceLoader::isValidModuleName()" );
308  }
309  if ( !is_array( $info ) ) {
310  throw new InvalidArgumentException(
311  'Invalid module info for "' . $name . '": expected array, got ' . gettype( $info )
312  );
313  }
314 
315  // Attach module
316  $this->moduleInfos[$name] = $info;
317 
318  // Last-minute changes
319  // Apply custom skin-defined styles to existing modules.
320  if ( $this->isFileModule( $name ) ) {
321  foreach ( $moduleSkinStyles as $skinName => $skinStyles ) {
322  // If this module already defines skinStyles for this skin, ignore $wgResourceModuleSkinStyles.
323  if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
324  continue;
325  }
326 
327  // If $name is preceded with a '+', the defined style files will be added to 'default'
328  // skinStyles, otherwise 'default' will be ignored as it normally would be.
329  if ( isset( $skinStyles[$name] ) ) {
330  $paths = (array)$skinStyles[$name];
331  $styleFiles = [];
332  } elseif ( isset( $skinStyles['+' . $name] ) ) {
333  $paths = (array)$skinStyles['+' . $name];
334  $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
335  (array)$this->moduleInfos[$name]['skinStyles']['default'] :
336  [];
337  } else {
338  continue;
339  }
340 
341  // Add new file paths, remapping them to refer to our directories and not use settings
342  // from the module we're modifying, which come from the base definition.
343  list( $localBasePath, $remoteBasePath ) =
345 
346  foreach ( $paths as $path ) {
347  $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
348  }
349 
350  $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
351  }
352  }
353  }
354  }
355 
360  public function registerTestModules() {
361  global $IP;
362 
363  if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
364  throw new MWException( 'Attempt to register JavaScript test modules '
365  . 'but <code>$wgEnableJavaScriptTest</code> is false. '
366  . 'Edit your <code>LocalSettings.php</code> to enable it.' );
367  }
368 
369  // This has a 'qunit' key for compat with the below hook.
370  $testModulesMeta = [ 'qunit' => [] ];
371 
372  // Get test suites from extensions
373  // Avoid PHP 7.1 warning from passing $this by reference
374  $rl = $this;
375  Hooks::run( 'ResourceLoaderTestModules', [ &$testModulesMeta, &$rl ] );
376  $extRegistry = ExtensionRegistry::getInstance();
377  // In case of conflict, the deprecated hook has precedence.
378  $testModules = $testModulesMeta['qunit'] + $extRegistry->getAttribute( 'QUnitTestModules' );
379 
381  foreach ( $testModules as $name => &$module ) {
382  // Turn any single-module dependency into an array
383  if ( isset( $module['dependencies'] ) && is_string( $module['dependencies'] ) ) {
384  $module['dependencies'] = [ $module['dependencies'] ];
385  }
386 
387  // Ensure the testrunner loads before any test suites
388  $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
389 
390  // Keep track of the test suites to load on SpecialJavaScriptTest
391  $testSuiteModuleNames[] = $name;
392  }
393 
394  // Core test suites (their names have further precedence).
395  $testModules = ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules;
396  $testSuiteModuleNames[] = 'test.mediawiki.qunit.suites';
397 
398  $this->register( $testModules );
399  $this->testSuiteModuleNames = $testSuiteModuleNames;
400  }
401 
412  public function addSource( $id, $loadUrl = null ) {
413  // Allow multiple sources to be registered in one call
414  if ( is_array( $id ) ) {
415  foreach ( $id as $key => $value ) {
416  $this->addSource( $key, $value );
417  }
418  return;
419  }
420 
421  // Disallow duplicates
422  if ( isset( $this->sources[$id] ) ) {
423  throw new MWException(
424  'ResourceLoader duplicate source addition error. ' .
425  'Another source has already been registered as ' . $id
426  );
427  }
428 
429  // Pre 1.24 backwards-compatibility
430  if ( is_array( $loadUrl ) ) {
431  if ( !isset( $loadUrl['loadScript'] ) ) {
432  throw new MWException(
433  __METHOD__ . ' was passed an array with no "loadScript" key.'
434  );
435  }
436 
437  $loadUrl = $loadUrl['loadScript'];
438  }
439 
440  $this->sources[$id] = $loadUrl;
441  }
442 
448  public function getModuleNames() {
449  return array_keys( $this->moduleInfos );
450  }
451 
459  public function getTestSuiteModuleNames() {
461  }
462 
470  public function isModuleRegistered( $name ) {
471  return isset( $this->moduleInfos[$name] );
472  }
473 
485  public function getModule( $name ) {
486  if ( !isset( $this->modules[$name] ) ) {
487  if ( !isset( $this->moduleInfos[$name] ) ) {
488  // No such module
489  return null;
490  }
491  // Construct the requested module object
492  $info = $this->moduleInfos[$name];
493  if ( isset( $info['factory'] ) ) {
495  $object = call_user_func( $info['factory'], $info );
496  } else {
497  $class = $info['class'] ?? ResourceLoaderFileModule::class;
499  $object = new $class( $info );
500  }
501  $object->setConfig( $this->getConfig() );
502  $object->setLogger( $this->logger );
503  $object->setName( $name );
504  $this->modules[$name] = $object;
505  }
506 
507  return $this->modules[$name];
508  }
509 
516  protected function isFileModule( $name ) {
517  if ( !isset( $this->moduleInfos[$name] ) ) {
518  return false;
519  }
520  $info = $this->moduleInfos[$name];
521  return !isset( $info['factory'] ) && (
522  // The implied default for 'class' is ResourceLoaderFileModule
523  !isset( $info['class'] ) ||
524  // Explicit default
525  $info['class'] === ResourceLoaderFileModule::class ||
526  is_subclass_of( $info['class'], ResourceLoaderFileModule::class )
527  );
528  }
529 
535  public function getSources() {
536  return $this->sources;
537  }
538 
548  public function getLoadScript( $source ) {
549  if ( !isset( $this->sources[$source] ) ) {
550  throw new MWException( "The $source source was never registered in ResourceLoader." );
551  }
552  return $this->sources[$source];
553  }
554 
558  const HASH_LENGTH = 5;
559 
622  public static function makeHash( $value ) {
623  $hash = hash( 'fnv132', $value );
624  // The base_convert will pad it (if too short),
625  // then substr() will trim it (if too long).
626  return substr(
627  Wikimedia\base_convert( $hash, 16, 36, self::HASH_LENGTH ),
628  0,
629  self::HASH_LENGTH
630  );
631  }
632 
642  public function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
644  $this->logger->warning(
645  $msg,
646  $context + [ 'exception' => $e ]
647  );
648  $this->errors[] = self::formatExceptionNoComment( $e );
649  }
650 
659  public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
660  if ( !$moduleNames ) {
661  return '';
662  }
663  $hashes = array_map( function ( $module ) use ( $context ) {
664  try {
665  return $this->getModule( $module )->getVersionHash( $context );
666  } catch ( Exception $e ) {
667  // If modules fail to compute a version, don't fail the request (T152266)
668  // and still compute versions of other modules.
669  $this->outputErrorAndLog( $e,
670  'Calculating version for "{module}" failed: {exception}',
671  [
672  'module' => $module,
673  ]
674  );
675  return '';
676  }
677  }, $moduleNames );
678  return self::makeHash( implode( '', $hashes ) );
679  }
680 
695  public function makeVersionQuery( ResourceLoaderContext $context, array $modules = null ) {
696  if ( $modules === null ) {
697  wfDeprecated( __METHOD__ . ' without $modules', '1.34' );
698  $modules = $context->getModules();
699  }
700  // As of MediaWiki 1.28, the server and client use the same algorithm for combining
701  // version hashes. There is no technical reason for this to be same, and for years the
702  // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
703  // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
704  // query parameter), then this method must continue to match the JS one.
705  $filtered = [];
706  foreach ( $modules as $name ) {
707  if ( !$this->getModule( $name ) ) {
708  // If a versioned request contains a missing module, the version is a mismatch
709  // as the client considered a module (and version) we don't have.
710  return '';
711  }
712  $filtered[] = $name;
713  }
714  return $this->getCombinedVersion( $context, $filtered );
715  }
716 
723  // Buffer output to catch warnings. Normally we'd use ob_clean() on the
724  // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
725  // is used: ob_clean() will clear the GZIP header in that case and it won't come
726  // back for subsequent output, resulting in invalid GZIP. So we have to wrap
727  // the whole thing in our own output buffer to be sure the active buffer
728  // doesn't use ob_gzhandler.
729  // See https://bugs.php.net/bug.php?id=36514
730  ob_start();
731 
732  $this->measureResponseTime( RequestContext::getMain()->getTiming() );
733 
734  // Find out which modules are missing and instantiate the others
735  $modules = [];
736  $missing = [];
737  foreach ( $context->getModules() as $name ) {
738  $module = $this->getModule( $name );
739  if ( $module ) {
740  // Do not allow private modules to be loaded from the web.
741  // This is a security issue, see T36907.
742  if ( $module->getGroup() === 'private' ) {
743  // Not a serious error, just means something is trying to access it (T101806)
744  $this->logger->debug( "Request for private module '$name' denied" );
745  $this->errors[] = "Cannot build private module \"$name\"";
746  continue;
747  }
748  $modules[$name] = $module;
749  } else {
750  $missing[] = $name;
751  }
752  }
753 
754  try {
755  // Preload for getCombinedVersion() and for batch makeModuleResponse()
756  $this->preloadModuleInfo( array_keys( $modules ), $context );
757  } catch ( Exception $e ) {
758  $this->outputErrorAndLog( $e, 'Preloading module info failed: {exception}' );
759  }
760 
761  // Combine versions to propagate cache invalidation
762  $versionHash = '';
763  try {
764  $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
765  } catch ( Exception $e ) {
766  $this->outputErrorAndLog( $e, 'Calculating version hash failed: {exception}' );
767  }
768 
769  // See RFC 2616 § 3.11 Entity Tags
770  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
771  $etag = 'W/"' . $versionHash . '"';
772 
773  // Try the client-side cache first
774  if ( $this->tryRespondNotModified( $context, $etag ) ) {
775  return; // output handled (buffers cleared)
776  }
777 
778  // Use file cache if enabled and available...
779  if ( $this->config->get( 'UseFileCache' ) ) {
781  if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
782  return; // output handled
783  }
784  } else {
785  $fileCache = null;
786  }
787 
788  // Generate a response
789  $response = $this->makeModuleResponse( $context, $modules, $missing );
790 
791  // Capture any PHP warnings from the output buffer and append them to the
792  // error list if we're in debug mode.
793  if ( $context->getDebug() ) {
794  $warnings = ob_get_contents();
795  if ( strlen( $warnings ) ) {
796  $this->errors[] = $warnings;
797  }
798  }
799 
800  // Consider saving the response to file cache (unless there are errors).
801  if ( $fileCache &&
802  !$this->errors &&
803  $missing === [] &&
805  ) {
806  if ( $fileCache->isCacheWorthy() ) {
807  // There were enough hits, save the response to the cache
808  $fileCache->saveText( $response );
809  } else {
810  $fileCache->incrMissesRecent( $context->getRequest() );
811  }
812  }
813 
814  $this->sendResponseHeaders( $context, $etag, (bool)$this->errors, $this->extraHeaders );
815 
816  // Remove the output buffer and output the response
817  ob_end_clean();
818 
819  if ( $context->getImageObj() && $this->errors ) {
820  // We can't show both the error messages and the response when it's an image.
821  $response = implode( "\n\n", $this->errors );
822  } elseif ( $this->errors ) {
823  $errorText = implode( "\n\n", $this->errors );
824  $errorResponse = self::makeComment( $errorText );
825  if ( $context->shouldIncludeScripts() ) {
826  $errorResponse .= 'if (window.console && console.error) { console.error('
827  . $context->encodeJson( $errorText )
828  . "); }\n";
829  }
830 
831  // Prepend error info to the response
832  $response = $errorResponse . $response;
833  }
834 
835  $this->errors = [];
836  echo $response;
837  }
838 
839  protected function measureResponseTime( Timing $timing ) {
840  DeferredUpdates::addCallableUpdate( function () use ( $timing ) {
841  $measure = $timing->measure( 'responseTime', 'requestStart', 'requestShutdown' );
842  if ( $measure !== false ) {
843  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
844  $stats->timing( 'resourceloader.responseTime', $measure['duration'] * 1000 );
845  }
846  } );
847  }
848 
860  protected function sendResponseHeaders(
861  ResourceLoaderContext $context, $etag, $errors, array $extra = []
862  ) {
864  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
865  // Use a short cache expiry so that updates propagate to clients quickly, if:
866  // - No version specified (shared resources, e.g. stylesheets)
867  // - There were errors (recover quickly)
868  // - Version mismatch (T117587, T47877)
869  if ( is_null( $context->getVersion() )
870  || $errors
871  || $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
872  ) {
873  $maxage = $rlMaxage['unversioned']['client'];
874  $smaxage = $rlMaxage['unversioned']['server'];
875  // If a version was specified we can use a longer expiry time since changing
876  // version numbers causes cache misses
877  } else {
878  $maxage = $rlMaxage['versioned']['client'];
879  $smaxage = $rlMaxage['versioned']['server'];
880  }
881  if ( $context->getImageObj() ) {
882  // Output different headers if we're outputting textual errors.
883  if ( $errors ) {
884  header( 'Content-Type: text/plain; charset=utf-8' );
885  } else {
886  $context->getImageObj()->sendResponseHeaders( $context );
887  }
888  } elseif ( $context->getOnly() === 'styles' ) {
889  header( 'Content-Type: text/css; charset=utf-8' );
890  header( 'Access-Control-Allow-Origin: *' );
891  } else {
892  header( 'Content-Type: text/javascript; charset=utf-8' );
893  }
894  // See RFC 2616 § 14.19 ETag
895  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
896  header( 'ETag: ' . $etag );
897  if ( $context->getDebug() ) {
898  // Do not cache debug responses
899  header( 'Cache-Control: private, no-cache, must-revalidate' );
900  header( 'Pragma: no-cache' );
901  } else {
902  header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
903  $exp = min( $maxage, $smaxage );
904  header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
905  }
906  foreach ( $extra as $header ) {
907  header( $header );
908  }
909  }
910 
921  protected function tryRespondNotModified( ResourceLoaderContext $context, $etag ) {
922  // See RFC 2616 § 14.26 If-None-Match
923  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
924  $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
925  // Never send 304s in debug mode
926  if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
927  // There's another bug in ob_gzhandler (see also the comment at
928  // the top of this function) that causes it to gzip even empty
929  // responses, meaning it's impossible to produce a truly empty
930  // response (because the gzip header is always there). This is
931  // a problem because 304 responses have to be completely empty
932  // per the HTTP spec, and Firefox behaves buggily when they're not.
933  // See also https://bugs.php.net/bug.php?id=51579
934  // To work around this, we tear down all output buffering before
935  // sending the 304.
936  wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
937 
938  HttpStatus::header( 304 );
939 
940  $this->sendResponseHeaders( $context, $etag, false );
941  return true;
942  }
943  return false;
944  }
945 
954  protected function tryRespondFromFileCache(
955  ResourceFileCache $fileCache,
957  $etag
958  ) {
959  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
960  // Buffer output to catch warnings.
961  ob_start();
962  // Get the maximum age the cache can be
963  $maxage = is_null( $context->getVersion() )
964  ? $rlMaxage['unversioned']['server']
965  : $rlMaxage['versioned']['server'];
966  // Minimum timestamp the cache file must have
967  $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
968  if ( !$good ) {
969  try { // RL always hits the DB on file cache miss...
970  wfGetDB( DB_REPLICA );
971  } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
972  $good = $fileCache->isCacheGood(); // cache existence check
973  }
974  }
975  if ( $good ) {
976  $ts = $fileCache->cacheTimestamp();
977  // Send content type and cache headers
978  $this->sendResponseHeaders( $context, $etag, false );
979  $response = $fileCache->fetchText();
980  // Capture any PHP warnings from the output buffer and append them to the
981  // response in a comment if we're in debug mode.
982  if ( $context->getDebug() ) {
983  $warnings = ob_get_contents();
984  if ( strlen( $warnings ) ) {
985  $response = self::makeComment( $warnings ) . $response;
986  }
987  }
988  // Remove the output buffer and output the response
989  ob_end_clean();
990  echo $response . "\n/* Cached {$ts} */";
991  return true; // cache hit
992  }
993  // Clear buffer
994  ob_end_clean();
995 
996  return false; // cache miss
997  }
998 
1007  public static function makeComment( $text ) {
1008  $encText = str_replace( '*/', '* /', $text );
1009  return "/*\n$encText\n*/\n";
1010  }
1011 
1018  public static function formatException( $e ) {
1019  return self::makeComment( self::formatExceptionNoComment( $e ) );
1020  }
1021 
1029  protected static function formatExceptionNoComment( $e ) {
1030  global $wgShowExceptionDetails;
1031 
1032  if ( !$wgShowExceptionDetails ) {
1034  }
1035 
1036  return MWExceptionHandler::getLogMessage( $e ) .
1037  "\nBacktrace:\n" .
1039  }
1040 
1053  array $modules, array $missing = []
1054  ) {
1055  $out = '';
1056  $states = [];
1057 
1058  if ( $modules === [] && $missing === [] ) {
1059  return <<<MESSAGE
1060 /* This file is the Web entry point for MediaWiki's ResourceLoader:
1061  <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
1062  no modules were requested. Max made me put this here. */
1063 MESSAGE;
1064  }
1065 
1066  $image = $context->getImageObj();
1067  if ( $image ) {
1068  $data = $image->getImageData( $context );
1069  if ( $data === false ) {
1070  $data = '';
1071  $this->errors[] = 'Image generation failed';
1072  }
1073  return $data;
1074  }
1075 
1076  foreach ( $missing as $name ) {
1077  $states[$name] = 'missing';
1078  }
1079 
1080  $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
1081 
1082  foreach ( $modules as $name => $module ) {
1083  try {
1084  $content = $module->getModuleContent( $context );
1085  $implementKey = $name . '@' . $module->getVersionHash( $context );
1086  $strContent = '';
1087 
1088  if ( isset( $content['headers'] ) ) {
1089  $this->extraHeaders = array_merge( $this->extraHeaders, $content['headers'] );
1090  }
1091 
1092  // Append output
1093  switch ( $context->getOnly() ) {
1094  case 'scripts':
1095  $scripts = $content['scripts'];
1096  if ( is_string( $scripts ) ) {
1097  // Load scripts raw...
1098  $strContent = $scripts;
1099  } elseif ( is_array( $scripts ) ) {
1100  // ...except when $scripts is an array of URLs or an associative array
1101  $strContent = self::makeLoaderImplementScript(
1102  $context,
1103  $implementKey,
1104  $scripts,
1105  [],
1106  [],
1107  []
1108  );
1109  }
1110  break;
1111  case 'styles':
1112  $styles = $content['styles'];
1113  // We no longer separate into media, they are all combined now with
1114  // custom media type groups into @media .. {} sections as part of the css string.
1115  // Module returns either an empty array or a numerical array with css strings.
1116  $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
1117  break;
1118  default:
1119  $scripts = $content['scripts'] ?? '';
1120  if ( is_string( $scripts ) ) {
1121  if ( $name === 'site' || $name === 'user' ) {
1122  // Legacy scripts that run in the global scope without a closure.
1123  // mw.loader.implement will use globalEval if scripts is a string.
1124  // Minify manually here, because general response minification is
1125  // not effective due it being a string literal, not a function.
1126  if ( !$context->getDebug() ) {
1127  $scripts = self::filter( 'minify-js', $scripts ); // T107377
1128  }
1129  } else {
1130  $scripts = new XmlJsCode( $scripts );
1131  }
1132  }
1133  $strContent = self::makeLoaderImplementScript(
1134  $context,
1135  $implementKey,
1136  $scripts,
1137  $content['styles'] ?? [],
1138  isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
1139  $content['templates'] ?? []
1140  );
1141  break;
1142  }
1143 
1144  if ( !$context->getDebug() ) {
1145  $strContent = self::filter( $filter, $strContent );
1146  } else {
1147  // In debug mode, separate each response by a new line.
1148  // For example, between 'mw.loader.implement();' statements.
1149  $strContent = $this->ensureNewline( $strContent );
1150  }
1151 
1152  if ( $context->getOnly() === 'scripts' ) {
1153  // Use a linebreak between module scripts (T162719)
1154  $out .= $this->ensureNewline( $strContent );
1155  } else {
1156  $out .= $strContent;
1157  }
1158 
1159  } catch ( Exception $e ) {
1160  $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1161 
1162  // Respond to client with error-state instead of module implementation
1163  $states[$name] = 'error';
1164  unset( $modules[$name] );
1165  }
1166  }
1167 
1168  // Update module states
1169  if ( $context->shouldIncludeScripts() && !$context->getRaw() ) {
1170  if ( $modules && $context->getOnly() === 'scripts' ) {
1171  // Set the state of modules loaded as only scripts to ready as
1172  // they don't have an mw.loader.implement wrapper that sets the state
1173  foreach ( $modules as $name => $module ) {
1174  $states[$name] = 'ready';
1175  }
1176  }
1177 
1178  // Set the state of modules we didn't respond to with mw.loader.implement
1179  if ( $states ) {
1180  $stateScript = self::makeLoaderStateScript( $context, $states );
1181  if ( !$context->getDebug() ) {
1182  $stateScript = self::filter( 'minify-js', $stateScript );
1183  }
1184  // Use a linebreak between module script and state script (T162719)
1185  $out = $this->ensureNewline( $out ) . $stateScript;
1186  }
1187  } elseif ( $states ) {
1188  $this->errors[] = 'Problematic modules: '
1189  . $context->encodeJson( $states );
1190  }
1191 
1192  return $out;
1193  }
1194 
1200  private function ensureNewline( $str ) {
1201  $end = substr( $str, -1 );
1202  if ( $end === false || $end === '' || $end === "\n" ) {
1203  return $str;
1204  }
1205  return $str . "\n";
1206  }
1207 
1214  public function getModulesByMessage( $messageKey ) {
1215  $moduleNames = [];
1216  foreach ( $this->getModuleNames() as $moduleName ) {
1217  $module = $this->getModule( $moduleName );
1218  if ( in_array( $messageKey, $module->getMessages() ) ) {
1219  $moduleNames[] = $moduleName;
1220  }
1221  }
1222  return $moduleNames;
1223  }
1224 
1243  private static function makeLoaderImplementScript(
1244  ResourceLoaderContext $context, $name, $scripts, $styles, $messages, $templates
1245  ) {
1246  if ( $scripts instanceof XmlJsCode ) {
1247  if ( $scripts->value === '' ) {
1248  $scripts = null;
1249  } elseif ( $context->getDebug() ) {
1250  $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1251  } else {
1252  $scripts = new XmlJsCode( 'function($,jQuery,require,module){' . $scripts->value . '}' );
1253  }
1254  } elseif ( is_array( $scripts ) && isset( $scripts['files'] ) ) {
1255  $files = $scripts['files'];
1256  foreach ( $files as $path => &$file ) {
1257  // $file is changed (by reference) from a descriptor array to the content of the file
1258  // All of these essentially do $file = $file['content'];, some just have wrapping around it
1259  if ( $file['type'] === 'script' ) {
1260  // Multi-file modules only get two parameters ($ and jQuery are being phased out)
1261  if ( $context->getDebug() ) {
1262  $file = new XmlJsCode( "function ( require, module ) {\n{$file['content']}\n}" );
1263  } else {
1264  $file = new XmlJsCode( 'function(require,module){' . $file['content'] . '}' );
1265  }
1266  } else {
1267  $file = $file['content'];
1268  }
1269  }
1270  $scripts = XmlJsCode::encodeObject( [
1271  'main' => $scripts['main'],
1272  'files' => XmlJsCode::encodeObject( $files, $context->getDebug() )
1273  ], $context->getDebug() );
1274  } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1275  throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
1276  }
1277 
1278  // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
1279  // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1280  // of "{}". Force them to objects.
1281  $module = [
1282  $name,
1283  $scripts,
1284  (object)$styles,
1285  (object)$messages,
1286  (object)$templates
1287  ];
1288  self::trimArray( $module );
1289 
1290  return Xml::encodeJsCall( 'mw.loader.implement', $module, $context->getDebug() );
1291  }
1292 
1299  public static function makeMessageSetScript( $messages ) {
1300  return 'mw.messages.set('
1301  . self::encodeJsonForScript( (object)$messages )
1302  . ');';
1303  }
1304 
1312  public static function makeCombinedStyles( array $stylePairs ) {
1313  $out = [];
1314  foreach ( $stylePairs as $media => $styles ) {
1315  // ResourceLoaderFileModule::getStyle can return the styles
1316  // as a string or an array of strings. This is to allow separation in
1317  // the front-end.
1318  $styles = (array)$styles;
1319  foreach ( $styles as $style ) {
1320  $style = trim( $style );
1321  // Don't output an empty "@media print { }" block (T42498)
1322  if ( $style !== '' ) {
1323  // Transform the media type based on request params and config
1324  // The way that this relies on $wgRequest to propagate request params is slightly evil
1325  $media = OutputPage::transformCssMedia( $media );
1326 
1327  if ( $media === '' || $media == 'all' ) {
1328  $out[] = $style;
1329  } elseif ( is_string( $media ) ) {
1330  $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1331  }
1332  // else: skip
1333  }
1334  }
1335  }
1336  return $out;
1337  }
1338 
1348  public static function encodeJsonForScript( $data ) {
1349  // Keep output as small as possible by disabling needless escape modes
1350  // that PHP uses by default.
1351  // However, while most module scripts are only served on HTTP responses
1352  // for JavaScript, some modules can also be embedded in the HTML as inline
1353  // scripts. This, and the fact that we sometimes need to export strings
1354  // containing user-generated content and labels that may genuinely contain
1355  // a sequences like "</script>", we need to encode either '/' or '<'.
1356  // By default PHP escapes '/'. Let's escape '<' instead which is less common
1357  // and allows URLs to mostly remain readable.
1358  $jsonFlags = JSON_UNESCAPED_SLASHES |
1359  JSON_UNESCAPED_UNICODE |
1360  JSON_HEX_TAG |
1361  JSON_HEX_AMP;
1362  if ( self::inDebugMode() ) {
1363  $jsonFlags |= JSON_PRETTY_PRINT;
1364  }
1365  return json_encode( $data, $jsonFlags );
1366  }
1367 
1380  public static function makeLoaderStateScript(
1381  ResourceLoaderContext $context, array $states
1382  ) {
1383  return 'mw.loader.state('
1384  . $context->encodeJson( $states )
1385  . ');';
1386  }
1387 
1388  private static function isEmptyObject( stdClass $obj ) {
1389  foreach ( $obj as $key => $value ) {
1390  return false;
1391  }
1392  return true;
1393  }
1394 
1407  private static function trimArray( array &$array ) {
1408  $i = count( $array );
1409  while ( $i-- ) {
1410  if ( $array[$i] === null
1411  || $array[$i] === []
1412  || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
1413  || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1414  ) {
1415  unset( $array[$i] );
1416  } else {
1417  break;
1418  }
1419  }
1420  }
1421 
1447  public static function makeLoaderRegisterScript(
1449  ) {
1450  // Optimisation: Transform dependency names into indexes when possible
1451  // to produce smaller output. They are expanded by mw.loader.register on
1452  // the other end using resolveIndexedDependencies().
1453  $index = [];
1454  foreach ( $modules as $i => &$module ) {
1455  // Build module name index
1456  $index[$module[0]] = $i;
1457  }
1458  foreach ( $modules as &$module ) {
1459  if ( isset( $module[2] ) ) {
1460  foreach ( $module[2] as &$dependency ) {
1461  if ( isset( $index[$dependency] ) ) {
1462  // Replace module name in dependency list with index
1463  $dependency = $index[$dependency];
1464  }
1465  }
1466  }
1467  }
1468 
1469  array_walk( $modules, [ self::class, 'trimArray' ] );
1470 
1471  return 'mw.loader.register('
1472  . $context->encodeJson( $modules )
1473  . ');';
1474  }
1475 
1490  public static function makeLoaderSourcesScript(
1492  ) {
1493  return 'mw.loader.addSource('
1494  . $context->encodeJson( $sources )
1495  . ');';
1496  }
1497 
1504  public static function makeLoaderConditionalScript( $script ) {
1505  // Adds a function to lazy-created RLQ
1506  return '(RLQ=window.RLQ||[]).push(function(){' .
1507  trim( $script ) . '});';
1508  }
1509 
1518  public static function makeInlineCodeWithModule( $modules, $script ) {
1519  // Adds an array to lazy-created RLQ
1520  return '(RLQ=window.RLQ||[]).push(['
1522  . 'function(){' . trim( $script ) . '}'
1523  . ']);';
1524  }
1525 
1537  public static function makeInlineScript( $script, $nonce = null ) {
1538  $js = self::makeLoaderConditionalScript( $script );
1539  $escNonce = '';
1540  if ( $nonce === null ) {
1541  wfWarn( __METHOD__ . " did not get nonce. Will break CSP" );
1542  } elseif ( $nonce !== false ) {
1543  // If it was false, CSP is disabled, so no nonce attribute.
1544  // Nonce should be only base64 characters, so should be safe,
1545  // but better to be safely escaped than sorry.
1546  $escNonce = ' nonce="' . htmlspecialchars( $nonce ) . '"';
1547  }
1548 
1549  return new WrappedString(
1550  Html::inlineScript( $js, $nonce ),
1551  "<script$escNonce>(RLQ=window.RLQ||[]).push(function(){",
1552  '});</script>'
1553  );
1554  }
1555 
1564  public static function makeConfigSetScript( array $configuration ) {
1565  $json = self::encodeJsonForScript( $configuration );
1566  if ( $json === false ) {
1567  $e = new Exception(
1568  'JSON serialization of config data failed. ' .
1569  'This usually means the config data is not valid UTF-8.'
1570  );
1572  return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) . ');';
1573  }
1574  return "mw.config.set($json);";
1575  }
1576 
1590  public static function makePackedModulesString( $modules ) {
1591  $moduleMap = []; // [ prefix => [ suffixes ] ]
1592  foreach ( $modules as $module ) {
1593  $pos = strrpos( $module, '.' );
1594  $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1595  $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1596  $moduleMap[$prefix][] = $suffix;
1597  }
1598 
1599  $arr = [];
1600  foreach ( $moduleMap as $prefix => $suffixes ) {
1601  $p = $prefix === '' ? '' : $prefix . '.';
1602  $arr[] = $p . implode( ',', $suffixes );
1603  }
1604  return implode( '|', $arr );
1605  }
1606 
1618  public static function expandModuleNames( $modules ) {
1619  $retval = [];
1620  $exploded = explode( '|', $modules );
1621  foreach ( $exploded as $group ) {
1622  if ( strpos( $group, ',' ) === false ) {
1623  // This is not a set of modules in foo.bar,baz notation
1624  // but a single module
1625  $retval[] = $group;
1626  } else {
1627  // This is a set of modules in foo.bar,baz notation
1628  $pos = strrpos( $group, '.' );
1629  if ( $pos === false ) {
1630  // Prefixless modules, i.e. without dots
1631  $retval = array_merge( $retval, explode( ',', $group ) );
1632  } else {
1633  // We have a prefix and a bunch of suffixes
1634  $prefix = substr( $group, 0, $pos ); // 'foo'
1635  $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // [ 'bar', 'baz' ]
1636  foreach ( $suffixes as $suffix ) {
1637  $retval[] = "$prefix.$suffix";
1638  }
1639  }
1640  }
1641  }
1642  return $retval;
1643  }
1644 
1650  public static function inDebugMode() {
1651  if ( self::$debugMode === null ) {
1653  self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
1654  $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
1655  );
1656  }
1657  return self::$debugMode;
1658  }
1659 
1670  public static function clearCache() {
1671  self::$debugMode = null;
1672  }
1673 
1684  $extraQuery = []
1685  ) {
1686  $query = self::createLoaderQuery( $context, $extraQuery );
1687  $script = $this->getLoadScript( $source );
1688 
1689  return wfAppendQuery( $script, $query );
1690  }
1691 
1701  protected static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = [] ) {
1702  return self::makeLoaderQuery(
1703  $context->getModules(),
1704  $context->getLanguage(),
1705  $context->getSkin(),
1706  $context->getUser(),
1707  $context->getVersion(),
1708  $context->getDebug(),
1709  $context->getOnly(),
1710  $context->getRequest()->getBool( 'printable' ),
1711  $context->getRequest()->getBool( 'handheld' ),
1712  $extraQuery
1713  );
1714  }
1715 
1732  public static function makeLoaderQuery( $modules, $lang, $skin, $user = null,
1733  $version = null, $debug = false, $only = null, $printable = false,
1734  $handheld = false, $extraQuery = []
1735  ) {
1736  $query = [
1737  'modules' => self::makePackedModulesString( $modules ),
1738  ];
1739  // Keep urls short by omitting query parameters that
1740  // match the defaults assumed by ResourceLoaderContext.
1741  // Note: This relies on the defaults either being insignificant or forever constant,
1742  // as otherwise cached urls could change in meaning when the defaults change.
1744  $query['lang'] = $lang;
1745  }
1746  if ( $skin !== ResourceLoaderContext::DEFAULT_SKIN ) {
1747  $query['skin'] = $skin;
1748  }
1749  if ( $debug === true ) {
1750  $query['debug'] = 'true';
1751  }
1752  if ( $user !== null ) {
1753  $query['user'] = $user;
1754  }
1755  if ( $version !== null ) {
1756  $query['version'] = $version;
1757  }
1758  if ( $only !== null ) {
1759  $query['only'] = $only;
1760  }
1761  if ( $printable ) {
1762  $query['printable'] = 1;
1763  }
1764  if ( $handheld ) {
1765  $query['handheld'] = 1;
1766  }
1767  $query += $extraQuery;
1768 
1769  // Make queries uniform in order
1770  ksort( $query );
1771  return $query;
1772  }
1773 
1783  public static function isValidModuleName( $moduleName ) {
1784  return strcspn( $moduleName, '!,|', 0, 255 ) === strlen( $moduleName );
1785  }
1786 
1797  public function getLessCompiler( $vars = [] ) {
1798  global $IP;
1799  // When called from the installer, it is possible that a required PHP extension
1800  // is missing (at least for now; see T49564). If this is the case, throw an
1801  // exception (caught by the installer) to prevent a fatal error later on.
1802  if ( !class_exists( 'Less_Parser' ) ) {
1803  throw new MWException( 'MediaWiki requires the less.php parser' );
1804  }
1805 
1806  $parser = new Less_Parser;
1807  $parser->ModifyVars( $vars );
1808  $parser->SetImportDirs( [
1809  "$IP/resources/src/mediawiki.less/" => '',
1810  ] );
1811  $parser->SetOption( 'relativeUrls', false );
1812 
1813  return $parser;
1814  }
1815 
1823  public function getLessVars() {
1824  wfDeprecated( __METHOD__, '1.32' );
1825  return [];
1826  }
1827 }
$filter
$filter
Definition: profileinfo.php:344
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:166
ResourceLoader\makeLoaderConditionalScript
static makeLoaderConditionalScript( $script)
Wraps JavaScript code to run after the startup module.
Definition: ResourceLoader.php:1502
ResourceLoaderContext
Context object that contains information about the state of a specific ResourceLoader web request.
Definition: ResourceLoaderContext.php:33
ResourceLoader\getMessageBlobStore
getMessageBlobStore()
Definition: ResourceLoader.php:266
wfResetOutputBuffers
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
Definition: GlobalFunctions.php:1676
ResourceLoader\sendResponseHeaders
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors, array $extra=[])
Send main response headers to the client.
Definition: ResourceLoader.php:860
ResourceLoader\$errors
array $errors
Errors accumulated during current respond() call.
Definition: ResourceLoader.php:69
ResourceLoaderModule\setMessageBlob
setMessageBlob( $blob, $lang)
Set in-object cache for message blobs.
Definition: ResourceLoaderModule.php:585
CSSMin\minify
static minify( $css)
Removes whitespace from CSS data.
Definition: CSSMin.php:540
ResourceLoader\makeConfigSetScript
static makeConfigSetScript(array $configuration)
Returns JS code which will set the MediaWiki configuration array to the given value.
Definition: ResourceLoader.php:1562
ResourceFileCache\newFromContext
static newFromContext(ResourceLoaderContext $context)
Construct an ResourceFileCache from a context.
Definition: ResourceFileCache.php:40
FileCacheBase\isCacheGood
isCacheGood( $timestamp='')
Check if up to date cache file exists.
Definition: FileCacheBase.php:117
$response
$response
Definition: opensearch_desc.php:38
ResourceLoader\encodeJsonForScript
static encodeJsonForScript( $data)
Wrapper around json_encode that avoids needless escapes, and pretty-prints in debug mode.
Definition: ResourceLoader.php:1346
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
ResourceLoader\formatException
static formatException( $e)
Handle exception display.
Definition: ResourceLoader.php:1018
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
ResourceLoader\makeVersionQuery
makeVersionQuery(ResourceLoaderContext $context, array $modules=null)
Get the expected value of the 'version' query parameter.
Definition: ResourceLoader.php:695
ResourceLoader\makeInlineScript
static makeInlineScript( $script, $nonce=null)
Returns an HTML script tag that runs given JS code after startup and base modules.
Definition: ResourceLoader.php:1535
ResourceLoader\makePackedModulesString
static makePackedModulesString( $modules)
Convert an array of module names to a packed query string.
Definition: ResourceLoader.php:1588
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1849
ResourceLoader\getLogger
getLogger()
Definition: ResourceLoader.php:258
ResourceLoader\isFileModule
isFileModule( $name)
Whether the module is a ResourceLoaderFileModule (including subclasses).
Definition: ResourceLoader.php:516
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:459
ResourceLoader\$testSuiteModuleNames
string[] $testSuiteModuleNames
List of module names that contain QUnit test suites.
Definition: ResourceLoader.php:64
$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)
Returns JS code which calls mw.loader.register with the given parameter.
Definition: ResourceLoader.php:1445
ResourceLoaderModule\getVary
static getVary(ResourceLoaderContext $context)
Get vary string.
Definition: ResourceLoaderModule.php:1022
$res
$res
Definition: testCompression.php:52
ResourceLoader\isEmptyObject
static isEmptyObject(stdClass $obj)
Definition: ResourceLoader.php:1386
ResourceLoader\$extraHeaders
string[] $extraHeaders
Extra HTTP response headers from modules loaded in makeModuleResponse()
Definition: ResourceLoader.php:71
ResourceLoader\preloadModuleInfo
preloadModuleInfo(array $moduleNames, ResourceLoaderContext $context)
Load information stored in the database about modules.
Definition: ResourceLoader.php:96
ResourceLoader\getLessCompiler
getLessCompiler( $vars=[])
Returns LESS compiler set up for use with MediaWiki.
Definition: ResourceLoader.php:1795
OutputPage\transformCssMedia
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
Definition: OutputPage.php:3903
$dbr
$dbr
Definition: testCompression.php:50
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:439
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:106
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:677
ResourceLoader\setMessageBlobStore
setMessageBlobStore(MessageBlobStore $blobStore)
Definition: ResourceLoader.php:274
Config
Interface for configuration instances.
Definition: Config.php:28
MWExceptionHandler\getPublicLogMessage
static getPublicLogMessage( $e)
Definition: MWExceptionHandler.php:543
ResourceLoader\clearCache
static clearCache()
Reset static members used for caching.
Definition: ResourceLoader.php:1668
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:1198
ResourceLoader\$moduleInfos
array[] $moduleInfos
Map of (module name => associative info array)
Definition: ResourceLoader.php:56
ResourceLoader\makeModuleResponse
makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing=[])
Generate code for a response.
Definition: ResourceLoader.php:1052
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1044
ResourceLoaderWikiModule\preloadTitleInfo
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Definition: ResourceLoaderWikiModule.php:450
Config\get
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
$blob
$blob
Definition: testCompression.php:65
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:2555
ResourceLoader\makeHash
static makeHash( $value)
Create a hash for module versioning purposes.
Definition: ResourceLoader.php:622
MWExceptionHandler\getLogMessage
static getLogMessage( $e)
Get a message formatting the exception message and its origin.
Definition: MWExceptionHandler.php:510
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:535
$IP
$IP
Definition: update.php:3
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:1378
ResourceLoader\makeInlineCodeWithModule
static makeInlineCodeWithModule( $modules, $script)
Wraps JavaScript code to run after a required module.
Definition: ResourceLoader.php:1516
ResourceLoaderFileModule\extractBasePaths
static extractBasePaths( $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
Definition: ResourceLoaderFileModule.php:342
ResourceLoader\makeComment
static makeComment( $text)
Generate a CSS or JS comment block.
Definition: ResourceLoader.php:1007
ResourceLoaderModule\expandRelativePaths
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
Definition: ResourceLoaderModule.php:545
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ResourceLoader\$logger
LoggerInterface $logger
Definition: ResourceLoader.php:51
FileCacheBase\fetchText
fetchText()
Get the uncompressed text from the cache.
Definition: FileCacheBase.php:144
ResourceLoader\getModulesByMessage
getModulesByMessage( $messageKey)
Get names of modules that use a certain message.
Definition: ResourceLoader.php:1212
ResourceLoader\$modules
ResourceLoaderModule[] $modules
Map of (module name => ResourceLoaderModule)
Definition: ResourceLoader.php:54
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:485
ResourceLoader\$debugMode
static bool $debugMode
Definition: ResourceLoader.php:74
$content
$content
Definition: router.php:78
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:548
ResourceLoader\createLoaderURL
createLoaderURL( $source, ResourceLoaderContext $context, $extraQuery=[])
Build a load.php URL.
Definition: ResourceLoader.php:1681
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:1310
ResourceLoader\__construct
__construct(Config $config=null, LoggerInterface $logger=null)
Register core modules and runs registration hooks.
Definition: ResourceLoader.php:221
ResourceLoaderContext\DEFAULT_LANG
const DEFAULT_LANG
Definition: ResourceLoaderContext.php:34
ResourceLoader\applyFilter
static applyFilter( $filter, $data)
Definition: ResourceLoader.php:201
ResourceLoader\HASH_LENGTH
const HASH_LENGTH
Definition: ResourceLoader.php:558
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:62
ResourceLoader\isModuleRegistered
isModuleRegistered( $name)
Check whether a ResourceLoader module is registered.
Definition: ResourceLoader.php:470
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:1616
ResourceLoader\$blobStore
MessageBlobStore $blobStore
Definition: ResourceLoader.php:48
ResourceLoader\getCombinedVersion
getCombinedVersion(ResourceLoaderContext $context, array $moduleNames)
Helper method to get and combine versions of multiple modules.
Definition: ResourceLoader.php:659
ResourceLoader\respond
respond(ResourceLoaderContext $context)
Output a response to a load request, including the content-type header.
Definition: ResourceLoader.php:722
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:431
ResourceLoader\$config
Config $config
Definition: ResourceLoader.php:46
ResourceLoader\trimArray
static trimArray(array &$array)
Remove empty values from the end of an array.
Definition: ResourceLoader.php:1405
$context
$context
Definition: load.php:45
ResourceLoaderModule
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition: ResourceLoaderModule.php:37
$debug
$debug
Definition: Setup.php:783
ResourceLoader
ResourceLoader is a loading system for JavaScript and CSS resources.
Definition: ResourceLoader.php:44
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:1241
$cache
$cache
Definition: mcc.php:33
HttpStatus\header
static header( $code)
Output an HTTP status code header.
Definition: HttpStatus.php:96
WebRequest\GETHEADER_LIST
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
Definition: WebRequest.php:71
ResourceLoader\inDebugMode
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie,...
Definition: ResourceLoader.php:1648
ResourceLoader\tryRespondFromFileCache
tryRespondFromFileCache(ResourceFileCache $fileCache, ResourceLoaderContext $context, $etag)
Send out code for a response from file cache if possible.
Definition: ResourceLoader.php:954
$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:6364
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:1297
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
ResourceLoader\addSource
addSource( $id, $loadUrl=null)
Add a foreign source of modules.
Definition: ResourceLoader.php:412
$source
$source
Definition: mwdoc-filter.php:34
ResourceLoader\registerTestModules
registerTestModules()
Definition: ResourceLoader.php:360
ResourceLoader\makeLoaderSourcesScript
static makeLoaderSourcesScript(ResourceLoaderContext $context, array $sources)
Returns JS code which calls mw.loader.addSource() with the given parameters.
Definition: ResourceLoader.php:1488
ResourceLoader\tryRespondNotModified
tryRespondNotModified(ResourceLoaderContext $context, $etag)
Respond with HTTP 304 Not Modified if appropiate.
Definition: ResourceLoader.php:921
$hashes
$hashes
Definition: testCompression.php:66
ResourceLoader\$sources
array $sources
Map of (source => path); E.g.
Definition: ResourceLoader.php:67
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:1065
ResourceLoader\getLessVars
getLessVars()
Get global LESS variables.
Definition: ResourceLoader.php:1821
ResourceLoader\formatExceptionNoComment
static formatExceptionNoComment( $e)
Handle exception display.
Definition: ResourceLoader.php:1029
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:751
ResourceLoader\makeLoaderQuery
static makeLoaderQuery( $modules, $lang, $skin, $user=null, $version=null, $debug=false, $only=null, $printable=false, $handheld=false, $extraQuery=[])
Build a query array (array representation of query string) for load.php.
Definition: ResourceLoader.php:1730
ResourceLoader\getConfig
getConfig()
Definition: ResourceLoader.php:242
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:124
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
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:3743
MWExceptionHandler\getRedactedTraceAsString
static getRedactedTraceAsString( $e)
Generate a string representation of an exception's stack trace.
Definition: MWExceptionHandler.php:404
ResourceLoader\outputErrorAndLog
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
Definition: ResourceLoader.php:642
ResourceLoader\isValidModuleName
static isValidModuleName( $moduleName)
Check a module name for validity.
Definition: ResourceLoader.php:1781
ResourceLoader\getModuleNames
getModuleNames()
Get a list of module names.
Definition: ResourceLoader.php:448
ResourceLoader\setLogger
setLogger(LoggerInterface $logger)
Definition: ResourceLoader.php:250
FileCacheBase\cacheTimestamp
cacheTimestamp()
Get the last-modified timestamp of the cache file.
Definition: FileCacheBase.php:103
ResourceLoader\measureResponseTime
measureResponseTime(Timing $timing)
Definition: ResourceLoader.php:839
ResourceLoader\createLoaderQuery
static createLoaderQuery(ResourceLoaderContext $context, $extraQuery=[])
Helper for createLoaderURL()
Definition: ResourceLoader.php:1699
MediaWiki\HeaderCallback\warnIfHeadersSent
static warnIfHeadersSent()
Log a warning message if headers have already been sent.
Definition: HeaderCallback.php:70
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:268
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER, $extraData=[])
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:691