MediaWiki  REL1_31
ResourceLoader.php
Go to the documentation of this file.
1 <?php
26 use Psr\Log\LoggerAwareInterface;
27 use Psr\Log\LoggerInterface;
28 use Psr\Log\NullLogger;
30 use Wikimedia\WrappedString;
31 
38 class ResourceLoader implements LoggerAwareInterface {
40  protected static $filterCacheVersion = 7;
41 
43  protected static $debugMode = null;
44 
46  private $lessVars = null;
47 
52  protected $modules = [];
53 
58  protected $moduleInfos = [];
59 
61  protected $config;
62 
68  protected $testModuleNames = [];
69 
74  protected $sources = [];
75 
80  protected $errors = [];
81 
89  protected $extraHeaders = [];
90 
94  protected $blobStore;
95 
99  private $logger;
100 
102  const FILTER_NOMIN = '/*@nomin*/';
103 
118  public function preloadModuleInfo( array $moduleNames, ResourceLoaderContext $context ) {
119  if ( !$moduleNames ) {
120  // Or else Database*::select() will explode, plus it's cheaper!
121  return;
122  }
123  $dbr = wfGetDB( DB_REPLICA );
124  $skin = $context->getSkin();
125  $lang = $context->getLanguage();
126 
127  // Batched version of ResourceLoaderModule::getFileDependencies
128  $vary = "$skin|$lang";
129  $res = $dbr->select( 'module_deps', [ 'md_module', 'md_deps' ], [
130  'md_module' => $moduleNames,
131  'md_skin' => $vary,
132  ], __METHOD__
133  );
134 
135  // Prime in-object cache for file dependencies
136  $modulesWithDeps = [];
137  foreach ( $res as $row ) {
138  $module = $this->getModule( $row->md_module );
139  if ( $module ) {
140  $module->setFileDependencies( $context, ResourceLoaderModule::expandRelativePaths(
141  FormatJson::decode( $row->md_deps, true )
142  ) );
143  $modulesWithDeps[] = $row->md_module;
144  }
145  }
146  // Register the absence of a dependency row too
147  foreach ( array_diff( $moduleNames, $modulesWithDeps ) as $name ) {
148  $module = $this->getModule( $name );
149  if ( $module ) {
150  $this->getModule( $name )->setFileDependencies( $context, [] );
151  }
152  }
153 
154  // Batched version of ResourceLoaderWikiModule::getTitleInfo
156 
157  // Prime in-object cache for message blobs for modules with messages
158  $modules = [];
159  foreach ( $moduleNames as $name ) {
160  $module = $this->getModule( $name );
161  if ( $module && $module->getMessages() ) {
162  $modules[$name] = $module;
163  }
164  }
165  $store = $this->getMessageBlobStore();
166  $blobs = $store->getBlobs( $modules, $lang );
167  foreach ( $blobs as $name => $blob ) {
168  $modules[$name]->setMessageBlob( $blob, $lang );
169  }
170  }
171 
189  public static function filter( $filter, $data, array $options = [] ) {
190  if ( strpos( $data, self::FILTER_NOMIN ) !== false ) {
191  return $data;
192  }
193 
194  if ( isset( $options['cache'] ) && $options['cache'] === false ) {
195  return self::applyFilter( $filter, $data );
196  }
197 
198  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
200 
201  $key = $cache->makeGlobalKey(
202  'resourceloader',
203  'filter',
204  $filter,
205  self::$filterCacheVersion, md5( $data )
206  );
207 
208  $result = $cache->get( $key );
209  if ( $result === false ) {
210  $stats->increment( "resourceloader_cache.$filter.miss" );
211  $result = self::applyFilter( $filter, $data );
212  $cache->set( $key, $result, 24 * 3600 );
213  } else {
214  $stats->increment( "resourceloader_cache.$filter.hit" );
215  }
216  if ( $result === null ) {
217  // Cached failure
218  $result = $data;
219  }
220 
221  return $result;
222  }
223 
224  private static function applyFilter( $filter, $data ) {
225  $data = trim( $data );
226  if ( $data ) {
227  try {
228  $data = ( $filter === 'minify-css' )
229  ? CSSMin::minify( $data )
230  : JavaScriptMinifier::minify( $data );
231  } catch ( Exception $e ) {
233  return null;
234  }
235  }
236  return $data;
237  }
238 
244  public function __construct( Config $config = null, LoggerInterface $logger = null ) {
245  global $IP;
246 
247  $this->logger = $logger ?: new NullLogger();
248 
249  if ( !$config ) {
250  $this->logger->debug( __METHOD__ . ' was called without providing a Config instance' );
251  $config = MediaWikiServices::getInstance()->getMainConfig();
252  }
253  $this->config = $config;
254 
255  // Add 'local' source first
256  $this->addSource( 'local', $config->get( 'LoadScript' ) );
257 
258  // Add other sources
259  $this->addSource( $config->get( 'ResourceLoaderSources' ) );
260 
261  // Register core modules
262  $this->register( include "$IP/resources/Resources.php" );
263  // Register extension modules
264  $this->register( $config->get( 'ResourceModules' ) );
265 
266  // Avoid PHP 7.1 warning from passing $this by reference
267  $rl = $this;
268  Hooks::run( 'ResourceLoaderRegisterModules', [ &$rl ] );
269 
270  if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
271  $this->registerTestModules();
272  }
273 
274  $this->setMessageBlobStore( new MessageBlobStore( $this, $this->logger ) );
275  }
276 
280  public function getConfig() {
281  return $this->config;
282  }
283 
288  public function setLogger( LoggerInterface $logger ) {
289  $this->logger = $logger;
290  }
291 
296  public function getLogger() {
297  return $this->logger;
298  }
299 
304  public function getMessageBlobStore() {
305  return $this->blobStore;
306  }
307 
313  $this->blobStore = $blobStore;
314  }
315 
329  public function register( $name, $info = null ) {
330  $moduleSkinStyles = $this->config->get( 'ResourceModuleSkinStyles' );
331 
332  // Allow multiple modules to be registered in one call
333  $registrations = is_array( $name ) ? $name : [ $name => $info ];
334  foreach ( $registrations as $name => $info ) {
335  // Warn on duplicate registrations
336  if ( isset( $this->moduleInfos[$name] ) ) {
337  // A module has already been registered by this name
338  $this->logger->warning(
339  'ResourceLoader duplicate registration warning. ' .
340  'Another module has already been registered as ' . $name
341  );
342  }
343 
344  // Check $name for validity
345  if ( !self::isValidModuleName( $name ) ) {
346  throw new MWException( "ResourceLoader module name '$name' is invalid, "
347  . "see ResourceLoader::isValidModuleName()" );
348  }
349 
350  // Attach module
351  if ( $info instanceof ResourceLoaderModule ) {
352  $this->moduleInfos[$name] = [ 'object' => $info ];
353  $info->setName( $name );
354  $this->modules[$name] = $info;
355  } elseif ( is_array( $info ) ) {
356  // New calling convention
357  $this->moduleInfos[$name] = $info;
358  } else {
359  throw new MWException(
360  'ResourceLoader module info type error for module \'' . $name .
361  '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
362  );
363  }
364 
365  // Last-minute changes
366 
367  // Apply custom skin-defined styles to existing modules.
368  if ( $this->isFileModule( $name ) ) {
369  foreach ( $moduleSkinStyles as $skinName => $skinStyles ) {
370  // If this module already defines skinStyles for this skin, ignore $wgResourceModuleSkinStyles.
371  if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
372  continue;
373  }
374 
375  // If $name is preceded with a '+', the defined style files will be added to 'default'
376  // skinStyles, otherwise 'default' will be ignored as it normally would be.
377  if ( isset( $skinStyles[$name] ) ) {
378  $paths = (array)$skinStyles[$name];
379  $styleFiles = [];
380  } elseif ( isset( $skinStyles['+' . $name] ) ) {
381  $paths = (array)$skinStyles['+' . $name];
382  $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
383  (array)$this->moduleInfos[$name]['skinStyles']['default'] :
384  [];
385  } else {
386  continue;
387  }
388 
389  // Add new file paths, remapping them to refer to our directories and not use settings
390  // from the module we're modifying, which come from the base definition.
391  list( $localBasePath, $remoteBasePath ) =
393 
394  foreach ( $paths as $path ) {
395  $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
396  }
397 
398  $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
399  }
400  }
401  }
402  }
403 
404  public function registerTestModules() {
405  global $IP;
406 
407  if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
408  throw new MWException( 'Attempt to register JavaScript test modules '
409  . 'but <code>$wgEnableJavaScriptTest</code> is false. '
410  . 'Edit your <code>LocalSettings.php</code> to enable it.' );
411  }
412 
413  // Get core test suites
414  $testModules = [];
415  $testModules['qunit'] = [];
416  // Get other test suites (e.g. from extensions)
417  // Avoid PHP 7.1 warning from passing $this by reference
418  $rl = $this;
419  Hooks::run( 'ResourceLoaderTestModules', [ &$testModules, &$rl ] );
420 
421  // Add the testrunner (which configures QUnit) to the dependencies.
422  // Since it must be ready before any of the test suites are executed.
423  foreach ( $testModules['qunit'] as &$module ) {
424  // Make sure all test modules are top-loading so that when QUnit starts
425  // on document-ready, it will run once and finish. If some tests arrive
426  // later (possibly after QUnit has already finished) they will be ignored.
427  $module['position'] = 'top';
428  $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
429  }
430 
431  $testModules['qunit'] =
432  ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules['qunit'];
433 
434  foreach ( $testModules as $id => $names ) {
435  // Register test modules
436  $this->register( $testModules[$id] );
437 
438  // Keep track of their names so that they can be loaded together
439  $this->testModuleNames[$id] = array_keys( $testModules[$id] );
440  }
441  }
442 
453  public function addSource( $id, $loadUrl = null ) {
454  // Allow multiple sources to be registered in one call
455  if ( is_array( $id ) ) {
456  foreach ( $id as $key => $value ) {
457  $this->addSource( $key, $value );
458  }
459  return;
460  }
461 
462  // Disallow duplicates
463  if ( isset( $this->sources[$id] ) ) {
464  throw new MWException(
465  'ResourceLoader duplicate source addition error. ' .
466  'Another source has already been registered as ' . $id
467  );
468  }
469 
470  // Pre 1.24 backwards-compatibility
471  if ( is_array( $loadUrl ) ) {
472  if ( !isset( $loadUrl['loadScript'] ) ) {
473  throw new MWException(
474  __METHOD__ . ' was passed an array with no "loadScript" key.'
475  );
476  }
477 
478  $loadUrl = $loadUrl['loadScript'];
479  }
480 
481  $this->sources[$id] = $loadUrl;
482  }
483 
489  public function getModuleNames() {
490  return array_keys( $this->moduleInfos );
491  }
492 
503  public function getTestModuleNames( $framework = 'all' ) {
505  if ( $framework == 'all' ) {
506  return $this->testModuleNames;
507  } elseif ( isset( $this->testModuleNames[$framework] )
508  && is_array( $this->testModuleNames[$framework] )
509  ) {
510  return $this->testModuleNames[$framework];
511  } else {
512  return [];
513  }
514  }
515 
523  public function isModuleRegistered( $name ) {
524  return isset( $this->moduleInfos[$name] );
525  }
526 
538  public function getModule( $name ) {
539  if ( !isset( $this->modules[$name] ) ) {
540  if ( !isset( $this->moduleInfos[$name] ) ) {
541  // No such module
542  return null;
543  }
544  // Construct the requested object
545  $info = $this->moduleInfos[$name];
547  if ( isset( $info['object'] ) ) {
548  // Object given in info array
549  $object = $info['object'];
550  } elseif ( isset( $info['factory'] ) ) {
551  $object = call_user_func( $info['factory'], $info );
552  $object->setConfig( $this->getConfig() );
553  $object->setLogger( $this->logger );
554  } else {
555  if ( !isset( $info['class'] ) ) {
557  } else {
558  $class = $info['class'];
559  }
561  $object = new $class( $info );
562  $object->setConfig( $this->getConfig() );
563  $object->setLogger( $this->logger );
564  }
565  $object->setName( $name );
566  $this->modules[$name] = $object;
567  }
568 
569  return $this->modules[$name];
570  }
571 
579  protected function isFileModule( $name ) {
580  if ( !isset( $this->moduleInfos[$name] ) ) {
581  return false;
582  }
583  $info = $this->moduleInfos[$name];
584  if ( isset( $info['object'] ) ) {
585  return false;
586  }
587  if (
588  isset( $info['class'] ) &&
589  $info['class'] !== ResourceLoaderFileModule::class &&
590  !is_subclass_of( $info['class'], ResourceLoaderFileModule::class )
591  ) {
592  return false;
593  }
594  return true;
595  }
596 
602  public function getSources() {
603  return $this->sources;
604  }
605 
615  public function getLoadScript( $source ) {
616  if ( !isset( $this->sources[$source] ) ) {
617  throw new MWException( "The $source source was never registered in ResourceLoader." );
618  }
619  return $this->sources[$source];
620  }
621 
627  public static function makeHash( $value ) {
628  $hash = hash( 'fnv132', $value );
629  return Wikimedia\base_convert( $hash, 16, 36, 7 );
630  }
631 
642  protected 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, do still consider the versions
668  // of other modules - don't set an empty string E-Tag for the whole request.
669  // See also T152266 and StartupModule::getModuleRegistrations().
670  $this->outputErrorAndLog( $e,
671  'Calculating version for "{module}" failed: {exception}',
672  [
673  'module' => $module,
674  ]
675  );
676  return '';
677  }
678  }, $moduleNames );
679  return self::makeHash( implode( '', $hashes ) );
680  }
681 
696  // As of MediaWiki 1.28, the server and client use the same algorithm for combining
697  // version hashes. There is no technical reason for this to be same, and for years the
698  // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
699  // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
700  // query parameter), then this method must continue to match the JS one.
701  $moduleNames = [];
702  foreach ( $context->getModules() as $name ) {
703  if ( !$this->getModule( $name ) ) {
704  // If a versioned request contains a missing module, the version is a mismatch
705  // as the client considered a module (and version) we don't have.
706  return '';
707  }
708  $moduleNames[] = $name;
709  }
710  return $this->getCombinedVersion( $context, $moduleNames );
711  }
712 
719  // Buffer output to catch warnings. Normally we'd use ob_clean() on the
720  // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
721  // is used: ob_clean() will clear the GZIP header in that case and it won't come
722  // back for subsequent output, resulting in invalid GZIP. So we have to wrap
723  // the whole thing in our own output buffer to be sure the active buffer
724  // doesn't use ob_gzhandler.
725  // See https://bugs.php.net/bug.php?id=36514
726  ob_start();
727 
728  $this->measureResponseTime( RequestContext::getMain()->getTiming() );
729 
730  // Find out which modules are missing and instantiate the others
731  $modules = [];
732  $missing = [];
733  foreach ( $context->getModules() as $name ) {
734  $module = $this->getModule( $name );
735  if ( $module ) {
736  // Do not allow private modules to be loaded from the web.
737  // This is a security issue, see T36907.
738  if ( $module->getGroup() === 'private' ) {
739  $this->logger->debug( "Request for private module '$name' denied" );
740  $this->errors[] = "Cannot show private module \"$name\"";
741  continue;
742  }
743  $modules[$name] = $module;
744  } else {
745  $missing[] = $name;
746  }
747  }
748 
749  try {
750  // Preload for getCombinedVersion() and for batch makeModuleResponse()
751  $this->preloadModuleInfo( array_keys( $modules ), $context );
752  } catch ( Exception $e ) {
753  $this->outputErrorAndLog( $e, 'Preloading module info failed: {exception}' );
754  }
755 
756  // Combine versions to propagate cache invalidation
757  $versionHash = '';
758  try {
759  $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
760  } catch ( Exception $e ) {
761  $this->outputErrorAndLog( $e, 'Calculating version hash failed: {exception}' );
762  }
763 
764  // See RFC 2616 § 3.11 Entity Tags
765  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
766  $etag = 'W/"' . $versionHash . '"';
767 
768  // Try the client-side cache first
769  if ( $this->tryRespondNotModified( $context, $etag ) ) {
770  return; // output handled (buffers cleared)
771  }
772 
773  // Use file cache if enabled and available...
774  if ( $this->config->get( 'UseFileCache' ) ) {
776  if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
777  return; // output handled
778  }
779  }
780 
781  // Generate a response
782  $response = $this->makeModuleResponse( $context, $modules, $missing );
783 
784  // Capture any PHP warnings from the output buffer and append them to the
785  // error list if we're in debug mode.
786  if ( $context->getDebug() ) {
787  $warnings = ob_get_contents();
788  if ( strlen( $warnings ) ) {
789  $this->errors[] = $warnings;
790  }
791  }
792 
793  // Save response to file cache unless there are errors
794  if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
795  // Cache single modules and images...and other requests if there are enough hits
797  if ( $fileCache->isCacheWorthy() ) {
798  $fileCache->saveText( $response );
799  } else {
800  $fileCache->incrMissesRecent( $context->getRequest() );
801  }
802  }
803  }
804 
805  $this->sendResponseHeaders( $context, $etag, (bool)$this->errors, $this->extraHeaders );
806 
807  // Remove the output buffer and output the response
808  ob_end_clean();
809 
810  if ( $context->getImageObj() && $this->errors ) {
811  // We can't show both the error messages and the response when it's an image.
812  $response = implode( "\n\n", $this->errors );
813  } elseif ( $this->errors ) {
814  $errorText = implode( "\n\n", $this->errors );
815  $errorResponse = self::makeComment( $errorText );
816  if ( $context->shouldIncludeScripts() ) {
817  $errorResponse .= 'if (window.console && console.error) {'
818  . Xml::encodeJsCall( 'console.error', [ $errorText ] )
819  . "}\n";
820  }
821 
822  // Prepend error info to the response
823  $response = $errorResponse . $response;
824  }
825 
826  $this->errors = [];
827  echo $response;
828  }
829 
830  protected function measureResponseTime( Timing $timing ) {
831  DeferredUpdates::addCallableUpdate( function () use ( $timing ) {
832  $measure = $timing->measure( 'responseTime', 'requestStart', 'requestShutdown' );
833  if ( $measure !== false ) {
834  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
835  $stats->timing( 'resourceloader.responseTime', $measure['duration'] * 1000 );
836  }
837  } );
838  }
839 
851  protected function sendResponseHeaders(
852  ResourceLoaderContext $context, $etag, $errors, array $extra = []
853  ) {
855  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
856  // Use a short cache expiry so that updates propagate to clients quickly, if:
857  // - No version specified (shared resources, e.g. stylesheets)
858  // - There were errors (recover quickly)
859  // - Version mismatch (T117587, T47877)
860  if ( is_null( $context->getVersion() )
861  || $errors
862  || $context->getVersion() !== $this->makeVersionQuery( $context )
863  ) {
864  $maxage = $rlMaxage['unversioned']['client'];
865  $smaxage = $rlMaxage['unversioned']['server'];
866  // If a version was specified we can use a longer expiry time since changing
867  // version numbers causes cache misses
868  } else {
869  $maxage = $rlMaxage['versioned']['client'];
870  $smaxage = $rlMaxage['versioned']['server'];
871  }
872  if ( $context->getImageObj() ) {
873  // Output different headers if we're outputting textual errors.
874  if ( $errors ) {
875  header( 'Content-Type: text/plain; charset=utf-8' );
876  } else {
877  $context->getImageObj()->sendResponseHeaders( $context );
878  }
879  } elseif ( $context->getOnly() === 'styles' ) {
880  header( 'Content-Type: text/css; charset=utf-8' );
881  header( 'Access-Control-Allow-Origin: *' );
882  } else {
883  header( 'Content-Type: text/javascript; charset=utf-8' );
884  }
885  // See RFC 2616 § 14.19 ETag
886  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
887  header( 'ETag: ' . $etag );
888  if ( $context->getDebug() ) {
889  // Do not cache debug responses
890  header( 'Cache-Control: private, no-cache, must-revalidate' );
891  header( 'Pragma: no-cache' );
892  } else {
893  header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
894  $exp = min( $maxage, $smaxage );
895  header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
896  }
897  foreach ( $extra as $header ) {
898  header( $header );
899  }
900  }
901 
912  protected function tryRespondNotModified( ResourceLoaderContext $context, $etag ) {
913  // See RFC 2616 § 14.26 If-None-Match
914  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
915  $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
916  // Never send 304s in debug mode
917  if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
918  // There's another bug in ob_gzhandler (see also the comment at
919  // the top of this function) that causes it to gzip even empty
920  // responses, meaning it's impossible to produce a truly empty
921  // response (because the gzip header is always there). This is
922  // a problem because 304 responses have to be completely empty
923  // per the HTTP spec, and Firefox behaves buggily when they're not.
924  // See also https://bugs.php.net/bug.php?id=51579
925  // To work around this, we tear down all output buffering before
926  // sending the 304.
927  wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
928 
929  HttpStatus::header( 304 );
930 
931  $this->sendResponseHeaders( $context, $etag, false );
932  return true;
933  }
934  return false;
935  }
936 
945  protected function tryRespondFromFileCache(
946  ResourceFileCache $fileCache,
948  $etag
949  ) {
950  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
951  // Buffer output to catch warnings.
952  ob_start();
953  // Get the maximum age the cache can be
954  $maxage = is_null( $context->getVersion() )
955  ? $rlMaxage['unversioned']['server']
956  : $rlMaxage['versioned']['server'];
957  // Minimum timestamp the cache file must have
958  $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
959  if ( !$good ) {
960  try { // RL always hits the DB on file cache miss...
961  wfGetDB( DB_REPLICA );
962  } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
963  $good = $fileCache->isCacheGood(); // cache existence check
964  }
965  }
966  if ( $good ) {
967  $ts = $fileCache->cacheTimestamp();
968  // Send content type and cache headers
969  $this->sendResponseHeaders( $context, $etag, false );
970  $response = $fileCache->fetchText();
971  // Capture any PHP warnings from the output buffer and append them to the
972  // response in a comment if we're in debug mode.
973  if ( $context->getDebug() ) {
974  $warnings = ob_get_contents();
975  if ( strlen( $warnings ) ) {
976  $response = self::makeComment( $warnings ) . $response;
977  }
978  }
979  // Remove the output buffer and output the response
980  ob_end_clean();
981  echo $response . "\n/* Cached {$ts} */";
982  return true; // cache hit
983  }
984  // Clear buffer
985  ob_end_clean();
986 
987  return false; // cache miss
988  }
989 
998  public static function makeComment( $text ) {
999  $encText = str_replace( '*/', '* /', $text );
1000  return "/*\n$encText\n*/\n";
1001  }
1002 
1009  public static function formatException( $e ) {
1010  return self::makeComment( self::formatExceptionNoComment( $e ) );
1011  }
1012 
1020  protected static function formatExceptionNoComment( $e ) {
1022 
1023  if ( !$wgShowExceptionDetails ) {
1025  }
1026 
1028  "\nBacktrace:\n" .
1030  }
1031 
1044  array $modules, array $missing = []
1045  ) {
1046  $out = '';
1047  $states = [];
1048 
1049  if ( !count( $modules ) && !count( $missing ) ) {
1050  return <<<MESSAGE
1051 /* This file is the Web entry point for MediaWiki's ResourceLoader:
1052  <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
1053  no modules were requested. Max made me put this here. */
1054 MESSAGE;
1055  }
1056 
1057  $image = $context->getImageObj();
1058  if ( $image ) {
1059  $data = $image->getImageData( $context );
1060  if ( $data === false ) {
1061  $data = '';
1062  $this->errors[] = 'Image generation failed';
1063  }
1064  return $data;
1065  }
1066 
1067  foreach ( $missing as $name ) {
1068  $states[$name] = 'missing';
1069  }
1070 
1071  // Generate output
1072  $isRaw = false;
1073 
1074  $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
1075 
1076  foreach ( $modules as $name => $module ) {
1077  try {
1078  $content = $module->getModuleContent( $context );
1079  $implementKey = $name . '@' . $module->getVersionHash( $context );
1080  $strContent = '';
1081 
1082  if ( isset( $content['headers'] ) ) {
1083  $this->extraHeaders = array_merge( $this->extraHeaders, $content['headers'] );
1084  }
1085 
1086  // Append output
1087  switch ( $context->getOnly() ) {
1088  case 'scripts':
1089  $scripts = $content['scripts'];
1090  if ( is_string( $scripts ) ) {
1091  // Load scripts raw...
1092  $strContent = $scripts;
1093  } elseif ( is_array( $scripts ) ) {
1094  // ...except when $scripts is an array of URLs
1095  $strContent = self::makeLoaderImplementScript( $implementKey, $scripts, [], [], [] );
1096  }
1097  break;
1098  case 'styles':
1099  $styles = $content['styles'];
1100  // We no longer seperate into media, they are all combined now with
1101  // custom media type groups into @media .. {} sections as part of the css string.
1102  // Module returns either an empty array or a numerical array with css strings.
1103  $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
1104  break;
1105  default:
1106  $scripts = isset( $content['scripts'] ) ? $content['scripts'] : '';
1107  if ( is_string( $scripts ) ) {
1108  if ( $name === 'site' || $name === 'user' ) {
1109  // Legacy scripts that run in the global scope without a closure.
1110  // mw.loader.implement will use globalEval if scripts is a string.
1111  // Minify manually here, because general response minification is
1112  // not effective due it being a string literal, not a function.
1113  if ( !self::inDebugMode() ) {
1114  $scripts = self::filter( 'minify-js', $scripts ); // T107377
1115  }
1116  } else {
1117  $scripts = new XmlJsCode( $scripts );
1118  }
1119  }
1120  $strContent = self::makeLoaderImplementScript(
1121  $implementKey,
1122  $scripts,
1123  isset( $content['styles'] ) ? $content['styles'] : [],
1124  isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
1125  isset( $content['templates'] ) ? $content['templates'] : []
1126  );
1127  break;
1128  }
1129 
1130  if ( !$context->getDebug() ) {
1131  $strContent = self::filter( $filter, $strContent );
1132  }
1133 
1134  if ( $context->getOnly() === 'scripts' ) {
1135  // Use a linebreak between module scripts (T162719)
1136  $out .= $this->ensureNewline( $strContent );
1137  } else {
1138  $out .= $strContent;
1139  }
1140 
1141  } catch ( Exception $e ) {
1142  $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1143 
1144  // Respond to client with error-state instead of module implementation
1145  $states[$name] = 'error';
1146  unset( $modules[$name] );
1147  }
1148  $isRaw |= $module->isRaw();
1149  }
1150 
1151  // Update module states
1152  if ( $context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw ) {
1153  if ( count( $modules ) && $context->getOnly() === 'scripts' ) {
1154  // Set the state of modules loaded as only scripts to ready as
1155  // they don't have an mw.loader.implement wrapper that sets the state
1156  foreach ( $modules as $name => $module ) {
1157  $states[$name] = 'ready';
1158  }
1159  }
1160 
1161  // Set the state of modules we didn't respond to with mw.loader.implement
1162  if ( count( $states ) ) {
1163  $stateScript = self::makeLoaderStateScript( $states );
1164  if ( !$context->getDebug() ) {
1165  $stateScript = self::filter( 'minify-js', $stateScript );
1166  }
1167  // Use a linebreak between module script and state script (T162719)
1168  $out = $this->ensureNewline( $out ) . $stateScript;
1169  }
1170  } else {
1171  if ( count( $states ) ) {
1172  $this->errors[] = 'Problematic modules: ' .
1173  FormatJson::encode( $states, self::inDebugMode() );
1174  }
1175  }
1176 
1177  return $out;
1178  }
1179 
1185  private function ensureNewline( $str ) {
1186  $end = substr( $str, -1 );
1187  if ( $end === false || $end === "\n" ) {
1188  return $str;
1189  }
1190  return $str . "\n";
1191  }
1192 
1199  public function getModulesByMessage( $messageKey ) {
1200  $moduleNames = [];
1201  foreach ( $this->getModuleNames() as $moduleName ) {
1202  $module = $this->getModule( $moduleName );
1203  if ( in_array( $messageKey, $module->getMessages() ) ) {
1204  $moduleNames[] = $moduleName;
1205  }
1206  }
1207  return $moduleNames;
1208  }
1209 
1226  protected static function makeLoaderImplementScript(
1227  $name, $scripts, $styles, $messages, $templates
1228  ) {
1229  if ( $scripts instanceof XmlJsCode ) {
1230  if ( self::inDebugMode() ) {
1231  $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1232  } else {
1233  $scripts = new XmlJsCode( 'function($,jQuery,require,module){'. $scripts->value . '}' );
1234  }
1235  } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1236  throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
1237  }
1238  // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
1239  // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1240  // of "{}". Force them to objects.
1241  $module = [
1242  $name,
1243  $scripts,
1244  (object)$styles,
1245  (object)$messages,
1246  (object)$templates,
1247  ];
1248  self::trimArray( $module );
1249 
1250  return Xml::encodeJsCall( 'mw.loader.implement', $module, self::inDebugMode() );
1251  }
1252 
1260  public static function makeMessageSetScript( $messages ) {
1261  return Xml::encodeJsCall(
1262  'mw.messages.set',
1263  [ (object)$messages ],
1264  self::inDebugMode()
1265  );
1266  }
1267 
1275  public static function makeCombinedStyles( array $stylePairs ) {
1276  $out = [];
1277  foreach ( $stylePairs as $media => $styles ) {
1278  // ResourceLoaderFileModule::getStyle can return the styles
1279  // as a string or an array of strings. This is to allow separation in
1280  // the front-end.
1281  $styles = (array)$styles;
1282  foreach ( $styles as $style ) {
1283  $style = trim( $style );
1284  // Don't output an empty "@media print { }" block (T42498)
1285  if ( $style !== '' ) {
1286  // Transform the media type based on request params and config
1287  // The way that this relies on $wgRequest to propagate request params is slightly evil
1288  $media = OutputPage::transformCssMedia( $media );
1289 
1290  if ( $media === '' || $media == 'all' ) {
1291  $out[] = $style;
1292  } elseif ( is_string( $media ) ) {
1293  $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1294  }
1295  // else: skip
1296  }
1297  }
1298  }
1299  return $out;
1300  }
1301 
1316  public static function makeLoaderStateScript( $name, $state = null ) {
1317  if ( is_array( $name ) ) {
1318  return Xml::encodeJsCall(
1319  'mw.loader.state',
1320  [ $name ],
1321  self::inDebugMode()
1322  );
1323  } else {
1324  return Xml::encodeJsCall(
1325  'mw.loader.state',
1326  [ $name, $state ],
1327  self::inDebugMode()
1328  );
1329  }
1330  }
1331 
1346  public static function makeCustomLoaderScript( $name, $version, $dependencies,
1347  $group, $source, $script
1348  ) {
1349  $script = str_replace( "\n", "\n\t", trim( $script ) );
1350  return Xml::encodeJsCall(
1351  "( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
1352  [ $name, $version, $dependencies, $group, $source ],
1353  self::inDebugMode()
1354  );
1355  }
1356 
1357  private static function isEmptyObject( stdClass $obj ) {
1358  foreach ( $obj as $key => $value ) {
1359  return false;
1360  }
1361  return true;
1362  }
1363 
1376  private static function trimArray( array &$array ) {
1377  $i = count( $array );
1378  while ( $i-- ) {
1379  if ( $array[$i] === null
1380  || $array[$i] === []
1381  || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
1382  || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1383  ) {
1384  unset( $array[$i] );
1385  } else {
1386  break;
1387  }
1388  }
1389  }
1390 
1418  public static function makeLoaderRegisterScript( $name, $version = null,
1419  $dependencies = null, $group = null, $source = null, $skip = null
1420  ) {
1421  if ( is_array( $name ) ) {
1422  // Build module name index
1423  $index = [];
1424  foreach ( $name as $i => &$module ) {
1425  $index[$module[0]] = $i;
1426  }
1427 
1428  // Transform dependency names into indexes when possible, they will be resolved by
1429  // mw.loader.register on the other end
1430  foreach ( $name as &$module ) {
1431  if ( isset( $module[2] ) ) {
1432  foreach ( $module[2] as &$dependency ) {
1433  if ( isset( $index[$dependency] ) ) {
1434  $dependency = $index[$dependency];
1435  }
1436  }
1437  }
1438  }
1439 
1440  array_walk( $name, [ 'self', 'trimArray' ] );
1441 
1442  return Xml::encodeJsCall(
1443  'mw.loader.register',
1444  [ $name ],
1445  self::inDebugMode()
1446  );
1447  } else {
1448  $registration = [ $name, $version, $dependencies, $group, $source, $skip ];
1449  self::trimArray( $registration );
1450  return Xml::encodeJsCall(
1451  'mw.loader.register',
1452  $registration,
1453  self::inDebugMode()
1454  );
1455  }
1456  }
1457 
1472  public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
1473  if ( is_array( $id ) ) {
1474  return Xml::encodeJsCall(
1475  'mw.loader.addSource',
1476  [ $id ],
1477  self::inDebugMode()
1478  );
1479  } else {
1480  return Xml::encodeJsCall(
1481  'mw.loader.addSource',
1482  [ $id, $loadUrl ],
1483  self::inDebugMode()
1484  );
1485  }
1486  }
1487 
1494  public static function makeLoaderConditionalScript( $script ) {
1495  return '(window.RLQ=window.RLQ||[]).push(function(){' .
1496  trim( $script ) . '});';
1497  }
1498 
1508  public static function makeInlineScript( $script ) {
1509  $js = self::makeLoaderConditionalScript( $script );
1510  return new WrappedString(
1511  Html::inlineScript( $js ),
1512  '<script>(window.RLQ=window.RLQ||[]).push(function(){',
1513  '});</script>'
1514  );
1515  }
1516 
1524  public static function makeConfigSetScript( array $configuration ) {
1525  return Xml::encodeJsCall(
1526  'mw.config.set',
1527  [ $configuration ],
1528  self::inDebugMode()
1529  );
1530  }
1531 
1545  public static function makePackedModulesString( $modules ) {
1546  $moduleMap = []; // [ prefix => [ suffixes ] ]
1547  foreach ( $modules as $module ) {
1548  $pos = strrpos( $module, '.' );
1549  $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1550  $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1551  $moduleMap[$prefix][] = $suffix;
1552  }
1553 
1554  $arr = [];
1555  foreach ( $moduleMap as $prefix => $suffixes ) {
1556  $p = $prefix === '' ? '' : $prefix . '.';
1557  $arr[] = $p . implode( ',', $suffixes );
1558  }
1559  return implode( '|', $arr );
1560  }
1561 
1567  public static function inDebugMode() {
1568  if ( self::$debugMode === null ) {
1570  self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
1571  $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
1572  );
1573  }
1574  return self::$debugMode;
1575  }
1576 
1584  public static function clearCache() {
1585  self::$debugMode = null;
1586  }
1587 
1597  public function createLoaderURL( $source, ResourceLoaderContext $context,
1598  $extraQuery = []
1599  ) {
1600  $query = self::createLoaderQuery( $context, $extraQuery );
1601  $script = $this->getLoadScript( $source );
1602 
1603  return wfAppendQuery( $script, $query );
1604  }
1605 
1615  protected static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = [] ) {
1616  return self::makeLoaderQuery(
1617  $context->getModules(),
1618  $context->getLanguage(),
1619  $context->getSkin(),
1620  $context->getUser(),
1621  $context->getVersion(),
1622  $context->getDebug(),
1623  $context->getOnly(),
1624  $context->getRequest()->getBool( 'printable' ),
1625  $context->getRequest()->getBool( 'handheld' ),
1626  $extraQuery
1627  );
1628  }
1629 
1647  public static function makeLoaderQuery( $modules, $lang, $skin, $user = null,
1648  $version = null, $debug = false, $only = null, $printable = false,
1649  $handheld = false, $extraQuery = []
1650  ) {
1651  $query = [
1652  'modules' => self::makePackedModulesString( $modules ),
1653  'lang' => $lang,
1654  'skin' => $skin,
1655  'debug' => $debug ? 'true' : 'false',
1656  ];
1657  if ( $user !== null ) {
1658  $query['user'] = $user;
1659  }
1660  if ( $version !== null ) {
1661  $query['version'] = $version;
1662  }
1663  if ( $only !== null ) {
1664  $query['only'] = $only;
1665  }
1666  if ( $printable ) {
1667  $query['printable'] = 1;
1668  }
1669  if ( $handheld ) {
1670  $query['handheld'] = 1;
1671  }
1672  $query += $extraQuery;
1673 
1674  // Make queries uniform in order
1675  ksort( $query );
1676  return $query;
1677  }
1678 
1688  public static function isValidModuleName( $moduleName ) {
1689  return strcspn( $moduleName, '!,|', 0, 255 ) === strlen( $moduleName );
1690  }
1691 
1701  public function getLessCompiler( $extraVars = [] ) {
1702  // When called from the installer, it is possible that a required PHP extension
1703  // is missing (at least for now; see T49564). If this is the case, throw an
1704  // exception (caught by the installer) to prevent a fatal error later on.
1705  if ( !class_exists( 'Less_Parser' ) ) {
1706  throw new MWException( 'MediaWiki requires the less.php parser' );
1707  }
1708 
1709  $parser = new Less_Parser;
1710  $parser->ModifyVars( array_merge( $this->getLessVars(), $extraVars ) );
1711  $parser->SetImportDirs(
1712  array_fill_keys( $this->config->get( 'ResourceLoaderLESSImportPaths' ), '' )
1713  );
1714  $parser->SetOption( 'relativeUrls', false );
1715 
1716  return $parser;
1717  }
1718 
1725  public function getLessVars() {
1726  if ( $this->lessVars === null ) {
1727  $this->lessVars = $this->config->get( 'ResourceLoaderLESSVars' );
1728  }
1729  return $this->lessVars;
1730  }
1731 }
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:189
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:247
ResourceLoaderContext
Object passed around to modules which contains information about the state of a specific loader reque...
Definition: ResourceLoaderContext.php:32
object
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:64
ResourceLoader\getMessageBlobStore
getMessageBlobStore()
Definition: ResourceLoader.php:304
wfResetOutputBuffers
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
Definition: GlobalFunctions.php:1788
ResourceLoader\sendResponseHeaders
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors, array $extra=[])
Send main response headers to the client.
Definition: ResourceLoader.php:851
ResourceLoader\$errors
array $errors
Errors accumulated during current respond() call.
Definition: ResourceLoader.php:80
CSSMin\minify
static minify( $css)
Removes whitespace from CSS data.
Definition: CSSMin.php:547
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
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
ResourceLoader\makeVersionQuery
makeVersionQuery(ResourceLoaderContext $context)
Get the expected value of the 'version' query parameter.
Definition: ResourceLoader.php:695
$response
this hook is for auditing only $response
Definition: hooks.txt:783
array
the array() calling protocol came about after MediaWiki 1.4rc1.
ResourceLoader\formatException
static formatException( $e)
Handle exception display.
Definition: ResourceLoader.php:1009
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
ResourceLoader\$lessVars
array $lessVars
Definition: ResourceLoader.php:46
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1980
ResourceLoader\getLogger
getLogger()
Definition: ResourceLoader.php:296
ResourceLoader\isFileModule
isFileModule( $name)
Return whether the definition of a module corresponds to a simple ResourceLoaderFileModule or one of ...
Definition: ResourceLoader.php:579
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:864
$res
$res
Definition: database.txt:21
ResourceLoader\$moduleInfos
array $moduleInfos
Associative array mapping module name to info associative array.
Definition: ResourceLoader.php:58
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1993
ResourceLoader\getTestModuleNames
getTestModuleNames( $framework='all')
Get a list of test module names for one (or all) frameworks.
Definition: ResourceLoader.php:503
Html\inlineScript
static inlineScript( $contents)
Output a "<script>" tag with the given contents.
Definition: Html.php:562
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:37
ResourceLoader\preloadModuleInfo
preloadModuleInfo(array $moduleNames, ResourceLoaderContext $context)
Load information stored in the database about modules.
Definition: ResourceLoader.php:118
OutputPage\transformCssMedia
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
Definition: OutputPage.php:3757
ResourceLoader\$modules
array $modules
Module name/ResourceLoaderModule object pairs.
Definition: ResourceLoader.php:52
$dbr
$dbr
Definition: testCompression.php:50
$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:469
XmlJsCode
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
Definition: XmlJsCode.php:39
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:678
ResourceLoader\setMessageBlobStore
setMessageBlobStore(MessageBlobStore $blobStore)
Definition: ResourceLoader.php:312
Config
Interface for configuration instances.
Definition: Config.php:28
MWExceptionHandler\getPublicLogMessage
static getPublicLogMessage( $e)
Definition: MWExceptionHandler.php:498
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
FormatJson\encode
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:127
MWException
MediaWiki exception.
Definition: MWException.php:26
ResourceLoader\makeModuleResponse
makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing=[])
Generate code for a response.
Definition: ResourceLoader.php:1043
ResourceLoaderWikiModule\preloadTitleInfo
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Definition: ResourceLoaderWikiModule.php:360
$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:2812
ResourceLoader\makeHash
static makeHash( $value)
Definition: ResourceLoader.php:627
MWExceptionHandler\getLogMessage
static getLogMessage( $e)
Get a message formatting the exception message and its origin.
Definition: MWExceptionHandler.php:465
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2603
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
$modules
$modules
Definition: HTMLFormElement.php:12
ResourceLoader\getSources
getSources()
Get the list of sources.
Definition: ResourceLoader.php:602
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:307
ResourceLoader\makeComment
static makeComment( $text)
Generate a CSS or JS comment block.
Definition: ResourceLoader.php:998
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
ResourceLoaderModule\expandRelativePaths
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
Definition: ResourceLoaderModule.php:532
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ResourceLoader\$logger
LoggerInterface $logger
Definition: ResourceLoader.php:99
FileCacheBase\fetchText
fetchText()
Get the uncompressed text from the cache.
Definition: FileCacheBase.php:144
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
ResourceLoader\$filterCacheVersion
static int $filterCacheVersion
Definition: ResourceLoader.php:40
errors
the value of this variable comes from LanguageConverter indexed by page_id indexed by prefixed DB keys can modify can modify can modify this should be populated with an alert message to that effect to be fed to an HTMLForm object and populate $result with the reason in the form of error messages should be plain text with no special etc to show that they re errors
Definition: hooks.txt:1748
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2001
ResourceLoader\getModule
getModule( $name)
Get the ResourceLoaderModule object for a given module name.
Definition: ResourceLoader.php:538
ResourceLoader\$debugMode
static bool $debugMode
Definition: ResourceLoader.php:43
MessageBlobStore\get
get(ResourceLoader $resourceLoader, $modules, $lang)
Definition: MessageBlobStore.php:127
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:615
$value
$value
Definition: styleTest.css.php:45
$header
$header
Definition: updateCredits.php:35
ResourceLoader\__construct
__construct(Config $config=null, LoggerInterface $logger=null)
Register core modules and runs registration hooks.
Definition: ResourceLoader.php:244
ResourceLoader\applyFilter
static applyFilter( $filter, $data)
Definition: ResourceLoader.php:224
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:111
ResourceLoader\$testModuleNames
array $testModuleNames
Associative array mapping framework ids to a list of names of test suite modules like [ 'qunit' => [ ...
Definition: ResourceLoader.php:68
ResourceLoader\isModuleRegistered
isModuleRegistered( $name)
Check whether a ResourceLoader module is registered.
Definition: ResourceLoader.php:523
ResourceLoader\$blobStore
MessageBlobStore $blobStore
Definition: ResourceLoader.php:94
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:718
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:434
ResourceLoader\$config
$config
Definition: ResourceLoader.php:61
$skin
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned $skin
Definition: hooks.txt:2011
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
ResourceLoaderModule
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition: ResourceLoaderModule.php:35
ResourceLoader
Dynamic JavaScript and CSS resource loading system.
Definition: ResourceLoader.php:38
$image
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check $image
Definition: hooks.txt:895
$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:45
ResourceLoader\tryRespondFromFileCache
tryRespondFromFileCache(ResourceFileCache $fileCache, ResourceLoaderContext $context, $etag)
Send out code for a response from file cache if possible.
Definition: ResourceLoader.php:945
or
or
Definition: APACHE-LICENSE-2.0.txt:114
$path
$path
Definition: NoLocalSettings.php:25
$wgShowExceptionDetails
$wgShowExceptionDetails
If set to true, uncaught exceptions will print a complete stack trace to output.
Definition: DefaultSettings.php:6303
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:22
JavaScriptMinifier\minify
static minify( $s)
Returns minified JavaScript code.
Definition: JavaScriptMinifier.php:78
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
from
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable from
Definition: APACHE-LICENSE-2.0.txt:46
MessageBlobStore
This class generates message blobs for use by ResourceLoader modules.
Definition: MessageBlobStore.php:37
ResourceLoader\addSource
addSource( $id, $loadUrl=null)
Add a foreign source of modules.
Definition: ResourceLoader.php:453
$source
$source
Definition: mwdoc-filter.php:46
ResourceLoader\registerTestModules
registerTestModules()
Definition: ResourceLoader.php:404
ResourceLoader\tryRespondNotModified
tryRespondNotModified(ResourceLoaderContext $context, $etag)
Respond with HTTP 304 Not Modified if appropiate.
Definition: ResourceLoader.php:912
$hashes
$hashes
Definition: testCompression.php:66
ResourceLoader\$sources
array $sources
E.g.
Definition: ResourceLoader.php:74
ResourceLoader\formatExceptionNoComment
static formatExceptionNoComment( $e)
Handle exception display.
Definition: ResourceLoader.php:1020
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:56
ResourceLoader\$extraHeaders
array $extraHeaders
List of extra HTTP response headers provided by loaded modules.
Definition: ResourceLoader.php:89
definition
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this definition
Definition: APACHE-LICENSE-2.0.txt:54
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:737
ResourceLoader\getConfig
getConfig()
Definition: ResourceLoader.php:280
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:25
$query
null for the wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1620
$IP
$IP
Definition: WebStart.php:52
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
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:3694
MWExceptionHandler\getRedactedTraceAsString
static getRedactedTraceAsString( $e)
Generate a string representation of an exception's stack trace.
Definition: MWExceptionHandler.php:343
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2811
ResourceLoader\outputErrorAndLog
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
Definition: ResourceLoader.php:642
$messages
$messages
Definition: LogTests.i18n.php:8
ResourceLoader\getModuleNames
getModuleNames()
Get a list of module names.
Definition: ResourceLoader.php:489
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
ResourceLoader\setLogger
setLogger(LoggerInterface $logger)
Definition: ResourceLoader.php:288
FileCacheBase\cacheTimestamp
cacheTimestamp()
Get the last-modified timestamp of the cache file.
Definition: FileCacheBase.php:103
ResourceLoader\measureResponseTime
measureResponseTime(Timing $timing)
Definition: ResourceLoader.php:830
MediaWiki\HeaderCallback\warnIfHeadersSent
static warnIfHeadersSent()
Log a warning message if headers have already been sent.
Definition: HeaderCallback.php:57
MWExceptionHandler\logException
static logException( $e, $catcher=self::CAUGHT_BY_OTHER)
Log an exception to the exception log (if enabled).
Definition: MWExceptionHandler.php:645
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:288