MediaWiki  1.30.0
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;
29 use WrappedString\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 
239  /* Methods */
240 
246  public function __construct( Config $config = null, LoggerInterface $logger = null ) {
247  global $IP;
248 
249  $this->logger = $logger ?: new NullLogger();
250 
251  if ( !$config ) {
252  $this->logger->debug( __METHOD__ . ' was called without providing a Config instance' );
253  $config = MediaWikiServices::getInstance()->getMainConfig();
254  }
255  $this->config = $config;
256 
257  // Add 'local' source first
258  $this->addSource( 'local', $config->get( 'LoadScript' ) );
259 
260  // Add other sources
261  $this->addSource( $config->get( 'ResourceLoaderSources' ) );
262 
263  // Register core modules
264  $this->register( include "$IP/resources/Resources.php" );
265  // Register extension modules
266  $this->register( $config->get( 'ResourceModules' ) );
267 
268  // Avoid PHP 7.1 warning from passing $this by reference
269  $rl = $this;
270  Hooks::run( 'ResourceLoaderRegisterModules', [ &$rl ] );
271 
272  if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
273  $this->registerTestModules();
274  }
275 
276  $this->setMessageBlobStore( new MessageBlobStore( $this, $this->logger ) );
277  }
278 
282  public function getConfig() {
283  return $this->config;
284  }
285 
290  public function setLogger( LoggerInterface $logger ) {
291  $this->logger = $logger;
292  }
293 
298  public function getLogger() {
299  return $this->logger;
300  }
301 
306  public function getMessageBlobStore() {
307  return $this->blobStore;
308  }
309 
314  public function setMessageBlobStore( MessageBlobStore $blobStore ) {
315  $this->blobStore = $blobStore;
316  }
317 
331  public function register( $name, $info = null ) {
332  $moduleSkinStyles = $this->config->get( 'ResourceModuleSkinStyles' );
333 
334  // Allow multiple modules to be registered in one call
335  $registrations = is_array( $name ) ? $name : [ $name => $info ];
336  foreach ( $registrations as $name => $info ) {
337  // Warn on duplicate registrations
338  if ( isset( $this->moduleInfos[$name] ) ) {
339  // A module has already been registered by this name
340  $this->logger->warning(
341  'ResourceLoader duplicate registration warning. ' .
342  'Another module has already been registered as ' . $name
343  );
344  }
345 
346  // Check $name for validity
347  if ( !self::isValidModuleName( $name ) ) {
348  throw new MWException( "ResourceLoader module name '$name' is invalid, "
349  . "see ResourceLoader::isValidModuleName()" );
350  }
351 
352  // Attach module
353  if ( $info instanceof ResourceLoaderModule ) {
354  $this->moduleInfos[$name] = [ 'object' => $info ];
355  $info->setName( $name );
356  $this->modules[$name] = $info;
357  } elseif ( is_array( $info ) ) {
358  // New calling convention
359  $this->moduleInfos[$name] = $info;
360  } else {
361  throw new MWException(
362  'ResourceLoader module info type error for module \'' . $name .
363  '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
364  );
365  }
366 
367  // Last-minute changes
368 
369  // Apply custom skin-defined styles to existing modules.
370  if ( $this->isFileModule( $name ) ) {
371  foreach ( $moduleSkinStyles as $skinName => $skinStyles ) {
372  // If this module already defines skinStyles for this skin, ignore $wgResourceModuleSkinStyles.
373  if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
374  continue;
375  }
376 
377  // If $name is preceded with a '+', the defined style files will be added to 'default'
378  // skinStyles, otherwise 'default' will be ignored as it normally would be.
379  if ( isset( $skinStyles[$name] ) ) {
380  $paths = (array)$skinStyles[$name];
381  $styleFiles = [];
382  } elseif ( isset( $skinStyles['+' . $name] ) ) {
383  $paths = (array)$skinStyles['+' . $name];
384  $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
385  (array)$this->moduleInfos[$name]['skinStyles']['default'] :
386  [];
387  } else {
388  continue;
389  }
390 
391  // Add new file paths, remapping them to refer to our directories and not use settings
392  // from the module we're modifying, which come from the base definition.
393  list( $localBasePath, $remoteBasePath ) =
395 
396  foreach ( $paths as $path ) {
397  $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
398  }
399 
400  $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
401  }
402  }
403  }
404  }
405 
406  public function registerTestModules() {
407  global $IP;
408 
409  if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
410  throw new MWException( 'Attempt to register JavaScript test modules '
411  . 'but <code>$wgEnableJavaScriptTest</code> is false. '
412  . 'Edit your <code>LocalSettings.php</code> to enable it.' );
413  }
414 
415  // Get core test suites
416  $testModules = [];
417  $testModules['qunit'] = [];
418  // Get other test suites (e.g. from extensions)
419  // Avoid PHP 7.1 warning from passing $this by reference
420  $rl = $this;
421  Hooks::run( 'ResourceLoaderTestModules', [ &$testModules, &$rl ] );
422 
423  // Add the testrunner (which configures QUnit) to the dependencies.
424  // Since it must be ready before any of the test suites are executed.
425  foreach ( $testModules['qunit'] as &$module ) {
426  // Make sure all test modules are top-loading so that when QUnit starts
427  // on document-ready, it will run once and finish. If some tests arrive
428  // later (possibly after QUnit has already finished) they will be ignored.
429  $module['position'] = 'top';
430  $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
431  }
432 
433  $testModules['qunit'] =
434  ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules['qunit'];
435 
436  foreach ( $testModules as $id => $names ) {
437  // Register test modules
438  $this->register( $testModules[$id] );
439 
440  // Keep track of their names so that they can be loaded together
441  $this->testModuleNames[$id] = array_keys( $testModules[$id] );
442  }
443  }
444 
455  public function addSource( $id, $loadUrl = null ) {
456  // Allow multiple sources to be registered in one call
457  if ( is_array( $id ) ) {
458  foreach ( $id as $key => $value ) {
459  $this->addSource( $key, $value );
460  }
461  return;
462  }
463 
464  // Disallow duplicates
465  if ( isset( $this->sources[$id] ) ) {
466  throw new MWException(
467  'ResourceLoader duplicate source addition error. ' .
468  'Another source has already been registered as ' . $id
469  );
470  }
471 
472  // Pre 1.24 backwards-compatibility
473  if ( is_array( $loadUrl ) ) {
474  if ( !isset( $loadUrl['loadScript'] ) ) {
475  throw new MWException(
476  __METHOD__ . ' was passed an array with no "loadScript" key.'
477  );
478  }
479 
480  $loadUrl = $loadUrl['loadScript'];
481  }
482 
483  $this->sources[$id] = $loadUrl;
484  }
485 
491  public function getModuleNames() {
492  return array_keys( $this->moduleInfos );
493  }
494 
505  public function getTestModuleNames( $framework = 'all' ) {
507  if ( $framework == 'all' ) {
508  return $this->testModuleNames;
509  } elseif ( isset( $this->testModuleNames[$framework] )
510  && is_array( $this->testModuleNames[$framework] )
511  ) {
512  return $this->testModuleNames[$framework];
513  } else {
514  return [];
515  }
516  }
517 
525  public function isModuleRegistered( $name ) {
526  return isset( $this->moduleInfos[$name] );
527  }
528 
540  public function getModule( $name ) {
541  if ( !isset( $this->modules[$name] ) ) {
542  if ( !isset( $this->moduleInfos[$name] ) ) {
543  // No such module
544  return null;
545  }
546  // Construct the requested object
547  $info = $this->moduleInfos[$name];
549  if ( isset( $info['object'] ) ) {
550  // Object given in info array
551  $object = $info['object'];
552  } elseif ( isset( $info['factory'] ) ) {
553  $object = call_user_func( $info['factory'], $info );
554  $object->setConfig( $this->getConfig() );
555  $object->setLogger( $this->logger );
556  } else {
557  if ( !isset( $info['class'] ) ) {
558  $class = 'ResourceLoaderFileModule';
559  } else {
560  $class = $info['class'];
561  }
563  $object = new $class( $info );
564  $object->setConfig( $this->getConfig() );
565  $object->setLogger( $this->logger );
566  }
567  $object->setName( $name );
568  $this->modules[$name] = $object;
569  }
570 
571  return $this->modules[$name];
572  }
573 
581  protected function isFileModule( $name ) {
582  if ( !isset( $this->moduleInfos[$name] ) ) {
583  return false;
584  }
585  $info = $this->moduleInfos[$name];
586  if ( isset( $info['object'] ) ) {
587  return false;
588  }
589  if (
590  isset( $info['class'] ) &&
591  $info['class'] !== 'ResourceLoaderFileModule' &&
592  !is_subclass_of( $info['class'], 'ResourceLoaderFileModule' )
593  ) {
594  return false;
595  }
596  return true;
597  }
598 
604  public function getSources() {
605  return $this->sources;
606  }
607 
617  public function getLoadScript( $source ) {
618  if ( !isset( $this->sources[$source] ) ) {
619  throw new MWException( "The $source source was never registered in ResourceLoader." );
620  }
621  return $this->sources[$source];
622  }
623 
629  public static function makeHash( $value ) {
630  $hash = hash( 'fnv132', $value );
631  return Wikimedia\base_convert( $hash, 16, 36, 7 );
632  }
633 
644  protected function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
646  $this->logger->warning(
647  $msg,
648  $context + [ 'exception' => $e ]
649  );
650  $this->errors[] = self::formatExceptionNoComment( $e );
651  }
652 
661  public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
662  if ( !$moduleNames ) {
663  return '';
664  }
665  $hashes = array_map( function ( $module ) use ( $context ) {
666  try {
667  return $this->getModule( $module )->getVersionHash( $context );
668  } catch ( Exception $e ) {
669  // If modules fail to compute a version, do still consider the versions
670  // of other modules - don't set an empty string E-Tag for the whole request.
671  // See also T152266 and StartupModule::getModuleRegistrations().
672  $this->outputErrorAndLog( $e,
673  'Calculating version for "{module}" failed: {exception}',
674  [
675  'module' => $module,
676  ]
677  );
678  return '';
679  }
680  }, $moduleNames );
681  return self::makeHash( implode( '', $hashes ) );
682  }
683 
698  public function makeVersionQuery( ResourceLoaderContext $context ) {
699  // As of MediaWiki 1.28, the server and client use the same algorithm for combining
700  // version hashes. There is no technical reason for this to be same, and for years the
701  // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
702  // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
703  // query parameter), then this method must continue to match the JS one.
704  $moduleNames = [];
705  foreach ( $context->getModules() as $name ) {
706  if ( !$this->getModule( $name ) ) {
707  // If a versioned request contains a missing module, the version is a mismatch
708  // as the client considered a module (and version) we don't have.
709  return '';
710  }
711  $moduleNames[] = $name;
712  }
713  return $this->getCombinedVersion( $context, $moduleNames );
714  }
715 
721  public function respond( ResourceLoaderContext $context ) {
722  // Buffer output to catch warnings. Normally we'd use ob_clean() on the
723  // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
724  // is used: ob_clean() will clear the GZIP header in that case and it won't come
725  // back for subsequent output, resulting in invalid GZIP. So we have to wrap
726  // the whole thing in our own output buffer to be sure the active buffer
727  // doesn't use ob_gzhandler.
728  // See https://bugs.php.net/bug.php?id=36514
729  ob_start();
730 
731  // Find out which modules are missing and instantiate the others
732  $modules = [];
733  $missing = [];
734  foreach ( $context->getModules() as $name ) {
735  $module = $this->getModule( $name );
736  if ( $module ) {
737  // Do not allow private modules to be loaded from the web.
738  // This is a security issue, see T36907.
739  if ( $module->getGroup() === 'private' ) {
740  $this->logger->debug( "Request for private module '$name' denied" );
741  $this->errors[] = "Cannot show private module \"$name\"";
742  continue;
743  }
744  $modules[$name] = $module;
745  } else {
746  $missing[] = $name;
747  }
748  }
749 
750  try {
751  // Preload for getCombinedVersion() and for batch makeModuleResponse()
752  $this->preloadModuleInfo( array_keys( $modules ), $context );
753  } catch ( Exception $e ) {
754  $this->outputErrorAndLog( $e, 'Preloading module info failed: {exception}' );
755  }
756 
757  // Combine versions to propagate cache invalidation
758  $versionHash = '';
759  try {
760  $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
761  } catch ( Exception $e ) {
762  $this->outputErrorAndLog( $e, 'Calculating version hash failed: {exception}' );
763  }
764 
765  // See RFC 2616 § 3.11 Entity Tags
766  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
767  $etag = 'W/"' . $versionHash . '"';
768 
769  // Try the client-side cache first
770  if ( $this->tryRespondNotModified( $context, $etag ) ) {
771  return; // output handled (buffers cleared)
772  }
773 
774  // Use file cache if enabled and available...
775  if ( $this->config->get( 'UseFileCache' ) ) {
777  if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
778  return; // output handled
779  }
780  }
781 
782  // Generate a response
783  $response = $this->makeModuleResponse( $context, $modules, $missing );
784 
785  // Capture any PHP warnings from the output buffer and append them to the
786  // error list if we're in debug mode.
787  if ( $context->getDebug() ) {
788  $warnings = ob_get_contents();
789  if ( strlen( $warnings ) ) {
790  $this->errors[] = $warnings;
791  }
792  }
793 
794  // Save response to file cache unless there are errors
795  if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
796  // Cache single modules and images...and other requests if there are enough hits
798  if ( $fileCache->isCacheWorthy() ) {
799  $fileCache->saveText( $response );
800  } else {
801  $fileCache->incrMissesRecent( $context->getRequest() );
802  }
803  }
804  }
805 
806  $this->sendResponseHeaders( $context, $etag, (bool)$this->errors, $this->extraHeaders );
807 
808  // Remove the output buffer and output the response
809  ob_end_clean();
810 
811  if ( $context->getImageObj() && $this->errors ) {
812  // We can't show both the error messages and the response when it's an image.
813  $response = implode( "\n\n", $this->errors );
814  } elseif ( $this->errors ) {
815  $errorText = implode( "\n\n", $this->errors );
816  $errorResponse = self::makeComment( $errorText );
817  if ( $context->shouldIncludeScripts() ) {
818  $errorResponse .= 'if (window.console && console.error) {'
819  . Xml::encodeJsCall( 'console.error', [ $errorText ] )
820  . "}\n";
821  }
822 
823  // Prepend error info to the response
824  $response = $errorResponse . $response;
825  }
826 
827  $this->errors = [];
828  echo $response;
829  }
830 
842  protected function sendResponseHeaders(
843  ResourceLoaderContext $context, $etag, $errors, array $extra = []
844  ) {
846  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
847  // Use a short cache expiry so that updates propagate to clients quickly, if:
848  // - No version specified (shared resources, e.g. stylesheets)
849  // - There were errors (recover quickly)
850  // - Version mismatch (T117587, T47877)
851  if ( is_null( $context->getVersion() )
852  || $errors
853  || $context->getVersion() !== $this->makeVersionQuery( $context )
854  ) {
855  $maxage = $rlMaxage['unversioned']['client'];
856  $smaxage = $rlMaxage['unversioned']['server'];
857  // If a version was specified we can use a longer expiry time since changing
858  // version numbers causes cache misses
859  } else {
860  $maxage = $rlMaxage['versioned']['client'];
861  $smaxage = $rlMaxage['versioned']['server'];
862  }
863  if ( $context->getImageObj() ) {
864  // Output different headers if we're outputting textual errors.
865  if ( $errors ) {
866  header( 'Content-Type: text/plain; charset=utf-8' );
867  } else {
868  $context->getImageObj()->sendResponseHeaders( $context );
869  }
870  } elseif ( $context->getOnly() === 'styles' ) {
871  header( 'Content-Type: text/css; charset=utf-8' );
872  header( 'Access-Control-Allow-Origin: *' );
873  } else {
874  header( 'Content-Type: text/javascript; charset=utf-8' );
875  }
876  // See RFC 2616 § 14.19 ETag
877  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
878  header( 'ETag: ' . $etag );
879  if ( $context->getDebug() ) {
880  // Do not cache debug responses
881  header( 'Cache-Control: private, no-cache, must-revalidate' );
882  header( 'Pragma: no-cache' );
883  } else {
884  header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
885  $exp = min( $maxage, $smaxage );
886  header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
887  }
888  foreach ( $extra as $header ) {
889  header( $header );
890  }
891  }
892 
903  protected function tryRespondNotModified( ResourceLoaderContext $context, $etag ) {
904  // See RFC 2616 § 14.26 If-None-Match
905  // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
906  $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
907  // Never send 304s in debug mode
908  if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
909  // There's another bug in ob_gzhandler (see also the comment at
910  // the top of this function) that causes it to gzip even empty
911  // responses, meaning it's impossible to produce a truly empty
912  // response (because the gzip header is always there). This is
913  // a problem because 304 responses have to be completely empty
914  // per the HTTP spec, and Firefox behaves buggily when they're not.
915  // See also https://bugs.php.net/bug.php?id=51579
916  // To work around this, we tear down all output buffering before
917  // sending the 304.
918  wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
919 
920  HttpStatus::header( 304 );
921 
922  $this->sendResponseHeaders( $context, $etag, false );
923  return true;
924  }
925  return false;
926  }
927 
936  protected function tryRespondFromFileCache(
937  ResourceFileCache $fileCache,
939  $etag
940  ) {
941  $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
942  // Buffer output to catch warnings.
943  ob_start();
944  // Get the maximum age the cache can be
945  $maxage = is_null( $context->getVersion() )
946  ? $rlMaxage['unversioned']['server']
947  : $rlMaxage['versioned']['server'];
948  // Minimum timestamp the cache file must have
949  $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
950  if ( !$good ) {
951  try { // RL always hits the DB on file cache miss...
952  wfGetDB( DB_REPLICA );
953  } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
954  $good = $fileCache->isCacheGood(); // cache existence check
955  }
956  }
957  if ( $good ) {
958  $ts = $fileCache->cacheTimestamp();
959  // Send content type and cache headers
960  $this->sendResponseHeaders( $context, $etag, false );
961  $response = $fileCache->fetchText();
962  // Capture any PHP warnings from the output buffer and append them to the
963  // response in a comment if we're in debug mode.
964  if ( $context->getDebug() ) {
965  $warnings = ob_get_contents();
966  if ( strlen( $warnings ) ) {
967  $response = self::makeComment( $warnings ) . $response;
968  }
969  }
970  // Remove the output buffer and output the response
971  ob_end_clean();
972  echo $response . "\n/* Cached {$ts} */";
973  return true; // cache hit
974  }
975  // Clear buffer
976  ob_end_clean();
977 
978  return false; // cache miss
979  }
980 
989  public static function makeComment( $text ) {
990  $encText = str_replace( '*/', '* /', $text );
991  return "/*\n$encText\n*/\n";
992  }
993 
1000  public static function formatException( $e ) {
1001  return self::makeComment( self::formatExceptionNoComment( $e ) );
1002  }
1003 
1011  protected static function formatExceptionNoComment( $e ) {
1013 
1014  if ( !$wgShowExceptionDetails ) {
1016  }
1017 
1019  "\nBacktrace:\n" .
1021  }
1022 
1034  public function makeModuleResponse( ResourceLoaderContext $context,
1035  array $modules, array $missing = []
1036  ) {
1037  $out = '';
1038  $states = [];
1039 
1040  if ( !count( $modules ) && !count( $missing ) ) {
1041  return <<<MESSAGE
1042 /* This file is the Web entry point for MediaWiki's ResourceLoader:
1043  <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
1044  no modules were requested. Max made me put this here. */
1045 MESSAGE;
1046  }
1047 
1048  $image = $context->getImageObj();
1049  if ( $image ) {
1050  $data = $image->getImageData( $context );
1051  if ( $data === false ) {
1052  $data = '';
1053  $this->errors[] = 'Image generation failed';
1054  }
1055  return $data;
1056  }
1057 
1058  foreach ( $missing as $name ) {
1059  $states[$name] = 'missing';
1060  }
1061 
1062  // Generate output
1063  $isRaw = false;
1064 
1065  $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
1066 
1067  foreach ( $modules as $name => $module ) {
1068  try {
1069  $content = $module->getModuleContent( $context );
1070  $implementKey = $name . '@' . $module->getVersionHash( $context );
1071  $strContent = '';
1072 
1073  if ( isset( $content['headers'] ) ) {
1074  $this->extraHeaders = array_merge( $this->extraHeaders, $content['headers'] );
1075  }
1076 
1077  // Append output
1078  switch ( $context->getOnly() ) {
1079  case 'scripts':
1080  $scripts = $content['scripts'];
1081  if ( is_string( $scripts ) ) {
1082  // Load scripts raw...
1083  $strContent = $scripts;
1084  } elseif ( is_array( $scripts ) ) {
1085  // ...except when $scripts is an array of URLs
1086  $strContent = self::makeLoaderImplementScript( $implementKey, $scripts, [], [], [] );
1087  }
1088  break;
1089  case 'styles':
1090  $styles = $content['styles'];
1091  // We no longer seperate into media, they are all combined now with
1092  // custom media type groups into @media .. {} sections as part of the css string.
1093  // Module returns either an empty array or a numerical array with css strings.
1094  $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
1095  break;
1096  default:
1097  $scripts = isset( $content['scripts'] ) ? $content['scripts'] : '';
1098  if ( is_string( $scripts ) ) {
1099  if ( $name === 'site' || $name === 'user' ) {
1100  // Legacy scripts that run in the global scope without a closure.
1101  // mw.loader.implement will use globalEval if scripts is a string.
1102  // Minify manually here, because general response minification is
1103  // not effective due it being a string literal, not a function.
1104  if ( !self::inDebugMode() ) {
1105  $scripts = self::filter( 'minify-js', $scripts ); // T107377
1106  }
1107  } else {
1108  $scripts = new XmlJsCode( $scripts );
1109  }
1110  }
1111  $strContent = self::makeLoaderImplementScript(
1112  $implementKey,
1113  $scripts,
1114  isset( $content['styles'] ) ? $content['styles'] : [],
1115  isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
1116  isset( $content['templates'] ) ? $content['templates'] : []
1117  );
1118  break;
1119  }
1120 
1121  if ( !$context->getDebug() ) {
1122  $strContent = self::filter( $filter, $strContent );
1123  }
1124 
1125  if ( $context->getOnly() === 'scripts' ) {
1126  // Use a linebreak between module scripts (T162719)
1127  $out .= $this->ensureNewline( $strContent );
1128  } else {
1129  $out .= $strContent;
1130  }
1131 
1132  } catch ( Exception $e ) {
1133  $this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
1134 
1135  // Respond to client with error-state instead of module implementation
1136  $states[$name] = 'error';
1137  unset( $modules[$name] );
1138  }
1139  $isRaw |= $module->isRaw();
1140  }
1141 
1142  // Update module states
1143  if ( $context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw ) {
1144  if ( count( $modules ) && $context->getOnly() === 'scripts' ) {
1145  // Set the state of modules loaded as only scripts to ready as
1146  // they don't have an mw.loader.implement wrapper that sets the state
1147  foreach ( $modules as $name => $module ) {
1148  $states[$name] = 'ready';
1149  }
1150  }
1151 
1152  // Set the state of modules we didn't respond to with mw.loader.implement
1153  if ( count( $states ) ) {
1154  $stateScript = self::makeLoaderStateScript( $states );
1155  if ( !$context->getDebug() ) {
1156  $stateScript = self::filter( 'minify-js', $stateScript );
1157  }
1158  // Use a linebreak between module script and state script (T162719)
1159  $out = $this->ensureNewline( $out ) . $stateScript;
1160  }
1161  } else {
1162  if ( count( $states ) ) {
1163  $this->errors[] = 'Problematic modules: ' .
1164  FormatJson::encode( $states, self::inDebugMode() );
1165  }
1166  }
1167 
1168  return $out;
1169  }
1170 
1176  private function ensureNewline( $str ) {
1177  $end = substr( $str, -1 );
1178  if ( $end === false || $end === "\n" ) {
1179  return $str;
1180  }
1181  return $str . "\n";
1182  }
1183 
1190  public function getModulesByMessage( $messageKey ) {
1191  $moduleNames = [];
1192  foreach ( $this->getModuleNames() as $moduleName ) {
1193  $module = $this->getModule( $moduleName );
1194  if ( in_array( $messageKey, $module->getMessages() ) ) {
1195  $moduleNames[] = $moduleName;
1196  }
1197  }
1198  return $moduleNames;
1199  }
1200 
1201  /* Static Methods */
1202 
1219  protected static function makeLoaderImplementScript(
1220  $name, $scripts, $styles, $messages, $templates
1221  ) {
1222  if ( $scripts instanceof XmlJsCode ) {
1223  $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1224  } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1225  throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
1226  }
1227  // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
1228  // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1229  // of "{}". Force them to objects.
1230  $module = [
1231  $name,
1232  $scripts,
1233  (object)$styles,
1234  (object)$messages,
1235  (object)$templates,
1236  ];
1237  self::trimArray( $module );
1238 
1239  return Xml::encodeJsCall( 'mw.loader.implement', $module, self::inDebugMode() );
1240  }
1241 
1249  public static function makeMessageSetScript( $messages ) {
1250  return Xml::encodeJsCall(
1251  'mw.messages.set',
1252  [ (object)$messages ],
1253  self::inDebugMode()
1254  );
1255  }
1256 
1264  public static function makeCombinedStyles( array $stylePairs ) {
1265  $out = [];
1266  foreach ( $stylePairs as $media => $styles ) {
1267  // ResourceLoaderFileModule::getStyle can return the styles
1268  // as a string or an array of strings. This is to allow separation in
1269  // the front-end.
1270  $styles = (array)$styles;
1271  foreach ( $styles as $style ) {
1272  $style = trim( $style );
1273  // Don't output an empty "@media print { }" block (T42498)
1274  if ( $style !== '' ) {
1275  // Transform the media type based on request params and config
1276  // The way that this relies on $wgRequest to propagate request params is slightly evil
1277  $media = OutputPage::transformCssMedia( $media );
1278 
1279  if ( $media === '' || $media == 'all' ) {
1280  $out[] = $style;
1281  } elseif ( is_string( $media ) ) {
1282  $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1283  }
1284  // else: skip
1285  }
1286  }
1287  }
1288  return $out;
1289  }
1290 
1305  public static function makeLoaderStateScript( $name, $state = null ) {
1306  if ( is_array( $name ) ) {
1307  return Xml::encodeJsCall(
1308  'mw.loader.state',
1309  [ $name ],
1310  self::inDebugMode()
1311  );
1312  } else {
1313  return Xml::encodeJsCall(
1314  'mw.loader.state',
1315  [ $name, $state ],
1316  self::inDebugMode()
1317  );
1318  }
1319  }
1320 
1335  public static function makeCustomLoaderScript( $name, $version, $dependencies,
1336  $group, $source, $script
1337  ) {
1338  $script = str_replace( "\n", "\n\t", trim( $script ) );
1339  return Xml::encodeJsCall(
1340  "( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
1341  [ $name, $version, $dependencies, $group, $source ],
1342  self::inDebugMode()
1343  );
1344  }
1345 
1346  private static function isEmptyObject( stdClass $obj ) {
1347  foreach ( $obj as $key => $value ) {
1348  return false;
1349  }
1350  return true;
1351  }
1352 
1365  private static function trimArray( array &$array ) {
1366  $i = count( $array );
1367  while ( $i-- ) {
1368  if ( $array[$i] === null
1369  || $array[$i] === []
1370  || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
1371  || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1372  ) {
1373  unset( $array[$i] );
1374  } else {
1375  break;
1376  }
1377  }
1378  }
1379 
1407  public static function makeLoaderRegisterScript( $name, $version = null,
1408  $dependencies = null, $group = null, $source = null, $skip = null
1409  ) {
1410  if ( is_array( $name ) ) {
1411  // Build module name index
1412  $index = [];
1413  foreach ( $name as $i => &$module ) {
1414  $index[$module[0]] = $i;
1415  }
1416 
1417  // Transform dependency names into indexes when possible, they will be resolved by
1418  // mw.loader.register on the other end
1419  foreach ( $name as &$module ) {
1420  if ( isset( $module[2] ) ) {
1421  foreach ( $module[2] as &$dependency ) {
1422  if ( isset( $index[$dependency] ) ) {
1423  $dependency = $index[$dependency];
1424  }
1425  }
1426  }
1427  }
1428 
1429  array_walk( $name, [ 'self', 'trimArray' ] );
1430 
1431  return Xml::encodeJsCall(
1432  'mw.loader.register',
1433  [ $name ],
1434  self::inDebugMode()
1435  );
1436  } else {
1437  $registration = [ $name, $version, $dependencies, $group, $source, $skip ];
1438  self::trimArray( $registration );
1439  return Xml::encodeJsCall(
1440  'mw.loader.register',
1441  $registration,
1442  self::inDebugMode()
1443  );
1444  }
1445  }
1446 
1461  public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
1462  if ( is_array( $id ) ) {
1463  return Xml::encodeJsCall(
1464  'mw.loader.addSource',
1465  [ $id ],
1466  self::inDebugMode()
1467  );
1468  } else {
1469  return Xml::encodeJsCall(
1470  'mw.loader.addSource',
1471  [ $id, $loadUrl ],
1472  self::inDebugMode()
1473  );
1474  }
1475  }
1476 
1485  public static function makeLoaderConditionalScript( $script ) {
1486  return '(window.RLQ=window.RLQ||[]).push(function(){' .
1487  trim( $script ) . '});';
1488  }
1489 
1499  public static function makeInlineScript( $script ) {
1500  $js = self::makeLoaderConditionalScript( $script );
1501  return new WrappedString(
1502  Html::inlineScript( $js ),
1503  '<script>(window.RLQ=window.RLQ||[]).push(function(){',
1504  '});</script>'
1505  );
1506  }
1507 
1515  public static function makeConfigSetScript( array $configuration ) {
1516  return Xml::encodeJsCall(
1517  'mw.config.set',
1518  [ $configuration ],
1519  self::inDebugMode()
1520  );
1521  }
1522 
1531  public static function makePackedModulesString( $modules ) {
1532  $groups = []; // [ prefix => [ suffixes ] ]
1533  foreach ( $modules as $module ) {
1534  $pos = strrpos( $module, '.' );
1535  $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1536  $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1537  $groups[$prefix][] = $suffix;
1538  }
1539 
1540  $arr = [];
1541  foreach ( $groups as $prefix => $suffixes ) {
1542  $p = $prefix === '' ? '' : $prefix . '.';
1543  $arr[] = $p . implode( ',', $suffixes );
1544  }
1545  $str = implode( '|', $arr );
1546  return $str;
1547  }
1548 
1554  public static function inDebugMode() {
1555  if ( self::$debugMode === null ) {
1557  self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
1558  $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
1559  );
1560  }
1561  return self::$debugMode;
1562  }
1563 
1571  public static function clearCache() {
1572  self::$debugMode = null;
1573  }
1574 
1584  public function createLoaderURL( $source, ResourceLoaderContext $context,
1585  $extraQuery = []
1586  ) {
1587  $query = self::createLoaderQuery( $context, $extraQuery );
1588  $script = $this->getLoadScript( $source );
1589 
1590  return wfAppendQuery( $script, $query );
1591  }
1592 
1602  protected static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = [] ) {
1603  return self::makeLoaderQuery(
1604  $context->getModules(),
1605  $context->getLanguage(),
1606  $context->getSkin(),
1607  $context->getUser(),
1608  $context->getVersion(),
1609  $context->getDebug(),
1610  $context->getOnly(),
1611  $context->getRequest()->getBool( 'printable' ),
1612  $context->getRequest()->getBool( 'handheld' ),
1613  $extraQuery
1614  );
1615  }
1616 
1634  public static function makeLoaderQuery( $modules, $lang, $skin, $user = null,
1635  $version = null, $debug = false, $only = null, $printable = false,
1636  $handheld = false, $extraQuery = []
1637  ) {
1638  $query = [
1639  'modules' => self::makePackedModulesString( $modules ),
1640  'lang' => $lang,
1641  'skin' => $skin,
1642  'debug' => $debug ? 'true' : 'false',
1643  ];
1644  if ( $user !== null ) {
1645  $query['user'] = $user;
1646  }
1647  if ( $version !== null ) {
1648  $query['version'] = $version;
1649  }
1650  if ( $only !== null ) {
1651  $query['only'] = $only;
1652  }
1653  if ( $printable ) {
1654  $query['printable'] = 1;
1655  }
1656  if ( $handheld ) {
1657  $query['handheld'] = 1;
1658  }
1659  $query += $extraQuery;
1660 
1661  // Make queries uniform in order
1662  ksort( $query );
1663  return $query;
1664  }
1665 
1675  public static function isValidModuleName( $moduleName ) {
1676  return strcspn( $moduleName, '!,|', 0, 255 ) === strlen( $moduleName );
1677  }
1678 
1688  public function getLessCompiler( $extraVars = [] ) {
1689  // When called from the installer, it is possible that a required PHP extension
1690  // is missing (at least for now; see T49564). If this is the case, throw an
1691  // exception (caught by the installer) to prevent a fatal error later on.
1692  if ( !class_exists( 'Less_Parser' ) ) {
1693  throw new MWException( 'MediaWiki requires the less.php parser' );
1694  }
1695 
1696  $parser = new Less_Parser;
1697  $parser->ModifyVars( array_merge( $this->getLessVars(), $extraVars ) );
1698  $parser->SetImportDirs(
1699  array_fill_keys( $this->config->get( 'ResourceLoaderLESSImportPaths' ), '' )
1700  );
1701  $parser->SetOption( 'relativeUrls', false );
1702 
1703  return $parser;
1704  }
1705 
1712  public function getLessVars() {
1713  if ( !$this->lessVars ) {
1714  $lessVars = $this->config->get( 'ResourceLoaderLESSVars' );
1715  Hooks::run( 'ResourceLoaderGetLessVars', [ &$lessVars ] );
1716  $this->lessVars = $lessVars;
1717  }
1718  return $this->lessVars;
1719  }
1720 }
ResourceLoaderContext
Object passed around to modules which contains information about the state of a specific loader reque...
Definition: ResourceLoaderContext.php:32
$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:244
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:25
wfResetOutputBuffers
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
Definition: GlobalFunctions.php:1848
errors
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:1730
CSSMin\minify
static minify( $css)
Removes whitespace from CSS data.
Definition: CSSMin.php:530
FileCacheBase\saveText
saveText( $text)
Save and compress text to the cache.
Definition: FileCacheBase.php:159
ResourceFileCache\newFromContext
static newFromContext(ResourceLoaderContext $context)
Construct an ResourceFileCache from a context.
Definition: ResourceFileCache.php:40
$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:2581
FileCacheBase\isCacheGood
isCacheGood( $timestamp='')
Check if up to date cache file exists.
Definition: FileCacheBase.php:117
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
captcha-old.count
count
Definition: captcha-old.py:249
$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. '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: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:1963
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2040
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
ResourceLoaderFilePath
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Definition: ResourceLoaderFilePath.php:28
JavaScriptMinifier\minify
static minify( $s, $statementsOnOwnLine=false, $maxLineLength=1000)
Returns minified JavaScript code.
Definition: JavaScriptMinifier.php:81
$res
$res
Definition: database.txt:21
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
$messages
$messages
Definition: LogTests.i18n.php:8
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:35
OutputPage\transformCssMedia
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
Definition: OutputPage.php:3835
$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:534
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:677
$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:1581
MWExceptionHandler\getPublicLogMessage
static getPublicLogMessage( $e)
Definition: MWExceptionHandler.php:466
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
ResourceLoaderWikiModule\preloadTitleInfo
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Definition: ResourceLoaderWikiModule.php:362
$blob
$blob
Definition: testCompression.php:63
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2856
MWExceptionHandler\getLogMessage
static getLogMessage( $e)
Get a message formatting the exception message and its origin.
Definition: MWExceptionHandler.php:433
$modules
$modules
Definition: HTMLFormElement.php:12
value
$status value
Definition: SyntaxHighlight.class.php:315
$IP
$IP
Definition: update.php:3
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2572
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:310
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
ResourceLoaderModule\expandRelativePaths
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
Definition: ResourceLoaderModule.php:545
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
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
$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:781
or
or
Definition: COPYING.txt:140
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
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2141
$value
$value
Definition: styleTest.css.php:45
$header
$header
Definition: updateCredits.php:35
CACHE_ANYTHING
const CACHE_ANYTHING
Definition: Defines.php:102
$response
this hook is for auditing only $response
Definition: hooks.txt:781
ResourceLoaderModule
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition: ResourceLoaderModule.php:34
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
$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
$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:1965
$path
$path
Definition: NoLocalSettings.php:26
$wgShowExceptionDetails
$wgShowExceptionDetails
If set to true, uncaught exceptions will print a complete stack trace to output.
Definition: DefaultSettings.php:6264
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:9
$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:1965
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:43
MessageBlobStore
This class generates message blobs for use by ResourceLoader modules.
Definition: MessageBlobStore.php:37
$source
$source
Definition: mwdoc-filter.php:46
true
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:1965
$hashes
$hashes
Definition: testCompression.php:64
FileCacheBase\incrMissesRecent
incrMissesRecent(WebRequest $request)
Roughly increments the cache misses in the last hour by unique visitors.
Definition: FileCacheBase.php:231
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:49
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:662
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:23
ResourceFileCache\isCacheWorthy
isCacheWorthy()
Item has many recent cache misses.
Definition: ResourceFileCache.php:107
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:3673
MWExceptionHandler\getRedactedTraceAsString
static getRedactedTraceAsString( $e)
Generate a string representation of an exception's stack trace.
Definition: MWExceptionHandler.php:311
array
the array() calling protocol came about after MediaWiki 1.4rc1.
FileCacheBase\cacheTimestamp
cacheTimestamp()
Get the last-modified timestamp of the cache file.
Definition: FileCacheBase.php:103
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:613
ObjectCache\getLocalServerInstance
static getLocalServerInstance( $fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
Definition: ObjectCache.php:288
$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:781