/** * @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; }
/** * @param $context ResourceLoaderContext * @return string */ public function getScript(ResourceLoaderContext $context) { global $IP, $wgLoadScript, $wgLegacyJavaScriptGlobals; $out = file_get_contents("{$IP}/resources/startup.js"); if ($context->getOnly() === 'scripts') { // The core modules: $modules = array('jquery', 'mediawiki'); wfRunHooks('ResourceLoaderGetStartupModules', array(&$modules)); // Get the latest version $version = 0; foreach ($modules as $moduleName) { $version = max($version, $context->getResourceLoader()->getModule($moduleName)->getModifiedTime($context)); } // Build load query for StartupModules $query = array('modules' => ResourceLoader::makePackedModulesString($modules), 'only' => 'scripts', 'lang' => $context->getLanguage(), 'skin' => $context->getSkin(), 'debug' => $context->getDebug() ? 'true' : 'false', 'version' => wfTimestamp(TS_ISO_8601_BASIC, $version)); // Ensure uniform query order ksort($query); // Startup function $configuration = $this->getConfig($context); $registrations = self::getModuleRegistrations($context); $out .= "var startUp = function() {\n" . "\tmw.config = new " . Xml::encodeJsCall('mw.Map', array($wgLegacyJavaScriptGlobals)) . "\n" . "\t{$registrations}\n" . "\t" . Xml::encodeJsCall('mw.config.set', array($configuration)) . "\t" . Xml::encodeJsCall('mw.loader.state', array(array('jquery' => 'ready'))) . "};\n"; // Conditional script injection // Wikia change - begin - @author: wladek // $scriptTag = Html::linkedScript( $wgLoadScript . '?' . wfArrayToCGI( $query ) ); // get jquery from CDN if we have wsl and getJqueryUrl loaded $scriptTagJquery = Xml::encodeJsVar(Html::linkedScript(ResourceLoader::makeLoaderURL($modules, $query['lang'], $query['skin'], null, $query['version'], $context->getDebug(), 'scripts'))); $scriptTagNoJquery = Xml::encodeJsVar(Html::linkedScript(ResourceLoader::makeLoaderURL(array('mediawiki'), $query['lang'], $query['skin'], null, $query['version'], $context->getDebug(), 'scripts'))); $scriptTag = <<<ENDSCRIPT ( (window.wsl && window.getJqueryUrl && window.wgJqueryUrl) ? (wsl.buildScript(window.getJqueryUrl()) + {$scriptTagNoJquery}) : ({$scriptTagJquery}) ) ENDSCRIPT; $scriptTag = new XmlJsCode($scriptTag); // Wikia change - end $out .= "if ( isCompatible() ) {\n" . "\t" . Xml::encodeJsCall('document.write', array($scriptTag)) . "}\n" . "delete isCompatible;"; } return $out; }
/** * @param $context ResourceLoaderContext * @return string */ public function getScript(ResourceLoaderContext $context) { global $IP, $wgLoadScript, $wgLegacyJavaScriptGlobals; $out = file_get_contents("{$IP}/resources/startup.js"); if ($context->getOnly() === 'scripts') { // The core modules: $moduleNames = array('jquery', 'mediawiki'); wfRunHooks('ResourceLoaderGetStartupModules', array(&$moduleNames)); // Get the latest version $loader = $context->getResourceLoader(); $version = 0; foreach ($moduleNames as $moduleName) { $version = max($version, $loader->getModule($moduleName)->getModifiedTime($context)); } // Build load query for StartupModules $query = array('modules' => ResourceLoader::makePackedModulesString($moduleNames), 'only' => 'scripts', 'lang' => $context->getLanguage(), 'skin' => $context->getSkin(), 'debug' => $context->getDebug() ? 'true' : 'false', 'version' => wfTimestamp(TS_ISO_8601_BASIC, $version)); // Ensure uniform query order ksort($query); // Startup function $configuration = $this->getConfig($context); $registrations = self::getModuleRegistrations($context); $registrations = str_replace("\n", "\n\t", trim($registrations)); // fix indentation $out .= "var startUp = function() {\n" . "\tmw.config = new " . Xml::encodeJsCall('mw.Map', array($wgLegacyJavaScriptGlobals)) . "\n" . "\t{$registrations}\n" . "\t" . Xml::encodeJsCall('mw.config.set', array($configuration)) . "};\n"; // Conditional script injection $scriptTag = Html::linkedScript($wgLoadScript . '?' . wfArrayToCGI($query)); $out .= "if ( isCompatible() ) {\n" . "\t" . Xml::encodeJsCall('document.write', array($scriptTag)) . "}\n" . "delete isCompatible;"; } return $out; }
/** * @dataProvider providePackedModules */ public function testMakePackedModulesString($desc, $modules, $packed) { $this->assertEquals($packed, ResourceLoader::makePackedModulesString($modules), $desc); }
/** * Get the load URL of the startup modules. * * This is a helper for getScript(), but can also be called standalone, such * as when generating an AppCache manifest. * * @param ResourceLoaderContext $context * @return string */ public static function getStartupModulesUrl(ResourceLoaderContext $context) { $rl = $context->getResourceLoader(); $moduleNames = self::getStartupModules(); $query = ['modules' => ResourceLoader::makePackedModulesString($moduleNames), 'only' => 'scripts', 'lang' => $context->getLanguage(), 'skin' => $context->getSkin(), 'debug' => $context->getDebug() ? 'true' : 'false', 'version' => $rl->getCombinedVersion($context, $moduleNames)]; // Ensure uniform query order ksort($query); return wfAppendQuery(wfScript('load'), $query); }
/** * TODO: Document * @param $skin Skin * @param $modules Array/string with the module name * @param $only String ResourceLoaderModule TYPE_ class constant * @param $useESI boolean * @return string html <script> and <style> tags */ protected function makeResourceLoaderLink(Skin $skin, $modules, $only, $useESI = false) { global $wgLoadScript, $wgResourceLoaderUseESI; // Lazy-load ResourceLoader // TODO: Should this be a static function of ResourceLoader instead? $baseQuery = array('lang' => $this->getContext()->getLang()->getCode(), 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false', 'skin' => $skin->getSkinName(), 'only' => $only); // Propagate printable and handheld parameters if present if ($this->isPrintable()) { $baseQuery['printable'] = 1; } if ($this->getRequest()->getBool('handheld')) { $baseQuery['handheld'] = 1; } 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($skin, $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) { $query = $baseQuery; // Special handling for user-specific groups if (($group === 'user' || $group === 'private') && $this->getUser()->isLoggedIn()) { $query['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 $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; } $query['modules'] = ResourceLoader::makePackedModulesString(array_keys($modules)); // 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))); } 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 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 $query['version'] = wfTimestamp(TS_ISO_8601_BASIC, $timestamp); } // Make queries uniform in order ksort($query); $url = wfAppendQuery($wgLoadScript, $query); // Prevent the IE6 extension check from being triggered (bug 28840) // by appending a character that's invalid in Windows extensions ('*') $url .= '&*'; 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 { $link = Html::linkedScript($url); } } if ($group == 'noscript') { $links .= Html::rawElement('noscript', array(), $link) . "\n"; } else { $links .= $link . "\n"; } } return $links; }
function outputInlineScript($moduleList) { $o = ""; $modules = array(); $resolvedModuleDependencyList = $this->getModuleDependencyList($moduleList); // "Fake" the request headers as ResourceLoaderContext derives it's data for module resolving from them $_GET['only'] = NULL; $_GET['modules'] = ResourceLoader::makePackedModulesString($resolvedModuleDependencyList); $fauxRequest = new WebRequest(); $resourceLoader = new MwEmbedResourceLoader(); foreach ($resolvedModuleDependencyList as $moduleName) { $modules[$moduleName] = $resourceLoader->getModule($moduleName); } $s = $resourceLoader->makeModuleResponse(new MwEmbedResourceLoaderContext($resourceLoader, $fauxRequest), $modules, array()); $o .= 'window.inlineScript = true;'; $o .= $s; $o .= ResourceLoader::makeLoaderStateScript(array_fill_keys($resolvedModuleDependencyList, 'ready')); return $o; }
/** * Get the load URL of the startup modules. * * This is a helper for getScript(), but can also be called standalone, such * as when generating an AppCache manifest. * * @param ResourceLoaderContext $context * @return string */ public static function getStartupModulesUrl(ResourceLoaderContext $context) { // The core modules: $moduleNames = array('jquery', 'mediawiki'); wfRunHooks('ResourceLoaderGetStartupModules', array(&$moduleNames), '1.23'); // Get the latest version $loader = $context->getResourceLoader(); $version = 0; foreach ($moduleNames as $moduleName) { $version = max($version, $loader->getModule($moduleName)->getModifiedTime($context)); } $query = array('modules' => ResourceLoader::makePackedModulesString($moduleNames), 'only' => 'scripts', 'lang' => $context->getLanguage(), 'skin' => $context->getSkin(), 'debug' => $context->getDebug() ? 'true' : 'false', 'version' => wfTimestamp(TS_ISO_8601_BASIC, $version)); // Ensure uniform query order ksort($query); return wfAppendQuery(wfScript('load'), $query); }
/** * @param $context ResourceLoaderContext * @return string */ public function getScript(ResourceLoaderContext $context) { global $IP, $wgScriptPath, $wgLoadScript, $wgLegacyJavaScriptGlobals; $out = file_get_contents("{$IP}/{$wgScriptPath}/resources/startup.js"); if ($context->getOnly() === 'scripts') { // The core modules: $modules = array('jquery', 'mediawiki'); wfRunHooks('ResourceLoaderGetStartupModules', array(&$modules)); // Get the latest version $version = 0; foreach ($modules as $moduleName) { $version = max($version, $context->getResourceLoader()->getModule($moduleName)->getModifiedTime($context)); } // Build load query for StartupModules $query = array('modules' => ResourceLoader::makePackedModulesString($modules), 'only' => 'scripts', 'lang' => $context->getLanguage(), 'skin' => $context->getSkin(), 'debug' => $context->getDebug() ? 'true' : 'false', 'version' => wfTimestamp(TS_ISO_8601_BASIC, $version)); // Ensure uniform query order ksort($query); // Startup function $configuration = $this->getConfig($context); $registrations = self::getModuleRegistrations($context); $out .= "var startUp = function() {\n" . "\tmw.config = new " . Xml::encodeJsCall('mw.Map', array($wgLegacyJavaScriptGlobals)) . "\n" . "\t{$registrations}\n" . "\t" . Xml::encodeJsCall('mw.config.set', array($configuration)) . "};\n"; $fauxRequest = new WebRequest(); $modulesToLoad = array(); $resourceLoader = $context->getResourceLoader(); foreach ($modules as $moduleName) { $modulesToLoad[$moduleName] = $resourceLoader->getModule($moduleName); } $s = $context->getResourceLoader()->makeModuleResponse(new MwEmbedResourceLoaderContext($context->getResourceLoader(), $fauxRequest), $modulesToLoad, array()); $out .= $s; // // Conditional script injection // $out .= "if ( isCompatible() ) {\n" . // "\t" . Xml::encodeJsCall( 'writeScript', array( $wgLoadScript . '?' . wfArrayToCGI( $query ) ) ) . // "}\n" . // "delete isCompatible;"; } return $out; }