public function test_background() { $optimiser = new css_optimiser(); $cssin = '.test {background-color: #123456;}'; $this->assertSame($cssin, $optimiser->process($cssin)); $this->assertDebuggingCalled('class css_optimiser is deprecated and no longer does anything, ' . 'please consider using stylelint to optimise your css.'); }
/** * Stores CSS in a file at the given path. * * This function either succeeds or throws an exception. * * @param theme_config $theme The theme that the CSS belongs to. * @param string $csspath The path to store the CSS at. * @param array $cssfiles The CSS files to store. */ function css_store_css(theme_config $theme, $csspath, array $cssfiles) { global $CFG; // Check if both the CSS optimiser is enabled and the theme supports it. if (!empty($CFG->enablecssoptimiser) && $theme->supportscssoptimisation) { // This is an experimental feature introduced in Moodle 2.3 // The CSS optimiser organises the CSS in order to reduce the overall number // of rules and styles being sent to the client. It does this by collating // the CSS before it is cached removing excess styles and rules and stripping // out any extraneous content such as comments and empty rules. $optimiser = new css_optimiser; $css = ''; foreach ($cssfiles as $file) { $css .= file_get_contents($file)."\n"; } $css = $theme->post_process($css); $css = $optimiser->process($css); // If cssoptimisestats is set then stats from the optimisation are collected // and output at the beginning of the CSS if (!empty($CFG->cssoptimiserstats)) { $css = $optimiser->output_stats_css().$css; } } else { // This is the default behaviour. // The cssoptimise setting was introduced in Moodle 2.3 and will hopefully // in the future be changed from an experimental setting to the default. // The css_minify_css will method will use the Minify library remove // comments, additional whitespace and other minor measures to reduce the // the overall CSS being sent. // However it has the distinct disadvantage of having to minify the CSS // before running the post process functions. Potentially things may break // here if theme designers try to push things with CSS post processing. $css = $theme->post_process(css_minify_css($cssfiles)); } clearstatcache(); if (!file_exists(dirname($csspath))) { @mkdir(dirname($csspath), $CFG->directorypermissions, true); } // Prevent serving of incomplete file from concurrent request, // the rename() should be more atomic than fwrite(). ignore_user_abort(true); if ($fp = fopen($csspath.'.tmp', 'xb')) { fwrite($fp, $css); fclose($fp); rename($csspath.'.tmp', $csspath); @chmod($csspath, $CFG->filepermissions); @unlink($csspath.'.tmp'); // just in case anything fails } ignore_user_abort(false); if (connection_aborted()) { die; } }
/** * This function tests some of the broken crazy CSS we have in Moodle. * For each of these things the value needs to be corrected if we can be 100% * certain what is going wrong, Or it needs to be left as is. * * @param css_optimiser $optimiser */ public function try_broken_css_found_in_moodle(css_optimiser $optimiser) { // Notice how things are out of order here but that they get corrected $cssin = '.test {background:url([[pix:theme|pageheaderbgred]]) top center no-repeat}'; $cssout = '.test{background:url([[pix:theme|pageheaderbgred]]) no-repeat top center;}'; $this->assertEqual($cssout, $optimiser->process($cssin)); // Cursor hand isn't valid $cssin = '.test {cursor: hand;}'; $cssout = '.test{cursor:hand;}'; $this->assertEqual($cssout, $optimiser->process($cssin)); // Zoom property isn't valid $cssin = '.test {zoom: 1;}'; $cssout = '.test{zoom:1;}'; $this->assertEqual($cssout, $optimiser->process($cssin)); // Left isn't a valid position property $cssin = '.test {position: left;}'; $cssout = '.test{position:left;}'; $this->assertEqual($cssout, $optimiser->process($cssin)); // The dark red color isn't a valid HTML color but has a standardised // translation of #8B0000 $cssin = '.test {color: darkred;}'; $cssout = '.test{color:#8B0000;}'; $this->assertEqual($cssout, $optimiser->process($cssin)); // You can't use argb colours as border colors $cssin = '.test {border-bottom: 1px solid rgba(0,0,0,0.25);}'; $cssout = '.test{border-bottom:1px solid rgba(0,0,0,0.25);}'; $this->assertEqual($cssout, $optimiser->process($cssin)); // Opacity with annoying IE equivilants.... $cssin = '.test {opacity: 0.5; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50);}'; $cssout = '.test{opacity:0.5;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";filter:alpha(opacity=50);}'; $this->assertEqual($cssout, $optimiser->process($cssin)); }
/** * Get the theme designer css markup, * the parameters are coming from css_urls(). * * NOTE: this method is not expected to be used from any addons. * * @param string $type * @param string $subtype * @param string $sheet * @return string CSS markup */ public function get_css_content_debug($type, $subtype, $sheet) { global $CFG; require_once $CFG->dirroot . '/lib/csslib.php'; // The LESS file of the theme is requested. if ($type === 'less') { $csscontent = $this->get_css_content_from_less(true); if ($csscontent !== false) { return $csscontent; } return ''; } $optimiser = null; if (!empty($CFG->enablecssoptimiser) && $this->supportscssoptimisation) { // This is an experimental feature introduced in Moodle 2.3 // The CSS optimiser organises the CSS in order to reduce the overall number // of rules and styles being sent to the client. It does this by collating // the CSS before it is cached removing excess styles and rules and stripping // out any extraneous content such as comments and empty rules. $optimiser = new css_optimiser(); } $cssfiles = array(); $css = $this->get_css_files(true); if ($type === 'ie') { // IE is a sloppy browser with weird limits, sorry. if ($subtype === 'plugins') { $cssfiles = $css['plugins']; } else { if ($subtype === 'parents') { if (empty($sheet)) { // Do not bother with the empty parent here. } else { // Build up the CSS for that parent so we can serve it as one file. foreach ($css[$subtype][$sheet] as $parent => $css) { $cssfiles[] = $css; } } } else { if ($subtype === 'theme') { $cssfiles = $css['theme']; foreach ($cssfiles as $key => $value) { if ($this->lessfile && $key === $this->lessfile) { // Remove the LESS file from the theme CSS files. // The LESS files use the type 'less', not 'ie'. unset($cssfiles[$key]); } } } } } } else { if ($type === 'plugin') { if (isset($css['plugins'][$subtype])) { $cssfiles[] = $css['plugins'][$subtype]; } } else { if ($type === 'parent') { if (isset($css['parents'][$subtype][$sheet])) { $cssfiles[] = $css['parents'][$subtype][$sheet]; } } else { if ($type === 'theme') { if (isset($css['theme'][$sheet])) { $cssfiles[] = $css['theme'][$sheet]; } } } } } $csscontent = ''; foreach ($cssfiles as $file) { $contents = file_get_contents($file); $contents = $this->post_process($contents); $comment = "/** Path: {$type} {$subtype} {$sheet}.' **/\n"; $stats = ''; if ($optimiser) { $contents = $optimiser->process($contents); if (!empty($CFG->cssoptimiserstats)) { $stats = $optimiser->output_stats_css(); } } $csscontent .= $comment . $stats . $contents . "\n\n"; } return $csscontent; }
/** * Sends CSS directly without caching it. * * This function takes a raw CSS string, optimises it if required, and then * serves it. * Turning both themedesignermode and CSS optimiser on at the same time is aweful * for performance because of the optimiser running here. However it was done so * that theme designers could utilise the optimised output during development to * help them optimise their CSS... not that they should write lazy CSS. * * @param string CSS */ function css_send_uncached_css($css) { global $CFG; header('Content-Disposition: inline; filename="styles_debug.php"'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) . ' GMT'); header('Pragma: '); header('Accept-Ranges: none'); header('Content-Type: text/css; charset=utf-8'); if (is_array($css)) { $css = implode("\n\n", $css); } if (!empty($CFG->enablecssoptimiser)) { $css = str_replace("\n", "\r\n", $css); $optimiser = new css_optimiser(); $css = $optimiser->process($css); if (!empty($CFG->cssoptimiserstats)) { $css = $optimiser->output_stats_css() . $css; } } echo $css; die; }
/** * Test media declarations. */ public function test_media_rules() { $optimiser = new css_optimiser(); $cssin = "@media print {\n .test{background-color:#333;}\n}"; $cssout = "@media print { .test{background-color:#333;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media screen and (min-width:30px) {\n #region-main-box{left: 30px;float: left;}\n}"; $cssout = "@media screen and (min-width:30px) { #region-main-box{left:30px;float:left;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media all and (min-width:500px) {\n #region-main-box{left:30px;float:left;}\n}"; $cssout = "@media all and (min-width:500px) { #region-main-box{left:30px;float:left;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media (min-width:500px) {\n #region-main-box{left:30px;float:left;}\n}"; $cssout = "@media (min-width:500px) { #region-main-box{left:30px;float:left;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media screen and (color), projection and (color) {\n #region-main-box{left:30px;float:left;}\n}"; $cssout = "@media screen and (color),projection and (color) { #region-main-box{left:30px;float:left;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media print {\n .test{background-color:#000;}\n}@media print {\n .test{background-color:#FFF;}\n}"; $cssout = "@media print { .test{background-color:#FFF;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media screen and (min-width:30px) {\n #region-main-box{background-color:#000;}\n}\n@media screen and (min-width:30px) {\n #region-main-box{background-color:#FFF;}\n}"; $cssout = "@media screen and (min-width:30px) { #region-main-box{background-color:#FFF;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media screen and (min-width:30px) {\n #region-main-box{background-color:#000;}\n}\n@media screen and (min-width:31px) {\n #region-main-box{background-color:#FFF;}\n}"; $cssout = "@media screen and (min-width:30px) { #region-main-box{background-color:#000;} }\n@media screen and (min-width:31px) { #region-main-box{background-color:#FFF;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media (min-width: 768px) and (max-width: 979px) {\n*{*zoom:1;}}"; $cssout = "@media (min-width: 768px) and (max-width: 979px) { *{*zoom:1;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "#test {min-width:1200px;}@media (min-width: 768px) {#test {min-width: 1024px;}}"; $cssout = "#test{min-width:1200px;} \n@media (min-width: 768px) { #test{min-width:1024px;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@media(min-width:768px){#page-calender-view .container fluid{min-width:1024px}}.section_add_menus{text-align:right}"; $cssout = ".section_add_menus{text-align:right;} \n@media (min-width:768px) { #page-calender-view .container fluid{min-width:1024px;} }"; $this->assertSame($cssout, $optimiser->process($cssin)); $cssin = "@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}"; $cssout = "@-ms-keyframes progress-bar-stripes {from{background-position:40px 0;}to{background-position:0 0;}}"; $this->assertSame($cssout, $optimiser->process($cssin)); }
/** * Stores CSS in a file at the given path. * * This function either succeeds or throws an exception. * * @param theme_config $theme The theme that the CSS belongs to. * @param string $csspath The path to store the CSS at. * @param array $cssfiles The CSS files to store. * @param bool $chunk If set to true these files will be chunked to ensure * that no one file contains more than 4095 selectors. * @param string $chunkurl If the CSS is be chunked then we need to know the URL * to use for the chunked files. */ function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk = false, $chunkurl = null) { global $CFG; $css = ''; foreach ($cssfiles as $file) { $css .= file_get_contents($file) . "\n"; } // Check if both the CSS optimiser is enabled and the theme supports it. if (!empty($CFG->enablecssoptimiser) && $theme->supportscssoptimisation) { // This is an experimental feature introduced in Moodle 2.3 // The CSS optimiser organises the CSS in order to reduce the overall number // of rules and styles being sent to the client. It does this by collating // the CSS before it is cached removing excess styles and rules and stripping // out any extraneous content such as comments and empty rules. $optimiser = new css_optimiser(); $css = $theme->post_process($css); $css = $optimiser->process($css); // If cssoptimisestats is set then stats from the optimisation are collected // and output at the beginning of the CSS. if (!empty($CFG->cssoptimiserstats)) { $css = $optimiser->output_stats_css() . $css; } } else { // This is the default behaviour. // The cssoptimise setting was introduced in Moodle 2.3 and will hopefully // in the future be changed from an experimental setting to the default. // The css_minify_css will method will use the Minify library remove // comments, additional whitespace and other minor measures to reduce the // the overall CSS being sent. // However it has the distinct disadvantage of having to minify the CSS // before running the post process functions. Potentially things may break // here if theme designers try to push things with CSS post processing. $css = $theme->post_process($css); $css = core_minify::css($css); } clearstatcache(); if (!file_exists(dirname($csspath))) { @mkdir(dirname($csspath), $CFG->directorypermissions, true); } // Prevent serving of incomplete file from concurrent request, // the rename() should be more atomic than fwrite(). ignore_user_abort(true); // First up write out the single file for all those using decent browsers. css_write_file($csspath, $css); if ($chunk) { // If we need to chunk the CSS for browsers that are sub-par. $css = css_chunk_by_selector_count($css, $chunkurl); $files = count($css); $count = 1; foreach ($css as $content) { if ($count === $files) { // If there is more than one file and this IS the last file. $filename = preg_replace('#\\.css$#', '.0.css', $csspath); } else { // If there is more than one file and this is not the last file. $filename = preg_replace('#\\.css$#', '.' . $count . '.css', $csspath); } $count++; css_write_file($filename, $content); } } ignore_user_abort(false); if (connection_aborted()) { die; } }