Merge branch 'MDL-62387-310' of https://github.com/paulholden/moodle into MOODLE_310_...
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 16 Sep 2020 22:09:42 +0000 (00:09 +0200)
committerSara Arjona <sara@moodle.com>
Thu, 17 Sep 2020 07:17:23 +0000 (09:17 +0200)
40 files changed:
admin/settings/courses.php
backup/moodle2/backup_root_task.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_course_task.class.php
backup/moodle2/restore_root_task.class.php
backup/util/dbops/backup_controller_dbops.class.php
backup/util/dbops/restore_controller_dbops.class.php
cache/classes/factory.php
cache/classes/loaders.php
cache/disabledlib.php
cache/tests/cache_test.php
contentbank/amd/build/sort.min.js
contentbank/amd/build/sort.min.js.map
contentbank/amd/src/sort.js
contentbank/templates/bankcontent.mustache
h5p/classes/editor.php
h5p/classes/file_storage.php
h5p/lib.php
h5p/tests/editor_test.php
h5p/tests/generator/lib.php
h5p/tests/h5p_file_storage_test.php
lang/en/backup.php
lang/en/contentbank.php
lib/db/upgrade.php
lib/externallib.php
lib/outputrenderers.php
lib/upgrade.txt
mod/lti/templates/tool_card.mustache
mod/quiz/accessrule/seb/db/install.php
theme/boost/scss/moodle.scss
theme/boost/scss/moodle/admin.scss
theme/boost/scss/moodle/atto.scss [moved from lib/editor/atto/styles.css with 85% similarity]
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/filemanager.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/classic/scss/preset/default.scss
theme/classic/style/moodle.css
version.php

index e7d9bf9..6b53eb0 100644 (file)
@@ -285,6 +285,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
         ['value' => 1, 'locked' => 0])
     );
 
+    $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_legacyfiles',
+        new lang_string('generallegacyfiles', 'backup'),
+        new lang_string('configlegacyfiles', 'backup'), array('value' => 1, 'locked' => 0)));
     $ADMIN->add('backups', $temp);
 
     // Create a page for general import configuration and defaults.
@@ -311,6 +314,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
         new lang_string('configgeneralcontentbankcontent', 'backup'),
         ['value' => 1, 'locked' => 0])
     );
+    $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_legacyfiles',
+        new lang_string('generallegacyfiles', 'backup'),
+        new lang_string('configlegacyfiles', 'backup'), array('value' => 1, 'locked' => 0)));
 
     $ADMIN->add('backups', $temp);
 
@@ -437,6 +443,10 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
         1)
     );
 
+    $temp->add(new admin_setting_configcheckbox('backup/backup_auto_legacyfiles',
+        new lang_string('generallegacyfiles', 'backup'),
+        new lang_string('configlegacyfiles', 'backup'), 1));
+
     //$temp->add(new admin_setting_configcheckbox('backup/backup_auto_messages', new lang_string('messages', 'message'), new lang_string('backupmessageshelp','message'), 0));
     //$temp->add(new admin_setting_configcheckbox('backup/backup_auto_blogs', new lang_string('blogs', 'blog'), new lang_string('backupblogshelp','blog'), 0));
 
@@ -499,6 +509,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_contentbankcontent',
         new lang_string('generalcontentbankcontent', 'backup'),
         new lang_string('configrestorecontentbankcontent', 'backup'), array('value' => 1, 'locked' => 0)));
+    $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_legacyfiles',
+        new lang_string('generallegacyfiles', 'backup'),
+        new lang_string('configlegacyfiles', 'backup'), array('value' => 1, 'locked' => 0)));
 
     // Restore defaults when merging into another course.
     $temp->add(new admin_setting_heading('mergerestoredefaults', new lang_string('mergerestoredefaults', 'backup'), ''));
index 4771c39..478ae2a 100644 (file)
@@ -184,5 +184,10 @@ class backup_root_task extends backup_task {
         $contentbank = new backup_contentbankcontent_setting('contentbankcontent', base_setting::IS_BOOLEAN, true);
         $contentbank->set_ui(new backup_setting_ui_checkbox($contentbank, get_string('rootsettingcontentbankcontent', 'backup')));
         $this->add_setting($contentbank);
+
+        // Define legacy file inclusion setting.
+        $legacyfiles = new backup_generic_setting('legacyfiles', base_setting::IS_BOOLEAN, true);
+        $legacyfiles->set_ui(new backup_setting_ui_checkbox($legacyfiles, get_string('rootsettinglegacyfiles', 'backup')));
+        $this->add_setting($legacyfiles);
     }
 }
index 8c84801..6a66e3f 100644 (file)
@@ -488,7 +488,10 @@ class backup_course_structure_step extends backup_structure_step {
 
         $course->annotate_files('course', 'summary', null);
         $course->annotate_files('course', 'overviewfiles', null);
-        $course->annotate_files('course', 'legacy', null);
+
+        if ($this->get_setting_value('legacyfiles')) {
+            $course->annotate_files('course', 'legacy', null);
+        }
 
         // Return root element ($course)
 
index be4f3f9..f39b766 100644 (file)
@@ -76,7 +76,9 @@ class restore_course_task extends restore_task {
             }
         }
 
-        $this->add_step(new restore_course_legacy_files_step('legacy_files'));
+        if ($this->get_setting_value('legacyfiles')) {
+            $this->add_step(new restore_course_legacy_files_step('legacy_files'));
+        }
 
         // Deal with enrolment methods and user enrolments.
         if ($this->plan->get_mode() == backup::MODE_IMPORT) {
index 632985f..3b37acc 100644 (file)
@@ -302,5 +302,13 @@ class restore_root_task extends restore_task {
         $contents->set_ui(new backup_setting_ui_checkbox($contents, get_string('rootsettingcontentbankcontent', 'backup')));
         $contents->get_ui()->set_changeable($changeable);
         $this->add_setting($contents);
+
+        // Include legacy files.
+        $defaultvalue = true;
+        $changeable = true;
+        $legacyfiles = new restore_generic_setting('legacyfiles', base_setting::IS_BOOLEAN, $defaultvalue);
+        $legacyfiles->set_ui(new backup_setting_ui_checkbox($legacyfiles, get_string('rootsettinglegacyfiles', 'backup')));
+        $legacyfiles->get_ui()->set_changeable($changeable);
+        $this->add_setting($legacyfiles);
     }
 }
index 9e09f55..e5c0163 100644 (file)
@@ -566,6 +566,7 @@ abstract class backup_controller_dbops extends backup_dbops {
                         'backup_general_groups'             => 'groups',
                         'backup_general_competencies'       => 'competencies',
                         'backup_general_contentbankcontent' => 'contentbankcontent',
+                        'backup_general_legacyfiles'        => 'legacyfiles'
                 );
                 self::apply_admin_config_defaults($controller, $settings, true);
                 break;
