MDL-62757 themes: validate theme preset files
authorBas Brands <bas@moodle.com>
Tue, 6 Aug 2019 15:10:56 +0000 (17:10 +0200)
committerBas Brands <bas@moodle.com>
Wed, 4 Dec 2019 18:12:44 +0000 (19:12 +0100)
Preset files need to be validated to ensure compiling SCSS does not
break.

lang/en/admin.php
lib/adminlib.php
lib/outputlib.php
theme/boost/settings.php
theme/classic/settings.php

index 400e094..3ec7313 100644 (file)
@@ -660,6 +660,7 @@ $string['intlrecommended'] = 'Intl extension is used to improve internationaliza
 $string['intlrequired'] = 'Intl extension is required to improve internationalization support, such as locale aware sorting and international domain names.';
 $string['invalidagedigitalconsent'] = 'The digital age of consent is not valid: {$a}';
 $string['invalidforgottenpasswordurl'] = 'The forgotten password URL is not a valid URL.';
+$string['invalidthemepreset'] = 'The chosen preset file is not compatible with this theme. The SCSS compile error was: "{$a}"';
 $string['invalidsection'] = 'Invalid section.';
 $string['invaliduserchangeme'] = 'Username "changeme" is reserved -- you cannot create an account with it.';
 $string['ipblocked'] = 'This site is not available currently.';
index ac2142b..1f731a4 100644 (file)
@@ -11092,3 +11092,95 @@ class admin_settings_sitepolicy_handler_select extends admin_setting_configselec
         return true;
     }
 }
+
+/**
+ * Used to validate theme presets code and ensuring they compile well.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2019 Bas Brands <bas@moodle.com>
+ */
+class admin_setting_configthemepreset extends admin_setting_configselect {
+
+    /** @var string The name of the theme to check for */
+    private $themename;
+
+    /**
+     * Constructor
+     * @param string $name unique ascii name, either 'mysetting' for settings that in config,
+     * or 'myplugin/mysetting' for ones in config_plugins.
+     * @param string $visiblename localised
+     * @param string $description long localised info
+     * @param string|int $defaultsetting
+     * @param array $choices array of $value=>$label for each selection
+     * @param string $themename name of theme to check presets for.
+     */
+    public function __construct($name, $visiblename, $description, $defaultsetting, $choices, $themename) {
+        $this->themename = $themename;
+        parent::__construct($name, $visiblename, $description, $defaultsetting, $choices);
+    }
+
+    /**
+     * Write settings if validated
+     *
+     * @param string $data
+     * @return string
+     */
+    public function write_setting($data) {
+        $validated = $this->validate($data);
+        if ($validated !== true) {
+            return $validated;
+        }
+        return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin'));
+    }
+
+    /**
+     * Validate the preset file to ensure its parsable.
+     *
+     * @param string $data The preset file chosen.
+     * @return mixed bool true for success or string:error on failure.
+     */
+    public function validate($data) {
+
+        if (in_array($data, ['default.scss', 'plain.scss'])) {
+            return true;
+        }
+
+        $fs = get_file_storage();
+        $theme = theme_config::load($this->themename);
+        $context = context_system::instance();
+
+        // If the preset has not changed there is no need to validate it.
+        if ($theme->settings->preset == $data) {
+            return true;
+        }
+
+        if ($presetfile = $fs->get_file($context->id, 'theme_' . $this->themename, 'preset', 0, '/', $data)) {
+            // This operation uses a lot of resources.
+            raise_memory_limit(MEMORY_EXTRA);
+            core_php_time_limit::raise(300);
+
+            // TODO: MDL-62757 When changing anything in this method please do not forget to check
+            // if the get_css_content_from_scss() method in class theme_config needs updating too.
+
+            $compiler = new core_scss();
+            $compiler->prepend_raw_scss($theme->get_pre_scss_code());
+            $compiler->append_raw_scss($presetfile->get_content());
+            if ($scssproperties = $theme->get_scss_property()) {
+                $compiler->setImportPaths($scssproperties[0]);
+            }
+            $compiler->append_raw_scss($theme->get_extra_scss_code());
+
+            try {
+                $compiler->to_css();
+            } catch (Exception $e) {
+                return get_string('invalidthemepreset', 'admin', $e->getMessage());
+            }
+
+            // Try to save memory.
+            $compiler = null;
+            unset($compiler);
+        }
+
+        return true;
+    }
+}
index bb36d5d..56a1110 100644 (file)
@@ -1409,6 +1409,9 @@ class theme_config {
         raise_memory_limit(MEMORY_EXTRA);
         core_php_time_limit::raise(300);
 
+        // TODO: MDL-62757 When changing anything in this method please do not forget to check
+        // if the validate() method in class admin_setting_configthemepreset needs updating too.
+
         // Set-up the compiler.
         $compiler = new core_scss();
         $compiler->prepend_raw_scss($this->get_pre_scss_code());
@@ -1485,7 +1488,7 @@ class theme_config {
      *
      * @return string The SCSS code to inject.
      */
-    protected function get_extra_scss_code() {
+    public function get_extra_scss_code() {
         $content = '';
 
         // Getting all the candidate functions.
@@ -1515,7 +1518,7 @@ class theme_config {
      *
      * @return string The SCSS code to inject.
      */
-    protected function get_pre_scss_code() {
+    public function get_pre_scss_code() {
         $content = '';
 
         // Getting all the candidate functions.
index c7cde2b..1f2bad2 100644 (file)
@@ -44,7 +44,7 @@ if ($ADMIN->fulltree) {
     $choices['default.scss'] = 'default.scss';
     $choices['plain.scss'] = 'plain.scss';
 
-    $setting = new admin_setting_configselect($name, $title, $description, $default, $choices);
+    $setting = new admin_setting_configthemepreset($name, $title, $description, $default, $choices, 'boost');
     $setting->set_updatedcallback('theme_reset_all_caches');
     $page->add($setting);
 
index 399b400..9761980 100644 (file)
@@ -54,7 +54,7 @@ if ($ADMIN->fulltree) {
     $choices['default.scss'] = 'default.scss';
     $choices['plain.scss'] = 'plain.scss';
 
-    $setting = new admin_setting_configselect($name, $title, $description, $default, $choices);
+    $setting = new admin_setting_configthemepreset($name, $title, $description, $default, $choices, 'classic');
     $setting->set_updatedcallback('theme_reset_all_caches');
     $page->add($setting);