MediaWiki  master
ResourceLoader.php
Go to the documentation of this file.
1 <?php
25 use Psr\Log\LoggerAwareInterface;
26 use Psr\Log\LoggerInterface;
27 use Psr\Log\NullLogger;
28 use WrappedString\WrappedString;
29 
36 class ResourceLoader implements LoggerAwareInterface {
38  protected static $filterCacheVersion = 7;
39 
41  protected static $debugMode = null;
42 
44  private $lessVars = null;
45 
50  protected $modules = [];
51 
56  protected $moduleInfos = [];
57 
59  private $config;
60 
66  protected $testModuleNames = [];
67 
72  protected $sources = [];
73 
78  protected $errors = [];
79 
83  protected $blobStore;
84 
88  private $logger;
89 
91  const FILTER_NOMIN = '/*@nomin*/';
92 
107  public function preloadModuleInfo( array $moduleNames, ResourceLoaderContext $context ) {
108  if ( !$moduleNames ) {
109  // Or else Database*::select() will explode, plus it's cheaper!
110  return;
111  }
112  $dbr = wfGetDB( DB_SLAVE );
113  $skin = $context->getSkin();
114  $lang = $context->getLanguage();
115 
116  // Batched version of ResourceLoaderModule::getFileDependencies
117  $vary = "$skin|$lang";
118  $res = $dbr->select( 'module_deps', [ 'md_module', 'md_deps' ], [
119  'md_module' => $moduleNames,
120  'md_skin' => $vary,
121  ], __METHOD__
122  );
123 
124  // Prime in-object cache for file dependencies
125  $modulesWithDeps = [];
126  foreach ( $res as $row ) {
127  $module = $this->getModule( $row->md_module );
128  if ( $module ) {
129  $module->setFileDependencies( $context, ResourceLoaderModule::expandRelativePaths(
130  FormatJson::decode( $row->md_deps, true )
131  ) );
132  $modulesWithDeps[] = $row->md_module;
133  }
134  }
135  // Register the absence of a dependency row too
136  foreach ( array_diff( $moduleNames, $modulesWithDeps ) as $name ) {
137  $module = $this->getModule( $name );
138  if ( $module ) {
139  $this->getModule( $name )->setFileDependencies( $context, [] );
140  }
141  }
142 
143  // Prime in-object cache for message blobs for modules with messages
144  $modules = [];
145  foreach ( $moduleNames as $name ) {
146  $module = $this->getModule( $name );
147  if ( $module && $module->getMessages() ) {
148  $modules[$name] = $module;
149  }
150  }
151  $store = $this->getMessageBlobStore();
152  $blobs = $store->getBlobs( $modules, $lang );
153  foreach ( $blobs as $name => $blob ) {
154  $modules[$name]->setMessageBlob( $blob, $lang );
155  }
156  }
157 
175  public static function filter( $filter, $data, array $options = [] ) {
176  if ( strpos( $data, ResourceLoader::FILTER_NOMIN ) !== false ) {
177  return $data;
178  }
179 
180  if ( isset( $options['cache'] ) && $options['cache'] === false ) {
181  return self::applyFilter( $filter, $data );
182  }
183 
184  $stats = RequestContext::getMain()->getStats();
186 
187  $key = $cache->makeGlobalKey(
188  'resourceloader',
189  'filter',
190  $filter,
191  self::$filterCacheVersion, md5( $data )
192  );
193 
194  $result = $cache->get( $key );
195  if ( $result === false ) {
196  $stats->increment( "resourceloader_cache.$filter.miss" );
197  $result = self::applyFilter( $filter, $data );
198  $cache->set( $key, $result, 24 * 3600 );
199  } else {
200  $stats->increment( "resourceloader_cache.$filter.hit" );
201  }
202  if ( $result === null ) {
203  // Cached failure
204  $result = $data;
205  }
206 
207  return $result;
208  }
209 
210  private static function applyFilter( $filter, $data ) {
211  $data = trim( $data );
212  if ( $data ) {
213  try {
214  $data = ( $filter === 'minify-css' )
215  ? CSSMin::minify( $data )
216  : JavaScriptMinifier::minify( $data );
217  } catch ( Exception $e ) {
219  return null;
220  }
221  }
222  return $data;
223  }
224 
225  /* Methods */
226 
232  public function __construct( Config $config = null, LoggerInterface $logger = null ) {
233  global $IP;
234 
235  $this->logger = $logger ?: new NullLogger();
236 
237  if ( !$config ) {
238  $this->logger->debug( __METHOD__ . ' was called without providing a Config instance' );
239  $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
240  }
241  $this->config = $config;
242 
243  // Add 'local' source first
244  $this->addSource( 'local', wfScript( 'load' ) );
245 
246  // Add other sources
247  $this->addSource( $config->get( 'ResourceLoaderSources' ) );
248 
249  // Register core modules
250  $this->register( include "$IP/resources/Resources.php" );
251  $this->register( include "$IP/resources/ResourcesOOUI.php" );
252  // Register extension modules
253  $this->register( $config->get( 'ResourceModules' ) );
254  Hooks::run( 'ResourceLoaderRegisterModules', [ &$this ] );
255 
256  if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
257  $this->registerTestModules();
258  }
259 
260  $this->setMessageBlobStore( new MessageBlobStore( $this, $this->logger ) );
261  }
262 
266  public function getConfig() {
267  return $this->config;
268  }
269 
274  public function setLogger( LoggerInterface $logger ) {
275  $this->logger = $logger;
276  }
277 
282  public function getLogger() {
283  return $this->logger;
284  }
285 
290  public function getMessageBlobStore() {
291  return $this->blobStore;
292  }
293 
299  $this->blobStore = $blobStore;
300  }
301 
315  public function register( $name, $info = null ) {
316  $moduleSkinStyles = $this->config->get( 'ResourceModuleSkinStyles' );
317 
318  // Allow multiple modules to be registered in one call
319  $registrations = is_array( $name ) ? $name : [ $name => $info ];
320  foreach ( $registrations as $name => $info ) {
321  // Warn on duplicate registrations
322  if ( isset( $this->moduleInfos[$name] ) ) {
323  // A module has already been registered by this name
324  $this->logger->warning(
325  'ResourceLoader duplicate registration warning. ' .
326  'Another module has already been registered as ' . $name
327  );
328  }
329 
330  // Check $name for validity
331  if ( !self::isValidModuleName( $name ) ) {
332  throw new MWException( "ResourceLoader module name '$name' is invalid, "
333  . "see ResourceLoader::isValidModuleName()" );
334  }
335 
336  // Attach module
337  if ( $info instanceof ResourceLoaderModule ) {
338  $this->moduleInfos[$name] = [ 'object' => $info ];
339  $info->setName( $name );
340  $this->modules[$name] = $info;
341  } elseif ( is_array( $info ) ) {
342  // New calling convention
343  $this->moduleInfos[$name] = $info;
344  } else {
345  throw new MWException(
346  'ResourceLoader module info type error for module \'' . $name .
347  '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
348  );
349  }
350 
351  // Last-minute changes
352 
353  // Apply custom skin-defined styles to existing modules.
354  if ( $this->isFileModule( $name ) ) {
355  foreach ( $moduleSkinStyles as $skinName => $skinStyles ) {
356  // If this module already defines skinStyles for this skin, ignore $wgResourceModuleSkinStyles.
357  if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
358  continue;
359  }
360 
361  // If $name is preceded with a '+', the defined style files will be added to 'default'
362  // skinStyles, otherwise 'default' will be ignored as it normally would be.
363  if ( isset( $skinStyles[$name] ) ) {
364  $paths = (array)$skinStyles[$name];
365  $styleFiles = [];
366  } elseif ( isset( $skinStyles['+' . $name] ) ) {
367  $paths = (array)$skinStyles['+' . $name];
368  $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
369  (array)$this->moduleInfos[$name]['skinStyles']['default'] :
370  [];
371  } else {
372  continue;
373  }
374 
375  // Add new file paths, remapping them to refer to our directories and not use settings
376  // from the module we're modifying, which come from the base definition.
377  list( $localBasePath, $remoteBasePath ) =
379 
380  foreach ( $paths as $path ) {
381  $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
382  }
383 
384  $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
385  }
386  }
387  }
388 
389  }
390 
393  public function registerTestModules() {
394  global $IP;
395 
396  if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
397  throw new MWException( 'Attempt to register JavaScript test modules '
398  . 'but <code>$wgEnableJavaScriptTest</code> is false. '
399  . 'Edit your <code>LocalSettings.php</code> to enable it.' );
400  }
401 
402  // Get core test suites
403  $testModules = [];
404  $testModules['qunit'] = [];
405  // Get other test suites (e.g. from extensions)
406  Hooks::run( 'ResourceLoaderTestModules', [ &$testModules, &$this ] );
407 
408  // Add the testrunner (which configures QUnit) to the dependencies.
409  // Since it must be ready before any of the test suites are executed.
410  foreach ( $testModules['qunit'] as &$module ) {
411  // Make sure all test modules are top-loading so that when QUnit starts
412  // on document-ready, it will run once and finish. If some tests arrive
413  // later (possibly after QUnit has already finished) they will be ignored.
414  $module['position'] = 'top';
415  $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
416  }
417 
418  $testModules['qunit'] =
419  ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules['qunit'];
420 
421  foreach ( $testModules as $id => $names ) {
422  // Register test modules
423  $this->register( $testModules[$id] );
424 
425  // Keep track of their names so that they can be loaded together
426  $this->testModuleNames[$id] = array_keys( $testModules[$id] );
427  }
428 
429  }
430 
441  public function addSource( $id, $loadUrl = null ) {
442  // Allow multiple sources to be registered in one call
443  if ( is_array( $id ) ) {
444  foreach ( $id as $key => $value ) {
445  $this->addSource( $key, $value );
446  }
447  return;
448  }
449 
450  // Disallow duplicates
451  if ( isset( $this->sources[$id] ) ) {
452  throw new MWException(
453  'ResourceLoader duplicate source addition error. ' .
454  'Another source has already been registered as ' . $id
455  );
456  }
457 
458  // Pre 1.24 backwards-compatibility
459  if ( is_array( $loadUrl ) ) {
460  if ( !isset( $loadUrl['loadScript'] ) ) {
461  throw new MWException(
462  __METHOD__ . ' was passed an array with no "loadScript" key.'
463  );
464  }
465 
466  $loadUrl = $loadUrl['loadScript'];
467  }
468 
469  $this->sources[$id] = $loadUrl;
470  }
471 
477  public function getModuleNames() {
478  return array_keys( $this->moduleInfos );
479  }
480 
491  public function getTestModuleNames( $framework = 'all' ) {
493  if ( $framework == 'all' ) {
494  return $this->testModuleNames;
495  } elseif ( isset( $this->testModuleNames[$framework] )
496  && is_array( $this->testModuleNames[$framework] )
497  ) {
498  return $this->testModuleNames[$framework];
499  } else {
500  return [];
501  }
502  }
503 
511  public function isModuleRegistered( $name ) {
512  return isset( $this->moduleInfos[$name] );
513  }
514 
526  public function getModule( $name ) {
527  if ( !isset( $this->modules[$name] ) ) {
528  if ( !isset( $this->moduleInfos[$name] ) ) {
529  // No such module
530  return null;
531  }
532  // Construct the requested object
533  $info = $this->moduleInfos[$name];
535  if ( isset( $info['object'] ) ) {
536  // Object given in info array
537  $object = $info['object'];
538  } else {
539  if ( !isset( $info['class'] ) ) {
540  $class = 'ResourceLoaderFileModule';
541  } else {
542  $class = $info['class'];
543  }
545  $object = new $class( $info );
546  $object->setConfig( $this->getConfig() );
547  $object->setLogger( $this->logger );
548  }
549  $object->setName( $name );
550  $this->modules[$name] = $object;
551  }
552 
553  return $this->modules[$name];
554  }
555 
562  protected function isFileModule( $name ) {
563  if ( !isset( $this->moduleInfos[$name] ) ) {
564  return false;
565  }
566  $info = $this->moduleInfos[$name];
567  if ( isset( $info['object'] ) || isset( $info['class'] ) ) {
568  return false;
569  }
570  return true;
571  }
572 
578  public function getSources() {
579  return $this->sources;
580  }
581 
591  public function getLoadScript( $source ) {
592  if ( !isset( $this->sources[$source] ) ) {
593  throw new MWException( "The $source source was never registered in ResourceLoader." );
594  }
595  return $this->sources[$source];
596  }
597 
603  public static function makeHash( $value ) {
604  $hash = hash( 'fnv132', $value );
605  return Wikimedia\base_convert( $hash, 16, 36, 7 );
606  }
607 
616  public function getCombinedVersion( ResourceLoaderContext $context, array $modules ) {
617  if ( !$modules ) {
618  return '';
619  }
620  $hashes = array_map( function ( $module ) use ( $context ) {
621  return $this->getModule( $module )->getVersionHash( $context );
622  }, $modules );
623  return self::makeHash( implode( $hashes ) );
624  }
625 
631  public function respond( ResourceLoaderContext $context ) {
632  // Buffer output to catch warnings. Normally we'd use ob_clean() on the
633  // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
634  // is used: ob_clean() will clear the GZIP header in that case and it won't come
635  // back for subsequent output, resulting in invalid GZIP. So we have to wrap
636  // the whole thing in our own output buffer to be sure the active buffer
637  // doesn't use ob_gzhandler.
638  // See http://bugs.php.net/bug.php?id=36514
639  ob_start();
640 
641  // Find out which modules are missing and instantiate the others
642  $modules = [];
643  $missing = [];
644  foreach ( $context->getModules() as $name ) {
645  $module = $this->getModule( $name );
646  if ( $module ) {
647  // Do not allow private modules to be loaded from the web.
648  // This is a security issue, see bug 34907.
649  if ( $module->getGroup() === 'private' ) {
650  $this->logger->debug( "Request for private module '$name' denied" );
651  $this->errors[] = "Cannot show private module \"$name\"";
652  continue;
653  }
654  $modules[$name] = $module;
655  } else {
656  $missing[] = $name;
657  }
658  }
659 
660  try {
661  // Preload for getCombinedVersion() and for batch makeModuleResponse()
662  $this->preloadModuleInfo( array_keys( $modules ), $context );
663  } catch ( Exception $e ) {
665  $this->logger->warning( 'Preloading module info failed: {exception}', [
666  'exception' => $e
667  ] );
668  $this->errors[] = self::formatExceptionNoComment( $e );
669  }
670 
671  // Combine versions to propagate cache invalidation
672  $versionHash = '';
673  try {
674  $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
675  } catch ( Exception $e ) {
677  $this->logger->warning( 'Calculating version hash failed: {exception}', [
678  'exception' => $e
679  ] );
680  $this->errors[] = self::formatExceptionNoComment( $e );
681  }
682 
683  // See RFC 2616 § 3.11 Entity Tags
684  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
685  $etag = 'W/"' . $versionHash . '"';
686 
687  // Try the client-side cache first
688  if ( $this->tryRespondNotModified( $context, $etag ) ) {
689  return; // output handled (buffers cleared)
690  }
691 
692  // Use file cache if enabled and available...
693  if ( $this->config->get( 'UseFileCache' ) ) {
694  $fileCache = ResourceFileCache::newFromContext( $context );
695  if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
696  return; // output handled
697  }
698  }
699 
700  // Generate a response
701  $response = $this->makeModuleResponse( $context, $modules, $missing );
702 
703  // Capture any PHP warnings from the output buffer and append them to the
704  // error list if we're in debug mode.
705  if ( $context->getDebug() ) {
706  $warnings = ob_get_contents();
707  if ( strlen( $warnings ) ) {
708  $this->errors[] = $warnings;
709  }
710  }
711 
712  // Save response to file cache unless there are errors
713  if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
714  // Cache single modules and images...and other requests if there are enough hits
715  if ( ResourceFileCache::useFileCache( $context ) ) {
716  if ( $fileCache->isCacheWorthy() ) {
717  $fileCache->saveText( $response );
718  } else {
719  $fileCache->incrMissesRecent( $context->getRequest() );
720  }
721  }
722  }
723 
724  $this->sendResponseHeaders( $context, $etag, (bool)$this->errors );
725 
726  // Remove the output buffer and output the response
727  ob_end_clean();
728 
729  if ( $context->getImageObj() && $this->errors ) {
730  // We can't show both the error messages and the response when it's an image.
731  $response = implode( "\n\n", $this->errors );
732  } elseif ( $this->errors ) {
733  $errorText = implode( "\n\n", $this->errors );
734  $errorResponse = self::makeComment( $errorText );
735  if ( $context->shouldIncludeScripts() ) {
736  $errorResponse .= 'if (window.console && console.error) {'
737  . Xml::encodeJsCall( 'console.error', [ $errorText ] )
738  . "}\n";
739  }
740 
741  // Prepend error info to the response
742  $response = $errorResponse . $response;
743  }
744 
745  $this->errors = [];
746  echo $response;
747 
748  }
749 
760  protected function sendResponseHeaders( ResourceLoaderContext $context, $etag, $errors ) {
761  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
762  // If a version wasn't specified we need a shorter expiry time for updates
763  // to propagate to clients quickly
764  // If there were errors, we also need a shorter expiry time so we can recover quickly
765  if ( is_null( $context->getVersion() ) || $errors ) {
766  $maxage = $rlMaxage['unversioned']['client'];
767  $smaxage = $rlMaxage['unversioned']['server'];
768  // If a version was specified we can use a longer expiry time since changing
769  // version numbers causes cache misses
770  } else {
771  $maxage = $rlMaxage['versioned']['client'];
772  $smaxage = $rlMaxage['versioned']['server'];
773  }
774  if ( $context->getImageObj() ) {
775  // Output different headers if we're outputting textual errors.
776  if ( $errors ) {
777  header( 'Content-Type: text/plain; charset=utf-8' );
778  } else {
779  $context->getImageObj()->sendResponseHeaders( $context );
780  }
781  } elseif ( $context->getOnly() === 'styles' ) {
782  header( 'Content-Type: text/css; charset=utf-8' );
783  header( 'Access-Control-Allow-Origin: *' );
784  } else {
785  header( 'Content-Type: text/javascript; charset=utf-8' );
786  }
787  // See RFC 2616 § 14.19 ETag
788  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
789  header( 'ETag: ' . $etag );
790  if ( $context->getDebug() ) {
791  // Do not cache debug responses
792  header( 'Cache-Control: private, no-cache, must-revalidate' );
793  header( 'Pragma: no-cache' );
794  } else {
795  header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
796  $exp = min( $maxage, $smaxage );
797  header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
798  }
799  }
800 
811  protected function tryRespondNotModified( ResourceLoaderContext $context, $etag ) {
812  // See RFC 2616 § 14.26 If-None-Match
813  // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
814  $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
815  // Never send 304s in debug mode
816  if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
817  // There's another bug in ob_gzhandler (see also the comment at
818  // the top of this function) that causes it to gzip even empty
819  // responses, meaning it's impossible to produce a truly empty
820  // response (because the gzip header is always there). This is
821  // a problem because 304 responses have to be completely empty
822  // per the HTTP spec, and Firefox behaves buggily when they're not.
823  // See also http://bugs.php.net/bug.php?id=51579
824  // To work around this, we tear down all output buffering before
825  // sending the 304.
826  wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
827 
828  HttpStatus::header( 304 );
829 
830  $this->sendResponseHeaders( $context, $etag, false );
831  return true;
832  }
833  return false;
834  }
835 
844  protected function tryRespondFromFileCache(
845  ResourceFileCache $fileCache,
846  ResourceLoaderContext $context,
847  $etag
848  ) {
849  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
850  // Buffer output to catch warnings.
851  ob_start();
852  // Get the maximum age the cache can be
853  $maxage = is_null( $context->getVersion() )
854  ? $rlMaxage['unversioned']['server']
855  : $rlMaxage['versioned']['server'];
856  // Minimum timestamp the cache file must have
857  $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
858  if ( !$good ) {
859  try { // RL always hits the DB on file cache miss...
860  wfGetDB( DB_SLAVE );
861  } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
862  $good = $fileCache->isCacheGood(); // cache existence check
863  }
864  }
865  if ( $good ) {
866  $ts = $fileCache->cacheTimestamp();
867  // Send content type and cache headers
868  $this->sendResponseHeaders( $context, $etag, false );
869  $response = $fileCache->fetchText();
870  // Capture any PHP warnings from the output buffer and append them to the
871  // response in a comment if we're in debug mode.
872  if ( $context->getDebug() ) {
873  $warnings = ob_get_contents();
874  if ( strlen( $warnings ) ) {
875  $response = self::makeComment( $warnings ) . $response;
876  }
877  }
878  // Remove the output buffer and output the response
879  ob_end_clean();
880  echo $response . "\n/* Cached {$ts} */";
881  return true; // cache hit
882  }
883  // Clear buffer
884  ob_end_clean();
885 
886  return false; // cache miss
887  }
888 
897  public static function makeComment( $text ) {
898  $encText = str_replace( '*/', '* /', $text );
899  return "/*\n$encText\n*/\n";
900  }
901 
908  public static function formatException( $e ) {
909  return self::makeComment( self::formatExceptionNoComment( $e ) );
910  }
911 
919  protected static function formatExceptionNoComment( $e ) {
921 
922  if ( !$wgShowExceptionDetails ) {
924  }
925 
927  }
928 
937  public function makeModuleResponse( ResourceLoaderContext $context,
938  array $modules, array $missing = []
939  ) {
940  $out = '';
941  $states = [];
942 
943  if ( !count( $modules ) && !count( $missing ) ) {
944  return <<<MESSAGE
945 /* This file is the Web entry point for MediaWiki's ResourceLoader:
946  <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
947  no modules were requested. Max made me put this here. */
948 MESSAGE;
949  }
950 
951  $image = $context->getImageObj();
952  if ( $image ) {
953  $data = $image->getImageData( $context );
954  if ( $data === false ) {
955  $data = '';
956  $this->errors[] = 'Image generation failed';
957  }
958  return $data;
959  }
960 
961  foreach ( $missing as $name ) {
962  $states[$name] = 'missing';
963  }
964 
965  // Generate output
966  $isRaw = false;
967 
968  $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
969 
970  foreach ( $modules as $name => $module ) {
971  try {
972  $content = $module->getModuleContent( $context );
973  $strContent = '';
974 
975  // Append output
976  switch ( $context->getOnly() ) {
977  case 'scripts':
978  $scripts = $content['scripts'];
979  if ( is_string( $scripts ) ) {
980  // Load scripts raw...
981  $strContent = $scripts;
982  } elseif ( is_array( $scripts ) ) {
983  // ...except when $scripts is an array of URLs
984  $strContent = self::makeLoaderImplementScript( $name, $scripts, [], [], [] );
985  }
986  break;
987  case 'styles':
988  $styles = $content['styles'];
989  // We no longer seperate into media, they are all combined now with
990  // custom media type groups into @media .. {} sections as part of the css string.
991  // Module returns either an empty array or a numerical array with css strings.
992  $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
993  break;
994  default:
995  $strContent = self::makeLoaderImplementScript(
996  $name,
997  isset( $content['scripts'] ) ? $content['scripts'] : '',
998  isset( $content['styles'] ) ? $content['styles'] : [],
999  isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
1000  isset( $content['templates'] ) ? $content['templates'] : []
1001  );
1002  break;
1003  }
1004 
1005  if ( !$context->getDebug() ) {
1006  $strContent = self::filter( $filter, $strContent );
1007  }
1008 
1009  $out .= $strContent;
1010 
1011  } catch ( Exception $e ) {
1013  $this->logger->warning( 'Generating module package failed: {exception}', [
1014  'exception' => $e
1015  ] );
1016  $this->errors[] = self::formatExceptionNoComment( $e );
1017 
1018  // Respond to client with error-state instead of module implementation
1019  $states[$name] = 'error';
1020  unset( $modules[$name] );
1021  }
1022  $isRaw |= $module->isRaw();
1023  }
1024 
1025  // Update module states
1026  if ( $context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw ) {
1027  if ( count( $modules ) && $context->getOnly() === 'scripts' ) {
1028  // Set the state of modules loaded as only scripts to ready as
1029  // they don't have an mw.loader.implement wrapper that sets the state
1030  foreach ( $modules as $name => $module ) {
1031  $states[$name] = 'ready';
1032  }
1033  }
1034 
1035  // Set the state of modules we didn't respond to with mw.loader.implement
1036  if ( count( $states ) ) {
1037  $stateScript = self::makeLoaderStateScript( $states );
1038  if ( !$context->getDebug() ) {
1039  $stateScript = self::filter( 'minify-js', $stateScript );
1040  }
1041  $out .= $stateScript;
1042  }
1043  } else {
1044  if ( count( $states ) ) {
1045  $this->errors[] = 'Problematic modules: ' .
1047  }
1048  }
1049 
1050  return $out;
1051  }
1052 
1059  public function getModulesByMessage( $messageKey ) {
1060  $moduleNames = [];
1061  foreach ( $this->getModuleNames() as $moduleName ) {
1062  $module = $this->getModule( $moduleName );
1063  if ( in_array( $messageKey, $module->getMessages() ) ) {
1064  $moduleNames[] = $moduleName;
1065  }
1066  }
1067  return $moduleNames;
1068  }
1069 
1070  /* Static Methods */
1071 
1087  public static function makeLoaderImplementScript(
1088  $name, $scripts, $styles, $messages, $templates
1089  ) {
1090  if ( is_string( $scripts ) ) {
1091  // Site and user module are a legacy scripts that run in the global scope (no closure).
1092  // Transportation as string instructs mw.loader.implement to use globalEval.
1093  if ( $name === 'site' || $name === 'user' ) {
1094  // Minify manually because the general makeModuleResponse() minification won't be
1095  // effective here due to the script being a string instead of a function. (T107377)
1096  if ( !ResourceLoader::inDebugMode() ) {
1097  $scripts = self::filter( 'minify-js', $scripts );
1098  }
1099  } else {
1100  $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts}\n}" );
1101  }
1102  } elseif ( !is_array( $scripts ) ) {
1103  throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
1104  }
1105  // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
1106  // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1107  // of "{}". Force them to objects.
1108  $module = [
1109  $name,
1110  $scripts,
1111  (object)$styles,
1112  (object)$messages,
1113  (object)$templates,
1114  ];
1115  self::trimArray( $module );
1116 
1117  return Xml::encodeJsCall( 'mw.loader.implement', $module, ResourceLoader::inDebugMode() );
1118  }
1119 
1127  public static function makeMessageSetScript( $messages ) {
1128  return Xml::encodeJsCall(
1129  'mw.messages.set',
1130  [ (object)$messages ],
1132  );
1133  }
1134 
1142  public static function makeCombinedStyles( array $stylePairs ) {
1143  $out = [];
1144  foreach ( $stylePairs as $media => $styles ) {
1145  // ResourceLoaderFileModule::getStyle can return the styles
1146  // as a string or an array of strings. This is to allow separation in
1147  // the front-end.
1148  $styles = (array)$styles;
1149  foreach ( $styles as $style ) {
1150  $style = trim( $style );
1151  // Don't output an empty "@media print { }" block (bug 40498)
1152  if ( $style !== '' ) {
1153  // Transform the media type based on request params and config
1154  // The way that this relies on $wgRequest to propagate request params is slightly evil
1155  $media = OutputPage::transformCssMedia( $media );
1156 
1157  if ( $media === '' || $media == 'all' ) {
1158  $out[] = $style;
1159  } elseif ( is_string( $media ) ) {
1160  $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1161  }
1162  // else: skip
1163  }
1164  }
1165  }
1166  return $out;
1167  }
1168 
1183  public static function makeLoaderStateScript( $name, $state = null ) {
1184  if ( is_array( $name ) ) {
1185  return Xml::encodeJsCall(
1186  'mw.loader.state',
1187  [ $name ],
1189  );
1190  } else {
1191  return Xml::encodeJsCall(
1192  'mw.loader.state',
1193  [ $name, $state ],
1195  );
1196  }
1197  }
1198 
1213  public static function makeCustomLoaderScript( $name, $version, $dependencies,
1214  $group, $source, $script
1215  ) {
1216  $script = str_replace( "\n", "\n\t", trim( $script ) );
1217  return Xml::encodeJsCall(
1218  "( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
1219  [ $name, $version, $dependencies, $group, $source ],
1221  );
1222  }
1223 
1224  private static function isEmptyObject( stdClass $obj ) {
1225  foreach ( $obj as $key => $value ) {
1226  return false;
1227  }
1228  return true;
1229  }
1230 
1243  private static function trimArray( array &$array ) {
1244  $i = count( $array );
1245  while ( $i-- ) {
1246  if ( $array[$i] === null
1247  || $array[$i] === []
1248  || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
1249  || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1250  ) {
1251  unset( $array[$i] );
1252  } else {
1253  break;
1254  }
1255  }
1256  }
1257 
1285  public static function makeLoaderRegisterScript( $name, $version = null,
1286  $dependencies = null, $group = null, $source = null, $skip = null
1287  ) {
1288  if ( is_array( $name ) ) {
1289  // Build module name index
1290  $index = [];
1291  foreach ( $name as $i => &$module ) {
1292  $index[$module[0]] = $i;
1293  }
1294 
1295  // Transform dependency names into indexes when possible, they will be resolved by
1296  // mw.loader.register on the other end
1297  foreach ( $name as &$module ) {
1298  if ( isset( $module[2] ) ) {
1299  foreach ( $module[2] as &$dependency ) {
1300  if ( isset( $index[$dependency] ) ) {
1301  $dependency = $index[$dependency];
1302  }
1303  }
1304  }
1305  }
1306 
1307  array_walk( $name, [ 'self', 'trimArray' ] );
1308 
1309  return Xml::encodeJsCall(
1310  'mw.loader.register',
1311  [ $name ],
1313  );
1314  } else {
1315  $registration = [ $name, $version, $dependencies, $group, $source, $skip ];
1316  self::trimArray( $registration );
1317  return Xml::encodeJsCall(
1318  'mw.loader.register',
1319  $registration,
1321  );
1322  }
1323  }
1324 
1339  public static function makeLoaderSourcesScript( $id, $properties = null ) {
1340  if ( is_array( $id ) ) {
1341  return Xml::encodeJsCall(
1342  'mw.loader.addSource',
1343  [ $id ],
1345  );
1346  } else {
1347  return Xml::encodeJsCall(
1348  'mw.loader.addSource',
1349  [ $id, $properties ],
1351  );
1352  }
1353  }
1354 
1363  public static function makeLoaderConditionalScript( $script ) {
1364  return '(window.RLQ=window.RLQ||[]).push(function(){' .
1365  trim( $script ) . '});';
1366  }
1367 
1377  public static function makeInlineScript( $script ) {
1378  $js = self::makeLoaderConditionalScript( $script );
1379  return new WrappedString(
1380  Html::inlineScript( $js ),
1381  '<script>(window.RLQ=window.RLQ||[]).push(function(){',
1382  '});</script>'
1383  );
1384  }
1385 
1394  public static function makeConfigSetScript( array $configuration, $pretty = null ) {
1395  return Xml::encodeJsCall(
1396  'mw.config.set',
1397  [ $configuration ],
1398  ( $pretty === null ) ? ResourceLoader::inDebugMode() : $pretty
1399  );
1400  }
1401 
1410  public static function makePackedModulesString( $modules ) {
1411  $groups = []; // array( prefix => array( suffixes ) )
1412  foreach ( $modules as $module ) {
1413  $pos = strrpos( $module, '.' );
1414  $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1415  $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1416  $groups[$prefix][] = $suffix;
1417  }
1418 
1419  $arr = [];
1420  foreach ( $groups as $prefix => $suffixes ) {
1421  $p = $prefix === '' ? '' : $prefix . '.';
1422  $arr[] = $p . implode( ',', $suffixes );
1423  }
1424  $str = implode( '|', $arr );
1425  return $str;
1426  }
1427 
1433  public static function inDebugMode() {
1434  if ( self::$debugMode === null ) {
1436  self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
1437  $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
1438  );
1439  }
1440  return self::$debugMode;
1441  }
1442 
1450  public static function clearCache() {
1451  self::$debugMode = null;
1452  }
1453 
1463  public function createLoaderURL( $source, ResourceLoaderContext $context,
1464  $extraQuery = []
1465  ) {
1466  $query = self::createLoaderQuery( $context, $extraQuery );
1467  $script = $this->getLoadScript( $source );
1468 
1469  return wfAppendQuery( $script, $query );
1470  }
1471 
1481  protected static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = [] ) {
1482  return self::makeLoaderQuery(
1483  $context->getModules(),
1484  $context->getLanguage(),
1485  $context->getSkin(),
1486  $context->getUser(),
1487  $context->getVersion(),
1488  $context->getDebug(),
1489  $context->getOnly(),
1490  $context->getRequest()->getBool( 'printable' ),
1491  $context->getRequest()->getBool( 'handheld' ),
1492  $extraQuery
1493  );
1494  }
1495 
1513  public static function makeLoaderQuery( $modules, $lang, $skin, $user = null,
1514  $version = null, $debug = false, $only = null, $printable = false,
1515  $handheld = false, $extraQuery = []
1516  ) {
1517  $query = [
1518  'modules' => self::makePackedModulesString( $modules ),
1519  'lang' => $lang,
1520  'skin' => $skin,
1521  'debug' => $debug ? 'true' : 'false',
1522  ];
1523  if ( $user !== null ) {
1524  $query['user'] = $user;
1525  }
1526  if ( $version !== null ) {
1527  $query['version'] = $version;
1528  }
1529  if ( $only !== null ) {
1530  $query['only'] = $only;
1531  }
1532  if ( $printable ) {
1533  $query['printable'] = 1;
1534  }
1535  if ( $handheld ) {
1536  $query['handheld'] = 1;
1537  }
1538  $query += $extraQuery;
1539 
1540  // Make queries uniform in order
1541  ksort( $query );
1542  return $query;
1543  }
1544 
1554  public static function isValidModuleName( $moduleName ) {
1555  return strcspn( $moduleName, '!,|', 0, 255 ) === strlen( $moduleName );
1556  }
1557 
1567  public function getLessCompiler( $extraVars = [] ) {
1568  // When called from the installer, it is possible that a required PHP extension
1569  // is missing (at least for now; see bug 47564). If this is the case, throw an
1570  // exception (caught by the installer) to prevent a fatal error later on.
1571  if ( !class_exists( 'Less_Parser' ) ) {
1572  throw new MWException( 'MediaWiki requires the less.php parser' );
1573  }
1574 
1575  $parser = new Less_Parser;
1576  $parser->ModifyVars( array_merge( $this->getLessVars(), $extraVars ) );
1577  $parser->SetImportDirs(
1578  array_fill_keys( $this->config->get( 'ResourceLoaderLESSImportPaths' ), '' )
1579  );
1580  $parser->SetOption( 'relativeUrls', false );
1581  $parser->SetCacheDir( $this->config->get( 'CacheDirectory' ) ?: wfTempDir() );
1582 
1583  return $parser;
1584  }
1585 
1592  public function getLessVars() {
1593  if ( !$this->lessVars ) {
1594  $lessVars = $this->config->get( 'ResourceLoaderLESSVars' );
1595  Hooks::run( 'ResourceLoaderGetLessVars', [ &$lessVars ] );
1596  $this->lessVars = $lessVars;
1597  }
1598  return $this->lessVars;
1599  }
1600 }
const TS_RFC2822
RFC 2822 format, for E-mail and HTTP headers.
#define the
table suitable for use with IDatabase::select()
This class generates message blobs for use by ResourceLoader modules.
getModuleNames()
Get a list of module names.
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
array $modules
Module name/ResourceLoaderModule object pairs.
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
static inlineScript($contents)
Output a "<script>" tag with the given contents.
Definition: Html.php:597
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:776
the array() calling protocol came about after MediaWiki 1.4rc1.
null for the local 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:1435
static getLogMessage($e)
Get a message formatting the exception message and its origin.
getSources()
Get the list of sources.
wfScript($script= 'index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
static formatException($e)
Handle exception display.
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
$IP
Definition: WebStart.php:58
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:1980
getModule($name)
Get the ResourceLoaderModule object for a given module name.
static header($code)
Output an HTTP status code header.
Definition: HttpStatus.php:96
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
if(!isset($args[0])) $lang
array $testModuleNames
Associative array mapping framework ids to a list of names of test suite modules like array( 'qunit' ...
$source
$value
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
Definition: WebRequest.php:44
static newFromContext(ResourceLoaderContext $context)
Construct an ResourceFileCache from a context.
isModuleRegistered($name)
Check whether a ResourceLoader module is registered.
$wgShowExceptionDetails
If set to true, uncaught exceptions will print a complete stack trace to output.
this hook is for auditing only $response
Definition: hooks.txt:776
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
getCombinedVersion(ResourceLoaderContext $context, array $modules)
Helper method to get and combine versions of multiple modules.
isCacheGood($timestamp= '')
Check if up to date cache file exists.
magic word & $parser
Definition: hooks.txt:2372
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:25
cacheTimestamp()
Get the last-modified timestamp of the cache file.
static makeHash($value)
tryRespondNotModified(ResourceLoaderContext $context, $etag)
Respond with HTTP 304 Not Modified if appropiate.
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. '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 '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:Associative array mapping language codes to prefixed links of the form"language:title".&$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:1814
__construct(Config $config=null, LoggerInterface $logger=null)
Register core modules and runs registration hooks.
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
preloadModuleInfo(array $moduleNames, ResourceLoaderContext $context)
Load information stored in the database about modules.
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
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 true
Definition: hooks.txt:1816
getLoadScript($source)
Get the URL to the load.php endpoint for the given ResourceLoader source.
MessageBlobStore $blobStore
wfResetOutputBuffers($resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
wfTempDir()
Tries to get the system directory for temporary files.
static getMain()
Static methods.
Interface for configuration instances.
Definition: Config.php:28
static extractBasePaths($options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information. ...
wfAppendQuery($url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
ResourceLoader request result caching in the file system.
array $moduleInfos
Associative array mapping module name to info associative array.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1020
$res
Definition: database.txt:21
setMessageBlobStore(MessageBlobStore $blobStore)
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors)
Send main response headers to the client.
MediaWiki exception.
Definition: MWException.php:26
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
Definition: Xml.php:884
static int $filterCacheVersion
static encode($value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:127
$cache
Definition: mcc.php:33
static applyFilter($filter, $data)
const DB_SLAVE
Definition: Defines.php:46
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
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:1816
tryRespondFromFileCache(ResourceFileCache $fileCache, ResourceLoaderContext $context, $etag)
Send out code for a response from file cache if possible.
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:9
fetchText()
Get the uncompressed text from the cache.
array $errors
Errors accumulated during current respond() call.
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 local account $user
Definition: hooks.txt:242
if the prop value should be in the metadata multi language array can modify can modify 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:1581
static getDefaultInstance()
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
getTestModuleNames($framework= 'all')
Get a list of test module names for one (or all) frameworks.
static minify($s, $statementsOnOwnLine=false, $maxLineLength=1000)
Returns minified JavaScript code.
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path...
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:35
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie...
static makeComment($text)
Generate a CSS or JS comment block.
static filter($filter, $data, array $options=[])
Run JavaScript or CSS data through a filter, caching the filtered result for future calls...
static bool $debugMode
static transformCssMedia($media)
Transform "media" attribute based on request parameters.
static minify($css)
Removes whitespace from CSS data.
Definition: CSSMin.php:453
static getLocalServerInstance($fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1020
static useFileCache(ResourceLoaderContext $context)
Check if an RL request can be cached.
array $sources
E.g.
const CACHE_ANYTHING
Definition: Defines.php:101
setLogger(LoggerInterface $logger)
LoggerInterface $logger
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:776
respond(ResourceLoaderContext $context)
Output a response to a load request, including the content-type header.
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
$messages
getImageObj()
If this is a request for an image, get the ResourceLoaderImage object.
get(ResourceLoader $resourceLoader, $modules, $lang)
$wgResourceLoaderDebug
The default debug mode (on/off) for of ResourceLoader requests.
$version
Definition: parserTests.php:94
addSource($id, $loadUrl=null)
Add a foreign source of modules.
static logException($e)
Log an exception to the exception log (if enabled).
static decode($value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
$debug
Definition: mcc.php:31
static encodeJsCall($name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:682
Dynamic JavaScript and CSS resource loading system.
makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing=[])
Generate code for a response.
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:663
static formatExceptionNoComment($e)
Handle exception display.
Object passed around to modules which contains information about the state of a specific loader reque...
isFileModule($name)
Return whether the definition of a module corresponds to a simple ResourceLoaderFileModule.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310