Merge branch 'MDL-64314_master' of git://github.com/dmonllao/moodle
authorAdrian Greeve <abgreeve@gmail.com>
Mon, 18 Feb 2019 13:50:59 +0000 (14:50 +0100)
committerAdrian Greeve <abgreeve@gmail.com>
Mon, 18 Feb 2019 13:50:59 +0000 (14:50 +0100)
49 files changed:
admin/tool/langimport/classes/locale.php [new file with mode: 0644]
admin/tool/langimport/index.php
admin/tool/langimport/lang/en/tool_langimport.php
admin/tool/langimport/tests/locale_test.php [new file with mode: 0644]
admin/tool/xmldb/actions/add_persistent_mandatory/add_persistent_mandatory.class.php [new file with mode: 0644]
admin/tool/xmldb/actions/edit_table/edit_table.class.php
admin/tool/xmldb/lang/en/tool_xmldb.php
lib/amd/build/checkbox-toggleall.min.js [new file with mode: 0644]
lib/amd/src/checkbox-toggleall.js [new file with mode: 0644]
lib/form/amd/build/showadvanced.min.js [new file with mode: 0644]
lib/form/amd/src/showadvanced.js [new file with mode: 0644]
lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-debug.js [deleted file]
lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-min.js [deleted file]
lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced.js [deleted file]
lib/form/yui/src/showadvanced/build.json [deleted file]
lib/form/yui/src/showadvanced/js/showadvanced.js [deleted file]
lib/form/yui/src/showadvanced/meta/showadvanced.json [deleted file]
lib/formslib.php
lib/moodlelib.php
lib/navigationlib.php
lib/upgrade.txt
message/amd/build/notification_processor_settings.min.js
message/amd/build/preferences_notifications_list_controller.min.js
message/amd/build/preferences_processor_form.min.js
message/amd/src/notification_processor_settings.js
message/amd/src/preferences_notifications_list_controller.js
message/amd/src/preferences_processor_form.js
message/templates/preferences_processor.mustache
question/amd/build/qbankmanager.min.js [new file with mode: 0644]
question/amd/src/qbankmanager.js [new file with mode: 0644]
question/classes/bank/checkbox_column.php
question/classes/bank/view.php
question/tests/behat/select_questions.feature [new file with mode: 0644]
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-debug.js [deleted file]
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-min.js [deleted file]
question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager.js [deleted file]
question/yui/src/qbankmanager/build.json [deleted file]
question/yui/src/qbankmanager/js/qbankmanager.js [deleted file]
question/yui/src/qbankmanager/meta/qbankmanager.json [deleted file]
theme/boost/layout/columns2.php
theme/boost/templates/columns1.mustache
theme/boost/templates/columns2.mustache
theme/boost/templates/core/navbar.mustache
theme/boost/templates/flat_navigation.mustache
theme/boost/templates/footer.mustache
theme/boost/templates/login.mustache
theme/boost/templates/maintenance.mustache
theme/boost/templates/navbar-secure.mustache
theme/boost/templates/secure.mustache

diff --git a/admin/tool/langimport/classes/locale.php b/admin/tool/langimport/classes/locale.php
new file mode 100644 (file)
index 0000000..69c43b6
--- /dev/null
@@ -0,0 +1,84 @@
+<?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/>.
+
+/**
+ * Helper class for the language import tool.
+ *
+ * @package    tool_langimport
+ * @copyright  2018 Université Rennes 2 {@link https://www.univ-rennes2.fr}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_langimport;
+
+use coding_exception;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Helper class for the language import tool.
+ *
+ * @copyright  2018 Université Rennes 2 {@link https://www.univ-rennes2.fr}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class locale {
+    /**
+     * Checks availability of locale on current operating system.
+     *
+     * @param string $langpackcode E.g.: en, es, fr, de.
+     * @return bool TRUE if the locale is available on OS.
+     * @throws coding_exception when $langpackcode parameter is a non-empty string.
+     */
+    public function check_locale_availability(string $langpackcode) : bool {
+        global $CFG;
+
+        if (empty($langpackcode)) {
+            throw new coding_exception('Invalid language pack code in \\'.__METHOD__.'() call, only non-empty string is allowed');
+        }
+
+        // Fetch the correct locale based on ostype.
+        if ($CFG->ostype === 'WINDOWS') {
+            $stringtofetch = 'localewin';
+        } else {
+            $stringtofetch = 'locale';
+        }
+
+        // Store current locale.
+        $currentlocale = $this->set_locale(LC_ALL, 0);
+
+        $locale = get_string_manager()->get_string($stringtofetch, 'langconfig', $a = null, $langpackcode);
+
+        // Try to set new locale.
+        $return = $this->set_locale(LC_ALL, $locale);
+
+        // Restore current locale.
+        $this->set_locale(LC_ALL, $currentlocale);
+
+        // If $return is not equal to false, it means that setlocale() succeed to change locale.
+        return $return !== false;
+    }
+
+    /**
+     * Wrap for the native PHP function setlocale().
+     *
+     * @param int $category Specifying the category of the functions affected by the locale setting.
+     * @param string $locale E.g.: en_AU.utf8, en_GB.utf8, es_ES.utf8, fr_FR.utf8, de_DE.utf8.
+     * @return string|false Returns the new current locale, or FALSE on error.
+     */
+    protected function set_locale(int $category = LC_ALL, string $locale = '0') {
+        return setlocale($category, $locale);
+    }
+}
index 92a09da..1180abc 100644 (file)
@@ -109,9 +109,16 @@ echo $OUTPUT->header();
 echo $OUTPUT->heading(get_string('langimport', 'tool_langimport'));
 
 $installedlangs = get_string_manager()->get_list_of_translations(true);
+$locale = new \tool_langimport\locale();
 
+$missinglocales = '';
 $missingparents = array();