@@ -580,6 +581,7 @@ abstract class backup_controller_dbops extends backup_dbops {
                         'backup_import_groups'             => 'groups',
                         'backup_import_competencies'       => 'competencies',
                         'backup_import_contentbankcontent' => 'contentbankcontent',
+                        'backup_import_legacyfiles'        => 'legacyfiles'
                 );
                 self::apply_admin_config_defaults($controller, $settings, true);
                 if ((!$controller->get_interactive()) &&
@@ -611,7 +613,8 @@ abstract class backup_controller_dbops extends backup_dbops {
                         'backup_auto_questionbank'       => 'questionbank',
                         'backup_auto_groups'             => 'groups',
                         'backup_auto_competencies'       => 'competencies',
-                        'backup_auto_contentbankcontent' => 'contentbankcontent'
+                        'backup_auto_contentbankcontent' => 'contentbankcontent',
+                        'backup_auto_legacyfiles'        => 'legacyfiles'
                 );
                 self::apply_admin_config_defaults($controller, $settings, false);
                 break;
index 148655b..216da03 100644 (file)
@@ -158,7 +158,8 @@ abstract class restore_controller_dbops extends restore_dbops {
             'restore_general_questionbank'       => 'questionbank',
             'restore_general_groups'             => 'groups',
             'restore_general_competencies'       => 'competencies',
-            'restore_general_contentbankcontent' => 'contentbankcontent'
+            'restore_general_contentbankcontent' => 'contentbankcontent',
+            'restore_general_legacyfiles'        => 'legacyfiles'
         );
         self::apply_admin_config_defaults($controller, $settings, true);
 
index 9abfe0e..a974377 100644 (file)
@@ -449,7 +449,8 @@ class cache_factory {
                         $definition = $instance->get_definition_by_id($id);
                         if (!$definition) {
                             throw new coding_exception('The requested cache definition does not exist.'. $id, $id);
-                        } else if (!$this->is_disabled()) {
+                        }
+                        if (!$this->is_disabled()) {
                             debugging('Cache definitions reparsed causing cache reset in order to locate definition.
                                 You should bump the version number to ensure definitions are reprocessed.', DEBUG_DEVELOPER);
                         }
index 2b86132..a557a3e 100644 (file)
@@ -223,11 +223,9 @@ class cache implements cache_loader {
         $this->storetype = get_class($store);
         $this->perfdebug = (!empty($CFG->perfdebug) and $CFG->perfdebug > 7);
         if ($loader instanceof cache_loader) {
-            $this->loader = $loader;
-            // Mark the loader as a sub (chained) loader.
-            $this->loader->set_is_sub_loader(true);
+            $this->set_loader($loader);
         } else if ($loader instanceof cache_data_source) {
-            $this->datasource = $loader;
+            $this->set_data_source($loader);
         }
         $this->definition->generate_definition_hash();
         $this->staticacceleration = $this->definition->use_static_acceleration();
@@ -237,6 +235,27 @@ class cache implements cache_loader {
         $this->hasattl = ($this->definition->get_ttl() > 0);
     }
 
+    /**
+     * Set the loader for this cache.
+     *
+     * @param   cache_loader $loader
+     */
+    protected function set_loader(cache_loader $loader): void {
+        $this->loader = $loader;
+
+        // Mark the loader as a sub (chained) loader.
+        $this->loader->set_is_sub_loader(true);
+    }
+
+    /**
+     * Set the data source for this cache.
+     *
+     * @param   cache_data_source $datasource
+     */
+    protected function set_data_source(cache_data_source $datasource): void {
+        $this->datasource = $datasource;
+    }
+
     /**
      * Used to inform the loader of its state as a sub loader, or as the top of the chain.
      *
index 2bcc1ca..88af42d 100644 (file)
@@ -49,7 +49,12 @@ class cache_disabled extends cache {
      * @param null $loader Unused.
      */
     public function __construct(cache_definition $definition, cache_store $store, $loader = null) {
-        // Nothing to do here.
+        if ($loader instanceof cache_data_source) {
+            // Set the data source to allow data sources to work when caching is entirely disabled.
+            $this->set_data_source($loader);
+        }
+
+        // No other features are handled.
     }
 
     /**
@@ -60,6 +65,10 @@ class cache_disabled extends cache {
      * @return bool
      */
     public function get($key, $strictness = IGNORE_MISSING) {
+        if ($this->get_datasource() !== false) {
+            return $this->get_datasource()->load_for_cache($key);
+        }
+
         return false;
     }
 
@@ -71,11 +80,11 @@ class cache_disabled extends cache {
      * @return array
      */
     public function get_many(array $keys, $strictness = IGNORE_MISSING) {
-        $return = array();
-        foreach ($keys as $key) {
-            $return[$key] = false;
+        if ($this->get_datasource() !== false) {
+            return $this->get_datasource()->load_many_for_cache($keys);
         }
-        return $return;
+
+        return array_combine($keys, array_fill(0, count($keys), false));
     }
 
     /**
@@ -129,7 +138,9 @@ class cache_disabled extends cache {
      * @return bool
      */
     public function has($key, $tryloadifpossible = false) {
-        return false;
+        $result = $this->get($key);
+
+        return $result !== false;
     }
 
     /**
@@ -138,7 +149,16 @@ class cache_disabled extends cache {
      * @return bool
      */
     public function has_all(array $keys) {
-        return false;
+        if (!$this->get_datasource()) {
+            return false;
+        }
+
+        foreach ($keys as $key) {
+            if (!$this->has($key)) {
+                return false;
+            }
+        }
+        return true;
     }
 
     /**
@@ -148,6 +168,12 @@ class cache_disabled extends cache {
      * @return bool
      */
     public function has_any(array $keys) {
+        foreach ($keys as $key) {
+            if ($this->has($key)) {
+                return true;
+            }
+        }
+
         return false;
     }
 
@@ -189,6 +215,11 @@ class cache_factory_disabled extends cache_factory {
      * @return cache_definition
      */
     public function create_definition($component, $area, $unused = null) {
+        $definition = parent::create_definition($component, $area);
+        if ($definition->has_data_source()) {
+            return $definition;
+        }
+
         return cache_definition::load_adhoc(cache_store::MODE_REQUEST, $component, $area);
     }
 
@@ -200,7 +231,11 @@ class cache_factory_disabled extends cache_factory {
      * @throws coding_exception
      */
     public function create_cache(cache_definition $definition) {
-        return new cache_disabled($definition, $this->create_dummy_store($definition));
+        $loader = null;
+        if ($definition->has_data_source()) {
+            $loader = $definition->get_data_source();
+        }
+        return new cache_disabled($definition, $this->create_dummy_store($definition), $loader);
     }
 
     /**
@@ -292,6 +327,15 @@ class cache_factory_disabled extends cache_factory {
         // Return the instance.
         return $this->configs[$class];
     }
+
+    /**
+     * Returns true if the cache API has been disabled.
+     *
+     * @return bool
+     */
+    public function is_disabled() {
+        return true;
+    }
 }
 
 /**
@@ -484,4 +528,4 @@ class cache_config_disabled extends cache_config_writer {
     public function set_definition_mappings($definition, $mappings) {
         // Nothing to do here.
     }
-}
\ No newline at end of file
+}
index a23672e..faafe51 100644 (file)
@@ -1335,15 +1335,13 @@ class core_cache_testcase extends advanced_testcase {
         $this->assertInstanceOf('cache_config_disabled', $config);
 
         // Check we get the expected disabled caches.
-        $cache = cache::make('phpunit', 'disable');
+        $cache = cache::make('core', 'string');
         $this->assertInstanceOf('cache_disabled', $cache);
 
         // Test an application cache.
         $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'phpunit', 'disable');
         $this->assertInstanceOf('cache_disabled', $cache);
 
-        $this->assertFalse(file_exists($configfile));
-
         $this->assertFalse($cache->get('test'));
         $this->assertFalse($cache->set('test', 'test'));
         $this->assertFalse($cache->delete('test'));
@@ -1353,8 +1351,6 @@ class core_cache_testcase extends advanced_testcase {
         $cache = cache::make_from_params(cache_store::MODE_SESSION, 'phpunit', 'disable');
         $this->assertInstanceOf('cache_disabled', $cache);
 
-        $this->assertFalse(file_exists($configfile));
-
         $this->assertFalse($cache->get('test'));
         $this->assertFalse($cache->set('test', 'test'));
         $this->assertFalse($cache->delete('test'));
@@ -1364,8 +1360,6 @@ class core_cache_testcase extends advanced_testcase {
         $cache = cache::make_from_params(cache_store::MODE_REQUEST, 'phpunit', 'disable');
         $this->assertInstanceOf('cache_disabled', $cache);
 
-        $this->assertFalse(file_exists($configfile));
-
         $this->assertFalse($cache->get('test'));
         $this->assertFalse($cache->set('test', 'test'));
         $this->assertFalse($cache->delete('test'));
index 2fd3317..4660de4 100644 (file)
Binary files a/contentbank/amd/build/sort.min.js and b/contentbank/amd/build/sort.min.js differ
index a1b8557..06c091c 100644 (file)
Binary files a/contentbank/amd/build/sort.min.js.map and b/contentbank/amd/build/sort.min.js.map differ
index 34c282b..ed67de5 100644 (file)
@@ -48,63 +48,78 @@ export const init = () => {
  */
 const registerListenerEvents = (contentBank) => {
 
-    // The search.
-    const fileArea = document.querySelector(selectors.regions.filearea);
-    const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);
-
-    // The view buttons.
-    const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);
-    const viewList = contentBank.querySelector(selectors.actions.viewlist);
-
-    viewGrid.addEventListener('click', () => {
-        contentBank.classList.remove('view-list');
-        contentBank.classList.add('view-grid');
-        viewGrid.classList.add('active');
-        viewList.classList.remove('active');
-        setViewListPreference(false);
-    });
-
-    viewList.addEventListener('click', () => {
-        contentBank.classList.remove('view-grid');
-        contentBank.classList.add('view-list');
-        viewList.classList.add('active');
-        viewGrid.classList.remove('active');
-        setViewListPreference(true);
-    });
-
-    // Sort by file name alphabetical
-    const sortByName = contentBank.querySelector(selectors.actions.sortname);
-    sortByName.addEventListener('click', () => {
-        const ascending = updateSortButtons(contentBank, sortByName);
-        updateSortOrder(fileArea, shownItems, 'data-file', ascending);
-    });
-
-    // Sort by date.
-    const sortByDate = contentBank.querySelector(selectors.actions.sortdate);
-    sortByDate.addEventListener('click', () => {
-        const ascending = updateSortButtons(contentBank, sortByDate);
-        updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);
-    });
+    contentBank.addEventListener('click', e => {
+        const viewList = contentBank.querySelector(selectors.actions.viewlist);
+        const viewGrid = contentBank.querySelector(selectors.actions.viewgrid);
+
+        // View as Grid button.
+        if (e.target.closest(selectors.actions.viewgrid)) {
+            contentBank.classList.remove('view-list');
+            contentBank.classList.add('view-grid');
+            viewGrid.classList.add('active');
+            viewList.classList.remove('active');
+            setViewListPreference(false);
+
+            return;
+        }
 
-    // Sort by size.
-    const sortBySize = contentBank.querySelector(selectors.actions.sortsize);
-    sortBySize.addEventListener('click', () => {
-        const ascending = updateSortButtons(contentBank, sortBySize);
-        updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);
-    });
+        // View as List button.
+        if (e.target.closest(selectors.actions.viewlist)) {
+            contentBank.classList.remove('view-grid');
+            contentBank.classList.add('view-list');
+            viewList.classList.add('active');
+            viewGrid.classList.remove('active');
+            setViewListPreference(true);
 
-    // Sort by type.
-    const sortByType = contentBank.querySelector(selectors.actions.sorttype);
-    sortByType.addEventListener('click', () => {
-        const ascending = updateSortButtons(contentBank, sortByType);
-        updateSortOrder(fileArea, shownItems, 'data-type', ascending);
-    });
+            return;
+        }
 
-    // Sort by author.
-    const sortByAuthor = contentBank.querySelector(selectors.actions.sortauthor);
-    sortByAuthor.addEventListener('click', () => {
-        const ascending = updateSortButtons(contentBank, sortByAuthor);
-        updateSortOrder(fileArea, shownItems, 'data-author', ascending);
+        // TODO: This should _not_ use `document`. Every query should be constrained to the content bank container.
+        const fileArea = document.querySelector(selectors.regions.filearea);
+        const shownItems = fileArea.querySelectorAll(selectors.elements.listitem);
+
+        if (fileArea && shownItems) {
+
+            // Sort by file name alphabetical
+            const sortByName = e.target.closest(selectors.actions.sortname);
+            if (sortByName) {
+                const ascending = updateSortButtons(contentBank, sortByName);
+                updateSortOrder(fileArea, shownItems, 'data-file', ascending);
+                return;
+            }
+
+            // Sort by date.
+            const sortByDate = e.target.closest(selectors.actions.sortdate);
+            if (sortByDate) {
+                const ascending = updateSortButtons(contentBank, sortByDate);
+                updateSortOrder(fileArea, shownItems, 'data-timemodified', ascending);
+                return;
+            }
+
+            // Sort by size.
+            const sortBySize = e.target.closest(selectors.actions.sortsize);
+            if (sortBySize) {
+                const ascending = updateSortButtons(contentBank, sortBySize);
+                updateSortOrder(fileArea, shownItems, 'data-bytes', ascending);
+                return;
+            }
+
+            // Sort by type.
+            const sortByType = e.target.closest(selectors.actions.sorttype);
+            if (sortByType) {
+                const ascending = updateSortButtons(contentBank, sortByType);
+                updateSortOrder(fileArea, shownItems, 'data-type', ascending);
+                return;
+            }
+
+            // Sort by author.
+            const sortByAuthor = e.target.closest(selectors.actions.sortauthor);
+            if (sortByAuthor) {
+                const ascending = updateSortButtons(contentBank, sortByAuthor);
+                updateSortOrder(fileArea, shownItems, 'data-author', ascending);
+            }
+            return;
+        }
     });
 };
 
index a62beff..f5c800e 100644 (file)
@@ -95,87 +95,96 @@ data-region="contentbank">
                 <div class="cb-navbar-totalsearch d-none">
                 </div>
             </div>
-            <div class="cb-content-wrapper d-flex px-2" data-region="filearea">
-                <div class="cb-heading bg-white">
-                    <div class="cb-file cb-column d-flex">
-                        <div class="title">{{#str}} contentname, contentbank {{/str}}</div>
-                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="contentname" data-action="sortname"
-                            title="{{#str}} sortbyx, core, {{#str}} contentname, contentbank {{/str}} {{/str}}">
-                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
-                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
-                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
-                        </button>
-                    </div>
-                    <div class="cb-date cb-column d-flex">
-                        <div class="title">{{#str}} lastmodified, contentbank {{/str}}</div>
-                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="lastmodified" data-action="sortdate"
-                        title="{{#str}} sortbyx, core, {{#str}} lastmodified, contentbank {{/str}} {{/str}}">
-                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
-                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
-                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
-                        </button>
-                    </div>
-                    <div class="cb-size cb-column d-flex">
-                        <div class="title">{{#str}} size, contentbank {{/str}}</div>
-                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="size" data-action="sortsize"
-                        title="{{#str}} sortbyx, core, {{#str}} size, contentbank {{/str}} {{/str}}">
-                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
-                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
-                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
-                        </button>
-                    </div>
-                    <div class="cb-type cb-column d-flex">
-                        <div class="title">{{#str}} type, contentbank {{/str}}</div>
-                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="type" data-action="sorttype"
-                        title="{{#str}} sortbyx, core, {{#str}} type, contentbank {{/str}} {{/str}}">
-                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
-                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
-                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
-                        </button>
-                    </div>
-                    <div class="cb-author cb-column d-flex last">
-                        <div class="title">{{#str}} author, contentbank {{/str}}</div>
-                        <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="author" data-action="sortauthor"
-                        title="{{#str}} sortbyx, core, {{#str}} author, contentbank {{/str}} {{/str}}">
-                            <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
-                            <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
-                            <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
-                        </button>
-                    </div>
-                </div>
-            {{#contents}}
-                <div class="cb-listitem"
-                    data-file="{{{ title }}}"
-                    data-name="{{{ name }}}"
-                    data-bytes="{{ bytes }}"
-                    data-timemodified="{{ timemodified }}"
-                    data-type="{{{ type }}}"
-                    data-author="{{{ author }}}">
-                    <div class="cb-file cb-column position-relative">
-                        <div class="cb-thumbnail" role="img" aria-label="{{{ name }}}"
-                        style="background-image: url('{{{ icon }}}');">
+            {{#contents.0}}
+                <div class="cb-content-wrapper d-flex px-2" data-region="filearea">
+                    <div class="cb-heading bg-white">
+                        <div class="cb-file cb-column d-flex">
+                            <div class="title">{{#str}} contentname, contentbank {{/str}}</div>
+                            <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="contentname" data-action="sortname"
+                                title="{{#str}} sortbyx, core, {{#str}} contentname, contentbank {{/str}} {{/str}}">
+                                <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                                <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                                <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                            </button>
+                        </div>
+                        <div class="cb-date cb-column d-flex">
+                            <div class="title">{{#str}} lastmodified, contentbank {{/str}}</div>
+                            <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="lastmodified" data-action="sortdate"
+                            title="{{#str}} sortbyx, core, {{#str}} lastmodified, contentbank {{/str}} {{/str}}">
+                                <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                                <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                                <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                            </button>
+                        </div>
+                        <div class="cb-size cb-column d-flex">
+                            <div class="title">{{#str}} size, contentbank {{/str}}</div>
+                            <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="size" data-action="sortsize"
+                            title="{{#str}} sortbyx, core, {{#str}} size, contentbank {{/str}} {{/str}}">
+                                <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                                <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                                <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                            </button>
+                        </div>
+                        <div class="cb-type cb-column d-flex">
+                            <div class="title">{{#str}} type, contentbank {{/str}}</div>
+                            <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="type" data-action="sorttype"
+                            title="{{#str}} sortbyx, core, {{#str}} type, contentbank {{/str}} {{/str}}">
+                                <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                                <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                                <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                            </button>
+                        </div>
+                        <div class="cb-author cb-column d-flex last">
+                            <div class="title">{{#str}} author, contentbank {{/str}}</div>
+                            <button class="btn btn-sm cb-btnsort dir-none ml-auto" data-string="author" data-action="sortauthor"
+                            title="{{#str}} sortbyx, core, {{#str}} author, contentbank {{/str}} {{/str}}">
+                                <span class="default">{{#pix}} t/sort, core, {{#str}}sort, core {{/str}} {{/pix}}</span>
+                                <span class="desc">{{#pix}} t/sort_desc, core, {{#str}}desc, core{{/str}} {{/pix}}</span>
+                                <span class="asc">{{#pix}} t/sort_asc, core, {{#str}}asc, core{{/str}} {{/pix}}</span>
+                            </button>
                         </div>
-                        <a href="{{{ link }}}" class="cb-link stretched-link" title="{{{ name }}}">
-                            <span class="cb-name word-break-all clamp-2" data-region="cb-content-name">
-                                {{{ name }}}
-                            </span>
-                        </a>
-                    </div>
-                    <div class="cb-date cb-column small">
-                        {{#userdate}} {{ timemodified }}, {{#str}} strftimedatetimeshort, core_langconfig {{/str}} {{/userdate}}
-                    </div>
-                    <div class="cb-size cb-column small">
-                        {{ size }}
                     </div>
-                    <div class="cb-type cb-column small">
-                        {{{ type }}}
+                {{#contents}}
+                    <div class="cb-listitem"
+                        data-file="{{{ title }}}"
+                        data-name="{{{ name }}}"
+                        data-bytes="{{ bytes }}"
+                        data-timemodified="{{ timemodified }}"
+                        data-type="{{{ type }}}"
+                        data-author="{{{ author }}}">
+                        <div class="cb-file cb-column position-relative">
+                            <div class="cb-thumbnail" role="img" aria-label="{{{ name }}}"
+                            style="background-image: url('{{{ icon }}}');">
+                            </div>
+                            <a href="{{{ link }}}" class="cb-link stretched-link" title="{{{ name }}}">
+                                <span class="cb-name word-break-all clamp-2" data-region="cb-content-name">
+                                    {{{ name }}}
+                                </span>
+                            </a>
+                        </div>
+                        <div class="cb-date cb-column small">
+                            {{#userdate}} {{ timemodified }}, {{#str}} strftimedatetimeshort, core_langconfig {{/str}} {{/userdate}}
+                        </div>
+                        <div class="cb-size cb-column small">
+                            {{ size }}
+                        </div>
+                        <div class="cb-type cb-column small">
+                            {{{ type }}}
+                        </div>
+                        <div class="cb-type cb-column last small">
+                            {{{ author }}}
+                        </div>
                     </div>
-                    <div class="cb-type cb-column last small">
-                        {{{ author }}}
+                {{/contents}}
+                </div>
+            {{/contents.0}}
+            {{^contents.0}}
+                <div class="cb-content-wrapper d-flex flex-wrap p-2" data-region="filearea">
+                    <div class="w-100 p-3 text-center text-muted">
+                        {{#str}} nocontentavailable, core_contentbank {{/str}}
                     </div>
                 </div>
-            {{/contents}}
-            </div>
+            {{/contents.0}}
         </div>
     </div>
 </div>
index a4b4af7..f46edc4 100644 (file)
@@ -382,7 +382,7 @@ class editor {
 
         // Add JavaScript settings.
         $root = $CFG->wwwroot;
-        $filespathbase = "{$root}/pluginfile.php/{$context->id}/core_h5p/";
+        $filespathbase = \moodle_url::make_draftfile_url(0, '', '');
 
         $factory = new factory();
         $contentvalidator = $factory->get_content_validator();
@@ -390,7 +390,7 @@ class editor {
         $editorajaxtoken = core::createToken(editor_ajax::EDITOR_AJAX_TOKEN);
         $sesskey = sesskey();
         $settings['editor'] = [
-            'filesPath' => $filespathbase . 'editor',
+            'filesPath' => $filespathbase->out(),
             'fileIcon' => [
                 'path' => $url . 'images/binary-file.png',
                 'width' => 50,
index c1e24ad..ba032a4 100644 (file)
@@ -48,7 +48,12 @@ class file_storage implements \H5PFileStorage {
     public const EXPORT_FILEAREA = 'export';
     /** The icon filename */
     public const ICON_FILENAME = 'icon.svg';
-    /** The editor file area */
+
+    /**
+     * The editor file area.
+     * @deprecated since Moodle 3.10 MDL-68909. Please do not use this constant any more.
+     * @todo MDL-69530 This will be deleted in Moodle 4.2.
+     */
     public const EDITOR_FILEAREA = 'editor';
 
     /**
@@ -331,10 +336,22 @@ class file_storage implements \H5PFileStorage {
      * @return int The id of the saved file.
      */
     public function saveFile($file, $contentid) {
+        global $USER;
+
+        $context = $this->context->id;
+        $component = self::COMPONENT;
+        $filearea = self::CONTENT_FILEAREA;
+        if ($contentid === 0) {
+            $usercontext = \context_user::instance($USER->id);
+            $context = $usercontext->id;
+            $component = 'user';
+            $filearea = 'draft';
+        }
+
         $record = array(
-            'contextid' => $this->context->id,
-            'component' => self::COMPONENT,
-            'filearea' => $contentid === 0 ? self::EDITOR_FILEAREA : self::CONTENT_FILEAREA,
+            'contextid' => $context,
+            'component' => $component,
+            'filearea' => $filearea,
             'itemid' => $contentid,
             'filepath' => '/' . $file->getType() . 's/',
             'filename' => $file->getName()
@@ -357,8 +374,8 @@ class file_storage implements \H5PFileStorage {
      */
     public function cloneContentFile($file, $fromid, $tocontent): void {
         // Determine source filearea and itemid.
-        if ($fromid === self::EDITOR_FILEAREA) {
-            $sourcefilearea = self::EDITOR_FILEAREA;
+        if ($fromid === 'editor') {
+            $sourcefilearea = 'draft';
             $sourceitemid = 0;
         } else {
             $sourcefilearea = self::CONTENT_FILEAREA;
@@ -791,15 +808,22 @@ class file_storage implements \H5PFileStorage {
      * @return stored_file|null
      */
     private function get_file(string $filearea, int $itemid, string $file): ?stored_file {
-        if ($filearea === 'editor') {
+        global $USER;
+
+        $component = self::COMPONENT;
+        $context = $this->context->id;
+        if ($filearea === 'draft') {
             $itemid = 0;
+            $component = 'user';
+            $usercontext = \context_user::instance($USER->id);
+            $context = $usercontext->id;
         }
 
         $filepath = '/'. dirname($file). '/';
         $filename = basename($file);
 
         // Load file.
-        $existingfile = $this->fs->get_file($this->context->id, self::COMPONENT, $filearea, $itemid, $filepath, $filename);
+        $existingfile = $this->fs->get_file($context, $component, $filearea, $itemid, $filepath, $filename);
         if (!$existingfile) {
             return null;
         }
@@ -824,8 +848,8 @@ class file_storage implements \H5PFileStorage {
         // Create file record for content.
         $record = array(
             'contextid' => $this->context->id,
-            'component' => self::COMPONENT,
-            'filearea' => $contentid > 0 ? self::CONTENT_FILEAREA : self::EDITOR_FILEAREA,
+            'component' => $contentid > 0 ? self::COMPONENT : 'user',
+            'filearea' => $contentid > 0 ? self::CONTENT_FILEAREA : 'draft',
             'itemid' => $contentid > 0 ? $contentid : 0,
             'filepath' => '/' . $foldername . '/',
             'filename' => $filename
index 025c58b..b640925 100644 (file)
@@ -94,7 +94,6 @@ function core_h5p_pluginfile($course, $cm, $context, string $filearea, array $ar
             }
             $itemid = array_shift($args);
             break;
-        case \core_h5p\file_storage::EDITOR_FILEAREA:
         case \core_h5p\file_storage::CACHED_ASSETS_FILEAREA:
         case \core_h5p\file_storage::EXPORT_FILEAREA:
             $itemid = 0;
index dff6022..8145ab5 100644 (file)
@@ -182,6 +182,9 @@ class editor_testcase extends advanced_testcase {
     public function test_add_editor_to_form() {
         global $PAGE, $CFG;
 
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
         // Get form data.
         $form = $this->get_test_form();
         $mform = $form->getform();
index 88c229d..72e8087 100644 (file)
@@ -414,6 +414,8 @@ class core_h5p_generator extends \component_generator_base {
      * @throws coding_exception
      */
     public function create_content_file(string $file, string $filearea, int $contentid = 0): stored_file {
+        global $USER;
+
         $filepath = '/'.dirname($file).'/';
         $filename = basename($file);
 
@@ -421,15 +423,25 @@ class core_h5p_generator extends \component_generator_base {
             throw new coding_exception('Files belonging to an H5P content must specify the H5P content id');
         }
 
-        $content = 'fake content';
+        if ($filearea === 'draft') {
+            $usercontext = \context_user::instance($USER->id);
+            $context = $usercontext->id;
+            $component = 'user';
+            $itemid = 0;
+        } else {
+            $systemcontext = context_system::instance();
+            $context = $systemcontext->id;
+            $component = \core_h5p\file_storage::COMPONENT;
+            $itemid = $contentid;
+        }
 
-        $systemcontext = context_system::instance();
+        $content = 'fake content';
 
         $filerecord = array(
-            'contextid' => $systemcontext->id,
-            'component' => \core_h5p\file_storage::COMPONENT,
+            'contextid' => $context,
+            'component' => $component,
             'filearea'  => $filearea,
-            'itemid'    => ($filearea === 'editor') ? 0 : $contentid,
+            'itemid'    => $itemid,
             'filepath'  => $filepath,
             'filename'  => $filename,
         );
index d94d395..8cce6c2 100644 (file)
@@ -625,6 +625,7 @@ class h5p_file_storage_testcase extends \advanced_testcase {
      */
     public function test_get_file(): void {
 
+        $this->setAdminUser();
         $file = 'img/fake.png';
         $h5pcontentid = 3;
 
@@ -641,9 +642,9 @@ class h5p_file_storage_testcase extends \advanced_testcase {
         $this->assertInstanceOf('stored_file', $contentfile);
 
         // Add a file to editor.
-        $this->h5p_generator->create_content_file($file, file_storage::EDITOR_FILEAREA, $h5pcontentid);
+        $this->h5p_generator->create_content_file($file, 'draft', $h5pcontentid);
 
-        $editorfile = $method->invoke(new file_storage(), file_storage::EDITOR_FILEAREA, $h5pcontentid, $file);
+        $editorfile = $method->invoke(new file_storage(), 'draft', $h5pcontentid, $file);
 
         // Check that it returns an instance of store_file.
         $this->assertInstanceOf('stored_file', $editorfile);
@@ -692,6 +693,9 @@ class h5p_file_storage_testcase extends \advanced_testcase {
      */
     public function test_cloneContentFile(): void {
 
+        $admin = get_admin();
+        $usercontext = \context_user::instance($admin->id);
+        $this->setUser($admin);
         // Upload a file to the editor.
         $file = 'images/fake.jpg';
         $filepath = '/'.dirname($file).'/';
@@ -700,9 +704,9 @@ class h5p_file_storage_testcase extends \advanced_testcase {
         $content = 'abcd';
 
         $filerecord = array(
-            'contextid' => $this->h5p_fs_context->id,
-            'component' => file_storage::COMPONENT,
-            'filearea'  => file_storage::EDITOR_FILEAREA,
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
             'itemid'    => 0,
             'filepath'  => $filepath,
             'filename'  => $filename,
@@ -731,7 +735,9 @@ class h5p_file_storage_testcase extends \advanced_testcase {
         $filename = basename($file);
 
         $sourcecontentid = 111;
-        $filerecord['filearea'] = 'content';
+        $filerecord['contextid'] = $this->h5p_fs_context->id;
+        $filerecord['component'] = file_storage::COMPONENT;
+        $filerecord['filearea'] = file_storage::CONTENT_FILEAREA;
         $filerecord['itemid'] = $sourcecontentid;
         $filerecord['filepath'] = $filepath;
         $filerecord['filename'] = $filename;
index f13cc48..7ff69a1 100644 (file)
@@ -137,6 +137,7 @@ $string['configgeneralgroups'] = 'Sets the default for including groups and grou
 $string['configgeneralroleassignments'] = 'If enabled by default roles assignments will also be backed up.';
 $string['configgeneraluserscompletion'] = 'If enabled user completion information will be included in backups by default.';
 $string['configgeneralusers'] = 'Sets the default for whether to include users in backups.';
+$string['configlegacyfiles'] = 'If disabled, legacy course files will not be included';
 $string['configloglifetime'] = 'This specifies the length of time you want to keep backup logs information. Logs that are older than this age are automatically deleted. It is recommended to keep this value small, because backup logged information can be huge.';
 $string['configrestoreactivities'] = 'Sets the default for restoring activities.';
 $string['configrestorebadges'] = 'Sets the default for restoring badges.';
@@ -227,6 +228,7 @@ $string['generalfiles'] = 'Include files';
 $string['generalfilters'] = 'Include filters';
 $string['generalhistories'] = 'Include histories';
 $string['generalgradehistories'] = 'Include histories';
+$string['generallegacyfiles'] = 'Include legacy course files';
 $string['generallogs'] = 'Include logs';
 $string['generalquestionbank'] = 'Include question bank';
 $string['generalgroups'] = 'Include groups and groupings';
@@ -359,6 +361,7 @@ $string['rootsettingcalendarevents'] = 'Include calendar events';
 $string['rootsettingcontentbankcontent'] = 'Include content bank content';
 $string['rootsettinguserscompletion'] = 'Include user completion details';
 $string['rootsettingquestionbank'] = 'Include question bank';
+$string['rootsettinglegacyfiles'] = 'Include legacy course files';
 $string['rootsettinglogs'] = 'Include course logs';
 $string['rootsettinggradehistories'] = 'Include grade history';
 $string['rootsettinggroups'] = 'Include groups and groupings';
index 6535c93..4e073a6 100644 (file)
@@ -51,6 +51,7 @@ $string['file_help'] = 'Files may be stored in the content bank for use in cours
 $string['itemsfound'] = '{$a} items found';
 $string['lastmodified'] = 'Last modified';
 $string['name'] = 'Content';
+$string['nocontentavailable'] = 'No content available';
 $string['nocontenttypes'] = 'No content types available';
 $string['nopermissiontodelete'] = 'You do not have permission to delete content.';
 $string['nopermissiontomanage'] = 'You do not have permission to manage content.';
index 60ac970..5d8760a 100644 (file)
@@ -2684,5 +2684,15 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2020082200.03);
     }
 
+    if ($oldversion < 2020091000.02) {
+        // Remove all the files with component='core_h5p' and filearea='editor' because they won't be used anymore.
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+        $fs->delete_area_files($syscontext->id, 'core_h5p', 'editor');
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2020091000.02);
+    }
+
     return true;
 }
index 50cd9da..7f33390 100644 (file)
@@ -250,7 +250,7 @@ class external_api {
                 foreach ($plugins as $plugin => $callback) {
                     $result = $callback($externalfunctioninfo, $params);
                     if ($result !== false) {
-                        break;
+                        break 2;
                     }
                 }
             }
index 74e7c80..ba5366f 100644 (file)
@@ -1385,15 +1385,20 @@ class core_renderer extends renderer_base {
     public function footer() {
         global $CFG, $DB;
 
+        $output = '';
+
         // Give plugins an opportunity to touch the page before JS is finalized.
         $pluginswithfunction = get_plugins_with_function('before_footer', 'lib.php');
         foreach ($pluginswithfunction as $plugins) {
             foreach ($plugins as $function) {
-                $function();
+                $extrafooter = $function();
+                if (is_string($extrafooter)) {
+                    $output .= $extrafooter;
+                }
             }
         }
 
-        $output = $this->container_end_all(true);
+        $output .= $this->container_end_all(true);
 
         $footer = $this->opencontainers->pop('header/footer');
 
@@ -2691,7 +2696,7 @@ $iconprogress
 EOD;
         if ($options->env != 'url') {
             $html .= <<<EOD
-    <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist border" style="position: relative">
+    <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist" style="position: relative">
     <div class="filepicker-filename">
         <div class="filepicker-container">$currentfile<div class="dndupload-message">$strdndenabled <br/><div class="dndupload-arrow"></div></div></div>
         <div class="dndupload-progressbars"></div>
index 23c54ee..5e5c3d1 100644 (file)
@@ -42,6 +42,8 @@ information provided here is intended especially for developers.
   be called before executing a task, and a new function \core\task\manager::get_running_tasks()
   returns information about currently-running tasks.
 * New library function rename_to_unused_name() to rename a file within its current location.
+* Constant \core_h5p\file_storage::EDITOR_FILEAREA has been deprecated
+  because it's not required any more.
 
 === 3.9 ===
 * Following function has been deprecated, please use \core\task\manager::run_from_cli().
index 8676816..1b63ab4 100644 (file)
@@ -99,7 +99,7 @@
     <div class="tool-card-content">
         <div class="tool-card-header">
             <div class="tool-card-subheader">
-                <div class="tag
+                <div class="badge
                             {{#state.pending}}badge-info{{/state.pending}}
                             {{#state.configured}}badge-success{{/state.configured}}
                             {{#state.rejected}}badge-danger{{/state.rejected}}
index 466eb74..5598e4c 100644 (file)
@@ -37,57 +37,59 @@ function xmldb_quizaccess_seb_install() {
     $params = ['browsersecurity' => 'safebrowser'];
 
     $total = $DB->count_records('quiz', $params);
-    $rs = $DB->get_recordset('quiz', $params);
+    if ($total > 0) {
+        $rs = $DB->get_recordset('quiz', $params);
 
-    $i = 0;
-    $pbar = new progress_bar('updatequizrecords', 500, true);
+        $i = 0;
+        $pbar = new progress_bar('updatequizrecords', 500, true);
 
-    foreach ($rs as $quiz) {
-        if (!$DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $quiz->id])) {
-            $cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course);
+        foreach ($rs as $quiz) {
+            if (!$DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $quiz->id])) {
+                $cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course);
 
-            $sebsettings = new stdClass();
+                $sebsettings = new stdClass();
 
-            $sebsettings->quizid = $quiz->id;
-            $sebsettings->cmid = $cm->id;
-            $sebsettings->templateid = 0;
-            $sebsettings->requiresafeexambrowser = \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG;
-            $sebsettings->showsebtaskbar = null;
-            $sebsettings->showwificontrol = null;
-            $sebsettings->showreloadbutton = null;
-            $sebsettings->showtime = null;
-            $sebsettings->showkeyboardlayout = null;
-            $sebsettings->allowuserquitseb = null;
-            $sebsettings->quitpassword = null;
-            $sebsettings->linkquitseb = null;
-            $sebsettings->userconfirmquit = null;
-            $sebsettings->enableaudiocontrol = null;
-            $sebsettings->muteonstartup = null;
-            $sebsettings->allowspellchecking = null;
-            $sebsettings->allowreloadinexam = null;
-            $sebsettings->activateurlfiltering = null;
-            $sebsettings->filterembeddedcontent = null;
-            $sebsettings->expressionsallowed = null;
-            $sebsettings->regexallowed = null;
-            $sebsettings->expressionsblocked = null;
-            $sebsettings->regexblocked = null;
-            $sebsettings->allowedbrowserexamkeys = null;
-            $sebsettings->showsebdownloadlink = 1;
-            $sebsettings->usermodified = get_admin()->id;
-            $sebsettings->timecreated = time();
-            $sebsettings->timemodified = time();
+                $sebsettings->quizid = $quiz->id;
+                $sebsettings->cmid = $cm->id;
+                $sebsettings->templateid = 0;
+                $sebsettings->requiresafeexambrowser = \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG;
+                $sebsettings->showsebtaskbar = null;
+                $sebsettings->showwificontrol = null;
+                $sebsettings->showreloadbutton = null;
+                $sebsettings->showtime = null;
+                $sebsettings->showkeyboardlayout = null;
+                $sebsettings->allowuserquitseb = null;
+                $sebsettings->quitpassword = null;
+                $sebsettings->linkquitseb = null;
+                $sebsettings->userconfirmquit = null;
+                $sebsettings->enableaudiocontrol = null;
+                $sebsettings->muteonstartup = null;
+                $sebsettings->allowspellchecking = null;
+                $sebsettings->allowreloadinexam = null;
+                $sebsettings->activateurlfiltering = null;
+                $sebsettings->filterembeddedcontent = null;
+                $sebsettings->expressionsallowed = null;
+                $sebsettings->regexallowed = null;
+                $sebsettings->expressionsblocked = null;
+                $sebsettings->regexblocked = null;
+                $sebsettings->allowedbrowserexamkeys = null;
+                $sebsettings->showsebdownloadlink = 1;
+                $sebsettings->usermodified = get_admin()->id;
+                $sebsettings->timecreated = time();
+                $sebsettings->timemodified = time();
 
-            $DB->insert_record('quizaccess_seb_quizsettings', $sebsettings);
+                $DB->insert_record('quizaccess_seb_quizsettings', $sebsettings);
 
-            $quiz->browsersecurity = '-';
-            $DB->update_record('quiz', $quiz);
+                $quiz->browsersecurity = '-';
+                $DB->update_record('quiz', $quiz);
+            }
+
+            $i++;
+            $pbar->update($i, $total, "Reconfiguring existing quizzes to use a new SEB plugin - $i/$total.");
         }
 
-        $i++;
-        $pbar->update($i, $total, "Reconfiguring existing quizzes to use a new SEB plugin - $i/$total.");
+        $rs->close();
     }
 
-    $rs->close();
-
     return true;
 }
index d71a4fd..6bac700 100644 (file)
@@ -41,3 +41,4 @@ $breadcrumb-divider-rtl: "◀" !default;
 @import "moodle/modal";
 @import "moodle/layout";
 @import "moodle/prefixes";
+@import "moodle/atto";
index b89ad6f..65b5cd0 100644 (file)
     }
     .admin_colourpicker .colourdialogue {
         float: left;
-        border: 1px solid $state-info-border;
+        border: 1px solid $input-border-color;
     }
     .admin_colourpicker .previewcolour {
-        border: 1px solid $state-info-border;
+        border: 1px solid $input-border-color;
         margin-left: 301px;
     }
 
     .admin_colourpicker .currentcolour {
-        border: 1px solid $state-info-border;
+        border: 1px solid $input-border-color;
         margin-left: 301px;
         border-top-width: 0;
     }
similarity index 85%
rename from lib/editor/atto/styles.css
rename to theme/boost/scss/moodle/atto.scss
index f2b74e1..af3b9f4 100644 (file)
@@ -13,8 +13,6 @@
 .editor_atto + textarea {
     width: 100%;
     padding: 0;
-    border: 1px solid #bbb;
-    border-top: none;
 }
 
 .editor_atto + textarea {
@@ -27,7 +25,7 @@ div.editor_atto_toolbar {
     display: block;
     background: #f2f2f2;
     min-height: 35px;
-    border: 1px solid #bbb;
+    border: 1px solid $input-border-color;
     width: 100%;
     padding: 0 0 9px 0;
 }
@@ -201,3 +199,22 @@ div.editor_atto_content:hover .atto_control {
 .editor_atto + textarea {
     box-sizing: border-box;
 }
+
+.editor_atto_content.form-control {
+    width: 100%;
+    border-top: 0;
+}
+
+/** Atto fields do not have form-control because that would break the layout of the editor.
+    So they need these extra styles to highlight the editor when there is a validation error. */
+.has-danger .editor_atto_content.form-control,
+.has-danger .editor_atto_content.form-control-danger {
+    @include form-validation-state('invalid', $form-feedback-invalid-color, $form-feedback-icon-invalid);
+}
+
+.open.atto_menu > .dropdown-menu {
+    display: block;
+}
+div.editor_atto_toolbar button .icon {
+    color: $gray-700;
+}
\ No newline at end of file
index 9bf6081..2ddbc8b 100644 (file)
@@ -2387,12 +2387,6 @@ $footer-link-color: $bg-inverse-link-color !default;
     }
 }
 
-.open.atto_menu > .dropdown-menu {
-    display: block;
-}
-div.editor_atto_toolbar button .icon {
-    color: $gray-700;
-}
 .w-auto {
     width: auto;
 }
index 347004d..5c22f2e 100644 (file)
     min-height: 520px;
 }
 .file-picker .fp-navbar {
-    border-bottom: 1px solid #e5e5e5;
     min-height: 40px;
     padding: 4px;
 }
 
+.fp-navbar {
+    border-color: $input-border-color;
+    border-bottom: 0;
+}
+
 .file-picker .fp-content {
     border-top: 0;
     background: #fff;
@@ -602,7 +606,7 @@ a.ygtvspacer:hover {
 .filepicker-filelist,
 .filemanager-container {
     min-height: 140px;
-    border-top: 0;
+    border: 1px solid $input-border-color;
 }
 
 .filemanager .fp-content {
index 12a3234..bd1dd48 100644 (file)
     }
 }
 
-.editor_atto_content.form-control {
-    width: 100%;
-}
-
 #adminsettings .form-control[size] {
     width: auto;
 }
@@ -410,20 +406,6 @@ textarea[data-auto-rows] {
     max-width: 30rem;
 }
 
-/** Atto fields do not have form-control because that would break the layout of the editor.
-    So they need these extra styles to highlight the editor when there is a validation error. */
-/* stylelint-disable function-url-scheme-blacklist */
-$form-icon-danger: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E") !default;
-/* stylelint-enable function-url-scheme-blacklist */
-.has-danger .editor_atto_content.form-control,
-.has-danger .editor_atto_content.form-control-danger {
-    background-image: $form-icon-danger;
-    padding-right: ($input-padding-x * 3);
-    background-repeat: no-repeat;
-    background-position: center right 1rem;
-    background-size: 1.5rem;
-}
-
 // Styles for the JS file types browser provided by the "filetypes" element.
 [data-filetypesbrowserbody] {
     [aria-expanded="false"] > [role="group"],
index 11482b3..4ae8a39 100644 (file)
@@ -4,7 +4,7 @@ $gray-100: #f8f9fa !default;
 $gray-200: #e9ecef !default;
 $gray-300: #dee2e6 !default;
 $gray-400: #ced4da !default;
-$gray-500: #adb5bd !default;
+$gray-500: #8f959e !default;
 $gray-600: #6c757d !default;
 $gray-700: #495057 !default;
 $gray-800: #343a40 !default;
@@ -71,6 +71,8 @@ $custom-control-indicator-size: 1.25rem;
 
 $input-btn-focus-color: rgba($primary, .75) !default;
 
+$input-border-color: $gray-500 !default;
+
 // stylelint-disable
 $theme-colors: () !default;
 $theme-colors: map-merge((
index 80336cf..5819e8b 100644 (file)
@@ -3778,7 +3778,7 @@ pre {
   color: #495057;
   background-color: #fff;
   background-clip: padding-box;
-  border: 1px solid #ced4da;
+  border: 1px solid #8f959e;
   border-radius: 0;
   transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; }
   @media (max-width: 1200px) {
@@ -4948,7 +4948,7 @@ input[type="button"].btn-block {
   text-align: center;
   white-space: nowrap;
   background-color: #e9ecef;
-  border: 1px solid #ced4da; }
+  border: 1px solid #8f959e; }
   @media (max-width: 1200px) {
     .input-group-text {
       font-size: calc(0.90375rem + 0.045vw) ; } }
@@ -5044,7 +5044,7 @@ input[type="button"].btn-block {
     pointer-events: none;
     content: "";
     background-color: #fff;
-    border: #adb5bd solid 1px; }
+    border: #8f959e solid 1px; }
   .custom-control-label::after {
     position: absolute;
     top: 0.078125rem;
@@ -5092,7 +5092,7 @@ input[type="button"].btn-block {
     left: calc(-2.6875rem + 2px);
     width: calc(1.25rem - 4px);
     height: calc(1.25rem - 4px);
-    background-color: #adb5bd;
+    background-color: #8f959e;
     border-radius: 0.625rem;
     transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; }
     @media (prefers-reduced-motion: reduce) {
@@ -5115,7 +5115,7 @@ input[type="button"].btn-block {
   color: #495057;
   vertical-align: middle;
   background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px;
-  border: 1px solid #ced4da;
+  border: 1px solid #8f959e;
   border-radius: 0;
   appearance: none; }
   @media (max-width: 1200px) {
@@ -5195,7 +5195,7 @@ input[type="button"].btn-block {
   line-height: 1.5;
   color: #495057;
   background-color: #fff;
-  border: 1px solid #ced4da; }
+  border: 1px solid #8f959e; }
   .custom-file-label::after {
     position: absolute;
     top: 0;
@@ -5295,15 +5295,15 @@ input[type="button"].btn-block {
     margin-right: 15px;
     background-color: #dee2e6; }
   .custom-range:disabled::-webkit-slider-thumb {
-    background-color: #adb5bd; }
+    background-color: #8f959e; }
   .custom-range:disabled::-webkit-slider-runnable-track {
     cursor: default; }
   .custom-range:disabled::-moz-range-thumb {
-    background-color: #adb5bd; }
+    background-color: #8f959e; }
   .custom-range:disabled::-moz-range-track {
     cursor: default; }
   .custom-range:disabled::-ms-thumb {
-    background-color: #adb5bd; }
+    background-color: #8f959e; }
 
 .custom-control-label::before,
 .custom-file-label,
@@ -9561,7 +9561,7 @@ a.text-dark:hover, a.text-dark:focus {
     white-space: pre-wrap !important; }
   pre,
   blockquote {
-    border: 1px solid #adb5bd;
+    border: 1px solid #8f959e;
     page-break-inside: avoid; }
   thead {
     display: table-header-group; }
@@ -11559,12 +11559,6 @@ ul {
       width: 30px;
       font-size: 30px; }
 
-.open.atto_menu > .dropdown-menu {
-  display: block; }
-
-div.editor_atto_toolbar button .icon {
-  color: #495057; }
-
 .w-auto {
   width: auto; }
 
@@ -12289,12 +12283,12 @@ input[disabled] {
     box-sizing: content-box; }
   .admin_colourpicker .colourdialogue {
     float: left;
-    border: 1px solid #b8dce2; }
+    border: 1px solid #8f959e; }
   .admin_colourpicker .previewcolour {
-    border: 1px solid #b8dce2;
+    border: 1px solid #8f959e;
     margin-left: 301px; }
   .admin_colourpicker .currentcolour {
-    border: 1px solid #b8dce2;
+    border: 1px solid #8f959e;
     margin-left: 301px;
     border-top-width: 0; } }
 
@@ -14191,10 +14185,13 @@ body.drawer-ease {
   min-height: 520px; }
 
 .file-picker .fp-navbar {
-  border-bottom: 1px solid #e5e5e5;
   min-height: 40px;
   padding: 4px; }
 
+.fp-navbar {
+  border-color: #8f959e;
+  border-bottom: 0; }
+
 .file-picker .fp-content {
   border-top: 0;
   background: #fff;
@@ -14656,7 +14653,7 @@ a.ygtvspacer:hover {
 .filepicker-filelist,
 .filemanager-container {
   min-height: 140px;
-  border-top: 0; }
+  border: 1px solid #8f959e; }
 
 .filemanager .fp-content {
   overflow: auto;
@@ -16297,9 +16294,6 @@ body.path-question-type .mform fieldset.hidden {
   .mform > .form-group {
     margin-left: 1.5rem; } }
 
-.editor_atto_content.form-control {
-  width: 100%; }
-
 #adminsettings .form-control[size] {
   width: auto; }
 
@@ -16577,18 +16571,6 @@ textarea[data-auto-rows] {
   margin-left: 15px;
   max-width: 30rem; }
 
-/** Atto fields do not have form-control because that would break the layout of the editor.
-    So they need these extra styles to highlight the editor when there is a validation error. */
-/* stylelint-disable function-url-scheme-blacklist */
-/* stylelint-enable function-url-scheme-blacklist */
-.has-danger .editor_atto_content.form-control,
-.has-danger .editor_atto_content.form-control-danger {
-  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E");
-  padding-right: 2.25rem;
-  background-repeat: no-repeat;
-  background-position: center right 1rem;
-  background-size: 1.5rem; }
-
 [data-filetypesbrowserbody] [aria-expanded="false"] > [role="group"],
 [data-filetypesbrowserbody] [aria-expanded="false"] [data-filetypesbrowserfeature="hideifcollapsed"],
 [data-filetypesbrowserbody] [aria-expanded="true"] [data-filetypesbrowserfeature="hideifexpanded"] {
@@ -16971,7 +16953,7 @@ select {
     height: calc(1.5em + 1rem + 2px);
     border-radius: 0;
     background-color: #fff;
-    border-color: #ced4da;
+    border-color: #8f959e;
     padding-left: calc(0.5rem + 8px);
     padding-top: 0.5rem;
     padding-bottom: 0.5rem;
@@ -16987,7 +16969,7 @@ select {
     height: calc(1.5em + 1rem + 2px);
     border-radius: 0;
     background-color: #fff;
-    border-color: #ced4da;
+    border-color: #8f959e;
     padding-left: calc(0.5rem + 8px);
     padding-top: 0.5rem;
     padding-bottom: 0.5rem;
@@ -19171,6 +19153,301 @@ input[type="month"].form-control {
   /* stylelint-disable-line declaration-no-important */
   -ms-user-select: none; }
 
+.editor_atto_content_wrap {
+  background-color: white;
+  color: #333; }
+
+.editor_atto_content {
+  padding: 4px;
+  resize: vertical;
+  overflow: auto; }
+
+.editor_atto_content_wrap,
+.editor_atto + textarea {
+  width: 100%;
+  padding: 0; }
+
+.editor_atto + textarea {
+  border-radius: 0;
+  resize: vertical;
+  margin-top: -1px; }
+
+div.editor_atto_toolbar {
+  display: block;
+  background: #f2f2f2;
+  min-height: 35px;
+  border: 1px solid #8f959e;
+  width: 100%;
+  padding: 0 0 9px 0; }
+
+div.editor_atto_toolbar button {
+  padding: 4px 9px;
+  background: none;
+  border: 0;
+  margin: 0;
+  border-radius: 0;
+  cursor: pointer; }
+
+div.editor_atto_toolbar button + button {
+  border-left: 1px solid #ccc; }
+
+div.editor_atto_toolbar button[disabled] {
+  opacity: .45;
+  background: none;
+  cursor: default; }
+
+.editor_atto_toolbar button:hover {
+  background-image: radial-gradient(ellipse at center, #fff 60%, #dfdfdf 100%);
+  background-color: #ebebeb; }
+
+.editor_atto_toolbar button:active,
+.editor_atto_toolbar button.highlight {
+  background-image: radial-gradient(ellipse at center, #fff 40%, #dfdfdf 100%);
+  background-color: #dfdfdf; }
+
+/* Make firefox button sizes match other browsers */
+div.editor_atto_toolbar button::-moz-focus-inner {
+  border: 0;
+  padding: 0; }
+
+div.editor_atto_toolbar button .icon {
+  padding: 0;
+  margin: 2px 0; }
+
+div.editor_atto_toolbar div.atto_group {
+  display: inline-block;
+  border: 1px solid #ccc;
+  border-bottom: 1px solid #b3b3b3;
+  border-radius: 4px;
+  margin: 9px 0 0 9px;
+  background: #fff; }
+
+.editor_atto_content img {
+  resize: both;
+  overflow: auto; }
+
+.atto_hasmenu {
+  /* IE8 places the images on top of each other if that is not set. */
+  white-space: nowrap; }
+
+.atto_menuentry .icon {
+  width: 16px;
+  height: 16px; }
+
+.atto_menuentry {
+  clear: left; }
+
+.atto_menuentry h1,
+.atto_menuentry h2,
+.atto_menuentry p {
+  margin: 4px; }
+
+/*.atto_form label.sameline {
+    display: inline-block;
+    min-width: 10em;
+}*/
+.atto_form textarea.fullwidth,
+.atto_form input.fullwidth {
+  width: 100%; }
+
+.atto_form {
+  padding: 0.5rem; }
+
+/*.atto_form label {
+    display: block;
+    margin: 0 0 5px 0;
+}*/
+.atto_control {
+  position: absolute;
+  right: -6px;
+  bottom: -6px;
+  display: none;
+  cursor: pointer; }
+
+.atto_control .icon {
+  background-color: white; }
+
+div.editor_atto_content:focus .atto_control,
+div.editor_atto_content:hover .atto_control {
+  display: block; }
+
+.editor_atto_menu.yui3-menu-hidden {
+  display: none; }
+
+/* Get broken images back in firefox */
+.editor_atto_content img:-moz-broken {
+  -moz-force-broken-image-icon: 1;
+  min-width: 24px;
+  min-height: 24px; }
+
+/* Atto menu styling */
+.moodle-dialogue-base .editor_atto_menu .moodle-dialogue-content .moodle-dialogue-bd {
+  padding: 0;
+  z-index: 1000; }
+
+.editor_atto_menu .dropdown-menu > li > a {
+  margin: 3px 14px; }
+
+.editor_atto_menu .open ul.dropdown-menu {
+  padding-top: 5px;
+  padding-bottom: 5px; }
+
+.editor_atto_wrap {
+  position: relative; }
+
+/*rtl:ignore*/
+.editor_atto_wrap textarea {
+  direction: ltr; }
+
+.editor_atto_notification .atto_info,
+.editor_atto_notification .atto_warning {
+  display: inline-block;
+  background-color: #f2f2f2;
+  padding: 0.5em;
+  padding-left: 1em;
+  padding-right: 1em;
+  border-bottom-left-radius: 1em;
+  border-bottom-right-radius: 1em; }
+
+.editor_atto_notification .atto_info {
+  background-color: #f2f2f2; }
+
+.editor_atto_notification .atto_warning {
+  background-color: #ffd700; }
+
+.editor_atto_toolbar,
+.editor_atto_content_wrap,
+.editor_atto + textarea {
+  box-sizing: border-box; }
+
+.editor_atto_content.form-control {
+  width: 100%;
+  border-top: 0; }
+
+/** Atto fields do not have form-control because that would break the layout of the editor.
+    So they need these extra styles to highlight the editor when there is a validation error. */
+.has-danger .editor_atto_content.form-control .invalid-feedback,
+.has-danger .editor_atto_content.form-control-danger .invalid-feedback {
+  display: none;
+  width: 100%;
+  margin-top: 0.25rem;
+  font-size: 80%;
+  color: #d43f3a; }
+
+.has-danger .editor_atto_content.form-control .invalid-tooltip,
+.has-danger .editor_atto_content.form-control-danger .invalid-tooltip {
+  position: absolute;
+  top: 100%;
+  z-index: 5;
+  display: none;
+  max-width: 100%;
+  padding: 0.25rem 0.5rem;
+  margin-top: .1rem;
+  font-size: 0.8203125rem;
+  line-height: 1.5;
+  color: #fff;
+  background-color: rgba(212, 63, 58, 0.9); }
+
+.was-validated .has-danger .editor_atto_content.form-control:invalid ~ .invalid-feedback,
+.was-validated .has-danger .editor_atto_content.form-control:invalid ~ .invalid-tooltip, .has-danger .editor_atto_content.form-control.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control.is-invalid ~ .invalid-tooltip, .was-validated
+.has-danger .editor_atto_content.form-control-danger:invalid ~ .invalid-feedback,
+.was-validated
+.has-danger .editor_atto_content.form-control-danger:invalid ~ .invalid-tooltip,
+.has-danger .editor_atto_content.form-control-danger.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control-danger.is-invalid ~ .invalid-tooltip {
+  display: block; }
+
+.was-validated .has-danger .editor_atto_content.form-control .form-control:invalid, .has-danger .editor_atto_content.form-control .form-control.is-invalid, .was-validated
+.has-danger .editor_atto_content.form-control-danger .form-control:invalid,
+.has-danger .editor_atto_content.form-control-danger .form-control.is-invalid {
+  border-color: #d43f3a;
+  padding-right: calc(1.5em + 0.75rem);
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23d43f3a' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d43f3a' stroke='none'/%3e%3c/svg%3e");
+  background-repeat: no-repeat;
+  background-position: right calc(0.375em + 0.1875rem) center;
+  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  .was-validated .has-danger .editor_atto_content.form-control .form-control:invalid:focus, .has-danger .editor_atto_content.form-control .form-control.is-invalid:focus, .was-validated
+  .has-danger .editor_atto_content.form-control-danger .form-control:invalid:focus,
+  .has-danger .editor_atto_content.form-control-danger .form-control.is-invalid:focus {
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.was-validated .has-danger .editor_atto_content.form-control textarea.form-control:invalid, .has-danger .editor_atto_content.form-control textarea.form-control.is-invalid, .was-validated
+.has-danger .editor_atto_content.form-control-danger textarea.form-control:invalid,
+.has-danger .editor_atto_content.form-control-danger textarea.form-control.is-invalid {
+  padding-right: calc(1.5em + 0.75rem);
+  background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-select:invalid, .has-danger .editor_atto_content.form-control .custom-select.is-invalid, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-select:invalid,
+.has-danger .editor_atto_content.form-control-danger .custom-select.is-invalid {
+  border-color: #d43f3a;
+  padding-right: calc(0.75em + 2.3125rem);
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23d43f3a' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d43f3a' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  .was-validated .has-danger .editor_atto_content.form-control .custom-select:invalid:focus, .has-danger .editor_atto_content.form-control .custom-select.is-invalid:focus, .was-validated
+  .has-danger .editor_atto_content.form-control-danger .custom-select:invalid:focus,
+  .has-danger .editor_atto_content.form-control-danger .custom-select.is-invalid:focus {
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.was-validated .has-danger .editor_atto_content.form-control .form-check-input:invalid ~ .form-check-label, .has-danger .editor_atto_content.form-control .form-check-input.is-invalid ~ .form-check-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .form-check-input:invalid ~ .form-check-label,
+.has-danger .editor_atto_content.form-control-danger .form-check-input.is-invalid ~ .form-check-label {
+  color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .form-check-input:invalid ~ .invalid-feedback,
+.was-validated .has-danger .editor_atto_content.form-control .form-check-input:invalid ~ .invalid-tooltip, .has-danger .editor_atto_content.form-control .form-check-input.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control .form-check-input.is-invalid ~ .invalid-tooltip, .was-validated
+.has-danger .editor_atto_content.form-control-danger .form-check-input:invalid ~ .invalid-feedback,
+.was-validated
+.has-danger .editor_atto_content.form-control-danger .form-check-input:invalid ~ .invalid-tooltip,
+.has-danger .editor_atto_content.form-control-danger .form-check-input.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control-danger .form-check-input.is-invalid ~ .invalid-tooltip {
+  display: block; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid ~ .custom-control-label, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid ~ .custom-control-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid ~ .custom-control-label,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid ~ .custom-control-label {
+  color: #d43f3a; }
+  .was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid ~ .custom-control-label::before, .was-validated
+  .has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid ~ .custom-control-label::before,
+  .has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid ~ .custom-control-label::before {
+    border-color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid:checked ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid:checked ~ .custom-control-label::before, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid:checked ~ .custom-control-label::before,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid:checked ~ .custom-control-label::before {
+  border-color: #dd6864;
+  background-color: #dd6864; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid:focus ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid:focus ~ .custom-control-label::before, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid:focus ~ .custom-control-label::before,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid:focus ~ .custom-control-label::before {
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-file-input:invalid ~ .custom-file-label, .has-danger .editor_atto_content.form-control .custom-file-input.is-invalid ~ .custom-file-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-file-input:invalid ~ .custom-file-label,
+.has-danger .editor_atto_content.form-control-danger .custom-file-input.is-invalid ~ .custom-file-label {
+  border-color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-file-input:invalid:focus ~ .custom-file-label, .has-danger .editor_atto_content.form-control .custom-file-input.is-invalid:focus ~ .custom-file-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-file-input:invalid:focus ~ .custom-file-label,
+.has-danger .editor_atto_content.form-control-danger .custom-file-input.is-invalid:focus ~ .custom-file-label {
+  border-color: #d43f3a;
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.open.atto_menu > .dropdown-menu {
+  display: block; }
+
+div.editor_atto_toolbar button .icon {
+  color: #495057; }
+
 body {
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale; }
index 28b8d10..7b0941e 100644 (file)
@@ -4,7 +4,7 @@ $gray-100: #f8f9fa !default;
 $gray-200: #e9ecef !default;
 $gray-300: #dee2e6 !default;
 $gray-400: #ced4da !default;
-$gray-500: #adb5bd !default;
+$gray-500: #8f959e !default;
 $gray-600: #6c757d !default;
 $gray-700: #495057 !default;
 $gray-800: #343a40 !default;
@@ -65,6 +65,8 @@ $card-group-margin: .25rem;
 
 $input-btn-focus-color: rgba($primary, .75) !default;
 
+$input-border-color: $gray-500 !default;
+
 // stylelint-disable
 $theme-colors: () !default;
 $theme-colors: map-merge((
index 5f5c0e7..2625a21 100644 (file)
@@ -3780,7 +3780,7 @@ pre {
   color: #495057;
   background-color: #fff;
   background-clip: padding-box;
-  border: 1px solid #ced4da;
+  border: 1px solid #8f959e;
   border-radius: 0.25rem;
   transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; }
   @media (max-width: 1200px) {
@@ -4988,7 +4988,7 @@ input[type="button"].btn-block {
   text-align: center;
   white-space: nowrap;
   background-color: #e9ecef;
-  border: 1px solid #ced4da;
+  border: 1px solid #8f959e;
   border-radius: 0.25rem; }
   @media (max-width: 1200px) {
     .input-group-text {
@@ -5105,7 +5105,7 @@ input[type="button"].btn-block {
     pointer-events: none;
     content: "";
     background-color: #fff;
-    border: #adb5bd solid 1px; }
+    border: #8f959e solid 1px; }
   .custom-control-label::after {
     position: absolute;
     top: 0.203125rem;
@@ -5156,7 +5156,7 @@ input[type="button"].btn-block {
     left: calc(-2.25rem + 2px);
     width: calc(1rem - 4px);
     height: calc(1rem - 4px);
-    background-color: #adb5bd;
+    background-color: #8f959e;
     border-radius: 0.5rem;
     transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; }
     @media (prefers-reduced-motion: reduce) {
@@ -5179,7 +5179,7 @@ input[type="button"].btn-block {
   color: #495057;
   vertical-align: middle;
   background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px;
-  border: 1px solid #ced4da;
+  border: 1px solid #8f959e;
   border-radius: 0.25rem;
   appearance: none; }
   @media (max-width: 1200px) {
@@ -5259,7 +5259,7 @@ input[type="button"].btn-block {
   line-height: 1.5;
   color: #495057;
   background-color: #fff;
-  border: 1px solid #ced4da;
+  border: 1px solid #8f959e;
   border-radius: 0.25rem; }
   .custom-file-label::after {
     position: absolute;
@@ -5368,15 +5368,15 @@ input[type="button"].btn-block {
     background-color: #dee2e6;
     border-radius: 1rem; }
   .custom-range:disabled::-webkit-slider-thumb {
-    background-color: #adb5bd; }
+    background-color: #8f959e; }
   .custom-range:disabled::-webkit-slider-runnable-track {
     cursor: default; }
   .custom-range:disabled::-moz-range-thumb {
-    background-color: #adb5bd; }
+    background-color: #8f959e; }
   .custom-range:disabled::-moz-range-track {
     cursor: default; }
   .custom-range:disabled::-ms-thumb {
-    background-color: #adb5bd; }
+    background-color: #8f959e; }
 
 .custom-control-label::before,
 .custom-file-label,
@@ -9764,7 +9764,7 @@ a.text-dark:hover, a.text-dark:focus {
     white-space: pre-wrap !important; }
   pre,
   blockquote {
-    border: 1px solid #adb5bd;
+    border: 1px solid #8f959e;
     page-break-inside: avoid; }
   thead {
     display: table-header-group; }
@@ -11769,12 +11769,6 @@ ul {
       width: 30px;
       font-size: 30px; }
 
-.open.atto_menu > .dropdown-menu {
-  display: block; }
-
-div.editor_atto_toolbar button .icon {
-  color: #495057; }
-
 .w-auto {
   width: auto; }
 
@@ -12502,12 +12496,12 @@ input[disabled] {
     box-sizing: content-box; }
   .admin_colourpicker .colourdialogue {
     float: left;
-    border: 1px solid #b8dce2; }
+    border: 1px solid #8f959e; }
   .admin_colourpicker .previewcolour {
-    border: 1px solid #b8dce2;
+    border: 1px solid #8f959e;
     margin-left: 301px; }
   .admin_colourpicker .currentcolour {
-    border: 1px solid #b8dce2;
+    border: 1px solid #8f959e;
     margin-left: 301px;
     border-top-width: 0; } }
 
@@ -14407,10 +14401,13 @@ body.drawer-ease {
   min-height: 520px; }
 
 .file-picker .fp-navbar {
-  border-bottom: 1px solid #e5e5e5;
   min-height: 40px;
   padding: 4px; }
 
+.fp-navbar {
+  border-color: #8f959e;
+  border-bottom: 0; }
+
 .file-picker .fp-content {
   border-top: 0;
   background: #fff;
@@ -14872,7 +14869,7 @@ a.ygtvspacer:hover {
 .filepicker-filelist,
 .filemanager-container {
   min-height: 140px;
-  border-top: 0; }
+  border: 1px solid #8f959e; }
 
 .filemanager .fp-content {
   overflow: auto;
@@ -16522,9 +16519,6 @@ body.path-question-type .mform fieldset.hidden {
   .mform > .form-group {
     margin-left: 1.5rem; } }
 
-.editor_atto_content.form-control {
-  width: 100%; }
-
 #adminsettings .form-control[size] {
   width: auto; }
 
@@ -16804,18 +16798,6 @@ textarea[data-auto-rows] {
   margin-left: 15px;
   max-width: 30rem; }
 
-/** Atto fields do not have form-control because that would break the layout of the editor.
-    So they need these extra styles to highlight the editor when there is a validation error. */
-/* stylelint-disable function-url-scheme-blacklist */
-/* stylelint-enable function-url-scheme-blacklist */
-.has-danger .editor_atto_content.form-control,
-.has-danger .editor_atto_content.form-control-danger {
-  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23d9534f' viewBox='-2 -2 7 7'%3E%3Cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3E%3Ccircle r='.5'/%3E%3Ccircle cx='3' r='.5'/%3E%3Ccircle cy='3' r='.5'/%3E%3Ccircle cx='3' cy='3' r='.5'/%3E%3C/svg%3E");
-  padding-right: 2.25rem;
-  background-repeat: no-repeat;
-  background-position: center right 1rem;
-  background-size: 1.5rem; }
-
 [data-filetypesbrowserbody] [aria-expanded="false"] > [role="group"],
 [data-filetypesbrowserbody] [aria-expanded="false"] [data-filetypesbrowserfeature="hideifcollapsed"],
 [data-filetypesbrowserbody] [aria-expanded="true"] [data-filetypesbrowserfeature="hideifexpanded"] {
@@ -17198,7 +17180,7 @@ select {
     height: calc(1.5em + 1rem + 2px);
     border-radius: 0;
     background-color: #fff;
-    border-color: #ced4da;
+    border-color: #8f959e;
     padding-left: calc(0.5rem + 8px);
     padding-top: 0.5rem;
     padding-bottom: 0.5rem;
@@ -17214,7 +17196,7 @@ select {
     height: calc(1.5em + 1rem + 2px);
     border-radius: 0;
     background-color: #fff;
-    border-color: #ced4da;
+    border-color: #8f959e;
     padding-left: calc(0.5rem + 8px);
     padding-top: 0.5rem;
     padding-bottom: 0.5rem;
@@ -19353,6 +19335,302 @@ input[type="month"].form-control {
   /* stylelint-disable-line declaration-no-important */
   -ms-user-select: none; }
 
+.editor_atto_content_wrap {
+  background-color: white;
+  color: #333; }
+
+.editor_atto_content {
+  padding: 4px;
+  resize: vertical;
+  overflow: auto; }
+
+.editor_atto_content_wrap,
+.editor_atto + textarea {
+  width: 100%;
+  padding: 0; }
+
+.editor_atto + textarea {
+  border-radius: 0;
+  resize: vertical;
+  margin-top: -1px; }
+
+div.editor_atto_toolbar {
+  display: block;
+  background: #f2f2f2;
+  min-height: 35px;
+  border: 1px solid #8f959e;
+  width: 100%;
+  padding: 0 0 9px 0; }
+
+div.editor_atto_toolbar button {
+  padding: 4px 9px;
+  background: none;
+  border: 0;
+  margin: 0;
+  border-radius: 0;
+  cursor: pointer; }
+
+div.editor_atto_toolbar button + button {
+  border-left: 1px solid #ccc; }
+
+div.editor_atto_toolbar button[disabled] {
+  opacity: .45;
+  background: none;
+  cursor: default; }
+
+.editor_atto_toolbar button:hover {
+  background-image: radial-gradient(ellipse at center, #fff 60%, #dfdfdf 100%);
+  background-color: #ebebeb; }
+
+.editor_atto_toolbar button:active,
+.editor_atto_toolbar button.highlight {
+  background-image: radial-gradient(ellipse at center, #fff 40%, #dfdfdf 100%);
+  background-color: #dfdfdf; }
+
+/* Make firefox button sizes match other browsers */
+div.editor_atto_toolbar button::-moz-focus-inner {
+  border: 0;
+  padding: 0; }
+
+div.editor_atto_toolbar button .icon {
+  padding: 0;
+  margin: 2px 0; }
+
+div.editor_atto_toolbar div.atto_group {
+  display: inline-block;
+  border: 1px solid #ccc;
+  border-bottom: 1px solid #b3b3b3;
+  border-radius: 4px;
+  margin: 9px 0 0 9px;
+  background: #fff; }
+
+.editor_atto_content img {
+  resize: both;
+  overflow: auto; }
+
+.atto_hasmenu {
+  /* IE8 places the images on top of each other if that is not set. */
+  white-space: nowrap; }
+
+.atto_menuentry .icon {
+  width: 16px;
+  height: 16px; }
+
+.atto_menuentry {
+  clear: left; }
+
+.atto_menuentry h1,
+.atto_menuentry h2,
+.atto_menuentry p {
+  margin: 4px; }
+
+/*.atto_form label.sameline {
+    display: inline-block;
+    min-width: 10em;
+}*/
+.atto_form textarea.fullwidth,
+.atto_form input.fullwidth {
+  width: 100%; }
+
+.atto_form {
+  padding: 0.5rem; }
+
+/*.atto_form label {
+    display: block;
+    margin: 0 0 5px 0;
+}*/
+.atto_control {
+  position: absolute;
+  right: -6px;
+  bottom: -6px;
+  display: none;
+  cursor: pointer; }
+
+.atto_control .icon {
+  background-color: white; }
+
+div.editor_atto_content:focus .atto_control,
+div.editor_atto_content:hover .atto_control {
+  display: block; }
+
+.editor_atto_menu.yui3-menu-hidden {
+  display: none; }
+
+/* Get broken images back in firefox */
+.editor_atto_content img:-moz-broken {
+  -moz-force-broken-image-icon: 1;
+  min-width: 24px;
+  min-height: 24px; }
+
+/* Atto menu styling */
+.moodle-dialogue-base .editor_atto_menu .moodle-dialogue-content .moodle-dialogue-bd {
+  padding: 0;
+  z-index: 1000; }
+
+.editor_atto_menu .dropdown-menu > li > a {
+  margin: 3px 14px; }
+
+.editor_atto_menu .open ul.dropdown-menu {
+  padding-top: 5px;
+  padding-bottom: 5px; }
+
+.editor_atto_wrap {
+  position: relative; }
+
+/*rtl:ignore*/
+.editor_atto_wrap textarea {
+  direction: ltr; }
+
+.editor_atto_notification .atto_info,
+.editor_atto_notification .atto_warning {
+  display: inline-block;
+  background-color: #f2f2f2;
+  padding: 0.5em;
+  padding-left: 1em;
+  padding-right: 1em;
+  border-bottom-left-radius: 1em;
+  border-bottom-right-radius: 1em; }
+
+.editor_atto_notification .atto_info {
+  background-color: #f2f2f2; }
+
+.editor_atto_notification .atto_warning {
+  background-color: #ffd700; }
+
+.editor_atto_toolbar,
+.editor_atto_content_wrap,
+.editor_atto + textarea {
+  box-sizing: border-box; }
+
+.editor_atto_content.form-control {
+  width: 100%;
+  border-top: 0; }
+
+/** Atto fields do not have form-control because that would break the layout of the editor.
+    So they need these extra styles to highlight the editor when there is a validation error. */
+.has-danger .editor_atto_content.form-control .invalid-feedback,
+.has-danger .editor_atto_content.form-control-danger .invalid-feedback {
+  display: none;
+  width: 100%;
+  margin-top: 0.25rem;
+  font-size: 80%;
+  color: #d43f3a; }
+
+.has-danger .editor_atto_content.form-control .invalid-tooltip,
+.has-danger .editor_atto_content.form-control-danger .invalid-tooltip {
+  position: absolute;
+  top: 100%;
+  z-index: 5;
+  display: none;
+  max-width: 100%;
+  padding: 0.25rem 0.5rem;
+  margin-top: .1rem;
+  font-size: 0.8203125rem;
+  line-height: 1.5;
+  color: #fff;
+  background-color: rgba(212, 63, 58, 0.9);
+  border-radius: 0.25rem; }
+
+.was-validated .has-danger .editor_atto_content.form-control:invalid ~ .invalid-feedback,
+.was-validated .has-danger .editor_atto_content.form-control:invalid ~ .invalid-tooltip, .has-danger .editor_atto_content.form-control.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control.is-invalid ~ .invalid-tooltip, .was-validated
+.has-danger .editor_atto_content.form-control-danger:invalid ~ .invalid-feedback,
+.was-validated
+.has-danger .editor_atto_content.form-control-danger:invalid ~ .invalid-tooltip,
+.has-danger .editor_atto_content.form-control-danger.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control-danger.is-invalid ~ .invalid-tooltip {
+  display: block; }
+
+.was-validated .has-danger .editor_atto_content.form-control .form-control:invalid, .has-danger .editor_atto_content.form-control .form-control.is-invalid, .was-validated
+.has-danger .editor_atto_content.form-control-danger .form-control:invalid,
+.has-danger .editor_atto_content.form-control-danger .form-control.is-invalid {
+  border-color: #d43f3a;
+  padding-right: calc(1.5em + 0.75rem);
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23d43f3a' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d43f3a' stroke='none'/%3e%3c/svg%3e");
+  background-repeat: no-repeat;
+  background-position: right calc(0.375em + 0.1875rem) center;
+  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  .was-validated .has-danger .editor_atto_content.form-control .form-control:invalid:focus, .has-danger .editor_atto_content.form-control .form-control.is-invalid:focus, .was-validated
+  .has-danger .editor_atto_content.form-control-danger .form-control:invalid:focus,
+  .has-danger .editor_atto_content.form-control-danger .form-control.is-invalid:focus {
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.was-validated .has-danger .editor_atto_content.form-control textarea.form-control:invalid, .has-danger .editor_atto_content.form-control textarea.form-control.is-invalid, .was-validated
+.has-danger .editor_atto_content.form-control-danger textarea.form-control:invalid,
+.has-danger .editor_atto_content.form-control-danger textarea.form-control.is-invalid {
+  padding-right: calc(1.5em + 0.75rem);
+  background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-select:invalid, .has-danger .editor_atto_content.form-control .custom-select.is-invalid, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-select:invalid,
+.has-danger .editor_atto_content.form-control-danger .custom-select.is-invalid {
+  border-color: #d43f3a;
+  padding-right: calc(0.75em + 2.3125rem);
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23d43f3a' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23d43f3a' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  .was-validated .has-danger .editor_atto_content.form-control .custom-select:invalid:focus, .has-danger .editor_atto_content.form-control .custom-select.is-invalid:focus, .was-validated
+  .has-danger .editor_atto_content.form-control-danger .custom-select:invalid:focus,
+  .has-danger .editor_atto_content.form-control-danger .custom-select.is-invalid:focus {
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.was-validated .has-danger .editor_atto_content.form-control .form-check-input:invalid ~ .form-check-label, .has-danger .editor_atto_content.form-control .form-check-input.is-invalid ~ .form-check-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .form-check-input:invalid ~ .form-check-label,
+.has-danger .editor_atto_content.form-control-danger .form-check-input.is-invalid ~ .form-check-label {
+  color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .form-check-input:invalid ~ .invalid-feedback,
+.was-validated .has-danger .editor_atto_content.form-control .form-check-input:invalid ~ .invalid-tooltip, .has-danger .editor_atto_content.form-control .form-check-input.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control .form-check-input.is-invalid ~ .invalid-tooltip, .was-validated
+.has-danger .editor_atto_content.form-control-danger .form-check-input:invalid ~ .invalid-feedback,
+.was-validated
+.has-danger .editor_atto_content.form-control-danger .form-check-input:invalid ~ .invalid-tooltip,
+.has-danger .editor_atto_content.form-control-danger .form-check-input.is-invalid ~ .invalid-feedback,
+.has-danger .editor_atto_content.form-control-danger .form-check-input.is-invalid ~ .invalid-tooltip {
+  display: block; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid ~ .custom-control-label, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid ~ .custom-control-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid ~ .custom-control-label,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid ~ .custom-control-label {
+  color: #d43f3a; }
+  .was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid ~ .custom-control-label::before, .was-validated
+  .has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid ~ .custom-control-label::before,
+  .has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid ~ .custom-control-label::before {
+    border-color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid:checked ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid:checked ~ .custom-control-label::before, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid:checked ~ .custom-control-label::before,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid:checked ~ .custom-control-label::before {
+  border-color: #dd6864;
+  background-color: #dd6864; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid:focus ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid:focus ~ .custom-control-label::before, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid:focus ~ .custom-control-label::before,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid:focus ~ .custom-control-label::before {
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .has-danger .editor_atto_content.form-control .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before,
+.has-danger .editor_atto_content.form-control-danger .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-file-input:invalid ~ .custom-file-label, .has-danger .editor_atto_content.form-control .custom-file-input.is-invalid ~ .custom-file-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-file-input:invalid ~ .custom-file-label,
+.has-danger .editor_atto_content.form-control-danger .custom-file-input.is-invalid ~ .custom-file-label {
+  border-color: #d43f3a; }
+
+.was-validated .has-danger .editor_atto_content.form-control .custom-file-input:invalid:focus ~ .custom-file-label, .has-danger .editor_atto_content.form-control .custom-file-input.is-invalid:focus ~ .custom-file-label, .was-validated
+.has-danger .editor_atto_content.form-control-danger .custom-file-input:invalid:focus ~ .custom-file-label,
+.has-danger .editor_atto_content.form-control-danger .custom-file-input.is-invalid:focus ~ .custom-file-label {
+  border-color: #d43f3a;
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
+
+.open.atto_menu > .dropdown-menu {
+  display: block; }
+
+div.editor_atto_toolbar button .icon {
+  color: #495057; }
+
 body {
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale; }
index 69dc1a4..d0decbb 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2020091000.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2020091000.02;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 $release  = '3.10dev (Build: 20200910)';// Human-friendly version name