MediaWiki REL1_28
ResourceLoader.php
Go to the documentation of this file.
1<?php
25use Psr\Log\LoggerAwareInterface;
26use Psr\Log\LoggerInterface;
27use Psr\Log\NullLogger;
28use WrappedString\WrappedString;
29
36class 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 protected $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 }
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 // Batched version of ResourceLoaderWikiModule::getTitleInfo
145
146 // Prime in-object cache for message blobs for modules with messages
147 $modules = [];
148 foreach ( $moduleNames as $name ) {
149 $module = $this->getModule( $name );
150 if ( $module && $module->getMessages() ) {
151 $modules[$name] = $module;
152 }
153 }
154 $store = $this->getMessageBlobStore();
155 $blobs = $store->getBlobs( $modules, $lang );
156 foreach ( $blobs as $name => $blob ) {
157 $modules[$name]->setMessageBlob( $blob, $lang );
158 }
159 }
160
178 public static function filter( $filter, $data, array $options = [] ) {
179 if ( strpos( $data, ResourceLoader::FILTER_NOMIN ) !== false ) {
180 return $data;
181 }
182
183 if ( isset( $options['cache'] ) && $options['cache'] === false ) {
184 return self::applyFilter( $filter, $data );
185 }
186
187 $stats = RequestContext::getMain()->getStats();
188 $cache = ObjectCache::getLocalServerInstance( CACHE_ANYTHING );
189
190 $key = $cache->makeGlobalKey(
191 'resourceloader',
192 'filter',
193 $filter,
194 self::$filterCacheVersion, md5( $data )
195 );
196
197 $result = $cache->get( $key );
198 if ( $result === false ) {
199 $stats->increment( "resourceloader_cache.$filter.miss" );
200 $result = self::applyFilter( $filter, $data );
201 $cache->set( $key, $result, 24 * 3600 );
202 } else {
203 $stats->increment( "resourceloader_cache.$filter.hit" );
204 }
205 if ( $result === null ) {
206 // Cached failure
207 $result = $data;
208 }
209
210 return $result;
211 }
212
213 private static function applyFilter( $filter, $data ) {
214 $data = trim( $data );
215 if ( $data ) {
216 try {
217 $data = ( $filter === 'minify-css' )
218 ? CSSMin::minify( $data )
220 } catch ( Exception $e ) {
221 MWExceptionHandler::logException( $e );
222 return null;
223 }
224 }
225 return $data;
226 }
227
228 /* Methods */
229
235 public function __construct( Config $config = null, LoggerInterface $logger = null ) {
236 global $IP;
237
238 $this->logger = $logger ?: new NullLogger();
239
240 if ( !$config ) {
241 $this->logger->debug( __METHOD__ . ' was called without providing a Config instance' );
242 $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
243 }
244 $this->config = $config;
245
246 // Add 'local' source first
247 $this->addSource( 'local', $config->get( 'LoadScript' ) );
248
249 // Add other sources
250 $this->addSource( $config->get( 'ResourceLoaderSources' ) );
251
252 // Register core modules
253 $this->register( include "$IP/resources/Resources.php" );
254 $this->register( include "$IP/resources/ResourcesOOUI.php" );
255 // Register extension modules
256 $this->register( $config->get( 'ResourceModules' ) );
257
258 // Avoid PHP 7.1 warning from passing $this by reference
259 $rl = $this;
260 Hooks::run( 'ResourceLoaderRegisterModules', [ &$rl ] );
261
262 if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
263 $this->registerTestModules();
264 }
265
266 $this->setMessageBlobStore( new MessageBlobStore( $this, $this->logger ) );
267 }
268
272 public function getConfig() {
273 return $this->config;
274 }
275
280 public function setLogger( LoggerInterface $logger ) {
281 $this->logger = $logger;
282 }
283
288 public function getLogger() {
289 return $this->logger;
290 }
291
296 public function getMessageBlobStore() {
297 return $this->blobStore;
298 }
299
305 $this->blobStore = $blobStore;
306 }
307
321 public function register( $name, $info = null ) {
322 $moduleSkinStyles = $this->config->get( 'ResourceModuleSkinStyles' );
323
324 // Allow multiple modules to be registered in one call
325 $registrations = is_array( $name ) ? $name : [ $name => $info ];
326 foreach ( $registrations as $name => $info ) {
327 // Warn on duplicate registrations
328 if ( isset( $this->moduleInfos[$name] ) ) {
329 // A module has already been registered by this name
330 $this->logger->warning(
331 'ResourceLoader duplicate registration warning. ' .
332 'Another module has already been registered as ' . $name
333 );
334 }
335
336 // Check $name for validity
337 if ( !self::isValidModuleName( $name ) ) {
338 throw new MWException( "ResourceLoader module name '$name' is invalid, "
339 . "see ResourceLoader::isValidModuleName()" );
340 }
341
342 // Attach module
343 if ( $info instanceof ResourceLoaderModule ) {
344 $this->moduleInfos[$name] = [ 'object' => $info ];
345 $info->setName( $name );
346 $this->modules[$name] = $info;
347 } elseif ( is_array( $info ) ) {
348 // New calling convention
349 $this->moduleInfos[$name] = $info;
350 } else {
351 throw new MWException(
352 'ResourceLoader module info type error for module \'' . $name .
353 '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
354 );
355 }
356
357 // Last-minute changes
358
359 // Apply custom skin-defined styles to existing modules.
360 if ( $this->isFileModule( $name ) ) {
361 foreach ( $moduleSkinStyles as $skinName => $skinStyles ) {
362 // If this module already defines skinStyles for this skin, ignore $wgResourceModuleSkinStyles.
363 if ( isset( $this->moduleInfos[$name]['skinStyles'][$skinName] ) ) {
364 continue;
365 }
366
367 // If $name is preceded with a '+', the defined style files will be added to 'default'
368 // skinStyles, otherwise 'default' will be ignored as it normally would be.
369 if ( isset( $skinStyles[$name] ) ) {
370 $paths = (array)$skinStyles[$name];
371 $styleFiles = [];
372 } elseif ( isset( $skinStyles['+' . $name] ) ) {
373 $paths = (array)$skinStyles['+' . $name];
374 $styleFiles = isset( $this->moduleInfos[$name]['skinStyles']['default'] ) ?
375 (array)$this->moduleInfos[$name]['skinStyles']['default'] :
376 [];
377 } else {
378 continue;
379 }
380
381 // Add new file paths, remapping them to refer to our directories and not use settings
382 // from the module we're modifying, which come from the base definition.
383 list( $localBasePath, $remoteBasePath ) =
385
386 foreach ( $paths as $path ) {
387 $styleFiles[] = new ResourceLoaderFilePath( $path, $localBasePath, $remoteBasePath );
388 }
389
390 $this->moduleInfos[$name]['skinStyles'][$skinName] = $styleFiles;
391 }
392 }
393 }
394
395 }
396
399 public function registerTestModules() {
400 global $IP;
401
402 if ( $this->config->get( 'EnableJavaScriptTest' ) !== true ) {
403 throw new MWException( 'Attempt to register JavaScript test modules '
404 . 'but <code>$wgEnableJavaScriptTest</code> is false. '
405 . 'Edit your <code>LocalSettings.php</code> to enable it.' );
406 }
407
408 // Get core test suites
409 $testModules = [];
410 $testModules['qunit'] = [];
411 // Get other test suites (e.g. from extensions)
412 // Avoid PHP 7.1 warning from passing $this by reference
413 $rl = $this;
414 Hooks::run( 'ResourceLoaderTestModules', [ &$testModules, &$rl ] );
415
416 // Add the testrunner (which configures QUnit) to the dependencies.
417 // Since it must be ready before any of the test suites are executed.
418 foreach ( $testModules['qunit'] as &$module ) {
419 // Make sure all test modules are top-loading so that when QUnit starts
420 // on document-ready, it will run once and finish. If some tests arrive
421 // later (possibly after QUnit has already finished) they will be ignored.
422 $module['position'] = 'top';
423 $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
424 }
425
426 $testModules['qunit'] =
427 ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules['qunit'];
428
429 foreach ( $testModules as $id => $names ) {
430 // Register test modules
431 $this->register( $testModules[$id] );
432
433 // Keep track of their names so that they can be loaded together
434 $this->testModuleNames[$id] = array_keys( $testModules[$id] );
435 }
436
437 }
438
449 public function addSource( $id, $loadUrl = null ) {
450 // Allow multiple sources to be registered in one call
451 if ( is_array( $id ) ) {
452 foreach ( $id as $key => $value ) {
453 $this->addSource( $key, $value );
454 }
455 return;
456 }
457
458 // Disallow duplicates
459 if ( isset( $this->sources[$id] ) ) {
460 throw new MWException(
461 'ResourceLoader duplicate source addition error. ' .
462 'Another source has already been registered as ' . $id
463 );
464 }
465
466 // Pre 1.24 backwards-compatibility
467 if ( is_array( $loadUrl ) ) {
468 if ( !isset( $loadUrl['loadScript'] ) ) {
469 throw new MWException(
470 __METHOD__ . ' was passed an array with no "loadScript" key.'
471 );
472 }
473
474 $loadUrl = $loadUrl['loadScript'];
475 }
476
477 $this->sources[$id] = $loadUrl;
478 }
479
485 public function getModuleNames() {
486 return array_keys( $this->moduleInfos );
487 }
488
499 public function getTestModuleNames( $framework = 'all' ) {
501 if ( $framework == 'all' ) {
502 return $this->testModuleNames;
503 } elseif ( isset( $this->testModuleNames[$framework] )
504 && is_array( $this->testModuleNames[$framework] )
505 ) {
506 return $this->testModuleNames[$framework];
507 } else {
508 return [];
509 }
510 }
511
519 public function isModuleRegistered( $name ) {
520 return isset( $this->moduleInfos[$name] );
521 }
522
534 public function getModule( $name ) {
535 if ( !isset( $this->modules[$name] ) ) {
536 if ( !isset( $this->moduleInfos[$name] ) ) {
537 // No such module
538 return null;
539 }
540 // Construct the requested object
541 $info = $this->moduleInfos[$name];
543 if ( isset( $info['object'] ) ) {
544 // Object given in info array
545 $object = $info['object'];
546 } else {
547 if ( !isset( $info['class'] ) ) {
548 $class = 'ResourceLoaderFileModule';
549 } else {
550 $class = $info['class'];
551 }
553 $object = new $class( $info );
554 $object->setConfig( $this->getConfig() );
555 $object->setLogger( $this->logger );
556 }
557 $object->setName( $name );
558 $this->modules[$name] = $object;
559 }
560
561 return $this->modules[$name];
562 }
563
570 protected function isFileModule( $name ) {
571 if ( !isset( $this->moduleInfos[$name] ) ) {
572 return false;
573 }
574 $info = $this->moduleInfos[$name];
575 if ( isset( $info['object'] ) || isset( $info['class'] ) ) {
576 return false;
577 }
578 return true;
579 }
580
586 public function getSources() {
587 return $this->sources;
588 }
589
599 public function getLoadScript( $source ) {
600 if ( !isset( $this->sources[$source] ) ) {
601 throw new MWException( "The $source source was never registered in ResourceLoader." );
602 }
603 return $this->sources[$source];
604 }
605
611 public static function makeHash( $value ) {
612 $hash = hash( 'fnv132', $value );
613 return Wikimedia\base_convert( $hash, 16, 36, 7 );
614 }
615
624 public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
625 if ( !$moduleNames ) {
626 return '';
627 }
628 $hashes = array_map( function ( $module ) use ( $context ) {
629 return $this->getModule( $module )->getVersionHash( $context );
630 }, $moduleNames );
631 return self::makeHash( implode( '', $hashes ) );
632 }
633
649 // As of MediaWiki 1.28, the server and client use the same algorithm for combining
650 // version hashes. There is no technical reason for this to be same, and for years the
651 // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
652 // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
653 // query parameter), then this method must continue to match the JS one.
654 $moduleNames = [];
655 foreach ( $context->getModules() as $name ) {
656 if ( !$this->getModule( $name ) ) {
657 // If a versioned request contains a missing module, the version is a mismatch
658 // as the client considered a module (and version) we don't have.
659 return '';
660 }
661 $moduleNames[] = $name;
662 }
663 return $this->getCombinedVersion( $context, $moduleNames );
664 }
665
672 // Buffer output to catch warnings. Normally we'd use ob_clean() on the
673 // top-level output buffer to clear warnings, but that breaks when ob_gzhandler
674 // is used: ob_clean() will clear the GZIP header in that case and it won't come
675 // back for subsequent output, resulting in invalid GZIP. So we have to wrap
676 // the whole thing in our own output buffer to be sure the active buffer
677 // doesn't use ob_gzhandler.
678 // See http://bugs.php.net/bug.php?id=36514
679 ob_start();
680
681 // Find out which modules are missing and instantiate the others
682 $modules = [];
683 $missing = [];
684 foreach ( $context->getModules() as $name ) {
685 $module = $this->getModule( $name );
686 if ( $module ) {
687 // Do not allow private modules to be loaded from the web.
688 // This is a security issue, see bug 34907.
689 if ( $module->getGroup() === 'private' ) {
690 $this->logger->debug( "Request for private module '$name' denied" );
691 $this->errors[] = "Cannot show private module \"$name\"";
692 continue;
693 }
694 $modules[$name] = $module;
695 } else {
696 $missing[] = $name;
697 }
698 }
699
700 try {
701 // Preload for getCombinedVersion() and for batch makeModuleResponse()
702 $this->preloadModuleInfo( array_keys( $modules ), $context );
703 } catch ( Exception $e ) {
704 MWExceptionHandler::logException( $e );
705 $this->logger->warning( 'Preloading module info failed: {exception}', [
706 'exception' => $e
707 ] );
708 $this->errors[] = self::formatExceptionNoComment( $e );
709 }
710
711 // Combine versions to propagate cache invalidation
712 $versionHash = '';
713 try {
714 $versionHash = $this->getCombinedVersion( $context, array_keys( $modules ) );
715 } catch ( Exception $e ) {
716 MWExceptionHandler::logException( $e );
717 $this->logger->warning( 'Calculating version hash failed: {exception}', [
718 'exception' => $e
719 ] );
720 $this->errors[] = self::formatExceptionNoComment( $e );
721 }
722
723 // See RFC 2616 § 3.11 Entity Tags
724 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11
725 $etag = 'W/"' . $versionHash . '"';
726
727 // Try the client-side cache first
728 if ( $this->tryRespondNotModified( $context, $etag ) ) {
729 return; // output handled (buffers cleared)
730 }
731
732 // Use file cache if enabled and available...
733 if ( $this->config->get( 'UseFileCache' ) ) {
735 if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
736 return; // output handled
737 }
738 }
739
740 // Generate a response
741 $response = $this->makeModuleResponse( $context, $modules, $missing );
742
743 // Capture any PHP warnings from the output buffer and append them to the
744 // error list if we're in debug mode.
745 if ( $context->getDebug() ) {
746 $warnings = ob_get_contents();
747 if ( strlen( $warnings ) ) {
748 $this->errors[] = $warnings;
749 }
750 }
751
752 // Save response to file cache unless there are errors
753 if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
754 // Cache single modules and images...and other requests if there are enough hits
756 if ( $fileCache->isCacheWorthy() ) {
757 $fileCache->saveText( $response );
758 } else {
759 $fileCache->incrMissesRecent( $context->getRequest() );
760 }
761 }
762 }
763
764 $this->sendResponseHeaders( $context, $etag, (bool)$this->errors );
765
766 // Remove the output buffer and output the response
767 ob_end_clean();
768
769 if ( $context->getImageObj() && $this->errors ) {
770 // We can't show both the error messages and the response when it's an image.
771 $response = implode( "\n\n", $this->errors );
772 } elseif ( $this->errors ) {
773 $errorText = implode( "\n\n", $this->errors );
774 $errorResponse = self::makeComment( $errorText );
775 if ( $context->shouldIncludeScripts() ) {
776 $errorResponse .= 'if (window.console && console.error) {'
777 . Xml::encodeJsCall( 'console.error', [ $errorText ] )
778 . "}\n";
779 }
780
781 // Prepend error info to the response
782 $response = $errorResponse . $response;
783 }
784
785 $this->errors = [];
786 echo $response;
787
788 }
789
800 protected function sendResponseHeaders( ResourceLoaderContext $context, $etag, $errors ) {
801 $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
802 // Use a short cache expiry so that updates propagate to clients quickly, if:
803 // - No version specified (shared resources, e.g. stylesheets)
804 // - There were errors (recover quickly)
805 // - Version mismatch (T117587, T47877)
806 if ( is_null( $context->getVersion() )
807 || $errors
808 || $context->getVersion() !== $this->makeVersionQuery( $context )
809 ) {
810 $maxage = $rlMaxage['unversioned']['client'];
811 $smaxage = $rlMaxage['unversioned']['server'];
812 // If a version was specified we can use a longer expiry time since changing
813 // version numbers causes cache misses
814 } else {
815 $maxage = $rlMaxage['versioned']['client'];
816 $smaxage = $rlMaxage['versioned']['server'];
817 }
818 if ( $context->getImageObj() ) {
819 // Output different headers if we're outputting textual errors.
820 if ( $errors ) {
821 header( 'Content-Type: text/plain; charset=utf-8' );
822 } else {
823 $context->getImageObj()->sendResponseHeaders( $context );
824 }
825 } elseif ( $context->getOnly() === 'styles' ) {
826 header( 'Content-Type: text/css; charset=utf-8' );
827 header( 'Access-Control-Allow-Origin: *' );
828 } else {
829 header( 'Content-Type: text/javascript; charset=utf-8' );
830 }
831 // See RFC 2616 § 14.19 ETag
832 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
833 header( 'ETag: ' . $etag );
834 if ( $context->getDebug() ) {
835 // Do not cache debug responses
836 header( 'Cache-Control: private, no-cache, must-revalidate' );
837 header( 'Pragma: no-cache' );
838 } else {
839 header( "Cache-Control: public, max-age=$maxage, s-maxage=$smaxage" );
840 $exp = min( $maxage, $smaxage );
841 header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
842 }
843 }
844
856 // See RFC 2616 § 14.26 If-None-Match
857 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
858 $clientKeys = $context->getRequest()->getHeader( 'If-None-Match', WebRequest::GETHEADER_LIST );
859 // Never send 304s in debug mode
860 if ( $clientKeys !== false && !$context->getDebug() && in_array( $etag, $clientKeys ) ) {
861 // There's another bug in ob_gzhandler (see also the comment at
862 // the top of this function) that causes it to gzip even empty
863 // responses, meaning it's impossible to produce a truly empty
864 // response (because the gzip header is always there). This is
865 // a problem because 304 responses have to be completely empty
866 // per the HTTP spec, and Firefox behaves buggily when they're not.
867 // See also http://bugs.php.net/bug.php?id=51579
868 // To work around this, we tear down all output buffering before
869 // sending the 304.
870 wfResetOutputBuffers( /* $resetGzipEncoding = */ true );
871
872 HttpStatus::header( 304 );
873
874 $this->sendResponseHeaders( $context, $etag, false );
875 return true;
876 }
877 return false;
878 }
879
888 protected function tryRespondFromFileCache(
889 ResourceFileCache $fileCache,
891 $etag
892 ) {
893 $rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
894 // Buffer output to catch warnings.
895 ob_start();
896 // Get the maximum age the cache can be
897 $maxage = is_null( $context->getVersion() )
898 ? $rlMaxage['unversioned']['server']
899 : $rlMaxage['versioned']['server'];
900 // Minimum timestamp the cache file must have
901 $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
902 if ( !$good ) {
903 try { // RL always hits the DB on file cache miss...
905 } catch ( DBConnectionError $e ) { // ...check if we need to fallback to cache
906 $good = $fileCache->isCacheGood(); // cache existence check
907 }
908 }
909 if ( $good ) {
910 $ts = $fileCache->cacheTimestamp();
911 // Send content type and cache headers
912 $this->sendResponseHeaders( $context, $etag, false );
913 $response = $fileCache->fetchText();
914 // Capture any PHP warnings from the output buffer and append them to the
915 // response in a comment if we're in debug mode.
916 if ( $context->getDebug() ) {
917 $warnings = ob_get_contents();
918 if ( strlen( $warnings ) ) {
919 $response = self::makeComment( $warnings ) . $response;
920 }
921 }
922 // Remove the output buffer and output the response
923 ob_end_clean();
924 echo $response . "\n/* Cached {$ts} */";
925 return true; // cache hit
926 }
927 // Clear buffer
928 ob_end_clean();
929
930 return false; // cache miss
931 }
932
941 public static function makeComment( $text ) {
942 $encText = str_replace( '*/', '* /', $text );
943 return "/*\n$encText\n*/\n";
944 }
945
952 public static function formatException( $e ) {
953 return self::makeComment( self::formatExceptionNoComment( $e ) );
954 }
955
963 protected static function formatExceptionNoComment( $e ) {
965
967 return MWExceptionHandler::getPublicLogMessage( $e );
968 }
969
970 return MWExceptionHandler::getLogMessage( $e );
971 }
972
982 array $modules, array $missing = []
983 ) {
984 $out = '';
985 $states = [];
986
987 if ( !count( $modules ) && !count( $missing ) ) {
988 return <<<MESSAGE
989/* This file is the Web entry point for MediaWiki's ResourceLoader:
990 <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
991 no modules were requested. Max made me put this here. */
992MESSAGE;
993 }
994
995 $image = $context->getImageObj();
996 if ( $image ) {
997 $data = $image->getImageData( $context );
998 if ( $data === false ) {
999 $data = '';
1000 $this->errors[] = 'Image generation failed';
1001 }
1002 return $data;
1003 }
1004
1005 foreach ( $missing as $name ) {
1006 $states[$name] = 'missing';
1007 }
1008
1009 // Generate output
1010 $isRaw = false;
1011
1012 $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
1013
1014 foreach ( $modules as $name => $module ) {
1015 try {
1016 $content = $module->getModuleContent( $context );
1017 $implementKey = $name . '@' . $module->getVersionHash( $context );
1018 $strContent = '';
1019
1020 // Append output
1021 switch ( $context->getOnly() ) {
1022 case 'scripts':
1023 $scripts = $content['scripts'];
1024 if ( is_string( $scripts ) ) {
1025 // Load scripts raw...
1026 $strContent = $scripts;
1027 } elseif ( is_array( $scripts ) ) {
1028 // ...except when $scripts is an array of URLs
1029 $strContent = self::makeLoaderImplementScript( $implementKey, $scripts, [], [], [] );
1030 }
1031 break;
1032 case 'styles':
1033 $styles = $content['styles'];
1034 // We no longer seperate into media, they are all combined now with
1035 // custom media type groups into @media .. {} sections as part of the css string.
1036 // Module returns either an empty array or a numerical array with css strings.
1037 $strContent = isset( $styles['css'] ) ? implode( '', $styles['css'] ) : '';
1038 break;
1039 default:
1040 $scripts = isset( $content['scripts'] ) ? $content['scripts'] : '';
1041 if ( is_string( $scripts ) ) {
1042 if ( $name === 'site' || $name === 'user' ) {
1043 // Legacy scripts that run in the global scope without a closure.
1044 // mw.loader.implement will use globalEval if scripts is a string.
1045 // Minify manually here, because general response minification is
1046 // not effective due it being a string literal, not a function.
1047 if ( !ResourceLoader::inDebugMode() ) {
1048 $scripts = self::filter( 'minify-js', $scripts ); // T107377
1049 }
1050 } else {
1051 $scripts = new XmlJsCode( $scripts );
1052 }
1053 }
1054 $strContent = self::makeLoaderImplementScript(
1055 $implementKey,
1056 $scripts,
1057 isset( $content['styles'] ) ? $content['styles'] : [],
1058 isset( $content['messagesBlob'] ) ? new XmlJsCode( $content['messagesBlob'] ) : [],
1059 isset( $content['templates'] ) ? $content['templates'] : []
1060 );
1061 break;
1062 }
1063
1064 if ( !$context->getDebug() ) {
1065 $strContent = self::filter( $filter, $strContent );
1066 }
1067
1068 $out .= $strContent;
1069
1070 } catch ( Exception $e ) {
1071 MWExceptionHandler::logException( $e );
1072 $this->logger->warning( 'Generating module package failed: {exception}', [
1073 'exception' => $e
1074 ] );
1075 $this->errors[] = self::formatExceptionNoComment( $e );
1076
1077 // Respond to client with error-state instead of module implementation
1078 $states[$name] = 'error';
1079 unset( $modules[$name] );
1080 }
1081 $isRaw |= $module->isRaw();
1082 }
1083
1084 // Update module states
1085 if ( $context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw ) {
1086 if ( count( $modules ) && $context->getOnly() === 'scripts' ) {
1087 // Set the state of modules loaded as only scripts to ready as
1088 // they don't have an mw.loader.implement wrapper that sets the state
1089 foreach ( $modules as $name => $module ) {
1090 $states[$name] = 'ready';
1091 }
1092 }
1093
1094 // Set the state of modules we didn't respond to with mw.loader.implement
1095 if ( count( $states ) ) {
1096 $stateScript = self::makeLoaderStateScript( $states );
1097 if ( !$context->getDebug() ) {
1098 $stateScript = self::filter( 'minify-js', $stateScript );
1099 }
1100 $out .= $stateScript;
1101 }
1102 } else {
1103 if ( count( $states ) ) {
1104 $this->errors[] = 'Problematic modules: ' .
1105 FormatJson::encode( $states, ResourceLoader::inDebugMode() );
1106 }
1107 }
1108
1109 return $out;
1110 }
1111
1118 public function getModulesByMessage( $messageKey ) {
1119 $moduleNames = [];
1120 foreach ( $this->getModuleNames() as $moduleName ) {
1121 $module = $this->getModule( $moduleName );
1122 if ( in_array( $messageKey, $module->getMessages() ) ) {
1123 $moduleNames[] = $moduleName;
1124 }
1125 }
1126 return $moduleNames;
1127 }
1128
1129 /* Static Methods */
1130
1147 protected static function makeLoaderImplementScript(
1148 $name, $scripts, $styles, $messages, $templates
1149 ) {
1150
1151 if ( $scripts instanceof XmlJsCode ) {
1152 $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
1153 } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
1154 throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
1155 }
1156 // mw.loader.implement requires 'styles', 'messages' and 'templates' to be objects (not
1157 // arrays). json_encode considers empty arrays to be numerical and outputs "[]" instead
1158 // of "{}". Force them to objects.
1159 $module = [
1160 $name,
1161 $scripts,
1162 (object)$styles,
1163 (object)$messages,
1164 (object)$templates,
1165 ];
1166 self::trimArray( $module );
1167
1168 return Xml::encodeJsCall( 'mw.loader.implement', $module, ResourceLoader::inDebugMode() );
1169 }
1170
1178 public static function makeMessageSetScript( $messages ) {
1179 return Xml::encodeJsCall(
1180 'mw.messages.set',
1181 [ (object)$messages ],
1183 );
1184 }
1185
1193 public static function makeCombinedStyles( array $stylePairs ) {
1194 $out = [];
1195 foreach ( $stylePairs as $media => $styles ) {
1196 // ResourceLoaderFileModule::getStyle can return the styles
1197 // as a string or an array of strings. This is to allow separation in
1198 // the front-end.
1199 $styles = (array)$styles;
1200 foreach ( $styles as $style ) {
1201 $style = trim( $style );
1202 // Don't output an empty "@media print { }" block (bug 40498)
1203 if ( $style !== '' ) {
1204 // Transform the media type based on request params and config
1205 // The way that this relies on $wgRequest to propagate request params is slightly evil
1206 $media = OutputPage::transformCssMedia( $media );
1207
1208 if ( $media === '' || $media == 'all' ) {
1209 $out[] = $style;
1210 } elseif ( is_string( $media ) ) {
1211 $out[] = "@media $media {\n" . str_replace( "\n", "\n\t", "\t" . $style ) . "}";
1212 }
1213 // else: skip
1214 }
1215 }
1216 }
1217 return $out;
1218 }
1219
1234 public static function makeLoaderStateScript( $name, $state = null ) {
1235 if ( is_array( $name ) ) {
1236 return Xml::encodeJsCall(
1237 'mw.loader.state',
1238 [ $name ],
1240 );
1241 } else {
1242 return Xml::encodeJsCall(
1243 'mw.loader.state',
1244 [ $name, $state ],
1246 );
1247 }
1248 }
1249
1264 public static function makeCustomLoaderScript( $name, $version, $dependencies,
1265 $group, $source, $script
1266 ) {
1267 $script = str_replace( "\n", "\n\t", trim( $script ) );
1268 return Xml::encodeJsCall(
1269 "( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
1270 [ $name, $version, $dependencies, $group, $source ],
1272 );
1274
1275 private static function isEmptyObject( stdClass $obj ) {
1276 foreach ( $obj as $key => $value ) {
1277 return false;
1278 }
1279 return true;
1280 }
1281
1294 private static function trimArray( array &$array ) {
1295 $i = count( $array );
1296 while ( $i-- ) {
1297 if ( $array[$i] === null
1298 || $array[$i] === []
1299 || ( $array[$i] instanceof XmlJsCode && $array[$i]->value === '{}' )
1300 || ( $array[$i] instanceof stdClass && self::isEmptyObject( $array[$i] ) )
1301 ) {
1302 unset( $array[$i] );
1303 } else {
1304 break;
1305 }
1306 }
1307 }
1308
1336 public static function makeLoaderRegisterScript( $name, $version = null,
1337 $dependencies = null, $group = null, $source = null, $skip = null
1338 ) {
1339 if ( is_array( $name ) ) {
1340 // Build module name index
1341 $index = [];
1342 foreach ( $name as $i => &$module ) {
1343 $index[$module[0]] = $i;
1344 }
1345
1346 // Transform dependency names into indexes when possible, they will be resolved by
1347 // mw.loader.register on the other end
1348 foreach ( $name as &$module ) {
1349 if ( isset( $module[2] ) ) {
1350 foreach ( $module[2] as &$dependency ) {
1351 if ( isset( $index[$dependency] ) ) {
1352 $dependency = $index[$dependency];
1353 }
1354 }
1355 }
1356 }
1357
1358 array_walk( $name, [ 'self', 'trimArray' ] );
1359
1360 return Xml::encodeJsCall(
1361 'mw.loader.register',
1362 [ $name ],
1364 );
1365 } else {
1366 $registration = [ $name, $version, $dependencies, $group, $source, $skip ];
1367 self::trimArray( $registration );
1368 return Xml::encodeJsCall(
1369 'mw.loader.register',
1370 $registration,
1372 );
1373 }
1374 }
1375
1390 public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
1391 if ( is_array( $id ) ) {
1392 return Xml::encodeJsCall(
1393 'mw.loader.addSource',
1394 [ $id ],
1396 );
1397 } else {
1398 return Xml::encodeJsCall(
1399 'mw.loader.addSource',
1400 [ $id, $loadUrl ],
1402 );
1403 }
1404 }
1405
1414 public static function makeLoaderConditionalScript( $script ) {
1415 return '(window.RLQ=window.RLQ||[]).push(function(){' .
1416 trim( $script ) . '});';
1417 }
1418
1428 public static function makeInlineScript( $script ) {
1429 $js = self::makeLoaderConditionalScript( $script );
1430 return new WrappedString(
1431 Html::inlineScript( $js ),
1432 '<script>(window.RLQ=window.RLQ||[]).push(function(){',
1433 '});</script>'
1434 );
1435 }
1436
1444 public static function makeConfigSetScript( array $configuration ) {
1445 return Xml::encodeJsCall(
1446 'mw.config.set',
1447 [ $configuration ],
1449 );
1450 }
1451
1460 public static function makePackedModulesString( $modules ) {
1461 $groups = []; // [ prefix => [ suffixes ] ]
1462 foreach ( $modules as $module ) {
1463 $pos = strrpos( $module, '.' );
1464 $prefix = $pos === false ? '' : substr( $module, 0, $pos );
1465 $suffix = $pos === false ? $module : substr( $module, $pos + 1 );
1466 $groups[$prefix][] = $suffix;
1467 }
1468
1469 $arr = [];
1470 foreach ( $groups as $prefix => $suffixes ) {
1471 $p = $prefix === '' ? '' : $prefix . '.';
1472 $arr[] = $p . implode( ',', $suffixes );
1473 }
1474 $str = implode( '|', $arr );
1475 return $str;
1476 }
1477
1483 public static function inDebugMode() {
1484 if ( self::$debugMode === null ) {
1486 self::$debugMode = $wgRequest->getFuzzyBool( 'debug',
1487 $wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug )
1488 );
1489 }
1490 return self::$debugMode;
1491 }
1492
1500 public static function clearCache() {
1501 self::$debugMode = null;
1502 }
1503
1513 public function createLoaderURL( $source, ResourceLoaderContext $context,
1514 $extraQuery = []
1515 ) {
1516 $query = self::createLoaderQuery( $context, $extraQuery );
1517 $script = $this->getLoadScript( $source );
1518
1519 return wfAppendQuery( $script, $query );
1520 }
1521
1531 protected static function createLoaderQuery( ResourceLoaderContext $context, $extraQuery = [] ) {
1532 return self::makeLoaderQuery(
1533 $context->getModules(),
1534 $context->getLanguage(),
1535 $context->getSkin(),
1536 $context->getUser(),
1537 $context->getVersion(),
1538 $context->getDebug(),
1539 $context->getOnly(),
1540 $context->getRequest()->getBool( 'printable' ),
1541 $context->getRequest()->getBool( 'handheld' ),
1542 $extraQuery
1543 );
1544 }
1545
1563 public static function makeLoaderQuery( $modules, $lang, $skin, $user = null,
1564 $version = null, $debug = false, $only = null, $printable = false,
1565 $handheld = false, $extraQuery = []
1566 ) {
1567 $query = [
1568 'modules' => self::makePackedModulesString( $modules ),
1569 'lang' => $lang,
1570 'skin' => $skin,
1571 'debug' => $debug ? 'true' : 'false',
1572 ];
1573 if ( $user !== null ) {
1574 $query['user'] = $user;
1575 }
1576 if ( $version !== null ) {
1577 $query['version'] = $version;
1578 }
1579 if ( $only !== null ) {
1580 $query['only'] = $only;
1581 }
1582 if ( $printable ) {
1583 $query['printable'] = 1;
1584 }
1585 if ( $handheld ) {
1586 $query['handheld'] = 1;
1587 }
1588 $query += $extraQuery;
1589
1590 // Make queries uniform in order
1591 ksort( $query );
1592 return $query;
1593 }
1594
1604 public static function isValidModuleName( $moduleName ) {
1605 return strcspn( $moduleName, '!,|', 0, 255 ) === strlen( $moduleName );
1606 }
1607
1617 public function getLessCompiler( $extraVars = [] ) {
1618 // When called from the installer, it is possible that a required PHP extension
1619 // is missing (at least for now; see bug 47564). If this is the case, throw an
1620 // exception (caught by the installer) to prevent a fatal error later on.
1621 if ( !class_exists( 'Less_Parser' ) ) {
1622 throw new MWException( 'MediaWiki requires the less.php parser' );
1623 }
1624
1625 $parser = new Less_Parser;
1626 $parser->ModifyVars( array_merge( $this->getLessVars(), $extraVars ) );
1627 $parser->SetImportDirs(
1628 array_fill_keys( $this->config->get( 'ResourceLoaderLESSImportPaths' ), '' )
1629 );
1630 $parser->SetOption( 'relativeUrls', false );
1631
1632 return $parser;
1633 }
1634
1641 public function getLessVars() {
1642 if ( !$this->lessVars ) {
1643 $lessVars = $this->config->get( 'ResourceLoaderLESSVars' );
1644 Hooks::run( 'ResourceLoaderGetLessVars', [ &$lessVars ] );
1645 $this->lessVars = $lessVars;
1646 }
1647 return $this->lessVars;
1648 }
1649}
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
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
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
$wgResourceLoaderDebug
The default debug mode (on/off) for of ResourceLoader requests.
$wgShowExceptionDetails
If set to true, uncaught exceptions will print a complete stack trace to output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
$messages
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:664
$IP
Definition WebStart.php:58
static minify( $css)
Removes whitespace from CSS data.
Definition CSSMin.php:453
isCacheGood( $timestamp='')
Check if up to date cache file exists.
fetchText()
Get the uncompressed text from the cache.
cacheTimestamp()
Get the last-modified timestamp of the cache file.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition Hooks.php:131
static header( $code)
Output an HTTP status code header.
static minify( $s, $statementsOnOwnLine=false, $maxLineLength=1000)
Returns minified JavaScript code.
MediaWiki exception.
This class generates message blobs for use by ResourceLoader modules.
get(ResourceLoader $resourceLoader, $modules, $lang)
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
static getMain()
Static methods.
ResourceLoader request result caching in the file system.
static useFileCache(ResourceLoaderContext $context)
Check if an RL request can be cached.
static newFromContext(ResourceLoaderContext $context)
Construct an ResourceFileCache from a context.
Object passed around to modules which contains information about the state of a specific loader reque...
static extractBasePaths( $options=[], $localBasePath=null, $remoteBasePath=null)
Extract a pair of local and remote base paths from module definition information.
An object to represent a path to a JavaScript/CSS file, along with a remote and local base path,...
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
static expandRelativePaths(array $filePaths)
Expand directories relative to $IP.
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Dynamic JavaScript and CSS resource loading system.
__construct(Config $config=null, LoggerInterface $logger=null)
Register core modules and runs registration hooks.
static formatExceptionNoComment( $e)
Handle exception display.
tryRespondFromFileCache(ResourceFileCache $fileCache, ResourceLoaderContext $context, $etag)
Send out code for a response from file cache if possible.
isFileModule( $name)
Return whether the definition of a module corresponds to a simple ResourceLoaderFileModule.
getModuleNames()
Get a list of module names.
setMessageBlobStore(MessageBlobStore $blobStore)
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie,...
makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing=[])
Generate code for a response.
LoggerInterface $logger
setLogger(LoggerInterface $logger)
getTestModuleNames( $framework='all')
Get a list of test module names for one (or all) frameworks.
getLoadScript( $source)
Get the URL to the load.php endpoint for the given ResourceLoader source.
static bool $debugMode
tryRespondNotModified(ResourceLoaderContext $context, $etag)
Respond with HTTP 304 Not Modified if appropiate.
static int $filterCacheVersion
getSources()
Get the list of sources.
addSource( $id, $loadUrl=null)
Add a foreign source of modules.
array $moduleInfos
Associative array mapping module name to info associative array.
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors)
Send main response headers to the client.
array $testModuleNames
Associative array mapping framework ids to a list of names of test suite modules like [ 'qunit' => [ ...
makeVersionQuery(ResourceLoaderContext $context)
Get the expected value of the 'version' query parameter.
getModule( $name)
Get the ResourceLoaderModule object for a given module name.
array $modules
Module name/ResourceLoaderModule object pairs.
isModuleRegistered( $name)
Check whether a ResourceLoader module is registered.
static applyFilter( $filter, $data)
static formatException( $e)
Handle exception display.
MessageBlobStore $blobStore
array $errors
Errors accumulated during current respond() call.
static makeComment( $text)
Generate a CSS or JS comment block.
getCombinedVersion(ResourceLoaderContext $context, array $moduleNames)
Helper method to get and combine versions of multiple modules.
respond(ResourceLoaderContext $context)
Output a response to a load request, including the content-type header.
static makeHash( $value)
static filter( $filter, $data, array $options=[])
Run JavaScript or CSS data through a filter, caching the filtered result for future calls.
preloadModuleInfo(array $moduleNames, ResourceLoaderContext $context)
Load information stored in the database about modules.
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
Definition Xml.php:884
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition Xml.php:682
$res
Definition database.txt:21
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
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition globals.txt:64
const CACHE_ANYTHING
Definition Defines.php:93
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead $parser
Definition hooks.txt:2259
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:1937
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:917
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:1096
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:1094
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:1705
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:886
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:1955
this hook is for auditing only $response
Definition hooks.txt:805
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:1595
returning false will NOT prevent logging $e
Definition hooks.txt:2110
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for configuration instances.
Definition Config.php:28
$context
Definition load.php:50
$debug
Definition mcc.php:31
$cache
Definition mcc.php:33
$source
const DB_REPLICA
Definition defines.php:22
if(!isset( $args[0])) $lang
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition defines.php:11
const TS_RFC2822
RFC 2822 format, for E-mail and HTTP headers.
Definition defines.php:21