-foreach ($installedlangs as $installedlang => $unused) {
+foreach ($installedlangs as $installedlang => $langpackname) {
+    // Check locale availability.
+    if (!$locale->check_locale_availability($installedlang)) {
+        $missinglocales .= '<li>'.$langpackname.'</li>';
+    }
+
     $parent = get_parent_language($installedlang);
     if (empty($parent)) {
         continue;
@@ -121,6 +128,14 @@ foreach ($installedlangs as $installedlang => $unused) {
     }
 }
 
+if (!empty($missinglocales)) {
+    // There is at least one missing locale.
+    $a = new stdClass();
+    $a->globallocale = moodle_getlocale();
+    $a->missinglocales = $missinglocales;
+    $controller->errors[] = get_string('langunsupported', 'tool_langimport', $a);
+}
+
 if ($availablelangs = $controller->availablelangs) {
     $remote = true;
 } else {
index faeb02c..27739ed 100644 (file)
@@ -37,6 +37,7 @@ $string['langpackupdateskipped'] = 'Update of \'{$a}\' language pack skipped';
 $string['langpackuptodate'] = 'Language pack \'{$a}\' is up-to-date';
 $string['langpackupdated'] = 'Language pack \'{$a}\' was successfully updated';
 $string['langpackupdatedevent'] = 'Language pack updated';
+$string['langunsupported'] = '<p>Your server does not seem to fully support the following languages:</p><ul>{$a->missinglocales}</ul><p>Instead, the global locale ({$a->globallocale}) will be used to format certain strings such as dates or numbers.</p>';
 $string['langupdatecomplete'] = 'Language pack update completed';
 $string['missingcfglangotherroot'] = 'Missing configuration value $CFG->langotherroot';
 $string['missinglangparent'] = 'Missing parent language <em>{$a->parent}</em> of <em>{$a->lang}</em>.';
diff --git a/admin/tool/langimport/tests/locale_test.php b/admin/tool/langimport/tests/locale_test.php
new file mode 100644 (file)
index 0000000..4d1ffef
--- /dev/null
@@ -0,0 +1,71 @@
+<?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/>.
+
+/**
+ * Tests for \tool_langimport\locale class.
+ *
+ * @package    tool_langimport
+ * @copyright  2018 Université Rennes 2 {@link https://www.univ-rennes2.fr}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tests for \tool_langimport\locale class.
+ *
+ * @copyright  2018 Université Rennes 2 {@link https://www.univ-rennes2.fr}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class locale_testcase extends \advanced_testcase {
+    /**
+     * Test that \tool_langimport\locale::check_locale_availability() works as expected.
+     *
+     * @return void
+     */
+    public function test_check_locale_availability() {
+        // Create a mock of set_locale() method to simulate :
+        // - first setlocale() call which backup current locale
+        // - second setlocale() call which try to set new 'es' locale
+        // - third setlocale() call which restore locale.
+        $mock = $this->getMockBuilder(\tool_langimport\locale::class)
+            ->setMethods(['set_locale'])
+            ->getMock();
+        $mock->method('set_locale')->will($this->onConsecutiveCalls('en', 'es', 'en'));
+
+        // Test what happen when locale is available on system.
+        $result = $mock->check_locale_availability('en');
+        $this->assertTrue($result);
+
+        // Create a mock of set_locale() method to simulate :
+        // - first setlocale() call which backup current locale
+        // - second setlocale() call which fail to set new locale
+        // - third setlocale() call which restore locale.
+        $mock = $this->getMockBuilder(\tool_langimport\locale::class)
+            ->setMethods(['set_locale'])
+            ->getMock();
+        $mock->method('set_locale')->will($this->onConsecutiveCalls('en', false, 'en'));
+
+        // Test what happen when locale is not available on system.
+        $result = $mock->check_locale_availability('en');
+        $this->assertFalse($result);
+
+        // Test an invalid parameter.
+        $locale = new \tool_langimport\locale();
+        $this->expectException(coding_exception::class);
+        $locale->check_locale_availability('');
+    }
+}
diff --git a/admin/tool/xmldb/actions/add_persistent_mandatory/add_persistent_mandatory.class.php b/admin/tool/xmldb/actions/add_persistent_mandatory/add_persistent_mandatory.class.php
new file mode 100644 (file)
index 0000000..a323c1e
--- /dev/null
@@ -0,0 +1,154 @@
+<?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/>.
+
+/**
+ * @package    tool_xmldb
+ * @copyright  2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Add the mandatory fields for persistent to the table.
+ *
+ * @package    tool_xmldb
+ * @copyright  2019 Michael Aherne
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class add_persistent_mandatory extends XMLDBAction {
+
+    function init() {
+
+        parent::init();
+
+        // Get needed strings.
+        $this->loadStrings(array(
+            'addpersistent' => 'tool_xmldb',
+            'persistentfieldsconfirm' => 'tool_xmldb',
+            'persistentfieldscomplete' => 'tool_xmldb',
+            'persistentfieldsexist' => 'tool_xmldb',
+            'back' => 'core'
+        ));
+
+    }
+
+    function getTitle() {
+        return $this->str['addpersistent'];
+    }
+
+    function invoke() {
+
+        parent::invoke();
+
+        $this->does_generate = ACTION_GENERATE_HTML;
+
+        global $CFG, $XMLDB, $OUTPUT;
+
+        $dir = required_param('dir', PARAM_PATH);
+        $dirpath = $CFG->dirroot . $dir;
+
+        if (empty($XMLDB->dbdirs)) {
+            return false;
+        }
+
+        if (!empty($XMLDB->editeddirs)) {
+            $editeddir = $XMLDB->editeddirs[$dirpath];
+            $structure = $editeddir->xml_file->getStructure();
+        }
+
+        $tableparam = required_param('table', PARAM_ALPHANUMEXT);
+
+        /** @var xmldb_table $table */
+        $table = $structure->getTable($tableparam);
+
+        $result = true;
+        // Launch postaction if exists (leave this here!)
+        if ($this->getPostAction() && $result) {
+            return $this->launch($this->getPostAction());
+        }
+
+        $confirm = optional_param('confirm', false, PARAM_BOOL);
+
+        $fields = ['usermodified', 'timecreated', 'timemodified'];
+        $existing = [];
+        foreach ($fields as $field) {
+            if ($table->getField($field)) {
+                $existing[] = $field;
+            }
+        }
+
+        $returnurl = new \moodle_url('/admin/tool/xmldb/index.php', [
+            'table' => $tableparam,
+            'dir' => $dir,
+            'action' => 'edit_table'
+        ]);
+
+        $backbutton = html_writer::link($returnurl, '[' . $this->str['back'] . ']');
+        $actionbuttons = html_writer::tag('p', $backbutton, ['class' => 'centerpara buttons']);
+
+        if (!$confirm) {
+
+            if (!empty($existing)) {
+
+                $message = html_writer::span($this->str['persistentfieldsexist']);
+                $message .= html_writer::alist($existing);
+                $this->output .= $OUTPUT->notification($message);
+
+                if (count($existing) == count($fields)) {
+                    $this->output .= $actionbuttons;
+                    return true;
+                }
+            }
+
+            $confirmurl = new \moodle_url('/admin/tool/xmldb/index.php', [
+                'table' => $tableparam,
+                'dir' => $dir,
+                'action' => 'add_persistent_mandatory',
+                'sesskey' => sesskey(),
+                'confirm' => '1'
+            ]);
+
+            $message = html_writer::span($this->str['persistentfieldsconfirm']);
+            $message .= html_writer::alist(array_diff($fields, $existing));
+            $this->output .= $OUTPUT->confirm($message, $confirmurl, $returnurl);
+
+        } else {
+
+            $fieldsadded = [];
+            foreach ($fields as $field) {
+                if (!in_array($field, $existing)) {
+                    $fieldsadded[] = $field;
+                    $table->add_field($field, XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, 0);
+                }
+            }
+
+            if (!$table->getKey('usermodified')) {
+                $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
+            }
+
+            $structure->setVersion(userdate(time(), '%Y%m%d', 99, false));
+            $structure->setChanged(true);
+
+            $message = html_writer::span($this->str['persistentfieldscomplete']);
+            $message .= html_writer::alist(array_diff($fields, $existing));
+            $this->output .= $OUTPUT->notification($message, 'success');
+
+            $this->output .= $actionbuttons;
+        }
+
+        return $result;
+    }
+
+}
index 5cd4bcd..aa82f99 100644 (file)
@@ -44,6 +44,7 @@ class edit_table extends XMLDBAction {
 
         // Get needed strings
         $this->loadStrings(array(
+            'addpersistent' => 'tool_xmldb',
             'change' => 'tool_xmldb',
             'vieworiginal' => 'tool_xmldb',
             'viewedited' => 'tool_xmldb',
@@ -177,6 +178,15 @@ class edit_table extends XMLDBAction {
         $b .= '<a href="index.php?action=view_table_sql&amp;table=' . $tableparam . '&amp;dir=' . urlencode(str_replace($CFG->dirroot, '', $dirpath)) . '">[' .$this->str['viewsqlcode'] . ']</a>';
         // The view php code button
         $b .= '&nbsp;<a href="index.php?action=view_table_php&amp;table=' . $tableparam . '&amp;dir=' . urlencode(str_replace($CFG->dirroot, '', $dirpath)) . '">[' . $this->str['viewphpcode'] . ']</a>';
+        // The add persistent fields button.
+        $url = new \moodle_url('/admin/tool/xmldb/index.php', [
+            'action' => 'add_persistent_mandatory',
+            'sesskey' => sesskey(),
+            'table' => $tableparam,
+            'dir'=> str_replace($CFG->dirroot, '', $dirpath)
+        ]);
+        $b .= '&nbsp;' . \html_writer::link($url, '[' . $this->str['addpersistent'] . ']');
+
         // The save button (if possible)
         if ($cansavenow) {
             $b .= '&nbsp;<a href="index.php?action=save_xml_file&amp;sesskey=' . sesskey() . '&amp;dir=' . urlencode(str_replace($CFG->dirroot, '', $dirpath)) . '&amp;time=' . time() . '&amp;unload=false&amp;postaction=edit_table&amp;table=' . $tableparam . '&amp;dir=' . urlencode(str_replace($CFG->dirroot, '', $dirpath)) . '">[' . $this->str['save'] . ']</a>';
index d531a86..d2efeab 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 $string['actual'] = 'Actual';
+$string['addpersistent'] = 'Add mandatory persistent fields';
 $string['aftertable'] = 'After table:';
 $string['back'] = 'Back';
 $string['backtomainview'] = 'Back to main';
@@ -169,6 +170,9 @@ $string['numberincorrectwholepart'] = 'Too big whole number part for number fiel
 $string['pendingchanges'] = 'Note: You have performed changes to this file. They can be saved at any moment.';
 $string['pendingchangescannotbesaved'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server.';
 $string['pendingchangescannotbesavedreload'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server. Then reload this page and you should be able to save those changes.';
+$string['persistentfieldsconfirm'] = 'Do you want to add the following fields: ';
+$string['persistentfieldscomplete'] = 'The following fields have been added: ';
+$string['persistentfieldsexist'] = 'The following fields already exist: ';
 $string['pluginname'] = 'XMLDB editor';
 $string['primarykeyonlyallownotnullfields'] = 'Primary keys cannot be null';
 $string['reserved'] = 'Reserved';
diff --git a/lib/amd/build/checkbox-toggleall.min.js b/lib/amd/build/checkbox-toggleall.min.js
new file mode 100644 (file)
index 0000000..90e5ebc
Binary files /dev/null and b/lib/amd/build/checkbox-toggleall.min.js differ
diff --git a/lib/amd/src/checkbox-toggleall.js b/lib/amd/src/checkbox-toggleall.js
new file mode 100644 (file)
index 0000000..c6d4103
--- /dev/null
@@ -0,0 +1,126 @@
+// 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 module to help with toggle select/deselect all.
+ *
+ * @module     core/checkbox-toggleall
+ * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/pubsub'], function($, PubSub) {
+
+    var registered = false;
+
+    var events = {
+        checkboxToggled: 'core/checkbox-toggleall:checkboxToggled',
+    };
+
+    var getAllCheckboxes = function(root, toggleGroup) {
+        return root.find('[data-action="toggle"][data-togglegroup="' + toggleGroup + '"]');
+    };
+
+    var getAllSlaveCheckboxes = function(root, toggleGroup) {
+        return getAllCheckboxes(root, toggleGroup).filter('[data-toggle="slave"]');
+    };
+
+    var getControlCheckboxes = function(root, toggleGroup) {
+        return getAllCheckboxes(root, toggleGroup).filter('[data-toggle="master"]');
+    };
+
+    var toggleSlavesFromMasters = function(e) {
+        var root = e.data.root;
+        var target = $(e.target);
+
+        var toggleGroupName = target.data('togglegroup');
+        var targetState = target.is(':checked');
+
+        var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
+        var checkedSlaves = slaves.filter(':checked');
+
+        setMasterStates(root, toggleGroupName, targetState);
+
+        // Set the slave checkboxes from the masters.
+        slaves.prop('checked', targetState);
+
+        PubSub.publish(events.checkboxToggled, {
+            root: root,
+            toggleGroupName: toggleGroupName,
+            slaves: slaves,
+            checkedSlaves: checkedSlaves,
+            anyChecked: targetState,
+        });
+    };
+
+    var toggleMastersFromSlaves = function(e) {
+        var root = e.data.root;
+        var target = $(e.target);
+
+        var toggleGroupName = target.data('togglegroup');
+
+        var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
+        var checkedSlaves = slaves.filter(':checked');
+        var targetState = (slaves.length === checkedSlaves.length);
+
+        setMasterStates(root, toggleGroupName, targetState);
+
+        PubSub.publish(events.checkboxToggled, {
+            root: root,
+            toggleGroupName: toggleGroupName,
+            slaves: slaves,
+            checkedSlaves: checkedSlaves,
+            anyChecked: !!checkedSlaves.length,
+        });
+    };
+
+    var setMasterStates = function(root, toggleGroupName, targetState) {
+        // Set the master checkboxes value and ARIA labels..
+        var masters = getControlCheckboxes(root, toggleGroupName);
+        masters.prop('checked', targetState);
+        masters.each(function(i, masterCheckbox) {
+            masterCheckbox = $(masterCheckbox);
+            var masterLabel = root.find('[for="' + masterCheckbox.attr('id') + '"]');
+            var targetString;
+            if (masterLabel.length) {
+                if (targetState) {
+                    targetString = masterCheckbox.data('toggle-deselectall');
+                } else {
+                    targetString = masterCheckbox.data('toggle-selectall');
+                }
+
+                if (masterLabel.html() !== targetString) {
+                    masterLabel.html(targetString);
+                }
+            }
+        });
+    };
+
+    var registerListeners = function() {
+        if (!registered) {
+            registered = true;
+
+            var root = $(document.body);
+            root.on('change', '[data-action="toggle"][data-toggle="master"]', {root: root}, toggleSlavesFromMasters);
+            root.on('change', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves);
+        }
+    };
+
+    return {
+        init: function() {
+            registerListeners();
+        },
+        events: events,
+    };
+});
diff --git a/lib/form/amd/build/showadvanced.min.js b/lib/form/amd/build/showadvanced.min.js
new file mode 100644 (file)
index 0000000..fb4ccde
Binary files /dev/null and b/lib/form/amd/build/showadvanced.min.js differ
diff --git a/lib/form/amd/src/showadvanced.js b/lib/form/amd/src/showadvanced.js
new file mode 100644 (file)
index 0000000..a9f9645
--- /dev/null
@@ -0,0 +1,219 @@
+// 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 class to help show and hide advanced form content.
+ *
+ * @module     core_form/showadvanced
+ * @class      showadvanced
+ * @package    core_form
+ * @copyright  2016 Damyon Wiese <damyon@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/log', 'core/str', 'core/notification'], function($, Log, Strings, Notification) {
+
+    var SELECTORS = {
+            FIELDSETCONTAINSADVANCED: 'fieldset.containsadvancedelements',
+            DIVFITEMADVANCED: 'div.fitem.advanced',
+            DIVFCONTAINER: 'div.fcontainer',
+            MORELESSLINK: 'fieldset.containsadvancedelements .moreless-toggler'
+        },
+        CSS = {
+            SHOW: 'show',
+            MORELESSACTIONS: 'moreless-actions',
+            MORELESSTOGGLER: 'moreless-toggler',
+            SHOWLESS: 'moreless-less'
+        },
+        WRAPPERS = {
+            FITEM: '<div class="fitem"></div>',
+            FELEMENT: '<div class="felement"></div>'
+        },
+        IDPREFIX = 'showadvancedid-';
+
+    /** @type {Integer} uniqIdSeed Auto incrementing number used to generate ids. */
+    var uniqIdSeed = 0;
+
+    /**
+     * ShowAdvanced behaviour class.
+     * @param {String} id The id of the form.
+     */
+    var ShowAdvanced = function(id) {
+        this.id = id;
+
+        var form = $(document.getElementById(id));
+        this.enhanceForm(form);
+    };
+
+    /** @type {String} id The form id to enhance. */
+    ShowAdvanced.prototype.id = '';
+
+    /**
+     * @method enhanceForm
+     * @param {JQuery} form JQuery selector representing the form
+     * @return {ShowAdvanced}
+     */
+    ShowAdvanced.prototype.enhanceForm = function(form) {
+        var fieldsets = form.find(SELECTORS.FIELDSETCONTAINSADVANCED);
+
+        // Enhance each fieldset in the form matching the selector.
+        fieldsets.each(function(index, item) {
+            this.enhanceFieldset($(item));
+        }.bind(this));
+
+        // Attach some event listeners.
+        // Subscribe more/less links to click event.
+        form.on('click', SELECTORS.MORELESSLINK, this.switchState);
+
+        // Subscribe to key events but filter for space or enter.
+        form.on('keydown', SELECTORS.MORELESSLINK, function(e) {
+            // Enter or space.
+            if (e.which == 13 || e.which == 32) {
+                return this.switchState(e);
+            }
+            return true;
+        }.bind(this));
+        return this;
+    };
+
+
+    /**
+     * Generates a uniq id for the dom element it's called on unless the element already has an id.
+     * The id is set on the dom node before being returned.
+     *
+     * @method generateId
+     * @param {JQuery} node JQuery selector representing a single DOM Node.
+     * @return {String}
+     */
+    ShowAdvanced.prototype.generateId = function(node) {
+        var id = node.prop('id');
+        if (typeof id === 'undefined') {
+            id = IDPREFIX + (uniqIdSeed++);
+            node.prop('id', id);
+        }
+        return id;
+    };
+
+    /**
+     * @method enhanceFieldset
+     * @param {JQuery} fieldset JQuery selector representing a fieldset
+     * @return {ShowAdvanced}
+     */
+    ShowAdvanced.prototype.enhanceFieldset = function(fieldset) {
+        var statuselement = $('input[name=mform_showmore_' + fieldset.prop('id') + ']');
+        if (!statuselement.length) {
+            Log.debug("M.form.showadvanced::processFieldset was called on an fieldset without a status field: '" +
+                fieldset.prop('id') + "'");
+            return this;
+        }
+
+        // Fetch some strings.
+        Strings.get_strings([{
+            key: 'showmore',
+            component: 'core_form'
+        }, {
+            key: 'showless',
+            component: 'core_form'
+        }]).then(function(results) {
+            var showmore = results[0],
+                showless = results[1];
+
+            // Generate more/less links.
+            var morelesslink = $('<a href="#"></a>');
+            morelesslink.addClass(CSS.MORELESSTOGGLER);
+            if (statuselement.val() === '0') {
+                morelesslink.html(showmore);
+            } else {
+                morelesslink.html(showless);
+                morelesslink.addClass(CSS.SHOWLESS);
+                fieldset.find(SELECTORS.DIVFITEMADVANCED).addClass(CSS.SHOW);
+            }
+            // Build a list of advanced fieldsets.
+            var idlist = [];
+            fieldset.find(SELECTORS.DIVFITEMADVANCED).each(function(index, node) {
+                idlist[idlist.length] = this.generateId($(node));
+            }.bind(this));
+
+            // Set aria attributes.
+            morelesslink.attr('role', 'button');
+            morelesslink.attr('aria-controls', idlist.join(' '));
+
+            // Add elements to the DOM.
+            var fitem = $(WRAPPERS.FITEM);
+            fitem.addClass(CSS.MORELESSACTIONS);
+            var felement = $(WRAPPERS.FELEMENT);
+            felement.append(morelesslink);
+            fitem.append(felement);
+
+            fieldset.find(SELECTORS.DIVFCONTAINER).append(fitem);
+            return true;
+        }.bind(this)).fail(Notification.exception);
+
+        return this;
+    };
+
+    /**
+     * @method switchState
+     * @param {Event} e Event that triggered this action.
+     * @return {Boolean}
+     */
+    ShowAdvanced.prototype.switchState = function(e) {
+        e.preventDefault();
+
+        // Fetch some strings.
+        Strings.get_strings([{
+            key: 'showmore',
+            component: 'core_form'
+        }, {
+            key: 'showless',
+            component: 'core_form'
+        }]).then(function(results) {
+            var showmore = results[0],
+                showless = results[1],
+                fieldset = $(e.target).closest(SELECTORS.FIELDSETCONTAINSADVANCED);
+
+            // Toggle collapsed class.
+            fieldset.find(SELECTORS.DIVFITEMADVANCED).toggleClass(CSS.SHOW);
+
+            // Get corresponding hidden variable.
+            var statuselement = $('input[name=mform_showmore_' + fieldset.prop('id') + ']');
+
+            // Invert it and change the link text.
+            if (statuselement.val() === '0') {
+                statuselement.val(1);
+                $(e.target).addClass(CSS.SHOWLESS);
+                $(e.target).html(showless);
+            } else {
+                statuselement.val(0);
+                $(e.target).removeClass(CSS.SHOWLESS);
+                $(e.target).html(showmore);
+            }
+            return true;
+        }).fail(Notification.exception);
+
+        return this;
+    };
+
+    return {
+        /**
+         * Initialise this module.
+         * @method init
+         * @param {String} formid
+         * @return {ShowAdvanced}
+         */
+        init: function(formid) {
+            return new ShowAdvanced(formid);
+        }
+    };
+});
diff --git a/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-debug.js b/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-debug.js
deleted file mode 100644 (file)
index 4a032e0..0000000
Binary files a/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-debug.js and /dev/null differ
diff --git a/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-min.js b/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-min.js
deleted file mode 100644 (file)
index 8cfbaa1..0000000
Binary files a/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced-min.js and /dev/null differ
diff --git a/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced.js b/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced.js
deleted file mode 100644 (file)
index 6c2ee05..0000000
Binary files a/lib/form/yui/build/moodle-form-showadvanced/moodle-form-showadvanced.js and /dev/null differ
diff --git a/lib/form/yui/src/showadvanced/build.json b/lib/form/yui/src/showadvanced/build.json
deleted file mode 100644 (file)
index 2f81737..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-    "name": "moodle-form-showadvanced",
-    "builds": {
-        "moodle-form-showadvanced": {
-            "jsfiles": [
-                "showadvanced.js"
-            ]
-        }
-    }
-}
diff --git a/lib/form/yui/src/showadvanced/js/showadvanced.js b/lib/form/yui/src/showadvanced/js/showadvanced.js
deleted file mode 100644 (file)
index 564f86f..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * Provides the form showadvanced class.
- *
- * @module moodle-form-showadvanced
- */
-
-/**
- * A class to help show and hide advanced form content.
- *
- * @class M.form.showadvanced
- * @constructor
- * @extends Base
- */
-function SHOWADVANCED() {
-    SHOWADVANCED.superclass.constructor.apply(this, arguments);
-}
-
-var SELECTORS = {
-        FIELDSETCONTAINSADVANCED: 'fieldset.containsadvancedelements',
-        DIVFITEMADVANCED: 'div.fitem.advanced',
-        DIVFCONTAINER: 'div.fcontainer',
-        MORELESSLINK: 'fieldset.containsadvancedelements .moreless-toggler'
-    },
-    CSS = {
-        SHOW: 'show',
-        MORELESSACTIONS: 'moreless-actions',
-        MORELESSTOGGLER: 'moreless-toggler',
-        SHOWLESS: 'moreless-less'
-    },
-    WRAPPERS = {
-        FITEM: '<div class="fitem"></div>',
-        FELEMENT: '<div class="felement"></div>'
-    },
-    ATTRS = {};
-
-/**
- * The form ID attribute definition.
- *
- * @attribute formid
- * @type String
- * @default null
- * @writeOnce
- */
-ATTRS.formid = {
-    value: null
-};
-
-Y.extend(SHOWADVANCED, Y.Base, {
-    /**
-     * The initializer for the showadvanced instance.
-     *
-     * @method initializer
-     * @protected
-     */
-    initializer: function() {
-        var form = Y.one('#' + this.get('formid')),
-            fieldlist = form.all(SELECTORS.FIELDSETCONTAINSADVANCED);
-
-        // Look through fieldset divs that contain advanced elements.
-        fieldlist.each(this.processFieldset, this);
-
-        // Subscribe more/less links to click event.
-        form.delegate('click', this.switchState, SELECTORS.MORELESSLINK);
-        form.delegate('key', this.switchState, 'down:enter,32', SELECTORS.MORELESSLINK);
-    },
-
-    /**
-     * Process the supplied fieldset to add appropriate links, and ARIA roles.
-     *
-     * @method processFieldset
-     * @param {Node} fieldset The Node relating to the fieldset to add collapsing to.
-     * @chainable
-     */
-    processFieldset: function(fieldset) {
-        var statuselement = Y.one('input[name=mform_showmore_' + fieldset.get('id') + ']');
-        if (!statuselement) {
-            Y.log("M.form.showadvanced::processFieldset was called on an fieldset without a status field: '" +
-                fieldset.get('id') + "'", 'debug', 'moodle-form-showadvanced');
-            return this;
-        }
-
-        var morelesslink = Y.Node.create('<a href="#"></a>');
-        morelesslink.addClass(CSS.MORELESSTOGGLER);
-        if (statuselement.get('value') === '0') {
-            morelesslink.setHTML(M.util.get_string('showmore', 'form'));
-        } else {
-            morelesslink.setHTML(M.util.get_string('showless', 'form'));
-            morelesslink.addClass(CSS.SHOWLESS);
-            fieldset.all(SELECTORS.DIVFITEMADVANCED).addClass(CSS.SHOW);
-        }
-
-        // Get list of IDs controlled by this button to set the aria-controls attribute.
-        var idlist = [];
-        fieldset.all(SELECTORS.DIVFITEMADVANCED).each(function(node) {
-            idlist[idlist.length] = node.generateID();
-        });
-        morelesslink.setAttribute('role', 'button');
-        morelesslink.setAttribute('aria-controls', idlist.join(' '));
-
-        var fitem = Y.Node.create(WRAPPERS.FITEM);
-        fitem.addClass(CSS.MORELESSACTIONS);
-        var felement = Y.Node.create(WRAPPERS.FELEMENT);
-        felement.append(morelesslink);
-        fitem.append(felement);
-
-        fieldset.one(SELECTORS.DIVFCONTAINER).append(fitem);
-
-        return this;
-    },
-
-    /**
-     * Toggle the state for the fieldset that was clicked.
-     *
-     * @method switchState
-     * @param {EventFacade} e
-     */
-    switchState: function(e) {
-        e.preventDefault();
-        var fieldset = this.ancestor(SELECTORS.FIELDSETCONTAINSADVANCED);
-
-        // Toggle collapsed class.
-        fieldset.all(SELECTORS.DIVFITEMADVANCED).toggleClass(CSS.SHOW);
-
-        // Get corresponding hidden variable.
-        var statuselement = Y.one('input[name=mform_showmore_' + fieldset.get('id') + ']');
-
-        // Invert it and change the link text.
-        if (statuselement.get('value') === '0') {
-            statuselement.set('value', 1);
-            this.addClass(CSS.SHOWLESS);
-            this.setHTML(M.util.get_string('showless', 'form'));
-        } else {
-            statuselement.set('value', 0);
-            this.removeClass(CSS.SHOWLESS);
-            this.setHTML(M.util.get_string('showmore', 'form'));
-        }
-    }
-}, {
-    NAME: 'moodle-form-showadvanced',
-    ATTRS: ATTRS
-});
-
-M.form = M.form || {};
-M.form.showadvanced = M.form.showadvanced || function(params) {
-    return new SHOWADVANCED(params);
-};
diff --git a/lib/form/yui/src/showadvanced/meta/showadvanced.json b/lib/form/yui/src/showadvanced/meta/showadvanced.json
deleted file mode 100644 (file)
index befdc20..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "moodle-form-showadvanced": {
-        "requires": [
-            "node",
-            "base",
-            "selector-css3"
-        ]
-    }
-}
index caa39ef..acd0b21 100644 (file)
@@ -2919,8 +2919,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
             $PAGE->requires->yui_module('moodle-form-shortforms', 'M.form.shortforms', array(array('formid' => $formid)));
         }
         if (!empty($this->_advancedElements)){
-            $PAGE->requires->strings_for_js(array('showmore', 'showless'), 'form');
-            $PAGE->requires->yui_module('moodle-form-showadvanced', 'M.form.showadvanced', array(array('formid' => $formid)));
+            $PAGE->requires->js_call_amd('core_form/showadvanced', 'init', [$formid]);
         }
     }
 
