Пример #1
0
    /**
     * Generate code for a response.
     *
     * @param ResourceLoaderContext $context Context in which to generate a response
     * @param array $modules List of module objects keyed by module name
     * @param array $missing List of requested module names that are unregistered (optional)
     * @return string Response data
     */
    public function makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing = array())
    {
        $out = '';
        $states = array();
        if (!count($modules) && !count($missing)) {
            return <<<MESSAGE
/* This file is the Web entry point for MediaWiki's ResourceLoader:
   <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
   no modules were requested. Max made me put this here. */
MESSAGE;
        }
        $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()) {
            try {
                $this->blobStore->get($this, $modules, $context->getLanguage());
            } catch (Exception $e) {
                MWExceptionHandler::logException($e);
                $this->logger->warning('Prefetching MessageBlobStore failed: {exception}', array('exception' => $e));
                $this->errors[] = self::formatExceptionNoComment($e);
            }
        }
        foreach ($missing as $name) {
            $states[$name] = 'missing';
        }
        // Generate output
        $isRaw = false;
        foreach ($modules as $name => $module) {
            try {
                $content = $module->getModuleContent($context);
                // Append output
                switch ($context->getOnly()) {
                    case 'scripts':
                        $scripts = $content['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;
                    case 'styles':
                        $styles = $content['styles'];
                        // We no longer seperate into media, they are all combined now with
                        // custom media type groups into @media .. {} sections as part of the css string.
                        // Module returns either an empty array or a numerical array with css strings.
                        $out .= isset($styles['css']) ? implode('', $styles['css']) : '';
                        break;
                    default:
                        $out .= self::makeLoaderImplementScript($name, isset($content['scripts']) ? $content['scripts'] : '', isset($content['styles']) ? $content['styles'] : array(), isset($content['messagesBlob']) ? new XmlJsCode($content['messagesBlob']) : array(), isset($content['templates']) ? $content['templates'] : array());
                        break;
                }
            } catch (Exception $e) {
                MWExceptionHandler::logException($e);
                $this->logger->warning('Generating module package failed: {exception}', array('exception' => $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();
        }
        // Update module states
        if ($context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw) {
            if (count($modules) && $context->getOnly() === 'scripts') {
                // Set the state of modules loaded as only scripts to ready as
                // they don't have an mw.loader.implement wrapper that sets the state
                foreach ($modules as $name => $module) {
                    $states[$name] = 'ready';
                }
            }
            // Set the state of modules we didn't respond to with mw.loader.implement
            if (count($states)) {
                $out .= self::makeLoaderStateScript($states);
            }
        } else {
            if (count($states)) {
                $this->errors[] = 'Problematic modules: ' . FormatJson::encode($states, ResourceLoader::inDebugMode());
            }
        }
        $enableFilterCache = true;
        if (count($modules) === 1 && reset($modules) instanceof ResourceLoaderUserTokensModule) {
            // If we're building the embedded user.tokens, don't cache (T84960)
            $enableFilterCache = false;
        }
        if (!$context->getDebug()) {
            if ($context->getOnly() === 'styles') {
                $out = $this->filter('minify-css', $out);
            } else {
                $out = $this->filter('minify-js', $out, array('cache' => $enableFilterCache));
            }
        }
        return $out;
    }
 /**
  * @covers ResourceLoaderContext::getImageObj
  */
 public function testContext()
 {
     $context = new ResourceLoaderContext(new EmptyResourceLoader(), new FauxRequest());
     $this->assertFalse($context->getImageObj(), 'Missing image parameter');
     $context = new ResourceLoaderContext(new EmptyResourceLoader(), new FauxRequest(['image' => 'example']));
     $this->assertFalse($context->getImageObj(), 'Missing module parameter');
     $context = new ResourceLoaderContext(new EmptyResourceLoader(), new FauxRequest(['modules' => 'unknown', 'image' => 'example']));
     $this->assertFalse($context->getImageObj(), 'Not an image module');
     $rl = new EmptyResourceLoader();
     $rl->register('test', ['class' => ResourceLoaderImageModule::class, 'prefix' => 'test', 'images' => ['example' => 'example.png']]);
     $context = new ResourceLoaderContext($rl, new FauxRequest(['modules' => 'test', 'image' => 'unknown']));
     $this->assertFalse($context->getImageObj(), 'Unknown image');
     $rl = new EmptyResourceLoader();
     $rl->register('test', ['class' => ResourceLoaderImageModule::class, 'prefix' => 'test', 'images' => ['example' => 'example.png']]);
     $context = new ResourceLoaderContext($rl, new FauxRequest(['modules' => 'test', 'image' => 'example']));
     $this->assertInstanceOf(ResourceLoaderImage::class, $context->getImageObj());
 }
    /**
     * Generate code for a response.
     *
     * @param ResourceLoaderContext $context Context in which to generate a response
     * @param array $modules List of module objects keyed by module name
     * @param array $missing List of requested module names that are unregistered (optional)
     * @return string Response data
     */
    public function makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing = array())
    {
        $out = '';
        $states = array();
        if (!count($modules) && !count($missing)) {
            return <<<MESSAGE
/* This file is the Web entry point for MediaWiki's ResourceLoader:
   <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
   no modules were requested. Max made me put this here. */
MESSAGE;
        }
        $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()) {
            try {
                $blobs = $this->blobStore->get($this, $modules, $context->getLanguage());
            } catch (Exception $e) {
                MWExceptionHandler::logException($e);
                wfDebugLog('resourceloader', __METHOD__ . ": pre-fetching blobs from MessageBlobStore failed: {$e}");
                $this->errors[] = self::formatExceptionNoComment($e);
            }
        } else {
            $blobs = array();
        }
        foreach ($missing as $name) {
            $states[$name] = 'missing';
        }
        // Generate output
        $isRaw = false;
        foreach ($modules as $name => $module) {
            /**
             * @var $module ResourceLoaderModule
             */
            try {
                $scripts = '';
                if ($context->shouldIncludeScripts()) {
                    // 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);
                        // rtrim() because there are usually a few line breaks
                        // after the last ';'. A new line at EOF, a new line
                        // added by ResourceLoaderFileModule::readScriptFiles, etc.
                        if (is_string($scripts) && strlen($scripts) && substr(rtrim($scripts), -1) !== ';') {
                            // Append semicolon to prevent weird bugs caused by files not
                            // terminating their statements right (bug 27054)
                            $scripts .= ";\n";
                        }
                    }
                }
                // Styles
                $styles = array();
                if ($context->shouldIncludeStyles()) {
                    // Don't create empty stylesheets like array( '' => '' ) for modules
                    // that don't *have* any stylesheets (bug 38024).
                    $stylePairs = $module->getStyles($context);
                    if (count($stylePairs)) {
                        // If we are in debug mode without &only= set, we'll want to return an array of URLs
                        // See comment near shouldIncludeScripts() for more details
                        if ($context->getDebug() && !$context->getOnly() && $module->supportsURLLoading()) {
                            $styles = array('url' => $module->getStyleURLsForDebug($context));
                        } else {
                            // Minify CSS before embedding in mw.loader.implement call
                            // (unless in debug mode)
                            if (!$context->getDebug()) {
                                foreach ($stylePairs as $media => $style) {
                                    // Can be either a string or an array of strings.
                                    if (is_array($style)) {
                                        $stylePairs[$media] = array();
                                        foreach ($style as $cssText) {
                                            if (is_string($cssText)) {
                                                $stylePairs[$media][] = $this->filter('minify-css', $cssText);
                                            }
                                        }
                                    } elseif (is_string($style)) {
                                        $stylePairs[$media] = $this->filter('minify-css', $style);
                                    }
                                }
                            }
                            // Wrap styles into @media groups as needed and flatten into a numerical array
                            $styles = array('css' => self::makeCombinedStyles($stylePairs));
                        }
                    }
                }
                // Messages
                $messagesBlob = isset($blobs[$name]) ? $blobs[$name] : '{}';
                // Append output
                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;
                    case 'styles':
                        // We no longer seperate into media, they are all combined now with
                        // custom media type groups into @media .. {} sections as part of the css string.
                        // Module returns either an empty array or a numerical array with css strings.
                        $out .= isset($styles['css']) ? implode('', $styles['css']) : '';
                        break;
                    case 'messages':
                        $out .= self::makeMessageSetScript(new XmlJsCode($messagesBlob));
                        break;
                    case 'templates':
                        $out .= Xml::encodeJsCall('mw.templates.set', array($name, (object) $module->getTemplates()), ResourceLoader::inDebugMode());
                        break;
                    default:
                        $out .= self::makeLoaderImplementScript($name, $scripts, $styles, new XmlJsCode($messagesBlob), $module->getTemplates());
                        break;
                }
            } catch (Exception $e) {
                MWExceptionHandler::logException($e);
                wfDebugLog('resourceloader', __METHOD__ . ": generating module package failed: {$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();
        }
        // Update module states
        if ($context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw) {
            if (count($modules) && $context->getOnly() === 'scripts') {
                // Set the state of modules loaded as only scripts to ready as
                // they don't have an mw.loader.implement wrapper that sets the state
                foreach ($modules as $name => $module) {
                    $states[$name] = 'ready';
                }
            }
            // Set the state of modules we didn't respond to with mw.loader.implement
            if (count($states)) {
                $out .= self::makeLoaderStateScript($states);
            }
        } else {
            if (count($states)) {
                $this->errors[] = 'Problematic modules: ' . FormatJson::encode($states, ResourceLoader::inDebugMode());
            }
        }
        if (!$context->getDebug()) {
            if ($context->getOnly() === 'styles') {
                $out = $this->filter('minify-css', $out);
            } else {
                $out = $this->filter('minify-js', $out);
            }
        }
        return $out;
    }
Пример #4
0
    /**
     * Generate code for a response.
     *
     * @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)
     * @return string Response data
     */
    public function makeModuleResponse(ResourceLoaderContext $context, array $modules, array $missing = [])
    {
        $out = '';
        $states = [];
        if (!count($modules) && !count($missing)) {
            return <<<MESSAGE
/* This file is the Web entry point for MediaWiki's ResourceLoader:
   <https://www.mediawiki.org/wiki/ResourceLoader>. In this request,
   no modules were requested. Max made me put this here. */
MESSAGE;
        }
        $image = $context->getImageObj();
        if ($image) {
            $data = $image->getImageData($context);
            if ($data === false) {
                $data = '';
                $this->errors[] = 'Image generation failed';
            }
            return $data;
        }
        foreach ($missing as $name) {
            $states[$name] = 'missing';
        }
        // Generate output
        $isRaw = false;
        $filter = $context->getOnly() === 'styles' ? 'minify-css' : 'minify-js';
        foreach ($modules as $name => $module) {
            try {
                $content = $module->getModuleContent($context);
                $implementKey = $name . '@' . $module->getVersionHash($context);
                $strContent = '';
                // Append output
                switch ($context->getOnly()) {
                    case 'scripts':
                        $scripts = $content['scripts'];
                        if (is_string($scripts)) {
                            // Load scripts raw...
                            $strContent = $scripts;
                        } elseif (is_array($scripts)) {
                            // ...except when $scripts is an array of URLs
                            $strContent = self::makeLoaderImplementScript($implementKey, $scripts, [], [], []);
                        }
                        break;
                    case 'styles':
                        $styles = $content['styles'];
                        // We no longer seperate into media, they are all combined now with
                        // custom media type groups into @media .. {} sections as part of the css string.
                        // Module returns either an empty array or a numerical array with css strings.
                        $strContent = isset($styles['css']) ? implode('', $styles['css']) : '';
                        break;
                    default:
                        $scripts = isset($content['scripts']) ? $content['scripts'] : '';
                        if (is_string($scripts)) {
                            if ($name === 'site' || $name === 'user') {
                                // Legacy scripts that run in the global scope without a closure.
                                // 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()) {
                                    $scripts = self::filter('minify-js', $scripts);
                                    // T107377
                                }
                            } else {
                                $scripts = new XmlJsCode($scripts);
                            }
                        }
                        $strContent = self::makeLoaderImplementScript($implementKey, $scripts, isset($content['styles']) ? $content['styles'] : [], isset($content['messagesBlob']) ? new XmlJsCode($content['messagesBlob']) : [], isset($content['templates']) ? $content['templates'] : []);
                        break;
                }
                if (!$context->getDebug()) {
                    $strContent = self::filter($filter, $strContent);
                }
                $out .= $strContent;
            } catch (Exception $e) {
                MWExceptionHandler::logException($e);
                $this->logger->warning('Generating module package failed: {exception}', ['exception' => $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();
        }
        // Update module states
        if ($context->shouldIncludeScripts() && !$context->getRaw() && !$isRaw) {
            if (count($modules) && $context->getOnly() === 'scripts') {
                // Set the state of modules loaded as only scripts to ready as
                // they don't have an mw.loader.implement wrapper that sets the state
                foreach ($modules as $name => $module) {
                    $states[$name] = 'ready';
                }
            }
            // Set the state of modules we didn't respond to with mw.loader.implement
            if (count($states)) {
                $stateScript = self::makeLoaderStateScript($states);
                if (!$context->getDebug()) {
                    $stateScript = self::filter('minify-js', $stateScript);
                }
                $out .= $stateScript;
            }
        } else {
            if (count($states)) {
                $this->errors[] = 'Problematic modules: ' . FormatJson::encode($states, ResourceLoader::inDebugMode());
            }
        }
        return $out;
    }