public function getModules() { if ($this->modules === self::INHERIT_VALUE) { return $this->context->getModules(); } return $this->modules; }
public function getModules() { if (!is_null($this->modules)) { return $this->modules; } else { return $this->context->getModules(); } }
public function testTypicalRequest() { $ctx = new ResourceLoaderContext($this->getResourceLoader(), new FauxRequest(['debug' => 'false', 'lang' => 'zh', 'modules' => 'foo|foo.quux,baz,bar|baz.quux', 'only' => 'styles', 'skin' => 'fallback'])); // Request parameters $this->assertEquals($ctx->getModules(), ['foo', 'foo.quux', 'foo.baz', 'foo.bar', 'baz.quux']); $this->assertEquals(false, $ctx->getDebug()); $this->assertEquals('zh', $ctx->getLanguage()); $this->assertEquals('styles', $ctx->getOnly()); $this->assertEquals('fallback', $ctx->getSkin()); $this->assertEquals(null, $ctx->getUser()); // Misc $this->assertEquals('ltr', $ctx->getDirection()); $this->assertEquals('zh|fallback|||styles|||||', $ctx->getHash()); }
/** * Construct an ResourceFileCache from a context * @param $context ResourceLoaderContext * @return ResourceFileCache */ public static function newFromContext(ResourceLoaderContext $context) { $cache = new self(); if ($context->getOnly() === 'styles') { $cache->mType = 'css'; } else { $cache->mType = 'js'; } $modules = array_unique($context->getModules()); // remove duplicates sort($modules); // normalize the order (permutation => combination) $cache->mKey = sha1($context->getHash() . implode('|', $modules)); if (count($modules) == 1) { $cache->mCacheWorthy = true; // won't take up much space } return $cache; }
/** * Helper for createLoaderURL() * * @since 1.24 * @see makeLoaderQuery * @param ResourceLoaderContext $context * @param array $extraQuery * @return array */ public static function createLoaderQuery(ResourceLoaderContext $context, $extraQuery = array()) { return self::makeLoaderQuery($context->getModules(), $context->getLanguage(), $context->getSkin(), $context->getUser(), $context->getVersion(), $context->getDebug(), $context->getOnly(), $context->getRequest()->getBool('printable'), $context->getRequest()->getBool('handheld'), $extraQuery); }
/** * Customize caching policy for RL modules * * * cache "static" modules for 30 days when cb param in the URL matches $wgStyleVersion * * cache "dynamic" modules for 30 days when version param is present in the URL and matches $mtime timestamp * * otherwise fallback to caching for 5 minutes * * @see BAC-1241 * * @param ResourceLoader $rl * @param ResourceLoaderContext $context * @param $mtime int UNIX timestamp from module(s) calculated from filesystem * @param $maxage int UNIX timestamp for maxage * @param $smaxage int UNIX timestamp for smaxage * @return bool it's a hook */ public static function onResourceLoaderModifyMaxAge(ResourceLoader $rl, ResourceLoaderContext $context, $mtime, &$maxage, &$smaxage) { global $wgStyleVersion, $wgResourceLoaderMaxage; // parse cb and version provided as URL parameters // version%3D123456-20140220T090000Z // cb%3D123456%26 $version = explode('-', (string) $context->getVersion(), 2); if (count($version) === 2) { list($cb, $ts) = $version; $ts = strtotime($ts); // convert MW to UNIX timestamp } else { $cb = $context->getRequest()->getVal('cb', false); $ts = false; } // check if at least one of required modules serves dynamic content $hasDynamicModule = false; $modules = $context->getModules(); foreach ($modules as $moduleName) { if (!$rl->getModule($moduleName) instanceof ResourceLoaderFileModule) { $hasDynamicModule = true; break; } } if ($hasDynamicModule) { // use long TTL when version value matches $mtime passed to the hook $useLongTTL = !empty($ts) && $ts <= $mtime; } else { // use long TTL when cache buster value from URL matches $wgStyleVersion $useLongTTL = !empty($cb) && $cb <= $wgStyleVersion; } // modify caching times if (!$useLongTTL) { WikiaLogger::instance()->info('rl.shortTTL', ['modules' => join(',', $modules), 'cb' => $cb, 'ts' => $ts]); } $cachingTimes = $wgResourceLoaderMaxage[$useLongTTL ? 'versioned' : 'unversioned']; $maxage = $cachingTimes['client']; $smaxage = $cachingTimes['server']; return true; }
/** * 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 $wgResourceLoaderMaxage, $wgCacheEpoch; // 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; } } // If a version wasn't specified we need a shorter expiry time for updates // to propagate to clients quickly if (is_null($context->getVersion())) { $maxage = $wgResourceLoaderMaxage['unversioned']['client']; $smaxage = $wgResourceLoaderMaxage['unversioned']['server']; } else { $maxage = $wgResourceLoaderMaxage['versioned']['client']; $smaxage = $wgResourceLoaderMaxage['versioned']['server']; } // 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) { 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'); if ($context->getOnly() === 'styles') { header('Content-Type: text/css; charset=utf-8'); } else { header('Content-Type: text/javascript; charset=utf-8'); } header('Last-Modified: ' . wfTimestamp(TS_RFC2822, $mtime)); if ($context->getDebug()) { // Do not cache debug responses header('Cache-Control: private, no-cache, must-revalidate'); header('Pragma: no-cache'); } else { header("Cache-Control: public, max-age={$maxage}, s-maxage={$smaxage}"); $exp = min($maxage, $smaxage); header('Expires: ' . wfTimestamp(TS_RFC2822, $exp + time())); } // If there's an If-Modified-Since header, respond with a 304 appropriately // Some clients send "timestamp;length=123". Strip the part after the first ';' // so we get a valid timestamp. $ims = $context->getRequest()->getHeader('If-Modified-Since'); // Never send 304s in debug mode if ($ims !== false && !$context->getDebug()) { $imsTS = strtok($ims, ';'); if ($mtime <= wfTimestamp(TS_UNIX, $imsTS)) { // There's another bug in ob_gzhandler (see also the comment at // the top of this function) that causes it to gzip even empty // responses, meaning it's impossible to produce a truly empty // response (because the gzip header is always there). This is // a problem because 304 responses have to be completely empty // per the HTTP spec, and Firefox behaves buggily when they're not. // See also http://bugs.php.net/bug.php?id=51579 // To work around this, we tear down all output buffering before // sending the 304. // On some setups, ob_get_level() doesn't seem to go down to zero // no matter how often we call ob_get_clean(), so instead of doing // the more intuitive while ( ob_get_level() > 0 ) ob_get_clean(); // we have to be safe here and avoid an infinite loop. for ($i = 0; $i < ob_get_level(); $i++) { ob_end_clean(); } header('HTTP/1.0 304 Not Modified'); header('Status: 304 Not Modified'); wfProfileOut(__METHOD__); return; } } // 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; } // Remove the output buffer and output the response ob_end_clean(); echo $response; wfProfileOut(__METHOD__); }