Merge branch 'MDL-66374-master' of git://github.com/jleyva/moodle
authorAdrian Greeve <abgreeve@gmail.com>
Wed, 28 Aug 2019 05:54:03 +0000 (13:54 +0800)
committerAdrian Greeve <abgreeve@gmail.com>
Wed, 28 Aug 2019 05:54:03 +0000 (13:54 +0800)
213 files changed:
admin/classes/local/settings/filesize.php [new file with mode: 0644]
admin/roles/classes/define_role_table_advanced.php
admin/settings/security.php
admin/templates/setting_configfilesize.mustache [new file with mode: 0644]
admin/tests/behat/manage_tokens.feature
admin/tool/lp/amd/build/competencies.min.js
admin/tool/lp/amd/build/competencies.min.js.map
admin/tool/lp/amd/build/competencyactions.min.js
admin/tool/lp/amd/build/competencyactions.min.js.map
admin/tool/lp/amd/build/competencypicker.min.js
admin/tool/lp/amd/build/competencypicker.min.js.map
admin/tool/lp/amd/build/course_competency_settings.min.js
admin/tool/lp/amd/build/course_competency_settings.min.js.map
admin/tool/lp/amd/build/grade_dialogue.min.js
admin/tool/lp/amd/build/grade_dialogue.min.js.map
admin/tool/lp/amd/build/grade_user_competency_inline.min.js
admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map
admin/tool/lp/amd/src/competencies.js
admin/tool/lp/amd/src/competencyactions.js
admin/tool/lp/amd/src/competencypicker.js
admin/tool/lp/amd/src/course_competency_settings.js
admin/tool/lp/amd/src/grade_dialogue.js
admin/tool/lp/amd/src/grade_user_competency_inline.js
admin/tool/lp/tests/behat/framework_crud.feature
admin/tool/lp/tests/behat/template_crud.feature
admin/tool/mobile/classes/api.php
admin/tool/uploaduser/index.php
admin/tool/uploaduser/locallib.php
admin/tool/uploaduser/tests/behat/upload_users.feature
analytics/classes/local/analyser/by_course.php
analytics/classes/local/analysis/result_file.php
analytics/tests/behat/manage_models.feature
analytics/tests/fixtures/test_target_course_users.php
analytics/tests/prediction_test.php
auth/ldap/lang/en/auth_ldap.php
auth/ldap/lang/en/deprecated.txt [deleted file]
blocks/myoverview/amd/build/view.min.js
blocks/myoverview/amd/build/view.min.js.map
blocks/myoverview/amd/src/view.js
blocks/myoverview/classes/output/main.php
blocks/myoverview/lang/en/block_myoverview.php
blocks/myoverview/lib.php
blocks/myoverview/settings.php
blocks/myoverview/styles.css [new file with mode: 0644]
blocks/myoverview/templates/nav-grouping-selector.mustache
blocks/myoverview/templates/view-cards.mustache
blocks/myoverview/templates/view-list.mustache
blocks/myoverview/templates/view-summary.mustache
blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature [new file with mode: 0644]
blocks/myoverview/tests/behat/block_myoverview_dashboard.feature
blocks/myoverview/tests/behat/block_myoverview_hidden.feature
blocks/myoverview/tests/behat/block_myoverview_pagelimit_persistence.feature
blocks/myoverview/tests/behat/block_myoverview_progress.feature
blocks/myoverview/tests/privacy_test.php
blocks/myoverview/version.php
blocks/recentlyaccesseditems/db/upgrade.php
blog/rsslib.php
calendar/classes/local/event/forms/create.php
calendar/externallib.php
calendar/lib.php
calendar/renderer.php
comment/classes/external.php
comment/lib.php
comment/tests/externallib_test.php
comment/upgrade.txt [new file with mode: 0644]
composer.json
composer.lock
course/classes/analytics/indicator/activities_due.php
course/classes/external/course_summary_exporter.php
course/externallib.php
course/lib.php
course/management.php
course/renderer.php
course/search.php
course/templates/activity_navigation.mustache
course/templates/course_search_form.mustache
course/templates/coursecard.mustache
course/tests/indicators_test.php
course/upgrade.txt
dataformat/json/classes/writer.php
grade/grading/form/rubric/tests/behat/grade_calculation.feature
grade/grading/form/rubric/tests/behat/negative_points.feature
grade/report/history/tests/behat/basic_functionality.feature
grade/report/singleview/classes/local/ui/dropdown_attribute.php
grade/report/singleview/templates/dropdown_attribute.mustache
grade/report/singleview/tests/behat/singleview.feature
install/lang/el/error.php
install/lang/el_wp/admin.php
install/lang/fr_wp/langconfig.php [new file with mode: 0644]
install/lang/ro_wp/moodle.php
lang/en/access.php
lang/en/admin.php
lang/en/calendar.php
lang/en/deprecated.txt
lang/en/hub.php
lang/en/install.php
lang/en/moodle.php
lang/en/notes.php
lib/accesslib.php
lib/adminlib.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-autocomplete.min.js.map
lib/amd/src/form-autocomplete.js
lib/behat/behat_base.php
lib/behat/classes/partial_named_selector.php
lib/classes/event/context_locked.php [new file with mode: 0644]
lib/classes/event/context_unlocked.php [new file with mode: 0644]
lib/classes/output/mustache_template_source_loader.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/editor/tinymce/tests/behat/disablecontrol.feature
lib/filelib.php
lib/moodlelib.php
lib/templates/filemanager_modal_generallayout.mustache
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_general.php
lib/tests/event_context_locked_test.php [new file with mode: 0644]
lib/tests/filelib_test.php
lib/tests/fixtures/upload_users_enrol_date_period.csv [new file with mode: 0644]
lib/tests/htmlpurifier_test.php
lib/tests/moodlelib_test.php
lib/tests/string_manager_standard_test.php
lib/upgrade.txt
lib/weblib.php
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-debug.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/src/notification/js/ajaxexception.js
lib/yui/src/notification/js/dialogue.js
mod/assign/amd/build/override_form.min.js [new file with mode: 0644]
mod/assign/amd/build/override_form.min.js.map [new file with mode: 0644]
mod/assign/amd/src/override_form.js [new file with mode: 0644]
mod/assign/externallib.php
mod/assign/feedback/editpdf/ajax.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/override_form.php
mod/assign/overrideedit.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/submission/file/lang/en/assignsubmission_file.php
mod/assign/submission/file/lang/en/deprecated.txt [deleted file]
mod/assign/submission/file/locallib.php
mod/assign/templates/grading_app.mustache
mod/assign/templates/override_form_user_defaults.mustache [new file with mode: 0644]
mod/assign/tests/behat/relative_dates.feature [new file with mode: 0644]
mod/assign/tests/externallib_test.php
mod/assign/tests/feedback_test.php [new file with mode: 0644]
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/book/lib.php
mod/book/tests/lib_test.php
mod/book/upgrade.txt
mod/data/locallib.php
mod/feedback/lib.php
mod/feedback/upgrade.txt
mod/forum/classes/output/big_search_form.php
mod/forum/deprecatedlib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/search.php
mod/forum/templates/big_search_form.mustache
mod/forum/tests/behat/advanced_search.feature
mod/forum/upgrade.txt
mod/forum/view.php
mod/glossary/lib.php
mod/glossary/tests/behat/categories.feature
mod/glossary/upgrade.txt
mod/lesson/lang/en/lesson.php
mod/lesson/locallib.php
mod/lesson/report.php
mod/lti/lib.php
mod/lti/upgrade.txt
mod/quiz/attempt.php
mod/quiz/attemptlib.php
mod/quiz/lang/en/deprecated.txt
mod/quiz/lang/en/quiz.php
mod/quiz/review.php
mod/quiz/summary.php
mod/quiz/tests/attempt_test.php
mod/wiki/lib.php
mod/wiki/upgrade.txt
mod/workshop/lang/en/deprecated.txt [deleted file]
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/upgrade.txt
question/tests/generator/lib.php
report/participation/index.php
search/classes/manager.php
search/engine/solr/classes/engine.php
search/tests/behat/search_by_user.feature
search/tests/manager_test.php
theme/boost/scss/moodle/modules.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
user/amd/build/participants.min.js
user/amd/build/participants.min.js.map
user/amd/src/participants.js
user/classes/participants_table.php
user/editadvanced_form.php
user/filters/date.php
user/index.php
user/tests/behat/behat_user.php
user/tests/behat/bulk_editenrolment.feature
user/tests/behat/view_participants.feature
version.php

