Merge "Fix type hint in HistoryAction::getArticle"
[lhc/web/wiklou.git] / includes / resourceloader / ResourceLoader.php
index 2c54f69..15bb13f 100644 (file)
@@ -63,8 +63,11 @@ class ResourceLoader {
         */
        protected $sources = array();
 
-       /** @var bool */
-       protected $hasErrors = false;
+       /**
+        * Errors accumulated during current respond() call.
+        * @var array
+        */
+       protected $errors = array();
 
        /**
         * Load information stored in the database about modules.
@@ -140,7 +143,7 @@ class ResourceLoader {
                foreach ( array_keys( $modulesWithoutMessages ) as $name ) {
                        $module = $this->getModule( $name );
                        if ( $module ) {
-                               $module->setMsgBlobMtime( $lang, 0 );
+                               $module->setMsgBlobMtime( $lang, 1 );
                        }
                }
        }
@@ -162,12 +165,10 @@ class ResourceLoader {
         * @return string Filtered data, or a comment containing an error message
         */
        public function filter( $filter, $data, $cacheReport = true ) {
-               wfProfileIn( __METHOD__ );
 
                // For empty/whitespace-only data or for unknown filters, don't perform
                // any caching or processing
                if ( trim( $data ) === '' || !in_array( $filter, array( 'minify-js', 'minify-css' ) ) ) {
-                       wfProfileOut( __METHOD__ );
                        return $data;
                }
 
@@ -178,7 +179,6 @@ class ResourceLoader {
                $cacheEntry = $cache->get( $key );
                if ( is_string( $cacheEntry ) ) {
                        wfIncrStats( "rl-$filter-cache-hits" );
-                       wfProfileOut( __METHOD__ );
                        return $cacheEntry;
                }
 
@@ -209,13 +209,9 @@ class ResourceLoader {
                } catch ( Exception $e ) {
                        MWExceptionHandler::logException( $e );
                        wfDebugLog( 'resourceloader', __METHOD__ . ": minification failed: $e" );
-                       $this->hasErrors = true;
-                       // Return exception as a comment
-                       $result = self::formatException( $e );
+                       $this->errors[] = self::formatExceptionNoComment( $e );
                }
 
-               wfProfileOut( __METHOD__ );
-
                return $result;
        }
 
@@ -228,8 +224,6 @@ class ResourceLoader {
        public function __construct( Config $config = null ) {
                global $IP;
 
-               wfProfileIn( __METHOD__ );
-
                if ( $config === null ) {
                        wfDebug( __METHOD__ . ' was called without providing a Config instance' );
                        $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
@@ -246,14 +240,13 @@ class ResourceLoader {
                // Register core modules
                $this->register( include "$IP/resources/Resources.php" );
                // Register extension modules
-               wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
+               Hooks::run( 'ResourceLoaderRegisterModules', array( &$this ) );
                $this->register( $config->get( 'ResourceModules' ) );
 
                if ( $config->get( 'EnableJavaScriptTest' ) === true ) {
                        $this->registerTestModules();
                }
 
-               wfProfileOut( __METHOD__ );
        }
 
        /**
@@ -277,14 +270,12 @@ class ResourceLoader {
         *   not registered
         */
        public function register( $name, $info = null ) {
-               wfProfileIn( __METHOD__ );
 
                // Allow multiple modules to be registered in one call
                $registrations = is_array( $name ) ? $name : array( $name => $info );
                foreach ( $registrations as $name => $info ) {
                        // Disallow duplicate registrations
                        if ( isset( $this->moduleInfos[$name] ) ) {
-                               wfProfileOut( __METHOD__ );
                                // A module has already been registered by this name
                                throw new MWException(
                                        'ResourceLoader duplicate registration error. ' .
@@ -294,7 +285,6 @@ class ResourceLoader {
 
                        // Check $name for validity
                        if ( !self::isValidModuleName( $name ) ) {
-                               wfProfileOut( __METHOD__ );
                                throw new MWException( "ResourceLoader module name '$name' is invalid, "
                                        . "see ResourceLoader::isValidModuleName()" );
                        }
@@ -308,7 +298,6 @@ class ResourceLoader {
                                // New calling convention
                                $this->moduleInfos[$name] = $info;
                        } else {
-                               wfProfileOut( __METHOD__ );
                                throw new MWException(
                                        'ResourceLoader module info type error for module \'' . $name .
                                        '\': expected ResourceLoaderModule or array (got: ' . gettype( $info ) . ')'
@@ -356,7 +345,6 @@ class ResourceLoader {
                        }
                }
 
-               wfProfileOut( __METHOD__ );
        }
 
        /**
@@ -370,13 +358,11 @@ class ResourceLoader {
                                . 'Edit your <code>LocalSettings.php</code> to enable it.' );
                }
 
-               wfProfileIn( __METHOD__ );
-
                // Get core test suites
                $testModules = array();
                $testModules['qunit'] = array();
                // Get other test suites (e.g. from extensions)
-               wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
+               Hooks::run( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
 
                // Add the testrunner (which configures QUnit) to the dependencies.
                // Since it must be ready before any of the test suites are executed.
@@ -399,7 +385,6 @@ class ResourceLoader {
                        $this->testModuleNames[$id] = array_keys( $testModules[$id] );
                }
 
-               wfProfileOut( __METHOD__ );
        }
 
        /**
@@ -578,9 +563,6 @@ class ResourceLoader {
                // See http://bugs.php.net/bug.php?id=36514
                ob_start();
 
-               wfProfileIn( __METHOD__ );
-               $errors = '';
-
                // Find out which modules are missing and instantiate the others
                $modules = array();
                $missing = array();
@@ -591,10 +573,7 @@ class ResourceLoader {
                                // This is a security issue, see bug 34907.
                                if ( $module->getGroup() === 'private' ) {
                                        wfDebugLog( 'resourceloader', __METHOD__ . ": request for private module '$name' denied" );
-                                       $this->hasErrors = true;
-                                       // Add exception to the output as a comment
-                                       $errors .= self::makeComment( "Cannot show private module \"$name\"" );
-
+                                       $this->errors[] = "Cannot show private module \"$name\"";
                                        continue;
                                }
                                $modules[$name] = $module;
@@ -609,13 +588,9 @@ class ResourceLoader {
                } catch ( Exception $e ) {
                        MWExceptionHandler::logException( $e );
                        wfDebugLog( 'resourceloader', __METHOD__ . ": preloading module info failed: $e" );
-                       $this->hasErrors = true;
-                       // Add exception to the output as a comment
-                       $errors .= self::formatException( $e );
+                       $this->errors[] = self::formatExceptionNoComment( $e );
                }
 
-               wfProfileIn( __METHOD__ . '-getModifiedTime' );
-
                // To send Last-Modified and support If-Modified-Since, we need to detect
                // the last modified time
                $mtime = wfTimestamp( TS_UNIX, $this->config->get( 'CacheEpoch' ) );
@@ -629,36 +604,27 @@ class ResourceLoader {
                        } catch ( Exception $e ) {
                                MWExceptionHandler::logException( $e );
                                wfDebugLog( 'resourceloader', __METHOD__ . ": calculating maximum modified time failed: $e" );
-                               $this->hasErrors = true;
-                               // Add exception to the output as a comment
-                               $errors .= self::formatException( $e );
+                               $this->errors[] = self::formatExceptionNoComment( $e );
                        }
                }
 
-               wfProfileOut( __METHOD__ . '-getModifiedTime' );
-
                // If there's an If-Modified-Since header, respond with a 304 appropriately
                if ( $this->tryRespondLastModified( $context, $mtime ) ) {
-                       wfProfileOut( __METHOD__ );
                        return; // output handled (buffers cleared)
                }
 
                // Generate a response
                $response = $this->makeModuleResponse( $context, $modules, $missing );
 
-               // Prepend comments indicating exceptions
-               $response = $errors . $response;
-
                // Capture any PHP warnings from the output buffer and append them to the
-               // response in a comment if we're in debug mode.
+               // error list if we're in debug mode.
                if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
-                       $response = self::makeComment( $warnings ) . $response;
-                       $this->hasErrors = true;
+                       $this->errors[] = $warnings;
                }
 
                // Save response to file cache unless there are errors
-               if ( isset( $fileCache ) && !$errors && !count( $missing ) ) {
-                       // Cache single modules...and other requests if there are enough hits
+               if ( isset( $fileCache ) && !$this->errors && !count( $missing ) ) {
+                       // Cache single modules and images...and other requests if there are enough hits
                        if ( ResourceFileCache::useFileCache( $context ) ) {
                                if ( $fileCache->isCacheWorthy() ) {
                                        $fileCache->saveText( $response );
@@ -669,20 +635,37 @@ class ResourceLoader {
                }
 
                // Send content type and cache related headers
-               $this->sendResponseHeaders( $context, $mtime, $this->hasErrors );
+               $this->sendResponseHeaders( $context, $mtime, (bool)$this->errors );
 
                // Remove the output buffer and output the response
                ob_end_clean();
+
+               if ( $context->getImageObj() && $this->errors ) {
+                       // We can't show both the error messages and the response when it's an image.
+                       $errorText = '';
+                       foreach ( $this->errors as $error ) {
+                               $errorText .= $error . "\n";
+                       }
+                       $response = $errorText;
+               } elseif ( $this->errors ) {
+                       // Prepend comments indicating errors
+                       $errorText = '';
+                       foreach ( $this->errors as $error ) {
+                               $errorText .= self::makeComment( $error );
+                       }
+                       $response = $errorText . $response;
+               }
+
+               $this->errors = array();
                echo $response;
 
-               wfProfileOut( __METHOD__ );
        }
 
        /**
         * Send content type and last modified headers to the client.
         * @param ResourceLoaderContext $context
         * @param string $mtime TS_MW timestamp to use for last-modified
-        * @param bool $errors Whether there are commented-out errors in the response
+        * @param bool $errors Whether there are errors in the response
         * @return void
         */
        protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime, $errors ) {
@@ -699,7 +682,14 @@ class ResourceLoader {
                        $maxage = $rlMaxage['versioned']['client'];
                        $smaxage = $rlMaxage['versioned']['server'];
                }
-               if ( $context->getOnly() === 'styles' ) {
+               if ( $context->getImageObj() ) {
+                       // Output different headers if we're outputting textual errors.
+                       if ( $errors ) {
+                               header( 'Content-Type: text/plain; charset=utf-8' );
+                       } else {
+                               $context->getImageObj()->sendResponseHeaders( $context );
+                       }
+               } elseif ( $context->getOnly() === 'styles' ) {
                        header( 'Content-Type: text/css; charset=utf-8' );
                        header( 'Access-Control-Allow-Origin: *' );
                } else {
@@ -823,15 +813,26 @@ class ResourceLoader {
         * Handle exception display.
         *
         * @param Exception $e Exception to be shown to the user
-        * @return string Sanitized text that can be returned to the user
+        * @return string Sanitized text in a CSS/JS comment that can be returned to the user
         */
        public static function formatException( $e ) {
+               return self::makeComment( self::formatExceptionNoComment( $e ) );
+       }
+
+       /**
+        * Handle exception display.
+        *
+        * @since 1.25
+        * @param Exception $e Exception to be shown to the user
+        * @return string Sanitized text that can be returned to the user
+        */
+       protected static function formatExceptionNoComment( $e ) {
                global $wgShowExceptionDetails;
 
                if ( $wgShowExceptionDetails ) {
-                       return self::makeComment( $e->__toString() );
+                       return $e->__toString();
                } else {
-                       return self::makeComment( wfMessage( 'internalerror' )->text() );
+                       return wfMessage( 'internalerror' )->text();
                }
        }
 
@@ -847,7 +848,6 @@ class ResourceLoader {
                array $modules, array $missing = array()
        ) {
                $out = '';
-               $exceptions = '';
                $states = array();
 
                if ( !count( $modules ) && !count( $missing ) ) {
@@ -856,7 +856,15 @@ class ResourceLoader {
    no modules were requested. Max made me put this here. */";
                }
 
-               wfProfileIn( __METHOD__ );
+               $image = $context->getImageObj();
+               if ( $image ) {
+                       $data = $image->getImageData( $context );
+                       if ( $data === false ) {
+                               $data = '';
+                               $this->errors[] = 'Image generation failed';
+                       }
+                       return $data;
+               }
 
                // Pre-fetch blobs
                if ( $context->shouldIncludeMessages() ) {
@@ -868,9 +876,7 @@ class ResourceLoader {
                                        'resourceloader',
                                        __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: $e"
                                );
-                               $this->hasErrors = true;
-                               // Add exception to the output as a comment
-                               $exceptions .= self::formatException( $e );
+                               $this->errors[] = self::formatExceptionNoComment( $e );
                        }
                } else {
                        $blobs = array();
@@ -887,7 +893,6 @@ class ResourceLoader {
                         * @var $module ResourceLoaderModule
                         */
 
-                       wfProfileIn( __METHOD__ . '-' . $name );
                        try {
                                $scripts = '';
                                if ( $context->shouldIncludeScripts() ) {
@@ -994,16 +999,13 @@ class ResourceLoader {
                        } catch ( Exception $e ) {
                                MWExceptionHandler::logException( $e );
                                wfDebugLog( 'resourceloader', __METHOD__ . ": generating module package failed: $e" );
-                               $this->hasErrors = true;
-                               // Add exception to the output as a comment
-                               $exceptions .= self::formatException( $e );
+                               $this->errors[] = self::formatExceptionNoComment( $e );
 
                                // Respond to client with error-state instead of module implementation
                                $states[$name] = 'error';
                                unset( $modules[$name] );
                        }
                        $isRaw |= $module->isRaw();
-                       wfProfileOut( __METHOD__ . '-' . $name );
                }
 
                // Update module states
@@ -1022,9 +1024,8 @@ class ResourceLoader {
                        }
                } else {
                        if ( count( $states ) ) {
-                               $exceptions .= self::makeComment(
-                                       'Problematic modules: ' . FormatJson::encode( $states, ResourceLoader::inDebugMode() )
-                               );
+                               $this->errors[] = 'Problematic modules: ' .
+                                       FormatJson::encode( $states, ResourceLoader::inDebugMode() );
                        }
                }
 
@@ -1036,8 +1037,7 @@ class ResourceLoader {
                        }
                }
 
-               wfProfileOut( __METHOD__ );
-               return $exceptions . $out;
+               return $out;
        }
 
        /* Static Methods */
@@ -1188,6 +1188,27 @@ class ResourceLoader {
                );
        }
 
+       /**
+        * Remove empty values from the end of an array.
+        *
+        * Values considered empty:
+        *
+        * - null
+        * - empty array
+        *
+        * @param Array $array
+        */
+       private static function trimArray( Array &$array ) {
+               $i = count( $array );
+               while ( $i-- ) {
+                       if ( $array[$i] === null || $array[$i] === array() ) {
+                               unset( $array[$i] );
+                       } else {
+                               break;
+                       }
+               }
+       }
+
        /**
         * Returns JS code which calls mw.loader.register with the given
         * parameters. Has three calling conventions:
@@ -1219,16 +1240,37 @@ class ResourceLoader {
                $dependencies = null, $group = null, $source = null, $skip = null
        ) {
                if ( is_array( $name ) ) {
+                       // Build module name index
+                       $index = array();
+                       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];
+                                               }
+                                       }
+                               }
+                       }
+
+                       array_walk( $name, array( 'self', 'trimArray' ) );
+
                        return Xml::encodeJsCall(
                                'mw.loader.register',
                                array( $name ),
                                ResourceLoader::inDebugMode()
                        );
                } else {
-                       $version = (int)$version > 1 ? (int)$version : 1;
+                       $registration = array( $name, $version, $dependencies, $group, $source, $skip );
+                       self::trimArray( $registration );
                        return Xml::encodeJsCall(
                                'mw.loader.register',
-                               array( $name, $version, $dependencies, $group, $source, $skip ),
+                               $registration,
                                ResourceLoader::inDebugMode()
                        );
                }