/** * @todo Document * @param array|string $modules One or more module names * @param string $only ResourceLoaderModule TYPE_ class constant * @param bool $useESI * @param array $extraQuery Array with extra query parameters to add to each * request. array( param => value ). * @param bool $loadCall If true, output an (asynchronous) mw.loader.load() * call rather than a "<script src='...'>" tag. * @return string The html "<script>", "<link>" and "<style>" tags */ public function makeResourceLoaderLink($modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false) { $modules = (array) $modules; $links = array('html' => '', 'states' => array()); if (!count($modules)) { return $links; } if (count($modules) > 1) { // Remove duplicate module requests $modules = array_unique($modules); // Sort module names so requests are more uniform sort($modules); if (ResourceLoader::inDebugMode()) { // Recursively call us for every item foreach ($modules as $name) { $link = $this->makeResourceLoaderLink($name, $only, $useESI); $links['html'] .= $link['html']; $links['states'] += $link['states']; } return $links; } } if (!is_null($this->mTarget)) { $extraQuery['target'] = $this->mTarget; } // Create keyed-by-source and then keyed-by-group list of module objects from modules list $sortedModules = array(); $resourceLoader = $this->getResourceLoader(); $resourceLoaderUseESI = $this->getConfig()->get('ResourceLoaderUseESI'); foreach ($modules as $name) { $module = $resourceLoader->getModule($name); # Check that we're allowed to include this module on this page if (!$module || $module->getOrigin() > $this->getAllowedModules(ResourceLoaderModule::TYPE_SCRIPTS) && $only == ResourceLoaderModule::TYPE_SCRIPTS || $module->getOrigin() > $this->getAllowedModules(ResourceLoaderModule::TYPE_STYLES) && $only == ResourceLoaderModule::TYPE_STYLES || $module->getOrigin() > $this->getAllowedModules(ResourceLoaderModule::TYPE_COMBINED) && $only == ResourceLoaderModule::TYPE_COMBINED || $this->mTarget && !in_array($this->mTarget, $module->getTargets())) { continue; } $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module; } foreach ($sortedModules as $source => $groups) { foreach ($groups as $group => $grpModules) { // Special handling for user-specific groups $user = null; if (($group === 'user' || $group === 'private') && $this->getUser()->isLoggedIn()) { $user = $this->getUser()->getName(); } // Create a fake request based on the one we are about to make so modules return // correct timestamp and emptiness data $query = ResourceLoader::makeLoaderQuery(array(), $this->getLanguage()->getCode(), $this->getSkin()->getSkinName(), $user, null, ResourceLoader::inDebugMode(), $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, $this->isPrintable(), $this->getRequest()->getBool('handheld'), $extraQuery); $context = new ResourceLoaderContext($resourceLoader, new FauxRequest($query)); // Extract modules that know they're empty and see if we have one or more // raw modules $isRaw = false; foreach ($grpModules as $key => $module) { // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857) // If we're only getting the styles, we don't need to do anything for empty modules. if ($module->isKnownEmpty($context)) { unset($grpModules[$key]); if ($only !== ResourceLoaderModule::TYPE_STYLES) { $links['states'][$key] = 'ready'; } } $isRaw |= $module->isRaw(); } // If there are no non-empty modules, skip this group if (count($grpModules) === 0) { continue; } // Inline private modules. These can't be loaded through load.php for security // reasons, see bug 34907. Note that these modules should be loaded from // getHeadScripts() before the first loader call. Otherwise other modules can't // properly use them as dependencies (bug 30914) if ($group === 'private') { if ($only == ResourceLoaderModule::TYPE_STYLES) { $links['html'] .= Html::inlineStyle($resourceLoader->makeModuleResponse($context, $grpModules)); } else { $links['html'] .= ResourceLoader::makeInlineScript($resourceLoader->makeModuleResponse($context, $grpModules)); } $links['html'] .= "\n"; continue; } // 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 Squid-cached HTML $version = null; if ($group === 'user') { $query['version'] = $resourceLoader->getCombinedVersion($context, array_keys($grpModules)); } $query['modules'] = ResourceLoader::makePackedModulesString(array_keys($grpModules)); $moduleContext = new ResourceLoaderContext($resourceLoader, new FauxRequest($query)); $url = $resourceLoader->createLoaderURL($source, $moduleContext, $extraQuery); if ($useESI && $resourceLoaderUseESI) { $esi = Xml::element('esi:include', array('src' => $url)); if ($only == ResourceLoaderModule::TYPE_STYLES) { $link = Html::inlineStyle($esi); } else { $link = Html::inlineScript($esi); } } else { // Automatically select style/script elements if ($only === ResourceLoaderModule::TYPE_STYLES) { $link = Html::linkedStyle($url); } elseif ($loadCall) { $link = ResourceLoader::makeInlineScript(Xml::encodeJsCall('mw.loader.load', array($url, 'text/javascript', true))); } else { $link = Html::linkedScript($url); if (!$context->getRaw() && !$isRaw) { // Wrap only=script / only=combined requests in a conditional as // browsers not supported by the startup module would unconditionally // execute this module. Otherwise users will get "ReferenceError: mw is // undefined" or "jQuery is undefined" from e.g. a "site" module. $link = ResourceLoader::makeInlineScript(Xml::encodeJsCall('document.write', array($link))); } // For modules requested directly in the html via <link> or <script>, // tell mw.loader they are being loading to prevent duplicate requests. foreach ($grpModules as $key => $module) { // Don't output state=loading for the startup module.. if ($key !== 'startup') { $links['states'][$key] = 'loading'; } } } } if ($group == 'noscript') { $links['html'] .= Html::rawElement('noscript', array(), $link) . "\n"; } else { $links['html'] .= $link . "\n"; } } } return $links; }
private function getRlClientContext() { if (!$this->rlClientContext) { $query = ResourceLoader::makeLoaderQuery([], $this->getLanguage()->getCode(), $this->getSkin()->getSkinName(), $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null, null, ResourceLoader::inDebugMode(), null, $this->isPrintable(), $this->getRequest()->getBool('handheld')); $this->rlClientContext = new ResourceLoaderContext($this->getResourceLoader(), new FauxRequest($query)); } return $this->rlClientContext; }
function headScriptsStartupScript($outputPage, $scripts) { $resourceLoader = $outputPage->getResourceLoader(); $module = $resourceLoader->getModule('startup'); $query = ResourceLoader::makeLoaderQuery(array(), $outputPage->getLanguage()->getCode(), $outputPage->getSkin()->getSkinName(), null, null, ResourceLoader::inDebugMode(), "scripts", $outputPage->isPrintable(), $outputPage->getRequest()->getBool('handheld'), array()); $context = new ResourceLoaderContext($resourceLoader, new FauxRequest($query)); $scripts = Html::inlineScript($resourceLoader->makeModuleResponse($context, array($module))); return true; }
/** * TODO: Document * @param $modules Array/string with the module name(s) * @param $only String ResourceLoaderModule TYPE_ class constant * @param $useESI boolean * @param $extraQuery Array with extra query parameters to add to each request. array( param => value ) * @param $loadCall boolean If true, output an (asynchronous) mw.loader.load() call rather than a <script src="..."> tag * @return string html <script> and <style> tags */ protected function makeResourceLoaderLink($modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false) { global $wgResourceLoaderUseESI; if (!count($modules)) { return ''; } if (count($modules) > 1) { // Remove duplicate module requests $modules = array_unique((array) $modules); // Sort module names so requests are more uniform sort($modules); if (ResourceLoader::inDebugMode()) { // Recursively call us for every item $links = ''; foreach ($modules as $name) { $links .= $this->makeResourceLoaderLink($name, $only, $useESI); } return $links; } } // Create keyed-by-group list of module objects from modules list $groups = array(); $resourceLoader = $this->getResourceLoader(); foreach ((array) $modules as $name) { $module = $resourceLoader->getModule($name); # Check that we're allowed to include this module on this page if (!$module || $module->getOrigin() > $this->getAllowedModules(ResourceLoaderModule::TYPE_SCRIPTS) && $only == ResourceLoaderModule::TYPE_SCRIPTS || $module->getOrigin() > $this->getAllowedModules(ResourceLoaderModule::TYPE_STYLES) && $only == ResourceLoaderModule::TYPE_STYLES) { continue; } $group = $module->getGroup(); if (!isset($groups[$group])) { $groups[$group] = array(); } $groups[$group][$name] = $module; } $links = ''; foreach ($groups as $group => $modules) { // Special handling for user-specific groups $user = null; if (($group === 'user' || $group === 'private') && $this->getUser()->isLoggedIn()) { $user = $this->getUser()->getName(); } // Create a fake request based on the one we are about to make so modules return // correct timestamp and emptiness data $query = ResourceLoader::makeLoaderQuery(array(), $this->getLanguage()->getCode(), $this->getSkin()->getSkinName(), $user, null, ResourceLoader::inDebugMode(), $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, $this->isPrintable(), $this->getRequest()->getBool('handheld'), $extraQuery); $context = new ResourceLoaderContext($resourceLoader, new FauxRequest($query)); // Drop modules that know they're empty foreach ($modules as $key => $module) { if ($module->isKnownEmpty($context)) { unset($modules[$key]); } } // If there are no modules left, skip this group if ($modules === array()) { continue; } // Inline private modules. These can't be loaded through load.php for security // reasons, see bug 34907. Note that these modules should be loaded from // getHeadScripts() before the first loader call. Otherwise other modules can't // properly use them as dependencies (bug 30914) if ($group === 'private') { if ($only == ResourceLoaderModule::TYPE_STYLES) { $links .= Html::inlineStyle($resourceLoader->makeModuleResponse($context, $modules)); } else { $links .= Html::inlineScript(ResourceLoader::makeLoaderConditionalScript($resourceLoader->makeModuleResponse($context, $modules))); } $links .= "\n"; continue; } // 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-changable 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 Squid-cached HTML $version = null; if ($group === 'user') { // Get the maximum timestamp $timestamp = 1; foreach ($modules as $module) { $timestamp = max($timestamp, $module->getModifiedTime($context)); } // Add a version parameter so cache will break when things change $version = wfTimestamp(TS_ISO_8601_BASIC, $timestamp); } $url = ResourceLoader::makeLoaderURL(array_keys($modules), $this->getLanguage()->getCode(), $this->getSkin()->getSkinName(), $user, $version, ResourceLoader::inDebugMode(), $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, $this->isPrintable(), $this->getRequest()->getBool('handheld'), $extraQuery); if ($useESI && $wgResourceLoaderUseESI) { $esi = Xml::element('esi:include', array('src' => $url)); if ($only == ResourceLoaderModule::TYPE_STYLES) { $link = Html::inlineStyle($esi); } else { $link = Html::inlineScript($esi); } } else { // Automatically select style/script elements if ($only === ResourceLoaderModule::TYPE_STYLES) { $link = Html::linkedStyle($url); } else { if ($loadCall) { $link = Html::inlineScript(ResourceLoader::makeLoaderConditionalScript(Xml::encodeJsCall('mw.loader.load', array($url, 'text/javascript', true)))); } else { $link = Html::linkedScript($url); } } } if ($group == 'noscript') { $links .= Html::rawElement('noscript', array(), $link) . "\n"; } else { $links .= $link . "\n"; } } return $links; }