MediaWiki REL1_30
ResourceLoader.php
Go to the documentation of this file.
1<?php
26use Psr\Log\LoggerAwareInterface;
27use Psr\Log\LoggerInterface;
28use Psr\Log\NullLogger;
29use WrappedString\WrappedString;
31
38class 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 }
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();
199 $cache = ObjectCache::getLocalServerInstance( CACHE_ANYTHING );
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 )
231 } catch ( Exception $e ) {
232 MWExceptionHandler::logException( $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
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 ) =
394 ResourceLoaderFileModule::extractBasePaths( $skinStyles );
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 = [] ) {
645 MWExceptionHandler::logException( $e );
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
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
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 ) {
845 \MediaWiki\HeaderCallback::warnIfHeadersSent();
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
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...
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 ) {
1015 return MWExceptionHandler::getPublicLogMessage( $e );
1016 }
1017
1018 return MWExceptionHandler::getLogMessage( $e ) .
1019 "\nBacktrace:\n" .
1020 MWExceptionHandler::getRedactedTraceAsString( $e );
1021 }
1022
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. */
1045MESSAGE;
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 );
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}
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
$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:662
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:203
static header( $code)
Output an HTTP status code header.
static minify( $s, $statementsOnOwnLine=false, $maxLineLength=1000)
Returns minified JavaScript code.
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
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.
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...
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 or one of ...
getModuleNames()
Get a list of module names.
setMessageBlobStore(MessageBlobStore $blobStore)
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.
sendResponseHeaders(ResourceLoaderContext $context, $etag, $errors, array $extra=[])
Send main response headers to the client.
static bool $debugMode
array $extraHeaders
List of extra HTTP response headers provided by loaded modules.
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.
outputErrorAndLog(Exception $e, $msg, array $context=[])
Add an error to the 'errors' array and log it.
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 XmlJsCode.php:39
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition Xml.php:677
if(! $regexes) $dbr
Definition cleanup.php:94
$res
Definition database.txt:21
the array() calling protocol came about after MediaWiki 1.4rc1.
namespace being checked & $result
Definition hooks.txt:2293
do that in ParserLimitReportFormat instead $parser
Definition hooks.txt:2572
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:893
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:1971
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:2780
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:862
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
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:1981
this hook is for auditing only $response
Definition hooks.txt:781
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:1610
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:247
returning false will NOT prevent logging $e
Definition hooks.txt:2146
$IP
Definition update.php:3
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:102
$debug
Definition mcc.php:31
$cache
Definition mcc.php:33
$source
const DB_REPLICA
Definition defines.php:25
if(!isset( $args[0])) $lang
$header