*/
protected $errors = [];
+ /**
+ * List of extra HTTP response headers provided by loaded modules.
+ *
+ * Populated by makeModuleResponse().
+ *
+ * @var array
+ */
+ protected $extraHeaders = [];
+
/**
* @var MessageBlobStore
*/
* @return string Filtered data, or a comment containing an error message
*/
public static function filter( $filter, $data, array $options = [] ) {
- if ( strpos( $data, ResourceLoader::FILTER_NOMIN ) !== false ) {
+ if ( strpos( $data, self::FILTER_NOMIN ) !== false ) {
return $data;
}
// Register core modules
$this->register( include "$IP/resources/Resources.php" );
- $this->register( include "$IP/resources/ResourcesOOUI.php" );
// Register extension modules
$this->register( $config->get( 'ResourceModules' ) );
}
/**
- * Return whether the definition of a module corresponds to a simple ResourceLoaderFileModule.
+ * Return whether the definition of a module corresponds to a simple ResourceLoaderFileModule
+ * or one of its subclasses.
*
* @param string $name Module name
* @return bool
return false;
}
$info = $this->moduleInfos[$name];
- if ( isset( $info['object'] ) || isset( $info['class'] ) ) {
+ if ( isset( $info['object'] ) ) {
+ return false;
+ }
+ if (
+ isset( $info['class'] ) &&
+ $info['class'] !== 'ResourceLoaderFileModule' &&
+ !is_subclass_of( $info['class'], 'ResourceLoaderFileModule' )
+ ) {
return false;
}
return true;
*
* @since 1.26
* @param ResourceLoaderContext $context
- * @param string[] $modules List of known module names
+ * @param string[] $moduleNames List of known module names
* @return string Hash
*/
public function getCombinedVersion( ResourceLoaderContext $context, array $moduleNames ) {
}
}
- $this->sendResponseHeaders( $context, $etag, (bool)$this->errors );
+ $this->sendResponseHeaders( $context, $etag, (bool)$this->errors, $this->extraHeaders );
// Remove the output buffer and output the response
ob_end_clean();
* @param ResourceLoaderContext $context
* @param string $etag ETag header value
* @param bool $errors Whether there are errors in the response
+ * @param string[] $extra Array of extra HTTP response headers
* @return void
*/
- protected function sendResponseHeaders( ResourceLoaderContext $context, $etag, $errors ) {
+ protected function sendResponseHeaders(
+ ResourceLoaderContext $context, $etag, $errors, array $extra = []
+ ) {
\MediaWiki\HeaderCallback::warnIfHeadersSent();
$rlMaxage = $this->config->get( 'ResourceLoaderMaxage' );
// Use a short cache expiry so that updates propagate to clients quickly, if:
$exp = min( $maxage, $smaxage );
header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
}
+ foreach ( $extra as $header ) {
+ header( $header );
+ }
}
/**
/**
* Generate code for a response.
*
+ * Calling this method also populates the `errors` and `headers` members,
+ * later used by respond().
+ *
* @param ResourceLoaderContext $context Context in which to generate a response
* @param ResourceLoaderModule[] $modules List of module objects keyed by module name
* @param string[] $missing List of requested module names that are unregistered (optional)
$implementKey = $name . '@' . $module->getVersionHash( $context );
$strContent = '';
+ if ( isset( $content['headers'] ) ) {
+ $this->extraHeaders = array_merge( $this->extraHeaders, $content['headers'] );
+ }
+
// Append output
switch ( $context->getOnly() ) {
case 'scripts':
// mw.loader.implement will use globalEval if scripts is a string.
// Minify manually here, because general response minification is
// not effective due it being a string literal, not a function.
- if ( !ResourceLoader::inDebugMode() ) {
+ if ( !self::inDebugMode() ) {
$scripts = self::filter( 'minify-js', $scripts ); // T107377
}
} else {
$strContent = self::filter( $filter, $strContent );
}
- $out .= $strContent;
+ if ( $context->getOnly() === 'scripts' ) {
+ // Use a linebreak between module scripts (T162719)
+ $out .= $this->ensureNewline( $strContent );
+ } else {
+ $out .= $strContent;
+ }
} catch ( Exception $e ) {
$this->outputErrorAndLog( $e, 'Generating module package failed: {exception}' );
if ( !$context->getDebug() ) {
$stateScript = self::filter( 'minify-js', $stateScript );
}
- $out .= $stateScript;
+ // Use a linebreak between module script and state script (T162719)
+ $out = $this->ensureNewline( $out ) . $stateScript;
}
} else {
if ( count( $states ) ) {
$this->errors[] = 'Problematic modules: ' .
- FormatJson::encode( $states, ResourceLoader::inDebugMode() );
+ FormatJson::encode( $states, self::inDebugMode() );
}
}
return $out;
}
+ /**
+ * Ensure the string is either empty or ends in a line break
+ * @param string $str
+ * @return string
+ */
+ private function ensureNewline( $str ) {
+ $end = substr( $str, -1 );
+ if ( $end === false || $end === "\n" ) {
+ return $str;
+ }
+ return $str . "\n";
+ }
+
/**
* Get names of modules that use a certain message.
*
* @param array $templates Keys are name of templates and values are the source of
* the template.
* @throws MWException
- * @return string
+ * @return string JavaScript code
*/
protected static function makeLoaderImplementScript(
$name, $scripts, $styles, $messages, $templates
];
self::trimArray( $module );
- return Xml::encodeJsCall( 'mw.loader.implement', $module, ResourceLoader::inDebugMode() );
+ return Xml::encodeJsCall( 'mw.loader.implement', $module, self::inDebugMode() );
}
/**
*
* @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.
- * @return string
+ * @return string JavaScript code
*/
public static function makeMessageSetScript( $messages ) {
return Xml::encodeJsCall(
'mw.messages.set',
[ (object)$messages ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
}
*
* @param string $name
* @param string $state
- * @return string
+ * @return string JavaScript code
*/
public static function makeLoaderStateScript( $name, $state = null ) {
if ( is_array( $name ) ) {
return Xml::encodeJsCall(
'mw.loader.state',
[ $name ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
} else {
return Xml::encodeJsCall(
'mw.loader.state',
[ $name, $state ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
}
}
* @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
+ * @return string JavaScript code
*/
public static function makeCustomLoaderScript( $name, $version, $dependencies,
$group, $source, $script
return Xml::encodeJsCall(
"( function ( name, version, dependencies, group, source ) {\n\t$script\n} )",
[ $name, $version, $dependencies, $group, $source ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
}
* @param string $group Group which the module is in
* @param string $source Source of the module, or 'local' if not foreign
* @param string $skip Script body of the skip function
- * @return string
+ * @return string JavaScript code
*/
public static function makeLoaderRegisterScript( $name, $version = null,
$dependencies = null, $group = null, $source = null, $skip = null
return Xml::encodeJsCall(
'mw.loader.register',
[ $name ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
} else {
$registration = [ $name, $version, $dependencies, $group, $source, $skip ];
return Xml::encodeJsCall(
'mw.loader.register',
$registration,
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
}
}
*
* @param string $id Source ID
* @param string $loadUrl load.php url
- * @return string
+ * @return string JavaScript code
*/
public static function makeLoaderSourcesScript( $id, $loadUrl = null ) {
if ( is_array( $id ) ) {
return Xml::encodeJsCall(
'mw.loader.addSource',
[ $id ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
} else {
return Xml::encodeJsCall(
'mw.loader.addSource',
[ $id, $loadUrl ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
}
}
*
* @deprecated since 1.25; use makeInlineScript instead
* @param string $script JavaScript code
- * @return string
+ * @return string JavaScript code
*/
public static function makeLoaderConditionalScript( $script ) {
return '(window.RLQ=window.RLQ||[]).push(function(){' .
* the given value.
*
* @param array $configuration List of configuration values keyed by variable name
- * @return string
+ * @return string JavaScript code
*/
public static function makeConfigSetScript( array $configuration ) {
return Xml::encodeJsCall(
'mw.config.set',
[ $configuration ],
- ResourceLoader::inDebugMode()
+ self::inDebugMode()
);
}