Followup r96978, clean up code duplication by factoring out the code building load...
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoader.php
index 2f513b7..1128264 100644 (file)
 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 */
 
        /**
@@ -178,10 +183,16 @@ class ResourceLoader {
         * 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
@@ -250,7 +261,43 @@ class ResourceLoader {
                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
@@ -291,6 +338,15 @@ class ResourceLoader {
                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.
         *
@@ -473,17 +529,31 @@ class ResourceLoader {
                        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
@@ -493,8 +563,10 @@ class ResourceLoader {
                                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;
@@ -660,30 +732,31 @@ class ResourceLoader {
         * @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.
@@ -692,18 +765,40 @@ class ResourceLoader {
         * @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 ) );
                }
        }
 
@@ -772,4 +867,65 @@ class ResourceLoader {
                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;
+       }
 }