diff --git a/admin/classes/local/settings/filesize.php b/admin/classes/local/settings/filesize.php
new file mode 100644 (file)
index 0000000..e5a6edb
--- /dev/null
@@ -0,0 +1,194 @@
+<?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/>.
+
+/**
+ * File size admin setting.
+ *
+ * @package    core_admin
+ * @copyright  2019 Shamim Rezaie <shamim@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_admin\local\settings;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/adminlib.php');
+
+/**
+ * An admin setting to support entering and displaying of file sizes in Bytes, KB, MB or GB.
+ *
+ * @copyright   2019 Shamim Rezaie <shamim@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class filesize extends \admin_setting {
+
+    /** @var int The byte unit. Number of bytes in a byte */
+    const UNIT_B = 1;
+
+    /** @var int The kilobyte unit (number of bytes in a kilobyte) */
+    const UNIT_KB = 1024;
+
+    /** @var int The megabyte unit (number of bytes in a megabyte) */
+    const UNIT_MB = 1048576;
+
+    /** @var int The gigabyte unit (number of bytes in a gigabyte) */
+    const UNIT_GB = 1073741824;
+
+    /** @var int default size unit */
+    protected $defaultunit;
+
+    /**
+     * Constructor
+     *
+     * @param string    $name           unique ascii name, either 'mysetting' for settings that in config,
+     *                                  or 'myplugin/mysetting' for ones in config_plugins.
+     * @param string    $visiblename    localised name
+     * @param string    $description    localised long description
+     * @param int|null  $defaultvalue   Value of the settings in bytes
+     * @param int|null  $defaultunit    GB, MB, etc. (in bytes)
+     */
+    public function __construct(string $name, string $visiblename, string $description,
+            int $defaultvalue = null, int $defaultunit = null) {
+
+        $defaultsetting = self::parse_bytes($defaultvalue);
+
+        if ($defaultunit && array_key_exists($defaultunit, self::get_units())) {
+            $this->defaultunit = $defaultunit;
+        } else {
+            $this->defaultunit = self::UNIT_MB;
+        }
+        parent::__construct($name, $visiblename, $description, $defaultsetting);
+    }
+
+    /**
+     * Returns selectable units.
+     *
+     * @return  array
+     */
+    protected static function get_units(): array {
+        return [
+            self::UNIT_GB => get_string('sizegb'),
+            self::UNIT_MB => get_string('sizemb'),
+            self::UNIT_KB => get_string('sizekb'),
+            self::UNIT_B  => get_string('sizeb'),
+        ];
+    }
+
+    /**
+     * Converts bytes to some more user friendly string.
+     *
+     * @param   int     $bytes  The number of bytes we want to convert from
+     * @return  string
+     */
+    protected static function get_size_text(int $bytes): string {
+        if (empty($bytes)) {
+            return get_string('none');
+        }
+        return display_size($bytes);
+    }
+
+    /**
+     * Finds suitable units for given file size.
+     *
+     * @param   int     $bytes  The number of bytes
+     * @return  array           Parsed file size in the format of ['v' => value, 'u' => unit]
+     */
+    protected static function parse_bytes(int $bytes): array {
+        foreach (self::get_units() as $unit => $unused) {
+            if ($bytes % $unit === 0) {
+                return ['v' => (int)($bytes / $unit), 'u' => $unit];
+            }
+        }
+        return ['v' => (int)$bytes, 'u' => self::UNIT_B];
+    }
+
+    /**
+     * Get the selected file size as array.
+     *
+     * @return  array|null  An array containing 'v' => xx, 'u' => xx, or null if not set
+     */
+    public function get_setting(): ?array {
+        $bytes = $this->config_read($this->name);
+        if (is_null($bytes)) {
+            return null;
+        }
+
+        return self::parse_bytes($bytes);
+    }
+
+    /**
+     * Store the file size as bytes.
+     *
+     * @param   array   $data   Must be form 'h' => xx, 'm' => xx
+     * @return  string          The error string if any
+     */
+    public function write_setting($data): string {
+        if (!is_array($data)) {
+            return '';
+        }
+
+        if (!is_numeric($data['v']) || $data['v'] < 0) {
+            return get_string('errorsetting', 'admin');
+        }
+
+        $bytes = $data['v'] * $data['u'];
+
+        $result = $this->config_write($this->name, $bytes);
+        return ($result ? '' : get_string('errorsetting', 'admin'));
+    }
+
+    /**
+     * Returns file size text+select fields.
+     *
+     * @param   array   $data   The current setting value. Must be form 'v' => xx, 'u' => xx.
+     * @param   string  $query  Admin search query to be highlighted.
+     * @return  string          File size text+select fields and wrapping div(s).
+     */
+    public function output_html($data, $query = ''): string {
+        global $OUTPUT;
+
+        $default = $this->get_defaultsetting();
+        if (is_number($default)) {
+            $defaultinfo = self::get_size_text($default);
+        } else if (is_array($default)) {
+            $defaultinfo = self::get_size_text($default['v'] * $default['u']);
+        } else {
+            $defaultinfo = null;
+        }
+
+        $inputid = $this->get_id() . 'v';
+        $units = self::get_units();
+        $defaultunit = $this->defaultunit;
+
+        $context = (object) [
+            'id' => $this->get_id(),
+            'name' => $this->get_full_name(),
+            'value' => $data['v'],
+            'options' => array_map(function($unit, $title) use ($data, $defaultunit) {
+                return [
+                    'value' => $unit,
+                    'name' => $title,
+                    'selected' => ($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u']
+                ];
+            }, array_keys($units), $units)
+        ];
+
+        $element = $OUTPUT->render_from_template('core_admin/setting_configfilesize', $context);
+
+        return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
+    }
+}
index 265119f..f4ab562 100644 (file)
@@ -652,7 +652,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             echo "</label>\n";
         }
         if ($helpicon) {
-            echo '<span class="pull-xs-right text-nowrap">'.$helpicon.'</span>';
+            echo '<span class="float-sm-right text-nowrap">'.$helpicon.'</span>';
         }
         echo '</div>';
         if (isset($this->errors[$name])) {
index 564845b..1b91d6a 100644 (file)
@@ -1,4 +1,29 @@
 <?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/>.
+
+/**
+ * Adds security related settings links for security category to admin tree.
+ *
+ * @copyright  1999 Martin Dougiamas  http://dougiamas.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_admin\local\settings\filesize;
 
 if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
@@ -36,12 +61,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     // maxbytes set to 0 will allow the maximum server limit for uploads
     $temp->add(new admin_setting_configselect('maxbytes', new lang_string('maxbytes', 'admin'), new lang_string('configmaxbytes', 'admin'), 0, $max_upload_choices));
     // 100MB
-    $defaultuserquota = 104857600;
-    $params = new stdClass();
-    $params->bytes = $defaultuserquota;
-    $params->displaysize = display_size($defaultuserquota);
-    $temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
-                new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
+    $defaultuserquota = 100 * filesize::UNIT_MB;
+    $temp->add(new filesize('userquota', new lang_string('userquota', 'admin'),
+            new lang_string('userquota_desc', 'admin'), $defaultuserquota));
 
     $temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
diff --git a/admin/templates/setting_configfilesize.mustache b/admin/templates/setting_configfilesize.mustache
new file mode 100644 (file)
index 0000000..4716c6e
--- /dev/null
@@ -0,0 +1,50 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core_admin/setting_configfilesize
+
+    Admin file size setting template.
+
+    Context variables required for this template:
+    * name - form element name
+    * options - list of options for units containing name, value, selected
+    * value - yes
+    * id - element id
+
+    Example context (json):
+    {
+        "name": "test",
+        "value": "5",
+        "id": "test0",
+        "options": [ { "name": "KB", "value": "1024", "selected": true } ]
+    }
+}}
+{{!
+    Setting configfilesize.
+}}
+<div class="form-filesize defaultsnext">
+    <div class="form-inline">
+        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
+        <label class="sr-only" for="{{id}}u">{{#str}}filesizeunits, admin{{/str}}</label>
+        <select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select">
+            {{#options}}
+                <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+            {{/options}}
+        </select>
+    </div>
+</div>
+
index d30e230..dc1cd94 100644 (file)
@@ -9,6 +9,7 @@ Feature: Manage tokens
     | username  | password  | firstname | lastname |
     | testuser  | testuser  | Joe | Bloggs |
     | testuser2 | testuser2 | TestFirstname | TestLastname |
+    And I change window size to "small"
     And I log in as "admin"
     And I am on site homepage
 
index fd8cb9c..867ec94 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js and b/admin/tool/lp/amd/build/competencies.min.js differ
index aa66f0b..b7aa8c0 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js.map and b/admin/tool/lp/amd/build/competencies.min.js.map differ
index 5314ff2..838c624 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyactions.min.js and b/admin/tool/lp/amd/build/competencyactions.min.js differ
index baafad8..3bdbdc7 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyactions.min.js.map and b/admin/tool/lp/amd/build/competencyactions.min.js.map differ
index e9f3dff..ec38fee 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker.min.js and b/admin/tool/lp/amd/build/competencypicker.min.js differ
index 822d4ea..44b0812 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker.min.js.map and b/admin/tool/lp/amd/build/competencypicker.min.js.map differ
index dce39b1..496c31d 100644 (file)
Binary files a/admin/tool/lp/amd/build/course_competency_settings.min.js and b/admin/tool/lp/amd/build/course_competency_settings.min.js differ
index 329c557..9ab4d54 100644 (file)
Binary files a/admin/tool/lp/amd/build/course_competency_settings.min.js.map and b/admin/tool/lp/amd/build/course_competency_settings.min.js.map differ
index 9c0ecbd..806eeeb 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_dialogue.min.js and b/admin/tool/lp/amd/build/grade_dialogue.min.js differ
index 6f0202f..6af7b51 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_dialogue.min.js.map and b/admin/tool/lp/amd/build/grade_dialogue.min.js.map differ
index 4d52c20..ade6899 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_user_competency_inline.min.js and b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js differ
index 4459d47..1095c0f 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map and b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map differ
index e3a5176..82052a4 100644 (file)
@@ -27,8 +27,9 @@ define(['jquery',
         'core/templates',
         'core/str',
         'tool_lp/competencypicker',
-        'tool_lp/dragdrop-reorder'],
-       function($, notification, ajax, templates, str, Picker, dragdrop) {
+        'tool_lp/dragdrop-reorder',
+        'core/pending'],
+       function($, notification, ajax, templates, str, Picker, dragdrop, Pending) {
 
     /**
      * Constructor
@@ -117,6 +118,7 @@ define(['jquery',
      * Pick a competency
      *
      * @method pickCompetency
+     * @return {Promise}
      */
     competencies.prototype.pickCompetency = function() {
         var self = this;
@@ -132,6 +134,7 @@ define(['jquery',
             self.pickerInstance = new Picker(self.pageContextId, false, pageContextIncludes);
             self.pickerInstance.on('save', function(e, data) {
                 var compIds = data.competencyIds;
+                var pendingPromise = new Pending();
 
                 if (self.itemtype === "course") {
                     requests = [];
@@ -181,17 +184,20 @@ define(['jquery',
                     pagerender = 'tool_lp/plan_page';
                     pageregion = 'plan-page';
                 }
-                ajax.call(requests)[requests.length - 1].then(function(context) {
+                ajax.call(requests)[requests.length - 1]
+                .then(function(context) {
                     return templates.render(pagerender, context);
-                }).then(function(html, js) {
-                    $('[data-region="' + pageregion + '"]').replaceWith(html);
-                    templates.runTemplateJS(js);
+                })
+                .then(function(html, js) {
+                    templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
                     return;
-                }).catch(notification.exception);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
-        self.pickerInstance.display();
+        return self.pickerInstance.display();
     };
 
     /**
@@ -302,6 +308,7 @@ define(['jquery',
         if (localthis.itemtype == 'course') {
             // Course completion rule handling.
             $('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e) {
+                var pendingPromise = new Pending();
                 var requests = [];
                 var pagerender = 'tool_lp/course_competencies_page';
                 var pageregion = 'coursecompetenciespage';
@@ -314,18 +321,24 @@ define(['jquery',
                       args: {courseid: localthis.itemid, moduleid: 0}}
                 ]);
 
-                requests[1].done(function(context) {
-                    templates.render(pagerender, context).done(function(html, js) {
-                        $('[data-region="' + pageregion + '"]').replaceWith(html);
-                        templates.runTemplateJS(js);
-                    }).fail(notification.exception);
-                }).fail(notification.exception);
+                requests[1].then(function(context) {
+                    return templates.render(pagerender, context);
+                })
+                .then(function(html, js) {
+                    return templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
         $('[data-region="actions"] button').click(function(e) {
+            var pendingPromise = new Pending();
             e.preventDefault();
-            localthis.pickCompetency();
+
+            localthis.pickCompetency()
+                .then(pendingPromise.resolve)
+                .catch();
         });
         $('[data-action="delete-competency-link"]').click(function(e) {
             e.preventDefault();
index 60c04da..78a7cd3 100644 (file)
@@ -33,8 +33,12 @@ define(['jquery',
         'tool_lp/menubar',
         'tool_lp/competencypicker',
         'tool_lp/competency_outcomes',
-        'tool_lp/competencyruleconfig'],
-       function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
+        'tool_lp/competencyruleconfig',
+        'core/pending',
+        ],
+       function(
+            $, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig, Pending
+        ) {
 
     // Private variables and functions.
     /** @var {Object} treeModel - This is an object representing the nodes in the tree. */
@@ -412,6 +416,7 @@ define(['jquery',
         if (!pickerInstance) {
             pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
             pickerInstance.on('save', function(e, data) {
+                var pendingPromise = new Pending();
                 var compIds = data.competencyIds;
 
                 var calls = [];
@@ -436,7 +441,9 @@ define(['jquery',
                     templates.runTemplateJS(js);
                     updatedRelatedCompetencies();
                     return;
-                }).catch(notification.exception);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
index 23dce8a..c3cb0b1 100644 (file)
@@ -31,8 +31,10 @@ define(['jquery',
         'core/templates',
         'tool_lp/dialogue',
         'core/str',
-        'tool_lp/tree'],
-        function($, Notification, Ajax, Templates, Dialogue, Str, Tree) {
+        'tool_lp/tree',
+        'core/pending'
+        ],
+        function($, Notification, Ajax, Templates, Dialogue, Str, Tree, Pending) {
 
     /**
      * Competency picker class.
@@ -157,6 +159,7 @@ define(['jquery',
         // Add listener for add.
         self._find('[data-region="competencylinktree"] [data-action="add"]').click(function(e) {
             e.preventDefault();
+            var pendingPromise = new Pending();
             if (!self._selectedCompetencies.length) {
                 return;
             }
@@ -168,7 +171,10 @@ define(['jquery',
                 self._trigger('save', {competencyId: self._selectedCompetencies[0]});
             }
 
+            // The dialogue here is a YUI dialogue and doesn't support Promises at all.
+            // However, it is typically synchronous so this shoudl suffice.
             self.close();
+            pendingPromise.resolve();
         });
 
         // The list of selected competencies will be modified while looping (because of the listeners above).
index ff21ade..fd497d2 100644 (file)
@@ -26,8 +26,10 @@ define(['jquery',
         'tool_lp/dialogue',
         'core/str',
         'core/ajax',
-        'core/templates'],
-       function($, notification, Dialogue, str, ajax, templates) {
+        'core/templates',
+        'core/pending'
+        ],
+       function($, notification, Dialogue, str, ajax, templates, Pending) {
 
     /**
      * Constructor
@@ -48,6 +50,7 @@ define(['jquery',
      * @method configureSettings
      */
     settingsMod.prototype.configureSettings = function(e) {
+        var pendingPromise = new Pending();
         var courseid = $(e.target).closest('a').data('courseid');
         var currentValue = $(e.target).closest('a').data('pushratingstouserplans');
         var context = {
@@ -56,16 +59,21 @@ define(['jquery',
         };
         e.preventDefault();
 
-        templates.render('tool_lp/course_competency_settings', context).done(function(html) {
-            str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function(title) {
-                this._dialogue = new Dialogue(
-                    title,
-                    html,
-                    this.addListeners.bind(this)
-                );
-            }.bind(this)).fail(notification.exception);
-        }.bind(this)).fail(notification.exception);
-
+        $.when(
+            str.get_string('configurecoursecompetencysettings', 'tool_lp'),
+            templates.render('tool_lp/course_competency_settings', context),
+        )
+        .then(function(title, templateResult) {
+            this._dialogue = new Dialogue(
+                title,
+                templateResult[0],
+                this.addListeners.bind(this)
+            );
+
+            return this._dialogue;
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
     };
 
     /**
@@ -108,6 +116,7 @@ define(['jquery',
      * @method saveSettings
      */
     settingsMod.prototype.saveSettings = function(e) {
+        var pendingPromise = new Pending();
         e.preventDefault();
 
         var newValue = this._find('input[name="pushratingstouserplans"]:checked').val();
@@ -117,9 +126,12 @@ define(['jquery',
         ajax.call([
             {methodname: 'core_competency_update_course_competency_settings',
               args: {courseid: courseId, settings: settings}}
-        ])[0].done(function() {
-            this.refreshCourseCompetenciesPage();
-        }.bind(this)).fail(notification.exception);
+        ])[0]
+        .then(function() {
+            return this.refreshCourseCompetenciesPage();
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
 
     };
 
@@ -131,18 +143,23 @@ define(['jquery',
      */
     settingsMod.prototype.refreshCourseCompetenciesPage = function() {
         var courseId = this._find('input[name="courseid"]').val();
+        var pendingPromise = new Pending();
 
         ajax.call([
             {methodname: 'tool_lp_data_for_course_competencies_page',
               args: {courseid: courseId, moduleid: 0}}
-        ])[0].done(function(context) {
-            templates.render('tool_lp/course_competencies_page', context).done(function(html, js) {
-                $('[data-region="coursecompetenciespage"]').replaceWith(html);
-                templates.runTemplateJS(js);
-                this._dialogue.close();
-            }.bind(this)).fail(notification.exception);
-        }.bind(this)).fail(notification.exception);
-
+        ])[0]
+        .then(function(context) {
+            return templates.render('tool_lp/course_competencies_page', context);
+        })
+        .then(function(html, js) {
+            templates.replaceNode($('[data-region="coursecompetenciespage"]'), html, js);
+            this._dialogue.close();
+
+            return;
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
     };
 
     return /** @alias module:tool_lp/configurecoursecompetencysettings */ settingsMod;
index 576b511..80ef97c 100644 (file)
@@ -102,15 +102,20 @@ define(['jquery',
      * @return {Promise}
      */
     Grade.prototype.display = function() {
-        return this._render().then(function(html) {
-            return Str.get_string('rate', 'tool_lp').then(function(title) {
-                this._popup = new Dialogue(
-                    title,
-                    html,
-                    this._afterRender.bind(this)
-                );
-            }.bind(this));
-        }.bind(this)).fail(Notification.exception);
+        return $.when(
+            Str.get_string('rate', 'tool_lp'),
+            this._render()
+        )
+        .then(function(title, templateResult) {
+            this._popup = new Dialogue(
+                title,
+                templateResult[0],
+                this._afterRender.bind(this)
+            );
+
+            return this._popup;
+        }.bind(this))
+        .catch(Notification.exception);
     };
 
     /**
index 7a030b6..81e4f35 100644 (file)
@@ -95,7 +95,7 @@ define(['jquery',
             self = this;
 
         var promise = ScaleValues.get_values(self._scaleId);
-        promise.done(function(scalevalues) {
+        promise.then(function(scalevalues) {
             options.push({
                 value: '',
                 name: self._chooseStr
@@ -109,8 +109,13 @@ define(['jquery',
                 });
             }
 
-            self._dialogue = new GradeDialogue(options);
-            self._dialogue.on('rated', function(e, data) {
+            return options;
+        })
+        .then(function(options) {
+            return new GradeDialogue(options);
+        })
+        .then(function(dialogue) {
+            dialogue.on('rated', function(e, data) {
                 var args = self._args;
                 args.grade = data.rating;
                 args.note = data.note;
@@ -123,7 +128,15 @@ define(['jquery',
                     fail: notification.exception
                 }]);
             });
-        }).fail(notification.exception);
+
+            return dialogue;
+        })
+        .then(function(dialogue) {
+            self._dialogue = dialogue;
+
+            return;
+        })
+        .fail(notification.exception);
     };
 
     /** @type {Number} The scale id for this competency. */
index df34c22..168a028 100644 (file)
@@ -6,6 +6,7 @@ Feature: Manage competency frameworks
 
   Background:
     Given I log in as "admin"
+    And I change window size to "small"
     And I am on site homepage
 
   Scenario: Create a new framework
index 0156243..a5386ff 100644 (file)
@@ -6,6 +6,7 @@ Feature: Manage plearning plan templates
 
   Background:
     Given I log in as "admin"
+    And I change window size to "small"
     And I am on site homepage
 
   Scenario: Create a new learning plan template
index 02027bd..978f950 100644 (file)
@@ -414,6 +414,7 @@ class api {
                 'NoDelegate_CoreRating' => new lang_string('ratings', 'rating'),
                 'NoDelegate_CoreTag' => new lang_string('tags'),
                 '$mmLoginEmailSignup' => new lang_string('startsignup'),
+                'NoDelegate_ForgottenPassword' => new lang_string('forgotten'),
                 'NoDelegate_ResponsiveMainMenuItems' => new lang_string('responsivemainmenuitems', 'tool_mobile'),
             ),
             "$mainmenu" => array(
index ec37326..dcf16e4 100644 (file)
@@ -1039,6 +1039,7 @@ if ($formdata = $mform2->is_cancelled()) {
                 if ($roleid) {
                     // Find duration and/or enrol status.
                     $timeend = 0;
+                    $timestart = $today;
                     $status = null;
 
                     if (isset($user->{'enrolstatus'.$i})) {
@@ -1054,16 +1055,23 @@ if ($formdata = $mform2->is_cancelled()) {
                         }
                     }
 
+                    if (!empty($user->{'enroltimestart'.$i})) {
+                        $parsedtimestart = strtotime($user->{'enroltimestart'.$i});
+                        if ($parsedtimestart !== false) {
+                            $timestart = $parsedtimestart;
+                        }
+                    }
+
                     if (!empty($user->{'enrolperiod'.$i})) {
                         $duration = (int)$user->{'enrolperiod'.$i} * 60*60*24; // convert days to seconds
                         if ($duration > 0) { // sanity check
-                            $timeend = $today + $duration;
+                            $timeend = $timestart + $duration;
                         }
                     } else if ($manualcache[$courseid]->enrolperiod > 0) {
-                        $timeend = $today + $manualcache[$courseid]->enrolperiod;
+                        $timeend = $timestart + $manualcache[$courseid]->enrolperiod;
                     }
 
-                    $manual->enrol_user($manualcache[$courseid], $user->id, $roleid, $today, $timeend, $status);
+                    $manual->enrol_user($manualcache[$courseid], $user->id, $roleid, $timestart, $timeend, $status);
 
                     $a = new stdClass();
                     $a->course = $shortname;
index e389f80..46b8bc4 100644 (file)
@@ -204,7 +204,7 @@ function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $pr
             // hack: somebody wrote uppercase in csv file, but the system knows only lowercase profile field
             $newfield = $lcfield;
 
-        } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus)\d+$/', $lcfield)) {
+        } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus|enroltimestart)\d+$/', $lcfield)) {
             // special fields for enrolments
             $newfield = $lcfield;
 
index ffd9140..7ac75ba 100644 (file)
@@ -145,3 +145,29 @@ Feature: Upload users
     And I should see "Users created: 4"
     And I press "Continue"
     And I log out
+
+  @javascript
+  Scenario: Upload users setting their enrol date and period
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Maths    | math102   | 0        |
+    # Upload the users.
+    And I change window size to "large"
+    And I log in as "admin"
+    And I navigate to "Users > Accounts > Upload users" in site administration
+    When I upload "lib/tests/fixtures/upload_users_enrol_date_period.csv" file to "File" filemanager
+    And I press "Upload users"
+    Then I should see "Upload users preview"
+    And I press "Upload users"
+    # Check user enrolment start date and period
+    And I am on "Maths" course homepage
+    Then I navigate to course participants
+    And I click on "Manual enrolments" "link" in the "Student One" "table_row"
+    Then I should see "1 January 2019" in the "Enrolment starts" "table_row"
+    And I should not see "Enrolment ends"
+    And I click on "Close" "button"
+    And I click on "Manual enrolments" "link" in the "Student Two" "table_row"
+    Then I should see "2 January 2020" in the "Enrolment starts" "table_row"
+    And I should see "12 January 2020" in the "Enrolment ends" "table_row"
+    And I click on "Close" "button"
+    And I log out
index 99e70c1..c0d308b 100644 (file)
@@ -50,12 +50,11 @@ abstract class by_course extends base {
         if (!empty($this->options['filter'])) {
             $courses = array();
             foreach ($this->options['filter'] as $courseid) {
-                $courses[$courseid] = new \stdClass();
-                $courses[$courseid]->id = $courseid;
+                $courses[$courseid] = intval($courseid);
             }
 
             list($coursesql, $courseparams) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
-            $sql .= " AND c.id IN $coursesql";
+            $sql .= " AND c.id $coursesql";
             $params = $params + $courseparams;
         }
 
index 7a6b61e..16ef9f1 100644 (file)
@@ -78,9 +78,9 @@ class result_file extends result {
         // if this analyser was analysed less that 1 week ago we skip generating a new one. This
         // helps scale the evaluation process as sites with tons of courses may need a lot of time to
         // complete an evaluation.
-        if (!empty($options['evaluation']) && !empty($options['reuseprevanalysed'])) {
+        if (!empty($this->options['evaluation']) && !empty($this->options['reuseprevanalysed'])) {
 
-            $previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->analyser->get_modelid(),
+            $previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->modelid,
                 $analysable->get_id(), $timesplitting->get_id());
             // 1 week is a partly random time interval, no need to worry about DST.
             $boundary = time() - WEEKSECS;
index 8a668c9..f8f4826 100644 (file)
@@ -51,8 +51,8 @@ Feature: Manage analytics models
     And I navigate to "Analytics > Analytics models" in site administration
 
   Scenario: Create a model
-    When I click on "New model" "link"
-    And I click on "Create model" "link"
+    When I open the action menu in ".top-nav" "css_element"
+    And I choose "Create model" in the open action menu
     And I set the field "Enabled" to "Enable"
     And I select "__core_course__analytics__target__course_completion" from the "target" singleselect
     And I open the autocomplete suggestions list
@@ -84,22 +84,22 @@ Feature: Manage analytics models
     And I click on "Save changes" "button"
     And I am on site homepage
     And I navigate to "Analytics > Analytics models" in site administration
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Evaluate" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Evaluate" in the open action menu
     And I press "Evaluate"
     And I should see "Evaluate model"
     And I press "Continue"
     # Evaluation log
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Evaluation log" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Evaluation log" in the open action menu
     And I should see "Configuration"
     And I click on "View" "link"
     And I should see "Log extra info"
     And I click on "Close" "button"
     And I click on "Analytics models" "link"
     # Execute scheduled analysis
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Execute scheduled analysis" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Execute scheduled analysis" in the open action menu
     And I should see "Training results"
     And I press "Continue"
     # Check notifications
@@ -111,53 +111,52 @@ Feature: Manage analytics models
     And I navigate to "Analytics > Analytics models" in site administration
     # View predictions
     When I select "C3" from the "contextid" singleselect
-    #And I click on "#dropdown-3" "css_element"
-    And I click on "Actions" "link" in the "Student 6" "table_row"
-    And I click on "View prediction details" "link"
+    And I open the action menu in "Student 6" "table_row"
+    And I choose "View prediction details" in the open action menu
     And I should see "Prediction details"
     And I should see "Any write action"
     And I should see "Read actions amount"
-    And I click on "Actions" "link"
-    And I click on "Acknowledged" "link"
-    And I click on "Actions" "link"
-    And I click on "View prediction details" "link"
-    And I click on "Actions" "link"
-    And I click on "Not useful" "link"
+    And I open the action menu in "Student 6" "table_row"
+    And I choose "Acknowledged" in the open action menu
+    And I open the action menu in "Student 5" "table_row"
+    And I choose "View prediction details" in the open action menu
+    And I open the action menu in "Student 5" "table_row"
+    And I choose "Not useful" in the open action menu
     And I should see "No insights reported"
     # Clear predictions
     When I am on site homepage
     And I navigate to "Analytics > Analytics models" in site administration
     And I should see "No insights reported" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Clear predictions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Clear predictions" in the open action menu
     And I press "Clear predictions"
     Then I should see "No predictions available yet" in the "Students at risk of not meeting the course completion conditions" "table_row"
 
   Scenario: Edit a model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Edit" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Edit" in the open action menu
     And I click on "Read actions amount" "text" in the ".form-autocomplete-selection" "css_element"
     And I press "Save changes"
     And I should not see "Read actions amount"
 
   Scenario: Disable a model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Disable" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Disable" in the open action menu
     Then I should see "Disabled model" in the "Students at risk of not meeting the course completion conditions" "table_row"
 
   Scenario: Export model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Export" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Export" in the open action menu
     And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
     And following "Export" should download between "100" and "500" bytes
 
   Scenario: Check invalid site elements
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Invalid site elements" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Invalid site elements" in the open action menu
     Then I should see "Invalid analysable elements"
 
   Scenario: Delete model
-    When I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
-    And I click on "Delete" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Delete" in the open action menu
     And I click on "Delete" "button" in the "Confirm" "dialogue"
     Then I should not see "Students at risk of not meeting the course completion conditions"
index 8907a3c..7be0a87 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-require_once(__DIR__ . '/test_target_shortname.php');
+require_once(__DIR__ . '/test_target_site_users.php');
 
 /**
  * Test target.
index 4294cad..7fbbdc6 100644 (file)
@@ -478,6 +478,13 @@ class core_analytics_prediction_testcase extends advanced_testcase {
             $message = 'The returned status code ' . $result->status . ' should include ' . $expected[$timesplitting];
             $filtered = $result->status & $expected[$timesplitting];
             $this->assertEquals($expected[$timesplitting], $filtered, $message);
+
+            $options = ['evaluation' => true, 'reuseprevanalysed' => true];
+            $result = new \core_analytics\local\analysis\result_file($model->get_id(), true, $options);
+            $timesplittingobj = \core_analytics\manager::get_time_splitting($timesplitting);
+            $analysable = new \core_analytics\site();
+            $cachedanalysis = $result->retrieve_cached_result($timesplittingobj, $analysable);
+            $this->assertInstanceOf(\stored_file::class, $cachedanalysis);
         }
 
         set_config('enabled_stores', '', 'tool_log');
index 7a063ab..da0f7e9 100644 (file)
@@ -164,8 +164,4 @@ $string['diag_toooldversion'] = 'It is very unlikely a modern LDAP server uses L
 $string['diag_emptycontext'] = 'Empty context found.';
 $string['diag_contextnotfound'] = 'Context {$a} doesn\'t exist or can\'t be read by bind DN.';
 $string['diag_rolegroupnotfound'] = 'Group {$a->group} for role {$a->localname} doesn\'t exist or can\'t be read by bind DN.';
-
-// Deprecated since Moodle 3.4.
-$string['auth_ldap_creators'] = 'List of groups or contexts whose members are allowed to create new courses. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
-$string['auth_ldap_creators_key'] = 'Creators';
 $string['privacy:metadata'] = 'The LDAP server authentication plugin does not store any personal data.';
diff --git a/auth/ldap/lang/en/deprecated.txt b/auth/ldap/lang/en/deprecated.txt
deleted file mode 100644 (file)
index fa73d1e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-auth_ldap_creators,auth_ldap
-auth_ldap_creators_key,auth_ldap
index f58ff47..01b71bc 100644 (file)
Binary files a/blocks/myoverview/amd/build/view.min.js and b/blocks/myoverview/amd/build/view.min.js differ
index ea5cedd..5de1f87 100644 (file)
Binary files a/blocks/myoverview/amd/build/view.min.js.map and b/blocks/myoverview/amd/build/view.min.js.map differ
index 3e53ea0..40d48b9 100644 (file)
@@ -67,6 +67,16 @@ function(
         NOCOURSES: 'core_course/no-courses'
     };
 
+    var GROUPINGS = {
+        GROUPING_ALLINCLUDINGHIDDEN: 'allincludinghidden',
+        GROUPING_ALL: 'all',
+        GROUPING_INPROGRESS: 'inprogress',
+        GROUPING_FUTURE: 'future',
+        GROUPING_PAST: 'past',
+        GROUPING_FAVOURITES: 'favourites',
+        GROUPING_HIDDEN: 'hidden'
+    };
+
     var NUMCOURSES_PERPAGE = [12, 24, 48, 96, 0];
 
     var loadedPages = [];
@@ -252,15 +262,104 @@ function(
         }).catch(Notification.exception);
     };
 
+    /**
+     * Get the action menu item
+     *
+     * @param {Object} root  root The course overview container
+     * @param {Number} courseId Course id.
+     * @return {Object} The hide course menu item.
+     */
+    var getHideCourseMenuItem = function(root, courseId) {
+        return root.find('[data-action="hide-course"][data-course-id="' + courseId + '"]');
+    };
+
+    /**
+     * Get the action menu item
+     *
+     * @param {Object} root  root The course overview container
+     * @param {Number} courseId Course id.
+     * @return {Object} The show course menu item.
+     */
+    var getShowCourseMenuItem = function(root, courseId) {
+        return root.find('[data-action="show-course"][data-course-id="' + courseId + '"]');
+    };
+
+    /**
+     * Hide course
+     *
+     * @param  {Object} root The course overview container
+     * @param  {Number} courseId Course id number
+     */
+    var hideCourse = function(root, courseId) {
+        var hideAction = getHideCourseMenuItem(root, courseId);
+        var showAction = getShowCourseMenuItem(root, courseId);
+        var filters = getFilterValues(root);
+
+        setCourseHiddenState(courseId, true);
+
+        // Remove the course from this view as it is now hidden and thus not covered by this view anymore.
+        // Do only if we are not in "All" view mode where really all courses are shown.
+        if (filters.grouping != GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) {
+            hideElement(root, courseId);
+        }
+
+        hideAction.addClass('hidden');
+        showAction.removeClass('hidden');
+    };
+
+    /**
+     * Show course
+     *
+     * @param  {Object} root The course overview container
+     * @param  {Number} courseId Course id number
+     */
+    var showCourse = function(root, courseId) {
+        var hideAction = getHideCourseMenuItem(root, courseId);
+        var showAction = getShowCourseMenuItem(root, courseId);
+        var filters = getFilterValues(root);
+
+        setCourseHiddenState(courseId, null);
+
+        // Remove the course from this view as it is now shown again and thus not covered by this view anymore.
+        // Do only if we are not in "All" view mode where really all courses are shown.
+        if (filters.grouping != GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) {
+            hideElement(root, courseId);
+        }
+
+        hideAction.removeClass('hidden');
+        showAction.addClass('hidden');
+    };
+
+    /**
+     * Set the courses hidden status and push to repository
+     *
+     * @param  {Number} courseId Course id to favourite.
+     * @param  {Bool} status new hidden status.
+     * @return {Promise} Repository promise.
+     */
+    var setCourseHiddenState = function(courseId, status) {
+
+        // If the given status is not hidden, the preference has to be deleted with a null value.
+        if (status === false) {
+            status = null;
+        }
+        return Repository.updateUserPreferences({
+            preferences: [
+                {
+                    type: 'block_myoverview_hidden_course_' + courseId,
+                    value: status
+                }
+            ]
+        });
+    };
+
     /**
      * Reset the loadedPages dataset to take into account the hidden element
      *
      * @param {Object} root The course overview container
-     * @param {Object} target The course that you want to hide
+     * @param {Number} id The course id number
      */
-    var hideElement = function(root, target) {
-        var id = getCourseId(target);
-
+    var hideElement = function(root, id) {
         var pagingBar = root.find('[data-region="paging-bar"]');
         var jumpto = parseInt(pagingBar.attr('data-active-page-number'));
 
@@ -570,38 +669,15 @@ function(
 
         root.on(CustomEvents.events.activate, SELECTORS.ACTION_HIDE_COURSE, function(e, data) {
             var target = $(e.target).closest(SELECTORS.ACTION_HIDE_COURSE);
-            var id = getCourseId(target);
-
-            var request = {
-                preferences: [
-                    {
-                        type: 'block_myoverview_hidden_course_' + id,
-                        value: true
-                    }
-                ]
-            };
-            Repository.updateUserPreferences(request);
-
-            hideElement(root, target);
+            var courseId = getCourseId(target);
+            hideCourse(root, courseId);
             data.originalEvent.preventDefault();
         });
 
         root.on(CustomEvents.events.activate, SELECTORS.ACTION_SHOW_COURSE, function(e, data) {
             var target = $(e.target).closest(SELECTORS.ACTION_SHOW_COURSE);
-            var id = getCourseId(target);
-
-            var request = {
-                preferences: [
-                    {
-                        type: 'block_myoverview_hidden_course_' + id,
-                        value: null
-                    }
-                ]
-            };
-
-            Repository.updateUserPreferences(request);
-
-            hideElement(root, target);
+            var courseId = getCourseId(target);
+            showCourse(root, courseId);
             data.originalEvent.preventDefault();
         });
     };
index e1c92a9..5f4a033 100644 (file)
@@ -81,6 +81,55 @@ class main implements renderable, templatable {
      */
     private $layouts;
 
+    /**
+     * Store a course grouping option setting
+     *
+     * @var boolean
+     */
+    private $displaygroupingallincludinghidden;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingall;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupinginprogress;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingfuture;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingpast;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingstarred;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupinghidden;
+
     /**
      * main constructor.
      * Initialize the user preferences
@@ -92,23 +141,89 @@ class main implements renderable, templatable {
      * @throws \dml_exception
      */
     public function __construct($grouping, $sort, $view, $paging) {
-        $this->grouping = $grouping ? $grouping : BLOCK_MYOVERVIEW_GROUPING_ALL;
+        // Get plugin config.
+        $config = get_config('block_myoverview');
+
+        // Build the course grouping option name to check if the given grouping is enabled afterwards.
+        $groupingconfigname = 'displaygrouping'.$grouping;
+        // Check the given grouping and remember it if it is enabled.
+        if ($grouping && $config->$groupingconfigname == true) {
+            $this->grouping = $grouping;
+
+            // Otherwise fall back to another grouping in a reasonable order.
+            // This is done to prevent one-time UI glitches in the case when a user has chosen a grouping option previously which
+            // was then disabled by the admin in the meantime.
+        } else if ($config->displaygroupingall == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALL;
+        } else if ($config->displaygroupingallincludinghidden == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN;
+        } else if ($config->displaygroupinginprogress == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_INPROGRESS;
+        } else if ($config->displaygroupingfuture == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_FUTURE;
+        } else if ($config->displaygroupingpast == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_PAST;
+        } else if ($config->displaygroupingstarred == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_FAVOURITES;
+        } else if ($config->displaygroupinghidden == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_HIDDEN;
+
+            // In this case, no grouping option is enabled and the grouping is not needed at all.
+            // But it's better not to leave $this->grouping unset for any unexpected case.
+        } else {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN;
+        }
+        unset ($groupingconfigname);
+
+        // Check and remember the given sorting.
         $this->sort = $sort ? $sort : BLOCK_MYOVERVIEW_SORTING_TITLE;
+
+        // Check and remember the given view.
+        $this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD;
+
+        // Check and remember the given page size.
         if ($paging == BLOCK_MYOVERVIEW_PAGING_ALL) {
             $this->paging = BLOCK_MYOVERVIEW_PAGING_ALL;
         } else {
             $this->paging = $paging ? $paging : BLOCK_MYOVERVIEW_PAGING_12;
         }
 
-        $config = get_config('block_myoverview');
+        // Check and remember if the course categories should be shown or not.
         if (!$config->displaycategories) {
             $this->displaycategories = BLOCK_MYOVERVIEW_DISPLAY_CATEGORIES_OFF;
         } else {
             $this->displaycategories = BLOCK_MYOVERVIEW_DISPLAY_CATEGORIES_ON;
         }
 
+        // Get and remember the available layouts.
         $this->set_available_layouts();
         $this->view = $view ? $view : reset($this->layouts);
+
+        // Check and remember if the particular grouping options should be shown or not.
+        $this->displaygroupingallincludinghidden = $config->displaygroupingallincludinghidden;
+        $this->displaygroupingall = $config->displaygroupingall;
+        $this->displaygroupinginprogress = $config->displaygroupinginprogress;
+        $this->displaygroupingfuture = $config->displaygroupingfuture;
+        $this->displaygroupingpast = $config->displaygroupingpast;
+        $this->displaygroupingstarred = $config->displaygroupingstarred;
+        $this->displaygroupinghidden = $config->displaygroupinghidden;
+
+        // Check and remember if the grouping selector should be shown at all or not.
+        // It will be shown if more than 1 grouping option is enabled.
+        $displaygroupingselectors = array($this->displaygroupingallincludinghidden,
+                $this->displaygroupingall,
+                $this->displaygroupinginprogress,
+                $this->displaygroupingfuture,
+                $this->displaygroupingpast,
+                $this->displaygroupingstarred,
+                $this->displaygroupinghidden);
+        $displaygroupingselectorscount = count(array_filter($displaygroupingselectors));
+        if ($displaygroupingselectorscount > 1) {
+            $this->displaygroupingselector = true;
+        } else {
+            $this->displaygroupingselector = false;
+        }
+        unset ($displaygroupingselectors, $displaygroupingselectorscount);
     }
 
 
@@ -204,6 +319,14 @@ class main implements renderable, templatable {
             'layouts' => $availablelayouts,
             'displaycategories' => $this->displaycategories,
             'displaydropdown' => (count($availablelayouts) > 1) ? true : false,
+            'displaygroupingallincludinghidden' => $this->displaygroupingallincludinghidden,
+            'displaygroupingall' => $this->displaygroupingall,
+            'displaygroupinginprogress' => $this->displaygroupinginprogress,
+            'displaygroupingfuture' => $this->displaygroupingfuture,
+            'displaygroupingpast' => $this->displaygroupingpast,
+            'displaygroupingstarred' => $this->displaygroupingstarred,
+            'displaygroupinghidden' => $this->displaygroupinghidden,
+            'displaygroupingselector' => $this->displaygroupingselector,
         ];
         return array_merge($defaultvariables, $preferences);
 
index 637368b..8fe7076 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['all'] = 'All';
-$string['allexcepthidden'] = 'All (except hidden)';
+$string['allincludinghidden'] = 'All';
+$string['all'] = 'All (except hidden)';
 $string['addtofavourites'] = 'Star this course';
 $string['aria:addtofavourites'] = 'Star for';
-$string['aria:allcourses'] = 'All courses';
-$string['aria:allcoursesexcepthidden'] = 'All courses except hidden courses';
+$string['aria:allcoursesincludinghidden'] = 'All courses';
+$string['aria:allcourses'] = 'All courses except hidden courses';
 $string['aria:card'] = 'Switch to card view';
 $string['aria:controls'] = 'Course overview controls';
 $string['aria:courseactions'] = 'Actions for current course';
@@ -45,6 +45,8 @@ $string['aria:past'] = 'Show past courses';
 $string['aria:removefromfavourites'] = 'Remove star for';
 $string['aria:summary'] = 'Switch to summary view';
 $string['aria:sortingdropdown'] = 'Sorting drop-down menu';
+$string['availablegroupings'] = 'Available filters';
+$string['availablegroupings_desc'] = 'Course filters which are available for selection by users. If none are selected, all courses will be displayed.';
 $string['card'] = 'Card';
 $string['cards'] = 'Cards';
 $string['courseprogress'] = 'Course progress:';
index 7acbaff..81090ed 100644 (file)
@@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
 /**
  * Constants for the user preferences grouping options
  */
+define('BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN', 'allincludinghidden');
 define('BLOCK_MYOVERVIEW_GROUPING_ALL', 'all');
 define('BLOCK_MYOVERVIEW_GROUPING_INPROGRESS', 'inprogress');
 define('BLOCK_MYOVERVIEW_GROUPING_FUTURE', 'future');
@@ -74,6 +75,7 @@ function block_myoverview_user_preferences() {
         'default' => BLOCK_MYOVERVIEW_GROUPING_ALL,
         'type' => PARAM_ALPHA,
         'choices' => array(
+            BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN,
             BLOCK_MYOVERVIEW_GROUPING_ALL,
             BLOCK_MYOVERVIEW_GROUPING_INPROGRESS,
             BLOCK_MYOVERVIEW_GROUPING_FUTURE,
index b17ea4e..b633f22 100644 (file)
@@ -27,22 +27,74 @@ defined('MOODLE_INTERNAL') || die;
 if ($ADMIN->fulltree) {
     require_once($CFG->dirroot . '/blocks/myoverview/lib.php');
 
+    // Presentation options heading.
+    $settings->add(new admin_setting_heading('block_myoverview/appearance',
+            get_string('appearance', 'admin'),
+            ''));
+
     // Display Course Categories on Dashboard course items (cards, lists, summary items).
     $settings->add(new admin_setting_configcheckbox(
-        'block_myoverview/displaycategories',
-        get_string('displaycategories', 'block_myoverview'),
-        get_string('displaycategories_help', 'block_myoverview'),
-        1));
-
-       $choices = array(BLOCK_MYOVERVIEW_VIEW_CARD => get_string('card', 'block_myoverview'),
-        BLOCK_MYOVERVIEW_VIEW_LIST => get_string('list', 'block_myoverview'),
-        BLOCK_MYOVERVIEW_VIEW_SUMMARY => get_string('summary', 'block_myoverview'));
+            'block_myoverview/displaycategories',
+            get_string('displaycategories', 'block_myoverview'),
+            get_string('displaycategories_help', 'block_myoverview'),
+            1));
 
+    // Enable / Disable available layouts.
+    $choices = array(BLOCK_MYOVERVIEW_VIEW_CARD => get_string('card', 'block_myoverview'),
+            BLOCK_MYOVERVIEW_VIEW_LIST => get_string('list', 'block_myoverview'),
+            BLOCK_MYOVERVIEW_VIEW_SUMMARY => get_string('summary', 'block_myoverview'));
     $settings->add(new admin_setting_configmulticheckbox(
-        'block_myoverview/layouts',
-        get_string('layouts', 'block_myoverview'),
-        get_string('layouts_help', 'block_myoverview'),
-        $choices,
-        $choices));
+            'block_myoverview/layouts',
+            get_string('layouts', 'block_myoverview'),
+            get_string('layouts_help', 'block_myoverview'),
+            $choices,
+            $choices));
+    unset ($choices);
+
+    // Enable / Disable course filter items.
+    $settings->add(new admin_setting_heading('block_myoverview/availablegroupings',
+            get_string('availablegroupings', 'block_myoverview'),
+            get_string('availablegroupings_desc', 'block_myoverview')));
+
+    $settings->add(new admin_setting_configcheckbox(
+            'block_myoverview/displaygroupingallincludinghidden',
+            get_string('allincludinghidden', 'block_myoverview'),
+            '',
+            0));
+
+    $settings->add(new admin_setting_configcheckbox(
+            'block_myoverview/displaygroupingall',
+            get_string('all', 'block_myoverview'),
+            '',
+            1));
+
+    $settings->add(new admin_setting_configcheckbox(
+            'block_myoverview/displaygroupinginprogress',
+            get_string('inprogress', 'block_myoverview'),
+            '',
+            1));
+
+    $settings->add(new admin_setting_configcheckbox(
+            'block_myoverview/displaygroupingpast',
+            get_string('past', 'block_myoverview'),
+            '',
+            1));
+
+    $settings->add(new admin_setting_configcheckbox(
+            'block_myoverview/displaygroupingfuture',
+            get_string('future', 'block_myoverview'),
+            '',
+            1));
+
+    $settings->add(new admin_setting_configcheckbox(
+            'block_myoverview/displaygroupingstarred',
+            get_string('favourites', 'block_myoverview'),
+            '',
+            1));
 
+    $settings->add(new admin_setting_configcheckbox(
+            'block_myoverview/displaygroupinghidden',
+            get_string('hiddencourses', 'block_myoverview'),
+            '',
+            1));
 }
diff --git a/blocks/myoverview/styles.css b/blocks/myoverview/styles.css
new file mode 100644 (file)
index 0000000..76daded
--- /dev/null
@@ -0,0 +1,5 @@
+/* Hide the first dropdown-divider if no filter option element is listed before it.
+   This can happen for some subset configurations of the block_myoverview course filter. */
+.block_myoverview button#groupingdropdown + .dropdown-menu li:first-of-type.dropdown-divider:first-of-type {
+    display: none;
+}
index fb56f6d..703482f 100644 (file)
 
     Example context (json):
     {
+        "allincludinghidden": false,
         "all": true,
         "inprogress": false,
         "future": false,
-        "past": false
+        "past": false,
+        "favourites": false,
+        "hidden": false,
+        "displaygroupingallincludinghidden": false,
+        "displaygroupingall": true,
+        "displaygroupinginprogress": true,
+        "displaygroupingfuture": true,
+        "displaygroupingpast": true,
+        "displaygroupingstarred": true,
+        "displaygroupinghidden": true,
+        "displaygroupingselector": true
     }
 }}
+{{#displaygroupingselector}}
 <div class="dropdown mb-1 mr-auto">
     <button id="groupingdropdown" type="button" class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{{#str}} aria:groupingdropdown, block_myoverview {{/str}}">
         {{#pix}} i/filter {{/pix}}
         <span class="d-sm-inline-block" data-active-item-text>
-            {{#all}}{{#str}} allexcepthidden, block_myoverview {{/str}}{{/all}}
+            {{#allincludinghidden}}{{#str}} allincludinghidden, block_myoverview {{/str}}{{/allincludinghidden}}
+            {{#all}}{{#str}} all, block_myoverview {{/str}}{{/all}}
             {{#inprogress}}{{#str}} inprogress, block_myoverview {{/str}}{{/inprogress}}
             {{#future}}{{#str}} future, block_myoverview {{/str}}{{/future}}
             {{#past}}{{#str}} past, block_myoverview {{/str}}{{/past}}
         </span>
     </button>
     <ul class="dropdown-menu" data-show-active-item data-active-item-text aria-labelledby="groupingdropdown">
+        {{#displaygroupingallincludinghidden}}
         <li>
-            <a class="dropdown-item {{#all}}active{{/all}}" href="#" data-filter="grouping" data-value="all" data-pref="all" aria-label="{{#str}} aria:allcoursesexcepthidden, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
-                {{#str}} allexcepthidden, block_myoverview {{/str}}
+            <a class="dropdown-item {{#allincludinghidden}}active{{/allincludinghidden}}" href="#" data-filter="grouping" data-value="allincludinghidden" data-pref="allincludinghidden" aria-label="{{#str}} aria:allcoursesincludinghidden, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+                {{#str}} allincludinghidden, block_myoverview {{/str}}
             </a>
         </li>
+        {{/displaygroupingallincludinghidden}}
+        {{#displaygroupingall}}
+        <li class="dropdown-divider" role="presentation">
+            <span class="filler">&nbsp;</span>
+        </li>
+        <li>
+            <a class="dropdown-item {{#all}}active{{/all}}" href="#" data-filter="grouping" data-value="all" data-pref="all" aria-label="{{#str}} aria:allcourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
+                {{#str}} all, block_myoverview {{/str}}
+            </a>
+        </li>
+        {{/displaygroupingall}}
+        {{#displaygroupinginprogress}}
         <li class="dropdown-divider" role="presentation">
             <span class="filler">&nbsp;</span>
         </li>
                 {{#str}} inprogress, block_myoverview {{/str}}
             </a>
         </li>
+        {{/displaygroupinginprogress}}
+        {{#displaygroupingfuture}}
+            {{^displaygroupinginprogress}}
+            <li class="dropdown-divider" role="presentation">
+                <span class="filler">&nbsp;</span>
+            </li>
+            {{/displaygroupinginprogress}}
         <li>
             <a class="dropdown-item {{#future}}active{{/future}}" href="#" data-filter="grouping" data-value="future" data-pref="future" aria-label="{{#str}} aria:future, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
                 {{#str}} future, block_myoverview {{/str}}
             </a>
         </li>
+        {{/displaygroupingfuture}}
+        {{#displaygroupingpast}}
+            {{^displaygroupinginprogress}}
+                {{^displaygroupingfuture}}
+                <li class="dropdown-divider" role="presentation">
+                    <span class="filler">&nbsp;</span>
+                </li>
+                {{/displaygroupingfuture}}
+            {{/displaygroupinginprogress}}
         <li>
             <a class="dropdown-item {{#past}}active{{/past}}" href="#" data-filter="grouping" data-value="past" data-pref="past" aria-label="{{#str}} aria:past, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
                 {{#str}} past, block_myoverview {{/str}}
             </a>
         </li>
+        {{/displaygroupingpast}}
+        {{#displaygroupingstarred}}
         <li class="dropdown-divider" role="presentation">
             <span class="filler">&nbsp;</span>
         </li>
             <a class="dropdown-item {{#favourites}}active{{/favourites}}" href="#" data-filter="grouping" data-value="favourites"  data-pref="favourites" aria-label="{{#str}} aria:favourites, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
                 {{#str}} favourites, block_myoverview {{/str}}
             </a>
-        </li>
+        {{/displaygroupingstarred}}
+        {{#displaygroupinghidden}}
         <li class="dropdown-divider" role="presentation">
             <span class="filler">&nbsp;</span>
         </li>
                 {{#str}} hiddencourses, block_myoverview {{/str}}
             </a>
         </li>
+        {{/displaygroupinghidden}}
     </ul>
 </div>
+{{/displaygroupingselector}}
+{{^displaygroupingselector}}
+<div class="mb-1 mr-auto">
+    <span class="filler">&nbsp;</span>
+</div>
+{{/displaygroupingselector}}
index 0a995fd..d3527ff 100644 (file)
@@ -29,7 +29,8 @@
                 "fullname": "course 3",
                 "hasprogress": true,
                 "progress": 10,
-                "coursecategory": "Miscellaneous"
+                "coursecategory": "Miscellaneous",
+                "visible": true
             }
         ]
     }
index ab98fcb..37992f0 100644 (file)
@@ -29,7 +29,8 @@
                 "fullname": "course 3",
                 "hasprogress": true,
                 "progress": 10,
-                "coursecategory": "Miscellaneous"
+                "coursecategory": "Miscellaneous",
+                "visible": true
             }
         ]
     }
                         </span>
                         {{{fullname}}}
                     </a>
+                    {{^visible}}
+                        <div class="d-flex flex-wrap">
+                            <span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
+                        </div>
+                    {{/visible}}
                 </div>
             </div>
             {{#hasprogress}}
index a57c690..c53cc32 100644 (file)
@@ -30,7 +30,8 @@
                 "summary": "This course is about assignments",
                 "hasprogress": true,
                 "progress": 10,
-                "coursecategory": "Miscellaneous"
+                "coursecategory": "Miscellaneous",
+                "visible": true
             }
         ]
     }
                     </a>
                     {{> block_myoverview/course-action-menu }}
                 </div>
+                {{^visible}}
+                    <div class="d-flex flex-wrap">
+                        <span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
+                    </div>
+                {{/visible}}
                 <div class="summary">
                     <span class="sr-only">{{#str}}aria:coursesummary, block_myoverview{{/str}}</span>
                     {{{summary}}}
diff --git a/blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature b/blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature
new file mode 100644 (file)
index 0000000..5e69256
--- /dev/null
@@ -0,0 +1,226 @@
+@block @block_myoverview @javascript
+Feature: The my overview block allows admins to easily configure the students' course list
+  In order to adapt the my overview block to my users' needs
+  As an admin
+  I can configure the appearance of the my overview block
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                | idnumber |
+      | student1 | Student   | X        | student1@example.com | S1       |
+    And the following "categories" exist:
+      | name        | category | idnumber |
+      | Category 1  | 0        | CAT1     |
+    And the following "courses" exist:
+      | fullname | shortname | category | startdate                   | enddate         |
+      | Course 1 | C1        | 0        | ##1 month ago##             | ##15 days ago## |
+      | Course 2 | C2        | 0        | ##yesterday##               | ##tomorrow## |
+      | Course 3 | C3        | 0        | ##yesterday##               | ##tomorrow## |
+      | Course 4 | C4        | CAT1     | ##yesterday##               | ##tomorrow## |
+      | Course 5 | C5        | 0        | ##first day of next month## | ##last day of next month## |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | student1 | C1 | student |
+      | student1 | C2 | student |
+      | student1 | C3 | student |
+      | student1 | C4 | student |
+      | student1 | C5 | student |
+
+  Scenario: Enable 'All' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "All" to "1"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    # We have to check for the data attribute instead of the list element text as we would get false positives from the "All (except hidden)" element otherwise
+    Then "[data-value='allincludinghidden']" "css_element" should exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable 'All' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "All" to "0"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    # We have to check for the data attribute instead of the list element text as we would get false negatives "All (except hidden)" element otherwise
+    Then "[data-value='allincludinghidden']" "css_element" should not exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Enable 'All (except hidden)' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "All (except hidden)" to "1"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "All (except hidden)" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable 'All (except hidden)' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "All (except hidden)" to "0"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    # 'All (except hidden)' option has been disabled, so the button is falling back to the 'In progress' option which is the next enabled option.
+    And I click on "In progress" "button" in the "Course overview" "block"
+    Then "All (except hidden)" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Enable 'In progress' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "In progress" to "1"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "In progress" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable 'In progress' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "In progress" to "0"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "In progress" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Enable 'Future' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Future" to "1"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Future" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable 'Future' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Future" to "0"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Future" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Enable 'Past' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Past" to "1"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Past" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable 'Past' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Past" to "0"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Past" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Enable 'Starred' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Starred" to "1"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Starred" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable 'Starred' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Starred" to "0"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Starred" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Enable 'Hidden' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Hidden" to "1"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Hidden" "list_item" should exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable 'Hidden' course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "Hidden" to "0"
+    And I press "Save"
+    And I log out
+    Then I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    Then "Hidden" "list_item" should not exist in the ".block_myoverview .dropdown-menu" "css_element"
+    And I log out
+
+  Scenario: Disable all course filter options
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "All" to "0"
+    And I set the field "All (except hidden)" to "0"
+    And I set the field "In progress" to "0"
+    And I set the field "Future" to "0"
+    And I set the field "Past" to "0"
+    And I set the field "Starred" to "0"
+    And I set the field "Hidden" to "0"
+    And I press "Save"
+    And I log out
+    And I log in as "student1"
+    Then "button#groupingdropdown" "css_element" should not exist in the ".block_myoverview" "css_element"
+    And I should see "Course 1" in the "Course overview" "block"
+    And I should see "Course 2" in the "Course overview" "block"
+    And I should see "Course 3" in the "Course overview" "block"
+    And I should see "Course 4" in the "Course overview" "block"
+    And I should see "Course 5" in the "Course overview" "block"
+    And I log out
+
+  Scenario: Disable all but one course filter option
+    Given I log in as "admin"
+    And I navigate to "Plugins > Blocks > Course overview" in site administration
+    And I set the field "All" to "0"
+    And I set the field "All (except hidden)" to "0"
+    And I set the field "In progress" to "1"
+    And I set the field "Future" to "0"
+    And I set the field "Past" to "0"
+    And I set the field "Starred" to "0"
+    And I set the field "Hidden" to "0"
+    And I press "Save"
+    And I log out
+    And I log in as "student1"
+    Then "button#groupingdropdown" "css_element" should not exist in the ".block_myoverview" "css_element"
+    And I should see "Course 2" in the "Course overview" "block"
+    And I should see "Course 3" in the "Course overview" "block"
+    And I should see "Course 4" in the "Course overview" "block"
+    And I should not see "Course 1" in the "Course overview" "block"
+    And I should not see "Course 5" in the "Course overview" "block"
+    And I log out
index b63804b..9101de8 100644 (file)
@@ -28,7 +28,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View past courses
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "Past" "link" in the "Course overview" "block"
     Then I should see "Course 1" in the "Course overview" "block"
     And I should not see "Course 2" in the "Course overview" "block"
@@ -39,7 +39,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View future courses
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "Future" "link" in the "Course overview" "block"
     Then I should see "Course 5" in the "Course overview" "block"
     And I should not see "Course 1" in the "Course overview" "block"
@@ -50,7 +50,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View inprogress courses
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "In progress" "link" in the "Course overview" "block"
     Then I should see "Course 2" in the "Course overview" "block"
     Then I should see "Course 3" in the "Course overview" "block"
@@ -59,10 +59,25 @@ Feature: The my overview block allows users to easily access their courses
     And I should not see "Course 5" in the "Course overview" "block"
     And I log out
 
-  Scenario: View all courses
+  Scenario: View all (except hidden) courses
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
-    When I click on "All" "link" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
+    Then I should see "Course 1" in the "Course overview" "block"
+    Then I should see "Course 2" in the "Course overview" "block"
+    Then I should see "Course 3" in the "Course overview" "block"
+    Then I should see "Course 4" in the "Course overview" "block"
+    Then I should see "Course 5" in the "Course overview" "block"
+    And I log out
+
+  Scenario: View all (including hidden) courses
+    Given the following config values are set as admin:
+      | config                            | value | plugin           |
+      | displaygroupingallincludinghidden | 1     | block_myoverview |
+    And I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except hidden)" element instead
+    When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block"
     Then I should see "Course 1" in the "Course overview" "block"
     Then I should see "Course 2" in the "Course overview" "block"
     Then I should see "Course 3" in the "Course overview" "block"
@@ -72,7 +87,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View inprogress courses - test persistence
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     And I click on "In progress" "link" in the "Course overview" "block"
     And I reload the page
     Then I should see "In progress" in the "Course overview" "block"
@@ -83,12 +98,12 @@ Feature: The my overview block allows users to easily access their courses
     And I should not see "Course 5" in the "Course overview" "block"
     And I log out
 
-  Scenario: View all courses - w/ persistence
+  Scenario: View all (except hidden) courses - w/ persistence
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
-    When I click on "All" "link" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
     And I reload the page
-    Then I should see "All" in the "Course overview" "block"
+    Then I should see "All (except hidden)" in the "Course overview" "block"
     Then I should see "Course 1" in the "Course overview" "block"
     Then I should see "Course 2" in the "Course overview" "block"
     Then I should see "Course 3" in the "Course overview" "block"
@@ -98,7 +113,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View past courses - w/ persistence
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "Past" "link" in the "Course overview" "block"
     And I reload the page
     Then I should see "Past" in the "Course overview" "block"
@@ -111,7 +126,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View future courses - w/ persistence
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "Future" "link" in the "Course overview" "block"
     And I reload the page
     Then I should see "Future" in the "Course overview" "block"
@@ -164,7 +179,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View inprogress courses with hide persistent functionality
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "In progress" "link" in the "Course overview" "block"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
@@ -178,7 +193,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View past courses with hide persistent functionality
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "Past" "link" in the "Course overview" "block"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 1')]" "xpath_element"
     And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 1')]" "xpath_element"
@@ -192,7 +207,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: View future courses with hide persistent functionality
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     When I click on "Future" "link" in the "Course overview" "block"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
     And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
@@ -204,6 +219,38 @@ Feature: The my overview block allows users to easily access their courses
     And I should not see "Course 4" in the "Course overview" "block"
     And I log out
 
+  Scenario: View all (except hidden) courses with hide persistent functionality
+    Given I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
+    And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
+    And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
+    And I reload the page
+    Then I should not see "Course 5" in the "Course overview" "block"
+    And I should see "Course 1" in the "Course overview" "block"
+    And I should see "Course 2" in the "Course overview" "block"
+    And I should see "Course 3" in the "Course overview" "block"
+    And I should see "Course 4" in the "Course overview" "block"
+    And I log out
+
+  Scenario: View all (including hidden) courses with hide persistent functionality
+    Given the following config values are set as admin:
+      | config                            | value | plugin           |
+      | displaygroupingallincludinghidden | 1     | block_myoverview |
+    And I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except hidden)" element instead
+    When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block"
+    And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
+    And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
+    And I reload the page
+    Then I should see "Course 5" in the "Course overview" "block"
+    And I should see "Course 1" in the "Course overview" "block"
+    And I should see "Course 2" in the "Course overview" "block"
+    And I should see "Course 3" in the "Course overview" "block"
+    And I should see "Course 4" in the "Course overview" "block"
+    And I log out
+
   Scenario: Show course category in cards display
     Given I log in as "student1"
     And I click on "Display drop-down menu" "button" in the "Course overview" "block"
index c9c4845..e494c75 100644 (file)
@@ -25,7 +25,9 @@ Feature: The my overview block allows users to hide their courses
 
   Scenario: Test hide toggle functionality
     Given I log in as "student1"
-    When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
+    And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I reload the page
     Then I should not see "Course 2" in the "Course overview" "block"
@@ -33,13 +35,15 @@ Feature: The my overview block allows users to hide their courses
 
   Scenario: Test hide toggle functionality w/ favorites
     Given I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     When I reload the page
     Then I should not see "Course 2" in the "Course overview" "block"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     And I click on "Starred" "link" in the "Course overview" "block"
     Then I should not see "Course 2" in the "Course overview" "block"
     And I click on "Starred" "button" in the "Course overview" "block"
@@ -49,25 +53,29 @@ Feature: The my overview block allows users to hide their courses
 
   Scenario: Test show toggle functionality
     Given I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
-    When I click on "All" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "button" in the "Course overview" "block"
     And I click on "Hidden" "link" in the "Course overview" "block"
     When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Show this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I reload the page
     And I click on "Hidden" "button" in the "Course overview" "block"
-    When I click on "All" "link" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
     Then I should see "Course 2" in the "Course overview" "block"
     And I log out
 
   Scenario: Test show toggle functionality w/ favorites
     Given I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     And I click on "Hidden" "link" in the "Course overview" "block"
     And I should see "Course 2" in the "Course overview" "block"
     And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
@@ -75,9 +83,39 @@ Feature: The my overview block allows users to hide their courses
     When I reload the page
     Then I should not see "Course 2" in the "Course overview" "block"
     And I click on "Hidden" "button" in the "Course overview" "block"
-    And I click on "All" "link" in the "Course overview" "block"
+    And I click on "All (except hidden)" "link" in the "Course overview" "block"
     Then I should see "Course 2" in the "Course overview" "block"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     And I click on "Starred" "link" in the "Course overview" "block"
     Then I should see "Course 2" in the "Course overview" "block"
+    And I log out
+
+  Scenario: Test a course is hidden directly with "All (except hidden)" courses
+    Given I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
+    And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    Then I should not see "Course 2" in the "Course overview" "block"
+    And I log out
+
+  Scenario: Test a course is never hidden with "All (including hidden)" courses
+    Given the following config values are set as admin:
+      | config                            | value | plugin           |
+      | displaygroupingallincludinghidden | 1     | block_myoverview |
+    And I log in as "student1"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    # We have to click on the data attribute instead of the button element text as we might risk to click on the false positive "All (except hidden)" element instead
+    When I click on "[data-value='allincludinghidden']" "css_element" in the "Course overview" "block"
+    And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I click on "Hide from view" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    Then I should see "Course 2" in the "Course overview" "block"
+    And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I should not see "Hide from view" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I should see "Show this course" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I click on "Show this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    Then I should see "Course 2" in the "Course overview" "block"
+    And I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I should see "Hide from view" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
+    And I should not see "Show this course" in the "//div[@class='card dashboard-card' and contains(.,'Course 2')]" "xpath_element"
     And I log out
\ No newline at end of file
index 6e0e107..84f2e4e 100644 (file)
@@ -50,7 +50,7 @@ Feature: The my overview block allows users to persistence of their page limits
     Given I log in as "student1"
     When I click on "[data-toggle='dropdown']" "css_element" in the "Course overview" "block"
     And I click on "All" "link"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     And I click on "In progress" "link" in the "Course overview" "block"
     Then I should see "Course 13"
     And I should see "All" in the "[data-action='limit-toggle']" "css_element"
index 06813f5..3c2c1c9 100644 (file)
@@ -22,8 +22,8 @@ Feature: Course overview block show users their progress on courses
 
   Scenario: Course progress percentage should not be displayed if completion is not enabled
     Given I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
-    When I click on "All" "link" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
+    When I click on "All (except hidden)" "link" in the "Course overview" "block"
     Then I should not see "0%" in the "Course overview" "block"
     And I log out
 
@@ -38,12 +38,12 @@ Feature: Course overview block show users their progress on courses
     And I press "Save and return to course"
     And I log out
     When I log in as "student1"
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     Then I should see "Course 1" in the "Course overview" "block"
     And I should see "0%" in the "Course overview" "block"
     And I am on "Course 1" course homepage
     And I follow "Test choice 1"
     And I follow "Dashboard" in the user menu
-    And I click on "All" "button" in the "Course overview" "block"
+    And I click on "All (except hidden)" "button" in the "Course overview" "block"
     And I should see "100%" in the "Course overview" "block"
     And I log out
index 5b5b488..12a7387 100644 (file)
@@ -72,6 +72,7 @@ class block_myoverview_privacy_testcase extends \core_privacy\tests\provider_tes
         return array(
             array('block_myoverview_user_sort_preference', 'lastaccessed', ''),
             array('block_myoverview_user_sort_preference', 'title', ''),
+            array('block_myoverview_user_grouping_preference', 'allincludinghidden', ''),
             array('block_myoverview_user_grouping_preference', 'all', ''),
             array('block_myoverview_user_grouping_preference', 'inprogress', ''),
             array('block_myoverview_user_grouping_preference', 'future', ''),
index 4c93e24..6b5751d 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019070400;         // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2019070401;         // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019051100;         // Requires this Moodle version.
 $plugin->component = 'block_myoverview'; // Full name of the plugin (used for diagnostics).
index cc895b3..8da5ad1 100644 (file)
@@ -50,8 +50,22 @@ function xmldb_block_recentlyaccesseditems_upgrade($oldversion, $block) {
     // Automatically generated Moodle v3.7.0 release upgrade line.
     // Put any upgrade step following this.
     if ($oldversion < 2019052001) {
-        $sql = "courseid NOT IN (SELECT c.id from {course} c) OR cmid NOT IN (SELECT cm.id from {course_modules} cm)";
-        $DB->delete_records_select("block_recentlyaccesseditems", $sql);
+        // Query the items to be deleted as a list of IDs. We cannot delete directly from this as a
+        // subquery because MySQL does not support delete with subqueries.
+        $fordeletion = $DB->get_fieldset_sql("
+                SELECT rai.id
+                  FROM {block_recentlyaccesseditems} rai
+             LEFT JOIN {course} c ON c.id = rai.courseid
+             LEFT JOIN {course_modules} cm ON cm.id = rai.cmid
+                 WHERE c.id IS NULL OR cm.id IS NULL");
+
+        // Delete the array in chunks of 500 (Oracle does not support more than 1000 parameters,
+        // let's leave some leeway, there are likely only one chunk anyway).
+        $chunks = array_chunk($fordeletion, 500);
+        foreach ($chunks as $chunk) {
+            $DB->delete_records_list('block_recentlyaccesseditems', 'id', $chunk);
+        }
+
         upgrade_block_savepoint(true, 2019052001, 'recentlyaccesseditems', false);
     }
 
index d671d1b..37d1968 100644 (file)
@@ -83,7 +83,7 @@ function blog_rss_print_link($context, $filtertype, $filterselect = 0, $tagid =
 
     $url = blog_rss_get_url($context->id, $userid, $filtertype, $filterselect, $tagid);
     $rsspix = $OUTPUT->pix_icon('i/rss', get_string('rss'), 'core', array('title' => $tooltiptext));
-    print '<div class="pull-xs-right"><a href="'. $url .'">' . $rsspix . '</a></div>';
+    print '<div class="float-sm-right"><a href="'. $url .'">' . $rsspix . '</a></div>';
 }
 
 /**
index ff9407b..90ebb35 100644 (file)
@@ -68,7 +68,7 @@ class create extends \moodleform {
         $editoroptions = !(empty($this->_customdata['editoroptions'])) ? $this->_customdata['editoroptions'] : null;
         $courseid = !(empty($this->_customdata['courseid'])) ? $this->_customdata['courseid'] : null;
 
-        $eventtypes = calendar_get_allowed_event_types($courseid);
+        $eventtypes = $this->_customdata['eventtypes'];
 
         if (in_array(true, $eventtypes, true) === false) {
             print_error('nopermissiontoupdatecalendar');
@@ -128,8 +128,8 @@ class create extends \moodleform {
         $eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
         $coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid';
         $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
-        $eventtypes = calendar_get_allowed_event_types($courseid);
 
+        $eventtypes = $this->_customdata['eventtypes'];
         if (empty($eventtype) || !isset($eventtypes[$eventtype]) || $eventtypes[$eventtype] == false) {
             $errors['eventtype'] = get_string('invalideventtype', 'calendar');
         }
index 664dd28..2704bc4 100644 (file)
@@ -880,6 +880,7 @@ class core_calendar_external extends external_api {
         $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
         $editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context);
         $formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid];
+        $formoptions['eventtypes'] = calendar_get_allowed_event_types($courseid);
         if ($courseid) {
             require_once($CFG->libdir . '/grouplib.php');
             $groupcoursedata = groups_get_course_data($courseid);
index b744caf..e1a850f 100644 (file)
@@ -1651,9 +1651,9 @@ function calendar_top_controls($type, $data) {
 
     switch ($type) {
         case 'frontpage':
-            $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), $urlbase, false, false, false,
+            $prevlink = calendar_get_link_previous(get_string('monthprev', 'calendar'), $urlbase, false, false, false,
                 true, $prevmonthtime);
-            $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), $urlbase, false, false, false, true,
+            $nextlink = calendar_get_link_next(get_string('monthnext', 'calendar'), $urlbase, false, false, false, true,
                 $nextmonthtime);
             $calendarlink = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', array('view' => 'month')),
                 false, false, false, $time);
@@ -1675,9 +1675,9 @@ function calendar_top_controls($type, $data) {
 
             break;
         case 'course':
-            $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), $urlbase, false, false, false,
+            $prevlink = calendar_get_link_previous(get_string('monthprev', 'calendar'), $urlbase, false, false, false,
                 true, $prevmonthtime);
-            $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), $urlbase, false, false, false,
+            $nextlink = calendar_get_link_next(get_string('monthnext', 'calendar'), $urlbase, false, false, false,
                 true, $nextmonthtime);
             $calendarlink = calendar_get_link_href(new \moodle_url(CALENDAR_URL . 'view.php', array('view' => 'month')),
                 false, false, false, $time);
@@ -3482,6 +3482,9 @@ function calendar_output_fragment_event_form($args) {
     if ($starttime) {
         $formoptions['starttime'] = $starttime;
     }
+    // Let's check first which event types user can add.
+    $eventtypes = calendar_get_allowed_event_types($courseid);
+    $formoptions['eventtypes'] = $eventtypes;
 
     if (is_null($eventid)) {
         if (!empty($courseid)) {
@@ -3491,6 +3494,7 @@ function calendar_output_fragment_event_form($args) {
                 $formoptions['groups'][$groupid] = $groupdata->name;
             }
         }
+
         $mform = new \core_calendar\local\event\forms\create(
             null,
             $formoptions,
@@ -3501,9 +3505,6 @@ function calendar_output_fragment_event_form($args) {
             $data
         );
 
-        // Let's check first which event types user can add.
-        $eventtypes = calendar_get_allowed_event_types($courseid);
-
         // If the user is on course context and is allowed to add course events set the event type default to course.
         if (!empty($courseid) && !empty($eventtypes['course'])) {
             $data['eventtype'] = 'course';
index 2f2242b..a078c02 100644 (file)
@@ -161,7 +161,7 @@ class core_calendar_renderer extends plugin_renderer_base {
                 $deletelink = null;
             }
 
-            $commands  = html_writer::start_tag('div', array('class' => 'commands pull-xs-right'));
+            $commands  = html_writer::start_tag('div', array('class' => 'commands float-sm-right'));
             $commands .= html_writer::start_tag('a', array('href' => $editlink));
             $str = get_string('tt_editevent', 'calendar');
             $commands .= $this->output->pix_icon('t/edit', $str);
@@ -205,9 +205,9 @@ class core_calendar_renderer extends plugin_renderer_base {
             $output .= html_writer::tag('div', $event->courselink);
         }
         if (!empty($event->time)) {
-            $output .= html_writer::tag('span', $event->time, array('class' => 'date pull-xs-right mr-1'));
+            $output .= html_writer::tag('span', $event->time, array('class' => 'date float-sm-right mr-1'));
         } else {
-            $attrs = array('class' => 'date pull-xs-right mr-1');
+            $attrs = array('class' => 'date float-sm-right mr-1');
             $output .= html_writer::tag('span', calendar_time_representation($event->timestart), $attrs);
         }
 
index 06f312d..b76483d 100644 (file)
@@ -49,12 +49,13 @@ class core_comment_external extends external_api {
 
         return new external_function_parameters(
             array(
-                'contextlevel' => new external_value(PARAM_ALPHA, 'contextlevel system, course, user...'),
-                'instanceid'   => new external_value(PARAM_INT, 'the Instance id of item associated with the context level'),
-                'component'    => new external_value(PARAM_COMPONENT, 'component'),
-                'itemid'       => new external_value(PARAM_INT, 'associated id'),
-                'area'         => new external_value(PARAM_AREA, 'string comment area', VALUE_DEFAULT, ''),
-                'page'         => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
+                'contextlevel'  => new external_value(PARAM_ALPHA, 'contextlevel system, course, user...'),
+                'instanceid'    => new external_value(PARAM_INT, 'the Instance id of item associated with the context level'),
+                'component'     => new external_value(PARAM_COMPONENT, 'component'),
+                'itemid'        => new external_value(PARAM_INT, 'associated id'),
+                'area'          => new external_value(PARAM_AREA, 'string comment area', VALUE_DEFAULT, ''),
+                'page'          => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
+                'sortdirection' => new external_value(PARAM_ALPHA, 'Sort direction: ASC or DESC', VALUE_DEFAULT, 'DESC'),
             )
         );
     }
@@ -68,22 +69,33 @@ class core_comment_external extends external_api {
      * @param int $itemid the item id
      * @param string $area comment area
      * @param int $page page number
+     * @param string $sortdirection sort direction
      * @return array of comments and warnings
      * @since Moodle 2.9
      */
-    public static function get_comments($contextlevel, $instanceid, $component, $itemid, $area = '', $page = 0) {
+    public static function get_comments($contextlevel, $instanceid, $component, $itemid, $area = '', $page = 0,
+            $sortdirection = 'DESC') {
+        global $CFG;
 
         $warnings = array();
         $arrayparams = array(
-            'contextlevel' => $contextlevel,
-            'instanceid'   => $instanceid,
-            'component'    => $component,
-            'itemid'       => $itemid,
-            'area'         => $area,
-            'page'         => $page
+            'contextlevel'  => $contextlevel,
+            'instanceid'    => $instanceid,
+            'component'     => $component,
+            'itemid'        => $itemid,
+            'area'          => $area,
+            'page'          => $page,
+            'sortdirection' => $sortdirection,
         );
         $params = self::validate_parameters(self::get_comments_parameters(), $arrayparams);
 
+        $sortdirection = strtoupper($params['sortdirection']);
+        $directionallowedvalues = array('ASC', 'DESC');
+        if (!in_array($sortdirection, $directionallowedvalues)) {
+            throw new invalid_parameter_exception('Invalid value for sortdirection parameter (value: ' . $sortdirection . '),' .
+                'allowed values are: ' . implode(',', $directionallowedvalues));
+        }
+
         $context = self::get_context_from_params($params);
         self::validate_context($context);
 
@@ -96,7 +108,7 @@ class core_comment_external extends external_api {
         $args->component = $params['component'];
 
         $commentobject = new comment($args);
-        $comments = $commentobject->get_comments($params['page']);
+        $comments = $commentobject->get_comments($params['page'], $sortdirection);
 
         // False means no permissions to see comments.
         if ($comments === false) {
@@ -117,6 +129,8 @@ class core_comment_external extends external_api {
 
         $results = array(
             'comments' => $comments,
+            'count' => $commentobject->count(),
+            'perpage' => (!empty($CFG->commentsperpage)) ? $CFG->commentsperpage : 15,
             'warnings' => $warnings
         );
         return $results;
@@ -148,6 +162,8 @@ class core_comment_external extends external_api {
                         ), 'comment'
                     ), 'List of comments'
                 ),
+                'count' => new external_value(PARAM_INT,  'Total number of comments.', VALUE_OPTIONAL),
+                'perpage' => new external_value(PARAM_INT,  'Number of comments per page.', VALUE_OPTIONAL),
                 'warnings' => new external_warnings()
             )
         );
index f8e9f23..463fa00 100644 (file)
@@ -537,9 +537,10 @@ class comment {
      * Return matched comments
      *
      * @param  int $page
+     * @param  str $sortdirection sort direction, ASC or DESC
      * @return array
      */
-    public function get_comments($page = '') {
+    public function get_comments($page = '', $sortdirection = 'DESC') {
         global $DB, $CFG, $USER, $OUTPUT;
         if (!$this->can_view()) {
             return false;
@@ -557,6 +558,7 @@ class comment {
             $params['component'] = $component;
         }
 
+        $sortdirection = ($sortdirection === 'ASC') ? 'ASC' : 'DESC';
         $sql = "SELECT $ufields, c.id AS cid, c.content AS ccontent, c.format AS cformat, c.timecreated AS ctimecreated
                   FROM {comments} c
                   JOIN {user} u ON u.id = c.userid
@@ -564,7 +566,7 @@ class comment {
                        c.commentarea = :commentarea AND
                        c.itemid = :itemid AND
                        $componentwhere
-              ORDER BY c.timecreated DESC";
+              ORDER BY c.timecreated $sortdirection";
         $params['contextid'] = $this->contextid;
         $params['commentarea'] = $this->commentarea;
         $params['itemid'] = $this->itemid;
index c6a7433..e9eb0c2 100644 (file)
@@ -123,11 +123,34 @@ class core_comment_externallib_testcase extends externallib_advanced_testcase {
 
         $this->assertCount(0, $result['warnings']);
         $this->assertCount(2, $result['comments']);
+        $this->assertEquals(2, $result['count']);
+        $this->assertEquals(15, $result['perpage']);
 
         $this->assertEquals($user->id, $result['comments'][0]['userid']);
         $this->assertEquals($user->id, $result['comments'][1]['userid']);
 
-        $this->assertEquals($cmtid2, $result['comments'][0]['id']);
+        $this->assertEquals($cmtid2, $result['comments'][0]['id']); // Default ordering newer first.
         $this->assertEquals($cmtid1, $result['comments'][1]['id']);
+
+        // Test sort direction and pagination.
+        $CFG->commentsperpage = 1;
+        $result = core_comment_external::get_comments($contextlevel, $instanceid, $component, $itemid, $area, $page, 'ASC');
+        $result = external_api::clean_returnvalue(core_comment_external::get_comments_returns(), $result);
+
+        $this->assertCount(0, $result['warnings']);
+        $this->assertCount(1, $result['comments']); // Only one per page.
+        $this->assertEquals(2, $result['count']);
+        $this->assertEquals($CFG->commentsperpage, $result['perpage']);
+        $this->assertEquals($cmtid1, $result['comments'][0]['id']); // Comments order older first.
+
+        // Next page.
+        $result = core_comment_external::get_comments($contextlevel, $instanceid, $component, $itemid, $area, $page + 1, 'ASC');
+        $result = external_api::clean_returnvalue(core_comment_external::get_comments_returns(), $result);
+
+        $this->assertCount(0, $result['warnings']);
+        $this->assertCount(1, $result['comments']);
+        $this->assertEquals(2, $result['count']);
+        $this->assertEquals($CFG->commentsperpage, $result['perpage']);
+        $this->assertEquals($cmtid2, $result['comments'][0]['id']);
     }
 }
diff --git a/comment/upgrade.txt b/comment/upgrade.txt
new file mode 100644 (file)
index 0000000..2510a99
--- /dev/null
@@ -0,0 +1,7 @@
+This files describes API changes in /comment/* ,
+information provided here is intended especially for developers.
+
+=== 3.8 ===
+  * External function get_comments now returns the total count of comments and the number of comments per page.
+    It also has a new parameter to indicate the sorting direction (defaulted to DESC).
+
index 5a86efa..597a77a 100644 (file)
@@ -13,7 +13,7 @@
     "require-dev": {
         "phpunit/phpunit": "7.5.*",
         "phpunit/dbunit": "4.0.*",
-        "moodlehq/behat-extension": "3.38.1",
+        "moodlehq/behat-extension": "3.38.2",
         "mikey179/vfsstream": "^1.6",
         "instaclick/php-webdriver": "dev-local as 1.x-dev"
     }
index fef1a71..30350b9 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "413289581153e5427c3f4fc277185fb0",
+    "content-hash": "f3e6814cafec1673c1fa51dea8a41306",
     "packages": [],
     "packages-dev": [
         {
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/php-webdriver.git",
-                "reference": "e311e55bf2c4746db9df72707f3cf1a731ad98aa"
+                "reference": "3df827208ec104a9716aa8c30741e330da620c1e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/php-webdriver/zipball/e311e55bf2c4746db9df72707f3cf1a731ad98aa",
-                "reference": "e311e55bf2c4746db9df72707f3cf1a731ad98aa",
+                "url": "https://api.github.com/repos/moodlehq/php-webdriver/zipball/3df827208ec104a9716aa8c30741e330da620c1e",
+                "reference": "3df827208ec104a9716aa8c30741e330da620c1e",
                 "shasum": ""
             },
             "require": {
             "support": {
                 "source": "https://github.com/moodlehq/php-webdriver/tree/local"
             },
-            "time": "2019-06-03T22:55:37+00:00"
+            "time": "2019-08-14T02:10:24+00:00"
         },
         {
             "name": "mikey179/vfsstream",
-            "version": "v1.6.6",
+            "version": "v1.6.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/bovigo/vfsStream.git",
-                "reference": "095238a0711c974ae5b4ebf4c4534a23f3f6c99d"
+                "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/095238a0711c974ae5b4ebf4c4534a23f3f6c99d",
-                "reference": "095238a0711c974ae5b4ebf4c4534a23f3f6c99d",
+                "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb",
+                "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.5"
+                "phpunit/phpunit": "^4.5|^5.0"
             },
             "type": "library",
             "extra": {
             "authors": [
                 {
                     "name": "Frank Kleine",
-                    "homepage": "http://frankkleine.de/",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "homepage": "http://frankkleine.de/"
                 }
             ],
             "description": "Virtual file system to mock the real file system in unit tests.",
             "homepage": "http://vfs.bovigo.org/",
-            "time": "2019-04-08T13:54:32+00:00"
+            "time": "2019-08-01T01:38:37+00:00"
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.38.1",
+            "version": "v3.38.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "a3c38c2864e7259b1de834218abfe49eecc03417"
+                "reference": "ee293554b4e75b7444576bfe0c9f6779fb1c04cb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/a3c38c2864e7259b1de834218abfe49eecc03417",
-                "reference": "a3c38c2864e7259b1de834218abfe49eecc03417",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/ee293554b4e75b7444576bfe0c9f6779fb1c04cb",
+                "reference": "ee293554b4e75b7444576bfe0c9f6779fb1c04cb",
                 "shasum": ""
             },
             "require": {
                 "behat/mink-extension": "~2.2",
                 "behat/mink-goutte-driver": "~1.2",
                 "behat/mink-selenium2-driver": "~1.3",
-                "php": ">=5.4.4",
+                "php": ">=7.1.0",
                 "symfony/process": "2.8.*"
             },
             "type": "library",
             "authors": [
                 {
                     "name": "David Monllaó",
+                    "role": "Developer",
                     "email": "david.monllao@gmail.com",
-                    "homepage": "http://moodle.com",
-                    "role": "Developer"
+                    "homepage": "http://moodle.com"
                 }
             ],
             "description": "Moodle behat extension",
                 "Behat",
                 "moodle"
             ],
-            "time": "2019-07-18T08:33:39+00:00"
+            "time": "2019-08-26T22:08:54+00:00"
         },
         {
             "name": "myclabs/deep-copy",
-            "version": "1.9.1",
+            "version": "1.9.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/myclabs/DeepCopy.git",
-                "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72"
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72",
-                "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea",
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea",
                 "shasum": ""
             },
             "require": {
                 "object",
                 "object graph"
             ],
-            "time": "2019-04-07T13:18:21+00:00"
+            "time": "2019-08-09T12:45:53+00:00"
         },
         {
             "name": "phar-io/manifest",
             "authors": [
                 {
                     "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "arne@blankerts.de"
                 },
                 {
                     "name": "Sebastian Heuer",
-                    "email": "sebastian@phpeople.de",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "sebastian@phpeople.de"
                 },
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
             "authors": [
                 {
                     "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "arne@blankerts.de"
                 },
                 {
                     "name": "Sebastian Heuer",
-                    "email": "sebastian@phpeople.de",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "sebastian@phpeople.de"
                 },
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Library for handling version information and constraints",
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "FilterIterator implementation that filters files based on a list of suffixes.",
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Simple template engine.",
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Utility class for timing",
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "3.0.2",
+            "version": "3.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "c4a66b97f040e3e20b3aa2a243230a1c3a9f7c8c"
+                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c4a66b97f040e3e20b3aa2a243230a1c3a9f7c8c",
-                "reference": "c4a66b97f040e3e20b3aa2a243230a1c3a9f7c8c",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a",
+                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.0-dev"
+                    "dev-master": "3.1-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2019-07-08T05:24:54+00:00"
+            "time": "2019-07-25T05:29:42+00:00"
         },
         {
             "name": "phpunit/phpunit",
-            "version": "7.5.14",
+            "version": "7.5.15",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "2834789aeb9ac182ad69bfdf9ae91856a59945ff"
+                "reference": "d79c053d972856b8b941bb233e39dc521a5093f0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2834789aeb9ac182ad69bfdf9ae91856a59945ff",
-                "reference": "2834789aeb9ac182ad69bfdf9ae91856a59945ff",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d79c053d972856b8b941bb233e39dc521a5093f0",
+                "reference": "d79c053d972856b8b941bb233e39dc521a5093f0",
                 "shasum": ""
             },
             "require": {
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "The PHP Unit Testing framework.",
                 "testing",
                 "xunit"
             ],
-            "time": "2019-07-15T06:24:08+00:00"
+            "time": "2019-08-21T07:05:16+00:00"
         },
         {
             "name": "psr/container",
         },
         {
             "name": "sebastian/exporter",
-            "version": "3.1.0",
+            "version": "3.1.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/exporter.git",
-                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
+                "reference": "06a9a5947f47b3029d76118eb5c22802e5869687"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
-                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687",
+                "reference": "06a9a5947f47b3029d76118eb5c22802e5869687",
                 "shasum": ""
             },
             "require": {
                 "BSD-3-Clause"
             ],
             "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
                 {
                     "name": "Jeff Welch",
                     "email": "whatthejeff@gmail.com"
                     "name": "Volker Dusch",
                     "email": "github@wallbash.com"
                 },
-                {
-                    "name": "Bernhard Schussek",
-                    "email": "bschussek@2bepublished.at"
-                },
-                {
-                    "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de"
-                },
                 {
                     "name": "Adam Harvey",
                     "email": "aharvey@php.net"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
                 }
             ],
             "description": "Provides the functionality to export PHP variables for visualization",
                 "export",
                 "exporter"
             ],
-            "time": "2017-04-03T13:19:02+00:00"
+            "time": "2019-08-11T12:43:14+00:00"
         },
         {
             "name": "sebastian/global-state",
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sebastian@phpunit.de",
-                    "role": "lead"
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v4.3.2",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "a29dd02a1f3f81b9a15c7730cc3226718ddb55ca"
+                "reference": "9e5dddb637b13db82e35695a8603fe6e118cc119"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a29dd02a1f3f81b9a15c7730cc3226718ddb55ca",
-                "reference": "a29dd02a1f3f81b9a15c7730cc3226718ddb55ca",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/9e5dddb637b13db82e35695a8603fe6e118cc119",
+                "reference": "9e5dddb637b13db82e35695a8603fe6e118cc119",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2019-06-11T15:41:59+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
         },
         {
             "name": "symfony/class-loader",
-            "version": "v3.4.29",
+            "version": "v3.4.31",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/class-loader.git",
-                "reference": "4459eef5298dedfb69f771186a580062b8516497"
+                "reference": "e212b06996819a2bce026a63da03b7182d05a690"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/class-loader/zipball/4459eef5298dedfb69f771186a580062b8516497",
-                "reference": "4459eef5298dedfb69f771186a580062b8516497",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/e212b06996819a2bce026a63da03b7182d05a690",
+                "reference": "e212b06996819a2bce026a63da03b7182d05a690",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony ClassLoader Component",
             "homepage": "https://symfony.com",
-            "time": "2019-01-16T09:39:14+00:00"
+            "time": "2019-08-20T13:31:17+00:00"
         },
         {
             "name": "symfony/config",
-            "version": "v4.3.2",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "9198eea354be75794a7b1064de00d9ae9ae5090f"
+                "reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/9198eea354be75794a7b1064de00d9ae9ae5090f",
-                "reference": "9198eea354be75794a7b1064de00d9ae9ae5090f",
+                "url": "https://api.github.com/repos/symfony/config/zipball/07d49c0f823e0bc367c6d84e35b61419188a5ece",
+                "reference": "07d49c0f823e0bc367c6d84e35b61419188a5ece",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2019-06-08T06:33:08+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
         },
         {
             "name": "symfony/console",
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.4.29",
+            "version": "v3.4.31",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf"
+                "reference": "e18c5c4b35e7f17513448a25d02f7af34a4bdb41"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
-                "reference": "8ca29297c29b64fb3a1a135e71cb25f67f9fdccf",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/e18c5c4b35e7f17513448a25d02f7af34a4bdb41",
+                "reference": "e18c5c4b35e7f17513448a25d02f7af34a4bdb41",
                 "shasum": ""
             },
             "require": {
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Jean-François Simon",
-                    "email": "jeanfrancois.simon@sensiolabs.com"
-                },
                 {
                     "name": "Fabien Potencier",
                     "email": "fabien@symfony.com"
                 },
+                {
+                    "name": "Jean-François Simon",
+                    "email": "jeanfrancois.simon@sensiolabs.com"
+                },
                 {
                     "name": "Symfony Community",
                     "homepage": "https://symfony.com/contributors"
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2019-01-16T09:39:14+00:00"
+            "time": "2019-08-20T13:31:17+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v3.4.29",
+            "version": "v3.4.31",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "1172dc1abe44dfadd162239153818b074e6e53bf"
+                "reference": "0b600300918780001e2821db77bc28b677794486"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/1172dc1abe44dfadd162239153818b074e6e53bf",
-                "reference": "1172dc1abe44dfadd162239153818b074e6e53bf",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/0b600300918780001e2821db77bc28b677794486",
+                "reference": "0b600300918780001e2821db77bc28b677794486",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2019-06-18T21:26:03+00:00"
+            "time": "2019-08-20T13:31:17+00:00"
         },
         {
             "name": "symfony/dependency-injection",
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v4.3.2",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "291397232a2eefb3347eaab9170409981eaad0e2"
+                "reference": "cc686552948d627528c0e2e759186dff67c2610e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/291397232a2eefb3347eaab9170409981eaad0e2",
-                "reference": "291397232a2eefb3347eaab9170409981eaad0e2",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/cc686552948d627528c0e2e759186dff67c2610e",
+                "reference": "cc686552948d627528c0e2e759186dff67c2610e",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2019-06-13T11:03:18+00:00"
+            "time": "2019-08-26T08:26:39+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v3.4.29",
+            "version": "v3.4.31",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "f18fdd6cc7006441865e698420cee26bac94741f"
+                "reference": "3e922c4c3430b9de624e8a285dada5e61e230959"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f18fdd6cc7006441865e698420cee26bac94741f",
-                "reference": "f18fdd6cc7006441865e698420cee26bac94741f",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3e922c4c3430b9de624e8a285dada5e61e230959",
+                "reference": "3e922c4c3430b9de624e8a285dada5e61e230959",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2019-06-25T07:45:31+00:00"
+            "time": "2019-08-23T08:05:57+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v4.3.2",
+            "version": "v4.3.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d"
+                "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/b9896d034463ad6fd2bf17e2bf9418caecd6313d",
-                "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263",
+                "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2019-06-23T08:51:25+00:00"
+            "time": "2019-08-20T14:07:54+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.11.0",
+            "version": "v1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "82ebae02209c21113908c229e9883c419720738a"
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
-                "reference": "82ebae02209c21113908c229e9883c419720738a",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.11-dev"
+                    "dev-master": "1.12-dev"
                 }
             },
             "autoload": {
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                },
                 {
                     "name": "Gert de Pagter",
                     "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
             "description": "Symfony polyfill for ctype functions",
                 "polyfill",
                 "portable"
             ],
-            "time": "2019-02-06T07:57:58+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.11.0",
+            "version": "v1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
-                "reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.11-dev"
+                    "dev-master": "1.12-dev"
                 }
             },
             "autoload": {
                 "portable",
                 "shim"
             ],
-            "time": "2019-02-06T07:57:58+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         {
             "name": "symfony/process",
             "authors": [
                 {
                     "name": "Arne Blankerts",
-                    "email": "arne@blankerts.de",
-                    "role": "Developer"
+                    "role": "Developer",
+                    "email": "arne@blankerts.de"
                 }
             ],
             "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
         },
         {
             "name": "webmozart/assert",
-            "version": "1.4.0",
+            "version": "1.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/webmozart/assert.git",
-                "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9"
+                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9",
-                "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4",
+                "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4",
                 "shasum": ""
             },
             "require": {
                 "symfony/polyfill-ctype": "^1.8"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.6",
-                "sebastian/version": "^1.0.1"
+                "phpunit/phpunit": "^4.8.36 || ^7.5.13"
             },
             "type": "library",
             "extra": {
                 "check",
                 "validate"
             ],
-            "time": "2018-12-25T11:19:39+00:00"
+            "time": "2019-08-24T08:43:50+00:00"
         }
     ],
     "aliases": [
index 4257851..b87da32 100644 (file)
@@ -68,8 +68,10 @@ class activities_due extends \core_analytics\local\indicator\binary {
      */
     protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
 
+        $user = $this->retrieve('user', $sampleid);
+
         $actionevents = \core_calendar_external::get_calendar_action_events_by_timesort($starttime, $endtime, 0, 1,
-            true, $sampleid);
+            true, $user->id);
 
         if ($actionevents->events) {
 
index 566af23..65471f7 100644 (file)
@@ -105,6 +105,9 @@ class course_summary_exporter extends \core\external\exporter {
             ),
             'enddate' => array(
                 'type' => PARAM_INT,
+            ),
+            'visible' => array(
+                'type' => PARAM_BOOL,
             )
         );
     }
index 7f3a170..8877119 100644 (file)
@@ -3741,6 +3741,8 @@ class core_course_external extends external_api {
         $sort = $params['sort'];
 
         switch($classification) {
+            case COURSE_TIMELINE_ALLINCLUDINGHIDDEN:
+                break;
             case COURSE_TIMELINE_ALL:
                 break;
             case COURSE_TIMELINE_PAST:
@@ -3764,10 +3766,16 @@ class core_course_external extends external_api {
         $hiddencourses = get_hidden_courses_on_timeline();
         $courses = [];
 
-        // If the timeline requires the hidden courses then restrict the result to only $hiddencourses else exclude.
-        if ($classification == COURSE_TIMELINE_HIDDEN) {
+        // If the timeline requires really all courses, get really all courses.
+        if ($classification == COURSE_TIMELINE_ALLINCLUDINGHIDDEN) {
+            $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields, COURSE_DB_QUERY_LIMIT);
+
+            // Otherwise if the timeline requires the hidden courses then restrict the result to only $hiddencourses.
+        } else if ($classification == COURSE_TIMELINE_HIDDEN) {
             $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
                 COURSE_DB_QUERY_LIMIT, $hiddencourses);
+
+            // Otherwise get the requested courses and exclude the hidden courses.
         } else {
             $courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields,
                 COURSE_DB_QUERY_LIMIT, [], $hiddencourses);
index 9622e70..3eec792 100644 (file)
@@ -56,6 +56,7 @@ define('FIRSTUSEDEXCELROW', 3);
 define('MOD_CLASS_ACTIVITY', 0);
 define('MOD_CLASS_RESOURCE', 1);
 
+define('COURSE_TIMELINE_ALLINCLUDINGHIDDEN', 'allincludinghidden');
 define('COURSE_TIMELINE_ALL', 'all');
 define('COURSE_TIMELINE_PAST', 'past');
 define('COURSE_TIMELINE_INPROGRESS', 'inprogress');
@@ -2558,7 +2559,7 @@ function update_course($data, $editoroptions = NULL) {
                 // The summary might be very long, we don't wan't to fill up the log record with the full text.
                 $updatedfields[$field] = '(updated)';
             }
-        } else if ($field == 'tags') {
+        } else if ($field == 'tags' && !empty($CFG->usetags)) {
             // Tags might not have the same array keys, just check the values.
             if (array_values($data->$field) !== array_values($value)) {
                 $updatedfields[$field] = $data->$field;
@@ -4320,9 +4321,9 @@ function course_filter_courses_by_timeline_classification(
 ) : array {
 
     if (!in_array($classification,
-            [COURSE_TIMELINE_ALL, COURSE_TIMELINE_PAST, COURSE_TIMELINE_INPROGRESS,
+            [COURSE_TIMELINE_ALLINCLUDINGHIDDEN, COURSE_TIMELINE_ALL, COURSE_TIMELINE_PAST, COURSE_TIMELINE_INPROGRESS,
                 COURSE_TIMELINE_FUTURE, COURSE_TIMELINE_HIDDEN])) {
-        $message = 'Classification must be one of COURSE_TIMELINE_ALL, COURSE_TIMELINE_PAST, '
+        $message = 'Classification must be one of COURSE_TIMELINE_ALLINCLUDINGHIDDEN, COURSE_TIMELINE_ALL, COURSE_TIMELINE_PAST, '
             . 'COURSE_TIMELINE_INPROGRESS or COURSE_TIMELINE_FUTURE';
         throw new moodle_exception($message);
     }
@@ -4336,7 +4337,7 @@ function course_filter_courses_by_timeline_classification(
         $pref = get_user_preferences('block_myoverview_hidden_course_' . $course->id, 0);
 
         // Added as of MDL-63457 toggle viewability for each user.
-        if (($classification == COURSE_TIMELINE_HIDDEN && $pref) ||
+        if ($classification == COURSE_TIMELINE_ALLINCLUDINGHIDDEN || ($classification == COURSE_TIMELINE_HIDDEN && $pref) ||
             (($classification == COURSE_TIMELINE_ALL || $classification == course_classify_for_timeline($course)) && !$pref)) {
             $filteredcourses[] = $course;
             $filtermatches++;
@@ -4621,7 +4622,7 @@ function course_get_recent_courses(int $userid = null, int $limit = 0, int $offs
     }
 
     $basefields = array('id', 'idnumber', 'summary', 'summaryformat', 'startdate', 'enddate', 'category',
-            'shortname', 'fullname', 'timeaccess', 'component');
+            'shortname', 'fullname', 'timeaccess', 'component', 'visible');
 
     $sort = trim($sort);
     if (empty($sort)) {
index 27784d1..492a9f1 100644 (file)
@@ -69,8 +69,11 @@ if ($courseid) {
     $course = null;
     $courseid = null;
     $topchildren = core_course_category::top()->get_children();
+    if (empty($topchildren)) {
+        throw new moodle_exception('cannotviewcategory', 'error');
+    }
     $category = reset($topchildren);
-    $categoryid = $category ? $category->id : 0;
+    $categoryid = $category->id;
     $context = context_coursecat::instance($category->id);
     $url->param('categoryid', $category->id);
 }
index 9d1439c..ba33b06 100644 (file)
@@ -372,13 +372,14 @@ class core_course_renderer extends plugin_renderer_base {
                 $inputsize = 30;
         }
 
-        $data = (object) [
-                'searchurl' => (new moodle_url('/course/search.php'))->out(false),
-                'id' => $formid,
-                'inputid' => $inputid,
-                'inputsize' => $inputsize,
-                'value' => $value
-        ];
+        $data = new stdClass();
+        $data->searchurl = \core_search\manager::get_course_search_url()->out(false);
+        $data->id = $formid;
+        $data->inputid = $inputid;
+        $data->inputsize = $inputsize;
+        $data->value = $value;
+        $data->areaids = 'core_course-course';
+
         if ($format != 'navbar') {
             $helpicon = new \help_icon('coursesearch', 'core');
             $data->helpicon = $helpicon->export_for_template($this);
index 9dcd112..144767d 100644 (file)
@@ -24,6 +24,7 @@
 require_once("../config.php");
 require_once($CFG->dirroot.'/course/lib.php');
 
+$q         = optional_param('q', '', PARAM_RAW);       // Global search words.
 $search    = optional_param('search', '', PARAM_RAW);  // search words
 $page      = optional_param('page', 0, PARAM_INT);     // which page to show
 $perpage   = optional_param('perpage', '', PARAM_RAW); // how many per page, may be integer or 'all'
@@ -31,6 +32,11 @@ $blocklist = optional_param('blocklist', 0, PARAM_INT);
 $modulelist= optional_param('modulelist', '', PARAM_PLUGIN);
 $tagid     = optional_param('tagid', '', PARAM_INT);   // searches for courses tagged with this tag id
 
+// Use global search.
+if ($q) {
+    $search = $q;
+}
+
 // List of minimum capabilities which user need to have for editing/moving course
 $capabilities = array('moodle/course:create', 'moodle/category:manage');
 
index c3583e1..3b2af23 100644 (file)
@@ -64,7 +64,7 @@
         }
     }
 }}
-<div class="mt-2 mb-1 activity-navigation">
+<div class="mt-5 mb-1 activity-navigation">
 {{< core/columns-1to1to1}}
     {{$column1}}
         <div class="float-left">
index d709096..f8ec186 100644 (file)
 {{!
     @template core_course/course_search_form
 
-    This template renders the search form.
+    This template renders the form for course search.
 
     Example context (json):
     {
-        "searchurl": "https://domain.example/course/search.php",
+        "searchurl": "https://moodlesite/course/search.php",
         "id": "coursesearch",
         "inputid": "coursesearchbox",
-        "inputsize": "30",
-        "value": "Find in course",
-        "helpicon": "<a class='btn'><i class='icon fa fa-question-circle'></i></a>"
+        "inputsize": 30,
+        "value": "certificate",
+        "name" : "q",
+        "helpicon": [{
+            "heading": "Search courses",
+            "text": "You can search for multiple words at once and can refine your search as follows:"
+        }]
     }
 }}
 <form action="{{searchurl}}" id="{{id}}" method="get" class="form-inline">
     <fieldset class="coursesearchbox invisiblefieldset">
         <label for="{{inputid}}">{{#str}}searchcourses{{/str}}</label>
-        <input id="{{inputid}}" name="search" type="text" size="{{inputsize}}" value="{{value}}" class="form-control">
+        <input id="{{inputid}}" name="q" type="text" size="{{inputsize}}" value="{{value}}" class="form-control">
+        <input name="areaids" type="hidden" value="{{areaids}}">
         <button class="btn btn-secondary" type="submit">{{#str}}go{{/str}}</button>
         {{#helpicon}}
             {{>core/help_icon}}
index 2d45db2..74300b9 100644 (file)
@@ -28,7 +28,8 @@
                 "courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
                 "fullname": "course 3",
                 "hasprogress": true,
-                "progress": 10
+                "progress": 10,
+                "visible": true
             }
         ]
     }
                         </span>
                     {{$coursename}}{{/coursename}}
                 </a>
+                {{^visible}}
+                    <div class="d-flex flex-wrap">
+                        <span class="tag tag-info">{{#str}} hiddenfromstudents {{/str}}</span>
+                    </div>
+                {{/visible}}
             </div>
             {{$menu}}{{/menu}}
         </div>
index 1749a13..3ad47f9 100644 (file)
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
 global $CFG;
 require_once(__DIR__ . '/../../lib/completionlib.php');
 require_once(__DIR__ . '/../../completion/criteria/completion_criteria_self.php');
+require_once(__DIR__ . '/../../analytics/tests/fixtures/test_target_course_users.php');
 
 /**
  * Unit tests for core_course indicators.
@@ -313,4 +314,30 @@ class core_course_indicators_testcase extends advanced_testcase {
         // Page social is level 1 (the lower level).
         $this->assertEquals($indicator::get_min_value(), $values[$cm2->id][0]);
     }
+
+    /**
+     * test_activities_due
+     *
+     * @return void
+     */
+    public function test_activities_due() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $this->setAdminuser();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $user1 = $this->getDataGenerator()->create_user();
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
+
+        $target = \core_analytics\manager::get_target('test_target_course_users');
+        $indicators = array('\core_course\analytics\indicator\activities_due');
+        foreach ($indicators as $key => $indicator) {
+            $indicators[$key] = \core_analytics\manager::get_indicator($indicator);
+        }
+
+        $model = \core_analytics\model::create($target, $indicators);
+        $model->enable('\core\analytics\time_splitting\single_range');
+        $model->train();
+    }
 }
index 10407f9..d48e163 100644 (file)
@@ -2,8 +2,12 @@ This files describes API changes in /course/*,
 information provided here is intended especially for developers.
 
 === 3.8 ===
+
 * The following functions have been finally deprecated and can not be used any more:
   - core_course_external::get_activities_overview
+* External function core_course_external::get_enrolled_courses_by_timeline_classification now also supports the classification
+  'allincludinghidden' which delivers all courses including hidden courses. The classification 'all' still returns all courses
+  without hidden courses.
 
 === 3.7 ===
 
index 8e3cfba..599f3fe 100644 (file)
@@ -80,7 +80,7 @@ class writer extends \core\dataformat\base {
             echo ",";
         }
 
-        echo json_encode($record);
+        echo json_encode($record, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
 
         $this->sheetdatadded = true;
     }
index 687f1a4..8cf1d39 100644 (file)
@@ -23,6 +23,7 @@ Feature: Converting rubric score to grades
       | activity   | name              | intro | course | idnumber    | grade   | advancedgradingmethod_submissions |
       | assign     | Test assignment 1 | Test  | C1     | assign1     | <grade> | rubric                            |
     When I log in as "teacher1"
+    And I change window size to "large"
     And I am on "Course 1" course homepage with editing mode on
     And I go to "Test assignment 1" advanced grading definition page
     And I set the following fields to these values:
index 33d0e1e..363bb6f 100644 (file)
@@ -26,6 +26,7 @@ Feature: Rubrics can have levels with negative scores
     And the following "activities" exist:
       | activity   | name              | intro | course | idnumber    | grade   | advancedgradingmethod_submissions |
       | assign     | Test assignment 1 | Test  | C1     | assign1     | 100     | rubric                            |
+    And I change window size to "large"
     When I log in as "teacher1"
     And I am on "Course 1" course homepage with editing mode on
     And I go to "Test assignment 1" advanced grading definition page
index 06a9518..1b1326a 100644 (file)
@@ -77,7 +77,7 @@ Feature: A teacher checks the grade history report in a course
       | Student 2          | The greatest assignment ever  | 50.00          | 70.00         | Teacher 2 |
       | Student 2          | Rewarding assignment          | 60.00          | 80.00         | Teacher 2 |
     # Test filtering by assignment.
-    And I click on "The greatest assignment ever" "option" in the "#id_itemid" "css_element"
+    And I set the field "Grade item" to "The greatest assignment ever"
     And I press "Submit"
     And the following should exist in the "gradereport_history" table:
       | First name/Surname | Grade item                    | Original grade | Revised grade | Grader    |
@@ -87,7 +87,7 @@ Feature: A teacher checks the grade history report in a course
       | Student 1          | Rewarding assignment          |                | 60.00         | Teacher 1 |
       | Student 1          | Rewarding assignment          | 60.00          | 80.00         | Teacher 2 |
     # Test filtering by grader.
-    And I click on "Teacher 1" "option" in the "#id_grader" "css_element"
+    And I set the field "Grader" to "Teacher 1"
     And I press "Submit"
     And the following should exist in the "gradereport_history" table:
       | First name/Surname | Grade item                    | Original grade | Revised grade | Grader    |
index 50f1ce9..06b37e1 100644 (file)
@@ -93,7 +93,8 @@ class dropdown_attribute extends element {
                     'value' => $option,
                     'selected' => $selected == $option
                 ];
-            }, array_keys($options))
+            }, array_keys($options)),
+            'label' => get_string('gradefor', 'gradereport_singleview', $this->label),
         );
 
         return $OUTPUT->render_from_template('gradereport_singleview/dropdown_attribute', $context);
index 436c685..3b0bdb9 100644 (file)
@@ -28,6 +28,7 @@
         "selected": "true"
     }
 }}
+<label for="{{name}}" class="accesshide">{{label}}</label>
 <select id="{{name}}" name="{{name}}" class="custom-select" tabindex="1" {{#disabled}}disabled{{/disabled}}>
     {{#options}}
         <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
index d86cdcf..e18eb5c 100644 (file)
@@ -87,7 +87,7 @@ Feature: We can use Single view
     And the field "Grade for james (Student) 1" matches value "12.05"
     And the field "Exclude for holly (Student) 2" matches value "1"
     And I select "new grade item 1" from the "Select grade item..." singleselect
-    And I click on "Very good" "option"
+    And I set the field "Grade for james (Student) 1" to "Very good"
     And I press "Save"
     Then I should see "Grades were set for 1 items"
     And I press "Continue"
@@ -98,7 +98,7 @@ Feature: We can use Single view
     And I log in as "teacher2"
     And I am on "Course 1" course homepage
     Given I navigate to "View > Single view" in the course gradebook
-    And I click on "Student 4" "option"
+    And I select "Student 4" from the "Select user..." singleselect
     And the "Exclude for Test assignment one" "checkbox" should be disabled
     And the "Override for Test assignment one" "checkbox" should be enabled
 
index bf4b25d..e391454 100644 (file)
@@ -40,7 +40,7 @@ $string['cannotsavemd5file'] = 'Αδυναμία αποθήκευσης αρχε
 $string['cannotsavezipfile'] = 'Αδυναμία αποθήκευσης συμπιεσμένου αρχείου';
 $string['cannotunzipfile'] = 'Αδυναμία αποσυμπίεσης αρχείου';
 $string['componentisuptodate'] = 'Το στοιχείο λογισμικού είναι ενημερωμένο.';
-$string['dmlexceptiononinstall'] = '<p>Παρουσιάστηκε σφάλμα βάσης δεδομένων [{$a->errorcode}].<br />{$a->debuginfo}</p>';
+$string['dmlexceptiononinstall'] = '<p>Παρουσιάστηκε κάποιο σφάλμα βάσης δεδομένων [{$a->errorcode}].<br />{$a->debuginfo}</p>';
 $string['downloadedfilecheckfailed'] = 'Αποτυχία ελέγχου αρχείου που έγινε λήψη';
 $string['invalidmd5'] = 'Μη έγκυρο md5';
 $string['missingrequiredfield'] = 'Κάποιο απαιτούμενο πεδίο λείπει';
index 4421c5a..7b6a1e8 100644 (file)
@@ -35,7 +35,7 @@ $string['cliansweryes'] = 'ν';
 $string['cliincorrectvalueerror'] = 'Σφάλμα, λανθασμένη τιμή «{$a->value}» για το «{$a->option}»';
 $string['cliincorrectvalueretry'] = 'Λανθασμένη τιμή. Παρακαλούμε προσπαθήστε ξανά.';
 $string['clitypevalue'] = 'πληκτρολογήστε μια τιμή';
-$string['clitypevaluedefault'] = 'πληκτρολογήστε μια τιμή· πατήστε Enter για να χρησιμοποιήσετε τηνπροεπιλεγμένη τιμή ({$a})';
+$string['clitypevaluedefault'] = 'πληκτρολογήστε μια τιμή, πατήστε Enter για να χρησιμοποιήσετε την προεπιλεγμένη τιμή ({$a})';
 $string['cliunknowoption'] = 'Μη αναγνωρίσιμες επιλογές: {$a}<br />
 Παρακαλούμε χρησιμοποιήστε την επιλογή --βοήθεια';
 $string['cliyesnoprompt'] = 'πατήστε y (σημαίνει yes=ναι) ή πατήστε n (σημαίνει no=όχι)';
diff --git a/install/lang/fr_wp/langconfig.php b/install/lang/fr_wp/langconfig.php
new file mode 100644 (file)
index 0000000..7c85fcf
--- /dev/null
@@ -0,0 +1,34 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['parentlanguage'] = 'fr';
+$string['thislanguage'] = 'Français pour Workplace';
index ead9bd3..c248e34 100644 (file)
@@ -30,4 +30,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['moodlelogo'] = 'Logo Moodle';
+$string['next'] = 'Următoarea';
 $string['previous'] = 'Anterior';
+$string['reload'] = 'Reîncarcă';
index 6fe3616..15bda41 100644 (file)
@@ -29,6 +29,8 @@ $string['accessstatement'] = 'Accessibility statement';
 $string['activitynext'] = 'Next activity';
 $string['activityprev'] = 'Previous activity';
 $string['breadcrumb'] = 'Navigation bar';
+$string['eventcontextlocked'] = 'Context frozen';
+$string['eventcontextunlocked'] = 'Context unfrozen';
 $string['hideblocka'] = 'Hide {$a} block';
 $string['showblocka'] = 'Show {$a} block';
 $string['sitemap'] = 'Site map';
@@ -39,7 +41,3 @@ $string['skipto'] = 'Skip to {$a}';
 $string['tocontent'] = 'Skip to main content';
 $string['tonavigation'] = 'Go to navigation';
 $string['youarehere'] = 'You are here';
-
-// Deprecated since Moodle 3.4.
-$string['monthnext'] = 'Next month';
-$string['monthprev'] = 'Previous month';
index ac5810a..7e2b8a3 100644 (file)
@@ -319,7 +319,6 @@ $string['configrequestedstudentname'] = 'Word for student used in requested cour
 $string['configrequestedstudentsname'] = 'Word for students used in requested courses';
 $string['configrequestedteachername'] = 'Word for teacher used in requested courses';
 $string['configrequestedteachersname'] = 'Word for teachers used in requested courses';
-$string['configuserquota'] = 'The maximum number of bytes that a user can store in their own private file area. {$a->bytes} bytes == {$a->displaysize}';
 $string['configsectioninterface'] = 'Interface';
 $string['configsectionmail'] = 'Mail';
 $string['configsectionmaintenance'] = 'Maintenance';
@@ -568,6 +567,7 @@ $string['filescleanupperiod'] = 'Clean trash pool files';
 $string['filescleanupperiod_help'] = 'How often trash files are removed. These are files that are associated with a context that no longer exists';
 $string['fileconversioncleanuptask'] = 'Cleanup of temporary records for file conversions.';
 $string['filecreated'] = 'New file created';
+$string['filesizeunits'] = 'file size units';
 $string['filestoredin'] = 'Save file into folder :';
 $string['filestoredinhelp'] = 'Where the file will be stored';
 $string['filterall'] = 'Filter all strings';
@@ -1389,6 +1389,7 @@ $string['userpreference'] = 'User preference';
 $string['userpolicies'] = 'User policies';
 $string['users'] = 'Users';
 $string['userquota'] = 'User quota';
+$string['userquota_desc'] = 'The maximum number of bytes that a user can store in their own private file area.';
 $string['usesitenameforsitepages'] = 'Use site name for site pages';
 $string['usetags'] = 'Enable tags functionality';
 $string['validateemptylineerror'] = 'Empty lines are not valid';
@@ -1414,11 +1415,8 @@ $string['cachesessionhelp'] = 'User specific cache that expires when the user\'s
 $string['cacheapplication'] = 'Application cache';
 $string['cacheapplicationhelp'] = 'Cached items are shared among all users and expire by a determined time to live (ttl).';
 
-// Deprecated since Moodle 3.4
-$string['moodleorghubname'] = 'Moodle';
-$string['hubs'] = 'Hubs';
-$string['configloginhttps'] = 'Turning this on will make Moodle use a secure https connection just for the login page (providing a secure login), and then afterwards revert back to the normal http URL for general speed.  CAUTION: this setting REQUIRES https to be specifically enabled on the web server - if it is not then YOU COULD LOCK YOURSELF OUT OF YOUR SITE.';
-$string['loginhttps'] = 'Use HTTPS for logins';
 // Deprecated since Moodle 3.7.
 $string['allowblockstodock'] = 'Allow blocks to use the dock';
-$string['configallowblockstodock'] = 'If enabled and supported by the selected theme users can choose to move blocks to a special dock.';
\ No newline at end of file
+$string['configallowblockstodock'] = 'If enabled and supported by the selected theme users can choose to move blocks to a special dock.';
+// Deprecated since Moodle 3.8.
+$string['configuserquota'] = 'The maximum number of bytes that a user can store in their own private file area. {$a->bytes} bytes == {$a->displaysize}';
index 5f675fd..fa3149a 100644 (file)
@@ -268,24 +268,3 @@ $string['weekthis'] = 'This week';
 $string['when'] = 'When';
 $string['yesterday'] = 'Yesterday';
 $string['youcandeleteallrepeats'] = 'This event is part of a repeating event series. You can delete this event only, or all {$a} events in the series at once.';
-
-// Deprecated since Moodle 3.4.
-$string['quickdownloadcalendar'] = 'Quick download / subscribe to calendar';
-$string['ical'] = 'iCal';
-$string['tt_hidecourse'] = 'Course events are shown (click to hide)';
-$string['tt_hideglobal'] = 'Global events are shown (click to hide)';
-$string['tt_hidegroups'] = 'Group events are shown (click to hide)';
-$string['tt_hideuser'] = 'User events are shown (click to hide)';
-$string['tt_showcourse'] = 'Course events are hidden (click to show)';
-$string['tt_showglobal'] = 'Global events are hidden (click to show)';
-$string['tt_showgroups'] = 'Group events are hidden (click to show)';
-$string['tt_showuser'] = 'User events are hidden (click to show)';
-$string['hidecourseevents'] = 'Hide course events';
-$string['hideglobalevents'] = 'Hide global events';
-$string['hidegroupsevents'] = 'Hide group events';
-$string['hideuserevents'] = 'Hide user events';
-$string['showcourseevents'] = 'Show course events';
-$string['showglobalevents'] = 'Show global events';
-$string['showgroupsevents'] = 'Show group events';
-$string['showuserevents'] = 'Show user events';
-$string['subsource'] = 'Event source: {$a->name}';
index f28f2b2..32d16f2 100644 (file)
@@ -5,109 +5,6 @@ myfilesmanage,core
 mypreferences,core_grades
 myprofile,core
 viewallmyentries,core_blog
-registermoochtips,core_hub
-monthnext,core_access
-monthprev,core_access
-tt_hidecourse,core_calendar
-tt_hideglobal,core_calendar
-tt_hidegroups,core_calendar
-tt_hideuser,core_calendar
-tt_showcourse,core_calendar
-tt_showglobal,core_calendar
-tt_showgroups,core_calendar
-tt_showuser,core_calendar
-hidecourseevents,core_calendar
-hideglobalevents,core_calendar
-hidegroupsevents,core_calendar
-hideuserevents,core_calendar
-showcourseevents,core_calendar
-showglobalevents,core_calendar
-showgroupsevents,core_calendar
-showuserevents,core_calendar
-errorcronnoxmlrpc,core_hub
-advertiseonhub,core_hub
-advertiseonmoodleorg,core_hub
-all,core_hub
-allowglobalsearch,core_hub
-allowpublicsearch,core_hub
-badurlformat,core_hub
-community,core_hub
-cannotsearchcommunity,core_hub
-contactable,core_hub
-contactable_help,core_hub
-contactemail,core_hub
-contactname,core_hub
-contactphone,core_hub
-contactphone_help,core_hub
-continue,core_hub
-coursemap,core_hub
-courseprivate,core_hub
-coursepublic,core_hub
-errorcron,core_hub
-errorhublisting,core_hub
-errorlangnotrecognized,core_hub
-forceunregister,core_hub
-forceunregisterconfirmation,core_hub
-hub,core_hub
-information,core_hub
-moodleorg,core_hub
-nocheckstatusfromunreghub,core_hub
-nohubselected,core_hub
-none,core_hub
-nosearch,core_hub
-notregisteredonmoodleorg,core_hub
-orenterprivatehub,core_hub
-prioritise,core_hub
-private,core_hub
-privatehuburl,core_hub
-publichub,core_hub
-publish,core
-publishon,core_hub
-publishonspecifichub,core_hub
-registeredmoodleorg,core_hub
-registeredon,core_hub
-registerwith,core_hub
-registrationupdated,core_hub
-registrationupdatedfailed,core_hub
-selecthub,core_hub
-selecthubinfo,core_hub
-selecthubforadvertise,core_hub
-selecthubforsharing,core_hub
-settings,core_hub
-settingsupdated,core_hub
-shareonhub,core_hub
-sitecreated,core_hub
-siteregconfcomment,core_hub
-siteupdatedcron,core_hub
-siteupdatesend,core_hub
-siteupdatesstart,core_hub
-specifichubregistrationdetail,core_hub
-statistics,core_hub
-trustme,core_hub
-unknownstatus,core_hub
-unlistedurl,core_hub
-unprioritise,core_hub
-unregisterconfirmation,core_hub
-untrustme,core_hub
-uploaded,core_hub
-url,core_hub
-warning,core_hub
-wrongurlformat,core_hub
-xmlrpcdisabledcommunity,core_hub
-xmlrpcdisabledpublish,core_hub
-xmlrpcdisabledregistration,core_hub
-moodleorghubname,core_admin
-hubs,core_admin
-quickdownloadcalendar,core_calendar
-ical,core_calendar
-privacy,core_hub
-privacy_help,core_hub
-configloginhttps,core_admin
-loginhttps,core_admin
-groupaddnewnote,core_notes
-selectnotestate,core_notes
-extendenrol,core
-groupextendenrol,core
 virusfounduser,core_antivirus
 formattexttype,core
 currentlyselectedusers,core
@@ -149,4 +46,5 @@ nobackpackcollections,core_badges
 error:nogroups,core_badges
 purgedefinitionsuccess,core_cache
 purgestoresuccess,core_cache
-eventrolecapabilitiesupdated,core_role
\ No newline at end of file
+eventrolecapabilitiesupdated,core_role
+configuserquota,core_admin
index cef5b1e..bd87b8a 100644 (file)
@@ -221,79 +221,3 @@ $string['unregisterexplained'] = 'If the site with URL {$a} is registered on Moo
 $string['urlalreadyregistered'] = 'Your site seems to be already registered on Moodle.net, which means something has gone wrong. Please contact the Moodle.net administrator to reset your registration so you can try again.';
 $string['usersnumber'] = 'Number of users ({$a})';
 $string['wrongtoken'] = 'The registration failed for some unknown reason (network?). Please try again.';
-
-// Deprecated since Moodle 3.4.
-$string['registermoochtips'] = 'By registering, you will receive security alerts, can activate mobile app push notifications from your site, and are contributing to our Moodle statistics of the worldwide community.';
-$string['errorcronnoxmlrpc'] = 'XML-RPC must be enabled in order to update the registration.';
-$string['advertiseonhub'] = 'Share this course for people to join';
-$string['advertiseonmoodleorg'] = 'Advertise this course on moodle.org';
-$string['all'] = 'All';
-$string['allowglobalsearch'] = 'Publish this hub and allow global search of all courses';
-$string['allowpublicsearch'] = 'Publish this hub so people can join it';
-$string['badurlformat'] = 'Bad URL format';
-$string['community'] = 'Community';
-$string['cannotsearchcommunity'] = 'Sorry, you don\'t have the right permissions to see this page';
-$string['contactable'] = 'Contact from the public';
-$string['contactable_help'] = 'Set to yes, the hub will display your email address.';
-$string['contactemail'] = 'Contact email';
-$string['contactname'] = 'Contact name';
-$string['contactphone'] = 'Phone';
-$string['contactphone_help'] = 'Phone numbers are displayed to the Hub administrator only and are not shown publicly.';
-$string['continue'] = 'Continue';
-$string['coursemap'] = 'Course map';
-$string['courseprivate'] = 'Private';
-$string['coursepublic'] = 'Public';
-$string['errorcron'] = 'An error occurred during registration update on "{$a->hubname}" ({$a->errormessage})';
-$string['errorhublisting'] = 'An error occurred when retrieving the hub listing from Moodle. Please try again later. ({$a})';
-$string['errorlangnotrecognized'] = 'The provided language code is unknown by Moodle. Please contact {$a}';
-$string['forceunregister'] = 'Yes, clean registration data';
-$string['forceunregisterconfirmation'] = 'Your site cannot reach {$a}. This hub could be temporarily down. Unless you are sure you want to continue to remove registration locally, please cancel and try again later.';
-$string['hub'] = 'Hub';
-$string['information'] = 'Information';
-$string['moodleorg'] = 'Moodle';
-$string['nocheckstatusfromunreghub'] = 'The site is not registered on the hub so the status can not be checked.';
-$string['nohubselected'] = 'No hub selected';
-$string['none'] = 'None';
-$string['nosearch'] = 'Don\'t publish hub or courses';
-$string['notregisteredonmoodleorg'] = 'Your administrator needs to register this site with moodle.org.';
-$string['orenterprivatehub'] = 'Alternatively, enter a private hub URL:';
-$string['prioritise'] = 'Prioritise';
-$string['privacy'] = 'Privacy';
-$string['privacy_help'] = 'The hub may want to display a list of registered sites. If it does then you can choose whether or not you want to appear on that list.';
-$string['private'] = 'Private';
-$string['privatehuburl'] = 'Private hub URL';
-$string['publichub'] = 'Public hub';
-$string['publishon'] = 'Share on';
-$string['publishonspecifichub'] = 'Publish on another Hub';
-$string['registeredmoodleorg'] = 'Moodle ({$a})';
-$string['registeredon'] = 'Where your site is registered';
-$string['registerwith'] = 'Register with a hub';
-$string['registrationupdated'] = 'Registration has been updated.';
-$string['registrationupdatedfailed'] = 'Registration update failed.';
-$string['selecthub'] = 'Select hub';
-$string['selecthubinfo'] = 'A community hub is a server that lists courses. You can only share your courses on hubs that this Moodle site is registered with.  If the hub you want is not listed below, please contact your site administrator.';
-$string['selecthubforadvertise'] = 'Select hub for advertising';
-$string['selecthubforsharing'] = 'Select hub for uploading';
-$string['settings'] = 'Settings';
-$string['settingsupdated'] = 'Settings have been updated.';
-$string['shareonhub'] = 'Upload this course to a hub';
-$string['sitecreated'] = 'Site created';
-$string['siteregconfcomment'] = 'Your site needs a final confirmation on {$a} (in order to avoid spam on {$a})';
-$string['siteupdatedcron'] = 'Site registration updated on "{$a}"';
-$string['siteupdatesend'] = 'Finished registration update on hubs.';
-$string['siteupdatesstart'] = 'Starting registration update on hubs...';
-$string['specifichubregistrationdetail'] = 'You can also register your site with other community hubs.';
-$string['statistics'] = 'Statistics privacy';
-$string['trustme'] = 'Trust';
-$string['unknownstatus'] = 'Unknown';
-$string['unlistedurl'] = 'Unlisted hub URL';
-$string['unprioritise'] = 'Unprioritise';
-$string['unregisterconfirmation'] = 'You are about to unregister this site from the hub {$a}.  Once you disconnect from it, you will not be able to manage any courses you left there.  Are you sure you want to unregister?';
-$string['untrustme'] = 'Not trusted';
-$string['uploaded'] = 'Uploaded';
-$string['url'] = 'hub URL';
-$string['warning'] = 'WARNING';
-$string['wrongurlformat'] = 'Bad URL format';
-$string['xmlrpcdisabledcommunity'] = 'The XML-RPC extension is not enabled on the server. You can not search and download courses.';
-$string['xmlrpcdisabledpublish'] = 'The XML-RPC extension is not enabled on the server, so it is not possible to share courses or manage shared courses.';
-$string['xmlrpcdisabledregistration'] = 'The XML-RPC extension is not enabled on the server. You will not be able to unregister or update your registration until you enable it.';
index e85fe13..9d06480 100644 (file)
@@ -247,4 +247,3 @@ $string['welcomep60'] = 'The following pages will lead you through some easy to
 $string['welcomep70'] = 'Click the "Next" button below to continue with the set up of <strong>Moodle</strong>.';
 $string['wwwroot'] = 'Web address';
 $string['wwwrooterror'] = 'The \'Web Address\' does not appear to be valid - this Moodle installation doesn\'t appear to be there. The value below has been reset.';
-// Deprecated since 3.4.
index 25e9c45..b0cef08 100644 (file)
@@ -505,6 +505,7 @@ $string['desc'] = 'Descending';
 $string['description'] = 'Description';
 $string['descriptiona'] = 'Description: {$a}';
 $string['deselectall'] = 'Deselect all';
+$string['deselectnos'] = 'Deselect all \'No\'';
 $string['detailedless'] = 'Less detailed';
 $string['detailedmore'] = 'More detailed';
 $string['digitalminor'] = 'Digital minor';
@@ -1638,6 +1639,7 @@ $string['rejectdots'] = 'Reject...';
 $string['relativedatesmode'] = 'Relative dates mode';
 $string['relativedatesmode_help'] = 'Display course or activity dates relative to the user\'s start date in the course.<br />The user\'s course start date will be their enrolment start date, unless they are enrolled before the course begins in which case their start date will be the course start date.<br/><strong>WARNING: This is an experimental feature and not all activities may support it. Once the course has been created, this course setting can no longer be changed.</strong>';
 $string['relativedatesmode_warning'] = '<strong>Warning:</strong> Relative dates mode cannot be changed once the course has been created.';
+$string['relativedatestimediffformat'] = '%ad %hh %im';
 $string['reload'] = 'Reload';
 $string['remoteappuser'] = 'Remote {$a} User';
 $string['remove'] = 'Remove';
@@ -1788,6 +1790,7 @@ $string['selectedfile'] = 'Selected file';
 $string['selectedcategories'] = 'Selected categories';
 $string['selectednowmove'] = '{$a} files selected for moving. Now go into the destination folder and press \'Move files to here\'';
 $string['selectfiles'] = 'Select files';
+$string['selectitem'] = 'Select \'{$a}\'';
 $string['selectmoduletoviewhelp'] = 'Select an activity or resource to view its help.
 
 Double-click on an activity or resource name to quickly add it.';
@@ -2197,11 +2200,6 @@ $string['yourwordforx'] = 'Your word for \'{$a}\'';
 $string['zippingbackup'] = 'Zipping backup';
 $string['deprecatedeventname'] = '{$a} (no longer in use)';
 
-// Deprecated since Moodle 3.4.
-$string['publish'] = 'Publish';
-$string['extendenrol'] = 'Extend enrolment (individual)';
-$string['groupextendenrol'] = 'Extend enrolment (common)';
-
 // Deprecated since Moodle 3.6.
 $string['addedrecip'] = 'Added {$a} new recipient';
 $string['addedrecips'] = 'Added {$a} new recipients';
index 3010079..f8b7e3c 100644 (file)
@@ -74,7 +74,3 @@ $string['publishstate_help'] = 'A note\'s context determines who can see the not
 $string['site'] = 'site';
 $string['sitenotes'] = 'Site notes';
 $string['unknown'] = 'unknown';
-
-// Deprecated since Moodle 3.4
-$string['groupaddnewnote'] = 'Add a common note';
-$string['selectnotestate'] = "Select note state";
index 2383769..d07475a 100644 (file)
@@ -5276,6 +5276,15 @@ abstract class context extends stdClass implements IteratorAggregate {
         $this->_locked = $locked;
         $DB->set_field('context', 'locked', (int) $locked, ['id' => $this->id]);
         $this->mark_dirty();
+
+        if ($locked) {
+            $eventname = '\\core\\event\\context_locked';
+        } else {
+            $eventname = '\\core\\event\\context_unlocked';
+        }
+        $event = $eventname::create(['context' => $this, 'objectid' => $this->id]);
+        $event->trigger();
+
         self::reset_caches();
 
         return $this;
index e8b3649..ac2142b 100644 (file)
@@ -8611,7 +8611,6 @@ function admin_search_settings_html($query) {
                     $data = $adminroot->errors[$fullname]->data;
                 } else {
                     $data = $setting->get_setting();
-                    $data = $setting->get_setting();
                 // do not use defaults if settings not available - upgradesettings handles the defaults!
                 }
                 $sectionsettings[] = $setting->output_html($data, $query);
index 549a653..1688749 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js and b/lib/amd/build/form-autocomplete.min.js differ
index db0aeaf..11e43c8 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js.map and b/lib/amd/build/form-autocomplete.min.js.map differ
index b91dd0b..43e3227 100644 (file)
@@ -718,6 +718,7 @@ function($, log, str, templates, notification, LoadingIcon) {
             window.setTimeout(function() {
                 // Get the current element with focus.
                 var focusElement = $(document.activeElement);
+                var timeoutPromise = $.Deferred();
 
                 // Only close the menu if the input hasn't regained focus and if the element still exists,
                 // and regain focus if the scrollbar is clicked.
@@ -727,18 +728,22 @@ function($, log, str, templates, notification, LoadingIcon) {
                     inputElement.focus(); // Probably the scrollbar is clicked. Regain focus.
                 } else if (!focusElement.is(inputElement) && $(document.getElementById(state.inputId)).length) {
                     if (options.tags) {
-                        pendingPromise.then(function() {
+                        timeoutPromise.then(function() {
                             return createItem(options, state, originalSelect);
                         })
                         .catch();
                     }
-                    pendingPromise.then(function() {
+                    timeoutPromise.then(function() {
                         return closeSuggestions(state);
                     })
                     .catch();
                 }
 
-                pendingPromise.resolve();
+                timeoutPromise.then(function() {
+                    return pendingPromise.resolve();
+                })
+                .catch();
+                timeoutPromise.resolve();
             }, 500);
         });
         if (options.showSuggestions) {
index 750251b..65c2c6a 100644 (file)
@@ -702,11 +702,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
 
         switch ($windowsize) {
             case "small":
-                $width = 640;
-                $height = 480;
+                $width = 1024;
+                $height = 768;
                 break;
             case "medium":
-                $width = 1024;
+                $width = 1366;
                 $height = 768;
                 break;
             case "large":
index 3f8c053..8503da8 100644 (file)
@@ -74,6 +74,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
         'form_row' => 'form_row',
         'group_message_header' => 'group_message_header',
         'group_message' => 'group_message',
+        'autocomplete' => 'autocomplete',
     );
 
     /**
@@ -116,6 +117,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
         'form_row' => 'form_row',
         'autocomplete_selection' => 'autocomplete_selection',
         'autocomplete_suggestions' => 'autocomplete_suggestions',
+        'autocomplete' => 'autocomplete',
     );
 
     /**
@@ -221,6 +223,9 @@ XPATH
 XPATH
         , 'autocomplete_suggestions' => <<<XPATH
 .//ul[contains(concat(' ', normalize-space(@class), ' '), concat(' ', 'form-autocomplete-suggestions', ' '))]/li[@role='option'][contains(normalize-space(.), %locator%)]
+XPATH
+        , 'autocomplete' => <<<XPATH
+.//descendant::input[@id = //label[contains(normalize-space(string(.)), %locator%)]/@for]/ancestor::*[@data-fieldtype = 'autocomplete']
 XPATH
     );
 
diff --git a/lib/classes/event/context_locked.php b/lib/classes/event/context_locked.php
new file mode 100644 (file)
index 0000000..f4bf990
--- /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/>.
+
+/**
+ * Context locked event.
+ *
+ * @package    core_access
+ * @copyright  2019 University of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event triggered after a context has been frozen.
+ *
+ * @package    core_access
+ * @since      Moodle 3.8
+ * @copyright  2019 University of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class context_locked extends base {
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' locked the context with id '$this->objectid' ";
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcontextlocked', 'access');
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        // Try to get the url for the context.
+        try {
+            $context = \context::instance_by_id($this->objectid);
+            $url = $context->get_url();
+        } catch (\dml_missing_record_exception $e) {
+            // The context no longer exists, give them the system url instead.
+            $url = \context_system::instance()->get_url();
+        }
+        return $url;
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+        $this->data['objecttable'] = 'context';
+    }
+}
diff --git a/lib/classes/event/context_unlocked.php b/lib/classes/event/context_unlocked.php
new file mode 100644 (file)
index 0000000..92e6ece
--- /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/>.
+
+/**
+ * Context unlocked event.
+ *
+ * @package    core_access
+ * @copyright  2019 University of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Event triggered after a context has been unfrozen.
+ *
+ * @package    core_access
+ * @since      Moodle 3.8
+ * @copyright  2019 University of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class context_unlocked extends base {
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' unlocked the context with id '$this->objectid' ";
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcontextunlocked', 'access');
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        // Try to get the url for the context.
+        try {
+            $context = \context::instance_by_id($this->objectid);
+            $url = $context->get_url();
+        } catch (\dml_missing_record_exception $e) {
+            // The context no longer exists, give them the system url instead.
+            $url = \context_system::instance()->get_url();
+        }
+        return $url;
+    }
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+        $this->data['objecttable'] = 'context';
+    }
+}
index e340326..826f814 100644 (file)
@@ -310,11 +310,11 @@ class mustache_template_source_loader {
             if ($name) {
                 switch ($type) {
                     case Mustache_Tokenizer::T_PARTIAL:
-                        list($component, $id) = explode('/', $name);
+                        list($component, $id) = explode('/', $name, 2);
                         $templates = $addtodependencies($templates, $component, $id);
                         break;
                     case Mustache_Tokenizer::T_PARENT:
-                        list($component, $id) = explode('/', $name);
+                        list($component, $id) = explode('/', $name, 2);
                         $templates = $addtodependencies($templates, $component, $id);
                         break;
                     case Mustache_Tokenizer::T_SECTION:
index 9e781cd..021706b 100644 (file)
@@ -834,7 +834,23 @@ abstract class moodle_database {
      * @return string The sql with tablenames being prefixed with $CFG->prefix
      */
     protected function fix_table_names($sql) {
-        return preg_replace('/\{([a-z][a-z0-9_]*)\}/', $this->prefix.'$1', $sql);
+        return preg_replace_callback(
+            '/\{([a-z][a-z0-9_]*)\}/',
+            function($matches) {
+                return $this->fix_table_name($matches[1]);
+            },
+            $sql
+        );
+    }
+
+    /**
+     * Adds the prefix to the table name.
+     *
+     * @param string $tablename The table name
+     * @return string The prefixed table name
+     */
+    protected function fix_table_name($tablename) {
+        return $this->prefix . $tablename;
     }
 
     /**
index b0a6a70..8469c0d 100644 (file)
@@ -675,7 +675,8 @@ class mysqli_native_moodle_database extends moodle_database {
      */
     public function get_indexes($table) {
         $indexes = array();
-        $sql = "SHOW INDEXES FROM {$this->prefix}$table";
+        $fixedtable = $this->fix_table_name($table);
+        $sql = "SHOW INDEXES FROM $fixedtable";
         $this->query_start($sql, null, SQL_QUERY_AUX);
         $result = $this->mysqli->query($sql);
         try {
@@ -746,7 +747,8 @@ class mysqli_native_moodle_database extends moodle_database {
         } else {
             // temporary tables are not in information schema, let's try it the old way
             $result->close();
-            $sql = "SHOW COLUMNS FROM {$this->prefix}$table";
+            $fixedtable = $this->fix_table_name($table);
+            $sql = "SHOW COLUMNS FROM $fixedtable";
             $this->query_start($sql, null, SQL_QUERY_AUX);
             $result = $this->mysqli->query($sql);
             $this->query_end(true);
@@ -1317,8 +1319,8 @@ class mysqli_native_moodle_database extends moodle_database {
         $fields = implode(',', array_keys($params));
         $qms    = array_fill(0, count($params), '?');
         $qms    = implode(',', $qms);
-
-        $sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($qms)";
+        $fixedtable = $this->fix_table_name($table);
+        $sql = "INSERT INTO $fixedtable ($fields) VALUES($qms)";
 
         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
         $rawsql = $this->emulate_bound_params($sql, $params);
@@ -1483,7 +1485,8 @@ class mysqli_native_moodle_database extends moodle_database {
             }
         }
 
-        $sql = "INSERT INTO {$this->prefix}$table $fieldssql VALUES $valuessql";
+        $fixedtable = $this->fix_table_name($table);
+        $sql = "INSERT INTO $fixedtable $fieldssql VALUES $valuessql";
 
         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
         $rawsql = $this->emulate_bound_params($sql, $params);
@@ -1547,7 +1550,8 @@ class mysqli_native_moodle_database extends moodle_database {
         $params[] = $id; // last ? in WHERE condition
 
         $sets = implode(',', $sets);
-        $sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=?";
+        $fixedtable = $this->fix_table_name($table);
+        $sql = "UPDATE $fixedtable SET $sets WHERE id=?";
 
         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
         $rawsql = $this->emulate_bound_params($sql, $params);
@@ -1621,7 +1625,8 @@ class mysqli_native_moodle_database extends moodle_database {
             $newfield = "$newfield = ?";
             array_unshift($params, $normalised_value);
         }
-        $sql = "UPDATE {$this->prefix}$table SET $newfield $select";
+        $fixedtable = $this->fix_table_name($table);
+        $sql = "UPDATE $fixedtable SET $newfield $select";
         $rawsql = $this->emulate_bound_params($sql, $params);
 
         $this->query_start($sql, $params, SQL_QUERY_UPDATE);
@@ -1644,7 +1649,8 @@ class mysqli_native_moodle_database extends moodle_database {
         if ($select) {
             $select = "WHERE $select";
         }
-        $sql = "DELETE FROM {$this->prefix}$table $select";
+        $fixedtable = $this->fix_table_name($table);
+        $sql = "DELETE FROM $fixedtable $select";
 
         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
         $rawsql = $this->emulate_bound_params($sql, $params);
@@ -2032,4 +2038,17 @@ class mysqli_native_moodle_database extends moodle_database {
         }
         return false;
     }
+
+    /**
+     * Fixes any table names that clash with reserved words.
+     *
+     * @param string $tablename The table name
+     * @return string The fixed table name
+     */
+    protected function fix_table_name($tablename) {
+        $prefixedtablename = parent::fix_table_name($tablename);
+        // This function quotes the table name if it matches one of the MySQL reserved
+        // words, e.g. groups.
+        return $this->get_manager()->generator->getEncQuoted($prefixedtablename);
+    }
 }
index 97d9490..d894b36 100644 (file)
@@ -842,8 +842,7 @@ class pgsql_native_moodle_database extends moodle_database {
      * @return array of objects, or empty array if no records were found
      * @throws dml_exception A DML specific exception is thrown for any errors.
      */
-    public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
-
+    public function get_records_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
         list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
 
    &n