Merge branch 'MDL-68165' of https://github.com/stronk7/moodle
authorJake Dallimore <jake@moodle.com>
Thu, 2 Apr 2020 00:42:45 +0000 (08:42 +0800)
committerJake Dallimore <jake@moodle.com>
Thu, 2 Apr 2020 00:42:45 +0000 (08:42 +0800)
75 files changed:
admin/index.php
course/classes/local/repository/content_item_readonly_repository.php
course/format/singleactivity/lib.php
course/format/singleactivity/tests/behat/edit_format_course.feature [new file with mode: 0644]
lang/en/moodle.php
lib/adminlib.php
lib/classes/event/database_text_field_content_replaced.php [new file with mode: 0644]
lib/db/services.php
lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button-debug.js
lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button-min.js
lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button.js
lib/editor/atto/plugins/align/yui/src/button/js/button.js
lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button-debug.js
lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button-min.js
lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button.js
lib/editor/atto/plugins/rtl/yui/src/button/js/button.js
lib/editor/atto/tests/behat/behat_editor_atto.php
lib/editor/atto/tests/behat/direction.feature [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/autosave.js
lib/editor/atto/yui/src/editor/js/clean.js
lib/editor/atto/yui/src/editor/js/editor.js
lib/editor/atto/yui/src/editor/js/styling.js
lib/editor/atto/yui/src/editor/js/textarea.js
lib/environmentlib.php
lib/form/templates/element-group.mustache
lib/minify/matthiasmullie-minify/src/CSS.php
lib/minify/readme_moodle.txt
lib/moodlelib.php
lib/navigationlib.php
lib/questionlib.php
lib/table/amd/build/dynamic.min.js [new file with mode: 0644]
lib/table/amd/build/dynamic.min.js.map [new file with mode: 0644]
lib/table/amd/build/local/dynamic/repository.min.js [new file with mode: 0644]
lib/table/amd/build/local/dynamic/repository.min.js.map [new file with mode: 0644]
lib/table/amd/build/local/dynamic/selectors.min.js [new file with mode: 0644]
lib/table/amd/build/local/dynamic/selectors.min.js.map [new file with mode: 0644]
lib/table/amd/src/dynamic.js [new file with mode: 0644]
lib/table/amd/src/local/dynamic/repository.js [new file with mode: 0644]
lib/table/amd/src/local/dynamic/selectors.js [new file with mode: 0644]
lib/table/classes/dynamic.php
lib/table/classes/external/dynamic/fetch.php [new file with mode: 0644]
lib/table/classes/local/filter/filter.php
lib/table/classes/local/filter/filterset.php
lib/table/tests/external/dynamic/fetch_test.php [new file with mode: 0644]
lib/tablelib.php
lib/tests/authlib_test.php
lib/tests/events_test.php
mod/folder/renderer.php
mod/lesson/import.php
mod/lti/edit_form.php
mod/lti/lang/en/deprecated.txt
mod/lti/lang/en/lti.php
mod/lti/locallib.php
mod/lti/service/memberships/classes/local/resources/linkmemberships.php
mod/lti/service/memberships/classes/local/service/memberships.php
mod/lti/tests/locallib_test.php
privacy/export_files/general.js
question/import.php
question/tests/behat/duplicate_questions.feature [new file with mode: 0644]
question/type/essay/renderer.php
theme/boost/scss/moodle/core.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/boost/templates/flat_navigation.mustache
theme/classic/scss/preset/default.scss
theme/classic/style/moodle.css
user/classes/participants_table.php
user/index.php
user/lib.php
user/tests/behat/full_name_display.feature [new file with mode: 0644]
user/tests/behat/view_participants_groups.feature [new file with mode: 0644]
version.php

index c95c73c..c391089 100644 (file)
@@ -226,7 +226,7 @@ if (!core_tables_exist()) {
     }
     if (empty($confirmrelease)) {
         require_once($CFG->libdir.'/environmentlib.php');
-        list($envstatus, $environment_results) = check_moodle_environment(normalize_version($release), ENV_SELECT_RELEASE);
+        list($envstatus, $environmentresults) = check_moodle_environment(normalize_version($release), ENV_SELECT_RELEASE);
         $strcurrentrelease = get_string('currentrelease');
 
         $PAGE->navbar->add($strcurrentrelease);
@@ -235,7 +235,7 @@ if (!core_tables_exist()) {
         $PAGE->set_cacheable(false);
 
         $output = $PAGE->get_renderer('core', 'admin');
-        echo $output->install_environment_page($maturity, $envstatus, $environment_results, $release);
+        echo $output->install_environment_page($maturity, $envstatus, $environmentresults, $release);
         die();
     }
 
@@ -358,9 +358,9 @@ if (!$cache and $version > $CFG->version) {  // upgrade
         echo $output->upgrade_confirm_page($a->newversion, $maturity, $testsite);
         die();
 
-    } else if (empty($confirmrelease)){
+    } else if (empty($confirmrelease)) {
         require_once($CFG->libdir.'/environmentlib.php');
-        list($envstatus, $environment_results) = check_moodle_environment($release, ENV_SELECT_RELEASE);
+        list($envstatus, $environmentresults) = check_moodle_environment($release, ENV_SELECT_RELEASE);
         $strcurrentrelease = get_string('currentrelease');
 
         $PAGE->navbar->add($strcurrentrelease);
@@ -368,7 +368,7 @@ if (!$cache and $version > $CFG->version) {  // upgrade
         $PAGE->set_heading($strcurrentrelease);
         $PAGE->set_cacheable(false);
 
-        echo $output->upgrade_environment_page($release, $envstatus, $environment_results);
+        echo $output->upgrade_environment_page($release, $envstatus, $environmentresults);
         die();
 
     } else if (empty($confirmplugins)) {
@@ -533,7 +533,10 @@ if (!$cache and $branch <> $CFG->branch) {  // Update the branch
 
 if (!$cache and moodle_needs_upgrading()) {
 
-    $PAGE->set_url(new moodle_url($PAGE->url, array('confirmplugincheck' => $confirmplugins)));
+    $PAGE->set_url(new moodle_url($PAGE->url, array(
+        'confirmrelease' => $confirmrelease,
+        'confirmplugincheck' => $confirmplugins,
+    )));
 
     check_upgrade_key($upgradekeyhash);
 
@@ -543,7 +546,21 @@ if (!$cache and moodle_needs_upgrading()) {
         $pluginman = core_plugin_manager::instance();
         $output = $PAGE->get_renderer('core', 'admin');
 
-        if (!$confirmplugins) {
+        if (empty($confirmrelease)) {
+            require_once($CFG->libdir . '/environmentlib.php');
+
+            list($envstatus, $environmentresults) = check_moodle_environment($release, ENV_SELECT_RELEASE);
+            $strcurrentrelease = get_string('currentrelease');
+
+            $PAGE->navbar->add($strcurrentrelease);
+            $PAGE->set_title($strcurrentrelease);
+            $PAGE->set_heading($strcurrentrelease);
+            $PAGE->set_cacheable(false);
+
+            echo $output->upgrade_environment_page($release, $envstatus, $environmentresults);
+            die();
+
+        } else if (!$confirmplugins) {
             $strplugincheck = get_string('plugincheck');
 
             $PAGE->navbar->add($strplugincheck);
@@ -802,7 +819,7 @@ $SESSION->admin_critical_warning = ($insecuredataroot==INSECURE_DATAROOT_ERROR);
 $adminroot = admin_get_root();
 
 // Check if there are any new admin settings which have still yet to be set
-if (any_new_admin_settings($adminroot)){
+if (any_new_admin_settings($adminroot)) {
     redirect('upgradesettings.php');
 }
 
index 8152c6d..3704b97 100644 (file)
@@ -185,7 +185,7 @@ class content_item_readonly_repository implements content_item_readonly_reposito
      * @return array the array of content items.
      */
     public function find_all(): array {
-        global $OUTPUT, $DB;
+        global $OUTPUT, $DB, $CFG;
 
         // Get all modules so we know which plugins are enabled and able to add content.
         // Only module plugins may add content items.
@@ -194,6 +194,10 @@ class content_item_readonly_repository implements content_item_readonly_reposito
 
         // Now, generate the content_items.
         foreach ($modules as $modid => $mod) {
+            // Exclude modules if the code doesn't exist.
+            if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) {
+                continue;
+            }
             // Create the content item for the module itself.
             // If the module chooses to implement the hook, this may be thrown away.
             $help = $this->get_core_module_help_string($mod->name);
@@ -241,7 +245,7 @@ class content_item_readonly_repository implements content_item_readonly_reposito
      * @return array the array of content_item objects
      */
     public function find_all_for_course(\stdClass $course, \stdClass $user): array {
-        global $OUTPUT, $DB;
+        global $OUTPUT, $DB, $CFG;
 
         // Get all modules so we know which plugins are enabled and able to add content.
         // Only module plugins may add content items.
@@ -253,7 +257,10 @@ class content_item_readonly_repository implements content_item_readonly_reposito
 
         // Now, generate the content_items.
         foreach ($modules as $modid => $mod) {
-
+            // Exclude modules if the code doesn't exist.
+            if (!file_exists("$CFG->dirroot/mod/$mod->name/lib.php")) {
+                continue;
+            }
             // Create the content item for the module itself.
             // If the module chooses to implement the hook, this may be thrown away.
             $help = $this->get_core_module_help_string($mod->name);
index d3af501..88aba73 100644 (file)
@@ -154,9 +154,9 @@ class format_singleactivity extends format_base {
 
         if ($fetchtypes) {
             $availabletypes = $this->get_supported_activities();
-            if ($this->course) {
+            if ($this->courseid) {
                 // The course exists. Test against the course.
-                $testcontext = context_course::instance($this->course->id);
+                $testcontext = context_course::instance($this->courseid);
             } else if ($this->categoryid) {
                 // The course does not exist yet, but we have a category ID that we can test against.
                 $testcontext = context_coursecat::instance($this->categoryid);
diff --git a/course/format/singleactivity/tests/behat/edit_format_course.feature b/course/format/singleactivity/tests/behat/edit_format_course.feature
new file mode 100644 (file)
index 0000000..3372196
--- /dev/null
@@ -0,0 +1,27 @@
+@format @format_singleactivity
+Feature: Edit format course to Single Activity format
+  In order to set the format course to single activity course
+  As a teacher
+  I need to edit the course settings and see the dropdown type activity
+
+  Scenario: Edit a format course as a teacher
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | summary | format |
+      | Course 1 | C1 | <p>Course summary</p> | topics |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    When I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | Course full name  | My first course |
+      | Course short name | myfirstcourse |
+      | Format | Single activity format |
+    And I press "Update format"
+    Then I should see "Forum" in the "Type of activity" "field"
+    And I press "Save and display"
+    And I should see "Adding a new Forum"
\ No newline at end of file
index bec67cc..10fa5d1 100644 (file)
@@ -790,6 +790,7 @@ $string['eventcourseviewed'] = 'Course viewed';
 $string['eventdashboardreset'] = 'Dashboard reset';
 $string['eventdashboardsreset'] = 'Dashboards reset';
 $string['eventdashboardviewed'] = 'Dashboard viewed';
+$string['eventdatabasetextfieldcontentreplaced'] = 'Database global search and replace';
 $string['eventemailfailed'] = 'Email failed to send';
 $string['eventname'] = 'Event name';
 $string['eventrecentactivityviewed'] = 'Recent activity viewed';
index 3f0dc85..1713735 100644 (file)
@@ -8873,6 +8873,17 @@ function db_replace($search, $replace) {
         echo $OUTPUT->notification("...finished", 'notifysuccess');
     }
 
+    // Trigger an event.
+    $eventargs = [
+        'context' => context_system::instance(),
+        'other' => [
+            'search' => $search,
+            'replace' => $replace
+        ]
+    ];
+    $event = \core\event\database_text_field_content_replaced::create($eventargs);
+    $event->trigger();
+
     purge_all_caches();
 
     return true;
diff --git a/lib/classes/event/database_text_field_content_replaced.php b/lib/classes/event/database_text_field_content_replaced.php
new file mode 100644 (file)
index 0000000..ed66f1a
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The database text field content replaced event.
+ *
+ * @package   core
+ * @copyright 2020 Mark Nelson <mdjnelson@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The database text field content replaced event class.
+ *
+ * @property-read array $other {
+ *      Extra information about the event.
+ *
+ *      - string search: The value being searched for.
+ *      - string replace: The replacement value that replaces found search value.
+ * }
+ *
+ * @package   core
+ * @copyright 2020 Mark Nelson <mdjnelson@gmail.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class database_text_field_content_replaced extends \core\event\base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventdatabasetextfieldcontentreplaced');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' replaced the string '" . $this->other['search'] . "' " .
+            "with the string '" . $this->other['replace'] . "' in the database.";
+    }
+
+    /**
+     * Custom validation.
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['search'])) {
+            throw new \coding_exception('The \'search\' value must be set in other.');
+        }
+        if (!isset($this->other['replace'])) {
+            throw new \coding_exception('The \'replace\' value must be set in other.');
+        }
+    }
+}
index 9442f9c..ed00b5f 100644 (file)
@@ -2744,6 +2744,14 @@ $functions = array(
         'capabilities'  => '',
         'services'      => [MOODLE_OFFICIAL_MOBILE_SERVICE],
     ],
+    'core_table_dynamic_fetch' => [
+        'classname' => 'core_table\external\dynamic\fetch',
+        'methodname' => 'execute',
+        'description' => 'Fetch a dynamic table view raw html',
+        'type' => 'read',
+        'ajax' => true,
+        'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
+    ],
 );
 
 $services = array(
index 60f5e4f..38645d7 100644 (file)
Binary files a/lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button-debug.js and b/lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button-debug.js differ
index 204f472..15107ef 100644 (file)
Binary files a/lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button-min.js and b/lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button-min.js differ
index 60f5e4f..38645d7 100644 (file)
Binary files a/lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button.js and b/lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button.js differ
index a1d62df..019699a 100644 (file)
@@ -81,6 +81,15 @@ Y.namespace('M.atto_align').Button = Y.Base.create('button', Y.M.editor_atto.Edi
 
         document.execCommand(justification, false, null);
 
+        // To clean up IE's mess.
+        this.editor.all('*[align]').each(function(node) {
+            var align = node.get('align');
+            if (align) {
+                node.setStyle('text-align', align);
+                node.removeAttribute('align');
+            }
+        }, this);
+
         // Re-disable the CSS styling after making the change.
         host.disableCssStyling();
 
index 10f02f5..1a7e8f1 100644 (file)
Binary files a/lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button-debug.js and b/lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button-debug.js differ
index 687aea1..b74d110 100644 (file)
Binary files a/lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button-min.js and b/lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button-min.js differ
index 10f02f5..1a7e8f1 100644 (file)
Binary files a/lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button.js and b/lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button.js differ
index 724c21d..7cca17c 100644 (file)
@@ -70,6 +70,10 @@ Y.namespace('M.atto_rtl').Button = Y.Base.create('button', Y.M.editor_atto.Edito
             newDirection = {
                 rtl: 'ltr',
                 ltr: 'rtl'
+            },
+            directionAlignment = {
+                rtl: 'right',
+                ltr: 'left'
             };
         if (selection) {
             // Format the selection to be sure it has a tag parent (not the contenteditable).
@@ -79,8 +83,10 @@ Y.namespace('M.atto_rtl').Button = Y.Base.create('button', Y.M.editor_atto.Edito
             var currentDirection = parentDOMNode.getAttribute('dir');
             if (currentDirection === direction) {
                 parentDOMNode.setAttribute("dir", newDirection[direction]);
+                parentDOMNode.style.textAlign = directionAlignment[newDirection[direction]];
             } else {
                 parentDOMNode.setAttribute("dir", direction);
+                parentDOMNode.style.textAlign = directionAlignment[direction];
             }
 
             // Change selection from the containing paragraph to the original one.
index 91723d8..94c9d5a 100644 (file)
@@ -25,6 +25,8 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
+require_once(__DIR__ . '/../../../../behat/behat_base.php');
+
 /**
  * Steps definitions to deal with the atto text editor
  *
diff --git a/lib/editor/atto/tests/behat/direction.feature b/lib/editor/atto/tests/behat/direction.feature
new file mode 100644 (file)
index 0000000..6009727
--- /dev/null
@@ -0,0 +1,49 @@
+@editor @editor_atto @atto
+Feature: Add text direction and alignment
+  In order to generate a content that can be displayed in the proper direction to everyone
+  As a user
+  I should see the Atto editor with explicit direction and alignment being set
+
+  Background:
+    Given the following "user preferences" exist:
+      | user  | preference  | value |
+      | admin | htmleditor  | atto  |
+    And I log in as "admin"
+    And I navigate to "Plugins > Text editors > Atto HTML editor > Atto toolbar settings" in site administration
+    And I set the field "Toolbar config" to multiline:
+    """
+    collapse = collapse
+    style1 = title, bold, italic
+    list = unorderedlist, orderedlist
+    links = link
+    files = image, media, recordrtc, managefiles, h5p
+    style2 = underline, strike, subscript, superscript
+    align = align,rtl
+    indent = indent
+    insert = equation, charmap, table, clear
+    undo = undo
+    accessibility = accessibilitychecker, accessibilityhelper
+    other = html
+    """
+    And I press "Save changes"
+    And I log out
+
+  @javascript
+  Scenario Outline: Atto should apply user's direction and alignment by default
+    Given the following "courses" exist:
+      | fullname  | shortname | summary | summaryformat |
+      | Course 1  | C1        |         | 1             |
+    And the following "language customisations" exist:
+      | component   | stringid    | value         |
+      | <component> | <stringid>  | <localstring> |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    When I navigate to "Edit settings" in current page administration
+    And I press "Show more buttons"
+    And I press "HTML"
+    Then I should see "<partialtext>"
+
+    Examples:
+      | component       | stringid      | localstring | partialtext                               |
+      | core_langconfig | thisdirection | ltr         | dir=\"ltr\" style=\"text-align: left;\"   |
+      | core_langconfig | thisdirection | rtl         | dir=\"rtl\" style=\"text-align: right;\"  |
index f24e521..46360b7 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index 7b7b5be..7c68071 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index c373535..eb50741 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
index aed2fb8..60b95cb 100644 (file)
@@ -143,14 +143,24 @@ EditorAutosave.prototype = {
                 }
 
                 // Revert untouched editor contents to an empty string.
-                // Check for FF and Chrome.
-                if (response.result === '<p></p>' || response.result === '<p><br></p>' ||
-                    response.result === '<br>') {
-                    response.result = '';
-                }
-
-                // Check for IE 9 and 10.
-                if (response.result === '<p>&nbsp;</p>' || response.result === '<p><br>&nbsp;</p>') {
+                var emptyContents = [
+                    // For FF and Chrome.
+                    '<p></p>',
+                    '<p><br></p>',
+                    '<br>',
+                    '<p dir="rtl" style="text-align: right;"></p>',
+                    '<p dir="rtl" style="text-align: right;"><br></p>',
+                    '<p dir="ltr" style="text-align: left;"></p>',
+                    '<p dir="ltr" style="text-align: left;"><br></p>',
+                    // For IE 9 and 10.
+                    '<p>&nbsp;</p>',
+                    '<p><br>&nbsp;</p>',
+                    '<p dir="rtl" style="text-align: right;">&nbsp;</p>',
+                    '<p dir="rtl" style="text-align: right;"><br>&nbsp;</p>',
+                    '<p dir="ltr" style="text-align: left;">&nbsp;</p>',
+                    '<p dir="ltr" style="text-align: left;"><br>&nbsp;</p>'
+                ];
+                if (emptyContents.includes(response.result)) {
                     response.result = '';
                 }
 
index cfb35e7..a3310f0 100644 (file)
@@ -55,12 +55,29 @@ EditorClean.prototype = {
         html = editorClone.get('innerHTML');
 
         // Revert untouched editor contents to an empty string.
-        if (html === '<p></p>' || html === '<p><br></p>') {
+        var emptyContents = [
+            // For FF and Chrome.
+            '<p></p>',
+            '<p><br></p>',
+            '<br>',
+            '<p dir="rtl" style="text-align: right;"></p>',
+            '<p dir="rtl" style="text-align: right;"><br></p>',
+            '<p dir="ltr" style="text-align: left;"></p>',
+            '<p dir="ltr" style="text-align: left;"><br></p>',
+            // For IE 9 and 10.
+            '<p>&nbsp;</p>',
+            '<p><br>&nbsp;</p>',
+            '<p dir="rtl" style="text-align: right;">&nbsp;</p>',
+            '<p dir="rtl" style="text-align: right;"><br>&nbsp;</p>',
+            '<p dir="ltr" style="text-align: left;">&nbsp;</p>',
+            '<p dir="ltr" style="text-align: left;"><br>&nbsp;</p>'
+        ];
+        if (emptyContents.includes(html)) {
             return '';
         }
 
         // Remove any and all nasties from source.
-       return this._cleanHTML(html);
+        return this._cleanHTML(html);
     },
 
     /**
index b846bcd..d085283 100644 (file)
@@ -148,6 +148,14 @@ Y.extend(Editor, Y.Base, {
      */
     plugins: null,
 
+    /**
+     * An indicator of the current input direction.
+     *
+     * @property coreDirection
+     * @type string
+     */
+    coreDirection: null,
+
     /**
      * Event Handles to clear on editor destruction.
      *
@@ -194,6 +202,9 @@ Y.extend(Editor, Y.Base, {
             this.editor.setAttribute('aria-labelledby', this.textareaLabel.get("id"));
         }
 
+        // Set diretcion according to current page language.
+        this.coreDirection = Y.one('body').hasClass('dir-rtl') ? 'rtl' : 'ltr';
+
         // Add everything to the wrapper.
         this.setupToolbar();
 
index dcf6fb8..66c91a9 100644 (file)
@@ -175,8 +175,14 @@ EditorStyling.prototype = {
 
         // No valid block element - make one.
         if (!nearestblock) {
+            var alignment;
+            if (this.coreDirection === 'rtl') {
+                alignment = 'style="text-align: right;"';
+            } else {
+                alignment = 'style="text-align: left;"';
+            }
             // There is no block node in the content, wrap the content in a p and use that.
-            newcontent = Y.Node.create('<p></p>');
+            newcontent = Y.Node.create('<p dir="' + this.coreDirection + '" ' + alignment + '></p>');
             boundary.get('childNodes').each(function(child) {
                 newcontent.append(child.remove());
             });
index 1e24287..0670ad9 100644 (file)
@@ -45,10 +45,16 @@ EditorTextArea.prototype = {
      * @private
      */
     _getEmptyContent: function() {
+        var alignment;
+        if (this.coreDirection === 'rtl') {
+            alignment = 'style="text-align: right;"';
+        } else {
+            alignment = 'style="text-align: left;"';
+        }
         if (Y.UA.ie && Y.UA.ie < 10) {
-            return '<p></p>';
+            return '<p dir="' + this.coreDirection + '" ' + alignment + '></p>';
         } else {
-            return '<p><br></p>';
+            return '<p dir="' + this.coreDirection + '" ' + alignment + '><br></p>';
         }
     },
 
index 3daebae..64a3a4f 100644 (file)
@@ -140,6 +140,7 @@ function environment_get_errors($environment_results) {
         $type = $environment_result->getPart();
         $info = $environment_result->getInfo();
         $status = $environment_result->getStatus();
+        $plugin = $environment_result->getPluginName();
         $error_code = $environment_result->getErrorCode();
 
         $a = new stdClass();
@@ -209,7 +210,13 @@ function environment_get_errors($environment_results) {
         // Append the restrict if there is some
         $feedbacktext .= $environment_result->strToReport($environment_result->getRestrictStr(), 'error');
 
-        $report .= html_to_text($feedbacktext);
+        if ($plugin === '') {
+            $report = '[' . get_string('coresystem') . '] ' . $report;
+        } else {
+            $report = '[' . $plugin . '] ' . $report;
+        }
+
+        $report .= ' - ' . html_to_text($feedbacktext);
 
         if ($environment_result->getPart() == 'custom_check'){
             $errors[] = array($info, $report);
index 7a53d37..4664a09 100644 (file)
@@ -7,7 +7,7 @@
         {{/element.hiddenlabel}}
     {{/label}}
     {{$element}}
-        <fieldset class="m-0 p-0 border-0">
+        <fieldset class="w-100 m-0 p-0 border-0">
             <legend class="sr-only">{{label}}</legend>
             <div class="d-flex flex-wrap">
             {{#element.elements}}
index a9a70a0..a66dfe0 100644 (file)
@@ -636,7 +636,35 @@ class CSS extends Minify
 
             return $placeholder;
         };
+        // Moodle-specific change MDL-68191 starts.
+        /* This was the old code:
         $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
+        */
+        // This is the new, more accurate and faster regex.
+        $this->registerPattern('/
+            # optional newline
+            \n?
+
+            # start comment
+            \/\*
+
+            # comment content
+            (?:
+                # either starts with an !
+                !
+            |
+                # or, after some number of characters which do not end the comment
+                (?:(?!\*\/).)*?
+
+                # there is either a @license or @preserve tag
+                @(?:license|preserve)
+            )
+
+            # then match to the end of the comment
+            .*?\*\/\n?
+
+            /ixs', $callback);
+        // Moodle-specific change MDL-68191.
 
         $this->registerPattern('/\/\*.*?\*\//s', '');
     }
index 9593ad0..d03fa3d 100644 (file)
@@ -14,3 +14,10 @@ Local changes applied:
 MDL-67115: php 74 compliance - implode() params order. Note this has been fixed upstream
   by https://github.com/matthiasmullie/minify/pull/300 so, whenever this library is updated
   check if the fix is included and remove this note.
+
+MDL-68191: https://github.com/matthiasmullie/minify/issues/317 is a bug that stops
+  large sections of the CSS from being minimised, and also is a huge performance drain.
+  We have applied the fix sent upstream because the performance win is so big.
+  (E.g. one case I measured, with the bug was 40 seconds to minify CSS, with the fix was
+  a few seconds. This is one of the reasons Behat runs in the browser are so slow.)
+  Whenever this library is updated check if the fix is included and remove this note.
index 5441dbd..c068e0e 100644 (file)
@@ -3520,11 +3520,12 @@ function ismoving($courseid) {
  * Returns a persons full name
  *
  * Given an object containing all of the users name values, this function returns a string with the full name of the person.
- * The result may depend on system settings or language.  'override' will force both names to be used even if system settings
- * specify one.
+ * The result may depend on system settings or language. 'override' will force the alternativefullnameformat to be used. In
+ * English, fullname as well as alternativefullnameformat is set to 'firstname lastname' by default. But you could have
+ * fullname set to 'firstname lastname' and alternativefullnameformat set to 'firstname middlename alternatename lastname'.
  *
  * @param stdClass $user A {@link $USER} object to get full name of.
- * @param bool $override If true then the name will be firstname followed by lastname rather than adhering to fullnamedisplay.
+ * @param bool $override If true then the alternativefullnameformat format rather than fullnamedisplay format will be used.
  * @return string
  */
 function fullname($user, $override=false) {
index 6bb6b81..b6b2819 100644 (file)
@@ -544,7 +544,11 @@ class navigation_node implements renderable {
         if (in_array($class, $this->classes)) {
             $key = array_search($class,$this->classes);
             if ($key!==false) {
+                // Remove the class' array element.
                 unset($this->classes[$key]);
+                // Reindex the array to avoid failures when the classes array is iterated later in mustache templates.
+                $this->classes = array_values($this->classes);
+
                 return true;
             }
         }
index 4b83105..8b95027 100644 (file)
@@ -2390,11 +2390,11 @@ function question_module_uses_questions($modname) {
  * So idnum -> null (no digits at the end) idnum0099 -> idnum0100 (if that is unused,
  * else whichever of idnum0101, idnume0102, ... is unused. idnum9 -> idnum10.
  *
- * @param string $oldidnumber a question idnumber.
+ * @param string|null $oldidnumber a question idnumber, or can be null.
  * @param int $categoryid a question category id.
  * @return string|null suggested new idnumber for a question in that category, or null if one cannot be found.
  */
-function core_question_find_next_unused_idnumber(string $oldidnumber, int $categoryid):? string {
+function core_question_find_next_unused_idnumber(?string $oldidnumber, int $categoryid): ?string {
     global $DB;
 
     // The the old idnumber is not of the right form, bail now.
diff --git a/lib/table/amd/build/dynamic.min.js b/lib/table/amd/build/dynamic.min.js
new file mode 100644 (file)
index 0000000..d00b78f
Binary files /dev/null and b/lib/table/amd/build/dynamic.min.js differ
diff --git a/lib/table/amd/build/dynamic.min.js.map b/lib/table/amd/build/dynamic.min.js.map
new file mode 100644 (file)
index 0000000..2d81312
Binary files /dev/null and b/lib/table/amd/build/dynamic.min.js.map differ
diff --git a/lib/table/amd/build/local/dynamic/repository.min.js b/lib/table/amd/build/local/dynamic/repository.min.js
new file mode 100644 (file)
index 0000000..d07cd46
Binary files /dev/null and b/lib/table/amd/build/local/dynamic/repository.min.js differ
diff --git a/lib/table/amd/build/local/dynamic/repository.min.js.map b/lib/table/amd/build/local/dynamic/repository.min.js.map
new file mode 100644 (file)
index 0000000..7629807
Binary files /dev/null and b/lib/table/amd/build/local/dynamic/repository.min.js.map differ
diff --git a/lib/table/amd/build/local/dynamic/selectors.min.js b/lib/table/amd/build/local/dynamic/selectors.min.js
new file mode 100644 (file)
index 0000000..e45e5ff
Binary files /dev/null and b/lib/table/amd/build/local/dynamic/selectors.min.js differ
diff --git a/lib/table/amd/build/local/dynamic/selectors.min.js.map b/lib/table/amd/build/local/dynamic/selectors.min.js.map
new file mode 100644 (file)
index 0000000..6af6cf1
Binary files /dev/null and b/lib/table/amd/build/local/dynamic/selectors.min.js.map differ
diff --git a/lib/table/amd/src/dynamic.js b/lib/table/amd/src/dynamic.js
new file mode 100644 (file)
index 0000000..d3b9d06
--- /dev/null
@@ -0,0 +1,160 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Module to handle dynamic table features.
+ *
+ * @module     core_table/dynamic
+ * @package    core_table
+ * @copyright  2020 Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+import {fetch as fetchTableData} from 'core_table/local/dynamic/repository';
+import * as Selectors from 'core_table/local/dynamic/selectors';
+
+let watching = false;
+
+/**
+ * Ensure that a table is a dynamic table.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {Bool}
+ */
+const checkTableIsDynamic = tableRoot => {
+    if (!tableRoot) {
+        // The table is not a dynamic table.
+        throw new Error("The table specified is not a dynamic table and cannot be updated");
+    }
+
+    if (!tableRoot.matches(Selectors.table.region)) {
+        // The table is not a dynamic table.
+        throw new Error("The table specified is not a dynamic table and cannot be updated");
+    }
+
+    return true;
+};
+
+/**
+ * Get the filterset data from a known dynamic table.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {Object}
+ */
+const getFiltersetFromTable = tableRoot => {
+    return JSON.parse(tableRoot.dataset.tableFilters);
+};
+
+/**
+ * Update the specified table based on its current values.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {Promise}
+ */
+export const refreshTableContent = tableRoot => {
+    const filterset = getFiltersetFromTable(tableRoot);
+
+    return fetchTableData(
+        tableRoot.dataset.tableHandler,
+        tableRoot.dataset.tableUniqueid,
+        {
+            sortBy: tableRoot.dataset.tableSortBy,
+            sortOrder: tableRoot.dataset.tableSortOrder,
+            joinType: filterset.jointype,
+            filters: filterset.filters,
+        }
+    )
+    .then(data => {
+        const placeholder = document.createElement('div');
+        placeholder.innerHTML = data.html;
+        tableRoot.replaceWith(...placeholder.childNodes);
+
+        return data;
+    });
+};
+
+export const updateTable = (tableRoot, {
+    sortBy = null,
+    sortOrder = null,
+    filters = null,
+} = {}, refreshContent = true) => {
+    checkTableIsDynamic(tableRoot);
+
+    // Update sort fields.
+    if (sortBy && sortOrder) {
+        tableRoot.dataset.tableSortBy = sortBy;
+        tableRoot.dataset.tableSortOrder = sortOrder;
+    }
+
+    // Update filters.
+    if (filters) {
+        tableRoot.dataset.tableFilters = JSON.stringify(filters);
+    }
+
+    // Refresh.
+    if (refreshContent) {
+        return refreshTableContent(tableRoot);
+    } else {
+        return Promise.resolve();
+    }
+};
+
+/**
+ * Update the specified table using the new filters.
+ *
+ * @param {HTMLElement} tableRoot
+ * @param {Object} filters
+ * @param {Bool} refreshContent
+ * @returns {Promise}
+ */
+export const setFilters = (tableRoot, filters, refreshContent = true) =>
+    updateTable(tableRoot, {filters}, refreshContent);
+
+/**
+ * Update the sort order.
+ *
+ * @param {HTMLElement} tableRoot
+ * @param {String} sortBy
+ * @param {Number} sortOrder
+ * @param {Bool} refreshContent
+ * @returns {Promise}
+ */
+export const setSortOrder = (tableRoot, sortBy, sortOrder, refreshContent = true) =>
+    updateTable(tableRoot, {sortBy, sortOrder}, refreshContent);
+
+/**
+ * Set up listeners to handle table updates.
+ */
+export const init = () => {
+    if (watching) {
+        // Already watching.
+        return;
+    }
+    watching = true;
+
+    document.addEventListener('click', e => {
+        const tableRoot = e.target.closest(Selectors.table.region);
+
+        if (!tableRoot) {
+            return;
+        }
+
+        const sortableLink = e.target.closest(Selectors.table.links.sortableColumn);
+        if (sortableLink) {
+            e.preventDefault();
+
+            setSortOrder(tableRoot, sortableLink.dataset.sortby, sortableLink.dataset.sortorder);
+        }
+    });
+};
diff --git a/lib/table/amd/src/local/dynamic/repository.js b/lib/table/amd/src/local/dynamic/repository.js
new file mode 100644 (file)
index 0000000..4fe7219
--- /dev/null
@@ -0,0 +1,54 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * A javascript module to handle calendar ajax actions.
+ *
+ * @module     core_calendar/repository
+ * @class      repository
+ * @package    core_calendar
+ * @copyright  2017 Simey Lameze <lameze@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+import {call as fetchMany} from 'core/ajax';
+
+/**
+ * Fetch table view.
+ *
+ * @method fetch
+ * @param {String} handler The name of the handler
+ * @param {String} uniqueid The unique id of the table
+ * @param {Number} params parameters to request table
+ * @return {Promise} Resolved with requested table view
+ */
+export const fetch = (handler, uniqueid, {
+        sortBy = null,
+        sortOrder = null,
+        joinType = null,
+        filters = {}
+    } = {}
+) => {
+    return fetchMany([{
+        methodname: `core_table_dynamic_fetch`,
+        args: {
+            handler,
+            uniqueid,
+            sortby: sortBy,
+            sortorder: sortOrder,
+            jointype: joinType,
+            filters,
+        },
+    }])[0];
+};
diff --git a/lib/table/amd/src/local/dynamic/selectors.js b/lib/table/amd/src/local/dynamic/selectors.js
new file mode 100644 (file)
index 0000000..68a0a3b
--- /dev/null
@@ -0,0 +1,31 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Dynamic table selectors.
+ *
+ * @module     core_table/selectors
+ * @package    core_table
+ * @copyright  2020 Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+export default {
+    table: {
+        region: '[data-region="core_table/dynamic"]',
+        links: {
+            sortableColumn: 'a[data-sortable="1"]',
+        },
+    },
+};
index f528685..69701a0 100644 (file)
@@ -30,6 +30,7 @@ namespace core_table;
 defined('MOODLE_INTERNAL') || die();
 
 use moodle_url;
+use context;
 use core_table\local\filter\filterset;
 
 /**
@@ -57,7 +58,7 @@ interface dynamic {
      *
      * @return moodle_url
      */
-    public static function get_base_url(): moodle_url;
+    public function get_base_url(): moodle_url;
 
     /**
      * Set the filterset filters build table object.
@@ -66,4 +67,20 @@ interface dynamic {
      * @return void
      */
     public function set_filterset(filterset $filterset): void;
+
+    /**
+     * Get the currently defined filterset.
+     *
+     * @return filterset
+     */
+    public function get_filterset(): ?filterset;
+
+    /**
+     * Get the context of the current table.
+     *
+     * Note: This function should not be called until after the filterset has been provided.
+     *
+     * @return context
+     */
+    public function get_context(): ?context;
 }
diff --git a/lib/table/classes/external/dynamic/fetch.php b/lib/table/classes/external/dynamic/fetch.php
new file mode 100644 (file)
index 0000000..8cef49c
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Table external API.
+ *
+ * @package    core_table
+ * @category   external
+ * @copyright  2020 Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_table\external\dynamic;
+
+use external_api;
+use external_function_parameters;
+use external_multiple_structure;
+use external_single_structure;
+use external_value;
+use external_warnings;
+use moodle_url;
+
+/**
+ * Core table external functions.
+ *
+ * @package    core_table
+ * @category   external
+ * @copyright  2020 Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class fetch extends external_api {
+
+    /**
+     * Describes the parameters for fetching the table html.
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.9
+     */
+    public static function execute_parameters(): external_function_parameters {
+        return new external_function_parameters ([
+            'handler' => new external_value(
+                // Note: We do not have a PARAM_CLASSNAME which would have been ideal.
+                PARAM_RAW,
+                'Handler',
+                VALUE_REQUIRED
+            ),
+            'uniqueid' => new external_value(
+                PARAM_ALPHANUMEXT,
+                'Unique ID for the container',
+                VALUE_REQUIRED
+            ),
+            'sortby' => new external_value(
+                PARAM_ALPHANUMEXT,
+                'The name of a sortable column',
+                VALUE_REQUIRED
+            ),
+            'sortorder' => new external_value(
+                PARAM_ALPHANUMEXT,
+                'The sort order',
+                VALUE_REQUIRED
+            ),
+            'filters' => new external_multiple_structure(
+                new external_single_structure([
+                    'name' => new external_value(PARAM_ALPHANUM, 'Name of the filter', VALUE_REQUIRED),
+                    'jointype' => new external_value(PARAM_INT, 'Type of join for filter values', VALUE_REQUIRED),
+                    'values' => new external_multiple_structure(
+                        new external_value(PARAM_RAW, 'Filter value'),
+                        'The value to filter on',
+                        VALUE_REQUIRED
+                    )
+                ]),
+                'The filters that will be applied in the request',
+                VALUE_OPTIONAL
+            ),
+            'jointype' => new external_value(PARAM_INT, 'Type of join to join all filters together', VALUE_REQUIRED),
+        ]);
+    }
+
+    /**
+     * External function to fetch a table view.
+     *
+     * @param string $handler Dynamic table class name.
+     * @param string $uniqueid Unique ID for the container.
+     * @param string $sortby The name of a sortable column.
+     * @param string $sortorder The sort order.
+     * @param array $filters The filters that will be applied in the request.
+     * @param string $jointype The join type.
+     *
+     * @return array
+     */
+    public static function execute(string $handler, string $uniqueid, string $sortby, string $sortorder,
+            array $filters = [], string $jointype = null) {
+
+        global $PAGE;
+
+        if (!class_exists($handler) || !is_subclass_of($handler, \core_table\dynamic::class)) {
+            throw new \UnexpectedValueException('Unknown table handler, or table handler does not support dynamic updating.');
+        }
+
+        [
+            'handler' => $handler,
+            'uniqueid' => $uniqueid,
+            'sortby' => $sortby,
+            'sortorder' => $sortorder,
+            'filters' => $filters,
+            'jointype' => $jointype,
+        ] = self::validate_parameters(self::execute_parameters(), [
+            'handler' => $handler,
+            'uniqueid' => $uniqueid,
+            'sortby' => $sortby,
+            'sortorder' => $sortorder,
+            'filters' => $filters,
+            'jointype' => $jointype,
+        ]);
+
+        $filterset = new \core_user\table\participants_filterset();
+        foreach ($filters as $rawfilter) {
+            $filterset->add_filter_from_params(
+                $rawfilter['name'],
+                $rawfilter['jointype'],
+                $rawfilter['values']
+            );
+        }
+
+        $instance = new $handler($uniqueid);
+        $instance->set_filterset($filterset);
+        $instance->set_sorting($sortby, $sortorder);
+
+        $context = $instance->get_context();
+
+        self::validate_context($context);
+        $PAGE->set_url($instance->get_base_url());
+
+        ob_start();
+        $instance->out(20, true);
+        $participanttablehtml = ob_get_contents();
+        ob_end_clean();
+
+        return [
+            'html' => $participanttablehtml,
+            'warnings' => []
+        ];
+    }
+
+    /**
+     * Describes the data returned from the external function.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.9
+     */
+    public static function execute_returns(): external_single_structure {
+        return new external_single_structure([
+            'html' => new external_value(PARAM_RAW, 'The raw html of the requested table.'),
+            'warnings' => new external_warnings()
+        ]);
+    }
+}
index f98d3f6..eda6039 100644 (file)
@@ -28,6 +28,7 @@ declare(strict_types=1);
 namespace core_table\local\filter;
 
 use Countable;
+use JsonSerializable;
 use InvalidArgumentException;
 use Iterator;
 
@@ -38,7 +39,7 @@ use Iterator;
  * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class filter implements Countable, Iterator {
+class filter implements Countable, Iterator, JsonSerializable {
 
     /** @var in The default filter type (ANY) */
     const JOINTYPE_DEFAULT = 1;
@@ -251,4 +252,17 @@ class filter implements Countable, Iterator {
         $this->sort_filter_values();
         return $this->filtervalues;
     }
+
+    /**
+     * Serialize filter.
+     *
+     * @return mixed|object
+     */
+    public function jsonSerialize() {
+        return (object) [
+            'name' => $this->get_name(),
+            'jointype' => $this->get_join_type(),
+            'values' => $this->get_filter_values(),
+        ];
+    }
 }
index e49fb5a..0afddeb 100644 (file)
@@ -28,6 +28,7 @@ declare(strict_types=1);
 namespace core_table\local\filter;
 
 use InvalidArgumentException;
+use JsonSerializable;
 use UnexpectedValueException;
 use moodle_exception;
 
@@ -38,7 +39,7 @@ use moodle_exception;
  * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class filterset {
+abstract class filterset implements JsonSerializable {
     /** @var in The default filter type (ANY) */
     const JOINTYPE_DEFAULT = 1;
 
@@ -285,4 +286,16 @@ abstract class filterset {
 
         return $this->filtertypes;
     }
+
+    /**
+     * Serialize filterset.
+     *
+     * @return mixed|object
+     */
+    public function jsonSerialize() {
+        return (object) [
+            'jointype' => $this->get_join_type(),
+            'filters' => $this->get_filters(),
+        ];
+    }
 }
diff --git a/lib/table/tests/external/dynamic/fetch_test.php b/lib/table/tests/external/dynamic/fetch_test.php
new file mode 100644 (file)
index 0000000..123b0ec
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for core_table\external\fetch;
+ *
+ * @package   core_table
+ * @category  test
+ * @copyright  2020 Simey Lameze <simey@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
+ */
+
+declare(strict_types = 1);
+
+namespace core_table\external\dynamic;
+
+use core_table\local\filter\filter;
+use advanced_testcase;
+
+/**
+ * Unit tests for core_table\external\fetch;
+ *
+ * @package   core_table
+ * @category  test
+ * @copyright  2020 Simey Lameze <simey@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class fetch_test extends advanced_testcase {
+
+    /**
+     * Setup before class.
+     */
+    public static function setUpBeforeClass(): void {
+        global $CFG;
+        require_once("{$CFG->libdir}/externallib.php");
+    }
+
+    /**
+     * Test execute invalid handler.
+     */
+    public function test_execute_invalid_handler(): void {
+        $this->resetAfterTest();
+
+        $this->expectException('UnexpectedValueException');
+        $this->expectExceptionMessage("Unknown table handler, or table handler does not support dynamic updating.");
+
+        // Tests that invalid users_participants_table class gets an exception.
+        fetch::execute("core_user\users_participants_table", "", "email", "4", [], "1");
+    }
+
+    /**
+     * Test execute invalid filter.
+     */
+    public function test_execute_invalid_filter(): void {
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+
+        // Filter with an invalid name.
+        $filter = [
+            [
+                'fullname' => 'courseid',
+                'jointype' => filter::JOINTYPE_ANY,
+                'values' => [(int)$course->id]
+            ]
+        ];
+        $this->expectException('invalid_parameter_exception');
+        $this->expectExceptionMessage("Invalid parameter value detected (filters => Invalid parameter value detected " .
+        "(Missing required key in single structure: name): Missing required key in single structure: name");
+
+        fetch::execute("core_user\participants_table",
+            "user-index-participants-{$course->id}", "firstname", "4", $filter, (string)filter::JOINTYPE_ANY);
+
+    }
+
+    /**
+     * Test execute fetch table.
+     */
+    public function test_execute_fetch_table(): void {
+        $this->resetAfterTest();
+
+        $course = $this->getDataGenerator()->create_course();
+        $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student', ['email' => 's1@moodle.com']);
+        $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student', ['email' => 's2@moodle.com']);
+        $user3 = $this->getDataGenerator()->create_user(['email' => 's3@moodle.com']);
+        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher', ['email' => 't1@moodle.com']);
+
+        $this->setUser($teacher);
+
+        $filter = [
+            [
+                'name' => 'courseid',
+                'jointype' => filter::JOINTYPE_ANY,
+                'values' => [(int)$course->id]
+            ]
+        ];
+
+        $participantstable = fetch::execute("core_user\participants_table",
+            "user-index-participants-{$course->id}", "firstname", "4", $filter, (string)filter::JOINTYPE_ANY);
+        $html = $participantstable['html'];
+
+        $this->assertStringContainsString($user1->email, $html);
+        $this->assertStringContainsString($user2->email, $html);
+        $this->assertStringContainsString($teacher->email, $html);
+        $this->assertStringNotContainsString($user3->email, $html);
+    }
+}
index c1093c2..e5f5b03 100644 (file)
@@ -84,6 +84,17 @@ class flexible_table {
     private $persistent = false;
     var $is_collapsible = false;
     var $is_sortable    = false;
+
+    /**
+     * @var string The field name to sort by.
+     */
+    protected $sortby;
+
+    /**
+     * @var string $sortorder The direction for sorting.
+     */
+    protected $sortorder;
+
     var $use_pages      = false;
     var $use_initials   = false;
 
@@ -513,35 +524,7 @@ class flexible_table {
             }
         }
 
-        if (($sortcol = optional_param($this->request[TABLE_VAR_SORT], '', PARAM_ALPHANUMEXT)) &&
-                $this->is_sortable($sortcol) && empty($this->prefs['collapse'][$sortcol]) &&
-                (isset($this->columns[$sortcol]) || in_array($sortcol, get_all_user_name_fields())
-                && isset($this->columns['fullname']))) {
-
-            $sortdir = optional_param($this->request[TABLE_VAR_DIR], $this->sort_default_order, PARAM_INT);
-
-            if (array_key_exists($sortcol, $this->prefs['sortby'])) {
-                // This key already exists somewhere. Change its sortorder and bring it to the top.
-                $sortorder = $this->prefs['sortby'][$sortcol] = $sortdir;
-                unset($this->prefs['sortby'][$sortcol]);
-                $this->prefs['sortby'] = array_merge(array($sortcol => $sortorder), $this->prefs['sortby']);
-            } else {
-                // Key doesn't exist, so just add it to the beginning of the array, ascending order
-                $this->prefs['sortby'] = array_merge(array($sortcol => $sortdir), $this->prefs['sortby']);
-            }
-
-            // Finally, make sure that no more than $this->maxsortkeys are present into the array
-            $this->prefs['sortby'] = array_slice($this->prefs['sortby'], 0, $this->maxsortkeys);
-        }
-
-        // MDL-35375 - If a default order is defined and it is not in the current list of order by columns, add it at the end.
-        // This prevents results from being returned in a random order if the only order by column contains equal values.
-        if (!empty($this->sort_default_column))  {
-            if (!array_key_exists($this->sort_default_column, $this->prefs['sortby'])) {
-                $defaultsort = array($this->sort_default_column => $this->sort_default_order);
-                $this->prefs['sortby'] = array_merge($this->prefs['sortby'], $defaultsort);
-            }
-        }
+        $this->set_sorting_preferences();
 
         $ilast = optional_param($this->request[TABLE_VAR_ILAST], null, PARAM_RAW);
         if (!is_null($ilast) && ($ilast ==='' || strpos(get_string('alphabet', 'langconfig'), $ilast) !== false)) {
@@ -1158,7 +1141,8 @@ class flexible_table {
      * This function is not part of the public api.
      */
     function finish_html() {
-        global $OUTPUT;
+        global $OUTPUT, $PAGE;
+
         if (!$this->started_output) {
             //no data has been added to the table.
             $this->print_nothing_to_display();
@@ -1187,6 +1171,13 @@ class flexible_table {
                 $pagingbar->pagevar = $this->request[TABLE_VAR_PAGE];
                 echo $OUTPUT->render($pagingbar);
             }
+
+            // Dynamic Table content.
+            if (is_a($this, \core_table\dynamic::class)) {
+                echo html_writer::end_tag('div');
+
+                $PAGE->requires->js_call_amd('core_table/dynamic', 'init');
+            }
         }
     }
 
@@ -1322,6 +1313,57 @@ class flexible_table {
         echo html_writer::end_tag('thead');
     }
 
+    /**
+     * Calculate the preferences for sort order based on user-supplied values and get params.
+     */
+    protected function set_sorting_preferences(): void {
+        $sortorder = $this->sortorder;
+        $sortby = $this->sortby;
+
+        if ($sortorder === null || $sortby === null) {
+            $sortorder = optional_param($this->request[TABLE_VAR_DIR], $this->sort_default_order, PARAM_INT);
+            $sortby = optional_param($this->request[TABLE_VAR_SORT], '', PARAM_ALPHANUMEXT);
+        }
+
+        $isvalidsort = $sortby && $this->is_sortable($sortby);
+        $isvalidsort = $isvalidsort && empty($this->prefs['collapse'][$sortby]);
+        $isrealcolumn = isset($this->columns[$sortby]);
+        $isfullnamefield = isset($this->columns['fullname']) && in_array($sortby, get_all_user_name_fields());
+
+        if ($isvalidsort && ($isrealcolumn || $isfullnamefield)) {
+            if (array_key_exists($sortby, $this->prefs['sortby'])) {
+                // This key already exists somewhere. Change its sortorder and bring it to the top.
+                $sortorder = $this->prefs['sortby'][$sortby] = $sortorder;
+                unset($this->prefs['sortby'][$sortby]);
+                $this->prefs['sortby'] = array_merge(array($sortby => $sortorder), $this->prefs['sortby']);
+            } else {
+                // Key doesn't exist, so just add it to the beginning of the array, ascending order.
+                $this->prefs['sortby'] = array_merge(array($sortby => $sortorder), $this->prefs['sortby']);
+            }
+
+            // Finally, make sure that no more than $this->maxsortkeys are present into the array.
+            $this->prefs['sortby'] = array_slice($this->prefs['sortby'], 0, $this->maxsortkeys);
+        }
+
+        // If a default order is defined and it is not in the current list of order by columns, add it at the end.
+        // This prevents results from being returned in a random order if the only order by column contains equal values.
+        if (!empty($this->sort_default_column) && !array_key_exists($this->sort_default_column, $this->prefs['sortby'])) {
+            $defaultsort = array($this->sort_default_column => $this->sort_default_order);
+            $this->prefs['sortby'] = array_merge($this->prefs['sortby'], $defaultsort);
+        }
+    }
+
+    /**
+     * Set the preferred table sorting attributes.
+     *
+     * @param string $sortby The field to sort by.
+     * @param int $sortorder The sort order.
+     */
+    public function set_sorting(string $sortby, int $sortorder): void {
+        $this->sortby = $sortby;
+        $this->sortorder = $sortorder;
+    }
+
     /**
      * Generate the HTML for the sort icon. This is a helper method used by {@link sort_link()}.
      * @param bool $isprimary whether an icon is needed (it is only needed for the primary sort column.)
@@ -1380,8 +1422,27 @@ class flexible_table {
 
         return html_writer::link($this->baseurl->out(false, $params),
                 $text . get_accesshide(get_string('sortby') . ' ' .
-                $text . ' ' . $this->sort_order_name($isprimary, $order))) . ' ' .
-                $this->sort_icon($isprimary, $order);
+                $text . ' ' . $this->sort_order_name($isprimary, $order)),
+                [
+                    'data-sortable' => $this->is_sortable($column),
+                    'data-sortby' => $column,
+                    'data-sortorder' => $sortorder,
+                ]) . ' ' . $this->sort_icon($isprimary, $order);
+    }
+
+    /**
+     * Return sorting attributes values.
+     *
+     * @return array
+     */
+    protected function get_sort_order(): array {
+        $sortbys = $this->prefs['sortby'];
+        $sortby = key($sortbys);
+
+        return [
+            'sortby' => $sortby,
+            'sortorder' => $sortbys[$sortby],
+        ];
     }
 
     /**
@@ -1390,6 +1451,18 @@ class flexible_table {
     function start_html() {
         global $OUTPUT;
 
+        if (is_a($this, \core_table\dynamic::class)) {
+            $sortdata = $this->get_sort_order();
+            echo html_writer::start_tag('div', [
+                'data-region' => 'core_table/dynamic',
+                'data-table-handler' => get_class($this),
+                'data-table-uniqueid' => $this->uniqueid,
+                'data-table-filters' => json_encode($this->get_filterset()),
+                'data-table-sort-by' => $sortdata['sortby'],
+                'data-table-sort-order' => $sortdata['sortorder'],
+            ]);
+        }
+
         // Render button to allow user to reset table preferences.
         echo $this->render_reset_button();
 
@@ -1460,10 +1533,8 @@ class flexible_table {
      * @return bool
      */
     protected function can_be_reset() {
-
         // Loop through preferences and make sure they are empty or set to the default value.
         foreach ($this->prefs as $prefname => $prefval) {
-
             if ($prefname === 'sortby' and !empty($this->sort_default_column)) {
                 // Check if the actual sorting differs from the default one.
                 if (empty($prefval) or $prefval !== array($this->sort_default_column => $this->sort_default_order)) {
index 99f5923..63f14e1 100644 (file)
@@ -421,6 +421,7 @@ class core_authlib_testcase extends advanced_testcase {
     public function test_signup_validate_data_same_email() {
         global $CFG;
         require_once($CFG->libdir . '/authlib.php');
+        require_once($CFG->libdir . '/phpmailer/moodle_phpmailer.php');
         require_once($CFG->dirroot . '/user/profile/lib.php');
 
         $this->resetAfterTest();
@@ -433,7 +434,8 @@ class core_authlib_testcase extends advanced_testcase {
         // inject our own validation method here and revert it back once we are done. This custom validator method is
         // identical to the default 'php' validator with the only difference: it has the FILTER_FLAG_EMAIL_UNICODE set
         // so that it allows to use non-ASCII characters in email addresses.
-        $defaultvalidator = moodle_phpmailer::$validator; moodle_phpmailer::$validator = function($address) {
+        $defaultvalidator = moodle_phpmailer::$validator;
+        moodle_phpmailer::$validator = function($address) {
             return (bool) filter_var($address, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE);
         };
 
index 23fa7cb..9fc766a 100644 (file)
@@ -371,4 +371,27 @@ class core_events_testcase extends advanced_testcase {
         $this->assertEquals($event->userid, $user->id);
         $this->assertEventContextNotUsed($event);
     }
+
+    /**
+     * Test the database text field content replaced event.
+     */
+    public function test_database_text_field_content_replaced() {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/lib/adminlib.php');
+
+        // Trigger and capture the event for finding and replacing strings in the database.
+        $sink = $this->redirectEvents();
+        ob_start();
+        db_replace('searchstring', 'replacestring');
+        ob_end_clean();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\database_text_field_content_replaced', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+        $this->assertEquals('searchstring', $event->other['search']);
+        $this->assertEquals('replacestring', $event->other['replace']);
+    }
 }
index ee251cd..c48bda9 100644 (file)
@@ -61,8 +61,9 @@ class mod_folder_renderer extends plugin_renderer_base {
             // Display module name as the name of the root directory.
             $foldertree->dir['dirname'] = $cm->get_formatted_name(array('escape' => false));
         }
-        $output .= $this->output->box($this->render($foldertree),
-                'generalbox foldertree');
+        $output .= $this->output->container_start("box generalbox pt-0 pb-3 foldertree");
+        $output .= $this->render($foldertree);
+        $output .= $this->output->container_end();
 
         // Do not append the edit button on the course page.
         $downloadable = folder_archive_available($folder, $cm);
@@ -91,7 +92,9 @@ class mod_folder_renderer extends plugin_renderer_base {
         }
 
         if ($buttons) {
-            $output .= $this->output->box($buttons, 'generalbox folderbuttons');
+            $output .= $this->output->container_start("box generalbox pt-0 pb-3 folderbuttons");
+            $output .= $buttons;
+            $output .= $this->output->container_end();
         }
 
         return $output;
index a3d2281..38d5c0f 100644 (file)
@@ -66,9 +66,7 @@ if ($data = $mform->get_data()) {
     require_sesskey();
 
     $realfilename = $mform->get_new_filename('questionfile');
-    //TODO: Leave all imported questions in Questionimport for now.
-    $importfile = "{$CFG->tempdir}/questionimport/{$realfilename}";
-    make_temp_directory('questionimport');
+    $importfile = make_request_directory() . "/{$realfilename}";
     if (!$result = $mform->save_file('questionfile', $importfile, true)) {
         throw new moodle_exception('uploadproblem');
     }
index edf22de..85507c0 100644 (file)
@@ -272,14 +272,19 @@ class mod_lti_edit_types_form extends moodleform {
                 // Add setup parameters fieldset.
                 $mform->addElement('header', 'setupoptions', get_string('miscellaneous', 'lti'));
 
-                // Adding option to change id that is placed in context_id.
-                $idoptions = array();
-                $idoptions[0] = get_string('id', 'lti');
-                $idoptions[1] = get_string('courseid', 'lti');
+                $options = array(
+                    LTI_DEFAULT_ORGID_SITEID => get_string('siteid', 'lti'),
+                    LTI_DEFAULT_ORGID_SITEHOST => get_string('sitehost', 'lti'),
+                );
 
-                $mform->addElement('text', 'lti_organizationid', get_string('organizationid', 'lti'));
+                $mform->addElement('select', 'lti_organizationid_default', get_string('organizationid_default', 'lti'), $options);
+                $mform->setType('lti_organizationid_default', PARAM_TEXT);
+                $mform->setDefault('lti_organizationid_default', LTI_DEFAULT_ORGID_SITEID);
+                $mform->addHelpButton('lti_organizationid_default', 'organizationid_default', 'lti');
+
+                $mform->addElement('text', 'lti_organizationid', get_string('organizationidguid', 'lti'));
                 $mform->setType('lti_organizationid', PARAM_TEXT);
-                $mform->addHelpButton('lti_organizationid', 'organizationid', 'lti');
+                $mform->addHelpButton('lti_organizationid', 'organizationidguid', 'lti');
 
                 $mform->addElement('text', 'lti_organizationurl', get_string('organizationurl', 'lti'));
                 $mform->setType('lti_organizationurl', PARAM_URL);
index f6fc8f6..0d0691f 100644 (file)
@@ -1 +1,3 @@
 leaveblank,mod_lti
+organizationid,mod_lti
+organizationid_help,mod_lti
index 906f364..4995d5c 100644 (file)
@@ -330,10 +330,14 @@ $string['oauthsecurity'] = 'LTI 1.0/1.1';
 $string['optionalsettings'] = 'Optional settings';
 $string['organization'] = 'Organization details';
 $string['organizationdescr'] = 'Organization description';
-$string['organizationid'] = 'Organization ID';
-$string['organizationid_help'] = 'A unique identifier for this Moodle instance. Typically, the DNS name of the organization is used.
-
-If this field is left blank, the host name of this Moodle site will be used as the default value.';
+$string['organizationid_default'] = 'Default organization ID';
+$string['siteid'] = 'Site ID';
+$string['sitehost'] = 'Site hostname';
+$string['organizationid_default_help'] = 'Default value to use for Organization ID. Site ID identifies this installation of moodle.';
+$string['organizationidguid'] = 'Organization ID';
+$string['organizationidguid_help'] = 'A unique identifier for this Moodle instance passed to the tool as the Platform Instance GUID.
+
+If this field is left blank, the default value will be used.';
 $string['organizationurl'] = 'Organization URL';
 $string['organizationurl_help'] = 'The base URL of this Moodle instance.
 
@@ -576,3 +580,9 @@ $string['using_tool_cartridge'] = 'Using tool cartridge';
 $string['using_tool_configuration'] = 'Using tool configuration: ';
 $string['validurl'] = 'A valid URL must start with http(s)://';
 $string['viewsubmissions'] = 'View submissions and grading screen';
+
+// Deprecated since Moodle 3.9.
+$string['organizationid'] = 'Organization ID';
+$string['organizationid_help'] = 'A unique identifier for this Moodle instance. Typically, the DNS name of the organization is used.
+
+If this field is left blank, the host name of this Moodle site will be used as the default value.';
index 4cd7786..7632f47 100644 (file)
@@ -91,6 +91,9 @@ define('LTI_VERSION_1', 'LTI-1p0');
 define('LTI_VERSION_2', 'LTI-2p0');
 define('LTI_VERSION_1P3', '1.3.0');
 
+define('LTI_DEFAULT_ORGID_SITEID', 'SITEID');
+define('LTI_DEFAULT_ORGID_SITEHOST', 'SITEHOST');
+
 define('LTI_ACCESS_TOKEN_LIFE', 3600);
 
 // Standard prefix for JWT claims.
@@ -537,13 +540,6 @@ function lti_get_launch_data($instance, $nonce = '') {
         $typeconfig['forcessl'] = '0';
     }
 
-    // Default the organizationid if not specified.
-    if (empty($typeconfig['organizationid'])) {
-        $urlparts = parse_url($CFG->wwwroot);
-
-        $typeconfig['organizationid'] = $urlparts['host'];
-    }
-
     if (isset($tool->toolproxyid)) {
         $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
         $key = $toolproxy->guid;
@@ -589,7 +585,7 @@ function lti_get_launch_data($instance, $nonce = '') {
         }
     }
 
-    $orgid = $typeconfig['organizationid'];
+    $orgid = lti_get_organizationid($typeconfig);
 
     $course = $PAGE->course;
     $islti2 = isset($tool->toolproxyid);
@@ -760,6 +756,25 @@ function lti_build_registration_request($toolproxy) {
     return $requestparams;
 }
 
+
+/** get Organization ID using default if no value provided
+ * @param object $typeconfig
+ * @return string
+ */
+function lti_get_organizationid($typeconfig) {
+    global $CFG;
+    // Default the organizationid if not specified.
+    if (empty($typeconfig['organizationid'])) {
+        if (($typeconfig['organizationid_default'] ?? LTI_DEFAULT_ORGID_SITEHOST) == LTI_DEFAULT_ORGID_SITEHOST) {
+            $urlparts = parse_url($CFG->wwwroot);
+            return $urlparts['host'];
+        } else {
+            return md5(get_site_identifier());
+        }
+    }
+    return $typeconfig['organizationid'];
+}
+
 /**
  * Build source ID
  *
@@ -1145,7 +1160,7 @@ function lti_build_content_item_selection_request($id, $course, moodle_url $retu
     }
 
     // Get standard request parameters and merge to the request parameters.
-    $orgid = !empty($typeconfig['organizationid']) ? $typeconfig['organizationid'] : '';
+    $orgid = lti_get_organizationid($typeconfig);
     $standardparams = lti_build_standard_message(null, $orgid, $tool->ltiversion, 'ContentItemSelectionRequest');
     $requestparams = array_merge($requestparams, $standardparams);
 
@@ -2501,6 +2516,12 @@ function lti_get_type_type_config($id) {
         $type->lti_forcessl = $config['forcessl'];
     }
 
+    if (isset($config['organizationid_default'])) {
+        $type->lti_organizationid_default = $config['organizationid_default'];
+    } else {
+        // Tool was configured before this option was available and the default then was host.
+        $type->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEHOST;
+    }
     if (isset($config['organizationid'])) {
         $type->lti_organizationid = $config['organizationid'];
     }
index 619efb7..7caf5d0 100644 (file)
@@ -79,10 +79,6 @@ class linkmemberships extends resource_base {
             $limitfrom = 0;
         }
 
-        if (!$this->check_tool(null, $response->get_request_data(), memberships::SCOPE_MEMBERSHIPS_READ)) {
-            $response->set_code(403);
-            return;
-        }
         if (empty($linkid)) {
             $response->set_code(404);
             return;
@@ -91,6 +87,10 @@ class linkmemberships extends resource_base {
             $response->set_code(404);
             return;
         }
+        if (!$this->check_tool($lti->typeid, $response->get_request_data(), array(memberships::SCOPE_MEMBERSHIPS_READ))) {
+            $response->set_code(403);
+            return;
+        }
         if (!($course = $DB->get_record('course', array('id' => $lti->course), 'id', IGNORE_MISSING))) {
             $response->set_code(404);
             return;
@@ -106,7 +106,7 @@ class linkmemberships extends resource_base {
         if ($info->is_available_for_all()) {
             $info = null;
         }
-        $json = $this->get_service()->get_members_json($this, $context, $lti->course, $role,
+        $json = $this->get_service()->get_members_json($this, $context, $course, $role,
                                                        $limitfrom, $limitnum, $lti, $info, $response);
 
         $response->set_content_type($this->formats[0]);
index 6b9a9f5..1205205 100644 (file)
@@ -267,14 +267,17 @@ class memberships extends \mod_lti\local\ltiservice\service_base {
                                             'member.field' => 'name',
                                             'source.value' => format_string("{$user->firstname} {$user->lastname}")],
                 'Person.name.given'    => ['type' => 'name',
-                                            'member.field' => 'giveName',
+                                            'member.field' => 'givenName',
                                             'source.value' => format_string($user->firstname)],
                 'Person.name.family'   => ['type' => 'name',
                                             'member.field' => 'familyName',
                                             'source.value' => format_string($user->lastname)],
                 'Person.email.primary' => ['type' => 'email',
                                             'member.field' => 'email',
-                                            'source.value' => format_string($user->email)]
+                                            'source.value' => format_string($user->email)],
+                'User.username'        => ['type' => 'name',
+                                           'member.field' => 'ext_user_username',
+                                           'source.value' => format_string($user->username)]
             ];
 
             if (!is_null($lti)) {
index d9c90ca..2b2f59e 100644 (file)
@@ -386,6 +386,7 @@ class mod_lti_locallib_testcase extends advanced_testcase {
         $this->assertFalse(isset($params['resource_link_description']));
         $this->assertFalse(isset($params['launch_presentation_return_url']));
         $this->assertFalse(isset($params['lis_result_sourcedid']));
+        $this->assertEquals($params['tool_consumer_instance_guid'], 'www.example.com');
 
         // Custom parameters.
         $title = 'My custom title';
@@ -1415,4 +1416,111 @@ MwIDAQAB
         $this->assertEquals('some-client-id', $request['client_id']);
         $this->assertEquals('some-type-id', $request['lti_deployment_id']);
     }
+
+    /**
+     * Test default orgid is host if not specified in config (tool installed in earlier version of Moodle).
+     */
+    public function test_lti_get_launch_data_default_organizationid_unset_usehost() {
+        global $DB;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $config = new stdClass();
+        $config->lti_organizationid = '';
+        $course = $this->getDataGenerator()->create_course();
+        $type = $this->create_type($config);
+        $link = $this->create_instance($type, $course);
+        $launchdata = lti_get_launch_data($link);
+        $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
+    }
+
+    /**
+     * Test default org id is set to host when config is usehost.
+     */
+    public function test_lti_get_launch_data_default_organizationid_set_usehost() {
+        global $DB;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $config = new stdClass();
+        $config->lti_organizationid = '';
+        $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEHOST;
+        $course = $this->getDataGenerator()->create_course();
+        $type = $this->create_type($config);
+        $link = $this->create_instance($type, $course);
+        $launchdata = lti_get_launch_data($link);
+        $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
+    }
+
+    /**
+     * Test default org id is set to site id when config is usesiteid.
+     */
+    public function test_lti_get_launch_data_default_organizationid_set_usesiteid() {
+        global $DB;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $config = new stdClass();
+        $config->lti_organizationid = '';
+        $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
+        $course = $this->getDataGenerator()->create_course();
+        $type = $this->create_type($config);
+        $link = $this->create_instance($type, $course);
+        $launchdata = lti_get_launch_data($link);
+        $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], md5(get_site_identifier()));
+    }
+
+    /**
+     * Test orgid can be overridden in which case default is ignored.
+     */
+    public function test_lti_get_launch_data_default_organizationid_orgid_override() {
+        global $DB;
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $config = new stdClass();
+        $config->lti_organizationid = 'overridden!';
+        $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
+        $course = $this->getDataGenerator()->create_course();
+        $type = $this->create_type($config);
+        $link = $this->create_instance($type, $course);
+        $launchdata = lti_get_launch_data($link);
+        $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'overridden!');
+    }
+
+    /**
+     * Create an LTI Tool.
+     *
+     * @param object $config tool config.
+     *
+     * @return object tool.
+     */
+    private function create_type(object $config) {
+        $type = new stdClass();
+        $type->state = LTI_TOOL_STATE_CONFIGURED;
+        $type->name = "Test tool";
+        $type->description = "Example description";
+        $type->clientid = "Test client ID";
+        $type->baseurl = $this->getExternalTestFileUrl('/test.html');
+
+        $configbase = new stdClass();
+        $configbase->lti_acceptgrades = LTI_SETTING_NEVER;
+        $configbase->lti_sendname = LTI_SETTING_NEVER;
+        $configbase->lti_sendemailaddr = LTI_SETTING_NEVER;
+        $mergedconfig = (object) array_merge( (array) $configbase, (array) $config);
+        $typeid = lti_add_type($type, $mergedconfig);
+        return lti_get_type($typeid);
+    }
+
+    /**
+     * Create an LTI Instance for the tool in a given course.
+     *
+     * @param object $type tool for which an instance should be added.
+     * @param object $course course where the instance should be added.
+     *
+     * @return object instance.
+     */
+    private function create_instance(object $type, object $course) {
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
+        return $generator->create_instance(array('course' => $course->id,
+                  'toolurl' => $type->baseurl,
+                  'typeid' => $type->id
+                  ), array());
+    }
 }
index 11fe97c..2e66166 100644 (file)
@@ -62,7 +62,11 @@ function loadContent(datafile, callback) {
     }
 
     newscript.type = 'text/javascript';
-    newscript.src = encodeURIComponent(data);
+    var dataParts = data.split('/');
+    dataParts.forEach(function(part, index) {
+        this[index] = encodeURIComponent(part);
+    }, dataParts);
+    newscript.src = dataParts.join('/');
     newscript.charset = 'utf-8';
     document.getElementsByTagName("head")[0].appendChild(newscript);
 
index 0f68d1e..567fb74 100644 (file)
@@ -86,9 +86,7 @@ if ($form = $import_form->get_data()) {
     // work out if this is an uploaded file
     // or one from the filesarea.
     $realfilename = $import_form->get_new_filename('newfile');
-
-    $importfile = "{$CFG->tempdir}/questionimport/{$realfilename}";
-    make_temp_directory('questionimport');
+    $importfile = make_request_directory() . "/{$realfilename}";
     if (!$result = $import_form->save_file('newfile', $importfile, true)) {
         throw new moodle_exception('uploadproblem');
     }
diff --git a/question/tests/behat/duplicate_questions.feature b/question/tests/behat/duplicate_questions.feature
new file mode 100644 (file)
index 0000000..1c4abb5
--- /dev/null
@@ -0,0 +1,51 @@
+@core @core_question
+Feature: A teacher can duplicate questions in the question bank
+  In order to efficiently expand my question bank
+  As a teacher
+  I need to be able to duplicate existing questions
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1 | weeks |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And the following "question categories" exist:
+      | contextlevel | reference | name           |
+      | Course       | C1        | Test questions |
+    And the following "questions" exist:
+      | questioncategory | qtype | name                       | questiontext                  |
+      | Test questions   | essay | Test question to be copied | Write about whatever you want |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to "Question bank > Questions" in current page administration
+
+  Scenario: Duplicate a previously created question
+    When I choose "Duplicate" action for "Test question to be copied" in the question bank
+    And I press "id_submitbutton"
+    Then I should see "Test question to be copied (copy)"
+    And I should see "Test question to be copied"
+    And "Test question to be copied (copy)" row "Created by" column of "categoryquestions" table should contain "Teacher 1"
+    And "Test question to be copied (copy)" row "Last modified by" column of "categoryquestions" table should contain "Teacher 1"
+
+  Scenario: Duplicating a question can be cancelled
+    When I choose "Duplicate" action for "Test question to be copied" in the question bank
+    And I set the field "Question name" to "Edited question name"
+    And I press "Cancel"
+    Then I should see "Test question to be copied"
+    And I should not see "Edited question name"
+    And I should not see "Test question to be copied (copy)"
+
+  Scenario: Duplicating a question with an idnumber increments it
+    Given the following "questions" exist:
+      | questioncategory | qtype | name                   | questiontext                  | idnumber |
+      | Test questions   | essay | Question with idnumber | Write about whatever you want | id101    |
+    And I reload the page
+    When I choose "Duplicate" action for "Question with idnumber" in the question bank
+    And I press "id_submitbutton"
+    Then I should see "Question with idnumber (copy)"
+    Then I should see "id102" in the "Question with idnumber (copy)" "table_row"
index 5f649f2..06116ea 100644 (file)
@@ -225,7 +225,10 @@ class qtype_essay_format_editor_renderer extends plugin_renderer_base {
 
     public function response_area_read_only($name, $qa, $step, $lines, $context) {
         return html_writer::tag('div', $this->prepare_response($name, $qa, $step, $context),
-                array('class' => $this->class_name() . ' qtype_essay_response readonly'));
+                ['class' => $this->class_name() . ' qtype_essay_response readonly',
+                        'style' => 'min-height: ' . ($lines * 1.5) . 'em;']);
+        // Height $lines * 1.5 because that is a typical line-height on web pages.
+        // That seems to give results that look OK.
     }
 
     public function response_area_input($name, $qa, $step, $lines, $context) {
index 9084b28..c2d84ff 100644 (file)
@@ -2470,3 +2470,9 @@ $picker-emojis-per-row: 7 !default;
         margin-top: $spacer;
     }
 }
+
+@each $color, $value in $theme-colors {
+    .alert-#{$color} a {
+        color: darken(theme-color-level($color, $alert-color-level), 10%);
+    }
+}
index 6078384..797fc0d 100644 (file)
@@ -1,14 +1,13 @@
 // Bootstrap variables
-
 $white:    #fff !default;
 $gray-100: #f8f9fa !default;
 $gray-200: #e9ecef !default;
 $gray-300: #dee2e6 !default;
 $gray-400: #ced4da !default;
 $gray-500: #adb5bd !default;
-$gray-600: #868e96 !default;
+$gray-600: #6c757d !default;
 $gray-700: #495057 !default;
-$gray-800: #373a3c !default;
+$gray-800: #343a40 !default;
 $gray-900: #212529 !default;
 $black:    #000 !default;
 
@@ -16,21 +15,22 @@ $blue:    #1177d1 !default;
 $indigo:  #6610f2 !default;
 $purple:  #613d7c !default;
 $pink:    #e83e8c !default;
-$red:     #d9534f !default;
+$red:     #d43f3a !default;
 $orange:  #f0ad4e !default;
 $yellow:  #ff7518 !default;
-$green:   #5cb85c !default;
+$green:   #398439 !default;
 $teal:    #20c997 !default;
 $cyan:    #5bc0de !default;
 
 $primary:       $blue !default;
-$secondary:     $gray-800 !default;
 $success:       $green !default;
 $info:          $cyan !default;
-$warning:       $yellow !default;
+$warning:       $orange !default;
 $danger:        $red !default;
-$light:         $gray-100 !default;
-$dark:          $gray-800 !default;
+$secondary:     $gray-400 !default;
+
+$info-outline:    #1f7e9a;
+$warning-outline: #a6670e;
 
 // Options
 $enable-rounded: false !default;
@@ -70,13 +70,11 @@ $custom-control-indicator-size: 1.25rem;
 $theme-colors: () !default;
 $theme-colors: map-merge((
     primary: $primary,
-    secondary: $gray-400,
+    secondary: $secondary,
     success: $success,
     info: $info,
-    warning: $orange,
+    warning: $warning,
     danger: $danger,
-    light: $gray-100,
-    dark: $gray-800
 ), $theme-colors);
 // stylelint-enable
 
@@ -114,7 +112,15 @@ body {
 // for an outline button.
 .btn-outline-secondary {
     @include button-outline-variant($gray-600);
-    border-color: $border-color;
+    border-color: $gray-600;
+}
+
+.btn-outline-info {
+    @include button-outline-variant($info-outline);
+}
+
+.btn-outline-warning {
+    @include button-outline-variant($warning-outline);
 }
 
-@include bg-variant(".bg-gray", $gray-200);
+@include bg-variant(".bg-gray", $gray-200);
\ No newline at end of file
index f8bca74..fc53575 100644 (file)
   --indigo: #6610f2;
   --purple: #613d7c;
   --pink: #e83e8c;
-  --red: #d9534f;
+  --red: #d43f3a;
   --orange: #f0ad4e;
   --yellow: #ff7518;
-  --green: #5cb85c;
+  --green: #398439;
   --teal: #20c997;
   --cyan: #5bc0de;
   --white: #fff;
-  --gray: #868e96;
-  --gray-dark: #373a3c;
+  --gray: #6c757d;
+  --gray-dark: #343a40;
   --primary: #1177d1;
   --secondary: #ced4da;
-  --success: #5cb85c;
+  --success: #398439;
   --info: #5bc0de;
   --warning: #f0ad4e;
-  --danger: #d9534f;
+  --danger: #d43f3a;
   --light: #f8f9fa;
-  --dark: #373a3c;
+  --dark: #343a40;
   --breakpoint-xs: 0;
   --breakpoint-sm: 576px;
   --breakpoint-md: 768px;
@@ -2382,7 +2382,7 @@ body {
   font-size: 0.9375rem;
   font-weight: 400;
   line-height: 1.5;
-  color: #373a3c;
+  color: #343a40;
   text-align: left;
   background-color: #fff; }
 
@@ -2503,7 +2503,7 @@ table {
 caption {
   padding-top: 0.75rem;
   padding-bottom: 0.75rem;
-  color: #868e96;
+  color: #6c757d;
   text-align: left;
   caption-side: bottom; }
 
@@ -2712,7 +2712,7 @@ mark,
 .blockquote-footer {
   display: block;
   font-size: 80%;
-  color: #868e96; }
+  color: #6c757d; }
   .blockquote-footer::before {
     content: "\2014\00A0"; }
 
@@ -2736,7 +2736,7 @@ mark,
 
 .figure-caption {
   font-size: 90%;
-  color: #868e96; }
+  color: #6c757d; }
 
 code {
   font-size: 87.5%;
@@ -3358,7 +3358,7 @@ pre {
 .table {
   width: 100%;
   margin-bottom: 1rem;
-  color: #373a3c; }
+  color: #343a40; }
   .table th,
   .table td {
     padding: 0.75rem;
@@ -3393,7 +3393,7 @@ pre {
   background-color: rgba(0, 0, 0, 0.05); }
 
 .table-hover tbody tr:hover {
-  color: #373a3c;
+  color: #343a40;
   background-color: rgba(0, 0, 0, 0.075); }
 
 .table-primary,
@@ -3433,19 +3433,19 @@ pre {
 .table-success,
 .table-success > th,
 .table-success > td {
-  background-color: #d1ebd1; }
+  background-color: #c8ddc8; }
 
 .table-success th,
 .table-success td,
 .table-success thead th,
 .table-success tbody + tbody {
-  border-color: #aadaaa; }
+  border-color: #98bf98; }
 
 .table-hover .table-success:hover {
-  background-color: #bfe3bf; }
+  background-color: #b8d3b8; }
   .table-hover .table-success:hover > td,
   .table-hover .table-success:hover > th {
-    background-color: #bfe3bf; }
+    background-color: #b8d3b8; }
 
 .table-info,
 .table-info > th,
@@ -3484,19 +3484,19 @@ pre {
 .table-danger,
 .table-danger > th,
 .table-danger > td {
-  background-color: #f4cfce; }
+  background-color: #f3c9c8; }
 
 .table-danger th,
 .table-danger td,
 .table-danger thead th,
 .table-danger tbody + tbody {
-  border-color: #eba6a3; }
+  border-color: #e99b99; }
 
 .table-hover .table-danger:hover {
-  background-color: #efbbb9; }
+  background-color: #eeb4b3; }
   .table-hover .table-danger:hover > td,
   .table-hover .table-danger:hover > th {
-    background-color: #efbbb9; }
+    background-color: #eeb4b3; }
 
 .table-light,
 .table-light > th,
@@ -3518,19 +3518,19 @@ pre {
 .table-dark,
 .table-dark > th,
 .table-dark > td {
-  background-color: #c7c8c8; }
+  background-color: #c6c8ca; }
 
 .table-dark th,
 .table-dark td,
 .table-dark thead th,
 .table-dark tbody + tbody {
-  border-color: #97999a; }
+  border-color: #95999c; }
 
 .table-hover .table-dark:hover {
-  background-color: #babbbb; }
+  background-color: #b9bbbe; }
   .table-hover .table-dark:hover > td,
   .table-hover .table-dark:hover > th {
-    background-color: #babbbb; }
+    background-color: #b9bbbe; }
 
 .table-active,
 .table-active > th,
@@ -3545,8 +3545,8 @@ pre {
 
 .table .thead-dark th {
   color: #fff;
-  background-color: #373a3c;
-  border-color: #494d50; }
+  background-color: #343a40;
+  border-color: #454d55; }
 
 .table .thead-light th {
   color: #495057;
@@ -3555,11 +3555,11 @@ pre {
 
 .table-dark {
   color: #fff;
-  background-color: #373a3c; }
+  background-color: #343a40; }
   .table-dark th,
   .table-dark td,
   .table-dark thead th {
-    border-color: #494d50; }
+    border-color: #454d55; }
   .table-dark.table-bordered {
     border: 0; }
   .table-dark.table-striped tbody tr:nth-of-type(odd) {
@@ -3639,7 +3639,7 @@ pre {
     outline: 0;
     box-shadow: 0 0 0 0.2rem rgba(17, 119, 209, 0.25); }
   .form-control::placeholder {
-    color: #868e96;
+    color: #6c757d;
     opacity: 1; }
   .form-control:disabled, .form-control[readonly] {
     background-color: #e9ecef;
@@ -3680,7 +3680,7 @@ select.form-control:focus::-ms-value {
   padding-bottom: 0.375rem;
   margin-bottom: 0;
   line-height: 1.5;
-  color: #373a3c;
+  color: #343a40;
   background-color: transparent;
   border: solid transparent;
   border-width: 1px 0; }
@@ -3733,7 +3733,7 @@ textarea.form-control {
   margin-top: 0.3rem;
   margin-left: -1.25rem; }
   .form-check-input:disabled ~ .form-check-label {
-    color: #868e96; }
+    color: #6c757d; }
 
 .form-check-label {
   margin-bottom: 0; }
@@ -3754,7 +3754,7 @@ textarea.form-control {
   width: 100%;
   margin-top: 0.25rem;
   font-size: 80%;
-  color: #5cb85c; }
+  color: #398439; }
 
 .valid-tooltip {
   position: absolute;
@@ -3767,18 +3767,18 @@ textarea.form-control {
   font-size: 0.8203125rem;
   line-height: 1.5;
   color: #fff;
-  background-color: rgba(92, 184, 92, 0.9); }
+  background-color: rgba(57, 132, 57, 0.9); }
 
 .was-validated .form-control:valid, .form-control.is-valid {
-  border-color: #5cb85c;
+  border-color: #398439;
   padding-right: calc(1.5em + 0.75rem);
-  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23398439' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
   background-repeat: no-repeat;
   background-position: center right calc(0.375em + 0.1875rem);
   background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .form-control:valid:focus, .form-control.is-valid:focus {
-    border-color: #5cb85c;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+    border-color: #398439;
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
   .was-validated .form-control:valid ~ .valid-feedback,
   .was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,
   .form-control.is-valid ~ .valid-tooltip {
@@ -3789,12 +3789,12 @@ textarea.form-control {
   background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); }
 
 .was-validated .custom-select:valid, .custom-select.is-valid {
-  border-color: #5cb85c;
+  border-color: #398439;
   padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);
-  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23373a3c' 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' viewBox='0 0 8 8'%3e%3cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' 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' viewBox='0 0 8 8'%3e%3cpath fill='%23398439' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {
-    border-color: #5cb85c;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+    border-color: #398439;
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
   .was-validated .custom-select:valid ~ .valid-feedback,
   .was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback,
   .custom-select.is-valid ~ .valid-tooltip {
@@ -3806,7 +3806,7 @@ textarea.form-control {
   display: block; }
 
 .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {
-  color: #5cb85c; }
+  color: #398439; }
 
 .was-validated .form-check-input:valid ~ .valid-feedback,
 .was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,
@@ -3814,9 +3814,9 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {
-  color: #5cb85c; }
+  color: #398439; }
   .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {
-    border-color: #5cb85c; }
+    border-color: #398439; }
 
 .was-validated .custom-control-input:valid ~ .valid-feedback,
 .was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,
@@ -3824,17 +3824,17 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {
-  border-color: #80c780;
-  background-color: #80c780; }
+  border-color: #48a848;
+  background-color: #48a848; }
 
 .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {
-  box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+  box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
 
 .was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {
-  border-color: #5cb85c; }
+  border-color: #398439; }
 
 .was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {
-  border-color: #5cb85c; }
+  border-color: #398439; }
 
 .was-validated .custom-file-input:valid ~ .valid-feedback,
 .was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,
@@ -3842,15 +3842,15 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {
-  border-color: #5cb85c;
-  box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+  border-color: #398439;
+  box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
 
 .invalid-feedback {
   display: none;
   width: 100%;
   margin-top: 0.25rem;
   font-size: 80%;
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .invalid-tooltip {
   position: absolute;
@@ -3863,18 +3863,18 @@ textarea.form-control {
   font-size: 0.8203125rem;
   line-height: 1.5;
   color: #fff;
-  background-color: rgba(217, 83, 79, 0.9); }
+  background-color: rgba(212, 63, 58, 0.9); }
 
 .was-validated .form-control:invalid, .form-control.is-invalid {
-  border-color: #d9534f;
+  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' 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");
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23d43f3a' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d43f3a' 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");
   background-repeat: no-repeat;
   background-position: center right calc(0.375em + 0.1875rem);
   background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
-    border-color: #d9534f;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
   .was-validated .form-control:invalid ~ .invalid-feedback,
   .was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,
   .form-control.is-invalid ~ .invalid-tooltip {
@@ -3885,12 +3885,12 @@ textarea.form-control {
   background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); }
 
 .was-validated .custom-select:invalid, .custom-select.is-invalid {
-  border-color: #d9534f;
+  border-color: #d43f3a;
   padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);
-  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23373a3c' 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' 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") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' 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' fill='%23d43f3a' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d43f3a' 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") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {
-    border-color: #d9534f;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
   .was-validated .custom-select:invalid ~ .invalid-feedback,
   .was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback,
   .custom-select.is-invalid ~ .invalid-tooltip {
@@ -3902,7 +3902,7 @@ textarea.form-control {
   display: block; }
 
 .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .was-validated .form-check-input:invalid ~ .invalid-feedback,
 .was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,
@@ -3910,9 +3910,9 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {
-  color: #d9534f; }
+  color: #d43f3a; }
   .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {
-    border-color: #d9534f; }
+    border-color: #d43f3a; }
 
 .was-validated .custom-control-input:invalid ~ .invalid-feedback,
 .was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,
@@ -3920,17 +3920,17 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {
-  border-color: #e27c79;
-  background-color: #e27c79; }
+  border-color: #dd6864;
+  background-color: #dd6864; }
 
 .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {
-  box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
 
 .was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {
-  border-color: #d9534f; }
+  border-color: #d43f3a; }
 
 .was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {
-  border-color: #d9534f; }
+  border-color: #d43f3a; }
 
 .was-validated .custom-file-input:invalid ~ .invalid-feedback,
 .was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,
@@ -3938,8 +3938,8 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {
-  border-color: #d9534f;
-  box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+  border-color: #d43f3a;
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
 
 .form-inline {
   display: flex;
@@ -3989,7 +3989,7 @@ textarea.form-control {
 .btn {
   display: inline-block;
   font-weight: 400;
-  color: #373a3c;
+  color: #343a40;
   text-align: center;
   vertical-align: middle;
   user-select: none;
@@ -4004,7 +4004,7 @@ textarea.form-control {
     .btn {
       transition: none; } }
   .btn:hover {
-    color: #373a3c;
+    color: #343a40;
     text-decoration: none; }
   .btn:focus, .btn.focus {
     outline: 0;
@@ -4064,26 +4064,26 @@ fieldset:disabled a.btn {
 
 .btn-success {
   color: #fff;
-  background-color: #5cb85c;
-  border-color: #5cb85c; }
+  background-color: #398439;
+  border-color: #398439; }
   .btn-success:hover {
     color: #fff;
-    background-color: #48a648;
-    border-color: #449d44; }
+    background-color: #2d692d;
+    border-color: #2a602a; }
   .btn-success:focus, .btn-success.focus {
-    box-shadow: 0 0 0 0.2rem rgba(116, 195, 116, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(87, 150, 87, 0.5); }
   .btn-success.disabled, .btn-success:disabled {
     color: #fff;
-    background-color: #5cb85c;
-    border-color: #5cb85c; }
+    background-color: #398439;
+    border-color: #398439; }
   .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,
   .show > .btn-success.dropdown-toggle {
     color: #fff;
-    background-color: #449d44;
-    border-color: #409440; }
+    background-color: #2a602a;
+    border-color: #265726; }
     .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,
     .show > .btn-success.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(116, 195, 116, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(87, 150, 87, 0.5); }
 
 .btn-info {
   color: #212529;
@@ -4133,26 +4133,26 @@ fieldset:disabled a.btn {
 
 .btn-danger {
   color: #fff;
-  background-color: #d9534f;
-  border-color: #d9534f; }
+  background-color: #d43f3a;
+  border-color: #d43f3a; }
   .btn-danger:hover {
     color: #fff;
-    background-color: #d23430;
-    border-color: #c9302c; }
+    background-color: #be2e2a;
+    border-color: #b42c27; }
   .btn-danger:focus, .btn-danger.focus {
-    box-shadow: 0 0 0 0.2rem rgba(223, 109, 105, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(218, 92, 88, 0.5); }
   .btn-danger.disabled, .btn-danger:disabled {
     color: #fff;
-    background-color: #d9534f;
-    border-color: #d9534f; }
+    background-color: #d43f3a;
+    border-color: #d43f3a; }
   .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,
   .show > .btn-danger.dropdown-toggle {
     color: #fff;
-    background-color: #c9302c;
-    border-color: #bf2e29; }
+    background-color: #b42c27;
+    border-color: #a92925; }
     .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,
     .show > .btn-danger.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(223, 109, 105, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(218, 92, 88, 0.5); }
 
 .btn-light {
   color: #212529;
@@ -4179,26 +4179,26 @@ fieldset:disabled a.btn {
 
 .btn-dark {
   color: #fff;
-  background-color: #373a3c;
-  border-color: #373a3c; }
+  background-color: #343a40;
+  border-color: #343a40; }
   .btn-dark:hover {
     color: #fff;
-    background-color: #252728;
-    border-color: #1f2021; }
+    background-color: #23272b;
+    border-color: #1d2124; }
   .btn-dark:focus, .btn-dark.focus {
-    box-shadow: 0 0 0 0.2rem rgba(85, 88, 89, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); }
   .btn-dark.disabled, .btn-dark:disabled {
     color: #fff;
-    background-color: #373a3c;
-    border-color: #373a3c; }
+    background-color: #343a40;
+    border-color: #343a40; }
   .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,
   .show > .btn-dark.dropdown-toggle {
     color: #fff;
-    background-color: #1f2021;
-    border-color: #191a1b; }
+    background-color: #1d2124;
+    border-color: #171a1d; }
     .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,
     .show > .btn-dark.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(85, 88, 89, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); }
 
 .btn-outline-primary {
   color: #1177d1;
@@ -4243,25 +4243,25 @@ fieldset:disabled a.btn {
       box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); }
 
 .btn-outline-success {
-  color: #5cb85c;
-  border-color: #5cb85c; }
+  color: #398439;
+  border-color: #398439; }
   .btn-outline-success:hover {
     color: #fff;
-    background-color: #5cb85c;
-    border-color: #5cb85c; }
+    background-color: #398439;
+    border-color: #398439; }
   .btn-outline-success:focus, .btn-outline-success.focus {
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
   .btn-outline-success.disabled, .btn-outline-success:disabled {
-    color: #5cb85c;
+    color: #398439;
     background-color: transparent; }
   .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,
   .show > .btn-outline-success.dropdown-toggle {
     color: #fff;
-    background-color: #5cb85c;
-    border-color: #5cb85c; }
+    background-color: #398439;
+    border-color: #398439; }
     .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-success.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
 
 .btn-outline-info {
   color: #5bc0de;
@@ -4306,25 +4306,25 @@ fieldset:disabled a.btn {
       box-shadow: 0 0 0 0.2rem rgba(240, 173, 78, 0.5); }
 
 .btn-outline-danger {
-  color: #d9534f;
-  border-color: #d9534f; }
+  color: #d43f3a;
+  border-color: #d43f3a; }
   .btn-outline-danger:hover {
     color: #fff;
-    background-color: #d9534f;
-    border-color: #d9534f; }
+    background-color: #d43f3a;
+    border-color: #d43f3a; }
   .btn-outline-danger:focus, .btn-outline-danger.focus {
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
   .btn-outline-danger.disabled, .btn-outline-danger:disabled {
-    color: #d9534f;
+    color: #d43f3a;
     background-color: transparent; }
   .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,
   .show > .btn-outline-danger.dropdown-toggle {
     color: #fff;
-    background-color: #d9534f;
-    border-color: #d9534f; }
+    background-color: #d43f3a;
+    border-color: #d43f3a; }
     .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-danger.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
 
 .btn-outline-light {
   color: #f8f9fa;
@@ -4348,25 +4348,25 @@ fieldset:disabled a.btn {
       box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); }
 
 .btn-outline-dark {
-  color: #373a3c;
-  border-color: #373a3c; }
+  color: #343a40;
+  border-color: #343a40; }
   .btn-outline-dark:hover {
     color: #fff;
-    background-color: #373a3c;
-    border-color: #373a3c; }
+    background-color: #343a40;
+    border-color: #343a40; }
   .btn-outline-dark:focus, .btn-outline-dark.focus {
-    box-shadow: 0 0 0 0.2rem rgba(55, 58, 60, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
   .btn-outline-dark.disabled, .btn-outline-dark:disabled {
-    color: #373a3c;
+    color: #343a40;
     background-color: transparent; }
   .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,
   .show > .btn-outline-dark.dropdown-toggle {
     color: #fff;
-    background-color: #373a3c;
-    border-color: #373a3c; }
+    background-color: #343a40;
+    border-color: #343a40; }
     .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-dark.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(55, 58, 60, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
 
 .btn-link {
   font-weight: 400;
@@ -4379,7 +4379,7 @@ fieldset:disabled a.btn {
     text-decoration: underline;
     box-shadow: none; }
   .btn-link:disabled, .btn-link.disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none; }
 
 .btn-lg, .btn-group-lg > .btn {
@@ -4456,7 +4456,7 @@ input[type="button"].btn-block {
   padding: 0.5rem 0;
   margin: 0.125rem 0 0;
   font-size: 0.9375rem;
-  color: #373a3c;
+  color: #343a40;
   text-align: left;
   list-style: none;
   background-color: #fff;
@@ -4606,7 +4606,7 @@ input[type="button"].btn-block {
     text-decoration: none;
     background-color: #1177d1; }
   .dropdown-item.disabled, .dropdown-item:disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none;
     background-color: transparent; }
 
@@ -4618,7 +4618,7 @@ input[type="button"].btn-block {
   padding: 0.5rem 1.5rem;
   margin-bottom: 0;
   font-size: 0.8203125rem;
-  color: #868e96;
+  color: #6c757d;
   white-space: nowrap; }
 
 .dropdown-item-text {
@@ -4834,7 +4834,7 @@ input[type="button"].btn-block {
     background-color: #9dcdf7;
     border-color: #9dcdf7; }
   .custom-control-input:disabled ~ .custom-control-label {
-    color: #868e96; }
+    color: #6c757d; }
     .custom-control-input:disabled ~ .custom-control-label::before {
       background-color: #e9ecef; }
 
@@ -4922,7 +4922,7 @@ input[type="button"].btn-block {
   line-height: 1.5;
   color: #495057;
   vertical-align: middle;
-  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23373a3c' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px;
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' 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;
   background-color: #fff;
   border: 1px solid #ced4da;
   border-radius: 0;
@@ -4939,7 +4939,7 @@ input[type="button"].btn-block {
     padding-right: 0.75rem;
     background-image: none; }
   .custom-select:disabled {
-    color: #868e96;
+    color: #6c757d;
     background-color: #e9ecef; }
   .custom-select::-ms-expand {
     display: none; }
@@ -5127,7 +5127,7 @@ input[type="button"].btn-block {
   .nav-link:hover, .nav-link:focus {
     text-decoration: none; }
   .nav-link.disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none;
     cursor: default; }
 
@@ -5140,7 +5140,7 @@ input[type="button"].btn-block {
     .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
       border-color: #e9ecef #e9ecef #dee2e6; }
     .nav-tabs .nav-link.disabled {
-      color: #868e96;
+      color: #6c757d;
       background-color: transparent;
       border-color: transparent; }
   .nav-tabs .nav-link.active,
@@ -5569,7 +5569,7 @@ input[type="button"].btn-block {
   .breadcrumb-item + .breadcrumb-item::before {
     display: inline-block;
     padding-right: 0.5rem;
-    color: #868e96;
+    color: #6c757d;
     content: "/"; }
 
 .breadcrumb-item + .breadcrumb-item:hover::before {
@@ -5579,7 +5579,7 @@ input[type="button"].btn-block {
   text-decoration: none; }
 
 .breadcrumb-item.active {
-  color: #868e96; }
+  color: #6c757d; }
 
 .pagination {
   display: flex;
@@ -5616,7 +5616,7 @@ input[type="button"].btn-block {
   border-color: #1177d1; }
 
 .page-item.disabled .page-link {
-  color: #868e96;
+  color: #6c757d;
   pointer-events: none;
   cursor: auto;
   background-color: #fff;
@@ -5680,13 +5680,13 @@ input[type="button"].btn-block {
 
 .badge-success {
   color: #fff;
-  background-color: #5cb85c; }
+  background-color: #398439; }
   .badge-success[href]:hover, .badge-success[href]:focus {
     color: #fff;
-    background-color: #449d44; }
+    background-color: #2a602a; }
   .badge-success[href]:focus, .badge-success[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
 
 .badge-info {
   color: #212529;
@@ -5710,13 +5710,13 @@ input[type="button"].btn-block {
 
 .badge-danger {
   color: #fff;
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
   .badge-danger[href]:hover, .badge-danger[href]:focus {
     color: #fff;
-    background-color: #c9302c; }
+    background-color: #b42c27; }
   .badge-danger[href]:focus, .badge-danger[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
 
 .badge-light {
   color: #212529;
@@ -5730,13 +5730,13 @@ input[type="button"].btn-block {
 
 .badge-dark {
   color: #fff;
-  background-color: #373a3c; }
+  background-color: #343a40; }
   .badge-dark[href]:hover, .badge-dark[href]:focus {
     color: #fff;
-    background-color: #1f2021; }
+    background-color: #1d2124; }
   .badge-dark[href]:focus, .badge-dark[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(55, 58, 60, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
 
 .jumbotron {
   padding: 2rem 1rem;
@@ -5790,13 +5790,13 @@ input[type="button"].btn-block {
     color: #525557; }
 
 .alert-success {
-  color: #306030;
-  background-color: #def1de;
-  border-color: #d1ebd1; }
+  color: #1e451e;
+  background-color: #d7e6d7;
+  border-color: #c8ddc8; }
   .alert-success hr {
-    border-top-color: #bfe3bf; }
+    border-top-color: #b8d3b8; }
   .alert-success .alert-link {
-    color: #1f3e1f; }
+    color: #0f210f; }
 
 .alert-info {
   color: #2f6473;
@@ -5817,13 +5817,13 @@ input[type="button"].btn-block {
     color: #573e1c; }
 
 .alert-danger {
-  color: #712b29;
-  background-color: #f7dddc;
-  border-color: #f4cfce; }
+  color: #6e211e;
+  background-color: #f6d9d8;
+  border-color: #f3c9c8; }
   .alert-danger hr {
-    border-top-color: #efbbb9; }
+    border-top-color: #eeb4b3; }
   .alert-danger .alert-link {
-    color: #4c1d1b; }
+    color: #461513; }
 
 .alert-light {
   color: #818182;
@@ -5835,11 +5835,11 @@ input[type="button"].btn-block {
     color: #686868; }
 
 .alert-dark {
-  color: #1d1e1f;
-  background-color: #d7d8d8;
-  border-color: #c7c8c8; }
+  color: #1b1e21;
+  background-color: #d6d8d9;
+  border-color: #c6c8ca; }
   .alert-dark hr {
-    border-top-color: #babbbb; }
+    border-top-color: #b9bbbe; }
   .alert-dark .alert-link {
     color: #040505; }
 
@@ -5902,7 +5902,7 @@ input[type="button"].btn-block {
     text-decoration: none;
     background-color: #f8f9fa; }
   .list-group-item-action:active {
-    color: #373a3c;
+    color: #343a40;
     background-color: #e9ecef; }
 
 .list-group-item {
@@ -5915,7 +5915,7 @@ input[type="button"].btn-block {
   .list-group-item:last-child {
     margin-bottom: 0; }
   .list-group-item.disabled, .list-group-item:disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none;
     background-color: #fff; }
   .list-group-item.active {
@@ -6004,15 +6004,15 @@ input[type="button"].btn-block {
     border-color: #6b6e71; }
 
 .list-group-item-success {
-  color: #306030;
-  background-color: #d1ebd1; }
+  color: #1e451e;
+  background-color: #c8ddc8; }
   .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {
-    color: #306030;
-    background-color: #bfe3bf; }
+    color: #1e451e;
+    background-color: #b8d3b8; }
   .list-group-item-success.list-group-item-action.active {
     color: #fff;
-    background-color: #306030;
-    border-color: #306030; }
+    background-color: #1e451e;
+    border-color: #1e451e; }
 
 .list-group-item-info {
   color: #2f6473;
@@ -6037,15 +6037,15 @@ input[type="button"].btn-block {
     border-color: #7d5a29; }
 
 .list-group-item-danger {
-  color: #712b29;
-  background-color: #f4cfce; }
+  color: #6e211e;
+  background-color: #f3c9c8; }
   .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {
-    color: #712b29;
-    background-color: #efbbb9; }
+    color: #6e211e;
+    background-color: #eeb4b3; }
   .list-group-item-danger.list-group-item-action.active {
     color: #fff;
-    background-color: #712b29;
-    border-color: #712b29; }
+    background-color: #6e211e;
+    border-color: #6e211e; }
 
 .list-group-item-light {
   color: #818182;
@@ -6059,15 +6059,15 @@ input[type="button"].btn-block {
     border-color: #818182; }
 
 .list-group-item-dark {
-  color: #1d1e1f;
-  background-color: #c7c8c8; }
+  color: #1b1e21;
+  background-color: #c6c8ca; }
   .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {
-    color: #1d1e1f;
-    background-color: #babbbb; }
+    color: #1b1e21;
+    background-color: #b9bbbe; }
   .list-group-item-dark.list-group-item-action.active {
     color: #fff;
-    background-color: #1d1e1f;
-    border-color: #1d1e1f; }
+    background-color: #1b1e21;
+    border-color: #1b1e21; }
 
 .close {
   float: right;
@@ -6116,7 +6116,7 @@ a.close.disabled {
   display: flex;
   align-items: center;
   padding: 0.25rem 0.75rem;
-  color: #868e96;
+  color: #6c757d;
   background-color: rgba(255, 255, 255, 0.85);
   background-clip: padding-box;
   border-bottom: 1px solid rgba(0, 0, 0, 0.05); }
@@ -6467,7 +6467,7 @@ a.close.disabled {
 
 .popover-body {
   padding: 0.5rem 0.75rem;
-  color: #373a3c; }
+  color: #343a40; }
 
 .carousel {
   position: relative; }
@@ -6693,12 +6693,12 @@ button.bg-secondary:focus {
   background-color: #b1bbc4 !important; }
 
 .bg-success {
-  background-color: #5cb85c !important; }
+  background-color: #398439 !important; }
 
 a.bg-success:hover, a.bg-success:focus,
 button.bg-success:hover,
 button.bg-success:focus {
-  background-color: #449d44 !important; }
+  background-color: #2a602a !important; }
 
 .bg-info {
   background-color: #5bc0de !important; }
@@ -6717,12 +6717,12 @@ button.bg-warning:focus {
   background-color: #ec971f !important; }
 
 .bg-danger {
-  background-color: #d9534f !important; }
+  background-color: #d43f3a !important; }
 
 a.bg-danger:hover, a.bg-danger:focus,
 button.bg-danger:hover,
 button.bg-danger:focus {
-  background-color: #c9302c !important; }
+  background-color: #b42c27 !important; }
 
 .bg-light {
   background-color: #f8f9fa !important; }
@@ -6733,12 +6733,12 @@ button.bg-light:focus {
   background-color: #dae0e5 !important; }
 
 .bg-dark {
-  background-color: #373a3c !important; }
+  background-color: #343a40 !important; }
 
 a.bg-dark:hover, a.bg-dark:focus,
 button.bg-dark:hover,
 button.bg-dark:focus {
-  background-color: #1f2021 !important; }
+  background-color: #1d2124 !important; }
 
 .bg-white {
   background-color: #fff !important; }
@@ -6784,7 +6784,7 @@ button.bg-dark:focus {
   border-color: #ced4da !important; }
 
 .border-success {
-  border-color: #5cb85c !important; }
+  border-color: #398439 !important; }
 
 .border-info {
   border-color: #5bc0de !important; }
@@ -6793,13 +6793,13 @@ button.bg-dark:focus {
   border-color: #f0ad4e !important; }
 
 .border-danger {
-  border-color: #d9534f !important; }
+  border-color: #d43f3a !important; }
 
 .border-light {
   border-color: #f8f9fa !important; }
 
 .border-dark {
-  border-color: #373a3c !important; }
+  border-color: #343a40 !important; }
 
 .border-white {
   border-color: #fff !important; }
@@ -9016,10 +9016,10 @@ a.text-secondary:hover, a.text-secondary:focus {
   color: #a2aeb9 !important; }
 
 .text-success {
-  color: #5cb85c !important; }
+  color: #398439 !important; }
 
 a.text-success:hover, a.text-success:focus {
-  color: #3d8b3d !important; }
+  color: #224f22 !important; }
 
 .text-info {
   color: #5bc0de !important; }
@@ -9034,10 +9034,10 @@ a.text-warning:hover, a.text-warning:focus {
   color: #df8a13 !important; }
 
 .text-danger {
-  color: #d9534f !important; }
+  color: #d43f3a !important; }
 
 a.text-danger:hover, a.text-danger:focus {
-  color: #b52b27 !important; }
+  color: #9f2723 !important; }
 
 .text-light {
   color: #f8f9fa !important; }
@@ -9046,16 +9046,16 @@ a.text-light:hover, a.text-light:focus {
   color: #cbd3da !important; }
 
 .text-dark {
-  color: #373a3c !important; }
+  color: #343a40 !important; }
 
 a.text-dark:hover, a.text-dark:focus {
-  color: #121314 !important; }
+  color: #121416 !important; }
 
 .text-body {
-  color: #373a3c !important; }
+  color: #343a40 !important; }
 
 .text-muted {
-  color: #868e96 !important; }
+  color: #6c757d !important; }
 
 .text-black-50 {
   color: rgba(0, 0, 0, 0.5) !important; }
@@ -9168,9 +9168,9 @@ a.text-dark:hover, a.text-dark:focus {
   background-size: calc(1.5em + 0.75rem + 2px)/2 calc(1.5em + 0.75rem + 2px)/2; }
 
 .tag-default {
-  background-color: #868e96; }
+  background-color: #6c757d; }
   .tag-default[href]:hover, .tag-default[href]:focus {
-    background-color: #6c757d; }
+    background-color: #545b62; }
 
 .tag-primary {
   background-color: #1177d1; }
@@ -9178,9 +9178,9 @@ a.text-dark:hover, a.text-dark:focus {
     background-color: #0d5ca2; }
 
 .tag-success {
-  background-color: #5cb85c; }
+  background-color: #398439; }
   .tag-success[href]:hover, .tag-success[href]:focus {
-    background-color: #449d44; }
+    background-color: #2a602a; }
 
 .tag-info {
   background-color: #5bc0de; }
@@ -9188,14 +9188,14 @@ a.text-dark:hover, a.text-dark:focus {
     background-color: #31b0d5; }
 
 .tag-warning {
-  background-color: #ff7518; }
+  background-color: #f0ad4e; }
   .tag-warning[href]:hover, .tag-warning[href]:focus {
-    background-color: #e45c00; }
+    background-color: #ec971f; }
 
 .tag-danger {
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
   .tag-danger[href]:hover, .tag-danger[href]:focus {
-    background-color: #c9302c; }
+    background-color: #b42c27; }
 
 .custom-select {
   width: auto; }
@@ -9492,7 +9492,7 @@ a.dimmed_text:visited,
 .usersuspended a:visited,
 .dimmed_category,
 .dimmed_category a {
-  color: #868e96; }
+  color: #6c757d; }
 
 .unlist,
 .unlist li,
@@ -9524,7 +9524,7 @@ a.dimmed_text:visited,
 
 .green,
 .notifysuccess {
-  color: #5cb85c; }
+  color: #398439; }
 
 .highlight {
   color: #5bc0de; }
@@ -10043,7 +10043,7 @@ tr.flagged-tag a {
   padding-left: 10px; }
 
 .tag_feed .media .muted a {
-  color: #868e96; }
+  color: #6c757d; }
 
 .tag_cloud {
   text-align: center; }
@@ -10704,7 +10704,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
     .modchoosercontainer .optionscontainer .option .optionactions .optionaction,
     .modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction {
       cursor: pointer;
-      color: #868e96; }
+      color: #6c757d; }
       .modchoosercontainer .optionscontainer .option .optionactions .optionaction i,
       .modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction i {
         margin: 0; }
@@ -10916,10 +10916,10 @@ ul.badges {
   vertical-align: top; }
 
 .connected {
-  color: #5cb85c; }
+  color: #398439; }
 
 .notconnected {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .connecting {
   color: #f0ad4e; }
@@ -10940,7 +10940,7 @@ ul.badges {
   display: inline-block; }
 
 .statusbox.active {
-  background-color: #def1de; }
+  background-color: #d7e6d7; }
 
 .statusbox.inactive {
   background-color: #fcefdc; }
@@ -11188,7 +11188,7 @@ ul {
 .dropdown-item a {
   display: block;
   width: 100%;
-  color: #373a3c; }
+  color: #343a40; }
 
 .dropdown-item:active a {
   color: #fff; }
@@ -11378,6 +11378,30 @@ body.h5p-embed .h5pmessages {
   .toast-wrapper > :first-child {
     margin-top: 1rem; }
 
+.alert-primary a {
+  color: #05233e; }
+
+.alert-secondary a {
+  color: #525557; }
+
+.alert-success a {
+  color: #0f210f; }
+
+.alert-info a {
+  color: #20454f; }
+
+.alert-warning a {
+  color: #573e1c; }
+
+.alert-danger a {
+  color: #461513; }
+
+.alert-light a {
+  color: #686868; }
+
+.alert-dark a {
+  color: #040505; }
+
 .icon {
   font-size: 16px;
   width: 16px;
@@ -11492,15 +11516,15 @@ body.h5p-embed .h5pmessages {
 
 .environmenttable .warn {
   background-color: #fcefdc;
-  color: #ff7518; }
+  color: #f0ad4e; }
 
 .environmenttable .error {
-  background-color: #f7dddc;
-  color: #d9534f; }
+  background-color: #f6d9d8;
+  color: #d43f3a; }
 
 .environmenttable .ok {
-  background-color: #def1de;
-  color: #5cb85c; }
+  background-color: #d7e6d7;
+  color: #398439; }
 
 .path-admin .admintable.environmenttable .name,
 .path-admin .admintable.environmenttable .info,
@@ -11826,25 +11850,25 @@ body.h5p-embed .h5pmessages {
 
 #page-admin-plugins #plugins-control-panel .pluginname .componentname {
   font-size: 0.8203125rem;
-  color: #868e96;
+  color: #6c757d;
   margin-left: 22px; }
 
 #page-admin-plugins #plugins-control-panel .version .versionnumber {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 #page-admin-plugins #plugins-control-panel .uninstall a {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 #page-admin-plugins #plugins-control-panel .notes .label {
   margin-right: 3px; }
 
 #page-admin-plugins #plugins-control-panel .notes .requiredby {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page .page-description {
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page .checkforupdates .singlebutton {
   margin: 5px 0;
@@ -11865,14 +11889,14 @@ body.h5p-embed .h5pmessages {
     margin: 0 3px 0 0; }
 
 #plugins-check-page #plugins-check .requires-ok {
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page #plugins-check .status-missing td,
 #plugins-check-page #plugins-check .status-downgrade td {
-  background-color: #f7dddc; }
+  background-color: #f6d9d8; }
 
 #plugins-check-page #plugins-check .displayname .plugindir {
-  color: #868e96;
+  color: #6c757d;
   font-size: 0.8203125rem; }
 
 #plugins-check-page #plugins-check .requires ul {
@@ -11897,7 +11921,7 @@ body.h5p-embed .h5pmessages {
 
 #plugins-check-page #plugins-check-available-dependencies .displayname .component {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page #plugins-check-available-dependencies .info .actions > div {
   display: inline-block;
@@ -11917,7 +11941,7 @@ body.h5p-embed .h5pmessages {
   margin: 10px 0; }
   #plugins-check-page .pluginupdateinfo.maturity50,
   #plugins-control-panel .pluginupdateinfo.maturity50 {
-    background-color: #f7dddc; }
+    background-color: #f6d9d8; }
   #plugins-check-page .pluginupdateinfo.maturity100, #plugins-check-page .pluginupdateinfo.maturity150,
   #plugins-control-panel .pluginupdateinfo.maturity100,
   #plugins-control-panel .pluginupdateinfo.maturity150 {
@@ -12008,7 +12032,7 @@ body.h5p-embed .h5pmessages {
 
 #page-admin-tasklogs .task-class {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 .blockmovetarget .accesshide {
   position: relative;
@@ -12029,7 +12053,7 @@ body.h5p-embed .h5pmessages {
 
 .block .block-controls .dropdown-toggle {
   /* So that the caret takes the colour of the icon. */
-  color: #373a3c; }
+  color: #343a40; }
 
 [data-region="blocks-column"] {
   width: 360px;
@@ -12269,7 +12293,7 @@ body.h5p-embed .h5pmessages {
     margin-right: 2px; }
 
 .block.invisibleblock .card-title {
-  color: #868e96; }
+  color: #6c757d; }
 
 .navbar {
   max-height: 50px; }
@@ -12431,7 +12455,7 @@ body.h5p-embed .h5pmessages {
     font-size: 0.8em;
     text-align: center; }
   .block .minicalendar td.weekend {
-    color: #868e96; }
+    color: #6c757d; }
   .block .minicalendar td a {
     width: 100%;
     height: 100%;
@@ -12608,7 +12632,7 @@ body:not(.editing) .sitetopic ul.section {
     .section .activity .activityinstance .dimmed .activityicon {
       opacity: .5; }
   .section .activity .stealth {
-    color: #868e96; }
+    color: #6c757d; }
   .section .activity a.stealth,
   .section .activity a.stealth:hover {
     color: #6eb5f3 !important;
@@ -12722,7 +12746,7 @@ body:not(.editing) .sitetopic ul.section {
   margin: 2px 5px 2px 5px; }
 
 .course-content .section-summary .section-summary-activities .activity-count {
-  color: #868e96;
+  color: #6c757d;
   font-size: 0.8203125rem;
   margin: 3px;
   white-space: nowrap;
@@ -12772,7 +12796,7 @@ body:not(.editing) .sitetopic ul.section {
 .course-content ul li.section.hidden .sectionname > span,
 .course-content ul li.section.hidden .content > div.summary,
 .course-content ul li.section.hidden .activity .activityinstance {
-  color: #868e96; }
+  color: #6c757d; }
 
 .course-content ul.topics,
 .course-content ul.weeks {
@@ -13194,9 +13218,9 @@ span.editinstructions {
     color: #a1a1a8;
     margin-right: 2em; }
   #course-category-listings .listitem[data-visible="0"] {
-    color: #868e96; }
+    color: #6c757d; }
     #course-category-listings .listitem[data-visible="0"] > div > a {
-      color: #868e96; }
+      color: #6c757d; }
     #course-category-listings .listitem[data-visible="0"] > div .item-actions .action-show {
       display: inline; }
     #course-category-listings .listitem[data-visible="0"] > div .item-actions .action-hide {
@@ -13311,7 +13335,7 @@ span.editinstructions {
   #course-category-listings .listing-pagination-totals {
     text-align: center; }
     #course-category-listings .listing-pagination-totals.dimmed {
-      color: #868e96;
+      color: #6c757d;
       margin: 0.4rem 1rem 0.45rem; }
   #course-category-listings .select-a-category .notifymessage,
   #course-category-listings .select-a-category .alert {
@@ -14025,7 +14049,7 @@ a.ygtvspacer:hover {
   background-color: #ebebe4; }
 
 .fitem.disabled .fp-btn-choose {
-  color: #868e96; }
+  color: #6c757d; }
 
 .fitem.disabled .filepicker-filelist .filepicker-filename {
   display: none; }
@@ -14595,7 +14619,7 @@ a.ygtvspacer:hover {
       direction: ltr; }
   .message-app .matchtext {
     background-color: #b5d9f9;
-    color: #373a3c;
+    color: #343a40;
     height: 1.5rem; }
   .message-app .contact-status {
     position: absolute;
@@ -14606,7 +14630,7 @@ a.ygtvspacer:hover {
     border-radius: 50%; }
     .message-app .contact-status.online {
       border: 1px solid #fff;
-      background-color: #5cb85c; }
+      background-color: #398439; }
   .message-app .message p {
     margin: 0; }
   .message-app .clickable {
@@ -15015,14 +15039,14 @@ body.path-question-type {
   max-width: 100%; }
 
 .que .comment {
-  color: #306030;
-  background-color: #def1de;
-  border-color: #d1ebd1;
+  color: #1e451e;
+  background-color: #d7e6d7;
+  border-color: #c8ddc8;
   /* stylelint-disable-line max-line-length */ }
   .que .comment hr {
-    border-top-color: #bfe3bf; }
+    border-top-color: #b8d3b8; }
   .que .comment .alert-link {
-    color: #1f3e1f; }
+    color: #0f210f; }
 
 .que .ablock {
   margin: 0.7em 0 0.3em 0; }
@@ -15040,19 +15064,19 @@ body.path-question-type {
   margin: 0 0 0.5em; }
 
 .que .correctness.correct {
-  background-color: #5cb85c; }
+  background-color: #398439; }
 
 .que .correctness.partiallycorrect {
-  background-color: #ff7518; }
+  background-color: #f0ad4e; }
 
 .que .correctness.notanswered, .que .correctness.incorrect {
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
 
 .que .qtext {
   margin-bottom: 1.5em; }
 
 .que .validationerror {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .que .grading,
 .que .comment,
@@ -15148,7 +15172,7 @@ body.jsenabled .questionflag input[type=checkbox] {
     margin: 0; }
 
 #page-mod-quiz-edit .questionbankwindow div.header .title {
-  color: #373a3c; }
+  color: #343a40; }
 
 #page-mod-quiz-edit div.container div.generalbox {
   background-color: transparent;
@@ -15572,9 +15596,9 @@ body.path-question-type .mform fieldset.hidden {
 
 #adminsettings span.error {
   display: inline-block;
-  border: 1px solid #f4cfce;
+  border: 1px solid #f3c9c8;
   border-radius: 4px;
-  background-color: #f7dddc;
+  background-color: #f6d9d8;
   padding: 4px;
   margin-bottom: 4px; }
 
@@ -15613,7 +15637,7 @@ body.path-question-type .mform fieldset.hidden {
   display: none; }
 
 #adminsettings .error {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .mform ul.file-list {
   padding: 0;
@@ -15633,7 +15657,7 @@ input#id_externalurl {
 
 .form-defaultinfo,
 .form-label .form-shortname {
-  color: #868e96; }
+  color: #6c757d; }
 
 .form-label .form-shortname {
   font-size: 0.703125rem;
@@ -15646,10 +15670,10 @@ input#id_externalurl {
   margin-left: 0.5rem; }
 
 .formsettingheading .form-horizontal {
-  color: #868e96; }
+  color: #6c757d; }
 
 .no-felement.fstatic {
-  color: #868e96;
+  color: #6c757d;
   padding-top: 5px; }
 
 .no-fitem .fstaticlabel {
@@ -15802,7 +15826,7 @@ fieldset.coursesearchbox label {
   padding: 0.2em;
   margin: 0;
   cursor: pointer;
-  color: #373a3c; }
+  color: #343a40; }
 
 .form-autocomplete-suggestions li:hover {
   background-color: #3f9def;
@@ -15813,7 +15837,7 @@ fieldset.coursesearchbox label {
   color: #495057; }
 
 .form-autocomplete-downarrow {
-  color: #373a3c;
+  color: #343a40;
   top: 0.2rem;
   right: 0.5rem;
   cursor: pointer; }
@@ -15901,18 +15925,18 @@ textarea[data-auto-rows] {
   .has-danger .editor_atto_content.form-control-danger .form-check-label,
   .has-danger .editor_atto_content.form-control-danger .form-check-inline,
   .has-danger .editor_atto_content.form-control-danger .custom-control {
-    color: #d9534f; }
+    color: #d43f3a; }
   .has-danger .editor_atto_content.form-control .form-control,
   .has-danger .editor_atto_content.form-control-danger .form-control {
-    border-color: #d9534f; }
+    border-color: #d43f3a; }
   .has-danger .editor_atto_content.form-control .input-group-addon,
   .has-danger .editor_atto_content.form-control-danger .input-group-addon {
-    color: #d9534f;
-    border-color: #d9534f;
-    background-color: #fdf7f7; }
+    color: #d43f3a;
+    border-color: #d43f3a;
+    background-color: #f9e2e1; }
   .has-danger .editor_atto_content.form-control .form-control-feedback,
   .has-danger .editor_atto_content.form-control-danger .form-control-feedback {
-    color: #d9534f; }
+    color: #d43f3a; }
 
 [data-filetypesbrowserbody] [aria-expanded="false"] > [role="group"],
 [data-filetypesbrowserbody] [aria-expanded="false"] [data-filetypesbrowserfeature="hideifcollapsed"],
@@ -16032,10 +16056,10 @@ select {
   font-weight: inherit; }
 
 .path-mod-forum .subscriptionmode {
-  color: #373a3c; }
+  color: #343a40; }
 
 .path-mod-forum .activesetting {
-  color: #373a3c;
+  color: #343a40;
   font-weight: bold; }
 
 .discussion-settings-container .custom-select {
@@ -16815,7 +16839,7 @@ div#dock {
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.correct .trafficlight {
   background-image: url([[pix:theme|mod/quiz/checkmark]]);
-  background-color: #5cb85c; }
+  background-color: #398439; }
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.blocked .trafficlight {
   background-image: url([[pix:core|t/locked]]);
@@ -16823,16 +16847,16 @@ div#dock {
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.notanswered .trafficlight,
 .path-mod-quiz #mod_quiz_navblock .qnbutton.incorrect .trafficlight {
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.partiallycorrect .trafficlight {
   background-image: url([[pix:theme|mod/quiz/whitecircle]]);
-  background-color: #ff7518; }
+  background-color: #f0ad4e; }
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.complete .trafficlight,
 .path-mod-quiz #mod_quiz_navblock .qnbutton.answersaved .trafficlight,
 .path-mod-quiz #mod_quiz_navblock .qnbutton.requiresgrading .trafficlight {
-  background-color: #868e96; }
+  background-color: #6c757d; }
 
 #page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmarkcontainer form input {
   height: 1.4em;
@@ -17128,14 +17152,14 @@ div#dock {
     content: ""; }
 
 .path-backup .notification.dependencies_enforced {
-  color: #d9534f;
+  color: #d43f3a;
   font-weight: bold; }
 
 .path-backup .backup_progress {
   margin-top: 1rem;
   margin-bottom: 1rem; }
   .path-backup .backup_progress .backup_stage {
-    color: #868e96; }
+    color: #6c757d; }
     .path-backup .backup_progress .backup_stage.backup_stage_current {
       font-weight: bold;
       color: inherit; }
@@ -17144,7 +17168,7 @@ div#dock {
   color: inherit; }
 
 #page-backup-restore .filealiasesfailures {
-  background-color: #f7dddc; }
+  background-color: #f6d9d8; }
   #page-backup-restore .filealiasesfailures .aliaseslist {
     background-color: #fff; }
 
@@ -17192,7 +17216,7 @@ div#dock {
 .generaltable {
   width: 100%;
   margin-bottom: 1rem;
-  color: #373a3c; }
+  color: #343a40; }
   .generaltable th,
   .generaltable td {
     padding: 0.75rem;
@@ -17209,7 +17233,7 @@ div#dock {
   .generaltable.table-sm td {
     padding: 0.3rem; }
   .generaltable tbody tr:hover {
-    color: #373a3c;
+    color: #343a40;
     background-color: rgba(0, 0, 0, 0.075); }
 
 table caption {
@@ -18307,7 +18331,7 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont
   height: auto; }
 
 .text-error {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .btn-default {
   color: #212529;
@@ -18343,26 +18367,26 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont
   vertical-align: baseline;
   transition: color 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;
   color: #fff;
-  background-color: #868e96; }
+  background-color: #6c757d; }
   @media (prefers-reduced-motion: reduce) {
     .label {
       transition: none; } }
   .label[href]:hover, .label[href]:focus {
     color: #fff;
-    background-color: #6c757d; }
+    background-color: #545b62; }
   .label[href]:focus, .label[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }
 
 .label-success {
   color: #fff;
-  background-color: #5cb85c; }
+  background-color: #398439; }
   .label-success[href]:hover, .label-success[href]:focus {
     color: #fff;
-    background-color: #449d44; }
+    background-color: #2a602a; }
   .label-success[href]:focus, .label-success[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
 
 .label-info {
   color: #212529;
@@ -18375,24 +18399,24 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont
     box-shadow: 0 0 0 0.2rem rgba(91, 192, 222, 0.5); }
 
 .label-warning {
-  color: #fff;
-  background-color: #ff7518; }
+  color: #212529;
+  background-color: #f0ad4e; }
   .label-warning[href]:hover, .label-warning[href]:focus {
-    color: #fff;
-    background-color: #e45c00; }
+    color: #212529;
+    background-color: #ec971f; }
   .label-warning[href]:focus, .label-warning[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(255, 117, 24, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(240, 173, 78, 0.5); }
 
 .label-important {
   color: #fff;
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
   .label-important[href]:hover, .label-important[href]:focus {
     color: #fff;
-    background-color: #c9302c; }
+    background-color: #b42c27; }
   .label-important[href]:focus, .label-important[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
 
 .pull-left {
   float: left !important;
@@ -18458,26 +18482,68 @@ body {
   border-radius: 50%; }
 
 .btn-outline-secondary {
-  color: #868e96;
-  border-color: #868e96;
-  border-color: #dee2e6; }
+  color: #6c757d;
+  border-color: #6c757d;
+  border-color: #6c757d; }
   .btn-outline-secondary:hover {
     color: #fff;
-    background-color: #868e96;
-    border-color: #868e96; }
+    background-color: #6c757d;
+    border-color: #6c757d; }
   .btn-outline-secondary:focus, .btn-outline-secondary.focus {
-    box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }
   .btn-outline-secondary.disabled, .btn-outline-secondary:disabled {
-    color: #868e96;
+    color: #6c757d;
     background-color: transparent; }
   .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,
   .show > .btn-outline-secondary.dropdown-toggle {
     color: #fff;
-    background-color: #868e96;
-    border-color: #868e96; }
+    background-color: #6c757d;
+    border-color: #6c757d; }
     .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-secondary.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }
+
+.btn-outline-info {
+  color: #1f7e9a;
+  border-color: #1f7e9a; }
+  .btn-outline-info:hover {
+    color: #fff;
+    background-color: #1f7e9a;
+    border-color: #1f7e9a; }
+  .btn-outline-info:focus, .btn-outline-info.focus {
+    box-shadow: 0 0 0 0.2rem rgba(31, 126, 154, 0.5); }
+  .btn-outline-info.disabled, .btn-outline-info:disabled {
+    color: #1f7e9a;
+    background-color: transparent; }
+  .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,
+  .show > .btn-outline-info.dropdown-toggle {
+    color: #fff;
+    background-color: #1f7e9a;
+    border-color: #1f7e9a; }
+    .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,
+    .show > .btn-outline-info.dropdown-toggle:focus {
+      box-shadow: 0 0 0 0.2rem rgba(31, 126, 154, 0.5); }
+
+.btn-outline-warning {
+  color: #a6670e;
+  border-color: #a6670e; }
+  .btn-outline-warning:hover {
+    color: #fff;
+    background-color: #a6670e;
+    border-color: #a6670e; }
+  .btn-outline-warning:focus, .btn-outline-warning.focus {
+    box-shadow: 0 0 0 0.2rem rgba(166, 103, 14, 0.5); }
+  .btn-outline-warning.disabled, .btn-outline-warning:disabled {
+    color: #a6670e;
+    background-color: transparent; }
+  .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,
+  .show > .btn-outline-warning.dropdown-toggle {
+    color: #fff;
+    background-color: #a6670e;
+    border-color: #a6670e; }
+    .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,
+    .show > .btn-outline-warning.dropdown-toggle:focus {
+      box-shadow: 0 0 0 0.2rem rgba(166, 103, 14, 0.5); }
 
 .bg-gray {
   background-color: #e9ecef !important; }
index bd70e4a..e6fef27 100644 (file)
@@ -66,7 +66,7 @@
         {{/showdivider}}
         {{#action}}
             <li>
-                <a class="list-group-item list-group-item-action {{#isactive}}active{{/isactive}}" href="{{{action}}}" data-key="{{key}}" data-isexpandable="{{isexpandable}}" data-indent="{{get_indent}}" data-showdivider="{{showdivider}}" data-type="{{type}}" data-nodetype="{{nodetype}}" data-collapse="{{collapse}}" data-forceopen="{{forceopen}}" data-isactive="{{isactive}}" data-hidden="{{hidden}}" data-preceedwithhr="{{preceedwithhr}}" {{#parent.key}}data-parent-key="{{.}}"{{/parent.key}}>
+                <a class="list-group-item list-group-item-action {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{action}}}" data-key="{{key}}" data-isexpandable="{{isexpandable}}" data-indent="{{get_indent}}" data-showdivider="{{showdivider}}" data-type="{{type}}" data-nodetype="{{nodetype}}" data-collapse="{{collapse}}" data-forceopen="{{forceopen}}" data-isactive="{{isactive}}" data-hidden="{{hidden}}" data-preceedwithhr="{{preceedwithhr}}" {{#parent.key}}data-parent-key="{{.}}"{{/parent.key}}>
                     <div class="ml-{{get_indent}}">
                         <div class="media">
                             {{#icon.pix}}
@@ -82,7 +82,7 @@
         {{/action}}
         {{^action}}
             <li>
-                <div class="list-group-item" data-key="{{key}}" data-isexpandable="{{isexpandable}}" data-indent="{{get_indent}}" data-showdivider="{{showdivider}}" data-type="{{type}}" data-nodetype="{{nodetype}}" data-collapse="{{collapse}}" data-forceopen="{{forceopen}}" data-isactive="{{isactive}}" data-hidden="{{hidden}}" data-preceedwithhr="{{preceedwithhr}}" {{#parent.key}}data-parent-key="{{.}}"{{/parent.key}}>
+                <div class="list-group-item {{#classes}}{{.}} {{/classes}}" data-key="{{key}}" data-isexpandable="{{isexpandable}}" data-indent="{{get_indent}}" data-showdivider="{{showdivider}}" data-type="{{type}}" data-nodetype="{{nodetype}}" data-collapse="{{collapse}}" data-forceopen="{{forceopen}}" data-isactive="{{isactive}}" data-hidden="{{hidden}}" data-preceedwithhr="{{preceedwithhr}}" {{#parent.key}}data-parent-key="{{.}}"{{/parent.key}}>
                     <div class="ml-{{get_indent}}">
                         <div class="media">
                             {{#icon.pix}}
index 4344355..4059751 100644 (file)
@@ -1,14 +1,13 @@
 // Bootstrap variables
-
 $white:    #fff !default;
 $gray-100: #f8f9fa !default;
 $gray-200: #e9ecef !default;
 $gray-300: #dee2e6 !default;
 $gray-400: #ced4da !default;
 $gray-500: #adb5bd !default;
-$gray-600: #868e96 !default;
+$gray-600: #6c757d !default;
 $gray-700: #495057 !default;
-$gray-800: #373a3c !default;
+$gray-800: #343a40 !default;
 $gray-900: #212529 !default;
 $black:    #000 !default;
 
@@ -16,21 +15,22 @@ $blue:    #1177d1 !default;
 $indigo:  #6610f2 !default;
 $purple:  #613d7c !default;
 $pink:    #e83e8c !default;
-$red:     #d9534f !default;
+$red:     #d43f3a !default;
 $orange:  #f0ad4e !default;
 $yellow:  #ff7518 !default;
-$green:   #5cb85c !default;
+$green:   #398439 !default;
 $teal:    #20c997 !default;
 $cyan:    #5bc0de !default;
 
 $primary:       $blue !default;
-$secondary:     $gray-800 !default;
 $success:       $green !default;
 $info:          $cyan !default;
-$warning:       $yellow !default;
+$warning:       $orange !default;
 $danger:        $red !default;
-$light:         $gray-100 !default;
-$dark:          $gray-800 !default;
+$secondary:     $gray-400 !default;
+
+$info-outline:    #1f7e9a;
+$warning-outline: #a6670e;
 
 // Options
 $enable-rounded: true !default;
@@ -64,13 +64,11 @@ $card-group-margin: .25rem;
 $theme-colors: () !default;
 $theme-colors: map-merge((
     primary: $primary,
-    secondary: $gray-200,
+    secondary: $secondary,
     success: $success,
     info: $info,
-    warning: $orange,
+    warning: $warning,
     danger: $danger,
-    light: $gray-100,
-    dark: $gray-800
 ), $theme-colors);
 // stylelint-enable
 
@@ -110,5 +108,13 @@ $theme-colors: map-merge((
 // for an outline button.
 .btn-outline-secondary {
     @include button-outline-variant($gray-600);
-    border-color: $border-color;
-}
\ No newline at end of file
+    border-color: $gray-600;
+}
+
+.btn-outline-info {
+    @include button-outline-variant($info-outline);
+}
+
+.btn-outline-warning {
+    @include button-outline-variant($warning-outline);
+}
index 11f75c1..a360666 100644 (file)
   --indigo: #6610f2;
   --purple: #613d7c;
   --pink: #e83e8c;
-  --red: #d9534f;
+  --red: #d43f3a;
   --orange: #f0ad4e;
   --yellow: #ff7518;
-  --green: #5cb85c;
+  --green: #398439;
   --teal: #20c997;
   --cyan: #5bc0de;
   --white: #fff;
-  --gray: #868e96;
-  --gray-dark: #373a3c;
+  --gray: #6c757d;
+  --gray-dark: #343a40;
   --primary: #1177d1;
-  --secondary: #e9ecef;
-  --success: #5cb85c;
+  --secondary: #ced4da;
+  --success: #398439;
   --info: #5bc0de;
   --warning: #f0ad4e;
-  --danger: #d9534f;
+  --danger: #d43f3a;
   --light: #f8f9fa;
-  --dark: #373a3c;
+  --dark: #343a40;
   --breakpoint-xs: 0;
   --breakpoint-sm: 576px;
   --breakpoint-md: 768px;
@@ -2382,7 +2382,7 @@ body {
   font-size: 0.9375rem;
   font-weight: 400;
   line-height: 1.5;
-  color: #373a3c;
+  color: #343a40;
   text-align: left;
   background-color: #fff; }
 
@@ -2503,7 +2503,7 @@ table {
 caption {
   padding-top: 0.75rem;
   padding-bottom: 0.75rem;
-  color: #868e96;
+  color: #6c757d;
   text-align: left;
   caption-side: bottom; }
 
@@ -2712,7 +2712,7 @@ mark,
 .blockquote-footer {
   display: block;
   font-size: 80%;
-  color: #868e96; }
+  color: #6c757d; }
   .blockquote-footer::before {
     content: "\2014\00A0"; }
 
@@ -2737,7 +2737,7 @@ mark,
 
 .figure-caption {
   font-size: 90%;
-  color: #868e96; }
+  color: #6c757d; }
 
 code {
   font-size: 87.5%;
@@ -3360,7 +3360,7 @@ pre {
 .table {
   width: 100%;
   margin-bottom: 1rem;
-  color: #373a3c; }
+  color: #343a40; }
   .table th,
   .table td {
     padding: 0.75rem;
@@ -3395,7 +3395,7 @@ pre {
   background-color: rgba(0, 0, 0, 0.05); }
 
 .table-hover tbody tr:hover {
-  color: #373a3c;
+  color: #343a40;
   background-color: rgba(0, 0, 0, 0.075); }
 
 .table-primary,
@@ -3418,36 +3418,36 @@ pre {
 .table-secondary,
 .table-secondary > th,
 .table-secondary > td {
-  background-color: #f9fafb; }
+  background-color: #f1f3f5; }
 
 .table-secondary th,
 .table-secondary td,
 .table-secondary thead th,
 .table-secondary tbody + tbody {
-  border-color: #f4f5f7; }
+  border-color: #e6e9ec; }
 
 .table-hover .table-secondary:hover {
-  background-color: #eaedf1; }
+  background-color: #e2e6ea; }
   .table-hover .table-secondary:hover > td,
   .table-hover .table-secondary:hover > th {
-    background-color: #eaedf1; }
+    background-color: #e2e6ea; }
 
 .table-success,
 .table-success > th,
 .table-success > td {
-  background-color: #d1ebd1; }
+  background-color: #c8ddc8; }
 
 .table-success th,
 .table-success td,
 .table-success thead th,
 .table-success tbody + tbody {
-  border-color: #aadaaa; }
+  border-color: #98bf98; }
 
 .table-hover .table-success:hover {
-  background-color: #bfe3bf; }
+  background-color: #b8d3b8; }
   .table-hover .table-success:hover > td,
   .table-hover .table-success:hover > th {
-    background-color: #bfe3bf; }
+    background-color: #b8d3b8; }
 
 .table-info,
 .table-info > th,
@@ -3486,19 +3486,19 @@ pre {
 .table-danger,
 .table-danger > th,
 .table-danger > td {
-  background-color: #f4cfce; }
+  background-color: #f3c9c8; }
 
 .table-danger th,
 .table-danger td,
 .table-danger thead th,
 .table-danger tbody + tbody {
-  border-color: #eba6a3; }
+  border-color: #e99b99; }
 
 .table-hover .table-danger:hover {
-  background-color: #efbbb9; }
+  background-color: #eeb4b3; }
   .table-hover .table-danger:hover > td,
   .table-hover .table-danger:hover > th {
-    background-color: #efbbb9; }
+    background-color: #eeb4b3; }
 
 .table-light,
 .table-light > th,
@@ -3520,19 +3520,19 @@ pre {
 .table-dark,
 .table-dark > th,
 .table-dark > td {
-  background-color: #c7c8c8; }
+  background-color: #c6c8ca; }
 
 .table-dark th,
 .table-dark td,
 .table-dark thead th,
 .table-dark tbody + tbody {
-  border-color: #97999a; }
+  border-color: #95999c; }
 
 .table-hover .table-dark:hover {
-  background-color: #babbbb; }
+  background-color: #b9bbbe; }
   .table-hover .table-dark:hover > td,
   .table-hover .table-dark:hover > th {
-    background-color: #babbbb; }
+    background-color: #b9bbbe; }
 
 .table-active,
 .table-active > th,
@@ -3547,8 +3547,8 @@ pre {
 
 .table .thead-dark th {
   color: #fff;
-  background-color: #373a3c;
-  border-color: #494d50; }
+  background-color: #343a40;
+  border-color: #454d55; }
 
 .table .thead-light th {
   color: #495057;
@@ -3557,11 +3557,11 @@ pre {
 
 .table-dark {
   color: #fff;
-  background-color: #373a3c; }
+  background-color: #343a40; }
   .table-dark th,
   .table-dark td,
   .table-dark thead th {
-    border-color: #494d50; }
+    border-color: #454d55; }
   .table-dark.table-bordered {
     border: 0; }
   .table-dark.table-striped tbody tr:nth-of-type(odd) {
@@ -3641,7 +3641,7 @@ pre {
     outline: 0;
     box-shadow: 0 0 0 0.2rem rgba(17, 119, 209, 0.25); }
   .form-control::placeholder {
-    color: #868e96;
+    color: #6c757d;
     opacity: 1; }
   .form-control:disabled, .form-control[readonly] {
     background-color: #e9ecef;
@@ -3682,7 +3682,7 @@ select.form-control:focus::-ms-value {
   padding-bottom: 0.375rem;
   margin-bottom: 0;
   line-height: 1.5;
-  color: #373a3c;
+  color: #343a40;
   background-color: transparent;
   border: solid transparent;
   border-width: 1px 0; }
@@ -3737,7 +3737,7 @@ textarea.form-control {
   margin-top: 0.3rem;
   margin-left: -1.25rem; }
   .form-check-input:disabled ~ .form-check-label {
-    color: #868e96; }
+    color: #6c757d; }
 
 .form-check-label {
   margin-bottom: 0; }
@@ -3758,7 +3758,7 @@ textarea.form-control {
   width: 100%;
   margin-top: 0.25rem;
   font-size: 80%;
-  color: #5cb85c; }
+  color: #398439; }
 
 .valid-tooltip {
   position: absolute;
@@ -3771,19 +3771,19 @@ textarea.form-control {
   font-size: 0.8203125rem;
   line-height: 1.5;
   color: #fff;
-  background-color: rgba(92, 184, 92, 0.9);
+  background-color: rgba(57, 132, 57, 0.9);
   border-radius: 0.25rem; }
 
 .was-validated .form-control:valid, .form-control.is-valid {
-  border-color: #5cb85c;
+  border-color: #398439;
   padding-right: calc(1.5em + 0.75rem);
-  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23398439' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
   background-repeat: no-repeat;
   background-position: center right calc(0.375em + 0.1875rem);
   background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .form-control:valid:focus, .form-control.is-valid:focus {
-    border-color: #5cb85c;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+    border-color: #398439;
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
   .was-validated .form-control:valid ~ .valid-feedback,
   .was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,
   .form-control.is-valid ~ .valid-tooltip {
@@ -3794,12 +3794,12 @@ textarea.form-control {
   background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); }
 
 .was-validated .custom-select:valid, .custom-select.is-valid {
-  border-color: #5cb85c;
+  border-color: #398439;
   padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);
-  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23373a3c' 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' viewBox='0 0 8 8'%3e%3cpath fill='%235cb85c' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' 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' viewBox='0 0 8 8'%3e%3cpath fill='%23398439' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {
-    border-color: #5cb85c;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+    border-color: #398439;
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
   .was-validated .custom-select:valid ~ .valid-feedback,
   .was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback,
   .custom-select.is-valid ~ .valid-tooltip {
@@ -3811,7 +3811,7 @@ textarea.form-control {
   display: block; }
 
 .was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {
-  color: #5cb85c; }
+  color: #398439; }
 
 .was-validated .form-check-input:valid ~ .valid-feedback,
 .was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,
@@ -3819,9 +3819,9 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {
-  color: #5cb85c; }
+  color: #398439; }
   .was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {
-    border-color: #5cb85c; }
+    border-color: #398439; }
 
 .was-validated .custom-control-input:valid ~ .valid-feedback,
 .was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,
@@ -3829,17 +3829,17 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {
-  border-color: #80c780;
-  background-color: #80c780; }
+  border-color: #48a848;
+  background-color: #48a848; }
 
 .was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {
-  box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+  box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
 
 .was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {
-  border-color: #5cb85c; }
+  border-color: #398439; }
 
 .was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {
-  border-color: #5cb85c; }
+  border-color: #398439; }
 
 .was-validated .custom-file-input:valid ~ .valid-feedback,
 .was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,
@@ -3847,15 +3847,15 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {
-  border-color: #5cb85c;
-  box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.25); }
+  border-color: #398439;
+  box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.25); }
 
 .invalid-feedback {
   display: none;
   width: 100%;
   margin-top: 0.25rem;
   font-size: 80%;
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .invalid-tooltip {
   position: absolute;
@@ -3868,19 +3868,19 @@ textarea.form-control {
   font-size: 0.8203125rem;
   line-height: 1.5;
   color: #fff;
-  background-color: rgba(217, 83, 79, 0.9);
+  background-color: rgba(212, 63, 58, 0.9);
   border-radius: 0.25rem; }
 
 .was-validated .form-control:invalid, .form-control.is-invalid {
-  border-color: #d9534f;
+  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' 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");
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23d43f3a' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d43f3a' 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");
   background-repeat: no-repeat;
   background-position: center right calc(0.375em + 0.1875rem);
   background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
-    border-color: #d9534f;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
   .was-validated .form-control:invalid ~ .invalid-feedback,
   .was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,
   .form-control.is-invalid ~ .invalid-tooltip {
@@ -3891,12 +3891,12 @@ textarea.form-control {
   background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); }
 
 .was-validated .custom-select:invalid, .custom-select.is-invalid {
-  border-color: #d9534f;
+  border-color: #d43f3a;
   padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);
-  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23373a3c' 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' 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") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' 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' fill='%23d43f3a' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d43f3a' 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") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); }
   .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {
-    border-color: #d9534f;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+    border-color: #d43f3a;
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
   .was-validated .custom-select:invalid ~ .invalid-feedback,
   .was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback,
   .custom-select.is-invalid ~ .invalid-tooltip {
@@ -3908,7 +3908,7 @@ textarea.form-control {
   display: block; }
 
 .was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .was-validated .form-check-input:invalid ~ .invalid-feedback,
 .was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,
@@ -3916,9 +3916,9 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {
-  color: #d9534f; }
+  color: #d43f3a; }
   .was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {
-    border-color: #d9534f; }
+    border-color: #d43f3a; }
 
 .was-validated .custom-control-input:invalid ~ .invalid-feedback,
 .was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,
@@ -3926,17 +3926,17 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {
-  border-color: #e27c79;
-  background-color: #e27c79; }
+  border-color: #dd6864;
+  background-color: #dd6864; }
 
 .was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {
-  box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
 
 .was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {
-  border-color: #d9534f; }
+  border-color: #d43f3a; }
 
 .was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {
-  border-color: #d9534f; }
+  border-color: #d43f3a; }
 
 .was-validated .custom-file-input:invalid ~ .invalid-feedback,
 .was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,
@@ -3944,8 +3944,8 @@ textarea.form-control {
   display: block; }
 
 .was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {
-  border-color: #d9534f;
-  box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.25); }
+  border-color: #d43f3a;
+  box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.25); }
 
 .form-inline {
   display: flex;
@@ -3995,7 +3995,7 @@ textarea.form-control {
 .btn {
   display: inline-block;
   font-weight: 400;
-  color: #373a3c;
+  color: #343a40;
   text-align: center;
   vertical-align: middle;
   user-select: none;
@@ -4010,7 +4010,7 @@ textarea.form-control {
     .btn {
       transition: none; } }
   .btn:hover {
-    color: #373a3c;
+    color: #343a40;
     text-decoration: none; }
   .btn:focus, .btn.focus {
     outline: 0;
@@ -4047,49 +4047,49 @@ fieldset:disabled a.btn {
 
 .btn-secondary {
   color: #212529;
-  background-color: #e9ecef;
-  border-color: #e9ecef; }
+  background-color: #ced4da;
+  border-color: #ced4da; }
   .btn-secondary:hover {
     color: #212529;
-    background-color: #d3d9df;
-    border-color: #cbd3da; }
+    background-color: #b8c1ca;
+    border-color: #b1bbc4; }
   .btn-secondary:focus, .btn-secondary.focus {
-    box-shadow: 0 0 0 0.2rem rgba(203, 206, 209, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(180, 186, 191, 0.5); }
   .btn-secondary.disabled, .btn-secondary:disabled {
     color: #212529;
-    background-color: #e9ecef;
-    border-color: #e9ecef; }
+    background-color: #ced4da;
+    border-color: #ced4da; }
   .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,
   .show > .btn-secondary.dropdown-toggle {
     color: #212529;
-    background-color: #cbd3da;
-    border-color: #c4ccd4; }
+    background-color: #b1bbc4;
+    border-color: #aab4bf; }
     .btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,
     .show > .btn-secondary.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(203, 206, 209, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(180, 186, 191, 0.5); }
 
 .btn-success {
   color: #fff;
-  background-color: #5cb85c;
-  border-color: #5cb85c; }
+  background-color: #398439;
+  border-color: #398439; }
   .btn-success:hover {
     color: #fff;
-    background-color: #48a648;
-    border-color: #449d44; }
+    background-color: #2d692d;
+    border-color: #2a602a; }
   .btn-success:focus, .btn-success.focus {
-    box-shadow: 0 0 0 0.2rem rgba(116, 195, 116, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(87, 150, 87, 0.5); }
   .btn-success.disabled, .btn-success:disabled {
     color: #fff;
-    background-color: #5cb85c;
-    border-color: #5cb85c; }
+    background-color: #398439;
+    border-color: #398439; }
   .btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,
   .show > .btn-success.dropdown-toggle {
     color: #fff;
-    background-color: #449d44;
-    border-color: #409440; }
+    background-color: #2a602a;
+    border-color: #265726; }
     .btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,
     .show > .btn-success.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(116, 195, 116, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(87, 150, 87, 0.5); }
 
 .btn-info {
   color: #212529;
@@ -4139,26 +4139,26 @@ fieldset:disabled a.btn {
 
 .btn-danger {
   color: #fff;
-  background-color: #d9534f;
-  border-color: #d9534f; }
+  background-color: #d43f3a;
+  border-color: #d43f3a; }
   .btn-danger:hover {
     color: #fff;
-    background-color: #d23430;
-    border-color: #c9302c; }
+    background-color: #be2e2a;
+    border-color: #b42c27; }
   .btn-danger:focus, .btn-danger.focus {
-    box-shadow: 0 0 0 0.2rem rgba(223, 109, 105, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(218, 92, 88, 0.5); }
   .btn-danger.disabled, .btn-danger:disabled {
     color: #fff;
-    background-color: #d9534f;
-    border-color: #d9534f; }
+    background-color: #d43f3a;
+    border-color: #d43f3a; }
   .btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,
   .show > .btn-danger.dropdown-toggle {
     color: #fff;
-    background-color: #c9302c;
-    border-color: #bf2e29; }
+    background-color: #b42c27;
+    border-color: #a92925; }
     .btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,
     .show > .btn-danger.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(223, 109, 105, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(218, 92, 88, 0.5); }
 
 .btn-light {
   color: #212529;
@@ -4185,26 +4185,26 @@ fieldset:disabled a.btn {
 
 .btn-dark {
   color: #fff;
-  background-color: #373a3c;
-  border-color: #373a3c; }
+  background-color: #343a40;
+  border-color: #343a40; }
   .btn-dark:hover {
     color: #fff;
-    background-color: #252728;
-    border-color: #1f2021; }
+    background-color: #23272b;
+    border-color: #1d2124; }
   .btn-dark:focus, .btn-dark.focus {
-    box-shadow: 0 0 0 0.2rem rgba(85, 88, 89, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); }
   .btn-dark.disabled, .btn-dark:disabled {
     color: #fff;
-    background-color: #373a3c;
-    border-color: #373a3c; }
+    background-color: #343a40;
+    border-color: #343a40; }
   .btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,
   .show > .btn-dark.dropdown-toggle {
     color: #fff;
-    background-color: #1f2021;
-    border-color: #191a1b; }
+    background-color: #1d2124;
+    border-color: #171a1d; }
     .btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,
     .show > .btn-dark.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(85, 88, 89, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); }
 
 .btn-outline-primary {
   color: #1177d1;
@@ -4228,46 +4228,46 @@ fieldset:disabled a.btn {
       box-shadow: 0 0 0 0.2rem rgba(17, 119, 209, 0.5); }
 
 .btn-outline-secondary {
-  color: #e9ecef;
-  border-color: #e9ecef; }
+  color: #ced4da;
+  border-color: #ced4da; }
   .btn-outline-secondary:hover {
     color: #212529;
-    background-color: #e9ecef;
-    border-color: #e9ecef; }
+    background-color: #ced4da;
+    border-color: #ced4da; }
   .btn-outline-secondary:focus, .btn-outline-secondary.focus {
-    box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); }
   .btn-outline-secondary.disabled, .btn-outline-secondary:disabled {
-    color: #e9ecef;
+    color: #ced4da;
     background-color: transparent; }
   .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,
   .show > .btn-outline-secondary.dropdown-toggle {
     color: #212529;
-    background-color: #e9ecef;
-    border-color: #e9ecef; }
+    background-color: #ced4da;
+    border-color: #ced4da; }
     .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-secondary.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); }
 
 .btn-outline-success {
-  color: #5cb85c;
-  border-color: #5cb85c; }
+  color: #398439;
+  border-color: #398439; }
   .btn-outline-success:hover {
     color: #fff;
-    background-color: #5cb85c;
-    border-color: #5cb85c; }
+    background-color: #398439;
+    border-color: #398439; }
   .btn-outline-success:focus, .btn-outline-success.focus {
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
   .btn-outline-success.disabled, .btn-outline-success:disabled {
-    color: #5cb85c;
+    color: #398439;
     background-color: transparent; }
   .btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,
   .show > .btn-outline-success.dropdown-toggle {
     color: #fff;
-    background-color: #5cb85c;
-    border-color: #5cb85c; }
+    background-color: #398439;
+    border-color: #398439; }
     .btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-success.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
 
 .btn-outline-info {
   color: #5bc0de;
@@ -4312,25 +4312,25 @@ fieldset:disabled a.btn {
       box-shadow: 0 0 0 0.2rem rgba(240, 173, 78, 0.5); }
 
 .btn-outline-danger {
-  color: #d9534f;
-  border-color: #d9534f; }
+  color: #d43f3a;
+  border-color: #d43f3a; }
   .btn-outline-danger:hover {
     color: #fff;
-    background-color: #d9534f;
-    border-color: #d9534f; }
+    background-color: #d43f3a;
+    border-color: #d43f3a; }
   .btn-outline-danger:focus, .btn-outline-danger.focus {
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
   .btn-outline-danger.disabled, .btn-outline-danger:disabled {
-    color: #d9534f;
+    color: #d43f3a;
     background-color: transparent; }
   .btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,
   .show > .btn-outline-danger.dropdown-toggle {
     color: #fff;
-    background-color: #d9534f;
-    border-color: #d9534f; }
+    background-color: #d43f3a;
+    border-color: #d43f3a; }
     .btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-danger.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
 
 .btn-outline-light {
   color: #f8f9fa;
@@ -4354,25 +4354,25 @@ fieldset:disabled a.btn {
       box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); }
 
 .btn-outline-dark {
-  color: #373a3c;
-  border-color: #373a3c; }
+  color: #343a40;
+  border-color: #343a40; }
   .btn-outline-dark:hover {
     color: #fff;
-    background-color: #373a3c;
-    border-color: #373a3c; }
+    background-color: #343a40;
+    border-color: #343a40; }
   .btn-outline-dark:focus, .btn-outline-dark.focus {
-    box-shadow: 0 0 0 0.2rem rgba(55, 58, 60, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
   .btn-outline-dark.disabled, .btn-outline-dark:disabled {
-    color: #373a3c;
+    color: #343a40;
     background-color: transparent; }
   .btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,
   .show > .btn-outline-dark.dropdown-toggle {
     color: #fff;
-    background-color: #373a3c;
-    border-color: #373a3c; }
+    background-color: #343a40;
+    border-color: #343a40; }
     .btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-dark.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(55, 58, 60, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
 
 .btn-link {
   font-weight: 400;
@@ -4385,7 +4385,7 @@ fieldset:disabled a.btn {
     text-decoration: underline;
     box-shadow: none; }
   .btn-link:disabled, .btn-link.disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none; }
 
 .btn-lg, .btn-group-lg > .btn {
@@ -4462,7 +4462,7 @@ input[type="button"].btn-block {
   padding: 0.5rem 0;
   margin: 0.125rem 0 0;
   font-size: 0.9375rem;
-  color: #373a3c;
+  color: #343a40;
   text-align: left;
   list-style: none;
   background-color: #fff;
@@ -4613,7 +4613,7 @@ input[type="button"].btn-block {
     text-decoration: none;
     background-color: #1177d1; }
   .dropdown-item.disabled, .dropdown-item:disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none;
     background-color: transparent; }
 
@@ -4625,7 +4625,7 @@ input[type="button"].btn-block {
   padding: 0.5rem 1.5rem;
   margin-bottom: 0;
   font-size: 0.8203125rem;
-  color: #868e96;
+  color: #6c757d;
   white-space: nowrap; }
 
 .dropdown-item-text {
@@ -4895,7 +4895,7 @@ input[type="button"].btn-block {
     background-color: #9dcdf7;
     border-color: #9dcdf7; }
   .custom-control-input:disabled ~ .custom-control-label {
-    color: #868e96; }
+    color: #6c757d; }
     .custom-control-input:disabled ~ .custom-control-label::before {
       background-color: #e9ecef; }
 
@@ -4986,7 +4986,7 @@ input[type="button"].btn-block {
   line-height: 1.5;
   color: #495057;
   vertical-align: middle;
-  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23373a3c' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px;
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' 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;
   background-color: #fff;
   border: 1px solid #ced4da;
   border-radius: 0.25rem;
@@ -5003,7 +5003,7 @@ input[type="button"].btn-block {
     padding-right: 0.75rem;
     background-image: none; }
   .custom-select:disabled {
-    color: #868e96;
+    color: #6c757d;
     background-color: #e9ecef; }
   .custom-select::-ms-expand {
     display: none; }
@@ -5200,7 +5200,7 @@ input[type="button"].btn-block {
   .nav-link:hover, .nav-link:focus {
     text-decoration: none; }
   .nav-link.disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none;
     cursor: default; }
 
@@ -5215,7 +5215,7 @@ input[type="button"].btn-block {
     .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
       border-color: #e9ecef #e9ecef #dee2e6; }
     .nav-tabs .nav-link.disabled {
-      color: #868e96;
+      color: #6c757d;
       background-color: transparent;
       border-color: transparent; }
   .nav-tabs .nav-link.active,
@@ -5693,7 +5693,7 @@ input[type="button"].btn-block {
   .breadcrumb-item + .breadcrumb-item::before {
     display: inline-block;
     padding-right: 0.5rem;
-    color: #868e96;
+    color: #6c757d;
     content: "/"; }
 
 .breadcrumb-item + .breadcrumb-item:hover::before {
@@ -5703,7 +5703,7 @@ input[type="button"].btn-block {
   text-decoration: none; }
 
 .breadcrumb-item.active {
-  color: #868e96; }
+  color: #6c757d; }
 
 .pagination {
   display: flex;
@@ -5747,7 +5747,7 @@ input[type="button"].btn-block {
   border-color: #1177d1; }
 
 .page-item.disabled .page-link {
-  color: #868e96;
+  color: #6c757d;
   pointer-events: none;
   cursor: auto;
   background-color: #fff;
@@ -5819,23 +5819,23 @@ input[type="button"].btn-block {
 
 .badge-secondary {
   color: #212529;
-  background-color: #e9ecef; }
+  background-color: #ced4da; }
   .badge-secondary[href]:hover, .badge-secondary[href]:focus {
     color: #212529;
-    background-color: #cbd3da; }
+    background-color: #b1bbc4; }
   .badge-secondary[href]:focus, .badge-secondary[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(206, 212, 218, 0.5); }
 
 .badge-success {
   color: #fff;
-  background-color: #5cb85c; }
+  background-color: #398439; }
   .badge-success[href]:hover, .badge-success[href]:focus {
     color: #fff;
-    background-color: #449d44; }
+    background-color: #2a602a; }
   .badge-success[href]:focus, .badge-success[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
 
 .badge-info {
   color: #212529;
@@ -5859,13 +5859,13 @@ input[type="button"].btn-block {
 
 .badge-danger {
   color: #fff;
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
   .badge-danger[href]:hover, .badge-danger[href]:focus {
     color: #fff;
-    background-color: #c9302c; }
+    background-color: #b42c27; }
   .badge-danger[href]:focus, .badge-danger[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
 
 .badge-light {
   color: #212529;
@@ -5879,13 +5879,13 @@ input[type="button"].btn-block {
 
 .badge-dark {
   color: #fff;
-  background-color: #373a3c; }
+  background-color: #343a40; }
   .badge-dark[href]:hover, .badge-dark[href]:focus {
     color: #fff;
-    background-color: #1f2021; }
+    background-color: #1d2124; }
   .badge-dark[href]:focus, .badge-dark[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(55, 58, 60, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); }
 
 .jumbotron {
   padding: 2rem 1rem;
@@ -5933,22 +5933,22 @@ input[type="button"].btn-block {
     color: #05233e; }
 
 .alert-secondary {
-  color: #797b7c;
-  background-color: #fbfbfc;
-  border-color: #f9fafb; }
+  color: #6b6e71;
+  background-color: #f5f6f8;
+  border-color: #f1f3f5; }
   .alert-secondary hr {
-    border-top-color: #eaedf1; }
+    border-top-color: #e2e6ea; }
   .alert-secondary .alert-link {
-    color: #606162; }
+    color: #525557; }
 
 .alert-success {
-  color: #306030;
-  background-color: #def1de;
-  border-color: #d1ebd1; }
+  color: #1e451e;
+  background-color: #d7e6d7;
+  border-color: #c8ddc8; }
   .alert-success hr {
-    border-top-color: #bfe3bf; }
+    border-top-color: #b8d3b8; }
   .alert-success .alert-link {
-    color: #1f3e1f; }
+    color: #0f210f; }
 
 .alert-info {
   color: #2f6473;
@@ -5969,13 +5969,13 @@ input[type="button"].btn-block {
     color: #573e1c; }
 
 .alert-danger {
-  color: #712b29;
-  background-color: #f7dddc;
-  border-color: #f4cfce; }
+  color: #6e211e;
+  background-color: #f6d9d8;
+  border-color: #f3c9c8; }
   .alert-danger hr {
-    border-top-color: #efbbb9; }
+    border-top-color: #eeb4b3; }
   .alert-danger .alert-link {
-    color: #4c1d1b; }
+    color: #461513; }
 
 .alert-light {
   color: #818182;
@@ -5987,11 +5987,11 @@ input[type="button"].btn-block {
     color: #686868; }
 
 .alert-dark {
-  color: #1d1e1f;
-  background-color: #d7d8d8;
-  border-color: #c7c8c8; }
+  color: #1b1e21;
+  background-color: #d6d8d9;
+  border-color: #c6c8ca; }
   .alert-dark hr {
-    border-top-color: #babbbb; }
+    border-top-color: #b9bbbe; }
   .alert-dark .alert-link {
     color: #040505; }
 
@@ -6055,7 +6055,7 @@ input[type="button"].btn-block {
     text-decoration: none;
     background-color: #f8f9fa; }
   .list-group-item-action:active {
-    color: #373a3c;
+    color: #343a40;
     background-color: #e9ecef; }
 
 .list-group-item {
@@ -6073,7 +6073,7 @@ input[type="button"].btn-block {
     border-bottom-right-radius: 0.25rem;
     border-bottom-left-radius: 0.25rem; }
   .list-group-item.disabled, .list-group-item:disabled {
-    color: #868e96;
+    color: #6c757d;
     pointer-events: none;
     background-color: #fff; }
   .list-group-item.active {
@@ -6187,26 +6187,26 @@ input[type="button"].btn-block {
     border-color: #093e6d; }
 
 .list-group-item-secondary {
-  color: #797b7c;
-  background-color: #f9fafb; }
+  color: #6b6e71;
+  background-color: #f1f3f5; }
   .list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {
-    color: #797b7c;
-    background-color: #eaedf1; }
+    color: #6b6e71;
+    background-color: #e2e6ea; }
   .list-group-item-secondary.list-group-item-action.active {
     color: #fff;
-    background-color: #797b7c;
-    border-color: #797b7c; }
+    background-color: #6b6e71;
+    border-color: #6b6e71; }
 
 .list-group-item-success {
-  color: #306030;
-  background-color: #d1ebd1; }
+  color: #1e451e;
+  background-color: #c8ddc8; }
   .list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {
-    color: #306030;
-    background-color: #bfe3bf; }
+    color: #1e451e;
+    background-color: #b8d3b8; }
   .list-group-item-success.list-group-item-action.active {
     color: #fff;
-    background-color: #306030;
-    border-color: #306030; }
+    background-color: #1e451e;
+    border-color: #1e451e; }
 
 .list-group-item-info {
   color: #2f6473;
@@ -6231,15 +6231,15 @@ input[type="button"].btn-block {
     border-color: #7d5a29; }
 
 .list-group-item-danger {
-  color: #712b29;
-  background-color: #f4cfce; }
+  color: #6e211e;
+  background-color: #f3c9c8; }
   .list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {
-    color: #712b29;
-    background-color: #efbbb9; }
+    color: #6e211e;
+    background-color: #eeb4b3; }
   .list-group-item-danger.list-group-item-action.active {
     color: #fff;
-    background-color: #712b29;
-    border-color: #712b29; }
+    background-color: #6e211e;
+    border-color: #6e211e; }
 
 .list-group-item-light {
   color: #818182;
@@ -6253,15 +6253,15 @@ input[type="button"].btn-block {
     border-color: #818182; }
 
 .list-group-item-dark {
-  color: #1d1e1f;
-  background-color: #c7c8c8; }
+  color: #1b1e21;
+  background-color: #c6c8ca; }
   .list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {
-    color: #1d1e1f;
-    background-color: #babbbb; }
+    color: #1b1e21;
+    background-color: #b9bbbe; }
   .list-group-item-dark.list-group-item-action.active {
     color: #fff;
-    background-color: #1d1e1f;
-    border-color: #1d1e1f; }
+    background-color: #1b1e21;
+    border-color: #1b1e21; }
 
 .close {
   float: right;
@@ -6311,7 +6311,7 @@ a.close.disabled {
   display: flex;
   align-items: center;
   padding: 0.25rem 0.75rem;
-  color: #868e96;
+  color: #6c757d;
   background-color: rgba(255, 255, 255, 0.85);
   background-clip: padding-box;
   border-bottom: 1px solid rgba(0, 0, 0, 0.05); }
@@ -6671,7 +6671,7 @@ a.close.disabled {
 
 .popover-body {
   padding: 0.5rem 0.75rem;
-  color: #373a3c; }
+  color: #343a40; }
 
 .carousel {
   position: relative; }
@@ -6889,20 +6889,20 @@ button.bg-primary:focus {
   background-color: #0d5ca2 !important; }
 
 .bg-secondary {
-  background-color: #e9ecef !important; }
+  background-color: #ced4da !important; }
 
 a.bg-secondary:hover, a.bg-secondary:focus,
 button.bg-secondary:hover,
 button.bg-secondary:focus {
-  background-color: #cbd3da !important; }
+  background-color: #b1bbc4 !important; }
 
 .bg-success {
-  background-color: #5cb85c !important; }
+  background-color: #398439 !important; }
 
 a.bg-success:hover, a.bg-success:focus,
 button.bg-success:hover,
 button.bg-success:focus {
-  background-color: #449d44 !important; }
+  background-color: #2a602a !important; }
 
 .bg-info {
   background-color: #5bc0de !important; }
@@ -6921,12 +6921,12 @@ button.bg-warning:focus {
   background-color: #ec971f !important; }
 
 .bg-danger {
-  background-color: #d9534f !important; }
+  background-color: #d43f3a !important; }
 
 a.bg-danger:hover, a.bg-danger:focus,
 button.bg-danger:hover,
 button.bg-danger:focus {
-  background-color: #c9302c !important; }
+  background-color: #b42c27 !important; }
 
 .bg-light {
   background-color: #f8f9fa !important; }
@@ -6937,12 +6937,12 @@ button.bg-light:focus {
   background-color: #dae0e5 !important; }
 
 .bg-dark {
-  background-color: #373a3c !important; }
+  background-color: #343a40 !important; }
 
 a.bg-dark:hover, a.bg-dark:focus,
 button.bg-dark:hover,
 button.bg-dark:focus {
-  background-color: #1f2021 !important; }
+  background-color: #1d2124 !important; }
 
 .bg-white {
   background-color: #fff !important; }
@@ -6984,10 +6984,10 @@ button.bg-dark:focus {
   border-color: #1177d1 !important; }
 
 .border-secondary {
-  border-color: #e9ecef !important; }
+  border-color: #ced4da !important; }
 
 .border-success {
-  border-color: #5cb85c !important; }
+  border-color: #398439 !important; }
 
 .border-info {
   border-color: #5bc0de !important; }
@@ -6996,13 +6996,13 @@ button.bg-dark:focus {
   border-color: #f0ad4e !important; }
 
 .border-danger {
-  border-color: #d9534f !important; }
+  border-color: #d43f3a !important; }
 
 .border-light {
   border-color: #f8f9fa !important; }
 
 .border-dark {
-  border-color: #373a3c !important; }
+  border-color: #343a40 !important; }
 
 .border-white {
   border-color: #fff !important; }
@@ -9213,16 +9213,16 @@ a.text-primary:hover, a.text-primary:focus {
   color: #0b4f8a !important; }
 
 .text-secondary {
-  color: #e9ecef !important; }
+  color: #ced4da !important; }
 
 a.text-secondary:hover, a.text-secondary:focus {
-  color: #bdc6cf !important; }
+  color: #a2aeb9 !important; }
 
 .text-success {
-  color: #5cb85c !important; }
+  color: #398439 !important; }
 
 a.text-success:hover, a.text-success:focus {
-  color: #3d8b3d !important; }
+  color: #224f22 !important; }
 
 .text-info {
   color: #5bc0de !important; }
@@ -9237,10 +9237,10 @@ a.text-warning:hover, a.text-warning:focus {
   color: #df8a13 !important; }
 
 .text-danger {
-  color: #d9534f !important; }
+  color: #d43f3a !important; }
 
 a.text-danger:hover, a.text-danger:focus {
-  color: #b52b27 !important; }
+  color: #9f2723 !important; }
 
 .text-light {
   color: #f8f9fa !important; }
@@ -9249,16 +9249,16 @@ a.text-light:hover, a.text-light:focus {
   color: #cbd3da !important; }
 
 .text-dark {
-  color: #373a3c !important; }
+  color: #343a40 !important; }
 
 a.text-dark:hover, a.text-dark:focus {
-  color: #121314 !important; }
+  color: #121416 !important; }
 
 .text-body {
-  color: #373a3c !important; }
+  color: #343a40 !important; }
 
 .text-muted {
-  color: #868e96 !important; }
+  color: #6c757d !important; }
 
 .text-black-50 {
   color: rgba(0, 0, 0, 0.5) !important; }
@@ -9372,9 +9372,9 @@ a.text-dark:hover, a.text-dark:focus {
   background-size: calc(1.5em + 0.75rem + 2px)/2 calc(1.5em + 0.75rem + 2px)/2; }
 
 .tag-default {
-  background-color: #868e96; }
+  background-color: #6c757d; }
   .tag-default[href]:hover, .tag-default[href]:focus {
-    background-color: #6c757d; }
+    background-color: #545b62; }
 
 .tag-primary {
   background-color: #1177d1; }
@@ -9382,9 +9382,9 @@ a.text-dark:hover, a.text-dark:focus {
     background-color: #0d5ca2; }
 
 .tag-success {
-  background-color: #5cb85c; }
+  background-color: #398439; }
   .tag-success[href]:hover, .tag-success[href]:focus {
-    background-color: #449d44; }
+    background-color: #2a602a; }
 
 .tag-info {
   background-color: #5bc0de; }
@@ -9392,14 +9392,14 @@ a.text-dark:hover, a.text-dark:focus {
     background-color: #31b0d5; }
 
 .tag-warning {
-  background-color: #ff7518; }
+  background-color: #f0ad4e; }
   .tag-warning[href]:hover, .tag-warning[href]:focus {
-    background-color: #e45c00; }
+    background-color: #ec971f; }
 
 .tag-danger {
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
   .tag-danger[href]:hover, .tag-danger[href]:focus {
-    background-color: #c9302c; }
+    background-color: #b42c27; }
 
 .custom-select {
   width: auto; }
@@ -9697,7 +9697,7 @@ a.dimmed_text:visited,
 .usersuspended a:visited,
 .dimmed_category,
 .dimmed_category a {
-  color: #868e96; }
+  color: #6c757d; }
 
 .unlist,
 .unlist li,
@@ -9729,7 +9729,7 @@ a.dimmed_text:visited,
 
 .green,
 .notifysuccess {
-  color: #5cb85c; }
+  color: #398439; }
 
 .highlight {
   color: #5bc0de; }
@@ -10248,7 +10248,7 @@ tr.flagged-tag a {
   padding-left: 10px; }
 
 .tag_feed .media .muted a {
-  color: #868e96; }
+  color: #6c757d; }
 
 .tag_cloud {
   text-align: center; }
@@ -10911,7 +10911,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
     .modchoosercontainer .optionscontainer .option .optionactions .optionaction,
     .modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction {
       cursor: pointer;
-      color: #868e96; }
+      color: #6c757d; }
       .modchoosercontainer .optionscontainer .option .optionactions .optionaction i,
       .modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction i {
         margin: 0; }
@@ -11127,10 +11127,10 @@ ul.badges {
   vertical-align: top; }
 
 .connected {
-  color: #5cb85c; }
+  color: #398439; }
 
 .notconnected {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .connecting {
   color: #f0ad4e; }
@@ -11151,7 +11151,7 @@ ul.badges {
   display: inline-block; }
 
 .statusbox.active {
-  background-color: #def1de; }
+  background-color: #d7e6d7; }
 
 .statusbox.inactive {
   background-color: #fcefdc; }
@@ -11399,7 +11399,7 @@ ul {
 .dropdown-item a {
   display: block;
   width: 100%;
-  color: #373a3c; }
+  color: #343a40; }
 
 .dropdown-item:active a {
   color: #fff; }
@@ -11589,6 +11589,30 @@ body.h5p-embed .h5pmessages {
   .toast-wrapper > :first-child {
     margin-top: 1rem; }
 
+.alert-primary a {
+  color: #05233e; }
+
+.alert-secondary a {
+  color: #525557; }
+
+.alert-success a {
+  color: #0f210f; }
+
+.alert-info a {
+  color: #20454f; }
+
+.alert-warning a {
+  color: #573e1c; }
+
+.alert-danger a {
+  color: #461513; }
+
+.alert-light a {
+  color: #686868; }
+
+.alert-dark a {
+  color: #040505; }
+
 .icon {
   font-size: 16px;
   width: 16px;
@@ -11703,15 +11727,15 @@ body.h5p-embed .h5pmessages {
 
 .environmenttable .warn {
   background-color: #fcefdc;
-  color: #ff7518; }
+  color: #f0ad4e; }
 
 .environmenttable .error {
-  background-color: #f7dddc;
-  color: #d9534f; }
+  background-color: #f6d9d8;
+  color: #d43f3a; }
 
 .environmenttable .ok {
-  background-color: #def1de;
-  color: #5cb85c; }
+  background-color: #d7e6d7;
+  color: #398439; }
 
 .path-admin .admintable.environmenttable .name,
 .path-admin .admintable.environmenttable .info,
@@ -12037,25 +12061,25 @@ body.h5p-embed .h5pmessages {
 
 #page-admin-plugins #plugins-control-panel .pluginname .componentname {
   font-size: 0.8203125rem;
-  color: #868e96;
+  color: #6c757d;
   margin-left: 22px; }
 
 #page-admin-plugins #plugins-control-panel .version .versionnumber {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 #page-admin-plugins #plugins-control-panel .uninstall a {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 #page-admin-plugins #plugins-control-panel .notes .label {
   margin-right: 3px; }
 
 #page-admin-plugins #plugins-control-panel .notes .requiredby {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page .page-description {
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page .checkforupdates .singlebutton {
   margin: 5px 0;
@@ -12076,14 +12100,14 @@ body.h5p-embed .h5pmessages {
     margin: 0 3px 0 0; }
 
 #plugins-check-page #plugins-check .requires-ok {
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page #plugins-check .status-missing td,
 #plugins-check-page #plugins-check .status-downgrade td {
-  background-color: #f7dddc; }
+  background-color: #f6d9d8; }
 
 #plugins-check-page #plugins-check .displayname .plugindir {
-  color: #868e96;
+  color: #6c757d;
   font-size: 0.8203125rem; }
 
 #plugins-check-page #plugins-check .requires ul {
@@ -12108,7 +12132,7 @@ body.h5p-embed .h5pmessages {
 
 #plugins-check-page #plugins-check-available-dependencies .displayname .component {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 #plugins-check-page #plugins-check-available-dependencies .info .actions > div {
   display: inline-block;
@@ -12129,7 +12153,7 @@ body.h5p-embed .h5pmessages {
   border-radius: 5px; }
   #plugins-check-page .pluginupdateinfo.maturity50,
   #plugins-control-panel .pluginupdateinfo.maturity50 {
-    background-color: #f7dddc; }
+    background-color: #f6d9d8; }
   #plugins-check-page .pluginupdateinfo.maturity100, #plugins-check-page .pluginupdateinfo.maturity150,
   #plugins-control-panel .pluginupdateinfo.maturity100,
   #plugins-control-panel .pluginupdateinfo.maturity150 {
@@ -12220,7 +12244,7 @@ body.h5p-embed .h5pmessages {
 
 #page-admin-tasklogs .task-class {
   font-size: 0.8203125rem;
-  color: #868e96; }
+  color: #6c757d; }
 
 .blockmovetarget .accesshide {
   position: relative;
@@ -12241,7 +12265,7 @@ body.h5p-embed .h5pmessages {
 
 .block .block-controls .dropdown-toggle {
   /* So that the caret takes the colour of the icon. */
-  color: #373a3c; }
+  color: #343a40; }
 
 [data-region="blocks-column"] {
   width: 360px;
@@ -12481,7 +12505,7 @@ body.h5p-embed .h5pmessages {
     margin-right: 2px; }
 
 .block.invisibleblock .card-title {
-  color: #868e96; }
+  color: #6c757d; }
 
 .navbar {
   max-height: 50px; }
@@ -12643,7 +12667,7 @@ body.h5p-embed .h5pmessages {
     font-size: 0.8em;
     text-align: center; }
   .block .minicalendar td.weekend {
-    color: #868e96; }
+    color: #6c757d; }
   .block .minicalendar td a {
     width: 100%;
     height: 100%;
@@ -12820,7 +12844,7 @@ body:not(.editing) .sitetopic ul.section {
     .section .activity .activityinstance .dimmed .activityicon {
       opacity: .5; }
   .section .activity .stealth {
-    color: #868e96; }
+    color: #6c757d; }
   .section .activity a.stealth,
   .section .activity a.stealth:hover {
     color: #6eb5f3 !important;
@@ -12934,7 +12958,7 @@ body:not(.editing) .sitetopic ul.section {
   margin: 2px 5px 2px 5px; }
 
 .course-content .section-summary .section-summary-activities .activity-count {
-  color: #868e96;
+  color: #6c757d;
   font-size: 0.8203125rem;
   margin: 3px;
   white-space: nowrap;
@@ -12984,7 +13008,7 @@ body:not(.editing) .sitetopic ul.section {
 .course-content ul li.section.hidden .sectionname > span,
 .course-content ul li.section.hidden .content > div.summary,
 .course-content ul li.section.hidden .activity .activityinstance {
-  color: #868e96; }
+  color: #6c757d; }
 
 .course-content ul.topics,
 .course-content ul.weeks {
@@ -13408,9 +13432,9 @@ span.editinstructions {
     color: #a1a1a8;
     margin-right: 2em; }
   #course-category-listings .listitem[data-visible="0"] {
-    color: #868e96; }
+    color: #6c757d; }
     #course-category-listings .listitem[data-visible="0"] > div > a {
-      color: #868e96; }
+      color: #6c757d; }
     #course-category-listings .listitem[data-visible="0"] > div .item-actions .action-show {
       display: inline; }
     #course-category-listings .listitem[data-visible="0"] > div .item-actions .action-hide {
@@ -13525,7 +13549,7 @@ span.editinstructions {
   #course-category-listings .listing-pagination-totals {
     text-align: center; }
     #course-category-listings .listing-pagination-totals.dimmed {
-      color: #868e96;
+      color: #6c757d;
       margin: 0.4rem 1rem 0.45rem; }
   #course-category-listings .select-a-category .notifymessage,
   #course-category-listings .select-a-category .alert {
@@ -13822,9 +13846,9 @@ body.drawer-ease {
   margin-right: 5px; }
 
 .fp-viewbar:not(.disabled) a.checked {
-  background-color: #cbd3da;
+  background-color: #b1bbc4;
   color: #212529;
-  border-color: #c4ccd4; }
+  border-color: #aab4bf; }
 
 .fp-viewbar.disabled a {
   pointer-events: none;
@@ -14240,7 +14264,7 @@ a.ygtvspacer:hover {
   background-color: #ebebe4; }
 
 .fitem.disabled .fp-btn-choose {
-  color: #868e96; }
+  color: #6c757d; }
 
 .fitem.disabled .filepicker-filelist .filepicker-filename {
   display: none; }
@@ -14810,7 +14834,7 @@ a.ygtvspacer:hover {
       direction: ltr; }
   .message-app .matchtext {
     background-color: #b5d9f9;
-    color: #373a3c;
+    color: #343a40;
     height: 1.5rem; }
   .message-app .contact-status {
     position: absolute;
@@ -14821,7 +14845,7 @@ a.ygtvspacer:hover {
     border-radius: 50%; }
     .message-app .contact-status.online {
       border: 1px solid #fff;
-      background-color: #5cb85c; }
+      background-color: #398439; }
   .message-app .message p {
     margin: 0; }
   .message-app .clickable {
@@ -15235,14 +15259,14 @@ body.path-question-type {
   max-width: 100%; }
 
 .que .comment {
-  color: #306030;
-  background-color: #def1de;
-  border-color: #d1ebd1;
+  color: #1e451e;
+  background-color: #d7e6d7;
+  border-color: #c8ddc8;
   /* stylelint-disable-line max-line-length */ }
   .que .comment hr {
-    border-top-color: #bfe3bf; }
+    border-top-color: #b8d3b8; }
   .que .comment .alert-link {
-    color: #1f3e1f; }
+    color: #0f210f; }
 
 .que .ablock {
   margin: 0.7em 0 0.3em 0; }
@@ -15260,19 +15284,19 @@ body.path-question-type {
   margin: 0 0 0.5em; }
 
 .que .correctness.correct {
-  background-color: #5cb85c; }
+  background-color: #398439; }
 
 .que .correctness.partiallycorrect {
-  background-color: #ff7518; }
+  background-color: #f0ad4e; }
 
 .que .correctness.notanswered, .que .correctness.incorrect {
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
 
 .que .qtext {
   margin-bottom: 1.5em; }
 
 .que .validationerror {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .que .grading,
 .que .comment,
@@ -15370,7 +15394,7 @@ body.jsenabled .questionflag input[type=checkbox] {
     margin: 0; }
 
 #page-mod-quiz-edit .questionbankwindow div.header .title {
-  color: #373a3c; }
+  color: #343a40; }
 
 #page-mod-quiz-edit div.container div.generalbox {
   background-color: transparent;
@@ -15796,9 +15820,9 @@ body.path-question-type .mform fieldset.hidden {
 
 #adminsettings span.error {
   display: inline-block;
-  border: 1px solid #f4cfce;
+  border: 1px solid #f3c9c8;
   border-radius: 4px;
-  background-color: #f7dddc;
+  background-color: #f6d9d8;
   padding: 4px;
   margin-bottom: 4px; }
 
@@ -15837,7 +15861,7 @@ body.path-question-type .mform fieldset.hidden {
   display: none; }
 
 #adminsettings .error {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .mform ul.file-list {
   padding: 0;
@@ -15857,7 +15881,7 @@ input#id_externalurl {
 
 .form-defaultinfo,
 .form-label .form-shortname {
-  color: #868e96; }
+  color: #6c757d; }
 
 .form-label .form-shortname {
   font-size: 0.703125rem;
@@ -15870,10 +15894,10 @@ input#id_externalurl {
   margin-left: 0.5rem; }
 
 .formsettingheading .form-horizontal {
-  color: #868e96; }
+  color: #6c757d; }
 
 .no-felement.fstatic {
-  color: #868e96;
+  color: #6c757d;
   padding-top: 5px; }
 
 .no-fitem .fstaticlabel {
@@ -16027,7 +16051,7 @@ fieldset.coursesearchbox label {
   padding: 0.2em;
   margin: 0;
   cursor: pointer;
-  color: #373a3c; }
+  color: #343a40; }
 
 .form-autocomplete-suggestions li:hover {
   background-color: #3f9def;
@@ -16038,7 +16062,7 @@ fieldset.coursesearchbox label {
   color: #495057; }
 
 .form-autocomplete-downarrow {
-  color: #373a3c;
+  color: #343a40;
   top: 0.2rem;
   right: 0.5rem;
   cursor: pointer; }
@@ -16127,18 +16151,18 @@ textarea[data-auto-rows] {
   .has-danger .editor_atto_content.form-control-danger .form-check-label,
   .has-danger .editor_atto_content.form-control-danger .form-check-inline,
   .has-danger .editor_atto_content.form-control-danger .custom-control {
-    color: #d9534f; }
+    color: #d43f3a; }
   .has-danger .editor_atto_content.form-control .form-control,
   .has-danger .editor_atto_content.form-control-danger .form-control {
-    border-color: #d9534f; }
+    border-color: #d43f3a; }
   .has-danger .editor_atto_content.form-control .input-group-addon,
   .has-danger .editor_atto_content.form-control-danger .input-group-addon {
-    color: #d9534f;
-    border-color: #d9534f;
-    background-color: #fdf7f7; }
+    color: #d43f3a;
+    border-color: #d43f3a;
+    background-color: #f9e2e1; }
   .has-danger .editor_atto_content.form-control .form-control-feedback,
   .has-danger .editor_atto_content.form-control-danger .form-control-feedback {
-    color: #d9534f; }
+    color: #d43f3a; }
 
 [data-filetypesbrowserbody] [aria-expanded="false"] > [role="group"],
 [data-filetypesbrowserbody] [aria-expanded="false"] [data-filetypesbrowserfeature="hideifcollapsed"],
@@ -16258,10 +16282,10 @@ select {
   font-weight: inherit; }
 
 .path-mod-forum .subscriptionmode {
-  color: #373a3c; }
+  color: #343a40; }
 
 .path-mod-forum .activesetting {
-  color: #373a3c;
+  color: #343a40;
   font-weight: bold; }
 
 .discussion-settings-container .custom-select {
@@ -17041,7 +17065,7 @@ div#dock {
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.correct .trafficlight {
   background-image: url([[pix:theme|mod/quiz/checkmark]]);
-  background-color: #5cb85c; }
+  background-color: #398439; }
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.blocked .trafficlight {
   background-image: url([[pix:core|t/locked]]);
@@ -17049,16 +17073,16 @@ div#dock {
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.notanswered .trafficlight,
 .path-mod-quiz #mod_quiz_navblock .qnbutton.incorrect .trafficlight {
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.partiallycorrect .trafficlight {
   background-image: url([[pix:theme|mod/quiz/whitecircle]]);
-  background-color: #ff7518; }
+  background-color: #f0ad4e; }
 
 .path-mod-quiz #mod_quiz_navblock .qnbutton.complete .trafficlight,
 .path-mod-quiz #mod_quiz_navblock .qnbutton.answersaved .trafficlight,
 .path-mod-quiz #mod_quiz_navblock .qnbutton.requiresgrading .trafficlight {
-  background-color: #868e96; }
+  background-color: #6c757d; }
 
 #page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmarkcontainer form input {
   height: 1.4em;
@@ -17359,14 +17383,14 @@ div#dock {
     content: ""; }
 
 .path-backup .notification.dependencies_enforced {
-  color: #d9534f;
+  color: #d43f3a;
   font-weight: bold; }
 
 .path-backup .backup_progress {
   margin-top: 1rem;
   margin-bottom: 1rem; }
   .path-backup .backup_progress .backup_stage {
-    color: #868e96; }
+    color: #6c757d; }
     .path-backup .backup_progress .backup_stage.backup_stage_current {
       font-weight: bold;
       color: inherit; }
@@ -17375,7 +17399,7 @@ div#dock {
   color: inherit; }
 
 #page-backup-restore .filealiasesfailures {
-  background-color: #f7dddc; }
+  background-color: #f6d9d8; }
   #page-backup-restore .filealiasesfailures .aliaseslist {
     background-color: #fff; }
 
@@ -17423,7 +17447,7 @@ div#dock {
 .generaltable {
   width: 100%;
   margin-bottom: 1rem;
-  color: #373a3c; }
+  color: #343a40; }
   .generaltable th,
   .generaltable td {
     padding: 0.75rem;
@@ -17440,7 +17464,7 @@ div#dock {
   .generaltable.table-sm td {
     padding: 0.3rem; }
   .generaltable tbody tr:hover {
-    color: #373a3c;
+    color: #343a40;
     background-color: rgba(0, 0, 0, 0.075); }
 
 table caption {
@@ -18488,30 +18512,30 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont
   height: auto; }
 
 .text-error {
-  color: #d9534f; }
+  color: #d43f3a; }
 
 .btn-default {
   color: #212529;
-  background-color: #e9ecef;
-  border-color: #e9ecef; }
+  background-color: #ced4da;
+  border-color: #ced4da; }
   .btn-default:hover {
     color: #212529;
-    background-color: #d3d9df;
-    border-color: #cbd3da; }
+    background-color: #b8c1ca;
+    border-color: #b1bbc4; }
   .btn-default:focus, .btn-default.focus {
-    box-shadow: 0 0 0 0.2rem rgba(203, 206, 209, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(180, 186, 191, 0.5); }
   .btn-default.disabled, .btn-default:disabled {
     color: #212529;
-    background-color: #e9ecef;
-    border-color: #e9ecef; }
+    background-color: #ced4da;
+    border-color: #ced4da; }
   .btn-default:not(:disabled):not(.disabled):active, .btn-default:not(:disabled):not(.disabled).active,
   .show > .btn-default.dropdown-toggle {
     color: #212529;
-    background-color: #cbd3da;
-    border-color: #c4ccd4; }
+    background-color: #b1bbc4;
+    border-color: #aab4bf; }
     .btn-default:not(:disabled):not(.disabled):active:focus, .btn-default:not(:disabled):not(.disabled).active:focus,
     .show > .btn-default.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(203, 206, 209, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(180, 186, 191, 0.5); }
 
 .label {
   display: inline-block;
@@ -18525,26 +18549,26 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont
   border-radius: 0.25rem;
   transition: color 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;
   color: #fff;
-  background-color: #868e96; }
+  background-color: #6c757d; }
   @media (prefers-reduced-motion: reduce) {
     .label {
       transition: none; } }
   .label[href]:hover, .label[href]:focus {
     color: #fff;
-    background-color: #6c757d; }
+    background-color: #545b62; }
   .label[href]:focus, .label[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }
 
 .label-success {
   color: #fff;
-  background-color: #5cb85c; }
+  background-color: #398439; }
   .label-success[href]:hover, .label-success[href]:focus {
     color: #fff;
-    background-color: #449d44; }
+    background-color: #2a602a; }
   .label-success[href]:focus, .label-success[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(92, 184, 92, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(57, 132, 57, 0.5); }
 
 .label-info {
   color: #212529;
@@ -18557,24 +18581,24 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont
     box-shadow: 0 0 0 0.2rem rgba(91, 192, 222, 0.5); }
 
 .label-warning {
-  color: #fff;
-  background-color: #ff7518; }
+  color: #212529;
+  background-color: #f0ad4e; }
   .label-warning[href]:hover, .label-warning[href]:focus {
-    color: #fff;
-    background-color: #e45c00; }
+    color: #212529;
+    background-color: #ec971f; }
   .label-warning[href]:focus, .label-warning[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(255, 117, 24, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(240, 173, 78, 0.5); }
 
 .label-important {
   color: #fff;
-  background-color: #d9534f; }
+  background-color: #d43f3a; }
   .label-important[href]:hover, .label-important[href]:focus {
     color: #fff;
-    background-color: #c9302c; }
+    background-color: #b42c27; }
   .label-important[href]:focus, .label-important[href].focus {
     outline: 0;
-    box-shadow: 0 0 0 0.2rem rgba(217, 83, 79, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(212, 63, 58, 0.5); }
 
 .pull-left {
   float: left !important;
@@ -18647,26 +18671,68 @@ span[data-flexitour="container"][x-placement="right"], span[data-flexitour="cont
   background-color: #fff; }
 
 .btn-outline-secondary {
-  color: #868e96;
-  border-color: #868e96;
-  border-color: #dee2e6; }
+  color: #6c757d;
+  border-color: #6c757d;
+  border-color: #6c757d; }
   .btn-outline-secondary:hover {
     color: #fff;
-    background-color: #868e96;
-    border-color: #868e96; }
+    background-color: #6c757d;
+    border-color: #6c757d; }
   .btn-outline-secondary:focus, .btn-outline-secondary.focus {
-    box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); }
+    box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }
   .btn-outline-secondary.disabled, .btn-outline-secondary:disabled {
-    color: #868e96;
+    color: #6c757d;
     background-color: transparent; }
   .btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,
   .show > .btn-outline-secondary.dropdown-toggle {
     color: #fff;
-    background-color: #868e96;
-    border-color: #868e96; }
+    background-color: #6c757d;
+    border-color: #6c757d; }
     .btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,
     .show > .btn-outline-secondary.dropdown-toggle:focus {
-      box-shadow: 0 0 0 0.2rem rgba(134, 142, 150, 0.5); }
+      box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); }
+
+.btn-outline-info {
+  color: #1f7e9a;
+  border-color: #1f7e9a; }
+  .btn-outline-info:hover {
+    color: #fff;
+    background-color: #1f7e9a;
+    border-color: #1f7e9a; }
+  .btn-outline-info:focus, .btn-outline-info.focus {
+    box-shadow: 0 0 0 0.2rem rgba(31, 126, 154, 0.5); }
+  .btn-outline-info.disabled, .btn-outline-info:disabled {
+    color: #1f7e9a;
+    background-color: transparent; }
+  .btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,
+  .show > .btn-outline-info.dropdown-toggle {
+    color: #fff;
+    background-color: #1f7e9a;
+    border-color: #1f7e9a; }
+    .btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,
+    .show > .btn-outline-info.dropdown-toggle:focus {
+      box-shadow: 0 0 0 0.2rem rgba(31, 126, 154, 0.5); }
+
+.btn-outline-warning {
+  color: #a6670e;
+  border-color: #a6670e; }
+  .btn-outline-warning:hover {
+    color: #fff;
+    background-color: #a6670e;
+    border-color: #a6670e; }
+  .btn-outline-warning:focus, .btn-outline-warning.focus {
+    box-shadow: 0 0 0 0.2rem rgba(166, 103, 14, 0.5); }
+  .btn-outline-warning.disabled, .btn-outline-warning:disabled {
+    color: #a6670e;
+    background-color: transparent; }
+  .btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,
+  .show > .btn-outline-warning.dropdown-toggle {
+    color: #fff;
+    background-color: #a6670e;
+    border-color: #a6670e; }
+    .btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,
+    .show > .btn-outline-warning.dropdown-toggle:focus {
+      box-shadow: 0 0 0 0.2rem rgba(166, 103, 14, 0.5); }
 
 @media (max-width: 767.98px) {
   .blockcolumn,
index 32aac2b..21ba357 100644 (file)
@@ -135,6 +135,9 @@ class participants_table extends \table_sql implements dynamic_table {
     /** @var \stdClass[] $viewableroles */
     private $viewableroles;
 
+    /** @var moodle_url $baseurl The base URL for the report. */
+    public $baseurl;
+
     /**
      * Render the participants table.
      *
@@ -143,7 +146,91 @@ class participants_table extends \table_sql implements dynamic_table {
      * @param string $downloadhelpbutton
      */
     public function out($pagesize, $useinitialsbar, $downloadhelpbutton = '') {
-        global $PAGE;
+        global $CFG, $OUTPUT, $PAGE;
+
+        // Define the headers and columns.
+        $headers = [];
+        $columns = [];
+
+        $bulkoperations = has_capability('moodle/course:bulkmessaging', $this->context);
+        if ($bulkoperations) {
+            $mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
+                'id' => 'select-all-participants',
+                'name' => 'select-all-participants',
+                'label' => $this->selectall ? get_string('deselectall') : get_string('selectall'),
+                'labelclasses' => 'sr-only',
+                'classes' => 'm-1',
+                'checked' => $this->selectall
+            ]);
+            $headers[] = $OUTPUT->render($mastercheckbox);
+            $columns[] = 'select';
+        }
+
+        $headers[] = get_string('fullname');
+        $columns[] = 'fullname';
+
+        $extrafields = get_extra_user_fields($this->context);
+        foreach ($extrafields as $field) {
+            $headers[] = get_user_field_name($field);
+            $columns[] = $field;
+        }
+
+        $headers[] = get_string('roles');
+        $columns[] = 'roles';
+
+        // Get the list of fields we have to hide.
+        $hiddenfields = array();
+        if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
+            $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
+        }
+
+        // Add column for groups if the user can view them.
+        $canseegroups = !isset($hiddenfields['groups']);
+        if ($canseegroups) {
+            $headers[] = get_string('groups');
+            $columns[] = 'groups';
+        }
+
+        // Do not show the columns if it exists in the hiddenfields array.
+        if (!isset($hiddenfields['lastaccess'])) {
+            if ($this->courseid == SITEID) {
+                $headers[] = get_string('lastsiteaccess');
+            } else {
+                $headers[] = get_string('lastcourseaccess');
+            }
+            $columns[] = 'lastaccess';
+        }
+
+        $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
+        if ($canreviewenrol && $this->courseid != SITEID) {
+            $columns[] = 'status';
+            $headers[] = get_string('participationstatus', 'enrol');
+            $this->no_sorting('status');
+        };
+
+        $this->define_columns($columns);
+        $this->define_headers($headers);
+
+        // Make this table sorted by last name by default.
+        $this->sortable(true, 'lastname');
+
+        $this->no_sorting('select');
+        $this->no_sorting('roles');
+        if ($canseegroups) {
+            $this->no_sorting('groups');
+        }
+
+        $this->set_attribute('id', 'participants');
+
+        $this->countries = get_string_manager()->get_list_of_countries(true);
+        $this->extrafields = $extrafields;
+        if ($canseegroups) {
+            $this->groups = groups_get_all_groups($this->courseid, 0, 0, 'g.*', true);
+        }
+        $this->allroles = role_fix_names(get_all_roles($this->context), $this->context);
+        $this->assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false);
+        $this->profileroles = get_profile_roles($this->context);
+        $this->viewableroles = get_viewable_roles($this->context);
 
         parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
 
@@ -401,141 +488,46 @@ class participants_table extends \table_sql implements dynamic_table {
      * @param filterset $filterset The filterset object to get the filters from.
      */
     public function set_filterset(filterset $filterset): void {
-        global $CFG, $OUTPUT;
+        // Store the filterset for later.
+        $this->filterset = $filterset;
 
-        $courseid = $filterset->get_filter('courseid')->current();
+        // Get the context.
+        $this->courseid = $filterset->get_filter('courseid')->current();
+        $this->course = get_course($this->courseid);
+        $this->context = \context_course::instance($this->courseid, MUST_EXIST);
 
         // Process the filterset.
-        $currentgroup = null;
+        $this->currentgroup = null;
         if ($filterset->has_filter('groups')) {
-            $currentgroup = $filterset->get_filter('groups')->current();
-        }
-
-        $contextid = null;
-        if ($filterset->has_filter('contextid')) {
-            $contextid = $filterset->get_filter('contextid')->current();
+            $this->currentgroup = $filterset->get_filter('groups')->current();
         }
 
-        $roleid = null;
+        $this->roleid = null;
         if ($filterset->has_filter('roles')) {
-            $roleid = $filterset->get_filter('roles')->current();
+            $this->roleid = $filterset->get_filter('roles')->current();
         }
 
-        $enrolid = null;
+        $this->enrolid = null;
         if ($filterset->has_filter('enrolments')) {
-            $enrolid = $filterset->get_filter('enrolments')->current();
+            $this->enrolid = $filterset->get_filter('enrolments')->current();
         }
 
-        $status = -1;
+        $this->status = -1;
         if ($filterset->has_filter('status')) {
-            $status = $filterset->get_filter('status')->current();
+            $this->status = $filterset->get_filter('status')->current();
         }
 
-        $accesssince = null;
+        $this->accesssince = null;
         if ($filterset->has_filter('accesssince')) {
-            $accesssince = $filterset->get_filter('accesssince')->current();
+            $this->accesssince = $filterset->get_filter('accesssince')->current();
         }
 
-        $keywords = null;
+        $this->search = null;
         if ($filterset->has_filter('keywords')) {
             $this->search = $filterset->get_filter('keywords')->get_filter_values();
         }
 
-        // Get the context.
-        $this->course = get_course($courseid);
-        $context = \context_course::instance($courseid, MUST_EXIST);
-        $this->context = $context;
-
-        // Define the headers and columns.
-        $headers = [];
-        $columns = [];
-
-        $bulkoperations = has_capability('moodle/course:bulkmessaging', $context);
-        if ($bulkoperations) {
-            $mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
-                'id' => 'select-all-participants',
-                'name' => 'select-all-participants',
-                'label' => $this->selectall ? get_string('deselectall') : get_string('selectall'),
-                'labelclasses' => 'sr-only',
-                'classes' => 'm-1',
-                'checked' => $this->selectall
-            ]);
-            $headers[] = $OUTPUT->render($mastercheckbox);
-            $columns[] = 'select';
-        }
-
-        $headers[] = get_string('fullname');
-        $columns[] = 'fullname';
-
-        $extrafields = get_extra_user_fields($context);
-        foreach ($extrafields as $field) {
-            $headers[] = get_user_field_name($field);
-            $columns[] = $field;
-        }
-
-        $headers[] = get_string('roles');
-        $columns[] = 'roles';
-
-        // Get the list of fields we have to hide.
-        $hiddenfields = array();
-        if (!has_capability('moodle/course:viewhiddenuserfields', $context)) {
-            $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
-        }
-
-        // Add column for groups if the user can view them.
-        $canseegroups = !isset($hiddenfields['groups']);
-        if ($canseegroups) {
-            $headers[] = get_string('groups');
-            $columns[] = 'groups';
-        }
-
-        // Do not show the columns if it exists in the hiddenfields array.
-        if (!isset($hiddenfields['lastaccess'])) {
-            if ($courseid == SITEID) {
-                $headers[] = get_string('lastsiteaccess');
-            } else {
-                $headers[] = get_string('lastcourseaccess');
-            }
-            $columns[] = 'lastaccess';
-        }
-
-        $canreviewenrol = has_capability('moodle/course:enrolreview', $context);
-        if ($canreviewenrol && $courseid != SITEID) {
-            $columns[] = 'status';
-            $headers[] = get_string('participationstatus', 'enrol');
-            $this->no_sorting('status');
-        };
-
-        $this->define_columns($columns);
-        $this->define_headers($headers);
-
-        // Make this table sorted by last name by default.
-        $this->sortable(true, 'lastname');
-
-        $this->no_sorting('select');
-        $this->no_sorting('roles');
-        if ($canseegroups) {
-            $this->no_sorting('groups');
-        }
-
-        $this->set_attribute('id', 'participants');
-
-        // Set the variables we need to use later.
-        $this->currentgroup = $currentgroup;
-        $this->accesssince = $accesssince;
-        $this->roleid = $roleid;
-        $this->enrolid = $enrolid;
-        $this->status = $status;
-        $this->countries = get_string_manager()->get_list_of_countries(true);
-        $this->extrafields = $extrafields;
-        $this->context = $context;
-        if ($canseegroups) {
-            $this->groups = groups_get_all_groups($courseid, 0, 0, 'g.*', true);
-        }
-        $this->allroles = role_fix_names(get_all_roles($this->context), $this->context);
-        $this->assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false);
-        $this->profileroles = get_profile_roles($this->context);
-        $this->viewableroles = get_viewable_roles($this->context);
+        $this->define_baseurl($this->get_base_url());
     }
 
     /**
@@ -552,7 +544,31 @@ class participants_table extends \table_sql implements dynamic_table {
      *
      * @return moodle_url
      */
-    public static function get_base_url(): moodle_url {
-        return new moodle_url('');
+    public function get_base_url(): moodle_url {
+        if ($this->baseurl === null) {
+            return new moodle_url('/user/index.php', ['id' => $this->courseid]);
+        }
+
+        return $this->baseurl;
+    }
+
+    /**
+     * Get the context of the current table.
+     *
+     * Note: This function should not be called until after the filterset has been provided.
+     *
+     * @return context
+     */
+    public function get_context(): ?context {
+        return $this->context;
+    }
+
+    /**
+     * Get the currently defined filterset.
+     *
+     * @return filterset
+     */
+    public function get_filterset(): ?filterset {
+        return $this->filterset;
     }
 }
index 7408bca..eb33740 100644 (file)
@@ -203,14 +203,17 @@ if (!empty($groupid)) {
     if ($canaccessallgroups) {
         // User can access all groups, let them filter by whatever was selected.
         $filtersapplied[] = USER_FILTER_GROUP . ':' . $groupid;
+        $groupfilter->add_filter_value((int)$groupid);
     } else if (!$filterwassubmitted && $course->groupmode == VISIBLEGROUPS) {
         // If we are in a course with visible groups and the user has not submitted anything and does not have
         // access to all groups, then set a default group.
         $filtersapplied[] = USER_FILTER_GROUP . ':' . $groupid;
+        $groupfilter->add_filter_value((int)$groupid);
     } else if (!$hasgroupfilter && $course->groupmode != VISIBLEGROUPS) {
         // The user can't access all groups and has not set a group filter in a course where the groups are not visible
         // then apply a default group filter.
         $filtersapplied[] = USER_FILTER_GROUP . ':' . $groupid;
+        $groupfilter->add_filter_value((int)$groupid);
     } else if (!$hasgroupfilter) { // No need for the group id to be set.
         $groupid = false;
     }
index 0c23775..1b669b6 100644 (file)
@@ -853,7 +853,7 @@ function user_get_user_navigation_info($user, $page, $options = array()) {
 
     // Get basic user metadata.
     $returnobject->metadata['userid'] = $user->id;
-    $returnobject->metadata['userfullname'] = fullname($user, true);
+    $returnobject->metadata['userfullname'] = fullname($user);
     $returnobject->metadata['userprofileurl'] = new moodle_url('/user/profile.php', array(
         'id' => $user->id
     ));
@@ -925,7 +925,7 @@ function user_get_user_navigation_info($user, $page, $options = array()) {
         // Save values for the real user, as $user will be full of data for the
         // user the user is disguised as.
         $returnobject->metadata['realuserid'] = $realuser->id;
-        $returnobject->metadata['realuserfullname'] = fullname($realuser, true);
+        $returnobject->metadata['realuserfullname'] = fullname($realuser);
         $returnobject->metadata['realuserprofileurl'] = new moodle_url('/user/profile.php', array(
             'id' => $realuser->id
         ));
diff --git a/user/tests/behat/full_name_display.feature b/user/tests/behat/full_name_display.feature
new file mode 100644 (file)
index 0000000..83b1cd7
--- /dev/null
@@ -0,0 +1,81 @@
+@core @core_user
+Feature: Users' names are displayed across the site according to the user policy settings
+  In order to control the way students and teachers see users' names
+  As a teacher or admin
+  I need to be able to configure the name display formats 'fullnamedisplay' and 'alternativefullnameformat'
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname    | email                | middlename | alternatename | firstnamephonetic | lastnamephonetic |
+      | user1    | Grainne   | Beauchamp   | one@example.com      | Ann        | Jill          | Gronya            | Beecham          |
+      | user2    | Niamh     | Cholmondely | two@example.com      | Jane       | Nina          | Nee               | Chumlee          |
+      | user3    | Siobhan   | Desforges   | three@example.com    | Sarah      | Sev           | Shevon            | De-forjay        |
+      | teacher1 | Teacher   | 1           | teacher1@example.com |            |               |                   |                  |
+    And the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1 | topics |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+      | user1    | C1     | student        |
+      | user2    | C1     | student        |
+    And the following config values are set as admin:
+      | fullnamedisplay | firstnamephonetic,lastnamephonetic |
+      | alternativefullnameformat | middlename, alternatename, firstname, lastname |
+
+  Scenario: As a student, 'fullnamedisplay' should be used in the participants list and when viewing my own course profile
+    Given I log in as "user1"
+    And I am on "Course 1" course homepage
+    When I navigate to course participants
+    And I click on "Gronya,Beecham" "link" in the "Gronya,Beecham" "table_row"
+    Then I should see "Gronya,Beecham" in the "region-main" "region"
+    And I log out
+
+  Scenario: As a student, 'fullnamedisplay' should be used in the participants list and when viewing another user's course profile
+    Given I log in as "user2"
+    And I am on "Course 1" course homepage
+    When I navigate to course participants
+    And I click on "Gronya,Beecham" "link" in the "Gronya,Beecham" "table_row"
+    Then I should see "Gronya,Beecham" in the "region-main" "region"
+    And I log out
+
+  Scenario: As a teacher, 'alternativefullnameformat' should be used in the participants list but 'fullnamedisplay' used on the course profile
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    When I navigate to course participants
+    Then I should see "Ann, Jill, Grainne, Beauchamp" in the "Ann, Jill, Grainne, Beauchamp" "table_row"
+    And I click on "Ann, Jill, Grainne, Beauchamp" "link" in the "Ann, Jill, Grainne, Beauchamp" "table_row"
+    And I should see "Gronya,Beecham" in the "region-main" "region"
+    And I log out
+
+  Scenario: As an authenticated user, 'fullnamedisplay' should be used in the navigation and when viewing my profile
+    Given I log in as "user1"
+    When I follow "Profile" in the user menu
+    Then I should see "Gronya,Beecham" in the ".usermenu" "css_element"
+    And I should see "Gronya,Beecham" in the ".page-context-header" "css_element"
+    And I log out
+
+  Scenario: As an admin, 'fullnamedisplay' should be used when using the 'log in as' function
+    Given I log in as "admin"
+    When I navigate to "Users > Accounts > Browse list of users" in site administration
+    And I follow "Jane, Nina, Niamh, Cholmondely"
+    And I follow "Log in as"
+    Then I should see "You are logged in as Nee,Chumlee"
+    And I log out
+
+  Scenario: As an admin, 'fullnamedisplay' should be used when viewing another user's site profile
+    Given I log in as "admin"
+    When I navigate to "Users > Accounts > Browse list of users" in site administration
+    And I follow "Ann, Jill, Grainne, Beauchamp"
+    Then I should see "Gronya,Beecham" in the ".page-header-headings" "css_element"
+    And I log out
+
+  @javascript
+  Scenario: As a teacher, the 'alternativefullnameformat' should be used when searching for and enrolling a user
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    When I navigate to course participants
+    And I press "Enrol users"
+    And I set the field "Select users" to "three@example.com"
+    And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
+    Then I should see "Sarah, Sev, Siobhan, Desforges"
diff --git a/user/tests/behat/view_participants_groups.feature b/user/tests/behat/view_participants_groups.feature
new file mode 100644 (file)
index 0000000..9479c5b
--- /dev/null
@@ -0,0 +1,86 @@
+@core @core_user
+Feature: View course participants groups
+  In order to know who is on a course
+  As a teacher
+  I need to be able to view the participants groups on a course
+
+  Background:
+    Given the following "users" exist:
+      | username   | firstname | lastname | email                  |
+      | teacher1x  | Teacher   | 1x       | teacher1x@example.com  |
+      | student1x  | Student   | 1x       | student1x@example.com  |
+      | student2x  | Student   | 2x       | student2x@example.com  |
+      | student3x  | Student   | 3x       | student3x@example.com  |
+      | student4x  | Student   | 4x       | student4x@example.com  |
+    And the following "courses" exist:
+      | fullname | shortname | format | groupmode |
+      | Course 1 | C1        | topics | 1         |
+    And the following "course enrolments" exist:
+      | user      | course | role            |
+      | teacher1x  | C1     | editingteacher |
+      | student1x  | C1     | student        |
+      | student2x  | C1     | student        |
+      | student3x  | C1     | student        |
+      | student4x  | C1     | student        |
+    And the following "groups" exist:
+      | name    | course | idnumber |
+      | Group A | C1     | G1       |
+      | Group B | C1     | G2       |
+    And the following "group members" exist:
+      | user      | group |
+      | student1x | G1    |
+      | student2x | G1    |
+      | student3x | G2    |
+      | student4x | G2    |
+
+  Scenario: User should not be able to see other groups in separated group mode
+    Given I log in as "student1x"
+    And I am on "Course 1" course homepage
+    When I navigate to course participants
+    Then I should see "Group A"
+    And I should see "Student 1x"
+    And I should see "Student 2x"
+    And I should not see "Group B"
+    And I should not see "Student 3x"
+    And I should not see "Student 4x"
+
+  @javascript
+  Scenario: User should be able to see other groups in visible group mode
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    And I set the field "Group mode" to "Visible groups"
+    And I press "Save and display"
+    And I log out
+    And I log in as "student1x"
+    And I am on "Course 1" course homepage
+    When I navigate to course participants
+    Then I should see "Group A"
+    And I should see "Student 1x"
+    And I should see "Student 2x"
+    And I open the autocomplete suggestions list
+    And I click on "Group: Group B" item in the autocomplete list
+    And I should see "Group B"
+    And I should see "Student 3x"
+    And I should see "Student 4x"
+
+  Scenario: User should be able to see all users in no groups mode
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Edit settings" in current page administration
+    And I expand all fieldsets
+    And I set the field "Group mode" to "No groups"
+    And I press "Save and display"
+    And I log out
+    And I log in as "student1x"
+    And I am on "Course 1" course homepage
+    When I navigate to course participants
+    Then I should see "Group A"
+    And I should see "Student 1x"
+    And I should see "Student 2x"
+    And I should see "Group B"
+    And I should see "Student 3x"
+    And I should see "Student 4x"
+    And I should see "Teacher 1x"
+    And I should see "No groups"
index cbc67e6..a62461c 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2020032700.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2020032700.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 $release  = '3.9dev (Build: 20200327)'; // Human-friendly version name