/** * @param $context ResourceLoaderContext * @return array */ public function getStyles(ResourceLoaderContext $context) { global $wgAllowUserCssPrefs, $wgUser; if ($wgAllowUserCssPrefs) { $options = $wgUser->getOptions(); // Build CSS rules $rules = array(); // Underline: 2 = browser default, 1 = always, 0 = never if ($options['underline'] < 2) { $rules[] = "a { text-decoration: " . ($options['underline'] ? 'underline' : 'none') . "; }"; } else { # The scripts of these languages are very hard to read with underlines $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(fa),a:lang(kk-arab), ' . 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }'; } if ($options['justify']) { $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n"; } if (!$options['showtoc']) { $rules[] = "#toc { display: none; }\n"; } if (!$options['editsection']) { $rules[] = ".editsection { display: none; }\n"; } if ($options['editfont'] !== 'default') { $rules[] = "textarea { font-family: {$options['editfont']}; }\n"; } $style = implode("\n", $rules); if ($this->getFlip($context)) { $style = CSSJanus::transform($style, true, false); } return array('all' => $style); } return array(); }
/** * @param ResourceLoaderContext $context * @return array */ public function getStyles(ResourceLoaderContext $context) { global $wgUser; if (!$this->getConfig()->get('AllowUserCssPrefs')) { return array(); } $options = $wgUser->getOptions(); // Build CSS rules $rules = array(); // Underline: 2 = browser default, 1 = always, 0 = never if ($options['underline'] < 2) { $rules[] = "a { text-decoration: " . ($options['underline'] ? 'underline' : 'none') . "; }"; } else { # The scripts of these languages are very hard to read with underlines $rules[] = 'a:lang(ar), a:lang(kk-arab), a:lang(mzn), ' . 'a:lang(ps), a:lang(ur) { text-decoration: none; }'; } if ($options['editfont'] !== 'default') { // Double-check that $options['editfont'] consists of safe characters only if (preg_match('/^[a-zA-Z0-9_, -]+$/', $options['editfont'])) { $rules[] = "textarea { font-family: {$options['editfont']}; }\n"; } } $style = implode("\n", $rules); if ($this->getFlip($context)) { $style = CSSJanus::transform($style, true, false); } else { $style = CSSJanus::nullTransform($style); } return array('all' => $style); }
/** * */ function setupSkinUserCss(OutputPage $out) { global $wgContLang; $qb = $this->qbSetting(); $rules = array(); if (2 == $qb) { # Right $rules[] = "#quickbar { position: absolute; top: 4px; right: 4px; border-left: 2px solid #000000; }"; $rules[] = "#article, #mw-data-after-content { margin-left: 4px; margin-right: 152px; }"; } elseif (1 == $qb || 3 == $qb) { $rules[] = "#quickbar { position: absolute; top: 4px; left: 4px; border-right: 1px solid gray; }"; $rules[] = "#article, #mw-data-after-content { margin-left: 152px; margin-right: 4px; }"; if (3 == $qb) { $rules[] = "#quickbar { position: fixed; padding: 4px; }"; } } elseif (4 == $qb) { $rules[] = "#quickbar { position: fixed; right: 0px; top: 0px; padding: 4px;}"; $rules[] = "#quickbar { border-right: 1px solid gray; }"; $rules[] = "#article, #mw-data-after-content { margin-right: 152px; margin-left: 4px; }"; } $style = implode("\n", $rules); if ($wgContLang->getDir() === 'rtl') { $style = CSSJanus::transform($style, true, false); } $out->addInlineStyle($style); parent::setupSkinUserCss($out); }
/** * @param ResourceLoaderContext $context * @return array */ public function getStyles(ResourceLoaderContext $context) { if (!$this->getConfig()->get('AllowUserCssPrefs')) { return []; } $options = $context->getUserObj()->getOptions(); // Build CSS rules $rules = []; // Underline: 2 = skin default, 1 = always, 0 = never if ($options['underline'] < 2) { $rules[] = "a { text-decoration: " . ($options['underline'] ? 'underline' : 'none') . "; }"; } $style = implode("\n", $rules); if ($this->getFlip($context)) { $style = CSSJanus::transform($style, true, false); } return ['all' => $style]; }
function setupSkinUserCss(OutputPage $out) { parent::setupSkinUserCss($out); $out->addModuleStyles('skins.simple'); /* Add some userprefs specific CSS styling */ global $wgUser, $wgContLang; $rules = array(); $underline = ""; if ($wgUser->getOption('underline') < 2) { $underline = "text-decoration: " . $wgUser->getOption('underline') ? 'underline' : 'none' . ";"; } /* Also inherits from resourceloader */ if (!$wgUser->getOption('highlightbroken')) { $rules[] = "a.new, a.stub { color: inherit; text-decoration: inherit;}"; $rules[] = "a.new:after { color: #CC2200; {$underline};}"; $rules[] = "a.stub:after { {$underline}; }"; } $style = implode("\n", $rules); if ($wgContLang->getDir() === 'rtl') { $style = CSSJanus::transform($style, true, false); } $out->addInlineStyle($style); }
/** * @param ResourceLoaderContext $context * @return array */ public function getStyles(ResourceLoaderContext $context) { if (!$this->getConfig()->get('AllowUserCssPrefs')) { return []; } $options = $context->getUserObj()->getOptions(); // Build CSS rules $rules = []; // Underline: 2 = skin default, 1 = always, 0 = never if ($options['underline'] < 2) { $rules[] = "a { text-decoration: " . ($options['underline'] ? 'underline' : 'none') . "; }"; } if ($options['editfont'] !== 'default') { // Double-check that $options['editfont'] consists of safe characters only if (preg_match('/^[a-zA-Z0-9_, -]+$/', $options['editfont'])) { $rules[] = "textarea { font-family: {$options['editfont']}; }\n"; } } $style = implode("\n", $rules); if ($this->getFlip($context)) { $style = CSSJanus::transform($style, true, false); } return ['all' => $style]; }
/** * Merges css and less files * * @param array $files Array with style files * @param string $styles Style code * @param string $prepend_prefix Prepend prefix * @param array $params additional params */ function fn_merge_styles($files, $styles = '', $prepend_prefix = '', $params = array(), $area = AREA) { $prefix = !empty($prepend_prefix) ? 'embedded' : 'standalone'; $make_rtl = false; if (fn_is_rtl_language()) { $prefix .= '-rtl'; $make_rtl = true; } $output = ''; $less_output = ''; $less_reflection = array(); $compiled_less = ''; $compiled_css = ''; $relative_path = fn_get_theme_path('[relative]/[theme]/css', $area); $hashes = array(); $names = array_map(function ($v) { return !empty($v['relative']) ? $v['relative'] : false; }, $files); // Check file changes if (Development::isEnabled('compile_check') || Debugger::isActive()) { $dir_root = Registry::get('config.dir.root'); foreach ($names as $index => $name) { if (file_exists($dir_root . '/' . $name)) { $hashes[] = $name . filemtime($dir_root . '/' . $name); } } } $hashes[] = md5(implode('|', $names)); $hashes[] = md5($styles); if ($area == 'C') { $hashes[] = Registry::get('runtime.layout.layout_id'); $hashes[] = Registry::get('runtime.layout.style_id'); } arsort($hashes); $hash = md5(implode(',', $hashes) . PRODUCT_VERSION) . fn_get_storage_data('cache_id'); $filename = $prefix . '.' . $hash . '.css'; $theme_manifest = Themes::factory(fn_get_theme_path('[theme]', 'C'))->getManifest(); if (!Storage::instance('assets')->isExist($relative_path . '/' . $filename)) { Debugger::checkpoint('Before styles compilation'); foreach ($files as $src) { $m_prefix = ''; $m_suffix = ''; if (!empty($src['media'])) { $m_prefix = "\n@media " . $src['media'] . " {\n"; $m_suffix = "\n}\n"; } if (strpos($src['file'], '.css') !== false) { $output .= "\n" . $m_prefix . fn_get_contents($src['file']) . $m_suffix; } elseif ($area != 'C' || empty($theme_manifest['converted_to_css'])) { $less_output_chunk = ''; if (file_exists($src['file'])) { if ($area == 'C' && (empty($theme_manifest['parent_theme']) || $theme_manifest['parent_theme'] == 'basic')) { $less_output_chunk = "\n" . $m_prefix . fn_get_contents($src['file']) . $m_suffix; } else { $less_output_chunk = "\n" . $m_prefix . '@import "' . str_replace($relative_path . '/', '', $src['relative']) . '";' . $m_suffix; } } if (!empty($params['reflect_less'])) { if (preg_match('{/addons/([^/]+)/}is', $src['relative'], $m)) { $less_reflection['output']['addons'][$m[1]] .= $less_output_chunk; } else { $less_reflection['output']['main'] .= $less_output_chunk; } } $less_output .= $less_output_chunk; } } $header = str_replace('[files]', implode("\n", $names), Registry::get('config.js_css_cache_msg')); if (!empty($styles)) { $less_output .= $styles; } // Prepend all styles with prefix if (!empty($prepend_prefix)) { $less_output = $output . "\n" . $less_output; $output = ''; } if (!empty($output)) { $compiled_css = Less::parseUrls($output, Storage::instance('assets')->getAbsolutePath($relative_path), fn_get_theme_path('[themes]/[theme]/media', $area)); } if (!empty($theme_manifest['converted_to_css']) && $area == 'C') { $theme_css_path = fn_get_theme_path('[themes]/[theme]', $area) . '/css'; $pcl_filepath = $theme_css_path . '/' . Themes::$compiled_less_filename; if (file_exists($pcl_filepath)) { $compiled_css .= fn_get_contents($pcl_filepath); } list($installed_addons) = fn_get_addons(array('type' => 'active')); foreach ($installed_addons as $addon) { $addon_pcl_filpath = $theme_css_path . "/addons/{$addon['addon']}/" . Themes::$compiled_less_filename; if (file_exists($pcl_filepath)) { $compiled_css .= fn_get_contents($addon_pcl_filpath); } } } if (!empty($less_output)) { $less = new Less(); if (!empty($params['compressed'])) { $less->setFormatter('compressed'); } $less->setImportDir($relative_path); try { $compiled_less = $less->customCompile($less_output, Storage::instance('assets')->getAbsolutePath($relative_path), array(), $prepend_prefix, $area); } catch (Exception $e) { $skip_save = true; $shift = 4; $message = '<div style="border: 2px solid red; padding: 5px;">LESS ' . $e->getMessage(); if (preg_match("/line: (\\d+)/", $message, $m)) { $lo = explode("\n", $less_output); $message .= '<br /><br /><pre>' . implode("\n", array_splice($lo, intval($m[1]) - $shift, $shift * 2)) . '</pre>'; } $message .= '</div>'; fn_set_notification('E', __('error'), $message); } } if (empty($skip_save)) { $compiled_content = $compiled_css . "\n" . $compiled_less; // Move all @import links to the Top of the file. if (preg_match_all('/@import url.*?;/', $compiled_content, $imports)) { $compiled_content = preg_replace('/@import url.*?;/', '', $compiled_content); foreach ($imports[0] as $import_link) { $compiled_content = $import_link . "\n" . $compiled_content; } } if ($make_rtl) { $compiled_content = \CSSJanus::transform($compiled_content); $compiled_content = "body {\ndirection: rtl;\n}\n" . $compiled_content; } Storage::instance('assets')->put($relative_path . '/' . $filename, array('contents' => $header . $compiled_content, 'compress' => false, 'caching' => true)); if (!empty($params['use_scheme'])) { fn_put_contents(fn_get_cache_path(false) . 'theme_editor/' . $filename, $output . '#LESS#' . $less_output); } if (!empty($params['reflect_less'])) { $less_reflection['import_dirs'] = array($relative_path); fn_put_contents(fn_get_cache_path(false) . 'less_reflection.json', json_encode($less_reflection)); } } Debugger::checkpoint('After styles compilation'); } $url = Storage::instance('assets')->getUrl($relative_path . '/' . $filename); return $url; }
/** * @param $context ResourceLoaderContext * @return array */ public function getStyles(ResourceLoaderContext $context) { global $wgScriptPath; $styles = array(); foreach ($this->getPages($context) as $titleText => $options) { if ($options['type'] !== 'style') { continue; } $title = Title::newFromText($titleText); if (!$title || $title->isRedirect()) { continue; } $media = isset($options['media']) ? $options['media'] : 'all'; $style = $this->getContent($title); if (strval($style) === '') { continue; } if ($this->getFlip($context)) { $style = CSSJanus::transform($style, true, false); } $style = CSSMin::remap($style, false, $wgScriptPath, true); if (!isset($styles[$media])) { $styles[$media] = array(); } if (strpos($titleText, '*/') === false) { $style = "/* {$titleText} */\n" . $style; } $styles[$media][] = $style; } return $styles; }
/** * Reads a style file. * * This method can be used as a callback for array_map() * * @param $path String: File path of style file to read * @param $flip bool * * @return String: CSS data in script file * @throws MWException if the file doesn't exist */ protected function readStyleFile($path, $flip) { $localPath = $this->getLocalPath($path); if (!file_exists($localPath)) { $msg = __METHOD__ . ": style file not found: \"{$localPath}\""; wfDebugLog('resourceloader', $msg); throw new MWException($msg); } $style = file_get_contents($localPath); if ($flip) { $style = CSSJanus::transform($style, true, false); } $dirname = dirname($path); if ($dirname == '.') { // If $path doesn't have a directory component, don't prepend a dot $dirname = ''; } $dir = $this->getLocalPath($dirname); $remoteDir = $this->getRemotePath($dirname); // Get and register local file references $this->localFileRefs = array_merge($this->localFileRefs, CSSMin::getLocalFileReferences($style, $dir)); return CSSMin::remap($style, $dir, $remoteDir, true); }
/** * Reads a style file. * * This method can be used as a callback for array_map() * * @param $path String: File path of style file to read * @param $flip bool * * @return String: CSS data in script file * @throws MWException if the file doesn't exist */ protected function readStyleFile($path, $flip, ResourceLoaderContext $context) { $localPath = $this->getLocalPath($path); if (!file_exists($localPath)) { throw new MWException(__METHOD__ . ": style file not found: \"{$localPath}\""); } // Wikia - change begin - @author: wladek $style = self::getFileContents($localPath, $context); // Wikia - change end if ($flip) { $style = CSSJanus::transform($style, true, false); } $dirname = dirname($path); if ($dirname == '.') { // If $path doesn't have a directory component, don't prepend a dot $dirname = ''; } $dir = $this->getLocalPath($dirname); $remoteDir = $this->getRemotePath($dirname, $context); // Get and register local file references $this->localFileRefs = array_merge($this->localFileRefs, CSSMin::getLocalFileReferences($style, $dir)); return CSSMin::remap($style, $dir, $remoteDir, true, $this->localBasePath); }
/** * @param $context ResourceLoaderContext * @return array */ public function getStyles(ResourceLoaderContext $context) { global $wgAllowUserCssPrefs; if ($wgAllowUserCssPrefs) { $options = $this->contextUserOptions($context); // Build CSS rules $rules = array(); if ($options['underline'] < 2) { $rules[] = "a { text-decoration: " . ($options['underline'] ? 'underline' : 'none') . "; }"; } else { # The scripts of these languages are very hard to read with underlines $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(fa),a:lang(kk-arab), ' . 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }'; } if ($options['highlightbroken']) { $rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n"; } else { $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }"; $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }"; $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }"; } if ($options['justify']) { $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n"; } if (!$options['showtoc']) { $rules[] = "#toc { display: none; }\n"; } if (!$options['editsection']) { $rules[] = ".editsection { display: none; }\n"; } if ($options['editfont'] !== 'default') { $rules[] = "textarea { font-family: {$options['editfont']}; }\n"; } $style = implode("\n", $rules); if ($this->getFlip($context)) { $style = CSSJanus::transform($style, true, false); } return array('all' => $style); } return array(); }
/** * Build exempt modules and legacy non-ResourceLoader styles. * * @return string|WrappedStringList HTML */ protected function buildExemptModules() { global $wgContLang; $resourceLoader = $this->getResourceLoader(); $chunks = []; // Things that go after the ResourceLoaderDynamicStyles marker $append = []; // Exempt 'user' styles module (may need 'excludepages' for live preview) if ($this->isUserCssPreview()) { $append[] = $this->makeResourceLoaderLink('user.styles', ResourceLoaderModule::TYPE_STYLES, ['excludepage' => $this->getTitle()->getPrefixedDBkey()]); // Load the previewed CSS. Janus it if needed. // User-supplied CSS is assumed to in the wiki's content language. $previewedCSS = $this->getRequest()->getText('wpTextbox1'); if ($this->getLanguage()->getDir() !== $wgContLang->getDir()) { $previewedCSS = CSSJanus::transform($previewedCSS, true, false); } $append[] = Html::inlineStyle($previewedCSS); } // We want site, private and user styles to override dynamically added styles from // general modules, but we want dynamically added styles to override statically added // style modules. So the order has to be: // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml()) // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles) // - ResourceLoaderDynamicStyles marker // - site/private/user styles // Add legacy styles added through addStyle()/addInlineStyle() here $chunks[] = implode('', $this->buildCssLinksArray()) . $this->mInlineStyles; $chunks[] = Html::element('meta', ['name' => 'ResourceLoaderDynamicStyles', 'content' => '']); foreach ($this->rlExemptStyleModules as $group => $moduleNames) { $chunks[] = $this->makeResourceLoaderLink($moduleNames, ResourceLoaderModule::TYPE_STYLES); } return self::combineWrappedStrings(array_merge($chunks, $append)); }
/** * 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 raw vector CSS, flipping if needed * @param $dir String 'ltr' or 'rtl' * @return String */ public function getCSS($dir) { $skinDir = dirname(dirname(dirname(__FILE__))) . '/skins'; $vectorCssFile = "{$skinDir}/vector/screen.css"; $configCssFile = "{$skinDir}/common/config.css"; $css = ''; wfSuppressWarnings(); $vectorCss = file_get_contents($vectorCssFile); $configCss = file_get_contents($configCssFile); wfRestoreWarnings(); if (!$vectorCss || !$configCss) { $css = "/** Your webserver cannot read {$vectorCssFile} or {$configCssFile}, please check file permissions */"; } $css .= str_replace('images/', '../skins/vector/images/', $vectorCss) . "\n" . str_replace('images/', '../skins/common/images/', $configCss); if ($dir == 'rtl') { $css = CSSJanus::transform($css, true); } return $css; }
/** * @dataProvider provideData */ public function testTransform($input, $settings, $output, $name) { $this->assertEquals($output, CSSJanus::transform($input, $settings['swapLtrRtlInUrl'], $settings['swapLeftRightInUrl']), $name); }
/** * Adds inline CSS styles * @param $style_css Mixed: inline CSS * @param $flip False or String: Set to 'flip' to flip the CSS if needed */ public function addInlineStyle($style_css, $flip = false) { if ($flip === 'flip' && $this->getLang()->isRTL()) { # If wanted, and the interface is right-to-left, flip the CSS $style_css = CSSJanus::transform($style_css, true, false); } $this->mInlineStyles .= Html::inlineStyle($style_css); }
/** * Reads a style file. * * This method can be used as a callback for array_map() * * @param string $path File path of style file to read * @param bool $flip * @param ResourceLoaderContext $context (optional) * * @return string CSS data in script file * @throws MWException If the file doesn't exist */ protected function readStyleFile($path, $flip, $context = null) { $localPath = $this->getLocalPath($path); $remotePath = $this->getRemotePath($path); if (!file_exists($localPath)) { $msg = __METHOD__ . ": style file not found: \"{$localPath}\""; wfDebugLog('resourceloader', $msg); throw new MWException($msg); } if ($this->getStyleSheetLang($localPath) === 'less') { $compiler = $this->getLessCompiler($context); $style = $this->compileLessFile($localPath, $compiler); $this->hasGeneratedStyles = true; } else { $style = file_get_contents($localPath); } if ($flip) { $style = CSSJanus::transform($style, true, false); } $localDir = dirname($localPath); $remoteDir = dirname($remotePath); // Get and register local file references $localFileRefs = CSSMin::getAllLocalFileReferences($style, $localDir); foreach ($localFileRefs as $file) { if (file_exists($file)) { $this->localFileRefs[] = $file; } else { $this->missingLocalFileRefs[] = $file; } } return CSSMin::remap($style, $localDir, $remoteDir, true); }
function setupSkinUserCss(OutputPage $out) { global $wgContLang; $qb = $this->qbSetting(); $rules = array(); if (2 == $qb) { # Right $rules[] = "#quickbar { position: absolute; right: 4px; }"; $rules[] = "#article { margin-left: 4px; margin-right: 148px; }"; } elseif (1 == $qb) { $rules[] = "#quickbar { position: absolute; left: 4px; }"; $rules[] = "#article { margin-left: 148px; margin-right: 4px; }"; } elseif (3 == $qb) { # Floating left $rules[] = "#quickbar { position:absolute; left:4px }"; $rules[] = "#topbar { margin-left: 148px }"; $rules[] = "#article { margin-left:148px; margin-right: 4px; }"; $rules[] = "body>#quickbar { position:fixed; left:4px; top:4px; overflow:auto ;bottom:4px;}"; # Hides from IE } elseif (4 == $qb) { # Floating right $rules[] = "#quickbar { position: fixed; right: 4px; }"; $rules[] = "#topbar { margin-right: 148px }"; $rules[] = "#article { margin-right: 148px; margin-left: 4px; }"; $rules[] = "body>#quickbar { position: fixed; right: 4px; top: 4px; overflow: auto ;bottom:4px;}"; # Hides from IE } $style = implode("\n", $rules); if ($wgContLang->getDir() === 'rtl') { $style = CSSJanus::transform($style, true, false); } $out->addInlineStyle($style); parent::setupSkinUserCss($out); }
/** * Reads a style file. * * This method can be used as a callback for array_map() * * @param string $path File path of style file to read * @param bool $flip * @param ResourceLoaderContext $context * * @return string CSS data in script file * @throws MWException If the file doesn't exist */ protected function readStyleFile($path, $flip, $context) { $localPath = $this->getLocalPath($path); $remotePath = $this->getRemotePath($path); if (!file_exists($localPath)) { $msg = __METHOD__ . ": style file not found: \"{$localPath}\""; wfDebugLog('resourceloader', $msg); throw new MWException($msg); } if ($this->getStyleSheetLang($localPath) === 'less') { $style = $this->compileLessFile($localPath, $context); $this->hasGeneratedStyles = true; } else { $style = file_get_contents($localPath); } if ($flip) { $style = CSSJanus::transform($style, true, false); } $localDir = dirname($localPath); $remoteDir = dirname($remotePath); // Get and register local file references $localFileRefs = CSSMin::getLocalFileReferences($style, $localDir); foreach ($localFileRefs as $file) { if (file_exists($file)) { $this->localFileRefs[] = $file; } else { $this->missingLocalFileRefs[] = $file; } } // Don't cache this call. remap() ensures data URIs embeds are up to date, // and urls contain correct content hashes in their query string. (T128668) return CSSMin::remap($style, $localDir, $remoteDir, true); }
/** * @dataProvider provideTransformBrokenCases * @group Broken */ function testTransformBroken($code, $expectedOutput) { $flipped = CSSJanus::transform($code); $this->assertEquals($expectedOutput, $flipped, 'Test flipping'); }
/** * Get the raw vector CSS, flipping if needed * @param string $dir 'ltr' or 'rtl' * @return String */ public function getCSS($dir) { $skinDir = dirname(dirname(__DIR__)) . '/skins'; // All these files will be concatenated in sequence and loaded // as one file. // The string 'images/' in the files' contents will be replaced // by '../skins/$skinName/images/', where $skinName is what appears // before the last '/' in each of the strings. $cssFileNames = array('common/shared.css', 'common/commonElements.css', 'common/commonContent.css', 'common/commonInterface.css', 'vector/screen.css', 'common/config.css'); $css = ''; wfSuppressWarnings(); foreach ($cssFileNames as $cssFileName) { $fullCssFileName = "{$skinDir}/{$cssFileName}"; $cssFileContents = file_get_contents($fullCssFileName); if ($cssFileContents) { preg_match("/^(\\w+)\\//", $cssFileName, $match); $skinName = $match[1]; $css .= str_replace('images/', "../skins/{$skinName}/images/", $cssFileContents); } else { $css .= "/** Your webserver cannot read {$fullCssFileName}. Please check file permissions. */"; } $css .= "\n"; } wfRestoreWarnings(); if ($dir == 'rtl') { $css = CSSJanus::transform($css, true); } return $css; }
/** * @param ResourceLoaderContext $context * @return array */ public function getStyles(ResourceLoaderContext $context) { $styles = array(); foreach ($this->getPages($context) as $titleText => $options) { if ($options['type'] !== 'style') { continue; } $media = isset($options['media']) ? $options['media'] : 'all'; $style = $this->getContent($titleText); if (strval($style) === '') { continue; } if ($this->getFlip($context)) { $style = CSSJanus::transform($style, true, false); } $style = CSSMin::remap($style, false, $this->getConfig()->get('ScriptPath'), true); if (!isset($styles[$media])) { $styles[$media] = array(); } $style = ResourceLoader::makeComment($titleText) . $style; $styles[$media][] = $style; } return $styles; }
/** * Build a set of "<link>" elements for the stylesheets specified in the $this->styles array. * These will be applied to various media & IE conditionals. * * @return string */ public function buildCssLinks() { global $wgContLang; $this->getSkin()->setupSkinUserCss($this); // Add ResourceLoader styles // Split the styles into these groups $styles = array('other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array()); $links = array(); $otherTags = ''; // Tags to append after the normal <link> tags $resourceLoader = $this->getResourceLoader(); $moduleStyles = $this->getModuleStyles(true, 'top'); // Per-site custom styles $moduleStyles[] = 'site'; $moduleStyles[] = 'noscript'; $moduleStyles[] = 'user.groups'; // Per-user custom styles if ($this->getConfig()->get('AllowUserCss') && $this->getTitle()->isCssSubpage() && $this->userCanPreview()) { // We're on a preview of a CSS subpage // Exclude this page from the user module in case it's in there (bug 26283) $link = $this->makeResourceLoaderLink('user', ResourceLoaderModule::TYPE_STYLES, false, array('excludepage' => $this->getTitle()->getPrefixedDBkey())); $otherTags .= $link['html']; // Load the previewed CSS // If needed, Janus it first. This is user-supplied CSS, so it's // assumed to be right for the content language directionality. $previewedCSS = $this->getRequest()->getText('wpTextbox1'); if ($this->getLanguage()->getDir() !== $wgContLang->getDir()) { $previewedCSS = CSSJanus::transform($previewedCSS, true, false); } $otherTags .= Html::inlineStyle($previewedCSS) . "\n"; } else { // Load the user styles normally $moduleStyles[] = 'user'; } // Per-user preference styles $moduleStyles[] = 'user.cssprefs'; foreach ($moduleStyles as $name) { $module = $resourceLoader->getModule($name); if (!$module) { continue; } $group = $module->getGroup(); // Modules in groups different than the ones listed on top (see $styles assignment) // will be placed in the "other" group $styles[isset($styles[$group]) ? $group : 'other'][] = $name; } // We want site, private and user styles to override dynamically added // styles from modules, but we want dynamically added styles to override // statically added styles from other modules. So the order has to be // other, dynamic, site, private, user. Add statically added styles for // other modules $links[] = $this->makeResourceLoaderLink($styles['other'], ResourceLoaderModule::TYPE_STYLES); // Add normal styles added through addStyle()/addInlineStyle() here $links[] = implode("\n", $this->buildCssLinksArray()) . $this->mInlineStyles; // Add marker tag to mark the place where the client-side loader should inject dynamic styles // We use a <meta> tag with a made-up name for this because that's valid HTML $links[] = Html::element('meta', array('name' => 'ResourceLoaderDynamicStyles', 'content' => '')) . "\n"; // Add site, private and user styles // 'private' at present only contains user.options, so put that before 'user' // Any future private modules will likely have a similar user-specific character foreach (array('site', 'noscript', 'private', 'user') as $group) { $links[] = $this->makeResourceLoaderLink($styles[$group], ResourceLoaderModule::TYPE_STYLES); } // Add stuff in $otherTags (previewed user CSS if applicable) return self::getHtmlFromLoaderLinks($links) . $otherTags; }
/** * Reads a style file. * * This method can be used as a callback for array_map() * * @param $path String: File path of script file to read * @return String: CSS data in script file */ protected function readStyleFile($path, $flip) { $localPath = $this->getLocalPath($path); $style = file_get_contents($localPath); if ($style === false) { throw new MWException(__METHOD__ . ": style file not found: \"{$localPath}\""); } if ($flip) { $style = CSSJanus::transform($style, true, false); } $dir = $this->getLocalPath(dirname($path)); $remoteDir = $this->getRemotePath(dirname($path)); // Get and register local file references $this->localFileRefs = array_merge($this->localFileRefs, CSSMin::getLocalFileReferences($style, $dir)); return CSSMin::remap($style, $dir, $remoteDir, true); }