/** * Output a response to a load request, including the content-type header. * * @param ResourceLoaderContext $context Context in which a response should be formed */ public function respond(ResourceLoaderContext $context) { // Use file cache if enabled and available... if ($this->config->get('UseFileCache')) { $fileCache = ResourceFileCache::newFromContext($context); if ($this->tryRespondFromFileCache($fileCache, $context)) { return; // output handled } } // Buffer output to catch warnings. Normally we'd use ob_clean() on the // top-level output buffer to clear warnings, but that breaks when ob_gzhandler // is used: ob_clean() will clear the GZIP header in that case and it won't come // back for subsequent output, resulting in invalid GZIP. So we have to wrap // the whole thing in our own output buffer to be sure the active buffer // doesn't use ob_gzhandler. // See http://bugs.php.net/bug.php?id=36514 ob_start(); // Find out which modules are missing and instantiate the others $modules = array(); $missing = array(); foreach ($context->getModules() as $name) { $module = $this->getModule($name); if ($module) { // Do not allow private modules to be loaded from the web. // This is a security issue, see bug 34907. if ($module->getGroup() === 'private') { wfDebugLog('resourceloader', __METHOD__ . ": request for private module '{$name}' denied"); $this->errors[] = "Cannot show private module \"{$name}\""; continue; } $modules[$name] = $module; } else { $missing[] = $name; } } // Preload information needed to the mtime calculation below try { $this->preloadModuleInfo(array_keys($modules), $context); } catch (Exception $e) { MWExceptionHandler::logException($e); wfDebugLog('resourceloader', __METHOD__ . ": preloading module info failed: {$e}"); $this->errors[] = self::formatExceptionNoComment($e); } // 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')); foreach ($modules as $module) { /** * @var $module ResourceLoaderModule */ try { // Calculate maximum modified time $mtime = max($mtime, $module->getModifiedTime($context)); } catch (Exception $e) { MWExceptionHandler::logException($e); wfDebugLog('resourceloader', __METHOD__ . ": calculating maximum modified time failed: {$e}"); $this->errors[] = self::formatExceptionNoComment($e); } } // If there's an If-Modified-Since header, respond with a 304 appropriately if ($this->tryRespondLastModified($context, $mtime)) { return; // output handled (buffers cleared) } // Generate a response $response = $this->makeModuleResponse($context, $modules, $missing); // Capture any PHP warnings from the output buffer and append them to the // error list if we're in debug mode. if ($context->getDebug() && strlen($warnings = ob_get_contents())) { $this->errors[] = $warnings; } // Save response to file cache unless there are errors 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); } else { $fileCache->incrMissesRecent($context->getRequest()); } } } // Send content type and cache related headers $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; }
/** * Output a response to a load request, including the content-type header. * * @param ResourceLoaderContext $context Context in which a response should be formed */ public function respond(ResourceLoaderContext $context) { // Buffer output to catch warnings. Normally we'd use ob_clean() on the // top-level output buffer to clear warnings, but that breaks when ob_gzhandler // is used: ob_clean() will clear the GZIP header in that case and it won't come // back for subsequent output, resulting in invalid GZIP. So we have to wrap // the whole thing in our own output buffer to be sure the active buffer // doesn't use ob_gzhandler. // See http://bugs.php.net/bug.php?id=36514 ob_start(); // Find out which modules are missing and instantiate the others $modules = array(); $missing = array(); foreach ($context->getModules() as $name) { $module = $this->getModule($name); if ($module) { // Do not allow private modules to be loaded from the web. // This is a security issue, see bug 34907. if ($module->getGroup() === 'private') { $this->logger->debug("Request for private module '{$name}' denied"); $this->errors[] = "Cannot show private module \"{$name}\""; continue; } $modules[$name] = $module; } else { $missing[] = $name; } } try { // Preload for getCombinedVersion() $this->preloadModuleInfo(array_keys($modules), $context); } catch (Exception $e) { MWExceptionHandler::logException($e); $this->logger->warning('Preloading module info failed: {exception}', array('exception' => $e)); $this->errors[] = self::formatExceptionNoComment($e); } // Combine versions to propagate cache invalidation $versionHash = ''; try { $versionHash = $this->getCombinedVersion($context, array_keys($modules)); } catch (Exception $e) { MWExceptionHandler::logException($e); $this->logger->warning('Calculating version hash failed: {exception}', array('exception' => $e)); $this->errors[] = self::formatExceptionNoComment($e); } // See RFC 2616 § 3.11 Entity Tags // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11 $etag = 'W/"' . $versionHash . '"'; // Try the client-side cache first if ($this->tryRespondNotModified($context, $etag)) { return; // output handled (buffers cleared) } // Use file cache if enabled and available... if ($this->config->get('UseFileCache')) { $fileCache = ResourceFileCache::newFromContext($context); if ($this->tryRespondFromFileCache($fileCache, $context, $etag)) { return; // output handled } } // Generate a response $response = $this->makeModuleResponse($context, $modules, $missing); // Capture any PHP warnings from the output buffer and append them to the // error list if we're in debug mode. if ($context->getDebug() && strlen($warnings = ob_get_contents())) { $this->errors[] = $warnings; } // Save response to file cache unless there are errors 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); } else { $fileCache->incrMissesRecent($context->getRequest()); } } } $this->sendResponseHeaders($context, $etag, (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; }
/** * Outputs a response to a resource load-request, including a content-type header. * * @param $context ResourceLoaderContext: Context in which a response should be formed */ public function respond(ResourceLoaderContext $context) { global $wgCacheEpoch, $wgUseFileCache; // Use file cache if enabled and available... if ($wgUseFileCache) { $fileCache = ResourceFileCache::newFromContext($context); if ($this->tryRespondFromFileCache($fileCache, $context)) { return; // output handled } } // Buffer output to catch warnings. Normally we'd use ob_clean() on the // top-level output buffer to clear warnings, but that breaks when ob_gzhandler // is used: ob_clean() will clear the GZIP header in that case and it won't come // back for subsequent output, resulting in invalid GZIP. So we have to wrap // the whole thing in our own output buffer to be sure the active buffer // doesn't use ob_gzhandler. // See http://bugs.php.net/bug.php?id=36514 ob_start(); wfProfileIn(__METHOD__); $errors = ''; // Split requested modules into two groups, modules and missing $modules = array(); $missing = array(); foreach ($context->getModules() as $name) { if (isset($this->moduleInfos[$name])) { $module = $this->getModule($name); // Do not allow private modules to be loaded from the web. // This is a security issue, see bug 34907. if ($module->getGroup() === 'private') { $errors .= $this->makeComment("Cannot show private module \"{$name}\""); continue; } $modules[$name] = $this->getModule($name); } else { $missing[] = $name; } } // Preload information needed to the mtime calculation below try { $this->preloadModuleInfo(array_keys($modules), $context); } catch (Exception $e) { // Add exception to the output as a comment $errors .= $this->makeComment($e->__toString()); } wfProfileIn(__METHOD__ . '-getModifiedTime'); // To send Last-Modified and support If-Modified-Since, we need to detect // the last modified time $mtime = wfTimestamp(TS_UNIX, $wgCacheEpoch); foreach ($modules as $module) { /** * @var $module ResourceLoaderModule */ try { // Calculate maximum modified time $mtime = max($mtime, $module->getModifiedTime($context)); } catch (Exception $e) { // Add exception to the output as a comment $errors .= $this->makeComment($e->__toString()); } } wfProfileOut(__METHOD__ . '-getModifiedTime'); // Send content type and cache related headers $this->sendResponseHeaders($context, $mtime); // 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. if ($context->getDebug() && strlen($warnings = ob_get_contents())) { $response = $this->makeComment($warnings) . $response; } // Save response to file cache unless there are errors if (isset($fileCache) && !$errors && !$missing) { // Cache single modules...and other requests if there are enough hits if (ResourceFileCache::useFileCache($context)) { if ($fileCache->isCacheWorthy()) { $fileCache->saveText($response); } else { $fileCache->incrMissesRecent($context->getRequest()); } } } // Remove the output buffer and output the response ob_end_clean(); echo $response; wfProfileOut(__METHOD__); }