$module = $this->getModule( $row->md_module );
if ( $module ) {
$module->setFileDependencies( $context, ResourceLoaderModule::expandRelativePaths(
- FormatJson::decode( $row->md_deps, true )
+ json_decode( $row->md_deps, true )
) );
$modulesWithDeps[] = $row->md_module;
}
/**
* Add an error to the 'errors' array and log it.
*
- * Should only be called from within respond().
- *
+ * @private For internal use by ResourceLoader and ResourceLoaderStartUpModule.
* @since 1.29
* @param Exception $e
* @param string $msg
* @param array $context
*/
- protected function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
+ public function outputErrorAndLog( Exception $e, $msg, array $context = [] ) {
MWExceptionHandler::logException( $e );
$this->logger->warning(
$msg,
try {
return $this->getModule( $module )->getVersionHash( $context );
} catch ( Exception $e ) {
- // If modules fail to compute a version, do still consider the versions
- // of other modules - don't set an empty string E-Tag for the whole request.
- // See also T152266 and StartupModule::getModuleRegistrations().
+ // If modules fail to compute a version, don't fail the request (T152266)
+ // and still compute versions of other modules.
$this->outputErrorAndLog( $e,
'Calculating version for "{module}" failed: {exception}',
[
$out = $this->ensureNewline( $out ) . $stateScript;
}
} else {
- if ( count( $states ) ) {
- $this->errors[] = 'Problematic modules: ' .
- FormatJson::encode( $states, self::inDebugMode() );
+ if ( $states ) {
+ $this->errors[] = 'Problematic modules: '
+ . self::encodeJsonForScript( $states );
}
}
if ( self::inDebugMode() ) {
$scripts = new XmlJsCode( "function ( $, jQuery, require, module ) {\n{$scripts->value}\n}" );
} else {
- $scripts = new XmlJsCode( 'function($,jQuery,require,module){'. $scripts->value . '}' );
+ $scripts = new XmlJsCode( 'function($,jQuery,require,module){' . $scripts->value . '}' );
}
} elseif ( !is_string( $scripts ) && !is_array( $scripts ) ) {
throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
return $out;
}
+ /**
+ * Wrapper around json_encode that avoids needless escapes,
+ * and pretty-prints in debug mode.
+ *
+ * @internal
+ * @since 1.32
+ * @param bool|string|array $data
+ * @return string JSON
+ */
+ public static function encodeJsonForScript( $data ) {
+ // Keep output as small as possible by disabling needless escape modes
+ // that PHP uses by default.
+ // However, while most module scripts are only served on HTTP responses
+ // for JavaScript, some modules can also be embedded in the HTML as inline
+ // scripts. This, and the fact that we sometimes need to export strings
+ // containing user-generated content and labels that may genuinely contain
+ // a sequences like "</script>", we need to encode either '/' or '<'.
+ // By default PHP escapes '/'. Let's escape '<' instead which is less common
+ // and allows URLs to mostly remain readable.
+ $jsonFlags = JSON_UNESCAPED_SLASHES |
+ JSON_UNESCAPED_UNICODE |
+ JSON_HEX_TAG |
+ JSON_HEX_AMP;
+ if ( self::inDebugMode() ) {
+ $jsonFlags |= JSON_PRETTY_PRINT;
+ }
+ return json_encode( $data, $jsonFlags );
+ }
+
/**
* 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:
);
}
- /**
- * Returns JS code which calls the script given by $script. The script will
- * be called with local variables name, version, dependencies and group,
- * which will have values corresponding to $name, $version, $dependencies
- * and $group as supplied.
- *
- * @param string $name Module name
- * @param string $version Module version hash
- * @param array $dependencies List of module names on which this module depends
- * @param string $group Group which the module is in.
- * @param string $source Source of the module, or 'local' if not foreign.
- * @param string $script JavaScript code
- * @return string JavaScript code
- */
- public static function makeCustomLoaderScript( $name, $version, $dependencies,
- $group, $source, $script
- ) {
- $script = str_replace( "\n", "\n\t", trim( $script ) );
- return Xml::encodeJsCall(
- "( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
- [ $name, $version, $dependencies, $group, $source ],
- self::inDebugMode()
- );
- }
-
private static function isEmptyObject( stdClass $obj ) {
foreach ( $obj as $key => $value ) {
return false;
/**
* Returns JS code which calls mw.loader.register with the given
- * parameters. Has three calling conventions:
- *
- * - ResourceLoader::makeLoaderRegisterScript( $name, $version,
- * $dependencies, $group, $source, $skip
- * ):
- * Register a single module.
+ * parameter.
*
- * - ResourceLoader::makeLoaderRegisterScript( [ $name1, $name2 ] ):
- * Register modules with the given names.
+ * @par Example
+ * @code
*
- * - ResourceLoader::makeLoaderRegisterScript( [
+ * ResourceLoader::makeLoaderRegisterScript( [
* [ $name1, $version1, $dependencies1, $group1, $source1, $skip1 ],
* [ $name2, $version2, $dependencies1, $group2, $source2, $skip2 ],
* ...
* ] ):
- * Registers modules with the given names and parameters.
+ * @endcode
*
- * @param string $name Module name
- * @param string|null $version Module version hash
- * @param array|null $dependencies List of module names on which this module depends
- * @param string|null $group Group which the module is in
- * @param string|null $source Source of the module, or 'local' if not foreign
- * @param string|null $skip Script body of the skip function
+ * @internal
+ * @since 1.32
+ * @param array $modules Array of module registration arrays, each containing
+ * - string: module name
+ * - string: module version
+ * - array|null: List of dependencies (optional)
+ * - string|null: Module group (optional)
+ * - string|null: Name of foreign module source, or 'local' (optional)
+ * - string|null: Script body of a skip function (optional)
* @return string JavaScript code
*/
- public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null, $source = null, $skip = null
- ) {
- if ( is_array( $name ) ) {
+ public static function makeLoaderRegisterScript( 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().
+ $index = [];
+ foreach ( $modules as $i => &$module ) {
// Build module name index
- $index = [];
- foreach ( $name as $i => &$module ) {
- $index[$module[0]] = $i;
- }
-
- // Transform dependency names into indexes when possible, they will be resolved by
- // mw.loader.register on the other end
- foreach ( $name as &$module ) {
- if ( isset( $module[2] ) ) {
- foreach ( $module[2] as &$dependency ) {
- if ( isset( $index[$dependency] ) ) {
- $dependency = $index[$dependency];
- }
+ $index[$module[0]] = $i;
+ }
+ foreach ( $modules as &$module ) {
+ if ( isset( $module[2] ) ) {
+ foreach ( $module[2] as &$dependency ) {
+ if ( isset( $index[$dependency] ) ) {
+ // Replace module name in dependency list with index
+ $dependency = $index[$dependency];
}
}
}
+ }
- array_walk( $name, [ 'self', 'trimArray' ] );
+ array_walk( $modules, [ 'self', 'trimArray' ] );
- return Xml::encodeJsCall(
- 'mw.loader.register',
- [ $name ],
- self::inDebugMode()
- );
- } else {
- $registration = [ $name, $version, $dependencies, $group, $source, $skip ];
- self::trimArray( $registration );
- return Xml::encodeJsCall(
- 'mw.loader.register',
- $registration,
- self::inDebugMode()
- );
- }
+ return Xml::encodeJsCall(
+ 'mw.loader.register',
+ [ $modules ],
+ self::inDebugMode()
+ );
}
/**
* - ResourceLoader::makeLoaderSourcesScript( [ $id1 => $loadUrl, $id2 => $loadUrl, ... ] );
* Register sources with the given IDs and properties.
*
- * @param string $id Source ID
+ * @param string|array $sources Source ID
* @param string|null $loadUrl load.php url
* @return string JavaScript code
*/
- public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
- if ( is_array( $id ) ) {
- return Xml::encodeJsCall(
- 'mw.loader.addSource',
- [ $id ],
- self::inDebugMode()
- );
- } else {
- return Xml::encodeJsCall(
- 'mw.loader.addSource',
- [ $id, $loadUrl ],
- self::inDebugMode()
- );
+ public static function makeLoaderSourcesScript( $sources, $loadUrl = null ) {
+ if ( !is_array( $sources ) ) {
+ $sources = [ $sources => $loadUrl ];
}
+ return Xml::encodeJsCall(
+ 'mw.loader.addSource',
+ [ $sources ],
+ self::inDebugMode()
+ );
}
/**
public static function makeInlineCodeWithModule( $modules, $script ) {
// Adds an array to lazy-created RLQ
return '(window.RLQ=window.RLQ||[]).push(['
- . json_encode( $modules ) . ','
+ . self::encodeJsonForScript( $modules ) . ','
. 'function(){' . trim( $script ) . '}'
. ']);';
}