Merge "MySQL connect: Parse the Unix domain socket path correctly"
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoader.php
index 6eb9908..9892b15 100644 (file)
@@ -36,66 +36,41 @@ use Wikimedia\WrappedString;
  *    https://www.mediawiki.org/wiki/ResourceLoader
  */
 class ResourceLoader implements LoggerAwareInterface {
-       /** @var int */
-       const CACHE_VERSION = 8;
+       /** @var Config $config */
+       protected $config;
+       /** @var MessageBlobStore */
+       protected $blobStore;
 
-       /** @var bool */
-       protected static $debugMode = null;
+       /** @var LoggerInterface */
+       private $logger;
 
-       /**
-        * Module name/ResourceLoaderModule object pairs
-        * @var array
-        */
+       /** @var ResourceLoaderModule[] Map of (module name => ResourceLoaderModule) */
        protected $modules = [];
-
-       /**
-        * Associative array mapping module name to info associative array
-        * @var array
-        */
+       /** @var array[] Map of (module name => associative info array) */
        protected $moduleInfos = [];
-
-       /** @var Config $config */
-       protected $config;
-
        /**
         * Associative array mapping framework ids to a list of names of test suite modules
         * like [ 'qunit' => [ 'mediawiki.tests.qunit.suites', 'ext.foo.tests', ... ], ... ]
         * @var array
         */
        protected $testModuleNames = [];
+       /** @var string[] List of module names that contain QUnit test suites */
+       protected $testSuiteModuleNames = [];
 
-       /**
-        * E.g. [ 'source-id' => 'http://.../load.php' ]
-        * @var array
-        */
+       /** @var array Map of (source => path); E.g. [ 'source-id' => 'http://.../load.php' ] */
        protected $sources = [];
-
-       /**
-        * Errors accumulated during current respond() call.
-        * @var array
-        */
+       /** @var array Errors accumulated during current respond() call */
        protected $errors = [];
-
-       /**
-        * List of extra HTTP response headers provided by loaded modules.
-        *
-        * Populated by makeModuleResponse().
-        *
-        * @var array
-        */
+       /** @var string[] Extra HTTP response headers from modules loaded in makeModuleResponse() */
        protected $extraHeaders = [];
 
-       /**
-        * @var MessageBlobStore
-        */
-       protected $blobStore;
+       /** @var bool */
+       protected static $debugMode = null;
 
-       /**
-        * @var LoggerInterface
-        */
-       private $logger;
+       /** @var int */
+       const CACHE_VERSION = 8;
 
-       /** @var string JavaScript / CSS pragma to disable minification. **/
+       /** @var string JavaScript / CSS pragma to disable minification. * */
        const FILTER_NOMIN = '/*@nomin*/';
 
        /**
@@ -297,13 +272,13 @@ class ResourceLoader implements LoggerAwareInterface {
        /**
         * Register a module with the ResourceLoader system.
         *
-        * @param mixed $name Name of module as a string or List of name/object pairs as an array
-        * @param array|null $info Module info array. For backwards compatibility with 1.17alpha,
-        *   this may also be a ResourceLoaderModule object. Optional when using
-        *   multiple-registration calling style.
+        * @param string|array[] $name Module name as a string or, array of module info arrays
+        *  keyed by name.
+        * @param array|null $info Module info array. When using the first parameter to register
+        *  multiple modules at once, this parameter is optional.
         * @throws MWException If a duplicate module registration is attempted
         * @throws MWException If a module name contains illegal characters (pipes or commas)
-        * @throws MWException If something other than a ResourceLoaderModule is being registered
+        * @throws InvalidArgumentException If the module info is not an array
         */
        public function register( $name, $info = null ) {
                $moduleSkinStyles = $this->config->get( 'ResourceModuleSkinStyles' );
@@ -320,29 +295,21 @@ class ResourceLoader implements LoggerAwareInterface {
                                );
                        }
 
-                       // Check $name for validity
+                       // Check validity
                        if ( !self::isValidModuleName( $name ) ) {
                                throw new MWException( "ResourceLoader module name '$name' is invalid, "
                                        . "see ResourceLoader::isValidModuleName()" );
                        }
-
-                       // Attach module
-                       if ( $info instanceof ResourceLoaderModule ) {
-                               $this->moduleInfos[$name] = [ 'object' => $info ];
-                               $info->setName( $name );
-                               $this->modules[$name] = $info;
-                       } elseif ( is_array( $info ) ) {
-                               // New calling convention
-                               $this->moduleInfos[$name] = $info;
-                       } else {
-                               throw new MWException(
-                                       'ResourceLoader module info type error for module \'' . $name .
-                                       '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
+                       if ( !is_array( $info ) ) {
+                               throw new InvalidArgumentException(
+                                       'Invalid module info for "' . $name . '": expected array, got ' . gettype( $info )
                                );
                        }
 
-                       // Last-minute changes
+                       // Attach module
+                       $this->moduleInfos[$name] = $info;
 
+                       // Last-minute changes
                        // Apply custom skin-defined styles to existing modules.
                        if ( $this->isFileModule( $name ) ) {
                                foreach ( $moduleSkinStyles as $skinName => $skinStyles ) {
@@ -382,6 +349,7 @@ class ResourceLoader implements LoggerAwareInterface {
 
        /**
         * @internal For use by ServiceWiring only
+        * @codeCoverageIgnore
         */
        public function registerTestModules() {
                global $IP;
@@ -392,39 +360,37 @@ class ResourceLoader implements LoggerAwareInterface {
                                . 'Edit your <code>LocalSettings.php</code> to enable it.' );
                }
 
-               $testModules = [
-                       'qunit' => [],
-               ];
+               // This has a 'qunit' key for compat with the below hook.
+               $testModulesMeta = [ 'qunit' => [] ];
 
                // Get test suites from extensions
                // Avoid PHP 7.1 warning from passing $this by reference
                $rl = $this;
-               Hooks::run( 'ResourceLoaderTestModules', [ &$testModules, &$rl ] );
+               Hooks::run( 'ResourceLoaderTestModules', [ &$testModulesMeta, &$rl ] );
                $extRegistry = ExtensionRegistry::getInstance();
                // In case of conflict, the deprecated hook has precedence.
-               $testModules['qunit'] += $extRegistry->getAttribute( 'QUnitTestModules' );
+               $testModules = $testModulesMeta['qunit'] + $extRegistry->getAttribute( 'QUnitTestModules' );
 
-               // Add the QUnit testrunner as implicit dependency to extension test suites.
-               foreach ( $testModules['qunit'] as &$module ) {
-                       // Shuck any single-module dependency as an array
+               $testSuiteModuleNames = [];
+               foreach ( $testModules as $name => &$module ) {
+                       // Turn any single-module dependency into an array
                        if ( isset( $module['dependencies'] ) && is_string( $module['dependencies'] ) ) {
                                $module['dependencies'] = [ $module['dependencies'] ];
                        }
 
+                       // Ensure the testrunner loads before any test suites
                        $module['dependencies'][] = 'test.mediawiki.qunit.testrunner';
-               }
 
-               // Get core test suites
-               $testModules['qunit'] =
-                       ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules['qunit'];
+                       // Keep track of the test suites to load on SpecialJavaScriptTest
+                       $testSuiteModuleNames[] = $name;
+               }
 
-               foreach ( $testModules as $id => $names ) {
-                       // Register test modules
-                       $this->register( $testModules[$id] );
+               // Core test suites (their names have further precedence).
+               $testModules = ( include "$IP/tests/qunit/QUnitTestResources.php" ) + $testModules;
+               $testSuiteModuleNames[] = 'test.mediawiki.qunit.suites';
 
-                       // Keep track of their names so that they can be loaded together
-                       $this->testModuleNames[$id] = array_keys( $testModules[$id] );
-               }
+               $this->register( $testModules );
+               $this->testSuiteModuleNames = $testSuiteModuleNames;
        }
 
        /**
@@ -478,26 +444,14 @@ class ResourceLoader implements LoggerAwareInterface {
        }
 
        /**
-        * Get a list of test module names for one (or all) frameworks.
-        *
-        * If the given framework id is unknkown, or if the in-object variable is not an array,
-        * then it will return an empty array.
+        * Get a list of module names with QUnit test suites.
         *
-        * @param string $framework Get only the test module names for one
-        *   particular framework (optional)
+        * @internal For use by SpecialJavaScriptTest only
         * @return array
+        * @codeCoverageIgnore
         */
-       public function getTestModuleNames( $framework = 'all' ) {
-               /** @todo api siteinfo prop testmodulenames modulenames */
-               if ( $framework == 'all' ) {
-                       return $this->testModuleNames;
-               } elseif ( isset( $this->testModuleNames[$framework] )
-                       && is_array( $this->testModuleNames[$framework] )
-               ) {
-                       return $this->testModuleNames[$framework];
-               } else {
-                       return [];
-               }
+       public function getTestSuiteModuleNames() {
+               return $this->testSuiteModuleNames;
        }
 
        /**
@@ -528,23 +482,18 @@ class ResourceLoader implements LoggerAwareInterface {
                                // No such module
                                return null;
                        }
-                       // Construct the requested object
+                       // Construct the requested module object
                        $info = $this->moduleInfos[$name];
-                       /** @var ResourceLoaderModule $object */
-                       if ( isset( $info['object'] ) ) {
-                               // Object given in info array
-                               $object = $info['object'];
-                       } elseif ( isset( $info['factory'] ) ) {
+                       if ( isset( $info['factory'] ) ) {
+                               /** @var ResourceLoaderModule $object */
                                $object = call_user_func( $info['factory'], $info );
-                               $object->setConfig( $this->getConfig() );
-                               $object->setLogger( $this->logger );
                        } else {
                                $class = $info['class'] ?? ResourceLoaderFileModule::class;
                                /** @var ResourceLoaderModule $object */
                                $object = new $class( $info );
-                               $object->setConfig( $this->getConfig() );
-                               $object->setLogger( $this->logger );
                        }
+                       $object->setConfig( $this->getConfig() );
+                       $object->setLogger( $this->logger );
                        $object->setName( $name );
                        $this->modules[$name] = $object;
                }
@@ -563,9 +512,6 @@ class ResourceLoader implements LoggerAwareInterface {
                        return false;
                }
                $info = $this->moduleInfos[$name];
-               if ( isset( $info['object'] ) ) {
-                       return false;
-               }
                return !isset( $info['factory'] ) && (
                        // The implied default for 'class' is ResourceLoaderFileModule
                        !isset( $info['class'] ) ||
@@ -755,6 +701,8 @@ class ResourceLoader implements LoggerAwareInterface {
                        if ( $this->tryRespondFromFileCache( $fileCache, $context, $etag ) ) {
                                return; // output handled
                        }
+               } else {
+                       $fileCache = null;
                }
 
                // Generate a response
@@ -769,15 +717,17 @@ class ResourceLoader implements LoggerAwareInterface {
                        }
                }
 
-               // Save response to file cache unless there are errors
-               if ( isset( $fileCache ) && !$this->errors && $missing === [] ) {
-                       // Cache single modules and images...and other requests if there are enough hits
-                       if ( ResourceFileCache::useFileCache( $context ) ) {
-                               if ( $fileCache->isCacheWorthy() ) {
-                                       $fileCache->saveText( $response );
-                               } else {
-                                       $fileCache->incrMissesRecent( $context->getRequest() );
-                               }
+               // Consider saving the response to file cache (unless there are errors).
+               if ( $fileCache &&
+                       !$this->errors &&
+                       $missing === [] &&
+                       ResourceFileCache::useFileCache( $context )
+               ) {
+                       if ( $fileCache->isCacheWorthy() ) {
+                               // There were enough hits, save the response to the cache
+                               $fileCache->saveText( $response );
+                       } else {
+                               $fileCache->incrMissesRecent( $context->getRequest() );
                        }
                }
 
@@ -1259,11 +1209,9 @@ MESSAGE;
         * @return string JavaScript code
         */
        public static function makeMessageSetScript( $messages ) {
-               return Xml::encodeJsCall(
-                       'mw.messages.set',
-                       [ (object)$messages ],
-                       self::inDebugMode()
-               );
+               return 'mw.messages.set('
+                       . self::encodeJsonForScript( (object)$messages )
+                       . ');';
        }
 
        /**
@@ -1347,11 +1295,9 @@ MESSAGE;
                if ( !is_array( $states ) ) {
                        $states = [ $states => $state ];
                }
-               return Xml::encodeJsCall(
-                       'mw.loader.state',
-                       [ $states ],
-                       self::inDebugMode()
-               );
+               return 'mw.loader.state('
+                       . self::encodeJsonForScript( $states )
+                       . ');';
        }
 
        private static function isEmptyObject( stdClass $obj ) {
@@ -1435,11 +1381,9 @@ MESSAGE;
 
                array_walk( $modules, [ self::class, 'trimArray' ] );
 
-               return Xml::encodeJsCall(
-                       'mw.loader.register',
-                       [ $modules ],
-                       self::inDebugMode()
-               );
+               return 'mw.loader.register('
+                       . self::encodeJsonForScript( $modules )
+                       . ');';
        }
 
        /**
@@ -1460,11 +1404,9 @@ MESSAGE;
                if ( !is_array( $sources ) ) {
                        $sources = [ $sources => $loadUrl ];
                }
-               return Xml::encodeJsCall(
-                       'mw.loader.addSource',
-                       [ $sources ],
-                       self::inDebugMode()
-               );
+               return 'mw.loader.addSource('
+                       . self::encodeJsonForScript( $sources )
+                       . ');';
        }
 
        /**