resourceloader: Hard deprecate ResourceLoader::getLessVars
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoader.php
index 693afcf..54ab6a1 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 /**
- * Base class for resource loading system.
- *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -30,13 +28,21 @@ use Wikimedia\Rdbms\DBConnectionError;
 use Wikimedia\WrappedString;
 
 /**
- * Dynamic JavaScript and CSS resource loading system.
+ * @defgroup ResourceLoader ResourceLoader
+ *
+ * For higher level documentation, see <https://www.mediawiki.org/wiki/ResourceLoader/Architecture>.
+ */
+
+/**
+ * ResourceLoader is a loading system for JavaScript and CSS resources.
+ *
+ * For higher level documentation, see <https://www.mediawiki.org/wiki/ResourceLoader/Architecture>.
  *
- * Most of the documentation is on the MediaWiki documentation wiki starting at:
- *    https://www.mediawiki.org/wiki/ResourceLoader
+ * @ingroup ResourceLoader
+ * @since 1.17
  */
 class ResourceLoader implements LoggerAwareInterface {
-       /** @var Config $config */
+       /** @var Config */
        protected $config;
        /** @var MessageBlobStore */
        protected $blobStore;
@@ -683,24 +689,29 @@ class ResourceLoader implements LoggerAwareInterface {
         *
         * @since 1.28
         * @param ResourceLoaderContext $context
+        * @param string[]|null $modules
         * @return string Hash
         */
-       public function makeVersionQuery( ResourceLoaderContext $context ) {
+       public function makeVersionQuery( ResourceLoaderContext $context, array $modules = null ) {
+               if ( $modules === null ) {
+                       wfDeprecated( __METHOD__ . ' without $modules', '1.34' );
+                       $modules = $context->getModules();
+               }
                // As of MediaWiki 1.28, the server and client use the same algorithm for combining
                // version hashes. There is no technical reason for this to be same, and for years the
                // implementations differed. If getCombinedVersion in PHP (used for StartupModule and
                // E-Tag headers) differs in the future from getCombinedVersion in JS (used for 'version'
                // query parameter), then this method must continue to match the JS one.
-               $moduleNames = [];
-               foreach ( $context->getModules() as $name ) {
+               $filtered = [];
+               foreach ( $modules as $name ) {
                        if ( !$this->getModule( $name ) ) {
                                // If a versioned request contains a missing module, the version is a mismatch
                                // as the client considered a module (and version) we don't have.
                                return '';
                        }
-                       $moduleNames[] = $name;
+                       $filtered[] = $name;
                }
-               return $this->getCombinedVersion( $context, $moduleNames );
+               return $this->getCombinedVersion( $context, $filtered );
        }
 
        /**
@@ -812,9 +823,9 @@ class ResourceLoader implements LoggerAwareInterface {
                        $errorText = implode( "\n\n", $this->errors );
                        $errorResponse = self::makeComment( $errorText );
                        if ( $context->shouldIncludeScripts() ) {
-                               $errorResponse .= 'if (window.console && console.error) {'
-                                       . Xml::encodeJsCall( 'console.error', [ $errorText ] )
-                                       . "}\n";
+                               $errorResponse .= 'if (window.console && console.error) { console.error('
+                                       . $context->encodeJson( $errorText )
+                                       . "); }\n";
                        }
 
                        // Prepend error info to the response
@@ -857,7 +868,7 @@ class ResourceLoader implements LoggerAwareInterface {
                // - Version mismatch (T117587, T47877)
                if ( is_null( $context->getVersion() )
                        || $errors
-                       || $context->getVersion() !== $this->makeVersionQuery( $context )
+                       || $context->getVersion() !== $this->makeVersionQuery( $context, $context->getModules() )
                ) {
                        $maxage = $rlMaxage['unversioned']['client'];
                        $smaxage = $rlMaxage['unversioned']['server'];
@@ -1087,7 +1098,14 @@ MESSAGE;
                                                        $strContent = $scripts;
                                                } elseif ( is_array( $scripts ) ) {
                                                        // ...except when $scripts is an array of URLs or an associative array
-                                                       $strContent = self::makeLoaderImplementScript( $implementKey, $scripts, [], [], [] );
+                                                       $strContent = self::makeLoaderImplementScript(
+                                                               $context,
+                                                               $implementKey,
+                                                               $scripts,
+                                                               [],
+                                                               [],
+                                                               []
+                                                       );
                                                }
                                                break;
                                        case 'styles':
@@ -1113,6 +1131,7 @@ MESSAGE;
                                                        }
                                                }
                                                $strContent = self::makeLoaderImplementScript(
+                                                       $context,
                                                        $implementKey,
                                                        $scripts,
                                                        $content['styles'] ?? [],
@@ -1158,7 +1177,7 @@ MESSAGE;
 
                        // Set the state of modules we didn't respond to with mw.loader.implement
                        if ( $states ) {
-                               $stateScript = self::makeLoaderStateScript( $states );
+                               $stateScript = self::makeLoaderStateScript( $context, $states );
                                if ( !$context->getDebug() ) {
                                        $stateScript = self::filter( 'minify-js', $stateScript );
                                }
@@ -1167,7 +1186,7 @@ MESSAGE;
                        }
                } elseif ( $states ) {
                        $this->errors[] = 'Problematic modules: '
-                               . self::encodeJsonForScript( $states );
+                               . $context->encodeJson( $states );
                }
 
                return $out;
@@ -1206,6 +1225,7 @@ MESSAGE;
        /**
         * Return JS code that calls mw.loader.implement with given module properties.
         *
+        * @param ResourceLoaderContext $context
         * @param string $name Module name or implement key (format "`[name]@[version]`")
         * @param XmlJsCode|array|string $scripts Code as XmlJsCode (to be wrapped in a closure),
         *  list of URLs to JavaScript files, string of JavaScript for `$.globalEval`, or array with
@@ -1220,13 +1240,13 @@ MESSAGE;
         * @throws MWException
         * @return string JavaScript code
         */
-       protected static function makeLoaderImplementScript(
-               $name, $scripts, $styles, $messages, $templates
+       private static function makeLoaderImplementScript(
+               ResourceLoaderContext $context, $name, $scripts, $styles, $messages, $templates
        ) {
                if ( $scripts instanceof XmlJsCode ) {
                        if ( $scripts->value === '' ) {
                                $scripts = null;
-                       } elseif ( self::inDebugMode() ) {
+                       } elseif ( $context->getDebug() ) {
                                $scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
                        } else {
                                $scripts = new XmlJsCode( 'function($,jQuery,require,module){' . $scripts->value . '}' );
@@ -1238,7 +1258,7 @@ MESSAGE;
                                // All of these essentially do $file = $file['content'];, some just have wrapping around it
                                if ( $file['type'] === 'script' ) {
                                        // Multi-file modules only get two parameters ($ and jQuery are being phased out)
-                                       if ( self::inDebugMode() ) {
+                                       if ( $context->getDebug() ) {
                                                $file = new XmlJsCode( "function ( require, module ) {\n{$file['content']}\n}" );
                                        } else {
                                                $file = new XmlJsCode( 'function(require,module){' . $file['content'] . '}' );
@@ -1249,8 +1269,8 @@ MESSAGE;
                        }
                        $scripts = XmlJsCode::encodeObject( [
                                'main' => $scripts['main'],
-                               'files' => XmlJsCode::encodeObject( $files, self::inDebugMode() )
-                       ], self::inDebugMode() );
+                               'files' => XmlJsCode::encodeObject( $files, $context->getDebug() )
+                       ], $context->getDebug() );
                } elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
                        throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
                }
@@ -1267,14 +1287,13 @@ MESSAGE;
                ];
                self::trimArray( $module );
 
-               return Xml::encodeJsCall( 'mw.loader.implement', $module, self::inDebugMode() );
+               return Xml::encodeJsCall( 'mw.loader.implement', $module, $context->getDebug() );
        }
 
        /**
         * Returns JS code which, when called, will register a given list of messages.
         *
-        * @param mixed $messages Either an associative array mapping message key to value, or a
-        *   JSON-encoded message blob containing the same data, wrapped in an XmlJsCode object.
+        * @param mixed $messages Associative array mapping message key to value.
         * @return string JavaScript code
         */
        public static function makeMessageSetScript( $messages ) {
@@ -1324,7 +1343,7 @@ MESSAGE;
         * @internal
         * @since 1.32
         * @param mixed $data
-        * @return string JSON
+        * @return string|false JSON string, false on error
         */
        public static function encodeJsonForScript( $data ) {
                // Keep output as small as possible by disabling needless escape modes
@@ -1347,25 +1366,22 @@ MESSAGE;
        }
 
        /**
-        * Returns a JS call to mw.loader.state, which sets the state of one
-        * ore more modules to a given value. Has two calling conventions:
-        *
-        *    - ResourceLoader::makeLoaderStateScript( $name, $state ):
-        *         Set the state of a single module called $name to $state
+        * Returns a JS call to mw.loader.state, which sets the state of modules
+        * to a given value:
         *
-        *    - ResourceLoader::makeLoaderStateScript( [ $name => $state, ... ] ):
+        *    - ResourceLoader::makeLoaderStateScript( $context, [ $name => $state, ... ] ):
         *         Set the state of modules with the given names to the given states
         *
-        * @param array|string $states
-        * @param string|null $state
+        * @internal
+        * @param ResourceLoaderContext $context
+        * @param array $states
         * @return string JavaScript code
         */
-       public static function makeLoaderStateScript( $states, $state = null ) {
-               if ( !is_array( $states ) ) {
-                       $states = [ $states => $state ];
-               }
+       public static function makeLoaderStateScript(
+               ResourceLoaderContext $context, array $states
+       ) {
                return 'mw.loader.state('
-                       . self::encodeJsonForScript( $states )
+                       . $context->encodeJson( $states )
                        . ');';
        }
 
@@ -1410,15 +1426,15 @@ MESSAGE;
         * @par Example
         * @code
         *
-        *     ResourceLoader::makeLoaderRegisterScript( [
+        *     ResourceLoader::makeLoaderRegisterScript( $context, [
         *        [ $name1, $version1, $dependencies1, $group1, $source1, $skip1 ],
         *        [ $name2, $version2, $dependencies1, $group2, $source2, $skip2 ],
         *        ...
         *     ] ):
         * @endcode
         *
-        * @internal
-        * @since 1.32
+        * @internal For use by ResourceLoaderStartUpModule only
+        * @param ResourceLoaderContext $context
         * @param array $modules Array of module registration arrays, each containing
         *  - string: module name
         *  - string: module version
@@ -1428,7 +1444,9 @@ MESSAGE;
         *  - string|null: Script body of a skip function (optional)
         * @return string JavaScript code
         */
-       public static function makeLoaderRegisterScript( array $modules ) {
+       public static function makeLoaderRegisterScript(
+               ResourceLoaderContext $context, array $modules
+       ) {
                // Optimisation: Transform dependency names into indexes when possible
                // to produce smaller output. They are expanded by mw.loader.register on
                // the other end using resolveIndexedDependencies().
@@ -1451,30 +1469,29 @@ MESSAGE;
                array_walk( $modules, [ self::class, 'trimArray' ] );
 
                return 'mw.loader.register('
-                       . self::encodeJsonForScript( $modules )
+                       . $context->encodeJson( $modules )
                        . ');';
        }
 
        /**
         * Returns JS code which calls mw.loader.addSource() with the given
-        * parameters. Has two calling conventions:
-        *
-        *   - ResourceLoader::makeLoaderSourcesScript( $id, $properties ):
-        *       Register a single source
+        * parameters.
         *
-        *   - ResourceLoader::makeLoaderSourcesScript( [ $id1 => $loadUrl, $id2 => $loadUrl, ... ] );
+        *   - ResourceLoader::makeLoaderSourcesScript( $context,
+        *         [ $id1 => $loadUrl, $id2 => $loadUrl, ... ]
+        *     );
         *       Register sources with the given IDs and properties.
         *
-        * @param string|array $sources Source ID
-        * @param string|null $loadUrl load.php url
+        * @internal For use by ResourceLoaderStartUpModule only
+        * @param ResourceLoaderContext $context
+        * @param array $sources
         * @return string JavaScript code
         */
-       public static function makeLoaderSourcesScript( $sources, $loadUrl = null ) {
-               if ( !is_array( $sources ) ) {
-                       $sources = [ $sources => $loadUrl ];
-               }
+       public static function makeLoaderSourcesScript(
+               ResourceLoaderContext $context, array $sources
+       ) {
                return 'mw.loader.addSource('
-                       . self::encodeJsonForScript( $sources )
+                       . $context->encodeJson( $sources )
                        . ');';
        }
 
@@ -1545,20 +1562,16 @@ MESSAGE;
         * @throws Exception
         */
        public static function makeConfigSetScript( array $configuration ) {
-               $js = Xml::encodeJsCall(
-                       'mw.config.set',
-                       [ $configuration ],
-                       self::inDebugMode()
-               );
-               if ( $js === false ) {
+               $json = self::encodeJsonForScript( $configuration );
+               if ( $json === false ) {
                        $e = new Exception(
                                'JSON serialization of config data failed. ' .
                                'This usually means the config data is not valid UTF-8.'
                        );
                        MWExceptionHandler::logException( $e );
-                       $js = Xml::encodeJsCall( 'mw.log.error', [ $e->__toString() ] );
+                       return 'mw.log.error(' . self::encodeJsonForScript( $e->__toString() ) . ');';
                }
-               return $js;
+               return "mw.config.set($json);";
        }
 
        /**
@@ -1804,10 +1817,11 @@ MESSAGE;
         * Get global LESS variables.
         *
         * @since 1.27
-        * @deprecated since 1.32 Use ResourceLoderModule::getLessVars() instead.
+        * @deprecated since 1.32 Use ResourceLoaderModule::getLessVars() instead.
         * @return array Map of variable names to string CSS values.
         */
        public function getLessVars() {
+               wfDeprecated( __METHOD__, '1.32' );
                return [];
        }
 }