MDL-56959 theme: SCSS file is inherited from parent

Also added support for the SCSS property to be a Closure
and return content rather than simply being the name of a file.
This commit is contained in:
Frederic Massart
2016-11-17 15:18:29 +08:00
parent b4d6669dd0
commit 41b973bcbb
4 changed files with 117 additions and 53 deletions
+73 -24
View File
@@ -152,6 +152,9 @@ class theme_config {
*/
const DEFAULT_THEME = 'boost';
/** The key under which the SCSS file is stored amongst the CSS files. */
const SCSS_KEY = '__SCSS__';
/**
* @var array You can base your theme on other themes by linking to the other theme as
* parents. This lets you use the CSS and layouts from the other themes
@@ -427,10 +430,18 @@ class theme_config {
public $lessfile = false;
/**
* The SCSS file to compile. This takes precedence over the LESS file.
* @var string
* The SCSS file to compile (without .scss), located in the scss/ folder of the theme.
* Or a Closure, which receives the theme_config as argument and must
* return the SCSS content. This setting takes precedence over self::$lessfile.
* @var string|Closure
*/
public $scssfile = false;
public $scss = false;
/**
* Local cache of the SCSS property.
* @var false|array
*/
protected $scsscache = null;
/**
* The name of the function to call to get the LESS code to inject.
@@ -535,7 +546,7 @@ class theme_config {
'rendererfactory', 'csspostprocess', 'editor_sheets', 'rarrow', 'larrow', 'uarrow', 'darrow',
'hidefromselector', 'doctype', 'yuicssmodules', 'blockrtlmanipulations',
'lessfile', 'extralesscallback', 'lessvariablescallback', 'blockrendermethod',
'scssfile', 'extrascsscallback', 'prescsscallback', 'csstreepostprocessor');
'scss', 'extrascsscallback', 'prescsscallback', 'csstreepostprocessor');
foreach ($config as $key=>$value) {
if (in_array($key, $configurable)) {
@@ -815,7 +826,7 @@ class theme_config {
// 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));
}
if (!empty($this->scssfile)) {
if ($this->get_scss_property()) {
// No need to define the type as IE here.
$urls[] = new moodle_url($baseurl, array('theme' => $this->name, 'type' => 'scss'));
} else if (!empty($this->lessfile)) {
@@ -834,7 +845,7 @@ class theme_config {
}
}
foreach ($css['theme'] as $sheet => $filename) {
if ($sheet === $this->scssfile) {
if ($sheet === self::SCSS_KEY) {
// This is the theme SCSS file.
$urls[] = new moodle_url($baseurl, array('theme' => $this->name, 'type' => 'scss'));
} else if ($sheet === $this->lessfile) {
@@ -868,7 +879,7 @@ class theme_config {
$csscontent .= file_get_contents($v) . "\n";
}
} else {
if ($type === 'theme' && $identifier === $this->scssfile) {
if ($type === 'theme' && $identifier === self::SCSS_KEY) {
// We need the content from SCSS because this is the SCSS file from the theme.
$csscontent .= $this->get_css_content_from_scss(false);
} else if ($type === 'theme' && $identifier === $this->lessfile) {
@@ -935,7 +946,7 @@ class theme_config {
} else if ($subtype === 'theme') {
$cssfiles = $css['theme'];
foreach ($cssfiles as $key => $value) {
if (in_array($key, [$this->lessfile, $this->scssfile])) {
if (in_array($key, [$this->lessfile, self::SCSS_KEY])) {
// Remove the LESS/SCSS file from the theme CSS files.
// The LESS/SCSS files use the type 'less' or 'scss', not 'ie'.
unset($cssfiles[$key]);
@@ -1075,15 +1086,13 @@ class theme_config {
}
}
// Current theme sheets and less file.
// We first add the SCSS, or LESS file because we want the CSS ones to
// be included after the SCSS/LESS code. However, if both the SCSS/LESS file
// be included after the SCSS/LESS code. However, if both the LESS file
// and a CSS file share the same name, the CSS file is ignored.
if (!empty($this->scssfile)) {
$sheetfile = "{$this->dir}/scss/{$this->scssfile}.scss";
if (is_readable($sheetfile)) {
$cssfiles['theme'][$this->scssfile] = $sheetfile;
}
if ($this->get_scss_property()) {
$cssfiles['theme'][self::SCSS_KEY] = true;
} else if (!empty($this->lessfile)) {
$sheetfile = "{$this->dir}/less/{$this->lessfile}.less";
if (is_readable($sheetfile)) {
@@ -1181,24 +1190,23 @@ class theme_config {
protected function get_css_content_from_scss($themedesigner) {
global $CFG;
$scssfile = $this->scssfile;
if (!$scssfile || !is_readable($this->dir . '/scss/' . $scssfile . '.scss')) {
list($paths, $scss) = $this->get_scss_property();
if (!$scss) {
throw new coding_exception('The theme did not define a SCSS file, or it is not readable.');
}
// We might need more memory to do this, so let's play safe.
raise_memory_limit(MEMORY_EXTRA);
// Files list.
$files = $this->get_css_files($themedesigner);
// Get the SCSS file path.
$themescssfile = $files['theme'][$scssfile];
// Set-up the compiler.
$compiler = new core_scss();
$compiler->prepend_raw_scss($this->get_pre_scss_code());
$compiler->set_file($themescssfile);
if (is_string($scss)) {
$compiler->set_file($scss);
} else {
$compiler->append_raw_scss($scss($this));
$compiler->setImportPaths($paths);
}
$compiler->append_raw_scss($this->get_extra_scss_code());
try {
@@ -1207,7 +1215,7 @@ class theme_config {
} catch (\Leafo\ScssPhp\Exception $e) {
$compiled = false;
debugging('Error while compiling SCSS ' . $scssfile . ' file: ' . $e->getMessage(), DEBUG_DEVELOPER);
debugging('Error while compiling SCSS: ' . $e->getMessage(), DEBUG_DEVELOPER);
}
// Try to save memory.
@@ -1344,6 +1352,47 @@ class theme_config {
return $content;
}
/**
* Get the SCSS property.
*
* This resolves whether a SCSS file (or content) has to be used when generating
* the stylesheet for the theme. It will look at parents themes and check the
* SCSS properties there.
*
* @return False when SCSS is not used.
* An array with the import paths, and the path to the SCSS file or Closure as second.
*/
public function get_scss_property() {
if ($this->scsscache === null) {
$configs = [$this] + $this->parent_configs;
$scss = null;
foreach ($configs as $config) {
$path = "{$config->dir}/scss";
// We collect the SCSS property until we've found one.
if (empty($scss) && !empty($config->scss)) {
$candidate = is_string($config->scss) ? "{$path}/{$config->scss}.scss" : $config->scss;
if ($candidate instanceof Closure) {
$scss = $candidate;
} else if (is_string($candidate) && is_readable($candidate)) {
$scss = $candidate;
}
}
// We collect the import paths once we've found a SCSS property.
if ($scss && is_dir($path)) {
$paths[] = $path;
}
}
$this->scsscache = $scss !== null ? [$paths, $scss] : false;
}
return $this->scsscache;
}
/**
* Generate a URL to the file that serves theme JavaScript files.
*