From 98245a84a2460a63dceda93b45ec0657f4ecc0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20S=CC=8Ckoda?= Date: Thu, 27 Feb 2014 14:35:03 +0800 Subject: [PATCH] MDL-44358 simplify CSS processing The affected code areas are not supposed to be used in any add-ons, the backwards compatibility should not be a concern for us. --- lib/csslib.php | 75 +++++------ lib/outputlib.php | 293 +++++++++++++++++++++++++---------------- theme/styles.php | 73 ++++------ theme/styles_debug.php | 111 ++++------------ 4 files changed, 270 insertions(+), 282 deletions(-) diff --git a/lib/csslib.php b/lib/csslib.php index 2f9eeb7a43f..193b538debd 100644 --- a/lib/csslib.php +++ b/lib/csslib.php @@ -27,6 +27,12 @@ defined('MOODLE_INTERNAL') || die(); +if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { + // This can be also set in config.php file, + // it needs to be higher than the time it takes to generate all CSS content. + define('THEME_DESIGNER_CACHE_LIFETIME', 10); +} + /** * Stores CSS in a file at the given path. * @@ -34,50 +40,15 @@ defined('MOODLE_INTERNAL') || die(); * * @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 string $csscontent the complete CSS in one string * @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) { +function css_store_css(theme_config $theme, $csspath, $csscontent, $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); @@ -88,11 +59,11 @@ function css_store_css(theme_config $theme, $csspath, array $cssfiles, $chunk = ignore_user_abort(true); // First up write out the single file for all those using decent browsers. - css_write_file($csspath, $css); + css_write_file($csspath, $csscontent); if ($chunk) { // If we need to chunk the CSS for browsers that are sub-par. - $css = css_chunk_by_selector_count($css, $chunkurl); + $css = css_chunk_by_selector_count($csscontent, $chunkurl); $files = count($css); $count = 1; foreach ($css as $content) { @@ -240,6 +211,32 @@ function css_send_cached_css($csspath, $etag) { die; } +/** + * Sends a cached CSS content + * + * @param string $csscontent The actual CSS markup. + * @param string $etag The revision to make sure we utilise any caches. + */ +function css_send_cached_css_content($csscontent, $etag) { + // 60 days only - the revision may get incremented quite often. + $lifetime = 60*60*24*60; + + header('Etag: "'.$etag.'"'); + header('Content-Disposition: inline; filename="styles.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() + $lifetime) .' GMT'); + header('Pragma: '); + header('Cache-Control: public, max-age='.$lifetime); + header('Accept-Ranges: none'); + header('Content-Type: text/css; charset=utf-8'); + if (!min_enable_zlib_compression()) { + header('Content-Length: '.strlen($csscontent)); + } + + echo($csscontent); + die; +} + /** * Sends CSS directly without caching it. * diff --git a/lib/outputlib.php b/lib/outputlib.php index 600714bac60..b68a6d27f95 100644 --- a/lib/outputlib.php +++ b/lib/outputlib.php @@ -55,6 +55,11 @@ function theme_reset_all_caches() { set_config('themerev', $next); // time is unique even when you reset/switch database + if (!empty($CFG->themedesignermode)) { + $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'themedesigner'); + $cache->purge(); + } + if ($PAGE) { $PAGE->reload_theme(); } @@ -66,8 +71,9 @@ function theme_reset_all_caches() { * @param bool $state */ function theme_set_designer_mod($state) { - theme_reset_all_caches(); set_config('themedesignermode', (int)!empty($state)); + // Reset caches after switching mode so that any designer mode caches get purged too. + theme_reset_all_caches(); } /** @@ -594,14 +600,12 @@ class theme_config { /** * Returns the content of the CSS to be used in editor content * - * @return string + * @return array */ public function editor_css_files() { - global $CFG; - $files = array(); - // first editor plugins + // First editor plugins. $plugins = core_component::get_plugin_list('editor'); foreach ($plugins as $plugin=>$fulldir) { $sheetfile = "$fulldir/editor_styles.css"; @@ -609,8 +613,8 @@ class theme_config { $files['plugin_'.$plugin] = $sheetfile; } } - // then parent themes - foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last + // Then parent themes - base first, the immediate parent last. + foreach (array_reverse($this->parent_configs) as $parent_config) { if (empty($parent_config->editor_sheets)) { continue; } @@ -621,7 +625,7 @@ class theme_config { } } } - // finally this theme + // Finally this theme. if (!empty($this->editor_sheets)) { foreach ($this->editor_sheets as $sheet) { $sheetfile = "$this->dir/style/$sheet.css"; @@ -635,10 +639,10 @@ class theme_config { } /** - * Get the stylesheet URL of this theme + * Get the stylesheet URL of this theme. * * @param moodle_page $page Not used... deprecated? - * @return array of moodle_url + * @return moodle_url[] */ public function css_urls(moodle_page $page) { global $CFG; @@ -679,50 +683,7 @@ class theme_config { $urls[] = $url; } else { - // find out the current CSS and cache it now for 5 seconds - // the point is to construct the CSS only once and pass it through the - // dataroot to the script that actually serves the sheets - if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { - define('THEME_DESIGNER_CACHE_LIFETIME', 4); // this can be also set in config.php - } - $candidatedir = "$CFG->cachedir/theme/$this->name"; - if ($svg) { - $candidatesheet = "$candidatedir/designer.ser"; - } else { - $candidatesheet = "$candidatedir/designer_nosvg.ser"; - } - $rebuild = true; - if (file_exists($candidatesheet) and filemtime($candidatesheet) > time() - THEME_DESIGNER_CACHE_LIFETIME) { - if ($css = file_get_contents($candidatesheet)) { - $css = unserialize($css); - if (is_array($css)) { - $rebuild = false; - } - } - } - if ($rebuild) { - // Prepare the CSS optimiser if it is to be used, - // please note that it may be very slow and is therefore strongly discouraged in theme designer mode. - $optimiser = null; - if (!empty($CFG->enablecssoptimiser) && $this->supportscssoptimisation) { - require_once($CFG->dirroot.'/lib/csslib.php'); - $optimiser = new css_optimiser; - } - $css = $this->css_content($optimiser); - - // We do not want any errors here because this may fail easily because of the concurrent access. - $prevabort = ignore_user_abort(true); - check_dir_exists($candidatedir); - $tempfile = tempnam($candidatedir, 'tmpdesigner'); - file_put_contents($tempfile, serialize($css)); - $reporting = error_reporting(0); - chmod($tempfile, $CFG->filepermissions); - unlink($candidatesheet); // Do not rely on rename() deleting original, they may decide to change it at any time as usually. - rename($tempfile, $candidatesheet); - error_reporting($reporting); - ignore_user_abort($prevabort); - } - + $css = $this->get_css_files(true); $baseurl = new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php'); if (!$svg) { // We add an SVG param so that we know not to serve SVG images. @@ -730,10 +691,10 @@ class theme_config { $baseurl->param('svg', '0'); } if (core_useragent::is_ie()) { - // lalala, IE does not allow more than 31 linked CSS files from main document + // Lalala, IE does not allow more than 31 linked CSS files from main document. $urls[] = new moodle_url($baseurl, array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'plugins')); foreach ($css['parents'] as $parent=>$sheets) { - // We need to serve parents individually otherwise we may easily exceed the style limit IE imposes (4096) + // We need to serve parents individually otherwise we may easily exceed the style limit IE imposes (4096). $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'ie', 'subtype'=>'parents', 'sheet'=>$parent)); } $urls[] = new moodle_url($baseurl, array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'theme')); @@ -748,7 +709,8 @@ class theme_config { } } foreach ($css['theme'] as $sheet=>$unused) { - $urls[] = new moodle_url($baseurl, array('sheet'=>$sheet, 'theme'=>$this->name, 'type'=>'theme')); // sheet first in order to make long urls easier to read + // Sheet first in order to make long urls easier to read. + $urls[] = new moodle_url($baseurl, array('sheet'=>$sheet, 'theme'=>$this->name, 'type'=>'theme')); } } } @@ -757,14 +719,162 @@ class theme_config { } /** - * Returns an array of organised CSS files required for this output + * Get the whole css stylesheet for production mode. * - * @return array + * @return string CSS markup, already optimised and compressed */ - public function css_files() { + public function get_css_content() { + global $CFG; + require_once($CFG->dirroot.'/lib/csslib.php'); + + $csscontent = ''; + foreach ($this->get_css_files(false) as $value) { + foreach ($value as $val) { + if (is_array($val)) { + foreach ($val as $v) { + $csscontent .= file_get_contents($v) . "\n"; + } + } else { + $csscontent .= file_get_contents($val) . "\n"; + } + } + } + $csscontent = $this->post_process($csscontent); + + 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(); + $csscontent = $optimiser->process($csscontent); + + } else { + $csscontent = core_minify::css($csscontent); + } + + return $csscontent; + } + + /** + * Get the theme designer css markup, + * the parameters are coming from css_urls(). + * + * @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'); + + $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']; + } + + } 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; + } + + /** + * Get the whole css stylesheet for editor iframe. + * @return string CSS markup + */ + public function get_css_content_editor() { + // Do not bother to optimise anything here, just very basic stuff. + $cssfiles = $this->editor_css_files(); + $css = ''; + foreach ($cssfiles as $file) { + $css .= file_get_contents($file)."\n"; + } + return $this->post_process($css); + } + + /** + * Returns an array of organised CSS files required for this output. + * + * @param bool $themedesigner + * @return array nested array of file paths + */ + protected function get_css_files($themedesigner) { + global $CFG; + + $cache = null; + if ($themedesigner) { + require_once($CFG->dirroot.'/lib/csslib.php'); + // We need some kind of caching here because otherwise the page navigation becomes + // way too slow in theme designer mode. Feel free to create full cache definition later... + $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'themedesigner', array('theme' => $this->name)); + if ($files = $cache->get('cssfiles')) { + if ($files['created'] > time() - THEME_DESIGNER_CACHE_LIFETIME) { + unset($files['created']); + return $files; + } + } + } + $cssfiles = array('plugins'=>array(), 'parents'=>array(), 'theme'=>array()); - // get all plugin sheets + // Get all plugin sheets. $excludes = $this->resolve_excludes('plugins_exclude_sheets'); if ($excludes !== true) { foreach (core_component::get_plugin_types() as $type=>$unused) { @@ -802,10 +912,10 @@ class theme_config { } } - // find out wanted parent sheets + // Find out wanted parent sheets. $excludes = $this->resolve_excludes('parents_exclude_sheets'); if ($excludes !== true) { - foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last + foreach (array_reverse($this->parent_configs) as $parent_config) { // Base first, the immediate parent last. $parent = $parent_config->name; if (empty($parent_config->sheets) || (!empty($excludes[$parent]) and $excludes[$parent] === true)) { continue; @@ -823,7 +933,7 @@ class theme_config { } } - // current theme sheets + // Current theme sheets. if (is_array($this->sheets)) { foreach ($this->sheets as $sheet) { $sheetfile = "$this->dir/style/$sheet.css"; @@ -833,57 +943,15 @@ class theme_config { } } + if ($cache) { + $files = $cssfiles; + $files['created'] = time(); + $cache->set('cssfiles', $files); + } + return $cssfiles; } - /** - * Returns the content of the one huge CSS merged from all style sheets. - * - * @param css_optimiser|null $optimiser A CSS optimiser to use during on the content. Null = don't optimise - * @return string - */ - public function css_content(css_optimiser $optimiser = null) { - $files = array_merge($this->css_files(), array('editor'=>$this->editor_css_files())); - $css = $this->css_files_get_contents($files, array(), $optimiser); - return $css; - } - - /** - * Given an array of file paths or a single file path loads the contents of - * the CSS file, processes it then returns it in the same structure it was given. - * - * Can be used recursively on the results of {@link css_files} - * - * @param array|string $file An array of file paths or a single file path - * @param array $keys An array of previous array keys [recursive addition] - * @param css_optimiser|null $optimiser A CSS optimiser to use during on the content. Null = don't optimise - * @return The converted array or the contents of the single file ($file type) - */ - protected function css_files_get_contents($file, array $keys, css_optimiser $optimiser = null) { - global $CFG; - if (is_array($file)) { - // We use a separate array to keep everything in the exact same order. - $return = array(); - foreach ($file as $key=>$f) { - $return[clean_param($key, PARAM_SAFEDIR)] = $this->css_files_get_contents($f, array_merge($keys, array($key)), $optimiser); - } - return $return; - } else { - $contents = file_get_contents($file); - $contents = $this->post_process($contents); - $comment = '/** Path: '.implode(' ', $keys).' **/'."\n"; - $stats = ''; - if (!is_null($optimiser)) { - $contents = $optimiser->process($contents); - if (!empty($CFG->cssoptimiserstats)) { - $stats = $optimiser->output_stats_css(); - } - } - return $comment.$stats.$contents; - } - } - - /** * Generate a URL to the file that serves theme JavaScript files. * @@ -1359,7 +1427,6 @@ class theme_config { /** * Return true if we should look for SVG images as well. * - * @staticvar bool $svg * @return bool */ public function use_svg_icons() { diff --git a/theme/styles.php b/theme/styles.php index 0279b5177ea..ed6164ee8eb 100644 --- a/theme/styles.php +++ b/theme/styles.php @@ -22,20 +22,14 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -// disable moodle specific debug messages and any errors in output, +// Disable moodle specific debug messages and any errors in output, // comment out when debugging or better look into error log! define('NO_DEBUG_DISPLAY', true); -// we need just the values from config.php and minlib.php define('ABORT_AFTER_CONFIG', true); -require('../config.php'); // this stops immediately at the beginning of lib/setup.php +require('../config.php'); require_once($CFG->dirroot.'/lib/csslib.php'); -if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { - define('THEME_DESIGNER_CACHE_LIFETIME', 4); // this can be also set in config.php -} - if ($slashargument = min_get_slash_argument()) { $slashargument = ltrim($slashargument, '/'); if (substr_count($slashargument, '/') < 2) { @@ -43,7 +37,7 @@ if ($slashargument = min_get_slash_argument()) { } if (strpos($slashargument, '_s/') === 0) { - // Can't use SVG + // Can't use SVG. $slashargument = substr($slashargument, 3); $usesvg = false; } else { @@ -79,9 +73,9 @@ if ($type === 'editor') { } if (file_exists("$CFG->dirroot/theme/$themename/config.php")) { - // exists + // The theme exists in standard location - ok. } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$themename/config.php")) { - // exists + // Alternative theme location contains this theme - ok. } else { header('HTTP/1.0 404 not found'); die('Theme was not found, sorry.'); @@ -105,19 +99,18 @@ $etag = sha1($etag); if (file_exists($candidatesheet)) { if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { - // we do not actually need to verify the etag value because our files - // never change in cache because we increment the rev parameter + // We do not actually need to verify the etag value because our files + // never change in cache because we increment the rev counter. css_send_unmodified(filemtime($candidatesheet), $etag); } css_send_cached_css($candidatesheet, $etag); } -//================================================================================= -// ok, now we need to start normal moodle script, we need to load all libs and $DB +// Ok, now we need to start normal moodle script, we need to load all libs and $DB. define('ABORT_AFTER_CONFIG_CANCEL', true); -define('NO_MOODLE_COOKIES', true); // Session not used here -define('NO_UPGRADE_CHECK', true); // Ignore upgrade check +define('NO_MOODLE_COOKIES', true); // Session not used here. +define('NO_UPGRADE_CHECK', true); // Ignore upgrade check. require("$CFG->dirroot/lib/setup.php"); @@ -131,7 +124,7 @@ if ($themerev <= 0 or $themerev != $rev) { $rev = $themerev; $cache = false; - $candidatedir = "$CFG->cachedir/theme/$rev/$themename/css"; + $candidatedir = "$CFG->localcachedir/theme/$rev/$themename/css"; $etag = "$rev/$themename/$type"; $candidatename = $type; if (!$usesvg) { @@ -151,12 +144,13 @@ if ($themerev <= 0 or $themerev != $rev) { make_localcache_directory('theme', false); if ($type === 'editor') { - $cssfiles = $theme->editor_css_files(); - css_store_css($theme, "$candidatedir/editor.css", $cssfiles, false); + $csscontent = $theme->get_css_content_editor(); + css_store_css($theme, "$candidatedir/editor.css", $csscontent, false); } else { // Older IEs require smaller chunks. - $css = $theme->css_files(); + $csscontent = $theme->get_css_content(); + $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot); if (!empty($slashargument)) { if ($usesvg) { @@ -171,36 +165,19 @@ if ($type === 'editor') { $chunkurl = "{$relroot}/theme/styles.php?theme={$themename}&rev={$rev}&type=all&svg=0"; } } - $cssfiles = array(); - foreach ($css as $key => $value) { - foreach ($value as $val) { - if (is_array($val)) { - foreach ($val as $k=>$v) { - $cssfiles[] = $v; - } - } else { - $cssfiles[] = $val; - } - } - } - css_store_css($theme, "$candidatedir/all.css", $cssfiles, true, $chunkurl); + css_store_css($theme, "$candidatedir/all.css", $csscontent, true, $chunkurl); } -// verify nothing failed in cache file creation -clearstatcache(); -if (!file_exists($candidatesheet)) { - // We need to send at least something, IE does not get it chunked properly but who cares. - $css = ''; - foreach ($cssfiles as $file) { - $css .= file_get_contents($file)."\n"; - } - css_send_uncached_css($css, false); +if (!$cache) { + // Do not pollute browser caches if invalid revision requested, + // let's ignore legacy IE breakage here too. + css_send_uncached_css($csscontent); -} else if (!$cache) { - // Do not pollute browser caches if invalid revision requested. - css_send_uncached_css(file_get_contents($candidatesheet), false); +} else if ($chunk and file_exists($candidatesheet)) { + // Greetings stupid legacy IEs! + css_send_cached_css($candidatesheet, $etag); } else { - // This is the expected result! - css_send_cached_css($candidatesheet, $etag); + // Real browsers - this is the expected result! + css_send_cached_css_content($csscontent, $etag); } diff --git a/theme/styles_debug.php b/theme/styles_debug.php index 1b7293ba36c..4e363ca6a52 100644 --- a/theme/styles_debug.php +++ b/theme/styles_debug.php @@ -25,100 +25,47 @@ // Disable moodle specific debug messages and any errors in output, // comment out when debugging or better look into error log! define('NO_DEBUG_DISPLAY', true); +define('NO_UPGRADE_CHECK', true); +define('NO_MOODLE_COOKIES', true); -define('ABORT_AFTER_CONFIG', true); -require('../config.php'); // this stops immediately at the beginning of lib/setup.php +require('../config.php'); require_once($CFG->dirroot.'/lib/csslib.php'); -$themename = min_optional_param('theme', 'standard', 'SAFEDIR'); -$type = min_optional_param('type', '', 'SAFEDIR'); -$subtype = min_optional_param('subtype', '', 'SAFEDIR'); -$sheet = min_optional_param('sheet', '', 'SAFEDIR'); -$usesvg = (bool)min_optional_param('svg', '1', 'INT'); - -if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { - define('THEME_DESIGNER_CACHE_LIFETIME', 4); // this can be also set in config.php -} +$themename = optional_param('theme', 'standard', PARAM_SAFEDIR); +$type = optional_param('type', '', PARAM_SAFEDIR); +$subtype = optional_param('subtype', '', PARAM_SAFEDIR); +$sheet = optional_param('sheet', '', PARAM_SAFEDIR); +$usesvg = optional_param('svg', 1, PARAM_BOOL); if (file_exists("$CFG->dirroot/theme/$themename/config.php")) { - // exists + // The theme exists in standard location - ok. } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$themename/config.php")) { - // exists + // Alternative theme location contains this theme - ok. } else { css_send_css_not_found(); } -// no gzip compression when debugging - -if ($usesvg) { - $candidatesheet = "$CFG->cachedir/theme/$themename/designer.ser"; -} else { - // Add to the sheet name, one day we'll be able to just drop this. - $candidatesheet = "$CFG->cachedir/theme/$themename/designer_nosvg.ser"; -} - -$css = false; -if (is_readable($candidatesheet) and filemtime($candidatesheet) > time() - THEME_DESIGNER_CACHE_LIFETIME) { - $css = @unserialize(file_get_contents($candidatesheet)); -} - -if (!is_array($css)) { - // Ok, we need to start normal moodle script, we need to load all libs and $DB. - define('ABORT_AFTER_CONFIG_CANCEL', true); - - define('NO_MOODLE_COOKIES', true); // Session not used here. - define('NO_UPGRADE_CHECK', true); // Ignore upgrade check. - - require("$CFG->dirroot/lib/setup.php"); - $theme = theme_config::load($themename); - $css = $theme->css_content(); -} +$theme = theme_config::load($themename); +$theme->force_svg_use($usesvg); if ($type === 'editor') { - if (isset($css['editor'])) { - css_send_uncached_css($css['editor']); - } -} else if ($type === 'ie') { - // IE is a sloppy browser with weird limits, sorry - if ($subtype === 'plugins') { - css_send_uncached_css($css['plugins']); + $csscontent = $theme->get_css_content_editor(); + css_send_uncached_css($csscontent); +} - } else if ($subtype === 'parents') { - $sendcss = array(); - if (empty($sheet)) { - // If not specific parent has been specified as $sheet then build a - // collection of @import statements into this one sheet. - // We shouldn't ever actually get here, but none the less we'll deal - // with it incase we ever do. - // @import statements arn't processed until after concurrent CSS requests - // making them slightly evil. - foreach (array_keys($css['parents']) as $sheet) { - $sendcss[] = "@import url(styles_debug.php?theme=$themename&type=$type&subtype=$subtype&sheet=$sheet);"; - } - } else { - // Build up the CSS for that parent so we can serve it as one file. - foreach ($css[$subtype][$sheet] as $parent=>$css) { - $sendcss[] = $css; - } - } - css_send_uncached_css($sendcss); - } else if ($subtype === 'theme') { - css_send_uncached_css($css['theme']); - } - -} else if ($type === 'plugin') { - if (isset($css['plugins'][$subtype])) { - css_send_uncached_css($css['plugins'][$subtype]); - } - -} else if ($type === 'parent') { - if (isset($css['parents'][$subtype][$sheet])) { - css_send_uncached_css($css['parents'][$subtype][$sheet]); - } - -} else if ($type === 'theme') { - if (isset($css['theme'][$sheet])) { - css_send_uncached_css($css['theme'][$sheet]); +// We need some kind of caching here because otherwise the page navigation becomes +// way too slow in theme designer mode. Feel free to create full cache definition later... +$key = "$type $subtype $sheet $usesvg"; +$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'themedesigner', array('theme' => $themename)); +if ($content = $cache->get($key)) { + if ($content['created'] > time() - THEME_DESIGNER_CACHE_LIFETIME) { + $csscontent = $content['data']; + css_send_uncached_css($csscontent); } } -css_send_css_not_found(); + +$csscontent = $theme->get_css_content_debug($type, $subtype, $sheet); + +$cache->set($key, array('data' => $csscontent, 'created' => time())); + +css_send_uncached_css($csscontent);