index 5ebe7af..3c44836 100644 (file)
@@ -8010,6 +8010,28 @@ function moodle_major_version($fromdisk = false) {
 
 // MISCELLANEOUS.
 
+/**
+ * Gets the system locale
+ *
+ * @return string Retuns the current locale.
+ */
+function moodle_getlocale() {
+    global $CFG;
+
+    // Fetch the correct locale based on ostype.
+    if ($CFG->ostype == 'WINDOWS') {
+        $stringtofetch = 'localewin';
+    } else {
+        $stringtofetch = 'locale';
+    }
+
+    if (!empty($CFG->locale)) { // Override locale for all language packs.
+        return $CFG->locale;
+    }
+
+    return get_string($stringtofetch, 'langconfig');
+}
+
 /**
  * Sets the system locale
  *
@@ -8023,20 +8045,11 @@ function moodle_setlocale($locale='') {
 
     $oldlocale = $currentlocale;
 
-    // Fetch the correct locale based on ostype.
-    if ($CFG->ostype == 'WINDOWS') {
-        $stringtofetch = 'localewin';
-    } else {
-        $stringtofetch = 'locale';
-    }
-
     // The priority is the same as in get_string() - parameter, config, course, session, user, global language.
     if (!empty($locale)) {
         $currentlocale = $locale;
-    } else if (!empty($CFG->locale)) { // Override locale for all language packs.
-        $currentlocale = $CFG->locale;
     } else {
-        $currentlocale = get_string($stringtofetch, 'langconfig');
+        $currentlocale = moodle_getlocale();
     }
 
     // Do nothing if locale already set up.
index 93d32b5..1090652 100644 (file)
@@ -430,15 +430,16 @@ class navigation_node implements renderable {
      *
      * @param flat_navigation $nodes List of the found flat navigation nodes.
      * @param boolean $showdivider Show a divider before the first node.
+     * @param string $label A label for the collection of navigation links.
      */
-    public function build_flat_navigation_list(flat_navigation $nodes, $showdivider = false) {
+    public function build_flat_navigation_list(flat_navigation $nodes, $showdivider = false, $label = '') {
         if ($this->showinflatnavigation) {
             $indent = 0;
             if ($this->type == self::TYPE_COURSE || $this->key === self::COURSE_INDEX_PAGE) {
                 $indent = 1;
             }
             $flat = new flat_navigation_node($this, $indent);
-            $flat->set_showdivider($showdivider);
+            $flat->set_showdivider($showdivider, $label);
             $nodes->add($flat);
         }
         foreach ($this->children as $child) {
@@ -913,6 +914,12 @@ class navigation_node_collection implements IteratorAggregate, Countable {
      */
     protected $count = 0;
 
+    /**
+     * Label for collection of nodes.
+     * @var string
+     */
+    protected $collectionlabel = '';
+
     /**
      * Adds a navigation node to the collection
      *
@@ -988,6 +995,24 @@ class navigation_node_collection implements IteratorAggregate, Countable {
         return $keys;
     }
 
+    /**
+     * Set a label for this collection.
+     *
+     * @param string $label
+     */
+    public function set_collectionlabel($label) {
+        $this->collectionlabel = $label;
+    }
+
+    /**
+     * Return a label for this collection.
+     *
+     * @return string
+     */
+    public function get_collectionlabel() {
+        return $this->collectionlabel;
+    }
+
     /**
      * Fetches a node from this collection.
      *
@@ -3770,6 +3795,9 @@ class flat_navigation_node extends navigation_node {
     /** @var $showdivider bool Show a divider before this element */
     private $showdivider = false;
 
+    /** @var $collectionlabel string Label for a group of nodes */
+    private $collectionlabel = '';
+
     /**
      * A proxy constructor
      *
@@ -3791,6 +3819,31 @@ class flat_navigation_node extends navigation_node {
         $this->indent = $indent;
     }
 
+    /**
+     * Setter, a label is required for a flat navigation node that shows a divider.
+     *
+     * @param string $label
+     */
+    public function set_collectionlabel($label) {
+        $this->collectionlabel = $label;
+    }
+
+    /**
+     * Getter, get the label for this flat_navigation node, or it's parent if it doesn't have one.
+     *
+     * @return string
+     */
+    public function get_collectionlabel() {
+        if (!empty($this->collectionlabel)) {
+            return $this->collectionlabel;
+        }
+        if ($this->parent && ($this->parent instanceof flat_navigation_node || $this->parent instanceof flat_navigation)) {
+            return $this->parent->get_collectionlabel();
+        }
+        debugging('Navigation region requires a label', DEBUG_DEVELOPER);
+        return '';
+    }
+
     /**
      * Does this node represent a course section link.
      * @return boolean
@@ -3828,9 +3881,15 @@ class flat_navigation_node extends navigation_node {
     /**
      * Setter for "showdivider"
      * @param $val boolean
+     * @param $label string Label for the group of nodes
      */
-    public function set_showdivider($val) {
+    public function set_showdivider($val, $label = '') {
         $this->showdivider = $val;
+        if ($this->showdivider && empty($label)) {
+            debugging('Navigation region requires a label', DEBUG_DEVELOPER);
+        } else {
+            $this->set_collectionlabel($label);
+        }
     }
 
     /**
@@ -3848,7 +3907,6 @@ class flat_navigation_node extends navigation_node {
     public function set_indent($val) {
         $this->indent = $val;
     }
-
 }
 
 /**
@@ -3903,6 +3961,7 @@ class flat_navigation extends navigation_node_collection {
                 format_string($course->fullname, true, array('context' => $coursecontext));
 
             $flat = new flat_navigation_node(navigation_node::create($coursename, $url), 0);
+            $flat->set_collectionlabel($coursename);
             $flat->key = 'coursehome';
             $flat->icon = new pix_icon('i/course', '');
 
@@ -3930,9 +3989,9 @@ class flat_navigation extends navigation_node_collection {
                 }
             }
 
-            $this->page->navigation->build_flat_navigation_list($this, true);
+            $this->page->navigation->build_flat_navigation_list($this, true, get_string('site'));
         } else {
-            $this->page->navigation->build_flat_navigation_list($this, false);
+            $this->page->navigation->build_flat_navigation_list($this, false, get_string('site'));
         }
 
         $admin = $PAGE->settingsnav->find('siteadministration', navigation_node::TYPE_SITE_ADMIN);
@@ -3942,7 +4001,7 @@ class flat_navigation extends navigation_node_collection {
         }
         if ($admin) {
             $flat = new flat_navigation_node($admin, 0);
-            $flat->set_showdivider(true);
+            $flat->set_showdivider(true, get_string('sitesettings'));
             $flat->key = 'sitesettings';
             $flat->icon = new pix_icon('t/preferences', '');
             $this->add($flat);
@@ -3956,7 +4015,7 @@ class flat_navigation extends navigation_node_collection {
             $url = new moodle_url($PAGE->url, ['bui_addblock' => '', 'sesskey' => sesskey()]);
             $addablock = navigation_node::create(get_string('addblock'), $url);
             $flat = new flat_navigation_node($addablock, 0);
-            $flat->set_showdivider(true);
+            $flat->set_showdivider(true, get_string('blocksaddedit'));
             $flat->key = 'addblock';
             $flat->icon = new pix_icon('i/addblock', '');
             $this->add($flat);
@@ -3969,6 +4028,26 @@ class flat_navigation extends navigation_node_collection {
         }
     }
 
+
+    /**
+     * Override the parent so we can set a label for this collection if it has not been set yet.
+     *
+     * @param navigation_node $node Node to add
+     * @param string $beforekey If specified, adds before a node with this key,
+     *   otherwise adds at end
+     * @return navigation_node Added node
+     */
+    public function add(navigation_node $node, $beforekey=null) {
+        $result = parent::add($node, $beforekey);
+        // Extend the parent to get a name for the collection of nodes if required.
+        if (empty($this->collectionlabel)) {
+            if ($node instanceof flat_navigation_node) {
+                $this->set_collectionlabel($node->get_collectionlabel());
+            }
+        }
+
+        return $result;
+    }
 }
 
 /**
index 530bce0..9f6e4af 100644 (file)
@@ -3,6 +3,7 @@ information provided here is intended especially for developers.
 
 === 3.7 ===
 
+* Nodes in the navigation api can have labels for each group. See set/get_collectionlabel().
 * The method core_user::is_real_user() now returns false for userid = 0 parameter
 * 'mform1' dependencies (in themes, js...) will stop working because a randomly generated string has been added to the id
 attribute on forms to avoid collisions in forms loaded in AJAX requests.
index 0bcc236..ff35374 100644 (file)
Binary files a/message/amd/build/notification_processor_settings.min.js and b/message/amd/build/notification_processor_settings.min.js differ
index d4347b0..3d1c9f6 100644 (file)
Binary files a/message/amd/build/preferences_notifications_list_controller.min.js and b/message/amd/build/preferences_notifications_list_controller.min.js differ
index 6964dfb..8ce9f05 100644 (file)
Binary files a/message/amd/build/preferences_processor_form.min.js and b/message/amd/build/preferences_processor_form.min.js differ
index bb3763b..1fb2132 100644 (file)
  * @copyright  2016 Ryan Wyllie <ryan@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-define(['jquery', 'core/ajax', 'core/notification', 'core/fragment', 'core/templates', 'core/str', 'tool_lp/dialogue'],
-        function($, Ajax, Notification, Fragment, Templates, Str, Dialogue) {
+define([
+        'jquery',
+        'core/ajax',
+        'core/str',
+        'core/notification',
+        'core/custom_interaction_events',
+        'core/modal',
+        'core/modal_registry',
+        'core/fragment',
+        ],
+        function(
+            $,
+            Ajax,
+            Str,
+            Notification,
+            CustomEvents,
+            Modal,
+            ModalRegistry,
+            Fragment
+        ) {
 
+    var registered = false;
     var SELECTORS = {
+        SAVE_BUTTON: '[data-action="save"]',
+        CANCEL_BUTTON: '[data-action="cancel"]',
         PROCESSOR: '[data-processor-name]',
         PREFERENCE_ROW: '[data-region="preference-row"]',
     };
 
     /**
-     * Constructor for the notification processor settings.
+     * Constructor for the Modal.
      *
-     * @param {object} element jQuery object root element of the processor
+     * @param {object} root The root jQuery element for the modal.
      */
-    var NotificationProcessorSettings = function(element) {
-        this.root = $(element);
-        this.name = this.root.attr('data-name');
-        this.userId = this.root.attr('data-user-id');
-        this.contextId = this.root.attr('data-context-id');
+    var NotificationProcessorSettings = function(root) {
+        Modal.call(this, root);
+        this.name = null;
+        this.userId = null;
+        this.contextId = null;
+        this.element = null;
+        this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
+        this.cancelButton = this.getFooter().find(SELECTORS.CANCEL_BUTTON);
+    };
+
+    NotificationProcessorSettings.TYPE = 'core_message-notification_processor_settings';
+    NotificationProcessorSettings.prototype = Object.create(Modal.prototype);
+    NotificationProcessorSettings.prototype.constructor = NotificationProcessorSettings;
+
+    /**
+     * Set the userid to the given value.
+     *
+     * @method setUserId
+     * @param {int} id The notification userid
+     */
+    NotificationProcessorSettings.prototype.setUserId = function(id) {
+        this.userId = id;
+    };
+
+    /**
+     * Retrieve the current userid, if any.
+     *
+     * @method getUserId
+     * @return {int|null} The notification userid
+     */
+    NotificationProcessorSettings.prototype.getUserId = function() {
+        return this.userId;
+    };
+
+    /**
+     * Set the object to the given value.
+     *
+     * @method setElement
+     * @param {object} element The notification node element.
+     */
+    NotificationProcessorSettings.prototype.setElement = function(element) {
+        this.element = element;
+    };
+
+    /**
+     * Retrieve the current element, if any.
+     *
+     * @method getElement
+     * @return {object|null} The notification node element.
+     */
+    NotificationProcessorSettings.prototype.getElement = function() {
+        return this.element;
+    };
+
+    /**
+     * Set the name to the given value.
+     *
+     * @method setName
+     * @param {string} name The notification name.
+     */
+    NotificationProcessorSettings.prototype.setName = function(name) {
+        this.name = name;
+    };
+
+    /**
+     * Retrieve the current name, if any.
+     *
+     * @method getName
+     * @return {string|null} The notification name.
+     */
+    NotificationProcessorSettings.prototype.getName = function() {
+        return this.name;
+    };
+    /**
+     * Set the context id to the given value.
+     *
+     * @method setContextId
+     * @param {Number} id The notification context id
+     */
+    NotificationProcessorSettings.prototype.setContextId = function(id) {
+        this.contextId = id;
+    };
+
+    /**
+     * Retrieve the current context id, if any.
+     *
+     * @method getContextId
+     * @return {Number|null} The notification context id
+     */
+    NotificationProcessorSettings.prototype.getContextId = function() {
+        return this.contextId;
+    };
+
+    /**
+     * Get the form element from the modal.
+     *
+     * @method getForm
+     * @return {object}
+     */
+    NotificationProcessorSettings.prototype.getForm = function() {
+        return this.getBody().find('form');
+    };
+
+    /**
+     * Disable the buttons in the footer.
+     *
+     * @method disableButtons
+     */
+    NotificationProcessorSettings.prototype.disableButtons = function() {
+        this.saveButton.prop('disabled', true);
+        this.cancelButton.prop('disabled', true);
+    };
+
+    /**
+     * Enable the buttons in the footer.
+     *
+     * @method enableButtons
+     */
+    NotificationProcessorSettings.prototype.enableButtons = function() {
+        this.saveButton.prop('disabled', false);
+        this.cancelButton.prop('disabled', false);
+    };
+
+    /**
+     * Load the title for the modal to the appropriate value
+     * depending on message outputs.
+     *
+     * @method loadTitleContent
+     * @return {object} A promise resolved with the new title text.
+     */
+    NotificationProcessorSettings.prototype.loadTitleContent = function() {
+        this.titlePromise = Str.get_string('processorsettings', 'message');
+        this.setTitle(this.titlePromise);
+
+        return this.titlePromise;
+    };
+
+    /**
+     * Load the body for the modal to the appropriate value
+     * depending on message outputs.
+     *
+     * @method loadBodyContent
+     * @return {object} A promise resolved with the fragment html and js from
+     */
+    NotificationProcessorSettings.prototype.loadBodyContent = function() {
+        this.disableButtons();
+
+        var args = {
+            userid: this.getUserId(),
+            type: this.getName(),
+        };
+
+        this.bodyPromise = Fragment.loadFragment('message', 'processor_settings', this.getContextId(), args);
+        this.setBody(this.bodyPromise);
+
+        this.bodyPromise.then(function() {
+            this.enableButtons();
+            return;
+        }.bind(this))
+        .fail(Notification.exception);
+
+        return this.bodyPromise;
     };
 
     /**
-     * Show the notification processor settings dialogue.
+     * Load both the title and body content.
+     *
+     * @method loadAllContent
+     * @return {object} promise
+     */
+    NotificationProcessorSettings.prototype.loadAllContent = function() {
+        return $.when(this.loadTitleContent(), this.loadBodyContent());
+    };
+
+    /**
+     * Load the modal content before showing it. This
+     * is to allow us to re-use the same modal for creating and
+     * editing different message outputs within the page.
      *
      * @method show
      */
     NotificationProcessorSettings.prototype.show = function() {
-        Fragment.loadFragment('message', 'processor_settings', this.contextId, {
-            userid: this.userId,
-            type: this.name,
-        })
-        .done(function(html, js) {
-            Str.get_string('processorsettings', 'message').done(function(string) {
-                var dialogue = new Dialogue(
-                    string,
-                    html,
-                    function() {
-                        Templates.runTemplateJS(js);
-                    },
-                    function() {
-                        // Removed dialogue from the DOM after close.
-                        dialogue.close();
-                    }
-                );
-
-                $(document).on('mpp:formsubmitted', function() {
-                    dialogue.close();
-                    this.updateConfiguredStatus();
-                }.bind(this));
-
-                $(document).on('mpp:formcancelled', function() {
-                    dialogue.close();
-                });
-            }.bind(this));
-        }.bind(this));
+        this.loadAllContent();
+        Modal.prototype.show.call(this);
+    };
+
+    /**
+     * Clear the notification from the modal when it's closed so
+     * that it is loaded fresh next time it's displayed.
+     *
+     * @method hide
+     */
+    NotificationProcessorSettings.prototype.hide = function() {
+        Modal.prototype.hide.call(this);
+        this.setContextId(null);
+        this.setName(null);
+        this.setUserId(null);
     };
 
     /**
@@ -86,7 +263,7 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/fragment', 'core/templ
      * @return {Promise|boolean}
      */
     NotificationProcessorSettings.prototype.updateConfiguredStatus = function() {
-        var processorHeader = this.root.closest(SELECTORS.PROCESSOR);
+        var processorHeader = $(this.getElement()).closest(SELECTORS.PROCESSOR);
 
         if (!processorHeader.hasClass('unconfigured')) {
             return false;
@@ -114,5 +291,44 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/fragment', 'core/templ
             });
     };
 
+    /**
+     * Set up all of the event handling for the modal.
+     *
+     * @method registerEventListeners
+     */
+    NotificationProcessorSettings.prototype.registerEventListeners = function() {
+        // Apply parent event listeners.
+        Modal.prototype.registerEventListeners.call(this);
+
+        // When the user clicks the save button we trigger the form submission.
+        this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, function(e, data) {
+            this.getForm().submit();
+            data.originalEvent.preventDefault();
+        }.bind(this));
+
+        this.getModal().on('mpp:formsubmitted', function(e) {
+            this.hide();
+            this.updateConfiguredStatus();
+            e.stopPropagation();
+        }.bind(this));
+
+        this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, function(e, data) {
+            this.hide();
+            data.originalEvent.preventDefault();
+            e.stopPropagation();
+        }.bind(this));
+    };
+
+    // Automatically register with the modal registry the first time this module is imported
+    // so that you can create modals
+    // of this type using the modal factory.
+    if (!registered) {
+        ModalRegistry.register(
+                                NotificationProcessorSettings.TYPE,
+                                NotificationProcessorSettings,
+                                'core/modal_save_cancel');
+        registered = true;
+    }
+
     return NotificationProcessorSettings;
 });
index 14ab2e6..8b73113 100644 (file)
  * @copyright  2016 Ryan Wyllie <ryan@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-define(['jquery', 'core/ajax', 'core/notification', 'core/custom_interaction_events', 'core_message/notification_preference',
-        'core_message/notification_processor_settings'],
-        function($, Ajax, Notification, CustomEvents, NotificationPreference, NotificationProcessorSettings) {
+define(['jquery',
+        'core/ajax',
+        'core/notification',
+        'core/custom_interaction_events',
+        'core_message/notification_preference',
+        'core_message/notification_processor_settings',
+        'core/modal_factory',
+        ],
+        function(
+          $,
+          Ajax,
+          Notification,
+          CustomEvents,
+          NotificationPreference,
+          NotificationProcessorSettings,
+          ModalFactory
+        ) {
 
     var SELECTORS = {
         DISABLE_NOTIFICATIONS: '[data-region="disable-notification-container"] [data-disable-notifications]',
@@ -143,11 +157,25 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/custom_interaction_eve
             }
         }.bind(this));
 
-        this.root.on(CustomEvents.events.activate, SELECTORS.PROCESSOR_SETTING, function(e, data) {
+        var eventFormPromise = ModalFactory.create({
+            type: NotificationProcessorSettings.TYPE,
+        });
+
+        this.root.on(CustomEvents.events.activate, SELECTORS.PROCESSOR_SETTING, function(e) {
             var element = $(e.target).closest(SELECTORS.PROCESSOR_SETTING);
-            var processorSettings = new NotificationProcessorSettings(element);
-            processorSettings.show();
-            data.originalEvent.preventDefault();
+
+            e.preventDefault();
+            eventFormPromise.then(function(modal) {
+                // Configure modal with element settings.
+                modal.setUserId($(element).attr('data-user-id'));
+                modal.setName($(element).attr('data-name'));
+                modal.setContextId($(element).attr('data-context-id'));
+                modal.setElement(element);
+                modal.show();
+
+                e.stopImmediatePropagation();
+                return;
+            }).fail(Notification.exception);
         });
 
         CustomEvents.define(disabledNotificationsElement, [
index 53b3766..e458d8b 100644 (file)
@@ -22,8 +22,8 @@
  * @copyright  2016 Ryan Wyllie <ryan@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-define(['jquery', 'core/ajax', 'core/notification', 'core/custom_interaction_events'],
-        function($, Ajax, Notification, CustomEvents) {
+define(['jquery', 'core/ajax', 'core/notification'],
+        function($, Ajax, Notification) {
     /**
      * Constructor for the ProcessorForm.
      *
@@ -37,18 +37,9 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/custom_interaction_eve
         this.root.find('form').on('submit', function(e) {
             e.preventDefault();
             this.save().done(function() {
-                $(document).trigger('mpp:formsubmitted');
+                $(element).trigger('mpp:formsubmitted');
             });
         }.bind(this));
-
-        var cancelButton = this.root.find('[data-cancel-button]');
-        CustomEvents.define(cancelButton, [
-            CustomEvents.events.activate
-        ]);
-
-        cancelButton.on(CustomEvents.events.activate, function() {
-            $(document).trigger('mpp:formcancelled');
-        });
     };
 
     /**
index 42be717..dd77931 100644 (file)
     </div>
     <form>
         {{{formhtml}}}
-        <div class="form-actions m-t-1">
-            <button type="submit" class="btn btn-primary">{{#str}} savechanges {{/str}}</button>
-            <button type="button" class="btn" data-cancel-button>{{#str}} cancel {{/str}}</button>
-        </div>
     </form>
 </div>
 {{#js}}
diff --git a/question/amd/build/qbankmanager.min.js b/question/amd/build/qbankmanager.min.js
new file mode 100644 (file)
index 0000000..50dd343
Binary files /dev/null and b/question/amd/build/qbankmanager.min.js differ
diff --git a/question/amd/src/qbankmanager.js b/question/amd/src/qbankmanager.js
new file mode 100644 (file)
index 0000000..78ff864
--- /dev/null
@@ -0,0 +1,55 @@
+// 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 question ajax actions.
+ *
+ * @module     core_question/qbankmanager
+ * @class      qbankmanager
+ * @package    core_question
+ * @copyright 2018 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/pubsub', 'core/checkbox-toggleall'], function($, PubSub, ToggleAll) {
+
+    var registerListeners = function() {
+        PubSub.subscribe(ToggleAll.events.checkboxToggled, toggleButtonStates);
+    };
+
+    var toggleButtonStates = function(data) {
+        if ('qbank' !== data.toggleGroupName) {
+            return;
+        }
+
+        setButtonState(data.anyChecked);
+    };
+
+    var setButtonState = function(state) {
+        var buttons = $('.modulespecificbuttonscontainer').find('input, select, link');
+        buttons.attr('disabled', !state);
+    };
+
+    return {
+        /**
+         * Set up the Question Bank Manager.
+         *
+         * @method init
+         */
+        init: function() {
+            setButtonState(false);
+            registerListeners();
+        },
+    };
+});
index 289aaba..cddbaf7 100644 (file)
@@ -26,7 +26,9 @@ class checkbox_column extends column_base {
     protected $strselect;
 
     public function init() {
-        $this->strselect = get_string('select');
+        global $PAGE;
+
+        $PAGE->requires->js_call_amd('core/checkbox-toggleall', 'init');
     }
 
     public function get_name() {
@@ -34,21 +36,41 @@ class checkbox_column extends column_base {
     }
 
     protected function get_title() {
-        return '<input type="checkbox" disabled="disabled" id="qbheadercheckbox" />';
+        $input = \html_writer::empty_tag('input', [
+            'id' => 'qbheadercheckbox',
+            'name' => 'qbheadercheckbox',
+            'type' => 'checkbox',
+            'value' => '1',
+            'data-action' => 'toggle',
+            'data-toggle' => 'master',
+            'data-togglegroup' => 'qbank',
+            'data-toggle-selectall' => get_string('selectall', 'moodle'),
+            'data-toggle-deselectall' => get_string('deselectall', 'moodle'),
+        ]);
+
+        $label = \html_writer::tag('label', get_string('selectall', 'moodle'), [
+            'class' => 'accesshide',
+            'for' => 'qbheadercheckbox',
+        ]);
+
+        return $input . $label;
     }
 
     protected function get_title_tip() {
-        global $PAGE;
-        $PAGE->requires->strings_for_js(array('selectall', 'deselectall'), 'moodle');
-        $PAGE->requires->yui_module('moodle-question-qbankmanager', 'M.question.qbankmanager.init');
         return get_string('selectquestionsforbulk', 'question');
-
     }
 
     protected function display_content($question, $rowclasses) {
-        global $PAGE;
-        echo '<input title="' . $this->strselect . '" type="checkbox" name="q' .
-                $question->id . '" id="checkq' . $question->id . '" value="1"/>';
+        echo \html_writer::empty_tag('input', [
+            'title' => get_string('select'),
+            'type' => 'checkbox',
+            'name' => "q{$question->id}",
+            'id' => "checkq{$question->id}",
+            'value' => '1',
+            'data-action' => 'toggle',
+            'data-toggle' => 'slave',
+            'data-togglegroup' => 'qbank',
+        ]);
     }
 
     public function get_required_fields() {
index ec5b81f..9225af8 100644 (file)
@@ -688,7 +688,7 @@ class view {
     protected function display_question_list($contexts, $pageurl, $categoryandcontext,
             $cm = null, $recurse=1, $page=0, $perpage=100, $showhidden=false,
             $showquestiontext = false, $addcontexts = array()) {
-        global $CFG, $DB, $OUTPUT;
+        global $CFG, $DB, $OUTPUT, $PAGE;
 
         // This function can be moderately slow with large question counts and may time out.
         // We probably do not want to raise it to unlimited, so randomly picking 5 minutes.
@@ -758,6 +758,8 @@ class view {
         }
         echo '</div>';
 
+        $PAGE->requires->js_call_amd('core_question/qbankmanager', 'init');
+
         $this->display_bottom_controls($totalnumber, $recurse, $category, $catcontext, $addcontexts);
 
         echo '</fieldset>';
diff --git a/question/tests/behat/select_questions.feature b/question/tests/behat/select_questions.feature
new file mode 100644 (file)
index 0000000..b4b766e
--- /dev/null
@@ -0,0 +1,56 @@
+@core @core_question
+Feature: The questions in the question bank can be selected in various ways
+  In selected to do something for questions
+  As a teacher
+  I want to choose them to move, delete it.
+
+  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              | user     | questiontext    |
+      | Test questions   | essay     | A question 1 name | admin    | Question 1 text |
+      | Test questions   | essay     | B question 2 name | teacher1 | Question 2 text |
+      | Test questions   | numerical | C question 3 name | teacher1 | Question 3 text |
+    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
+
+  @javascript
+  Scenario: The question text can be chosen all in the list of questions
+    Given the field "Select all" matches value ""
+    When I click on "Select all" "checkbox"
+    And the field "A question 1 name" matches value "1"
+    And the field "B question 2 name" matches value "1"
+    And the field "C question 3 name" matches value "1"
+    Then I click on "Deselect all" "checkbox"
+    And the field "A question 1 name" matches value ""
+    And the field "B question 2 name" matches value ""
+    And the field "C question 3 name" matches value ""
+
+  @javascript
+  Scenario: The question text can be chosen in the list of questions
+    Given the field "Select all" matches value ""
+    When I click on "A question 1 name" "checkbox"
+    Then the field "Select all" matches value ""
+    And I click on "B question 2 name" "checkbox"
+    And I click on "C question 3 name" "checkbox"
+    And the field "Deselect all" matches value "1"
+
+  @javascript
+  Scenario: The action button can be disabled when the question not be chosen in the list of questions
+    Given the "Delete" "button" should be disabled
+    And the "Move to >>" "button" should be disabled
+    When I click on "Select all" "checkbox"
+    Then the "Delete" "button" should be enabled
+    And the "Move to >>" "button" should be enabled
diff --git a/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-debug.js b/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-debug.js
deleted file mode 100644 (file)
index fbe45fe..0000000
Binary files a/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-debug.js and /dev/null differ
diff --git a/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-min.js b/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-min.js
deleted file mode 100644 (file)
index 22b2432..0000000
Binary files a/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager-min.js and /dev/null differ
diff --git a/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager.js b/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager.js
deleted file mode 100644 (file)
index fbe45fe..0000000
Binary files a/question/yui/build/moodle-question-qbankmanager/moodle-question-qbankmanager.js and /dev/null differ
diff --git a/question/yui/src/qbankmanager/build.json b/question/yui/src/qbankmanager/build.json
deleted file mode 100644 (file)
index 2c8d36e..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-    "name": "moodle-question-qbankmanager",
-    "builds": {
-        "moodle-question-qbankmanager": {
-            "jsfiles": [
-                "qbankmanager.js"
-            ]
-        }
-    }
-}
diff --git a/question/yui/src/qbankmanager/js/qbankmanager.js b/question/yui/src/qbankmanager/js/qbankmanager.js
deleted file mode 100644 (file)
index cb6ea38..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-// 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/>.
-
-/*
- * Question Bank Management.
- *
- * @package    question
- * @copyright  2014 Andrew Nicols
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-/**
- * Questionbank Management.
- *
- * @module moodle-question-qbankmanager
- */
-
-/**
- * Question Bank Management.
- *
- * @class M.question.qbankmanager
- */
-
-var manager = {
-    /**
-     * A reference to the header checkbox.
-     *
-     * @property _header
-     * @type Node
-     * @private
-     */
-    _header: null,
-
-    /**
-     * A reference to the add to quiz button.
-     *
-     * @property _addbutton
-     * @type Node
-     * @private
-     */
-    _addbutton: null,
-
-    /**
-     * The ID of the first checkbox on the page.
-     *
-     * @property _firstCheckbox
-     * @type Node
-     * @private
-     */
-    _firstCheckbox: null,
-
-    /**
-     * Set up the Question Bank Manager.
-     *
-     * @method init
-     */
-    init: function() {
-        // Find the header checkbox, and set the initial values.
-        this._header = Y.one('#qbheadercheckbox');
-        if (!this._header) {
-            return;
-        }
-        this._header.setAttrs({
-            disabled: false,
-            title: M.util.get_string('selectall', 'moodle')
-        });
-
-        this._header.on('click', this._headerClick, this);
-
-        this._addbutton = Y.one('.modulespecificbuttonscontainer input[name="add"]');
-        // input[name="add"] is not always available.
-        if (this._addbutton) {
-            this._addbutton.setAttrs({
-                disabled: true
-            });
-
-            this._header.on('click', this._questionClick, this);
-            Y.one('.categoryquestionscontainer').delegate('change', this._questionClick,
-                'td.checkbox input[type="checkbox"]', this);
-        }
-
-        // Store the first checkbox details.
-        var table = this._header.ancestor('table');
-        this._firstCheckbox = table.one('tbody tr td.checkbox input');
-    },
-
-    /**
-     * Handle toggling of the header checkbox.
-     *
-     * @method _headerClick
-     * @private
-     */
-    _headerClick: function() {
-        // Get the list of questions we affect.
-        var categoryQuestions = Y.one('#categoryquestions')
-                .all('[type=checkbox],[type=radio]');
-
-        // We base the state of all of the questions on the state of the first.
-        if (this._firstCheckbox.get('checked')) {
-            categoryQuestions.set('checked', false);
-            this._header.setAttribute('title', M.util.get_string('selectall', 'moodle'));
-        } else {
-            categoryQuestions.set('checked', true);
-            this._header.setAttribute('title', M.util.get_string('deselectall', 'moodle'));
-        }
-
-        this._header.set('checked', false);
-    },
-
-    /**
-     * Handle toggling of a question checkbox.
-     *
-     * @method _questionClick
-     * @private
-     */
-    _questionClick: function() {
-        var areChecked = Y.all('td.checkbox input[type="checkbox"]:checked').size();
-        this._addbutton.setAttrs({
-            disabled: (areChecked === 0)
-        });
-    }
-};
-
-M.question = M.question || {};
-M.question.qbankmanager = M.question.qbankmanager || manager;
diff --git a/question/yui/src/qbankmanager/meta/qbankmanager.json b/question/yui/src/qbankmanager/meta/qbankmanager.json
deleted file mode 100644 (file)
index 6410cbc..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-    "moodle-question-qbankmanager": {
-        "requires": [
-            "node",
-            "selector-css3"
-        ]
-    }
-}
index d942fcb..b44726f 100644 (file)
@@ -51,6 +51,8 @@ $templatecontext = [
     'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu)
 ];
 
-$templatecontext['flatnavigation'] = $PAGE->flatnav;
+$nav = $PAGE->flatnav;
+$templatecontext['flatnavigation'] = $nav;
+$templatecontext['firstcollectionlabel'] = $nav->get_collectionlabel();
 echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext);
 
index 022df60..49ed3ba 100644 (file)
@@ -47,7 +47,7 @@
     <div id="page" class="container-fluid">
         <div id="page-content" class="row pb-3">
             <div id="region-main-box" class="col-12">
-                <section id="region-main">
+                <section id="region-main" aria-label="{{#str}}content{{/str}}">
                     {{{ output.course_content_header }}}
                     {{{ output.main_content }}}
                     {{{ output.activity_navigation }}}
index 7b329cb..d0425d3 100644 (file)
@@ -68,7 +68,7 @@
                     <div> {{{ output.region_main_settings_menu }}} </div>
                 </div>
                 {{/hasregionmainsettingsmenu}}
-                <section id="region-main" {{#hasblocks}}class="has-blocks mb-3"{{/hasblocks}}>
+                <section id="region-main" {{#hasblocks}}class="has-blocks mb-3"{{/hasblocks}} aria-label="{{#str}}content{{/str}}">
 
                     {{#hasregionmainsettingsmenu}}
                         <div class="region_main_settings_menu_proxy"></div>
@@ -80,7 +80,7 @@
 
                 </section>
                 {{#hasblocks}}
-                <section data-region="blocks-column" class="d-print-none">
+                <section data-region="blocks-column" class="d-print-none" aria-label="{{#str}}blocks{{/str}}">
                     {{{ sidepreblocks }}}
                 </section>
                 {{/hasblocks}}
index 07d0c04..207081f 100644 (file)
@@ -63,7 +63,7 @@
         ]
     }
 }}
-<nav role="navigation">
+<nav role="navigation" aria-label="{{#str}}breadcrumb, access{{/str}}">
     <ol class="breadcrumb">
         {{#get_items}}
             {{#has_action}}
index f19e907..3c9d44a 100644 (file)
         ]
     }
 }}
-<nav class="list-group">
+<nav class="list-group" aria-label="{{firstcollectionlabel}}">
 {{# flatnavigation }}
     {{#showdivider}}
 </nav>
-<nav class="list-group m-t-1">
+<nav class="list-group m-t-1" aria-label="{{get_collectionlabel}}">
     {{/showdivider}}
     {{#action}}
     <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}}>
index 4cb4c34..9827bbd 100644 (file)
@@ -28,7 +28,7 @@
         {{{ output.login_info }}}
         <div class="tool_usertours-resettourcontainer"></div>
         {{{ output.home_link }}}
-        <nav class="nav navbar-nav d-md-none">
+        <nav class="nav navbar-nav d-md-none" aria-label="{{#str}}custommenu, admin{{/str}}">
             {{# output.custom_menu_flat }}
                 <ul class="list-unstyled pt-3">
                     {{> theme_boost/custom_menu_footer }}
@@ -38,4 +38,4 @@
         {{{ output.standard_footer_html }}}
         {{{ output.standard_end_of_body_html }}}
     </div>
-</footer>
\ No newline at end of file
+</footer>
index ebca109..f44017d 100644 (file)
@@ -40,7 +40,7 @@
     <div id="page" class="container-fluid mt-0">
         <div id="page-content" class="row">
             <div id="region-main-box" class="col-12">
-                <section id="region-main" class="col-12">
+                <section id="region-main" class="col-12" aria-label="{{#str}}content{{/str}}">
                     {{{ output.course_content_header }}}
                     {{{ output.main_content }}}
                     {{{ output.course_content_footer }}}
index f62c6c8..0330892 100644 (file)
@@ -56,7 +56,7 @@
         </div>
 
         <div id="page-content" class="row">
-            <section id="region-main" class="col-12">
+            <section id="region-main" class="col-12" aria-label="{{#str}}content{{/str}}">
                 {{{ output.main_content }}}
             </section>
         </div>
index dbbca33..8dadab0 100644 (file)
@@ -17,7 +17,7 @@
 {{!
     secure navbar.
 }}
-<nav class="fixed-top navbar navbar-light bg-white navbar-expand moodle-has-zindex">
+<nav class="fixed-top navbar navbar-light bg-white navbar-expand moodle-has-zindex" aria-label="{{#str}}navigation{{/str}}">
 
         <a href="{{{ config.wwwroot }}}" class="navbar-brand {{# output.should_display_navbar_logo }}has-logo{{/ output.should_display_navbar_logo }}
             {{^ output.should_display_navbar_logo }}
@@ -37,4 +37,4 @@
             {{{ output.secure_login_info }}}
             </li>
         </ul>
-</nav>
\ No newline at end of file
+</nav>
index 385e3ab..a6ceaba 100644 (file)
@@ -59,7 +59,7 @@
 
         <div id="page-content" class="row">
             <div id="region-main-box" class="col-12">
-                <section id="region-main" {{#hasblocks}}class="has-blocks"{{/hasblocks}}>
+                <section id="region-main" {{#hasblocks}}class="has-blocks"{{/hasblocks}} aria-label="{{#str}}content{{/str}}">
 
                     {{{ output.course_content_header }}}
                     {{{ output.main_content }}}
@@ -67,7 +67,7 @@
 
                 </section>
                 {{#hasblocks}}
-                <section data-region="blocks-column">
+                <section data-region="blocks-column" aria-label="{{#str}}blocks{{/str}}">
                     {{{ sidepreblocks }}}
                 </section>
                 {{/hasblocks}}