}
} else {
if ( $states ) {
- // Keep default escaping of slashes (e.g. "</script>") for ResourceLoaderClientHtml.
- $this->errors[] = 'Problematic modules: ' . json_encode( $states, JSON_PRETTY_PRINT );
+ $this->errors[] = 'Problematic modules: '
+ . self::encodeJsonForScript( $states );
}
}
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:
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 ) . '}'
. ']);';
}
$mwLoaderCode .= file_get_contents( "$IP/resources/src/startup/profiler.js" );
}
- // Keep output as small as possible by disabling needless escapes that PHP uses by default.
- // This is not HTML output, only used in a JS response.
- $jsonFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
- if ( ResourceLoader::inDebugMode() ) {
- $jsonFlags |= JSON_PRETTY_PRINT;
- }
-
// Perform replacements for mediawiki.js
$mwLoaderPairs = [
- '$VARS.baseModules' => json_encode( $this->getBaseModules(), $jsonFlags ),
+ '$VARS.baseModules' => ResourceLoader::encodeJsonForScript( $this->getBaseModules() ),
];
$profilerStubs = [
'$CODE.profileExecuteStart();' => 'mw.loader.profiler.onExecuteStart( module );',
// Perform string replacements for startup.js
$pairs = [
- '$VARS.wgLegacyJavaScriptGlobals' => json_encode(
- $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
- $jsonFlags
+ '$VARS.wgLegacyJavaScriptGlobals' => ResourceLoader::encodeJsonForScript(
+ $this->getConfig()->get( 'LegacyJavaScriptGlobals' )
),
- '$VARS.configuration' => json_encode(
- $this->getConfigSettings( $context ),
- $jsonFlags
+ '$VARS.configuration' => ResourceLoader::encodeJsonForScript(
+ $this->getConfigSettings( $context )
),
// Raw JavaScript code (not JSON)
'$CODE.registrations();' => trim( $this->getModuleRegistrations( $context ) ),