public function execute() { $result = false; $resourceLoader = new ResourceLoader(); foreach ($resourceLoader->getModuleNames() as $name) { /** @var ResourceLoaderFileModule $module */ $module = $resourceLoader->getModule($name); if (!$module || !$module instanceof ResourceLoaderFileModule) { continue; } $hadErrors = false; foreach ($module->getAllStyleFiles() as $file) { if ($module->getStyleSheetLang($file) !== 'less') { continue; } try { $compiler = ResourceLoader::getLessCompiler(); $compiler->compileFile($file); } catch (Exception $e) { if (!$hadErrors) { $this->error("Errors checking module {$name}:\n"); $hadErrors = true; } $this->error($e->getMessage() . "\n"); $result = true; } } } if (!$result) { $this->output("No errors found\n"); } else { die(1); } }
/** * Get all registered modules from ResouceLoader. */ protected static function getAllModules() { global $wgEnableJavaScriptTest; // Test existance of test suite files as well // (can't use setUp or setMwGlobals because providers are static) $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest; $wgEnableJavaScriptTest = true; // Initialize ResourceLoader $rl = new ResourceLoader(); $modules = array(); foreach ($rl->getModuleNames() as $moduleName) { $modules[$moduleName] = $rl->getModule($moduleName); } // Restore settings $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest; return array('modules' => $modules, 'resourceloader' => $rl, 'context' => new ResourceLoaderContext($rl, new FauxRequest())); }
public function __construct() { parent::__construct(); $resourceLoader = new ResourceLoader(); foreach ($resourceLoader->getModuleNames() as $name) { $module = $resourceLoader->getModule($name); if (!$module || !$module instanceof ResourceLoaderFileModule) { continue; } foreach ($module->getAllStyleFiles() as $styleFile) { // TODO (phuedx, 2014-03-19) The // ResourceLoaderFileModule class shouldn't // know how to get a file's extension. if ($module->getStyleSheetLang($styleFile) !== 'less') { continue; } $this->addTest(new LessFileCompilationTest($styleFile, $module)); } } }
/** * @dataProvider provideValidModules * @depends testCreatingNewResourceLoaderCallsRegistrationHook * @covers ResourceLoader::register * @covers ResourceLoader::getModule */ public function testRegisteredValidModulesAreAccessible($name, ResourceLoaderModule $module, ResourceLoader $resourceLoader) { $resourceLoader->register($name, $module); $this->assertEquals($module, $resourceLoader->getModule($name)); }
/** * Get the stylesheet of the MediaWiki skin. * * @return string */ public function getCSS() { global $wgStyleDirectory; $moduleNames = array('mediawiki.legacy.shared', 'mediawiki.skinning.interface'); if (file_exists("{$wgStyleDirectory}/Vector/Vector.php")) { // Force loading Vector skin if available as a fallback skin // for whatever ResourceLoader wants to have as the default. // Include instead of require, as this will work without it, it will just look bad. // We need the 'global' statement for $wgResourceModules because the Vector skin adds the // definitions for its RL modules there that we use implicitly below. // @codingStandardsIgnoreStart global $wgResourceModules; // This is NOT UNUSED! // @codingStandardsIgnoreEnd include_once "{$wgStyleDirectory}/Vector/Vector.php"; $moduleNames[] = 'skins.vector.styles'; } $moduleNames[] = 'mediawiki.legacy.config'; $resourceLoader = new ResourceLoader(); $rlContext = new ResourceLoaderContext($resourceLoader, new FauxRequest(array('debug' => 'true', 'lang' => $this->getLanguageCode(), 'only' => 'styles'))); $styles = array(); foreach ($moduleNames as $moduleName) { /** @var ResourceLoaderFileModule $module */ $module = $resourceLoader->getModule($moduleName); // Based on: ResourceLoaderFileModule::getStyles (without the DB query) $styles = array_merge($styles, ResourceLoader::makeCombinedStyles($module->readStyleFiles($module->getStyleFiles($rlContext), $module->getFlip($rlContext)))); } return implode("\n", $styles); }
/** * Get the stylesheet of the MediaWiki skin. * * @return string */ public function getCSS() { global $wgStyleDirectory; $moduleNames = array('mediawiki.legacy.shared', 'mediawiki.skinning.interface'); $resourceLoader = new ResourceLoader(); if (file_exists("{$wgStyleDirectory}/Vector/skin.json")) { // Force loading Vector skin if available as a fallback skin // for whatever ResourceLoader wants to have as the default. $registry = new ExtensionRegistry(); $data = $registry->readFromQueue(array("{$wgStyleDirectory}/Vector/skin.json" => 1)); if (isset($data['globals']['wgResourceModules'])) { $resourceLoader->register($data['globals']['wgResourceModules']); } $moduleNames[] = 'skins.vector.styles'; } $moduleNames[] = 'mediawiki.legacy.config'; $rlContext = new ResourceLoaderContext($resourceLoader, new FauxRequest(array('debug' => 'true', 'lang' => $this->getLanguageCode(), 'only' => 'styles'))); $styles = array(); foreach ($moduleNames as $moduleName) { /** @var ResourceLoaderFileModule $module */ $module = $resourceLoader->getModule($moduleName); if (!$module) { // T98043: Don't fatal, but it won't look as pretty. continue; } // Based on: ResourceLoaderFileModule::getStyles (without the DB query) $styles = array_merge($styles, ResourceLoader::makeCombinedStyles($module->readStyleFiles($module->getStyleFiles($rlContext), $module->getFlip($rlContext)))); } return implode("\n", $styles); }
/** * Get the message blobs for a set of modules from the database. * Modules whose blobs are not in the database are silently dropped. * * @param ResourceLoader $resourceLoader * @param array $modules Array of module names * @param string $lang Language code * @throws MWException * @return array Array mapping module names to blobs */ private function getFromDB(ResourceLoader $resourceLoader, $modules, $lang) { if (!count($modules)) { return array(); } $retval = array(); $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('msg_resource', array('mr_blob', 'mr_resource', 'mr_timestamp'), array('mr_resource' => $modules, 'mr_lang' => $lang), __METHOD__); foreach ($res as $row) { $module = $resourceLoader->getModule($row->mr_resource); if (!$module) { // This shouldn't be possible throw new MWException(__METHOD__ . ' passed an invalid module name'); } // Update the module's blob if the list of messages changed $blobKeys = array_keys(FormatJson::decode($row->mr_blob, true)); $moduleMsgs = array_values(array_unique($module->getMessages())); if ($blobKeys !== $moduleMsgs) { $retval[$row->mr_resource] = $this->updateModule($row->mr_resource, $module, $lang); } else { $retval[$row->mr_resource] = $row->mr_blob; } } return $retval; }
/** * Get the message blobs for a set of modules from the database. * Modules whose blobs are not in the database are silently dropped. * * @param $resourceLoader ResourceLoader object * @param $modules Array of module names * @param $lang String: language code * @return array Array mapping module names to blobs */ private static function getFromDB(ResourceLoader $resourceLoader, $modules, $lang) { global $wgCacheEpoch; $retval = array(); $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('msg_resource', array('mr_blob', 'mr_resource', 'mr_timestamp'), array('mr_resource' => $modules, 'mr_lang' => $lang), __METHOD__); foreach ($res as $row) { $module = $resourceLoader->getModule($row->mr_resource); if (!$module) { // This shouldn't be possible throw new MWException(__METHOD__ . ' passed an invalid module name'); } // Update the module's blobs if the set of messages changed or if the blob is // older than $wgCacheEpoch if (array_keys(FormatJson::decode($row->mr_blob, true)) !== array_values(array_unique($module->getMessages())) || wfTimestamp(TS_MW, $row->mr_timestamp) <= $wgCacheEpoch) { $retval[$row->mr_resource] = self::updateModule($row->mr_resource, $module, $lang); } else { $retval[$row->mr_resource] = $row->mr_blob; } } return $retval; }
/** * 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; }
/** * Get the raw vector CSS, flipping if needed * * @todo Possibly get rid of this function and use ResourceLoader in the manner it was * designed to be used in, rather than just grabbing a list of filenames from it, * and not properly handling such details as media types in module definitions. * * @param string $dir 'ltr' or 'rtl' * @return String */ public function getCSS($dir) { // All CSS files these modules reference will be concatenated in sequence // and loaded as one file. $moduleNames = array('mediawiki.legacy.shared', 'skins.common.interface', 'skins.vector.styles', 'mediawiki.legacy.config'); $prepend = ''; $css = ''; $resourceLoader = new ResourceLoader(); foreach ($moduleNames as $moduleName) { /** @var ResourceLoaderFileModule $module */ $module = $resourceLoader->getModule($moduleName); $cssFileNames = $module->getAllStyleFiles(); wfSuppressWarnings(); foreach ($cssFileNames as $cssFileName) { if (!file_exists($cssFileName)) { $prepend .= ResourceLoader::makeComment("Unable to find {$cssFileName}."); continue; } if (!is_readable($cssFileName)) { $prepend .= ResourceLoader::makeComment("Unable to read {$cssFileName}. " . "Please check file permissions."); continue; } try { if (preg_match('/\\.less$/', $cssFileName)) { // Run the LESS compiler for *.less files (bug 55589) $compiler = ResourceLoader::getLessCompiler(); $cssFileContents = $compiler->compileFile($cssFileName); } else { // Regular CSS file $cssFileContents = file_get_contents($cssFileName); } if ($cssFileContents) { // Rewrite URLs, though don't bother embedding images. While static image // files may be cached, CSS returned by this function is definitely not. $cssDirName = dirname($cssFileName); $css .= CSSMin::remap($cssFileContents, $cssDirName, '..' . str_replace(array($GLOBALS['IP'], DIRECTORY_SEPARATOR), array('', '/'), $cssDirName), false); } else { $prepend .= ResourceLoader::makeComment("Unable to read {$cssFileName}."); } } catch (Exception $e) { $prepend .= ResourceLoader::formatException($e); } $css .= "\n"; } wfRestoreWarnings(); } $css = $prepend . $css; if ($dir == 'rtl') { $css = CSSJanus::transform($css, true); } return $css; }
/** * Get the message blobs for a set of modules from the database. * Modules whose blobs are not in the database are silently dropped. * * @param ResourceLoader $resourceLoader * @param array $modules Array of module names * @param string $lang Language code * @throws MWException * @return array Array mapping module names to blobs */ private function getFromDB(ResourceLoader $resourceLoader, $modules, $lang) { if (!count($modules)) { return array(); } $config = $resourceLoader->getConfig(); $retval = array(); $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('msg_resource', array('mr_blob', 'mr_resource', 'mr_timestamp'), array('mr_resource' => $modules, 'mr_lang' => $lang), __METHOD__); foreach ($res as $row) { $module = $resourceLoader->getModule($row->mr_resource); if (!$module) { // This shouldn't be possible throw new MWException(__METHOD__ . ' passed an invalid module name'); } // Update the module's blobs if the set of messages changed or if the blob is // older than the CacheEpoch setting $keys = array_keys(FormatJson::decode($row->mr_blob, true)); $values = array_values(array_unique($module->getMessages())); if ($keys !== $values || wfTimestamp(TS_MW, $row->mr_timestamp) <= $config->get('CacheEpoch')) { $retval[$row->mr_resource] = $this->updateModule($row->mr_resource, $module, $lang); } else { $retval[$row->mr_resource] = $row->mr_blob; } } return $retval; }
/** * Conditionally register the jquery.uls.data and jquery.i18n modules, in case they've already * been registered by the UniversalLanguageSelector extension. * * @param ResourceLoader $resourceLoader * @return boolean true */ public static function onResourceLoaderRegisterModules(ResourceLoader &$resourceLoader) { global $wgResourceModules; $veResourceTemplate = ConfigFactory::getDefaultInstance()->makeConfig('visualeditor')->get('VisualEditorResourceTemplate'); $libModules = array('jquery.uls.data' => $veResourceTemplate + array('scripts' => array('lib/ve/lib/jquery.uls/src/jquery.uls.data.js', 'lib/ve/lib/jquery.uls/src/jquery.uls.data.utils.js'), 'targets' => array('desktop', 'mobile')), 'jquery.i18n' => $veResourceTemplate + array('scripts' => array('lib/ve/lib/jquery.i18n/src/jquery.i18n.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.messagestore.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.parser.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.emitter.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.language.js'), 'dependencies' => 'mediawiki.libs.pluralruleparser', 'languageScripts' => array('bs' => 'lib/ve/lib/jquery.i18n/src/languages/bs.js', 'dsb' => 'lib/ve/lib/jquery.i18n/src/languages/dsb.js', 'fi' => 'lib/ve/lib/jquery.i18n/src/languages/fi.js', 'ga' => 'lib/ve/lib/jquery.i18n/src/languages/ga.js', 'he' => 'lib/ve/lib/jquery.i18n/src/languages/he.js', 'hsb' => 'lib/ve/lib/jquery.i18n/src/languages/hsb.js', 'hu' => 'lib/ve/lib/jquery.i18n/src/languages/hu.js', 'hy' => 'lib/ve/lib/jquery.i18n/src/languages/hy.js', 'la' => 'lib/ve/lib/jquery.i18n/src/languages/la.js', 'ml' => 'lib/ve/lib/jquery.i18n/src/languages/ml.js', 'os' => 'lib/ve/lib/jquery.i18n/src/languages/os.js', 'ru' => 'lib/ve/lib/jquery.i18n/src/languages/ru.js', 'sl' => 'lib/ve/lib/jquery.i18n/src/languages/sl.js', 'uk' => 'lib/ve/lib/jquery.i18n/src/languages/uk.js'), 'targets' => array('desktop', 'mobile'))); $addModules = array(); foreach ($libModules as $name => $data) { if (!isset($resourceModules[$name]) && !$resourceLoader->getModule($name)) { $addModules[$name] = $data; } } $resourceLoader->register($addModules); return true; }
/** * This ask the ResouceLoader for all registered files from modules * created by ResourceLoaderFileModule (or one of its descendants). * * * Since the raw data is stored in protected properties, we have to * overrride this through ReflectionObject methods. */ public static function provideResourceFiles() { global $wgEnableJavaScriptTest; // Test existance of test suite files as well // (can't use setUp or setMwGlobals because providers are static) $live_wgEnableJavaScriptTest = $wgEnableJavaScriptTest; $wgEnableJavaScriptTest = true; // Array with arguments for the test function $cases = array(); // Initialize ResourceLoader $rl = new ResourceLoader(); // See also ResourceLoaderFileModule::__construct $filePathProps = array('lists' => array('scripts', 'debugScripts', 'loaderScripts', 'styles'), 'nested-lists' => array('languageScripts', 'skinScripts', 'skinStyles')); foreach ($rl->getModuleNames() as $moduleName) { $module = $rl->getModule($moduleName); if (!$module instanceof ResourceLoaderFileModule) { continue; } $reflectedModule = new ReflectionObject($module); $files = array(); foreach ($filePathProps['lists'] as $propName) { $property = $reflectedModule->getProperty($propName); $property->setAccessible(true); $list = $property->getValue($module); foreach ($list as $key => $value) { // 'scripts' are numeral arrays. // 'styles' can be numeral or associative. // In case of associative the key is the file path // and the value is the 'media' attribute. if (is_int($key)) { $files[] = $value; } else { $files[] = $key; } } } foreach ($filePathProps['nested-lists'] as $propName) { $property = $reflectedModule->getProperty($propName); $property->setAccessible(true); $lists = $property->getValue($module); foreach ($lists as $group => $list) { foreach ($list as $key => $value) { // We need the same filter as for 'lists', // due to 'skinStyles'. if (is_int($key)) { $files[] = $value; } else { $files[] = $key; } } } } // Get method for resolving the paths to full paths $method = $reflectedModule->getMethod('getLocalPath'); $method->setAccessible(true); // Populate cases foreach ($files as $file) { $cases[] = array($method->invoke($module, $file), $module->getName(), $file); } } // Restore settings $wgEnableJavaScriptTest = $live_wgEnableJavaScriptTest; return $cases; }
/** * Conditionally register the oojs and oojs-ui modules, in case they've already been registered * by a more recent version of MediaWiki core. * * Also conditionally register the jquery.uls.data and jquery.i18n modules, in case they've already * been registered by the UniversalLanguageSelector extension. * * @param ResourceLoader $resourceLoader * @returns boolean true */ public static function onResourceLoaderRegisterModules(ResourceLoader &$resourceLoader) { global $wgResourceModules, $wgVisualEditorResourceTemplate; $libModules = array('oojs' => $wgVisualEditorResourceTemplate + array('scripts' => array('lib/ve/lib/oojs/oojs.js'), 'targets' => array('desktop', 'mobile')), 'oojs-ui' => $wgVisualEditorResourceTemplate + array('scripts' => array('lib/ve/lib/oojs-ui/oojs-ui.js'), 'styles' => array('lib/ve/lib/oojs-ui/oojs-ui.svg.css'), 'skinStyles' => array('default' => 'lib/ve/lib/oojs-ui/oojs-ui-apex.css'), 'messages' => array('ooui-dialog-action-close', 'ooui-outline-control-move-down', 'ooui-outline-control-move-up', 'ooui-outline-control-remove', 'ooui-toolbar-more', 'ooui-dialog-confirm-title', 'ooui-dialog-confirm-default-prompt', 'ooui-dialog-confirm-default-ok', 'ooui-dialog-confirm-default-cancel'), 'dependencies' => array('oojs'), 'targets' => array('desktop', 'mobile')), 'jquery.uls.data' => $wgVisualEditorResourceTemplate + array('scripts' => array('lib/ve/lib/jquery.uls/src/jquery.uls.data.js', 'lib/ve/lib/jquery.uls/src/jquery.uls.data.utils.js'), 'targets' => array('desktop', 'mobile')), 'jquery.i18n' => $wgVisualEditorResourceTemplate + array('scripts' => array('lib/ve/lib/jquery.i18n/src/jquery.i18n.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.messagestore.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.parser.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.emitter.js', 'lib/ve/lib/jquery.i18n/src/jquery.i18n.language.js'), 'languageScripts' => array('bs' => 'lib/ve/lib/jquery.i18n/src/languages/bs.js', 'dsb' => 'lib/ve/lib/jquery.i18n/src/languages/dsb.js', 'fi' => 'lib/ve/lib/jquery.i18n/src/languages/fi.js', 'ga' => 'lib/ve/lib/jquery.i18n/src/languages/ga.js', 'he' => 'lib/ve/lib/jquery.i18n/src/languages/he.js', 'hsb' => 'lib/ve/lib/jquery.i18n/src/languages/hsb.js', 'hu' => 'lib/ve/lib/jquery.i18n/src/languages/hu.js', 'hy' => 'lib/ve/lib/jquery.i18n/src/languages/hy.js', 'la' => 'lib/ve/lib/jquery.i18n/src/languages/la.js', 'ml' => 'lib/ve/lib/jquery.i18n/src/languages/ml.js', 'os' => 'lib/ve/lib/jquery.i18n/src/languages/os.js', 'ru' => 'lib/ve/lib/jquery.i18n/src/languages/ru.js', 'sl' => 'lib/ve/lib/jquery.i18n/src/languages/sl.js', 'uk' => 'lib/ve/lib/jquery.i18n/src/languages/uk.js'), 'targets' => array('desktop', 'mobile'))); $addModules = array(); foreach ($libModules as $name => $data) { if (!isset($wgResourceModules[$name]) && !$resourceLoader->getModule($name)) { $addModules[$name] = $data; } } $resourceLoader->register($addModules); return true; }