/** * @param $context ResourceLoaderContext * @return array|mixed */ public function getModifiedTime(ResourceLoaderContext $context) { global $IP, $wgCacheEpoch; $hash = $context->getHash(); if (isset($this->modifiedTime[$hash])) { return $this->modifiedTime[$hash]; } // Call preloadModuleInfo() on ALL modules as we're about // to call getModifiedTime() on all of them $loader = $context->getResourceLoader(); $loader->preloadModuleInfo($loader->getModuleNames(), $context); $this->modifiedTime[$hash] = filemtime("{$IP}/resources/startup.js"); // ATTENTION!: Because of the line above, this is not going to cause // infinite recursion - think carefully before making changes to this // code! $time = wfTimestamp(TS_UNIX, $wgCacheEpoch); foreach ($loader->getModuleNames() as $name) { $module = $loader->getModule($name); $time = max($time, $module->getModifiedTime($context)); } return $this->modifiedTime[$hash] = $time; }
/** * @param ResourceLoaderContext $context * @return array|mixed */ public function getModifiedTime(ResourceLoaderContext $context) { global $IP, $wgCacheEpoch; $hash = $context->getHash(); if (isset($this->modifiedTime[$hash])) { return $this->modifiedTime[$hash]; } // Call preloadModuleInfo() on ALL modules as we're about // to call getModifiedTime() on all of them $loader = $context->getResourceLoader(); $loader->preloadModuleInfo($loader->getModuleNames(), $context); $time = max(wfTimestamp(TS_UNIX, $wgCacheEpoch), filemtime("{$IP}/resources/src/startup.js"), $this->getHashMtime($context)); // ATTENTION!: Because of the line below, this is not going to cause // infinite recursion - think carefully before making changes to this // code! // Pre-populate modifiedTime with something because the the loop over // all modules below includes the the startup module (this module). $this->modifiedTime[$hash] = 1; foreach ($loader->getModuleNames() as $name) { $module = $loader->getModule($name); $time = max($time, $module->getModifiedTime($context)); } $this->modifiedTime[$hash] = $time; return $this->modifiedTime[$hash]; }
/** * Helper method for getDefinitionSummary(). * * @param ResourceLoaderContext $context * @return string SHA-1 */ protected function getAllModuleHashes(ResourceLoaderContext $context) { $rl = $context->getResourceLoader(); // Preload for getCombinedVersion() $rl->preloadModuleInfo($rl->getModuleNames(), $context); // ATTENTION: Because of the line below, this is not going to cause infinite recursion. // Think carefully before making changes to this code! // Pre-populate versionHash with something because the loop over all modules below includes // the startup module (this module). // See ResourceLoaderModule::getVersionHash() for usage of this cache. $this->versionHash[$context->getHash()] = null; return $rl->getCombinedVersion($context, $rl->getModuleNames()); }
public function getResourceLoader() { return $this->context->getResourceLoader(); }
/** * Bundle all resources attached to this module into an array. * * @since 1.26 * @param ResourceLoaderContext $context * @return array */ protected final function buildContent(ResourceLoaderContext $context) { $rl = $context->getResourceLoader(); $stats = RequestContext::getMain()->getStats(); $statStart = microtime(true); // Only include properties that are relevant to this context (e.g. only=scripts) // and that are non-empty (e.g. don't include "templates" for modules without // templates). This helps prevent invalidating cache for all modules when new // optional properties are introduced. $content = array(); // 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() && $this->supportsURLLoading()) { $scripts = $this->getScriptURLsForDebug($context); } else { $scripts = $this->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"; } } $content['scripts'] = $scripts; } // Styles if ($context->shouldIncludeStyles()) { $styles = array(); // Don't create empty stylesheets like array( '' => '' ) for modules // that don't *have* any stylesheets (bug 38024). $stylePairs = $this->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() && $this->supportsURLLoading()) { $styles = array('url' => $this->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][] = $rl->filter('minify-css', $cssText); } } } elseif (is_string($style)) { $stylePairs[$media] = $rl->filter('minify-css', $style); } } } // Wrap styles into @media groups as needed and flatten into a numerical array $styles = array('css' => $rl->makeCombinedStyles($stylePairs)); } } $content['styles'] = $styles; } // Messages $blobs = $rl->getMessageBlobStore()->get($rl, array($this->getName() => $this), $context->getLanguage()); if (isset($blobs[$this->getName()])) { $content['messagesBlob'] = $blobs[$this->getName()]; } $templates = $this->getTemplates(); if ($templates) { $content['templates'] = $templates; } $statTiming = microtime(true) - $statStart; $statName = strtr($this->getName(), '.', '_'); $stats->timing("resourceloader_build.all", 1000 * $statTiming); $stats->timing("resourceloader_build.{$statName}", 1000 * $statTiming); return $content; }
/** * @param ResourceLoaderContext $context * @return array */ public function getStyles(ResourceLoaderContext $context) { $this->loadFromDefinition(); // Build CSS rules $rules = []; $script = $context->getResourceLoader()->getLoadScript($this->getSource()); $selectors = $this->getSelectors(); foreach ($this->getImages($context) as $name => $image) { $declarations = $this->getCssDeclarations($image->getDataUri($context, null, 'original'), $image->getUrl($context, $script, null, 'rasterized')); $declarations = implode("\n\t", $declarations); $selector = strtr($selectors['selectorWithoutVariant'], ['{prefix}' => $this->getPrefix(), '{name}' => $name, '{variant}' => '']); $rules[] = "{$selector} {\n\t{$declarations}\n}"; foreach ($image->getVariants() as $variant) { $declarations = $this->getCssDeclarations($image->getDataUri($context, $variant, 'original'), $image->getUrl($context, $script, $variant, 'rasterized')); $declarations = implode("\n\t", $declarations); $selector = strtr($selectors['selectorWithVariant'], ['{prefix}' => $this->getPrefix(), '{name}' => $name, '{variant}' => $variant]); $rules[] = "{$selector} {\n\t{$declarations}\n}"; } } $style = implode("\n", $rules); return ['all' => $style]; }
/** * Get the URL or URLs to load for this module's CSS in debug mode. * The default behavior is to return a load.php?only=styles URL for * the module, but file-based modules will want to override this to * load the files directly. See also getScriptURLsForDebug() * * @param ResourceLoaderContext $context * @return array Array( mediaType => array( URL1, URL2, ... ), ... ) */ public function getStyleURLsForDebug(ResourceLoaderContext $context) { $resourceLoader = $context->getResourceLoader(); $derivative = new DerivativeResourceLoaderContext($context); $derivative->setModules(array($this->getName())); $derivative->setOnly('styles'); $derivative->setDebug(true); $url = $resourceLoader->createLoaderURL($this->getSource(), $derivative); return array('all' => array($url)); }
/** * Explicily load or embed modules on a page. * * @param ResourceLoaderContext $mainContext * @param array $modules One or more module names * @param string $only ResourceLoaderModule TYPE_ class constant * @param array $extraQuery [optional] Array with extra query parameters for the request * @return string|WrappedStringList HTML */ public static function makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery = []) { $rl = $mainContext->getResourceLoader(); $chunks = []; if ($mainContext->getDebug() && count($modules) > 1) { $chunks = []; // Recursively call us for every item foreach ($modules as $name) { $chunks[] = self::makeLoad($mainContext, [$name], $only, $extraQuery); } return new WrappedStringList("\n", $chunks); } // Sort module names so requests are more uniform sort($modules); // Create keyed-by-source and then keyed-by-group list of module objects from modules list $sortedModules = []; foreach ($modules as $name) { $module = $rl->getModule($name); if (!$module) { $rl->getLogger()->warning('Unknown module "{module}"', ['module' => $name]); continue; } $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module; } foreach ($sortedModules as $source => $groups) { foreach ($groups as $group => $grpModules) { $context = self::makeContext($mainContext, $group, $only, $extraQuery); $context->setModules(array_keys($grpModules)); if ($group === 'private') { // Decide whether to use style or script element if ($only == ResourceLoaderModule::TYPE_STYLES) { $chunks[] = Html::inlineStyle($rl->makeModuleResponse($context, $grpModules)); } else { $chunks[] = ResourceLoader::makeInlineScript($rl->makeModuleResponse($context, $grpModules)); } continue; } // See if we have one or more raw modules $isRaw = false; foreach ($grpModules as $key => $module) { $isRaw |= $module->isRaw(); } // Special handling for the user group; because users might change their stuff // on-wiki like user pages, or user preferences; we need to find the highest // timestamp of these user-changeable modules so we can ensure cache misses on change // This should NOT be done for the site group (bug 27564) because anons get that too // and we shouldn't be putting timestamps in CDN-cached HTML if ($group === 'user') { // Must setModules() before makeVersionQuery() $context->setVersion($rl->makeVersionQuery($context)); } $url = $rl->createLoaderURL($source, $context, $extraQuery); // Decide whether to use 'style' or 'script' element if ($only === ResourceLoaderModule::TYPE_STYLES) { $chunk = Html::linkedStyle($url); } else { if ($context->getRaw() || $isRaw) { $chunk = Html::element('script', ['async' => !isset($extraQuery['sync']), 'src' => $url]); } else { $chunk = ResourceLoader::makeInlineScript(Xml::encodeJsCall('mw.loader.load', [$url])); } } if ($group == 'noscript') { $chunks[] = Html::rawElement('noscript', [], $chunk); } else { $chunks[] = $chunk; } } } return new WrappedStringList("\n", $chunks); }
/** * @since 1.28 * @param ResourceLoaderContext $context * @param IDatabase $db * @param string[] $moduleNames */ public static function preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames) { $rl = $context->getResourceLoader(); // getDB() can be overridden to point to a foreign database. // For now, only preload local. In the future, we could preload by wikiID. $allPages = []; /** @var ResourceLoaderWikiModule[] $wikiModules */ $wikiModules = []; foreach ($moduleNames as $name) { $module = $rl->getModule($name); if ($module instanceof self) { $mDB = $module->getDB(); // Subclasses may disable getDB and implement getTitleInfo differently if ($mDB && $mDB->getWikiID() === $db->getWikiID()) { $wikiModules[] = $module; $allPages += $module->getPages($context); } } } $pageNames = array_keys($allPages); sort($pageNames); $hash = sha1(implode('|', $pageNames)); // Avoid Zend bug where "static::" does not apply LSB in the closure $func = [static::class, 'fetchTitleInfo']; $fname = __METHOD__; $cache = ObjectCache::getMainWANInstance(); $allInfo = $cache->getWithSetCallback($cache->makeGlobalKey('resourceloader', 'titleinfo', $db->getWikiID(), $hash), $cache::TTL_HOUR, function ($curVal, &$ttl, array &$setOpts) use($func, $pageNames, $db, $fname) { $setOpts += Database::getCacheSetOptions($db); return call_user_func($func, $db, $pageNames, $fname); }, ['checkKeys' => [$cache->makeGlobalKey('resourceloader', 'titleinfo', $db->getWikiID())]]); foreach ($wikiModules as $wikiModule) { $pages = $wikiModule->getPages($context); // Before we intersect, map the names to canonical form (T145673). $intersect = []; foreach ($pages as $page => $unused) { $title = Title::newFromText($page); if ($title) { $intersect[$title->getPrefixedText()] = 1; } else { // Page name may be invalid if user-provided (e.g. gadgets) $rl->getLogger()->info('Invalid wiki page title "{title}" in ' . __METHOD__, ['title' => $page]); } } $info = array_intersect_key($allInfo, $intersect); $pageNames = array_keys($pages); sort($pageNames); $key = implode('|', $pageNames); $wikiModule->setTitleInfo($key, $info); } }
public function getScript(ResourceLoaderContext $context) { $config = $context->getResourceLoader()->getConfig(); return ResourceLoader::makeConfigSetScript(['wgUploadDialog' => $config->get('UploadDialog')]); }