class ResourceLoader {
/* Protected Static Members */
- protected static $filterCacheVersion = 4;
+ protected static $filterCacheVersion = 5;
+ protected static $requiredSourceProperties = array( 'loadScript' );
/** Array: List of module name/ResourceLoaderModule object pairs */
protected $modules = array();
+
/** Associative array mapping module name to info associative array */
protected $moduleInfos = array();
+ /** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/
+ protected $sources = array();
+
/* Protected Methods */
/**
* Registers core modules and runs registration hooks.
*/
public function __construct() {
- global $IP, $wgResourceModules;
+ global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript;
wfProfileIn( __METHOD__ );
+ // Add 'local' source first
+ $this->addSource( 'local', array( 'loadScript' => $wgLoadScript ) );
+
+ // Add other sources
+ $this->addSource( $wgResourceLoaderSources );
+
// Register core modules
$this->register( include( "$IP/resources/Resources.php" ) );
// Register extension modules
wfProfileOut( __METHOD__ );
}
- /**
+ /**
+ * Add a foreign source of modules.
+ *
+ * Source properties:
+ * 'loadScript': URL (either fully-qualified or protocol-relative) of load.php for this source
+ *
+ * @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... )
+ * @param $properties Array: source properties
+ */
+ public function addSource( $id, $properties = null) {
+ // Allow multiple sources to be registered in one call
+ if ( is_array( $id ) ) {
+ foreach ( $id as $key => $value ) {
+ $this->addSource( $key, $value );
+ }
+ return;
+ }
+
+ // Disallow duplicates
+ if ( isset( $this->sources[$id] ) ) {
+ throw new MWException(
+ 'ResourceLoader duplicate source addition error. ' .
+ 'Another source has already been registered as ' . $id
+ );
+ }
+
+ // Validate properties
+ foreach ( self::$requiredSourceProperties as $prop ) {
+ if ( !isset( $properties[$prop] ) ) {
+ throw new MWException( "Required property $prop missing from source ID $id" );
+ }
+ }
+
+ $this->sources[$id] = $properties;
+ }
+
+ /**
* Get a list of module names
*
* @return Array: List of module names
return $this->modules[$name];
}
+ /**
+ * Get the list of sources
+ *
+ * @return Array: array( id => array of properties, .. )
+ */
+ public function getSources() {
+ return $this->sources;
+ }
+
/**
* Outputs a response to a resource load-request, including a content-type header.
*
try {
$scripts = '';
if ( $context->shouldIncludeScripts() ) {
- $scripts = $module->getScript( $context );
- if ( is_string( $scripts ) ) {
- // bug 27054: Append semicolon to prevent weird bugs
- // caused by files not terminating their statements right
- $scripts .= ";\n";
+ // If we are in debug mode, we'll want to return an array of URLs if possible
+ // However, we can't do this if the module doesn't support it
+ // We also can't do this if there is an only= parameter, because we have to give
+ // the module a way to return a load.php URL without causing an infinite loop
+ if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
+ $scripts = $module->getScriptURLsForDebug( $context );
+ } else {
+ $scripts = $module->getScript( $context );
+ if ( is_string( $scripts ) ) {
+ // bug 27054: Append semicolon to prevent weird bugs
+ // caused by files not terminating their statements right
+ $scripts .= ";\n";
+ }
}
}
// Styles
$styles = array();
if ( $context->shouldIncludeStyles() ) {
- $styles = $module->getStyles( $context );
+ // If we are in debug mode, we'll want to return an array of URLs
+ // See comment near shouldIncludeScripts() for more details
+ if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
+ $styles = $module->getStyleURLsForDebug( $context );
+ } else {
+ $styles = $module->getStyles( $context );
+ }
}
// Messages
switch ( $context->getOnly() ) {
case 'scripts':
if ( is_string( $scripts ) ) {
+ // Load scripts raw...
$out .= $scripts;
} elseif ( is_array( $scripts ) ) {
+ // ...except when $scripts is an array of URLs
$out .= self::makeLoaderImplementScript( $name, $scripts, array(), array() );
}
break;
* @param $version Integer: Module version number as a timestamp
* @param $dependencies Array: List of module names on which this module depends
* @param $group String: Group which the module is in.
+ * @param $source String: Source of the module, or 'local' if not foreign.
* @param $script String: JavaScript code
*
* @return string
*/
- public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $script ) {
+ 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 ) {\n\t$script\n} )",
- array( $name, $version, $dependencies, $group ) );
+ "( function( name, version, dependencies, group, source ) {\n\t$script\n} )",
+ array( $name, $version, $dependencies, $group, $source ) );
}
/**
* Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
*
- * - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group ):
+ * - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group, $source ):
* Register a single module.
*
* - ResourceLoader::makeLoaderRegisterScript( array( $name1, $name2 ) ):
* Register modules with the given names.
*
* - ResourceLoader::makeLoaderRegisterScript( array(
- * array( $name1, $version1, $dependencies1, $group1 ),
- * array( $name2, $version2, $dependencies1, $group2 ),
+ * array( $name1, $version1, $dependencies1, $group1, $source1 ),
+ * array( $name2, $version2, $dependencies1, $group2, $source2 ),
* ...
* ) ):
* Registers modules with the given names and parameters.
* @param $version Integer: Module version number as a timestamp
* @param $dependencies Array: List of module names on which this module depends
* @param $group String: group which the module is in.
+ * @param $source String: source of the module, or 'local' if not foreign
*
* @return string
*/
public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null )
+ $dependencies = null, $group = null, $source = null )
{
if ( is_array( $name ) ) {
return Xml::encodeJsCall( 'mw.loader.register', array( $name ) );
} else {
$version = (int) $version > 1 ? (int) $version : 1;
return Xml::encodeJsCall( 'mw.loader.register',
- array( $name, $version, $dependencies, $group ) );
+ array( $name, $version, $dependencies, $group, $source ) );
+ }
+ }
+
+ /**
+ * Returns JS code which calls mw.loader.addSource() with the given
+ * parameters. Has two calling conventions:
+ *
+ * - ResourceLoader::makeLoaderSourcesScript( $id, $properties ):
+ * Register a single source
+ *
+ * - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $props1, $id2 => $props2, ... ) );
+ * Register sources with the given IDs and properties.
+ *
+ * @param $id String: source ID
+ * @param $properties Array: source properties (see addSource())
+ */
+ public static function makeLoaderSourcesScript( $id, $properties = null ) {
+ if ( is_array( $id ) ) {
+ return Xml::encodeJsCall( 'mw.loader.addSource', array( $id ) );
+ } else {
+ return Xml::encodeJsCall( 'mw.loader.addSource', array( $id, $properties ) );
}
}
return $retval = $wgRequest->getFuzzyBool( 'debug',
$wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug ) );
}
+
+ /**
+ * Build a load.php URL
+ * @param $modules array of module names (strings)
+ * @param $lang string Language code
+ * @param $skin string Skin name
+ * @param $user string|null User name. If null, the &user= parameter is omitted
+ * @param $version string|null Versioning timestamp
+ * @param $debug bool Whether the request should be in debug mode
+ * @param $only string|null &only= parameter
+ * @param $printable bool Printable mode
+ * @param $handheld bool Handheld mode
+ * @param $extraQuery array Extra query parameters to add
+ * @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
+ */
+ public static function makeLoaderURL( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
+ $printable = false, $handheld = false, $extraQuery = array() ) {
+ global $wgLoadScript;
+ $query = self::makeLoaderQuery( $modules, $lang, $skin, $user, $version, $debug,
+ $only, $printable, $handheld, $extraQuery
+ );
+
+ // Prevent the IE6 extension check from being triggered (bug 28840)
+ // by appending a character that's invalid in Windows extensions ('*')
+ return wfAppendQuery( $wgLoadScript, $query ) . '&*';
+ }
+
+ /**
+ * Build a query array (array representation of query string) for load.php. Helper
+ * function for makeLoaderURL().
+ * @return array
+ */
+ public static function makeLoaderQuery( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
+ $printable = false, $handheld = false, $extraQuery = array() ) {
+ $query = array(
+ 'modules' => self::makePackedModulesString( $modules ),
+ 'lang' => $lang,
+ 'skin' => $skin,
+ 'debug' => $debug ? 'true' : 'false',
+ );
+ if ( $user !== null ) {
+ $query['user'] = $user;
+ }
+ if ( $version !== null ) {
+ $query['version'] = $version;
+ }
+ if ( $only !== null ) {
+ $query['only'] = $only;
+ }
+ if ( $printable ) {
+ $query['printable'] = 1;
+ }
+ if ( $handheld ) {
+ $query['handheld'] = 1;
+ }
+ $query += $extraQuery;
+
+ // Make queries uniform in order
+ ksort( $query );
+ return $query;
+ }
}