Merge branch 'MDL-69130-master-enfix' of git://github.com/mudrd8mz/moodle
authorJake Dallimore <jake@moodle.com>
Thu, 2 Jul 2020 04:07:58 +0000 (12:07 +0800)
committerJake Dallimore <jake@moodle.com>
Thu, 2 Jul 2020 04:07:58 +0000 (12:07 +0800)
65 files changed:
admin/cli/install.php
admin/settings/users.php
admin/tool/behat/cli/util_single_run.php
admin/tool/task/renderer.php
admin/tool/task/tests/behat/manage_tasks.feature
config-dist.php
contentbank/amd/build/actions.min.js
contentbank/amd/build/actions.min.js.map
contentbank/amd/src/actions.js
contentbank/index.php
contentbank/view.php
course/amd/build/activitychooser.min.js
course/amd/build/activitychooser.min.js.map
course/amd/src/activitychooser.js
course/format/renderer.php
course/renderer.php
course/tests/behat/paged_course_navigation.feature
grade/grading/classes/privacy/gradingform_legacy_polyfill.php
grade/grading/classes/privacy/gradingform_provider.php [deleted file]
grade/grading/classes/privacy/provider.php
grade/grading/form/upgrade.txt
grade/grading/tests/privacy_legacy_polyfill_test.php
grade/tests/behat/behat_grade.php
install.php
install/lang/te/error.php
lang/en/install.php
lang/en/moodle.php
lib/amd/build/modal.min.js
lib/amd/build/modal.min.js.map
lib/amd/build/modal_factory.min.js
lib/amd/build/modal_factory.min.js.map
lib/amd/src/modal.js
lib/amd/src/modal_factory.js
lib/behat/classes/util.php
lib/clilib.php
lib/cronlib.php
lib/db/install.xml
lib/db/upgrade.php
lib/dml/auroramysql_native_moodle_database.php [new file with mode: 0644]
lib/outputlib.php
lib/questionlib.php
lib/templates/paged_content.mustache
lib/templates/paged_content_paging_bar.mustache
lib/templates/paged_content_paging_bar_item.mustache
lib/tests/behat/behat_hooks.php
lib/upgrade.txt
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js
lib/yui/src/chooserdialogue/js/chooserdialogue.js
message/output/airnotifier/requestaccesskey.php
message/output/airnotifier/settings.php
mod/quiz/autosave.ajax.php
mod/quiz/module.js
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js
mod/quiz/yui/src/autosave/js/autosave.js
mod/scorm/tests/behat/behat_mod_scorm.php [deleted file]
question/export_form.php
report/competency/index.php
theme/boost/scss/moodle/core.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
version.php

index cfc8ea0..5c68d52 100644 (file)
@@ -215,6 +215,7 @@ define('SITEID', 1);
 
 //Database types
 $databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'native'),
+                   'auroramysql' => moodle_database::get_driver_instance('auroramysql', 'native'),
                    'mariadb'=> moodle_database::get_driver_instance('mariadb', 'native'),
                    'pgsql'  => moodle_database::get_driver_instance('pgsql',  'native'),
                    'oci'    => moodle_database::get_driver_instance('oci',    'native'),
index 0b7c4a4..c54a169 100644 (file)
@@ -261,10 +261,28 @@ if ($hassiteconfig) {
     $temp->add($setting);
 
     // See {@link https://gdpr-info.eu/art-8-gdpr/}.
+    // See {@link https://www.betterinternetforkids.eu/web/portal/practice/awareness/detail?articleId=3017751}.
     $ageofdigitalconsentmap = implode(PHP_EOL, [
         '*, 16',
         'AT, 14',
+        'BE, 13',
+        'BG, 14',
+        'CY, 14',
+        'CZ, 15',
+        'DK, 13',
+        'EE, 13',
         'ES, 14',
+        'FI, 13',
+        'FR, 15',
+        'GB, 13',
+        'GR, 15',
+        'IT, 14',
+        'LT, 14',
+        'LV, 13',
+        'MT, 13',
+        'NO, 13',
+        'PT, 13',
+        'SE, 13',
         'US, 13'
     ]);
     $setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
index 967fde3..f0a3650 100644 (file)
@@ -164,6 +164,8 @@ if ($options['install']) {
         mtrace("Acceptance tests site installed");
     }
 
+    // Note: Do not build the themes here. This is done during the 'enable' stage.
+
 } else if ($options['drop']) {
     // Ensure no tests are running.
     test_lock::acquire('behat');
@@ -182,6 +184,10 @@ if ($options['install']) {
     // Enable test mode.
     behat_util::start_test_mode($options['add-core-features-to-theme'], $options['optimize-runs'], $parallel, $run);
 
+    // Themes are only built in the 'enable' command.
+    behat_util::build_themes();
+    mtrace("Testing environment themes built");
+
     // This is only displayed once for parallel install.
     if (empty($run)) {
         // Notify user that 2.5 profile has been converted to 3.5.
index a003eab..ec0bb0c 100644 (file)
@@ -48,6 +48,7 @@ class tool_task_renderer extends plugin_renderer_base {
         $showloglink = \core\task\logmanager::has_log_report();
 
         $table = new html_table();
+        $table->caption = get_string('scheduledtasks', 'tool_task');
         $table->head = [
             get_string('name'),
             get_string('component', 'tool_task'),
@@ -180,7 +181,7 @@ class tool_task_renderer extends plugin_renderer_base {
         $plugininfo->init_display_name();
 
         $componentname = $plugininfo->displayname;
-        if (!$plugininfo->is_enabled()) {
+        if ($plugininfo->is_enabled() === false) {
             $componentname .= ' ' . html_writer::span(
                             get_string('disabled', 'tool_task'), 'badge badge-secondary');
         }
index 160451d..dd27d85 100644 (file)
@@ -65,3 +65,9 @@ Feature: Manage scheduled tasks
       | Name               | Component    | Minute | Hour | Day | Day of week | Month |
       | Log table cleanup  | Standard log | */5    | 1    | 2   | 4           | 3     |
     And I should see "Log table cleanup" in the "tr.table-primary" "css_element"
+
+  Scenario: Disabled plugin's tasks are labelled as disabled too
+    When "CAS users sync job \auth_cas\task\sync_task" row "Next run" column of "Scheduled tasks" table should contain "Plugin disabled"
+    Then "CAS users sync job \auth_cas\task\sync_task" row "Component" column of "Scheduled tasks" table should contain "Disabled"
+    And "Background processing for scheduled allocation \workshopallocation_scheduled\task\cron_task" row "Next run" column of "Scheduled tasks" table should not contain "Plugin disabled"
+    And "Background processing for scheduled allocation \workshopallocation_scheduled\task\cron_task" row "Component" column of "Scheduled tasks" table should not contain "Disabled"
index 995e0df..ecd56b6 100644 (file)
@@ -898,13 +898,6 @@ $CFG->admin = 'admin';
 //     ),
 // );
 //
-// You can force the browser session (not user's sessions) to restart after N seconds. This could
-// be useful if you are using a cloud-based service with time restrictions in the browser side.
-// Setting this value the browser session that Behat is using will be restarted. Set the time in
-// seconds. Is not recommended to use this setting if you don't explicitly need it.
-// Example:
-//   $CFG->behat_restart_browser_after = 7200;     // Restarts the browser session after 2 hours
-//
 // All this page's extra Moodle settings are compared against a white list of allowed settings
 // (the basic and behat_* ones) to avoid problems with production environments. This setting can be
 // used to expand the default white list with an array of extra settings.
index 4d78f53..237c664 100644 (file)
Binary files a/contentbank/amd/build/actions.min.js and b/contentbank/amd/build/actions.min.js differ
index 6e5344e..79956ea 100644 (file)
Binary files a/contentbank/amd/build/actions.min.js.map and b/contentbank/amd/build/actions.min.js.map differ
index 561cb99..1776292 100644 (file)
@@ -176,10 +176,10 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
         var requestType = 'success';
         Ajax.call([request])[0].then(function(data) {
             if (data.result) {
-                return Str.get_string('contentdeleted', 'core_contentbank');
+                return 'contentdeleted';
             }
             requestType = 'error';
-            return Str.get_string('contentnotdeleted', 'core_contentbank');
+            return 'contentnotdeleted';
 
         }).done(function(message) {
             var params = {
@@ -212,10 +212,10 @@ function($, Ajax, Notification, Str, Templates, Url, ModalFactory, ModalEvents)
         var requestType = 'success';
         Ajax.call([request])[0].then(function(data) {
             if (data) {
-                return Str.get_string('contentrenamed', 'core_contentbank');
+                return 'contentrenamed';
             }
             requestType = 'error';
-            return Str.get_string('contentnotrenamed', 'core_contentbank');
+            return 'contentnotrenamed';
 
         }).then(function(message) {
             var params = null;
index bf8de2b..33eff29 100644 (file)
@@ -32,8 +32,8 @@ $context = context::instance_by_id($contextid, MUST_EXIST);
 
 require_capability('moodle/contentbank:access', $context);
 
-$statusmsg = optional_param('statusmsg', '', PARAM_RAW);
-$errormsg = optional_param('errormsg', '', PARAM_RAW);
+$statusmsg = optional_param('statusmsg', '', PARAM_ALPHANUMEXT);
+$errormsg = optional_param('errormsg', '', PARAM_ALPHANUMEXT);
 
 $title = get_string('contentbank');
 \core_contentbank\helper::get_page_ready($context, $title);
@@ -98,9 +98,11 @@ echo $OUTPUT->header();
 echo $OUTPUT->box_start('generalbox');
 
 // If needed, display notifications.
-if ($errormsg !== '') {
+if ($errormsg !== '' && get_string_manager()->string_exists($errormsg, 'core_contentbank')) {
+    $errormsg = get_string($errormsg, 'core_contentbank');
     echo $OUTPUT->notification($errormsg);
-} else if ($statusmsg !== '') {
+} else if ($statusmsg !== '' && get_string_manager()->string_exists($statusmsg, 'core_contentbank')) {
+    $statusmsg = get_string($statusmsg, 'core_contentbank');
     echo $OUTPUT->notification($statusmsg, 'notifysuccess');
 }
 
index 9f2a8d1..5fd66d1 100644 (file)
@@ -35,8 +35,8 @@ $record = $DB->get_record('contentbank_content', ['id' => $id], '*', MUST_EXIST)
 $context = context::instance_by_id($record->contextid, MUST_EXIST);
 require_capability('moodle/contentbank:access', $context);
 
-$statusmsg = optional_param('statusmsg', '', PARAM_RAW);
-$errormsg = optional_param('errormsg', '', PARAM_RAW);
+$statusmsg = optional_param('statusmsg', '', PARAM_ALPHANUMEXT);
+$errormsg = optional_param('errormsg', '', PARAM_ALPHANUMEXT);
 
 $returnurl = new \moodle_url('/contentbank/index.php', ['contextid' => $context->id]);
 $plugin = core_plugin_manager::instance()->get_plugin_info($record->contenttype);
@@ -111,9 +111,11 @@ $PAGE->add_header_action(html_writer::div(
 echo $OUTPUT->header();
 
 // If needed, display notifications.
-if ($errormsg !== '') {
+if ($errormsg !== '' && get_string_manager()->string_exists($errormsg, 'core_contentbank')) {
+    $errormsg = get_string($errormsg, 'core_contentbank');
     echo $OUTPUT->notification($errormsg);
-} else if ($statusmsg !== '') {
+} else if ($statusmsg !== '' && get_string_manager()->string_exists($statusmsg, 'core_contentbank')) {
+    $statusmsg = get_string($statusmsg, 'core_contentbank');
     echo $OUTPUT->notification($statusmsg, 'notifysuccess');
 }
 if ($contenttype->can_access()) {
index 27aebcf..0ea080e 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js and b/course/amd/build/activitychooser.min.js differ
index 055d5ac..6519c83 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js.map and b/course/amd/build/activitychooser.min.js.map differ
index 176ed99..a1d1bbc 100644 (file)
@@ -136,7 +136,7 @@ const registerListenerEvents = (courseId, chooserConfig) => {
                 const data = await fetchModuleData();
 
                 // Apply the section id to all the module instance links.
-                const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);
+                const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid, caller.dataset.sectionreturnid);
 
                 ChooserDialogue.displayChooser(
                     sectionModal,
@@ -161,13 +161,14 @@ const registerListenerEvents = (courseId, chooserConfig) => {
  * @method sectionIdMapper
  * @param {Object} webServiceData Our original data from the Web service call
  * @param {Number} id The ID of the section we need to append to the links
+ * @param {Number|null} sectionreturnid The ID of the section return we need to append to the links
  * @return {Array} [modules] with URL's built
  */
-const sectionIdMapper = (webServiceData, id) => {
+const sectionIdMapper = (webServiceData, id, sectionreturnid) => {
     // We need to take a fresh deep copy of the original data as an object is a reference type.
     const newData = JSON.parse(JSON.stringify(webServiceData));
     newData.content_items.forEach((module) => {
-        module.link += '&section=' + id;
+        module.link += '&section=' + id + '&sr=' + (sectionreturnid ?? 0);
     });
     return newData.content_items;
 };
@@ -247,6 +248,7 @@ const buildModal = (bodyPromise, footer) => {
         body: bodyPromise,
         footer: footer.customfootertemplate,
         large: true,
+        scrollable: false,
         templateContext: {
             classes: 'modchooser'
         }
index 6fbe141..b5884c3 100644 (file)
@@ -203,7 +203,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             'class' => 'section main clearfix'.$sectionstyle,
             'role' => 'region',
             'aria-labelledby' => "sectionid-{$section->id}-title",
-            'data-sectionid' => $section->section
+            'data-sectionid' => $section->section,
+            'data-sectionreturnid' => $sectionreturn
         ]);
 
         $leftcontent = $this->section_left_content($section, $course, $onsectionpage);
index bb7b3b9..da82415 100644 (file)
@@ -376,6 +376,7 @@ class core_course_renderer extends plugin_renderer_base {
                     'class' => 'section-modchooser-link btn btn-link',
                     'data-action' => 'open-chooser',
                     'data-sectionid' => $section,
+                    'data-sectionreturnid' => $sectionreturn,
                 ]
             );
             $ajaxcontrol .= html_writer::end_tag('div');
index 92fd475..04b7b15 100644 (file)
@@ -38,6 +38,29 @@ Feature: Course paged mode
       | topics | "Topic 1" | "Topic 2" | "Topic 3" | "Topic 0" | "Topic 4" |
       | weeks | "1 January - 7 January" | "8 January - 14 January" | "15 January - 21 January" | "25 December - 31 December" | "22 January - 28 January" |
 
+  @javascript
+  Scenario Outline: Paged section redirect after creating an activity
+    Given the following "courses" exist:
+      | fullname | shortname | category | format | coursedisplay | numsections | startdate |
+      | Course 1 | C1        | 0        | <courseformat> | 1     | 3           | 0         |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    And I click on <section1> "link" in the <section1> "section"
+    And I should see <section1> in the "div.single-section" "css_element"
+    And I should see <section2> in the ".single-section span.mdl-right" "css_element"
+    And I should not see <prevunexistingsection> in the ".single-section" "css_element"
+    When I add a "Chat" to section "1" and I fill the form with:
+      | Name of this chat room | Chat room |
+      | Description | Chat description |
+    Then I should see <section1> in the "div.single-section" "css_element"
+    And I should see <section2> in the ".single-section span.mdl-right" "css_element"
+    And I should not see <prevunexistingsection> in the ".single-section" "css_element"
+
+    Examples:
+      | courseformat | section1 | section2 | prevunexistingsection |
+      | topics       | "Topic 1" | "Topic 2" | "Topic 0"          |
+      | weeks        | "1 January - 7 January" | "8 January - 14 January" | "25 December - 31 December" |
+
   Scenario Outline: Weekly and topics course formats with Javascript disabled
     Given the following "courses" exist:
       | fullname | shortname | category | format | coursedisplay | numsections | startdate |
index 5973857..b6f24a3 100644 (file)
@@ -53,48 +53,4 @@ trait gradingform_legacy_polyfill {
     public static function delete_gradingform_for_instances(array $instanceids) {
         static::_delete_gradingform_for_instances($instanceids);
     }
-
-    /**
-     * This method is used to export any user data this sub-plugin has using the object to get the context and userid.
-     *
-     * @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
-     * @todo MDL-63167 remove this method.
-     *
-     * @param context $context Context owner of the data.
-     * @param stdClass $definition Grading definition entry to export.
-     * @param int $userid The user whose information is to be exported.
-     *
-     * @return stdClass The data to export.
-     */
-    public static function get_gradingform_export_data(\context $context, $definition, int $userid) {
-        debugging('This method is deprecated. Please use the gradingform_provider_v2 interface', DEBUG_DEVELOPER);
-        return static::_get_gradingform_export_data($context, $definition, $userid);
-    }
-
-    /**
-     * Any call to this method should delete all user data for the context defined.
-     *
-     * @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
-     * @todo MDL-63167 remove this method.
-     *
-     * @param context $context Context owner of the data.
-     */
-    public static function delete_gradingform_for_context(\context $context) {
-        debugging('This method is deprecated. Please use the gradingform_provider_v2 interface', DEBUG_DEVELOPER);
-        static::_delete_gradingform_for_context($context);
-    }
-
-    /**
-     * A call to this method should delete user data (where practicle) from the userid and context.
-     *
-     * @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
-     * @todo MDL-63167 remove this method.
-     *
-     * @param int $userid The user whose information is to be deleted.
-     * @param context $context Context owner of the data.
-     */
-    public static function delete_gradingform_for_userid(int $userid, \context $context) {
-        debugging('This method is deprecated. Please use the gradingform_provider_v2 interface', DEBUG_DEVELOPER);
-        static::_delete_gradingform_for_userid($userid, $context);
-    }
 }
diff --git a/grade/grading/classes/privacy/gradingform_provider.php b/grade/grading/classes/privacy/gradingform_provider.php
deleted file mode 100644 (file)
index 1602956..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-<?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/>.
-
-/**
- * This file contains the grading method interface.
- *
- * Grading method plugins should implement this if they store personal information.
- *
- * @deprecated since Moodle 3.6 MDL-62535 Please use the gradingform_provider_v2 interface
- * @todo MDL-63167 Remove this file.
- *
- * @package    core_grading
- * @copyright  2018 Sara Arjona <sara@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace core_grading\privacy;
-
-defined('MOODLE_INTERNAL') || die();
-
-use core_privacy\local\request\approved_contextlist;
-
-interface gradingform_provider extends
-    \core_privacy\local\request\plugin\subsystem_provider,
-    \core_privacy\local\deprecated {
-
-    /**
-     * This method is used to export any user data this sub-plugin has using the object to get the context and userid.
-     *
-     * @deprecated since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface
-     * @todo MDL-63167 Remove this file.
-     *
-     * @param \context $context Context owner of the data.
-     * @param \stdClass $definition Grading definition entry to export.
-     * @param  int $userid The user whose information is to be exported.
-     *
-     * @return \stdClass The data to export.
-     */
-    public static function get_gradingform_export_data(\context $context, $definition, int $userid);
-
-    /**
-     * Any call to this method should delete all user data for the context defined.
-     *
-     * @deprecated since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface
-     * @todo MDL-63167 Remove this file.
-     *
-     * @param \context $context Context owner of the data.
-     */
-    public static function delete_gradingform_for_context(\context $context);
-
-    /**
-     * A call to this method should delete user data (where practicle) from the userid and context.
-     *
-     * @deprecated since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface
-     * @todo MDL-63167 Remove this file.
-     *
-     * @param int $userid The user to delete.
-     * @param \context $context the context to refine the deletion.
-     */
-    public static function delete_gradingform_for_userid(int $userid, \context $context);
-}
index 54eb9ed..646c050 100644 (file)
@@ -304,19 +304,6 @@ class provider implements
                 $tmpdata['timecopied'] = transform::datetime($definition->timecopied);
             }
 
-            // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
-            // Export gradingform information (if needed).
-            $instancedata = manager::component_class_callback(
-                "gradingform_{$definition->method}",
-                gradingform_provider::class,
-                'get_gradingform_export_data',
-                [$context, $definition, $userid]
-            );
-            if (null !== $instancedata) {
-                $tmpdata = array_merge($tmpdata, $instancedata);
-            }
-            // End of section to be removed with deprecation.
-
             $defdata[] = (object) $tmpdata;
 
             // Export grading_instances information.
@@ -378,14 +365,7 @@ class provider implements
      * @param \context $context the context to delete in.
      */
     public static function delete_data_for_all_users_in_context(\context $context) {
-        // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
-        manager::plugintype_class_callback(
-            'gradingform',
-            gradingform_provider::class,
-            'delete_gradingform_for_context',
-            [$context]
-        );
-        // End of section to be removed for final deprecation.
+        // The only information left to be deleted here is the grading definitions. Currently we are not deleting these.
     }
 
     /**
@@ -395,14 +375,7 @@ class provider implements
      * @param approved_contextlist $contextlist a list of contexts approved for deletion.
      */
     public static function delete_data_for_user(approved_contextlist $contextlist) {
-        // MDL-63167 - This section is to be removed with the final deprecation of the gradingform_provider interface.
-        manager::plugintype_class_callback(
-            'gradingform',
-            gradingform_provider::class,
-            'delete_gradingform_for_userid',
-            [$contextlist]
-        );
-        // End of section to be removed for final deprecation.
+        // The only information left to be deleted here is the grading definitions. Currently we are not deleting these.
     }
 
     /**
index 0bca314..7a8be2d 100644 (file)
@@ -1,6 +1,14 @@
 This files describes API changes in /grade/grading/form/* - Advanced grading methods
 information provided here is intended especially for developers.
 
+=== 4.0 ===
+
+* Removed gradingform_provider.
+* Removed the following deprecated functions:
+    get_gradingform_export_data
+    delete_gradingform_for_context
+    delete_gradingform_for_userid
+
 === 3.6 ===
 
 * The privacy interface gradingform_provider has been deprecated. Please use
index 63465c8..c424d57 100644 (file)
@@ -69,56 +69,6 @@ class gradeform_privacy_legacy_polyfill_test extends advanced_testcase {
         test_legacy_polyfill_gradingform_provider::$mock = $mock;
         test_legacy_polyfill_gradingform_provider::delete_gradingform_for_instances([3, 17]);
     }
-
-    /**
-     * Test the __get_gradingform_export_data shim.
-     */
-    public function test_get_gradingform_export_data() {
-        $userid = 476;
-        $context = context_system::instance();
-
-        $mock = $this->createMock(test_gradingform_legacy_polyfill_mock_wrapper::class);
-        $mock->expects($this->once())
-            ->method('get_return_value')
-            ->with('_get_gradingform_export_data', [$context, (object)[], $userid]);
-
-        test_legacy_polyfill_gradingform_provider::$mock = $mock;
-        test_legacy_polyfill_gradingform_provider::get_gradingform_export_data($context, (object)[], $userid);
-        $this->assertDebuggingCalled();
-    }
-
-    /**
-     * Test the _delete_gradingform_for_context shim.
-     */
-    public function test_delete_gradingform_for_context() {
-        $context = context_system::instance();
-
-        $mock = $this->createMock(test_gradingform_legacy_polyfill_mock_wrapper::class);
-        $mock->expects($this->once())
-            ->method('get_return_value')
-            ->with('_delete_gradingform_for_context', [$context]);
-
-        test_legacy_polyfill_gradingform_provider::$mock = $mock;
-        test_legacy_polyfill_gradingform_provider::delete_gradingform_for_context($context);
-        $this->assertDebuggingCalled();
-    }
-
-    /**
-     * Test the _delete_gradingform_for_userid shim.
-     */
-    public function test_delete_gradingform_for_user() {
-        $userid = 696;
-        $context = \context_system::instance();
-
-        $mock = $this->createMock(test_gradingform_legacy_polyfill_mock_wrapper::class);
-        $mock->expects($this->once())
-            ->method('get_return_value')
-            ->with('_delete_gradingform_for_userid', [$userid, $context]);
-
-        test_legacy_polyfill_gradingform_provider::$mock = $mock;
-        test_legacy_polyfill_gradingform_provider::delete_gradingform_for_userid($userid, $context);
-        $this->assertDebuggingCalled();
-    }
 }
 
 /**
@@ -129,7 +79,6 @@ class gradeform_privacy_legacy_polyfill_test extends advanced_testcase {
  */
 class test_legacy_polyfill_gradingform_provider implements
     \core_privacy\local\metadata\provider,
-    \core_grading\privacy\gradingform_provider,
     \core_grading\privacy\gradingform_provider_v2 {
 
     use \core_grading\privacy\gradingform_legacy_polyfill;
@@ -169,47 +118,6 @@ class test_legacy_polyfill_gradingform_provider implements
     protected static function _get_metadata(\core_privacy\local\metadata\collection $collection) {
         return $collection;
     }
-
-    /**
-     * This method is used to export any user data this sub-plugin has using the object to get the context and userid.
-     *
-     * @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
-     * @todo MDL-63167 remove this method.
-     *
-     * @param context $context Context owner of the data.
-     * @param stdClass $definition Grading definition entry to export.
-     * @param int $userid The user whose information is to be exported.
-     *
-     * @return stdClass The data to export.
-     */
-    protected static function _get_gradingform_export_data(\context $context, $definition, int $userid) {
-        static::$mock->get_return_value(__FUNCTION__, func_get_args());
-    }
-
-    /**
-     * Any call to this method should delete all user data for the context defined.
-     *
-     * @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
-     * @todo MDL-63167 remove this method.
-     *
-     * @param context $context Context owner of the data.
-     */
-    protected static function _delete_gradingform_for_context(\context $context) {
-        static::$mock->get_return_value(__FUNCTION__, func_get_args());
-    }
-
-    /**
-     * A call to this method should delete user data (where practicle) from the userid and context.
-     *
-     * @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
-     * @todo MDL-63167 remove this method.
-     *
-     * @param int $userid The user whose information is to be deleted.
-     * @param context $context Context owner of the data.
-     */
-    protected static function _delete_gradingform_for_userid(int $userid, \context $context) {
-        static::$mock->get_return_value(__FUNCTION__, func_get_args());
-    }
 }
 
 /**
index b5e6dde..02a4d1e 100644 (file)
@@ -226,7 +226,7 @@ class behat_grade extends behat_base {
             $inputxpath = "//input[@class='idnumber'][" .
                     "parent::li[@class='item'][text()='" . $gradeitem . "']" .
                     " | " .
-                    "parent::li[@class='categoryitem' | @class='courseitem']" .
+                    "parent::li[@class='categoryitem' or @class='courseitem']" .
                     "/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
                     "]";
             $this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
index 973f833..82c5e35 100644 (file)
@@ -493,6 +493,7 @@ if ($config->stage == INSTALL_DATABASETYPE) {
                                   get_string('databasetypesub', 'install'));
 
     $databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'native'),
+                       'auroramysql' => moodle_database::get_driver_instance('auroramysql', 'native'),
                        'mariadb'=> moodle_database::get_driver_instance('mariadb', 'native'),
                        'pgsql'  => moodle_database::get_driver_instance('pgsql',  'native'),
                        'oci'    => moodle_database::get_driver_instance('oci',    'native'),
index 627afb6..a80e43a 100644 (file)
@@ -42,3 +42,6 @@ $string['cannotsavemd5file'] = 'Md5 ఫైల్ను సేవ్ చేయల
 $string['cannotsavezipfile'] = 'జిప్ ఫైల్ను సేవ్ చేయడం సాధ్యపడదు';
 $string['cannotunzipfile'] = 'ఫైల్ అన్జిప్ చేయలేరు';
 $string['componentisuptodate'] = 'కాంపొనెంటు తాజాగా ఉంది';
+$string['dmlexceptiononinstall'] = '<p>A డేటాబేస్ లోపం ఏర్పడింది [{$ a-> errorcode}]. <br /> {$a->debuginfo} </p>';
+$string['downloadedfilecheckfailed'] = 'డౌన్లోడ్ చేసిన ఫైల్ తనిఖీ విఫలమైంది';
+$string['invalidmd5'] = 'చెక్ వేరియబుల్ తప్పు. - మళ్ళీ ప్రయత్నించండి';
index 047c1cb..681807f 100644 (file)
@@ -169,6 +169,12 @@ $string['memorylimithelp'] = '<p>The PHP memory limit for your server is current
     (you will see errors when you look at pages) so you\'ll have to remove the .htaccess file.</p></li>
 </ol>';
 $string['mysqliextensionisnotpresentinphp'] = 'PHP has not been properly configured with the MySQLi extension for it to communicate with MySQL. Please check your php.ini file or recompile PHP.';
+$string['nativeauroramysql'] = 'Aurora MySQL (native/auroramysql)';
+$string['nativeauroramysqlhelp'] = '<p>The database is where most of the Moodle settings and data are stored and must be configured here.</p>
+<p>The database name, username, and password are required fields; table prefix is optional.</p>
+<p>The database name may contain only alphanumeric characters, dollar ($) and underscore (_).</p>
+<p>If the database currently does not exist, and the user you specify has permission, Moodle will attempt to create a new database with the correct permissions and settings.</p>
+<p>This driver is not compatible with legacy MyISAM engine.</p>';
 $string['nativemariadb'] = 'MariaDB (native/mariadb)';
 $string['nativemariadbhelp'] = '<p>The database is where most of the Moodle settings and data are stored and must be configured here.</p>
 <p>The database name, username, and password are required fields; table prefix is optional.</p>
index 7672db6..24d2c03 100644 (file)
@@ -854,6 +854,7 @@ $string['first'] = 'First';
 $string['firstaccess'] = 'First access';
 $string['firstname'] = 'First name';
 $string['firstnamephonetic'] = 'First name - phonetic';
+$string['firstpage'] = 'First page';
 $string['firstsiteaccess'] = 'First access to site';
 $string['firsttime'] = 'Is this your first time here?';
 $string['folder'] = 'Folder';
@@ -1133,6 +1134,7 @@ $string['lastlogin'] = 'Last login';
 $string['lastmodified'] = 'Last modified';
 $string['lastname'] = 'Surname';
 $string['lastnamephonetic'] = 'Surname - phonetic';
+$string['lastpage'] = 'Last page';
 $string['lastsiteaccess'] = 'Last access to site';
 $string['lastyear'] = 'Last year';
 $string['latestlanguagepack'] = 'Check for latest language pack on moodle.org';
@@ -1417,6 +1419,7 @@ Cheers from the \'{$a->sitename}\' administrator,
 $string['newusers'] = 'New users';
 $string['newwindow'] = 'New window';
 $string['next'] = 'Next';
+$string['nextpage'] = 'Next page';
 $string['nextsection'] = 'Next section';
 $string['no'] = 'No';
 $string['noblockstoaddhere'] = 'There are no blocks that you can add to this page.';
@@ -1600,6 +1603,7 @@ $string['preview'] = 'Preview';
 $string['previeworchoose'] = 'Preview or choose a theme';
 $string['previous'] = 'Previous';
 $string['previouslyselectedusers'] = 'Previously selected users not matching \'{$a}\'';
+$string['previouspage'] = 'Previous page';
 $string['previoussection'] = 'Previous section';
 $string['primaryadminsetup'] = 'Setup administrator account';
 $string['privacy:metadata:config_log'] = 'The log of configuration changes.';
index 1b3b1b4..69414dc 100644 (file)
Binary files a/lib/amd/build/modal.min.js and b/lib/amd/build/modal.min.js differ
index 6ebad58..19c89db 100644 (file)
Binary files a/lib/amd/build/modal.min.js.map and b/lib/amd/build/modal.min.js.map differ
index 00c5ea4..3e6d275 100644 (file)
Binary files a/lib/amd/build/modal_factory.min.js and b/lib/amd/build/modal_factory.min.js differ
index e994255..8b2bf04 100644 (file)
Binary files a/lib/amd/build/modal_factory.min.js.map and b/lib/amd/build/modal_factory.min.js.map differ
index d04924f..442c5b0 100644 (file)
@@ -553,6 +553,22 @@ define([
         return !this.getModal().hasClass('modal-lg');
     };
 
+    /**
+     * Set this modal to be scrollable or not.
+     *
+     * @method setScrollable
+     * @param {bool} value Whether the modal is scrollable or not
+     */
+    Modal.prototype.setScrollable = function(value) {
+        if (!value) {
+            this.getModal()[0].classList.remove('modal-dialog-scrollable');
+            return;
+        }
+
+        this.getModal()[0].classList.add('modal-dialog-scrollable');
+    };
+
+
     /**
      * Determine the highest z-index value currently on the page.
      *
index 1e27c2f..e96681a 100644 (file)
@@ -161,6 +161,8 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
     var create = function(modalConfig, triggerElement) {
         var type = modalConfig.type || TYPES.DEFAULT;
         var isLarge = modalConfig.large ? true : false;
+        // If 'scrollable' is not configured, set the modal to be scrollable by default.
+        var isScrollable = modalConfig.hasOwnProperty('scrollable') ? modalConfig.scrollable : true;
         var registryConf = null;
         var templateContext = {};
 
@@ -203,6 +205,8 @@ define(['jquery', 'core/modal_events', 'core/modal_registry', 'core/modal',
                     modal.setRemoveOnClose(modalConfig.removeOnClose);
                 }
 
+                modal.setScrollable(isScrollable);
+
                 return modal;
             });
 
index 267406b..b24ea48 100644 (file)
@@ -32,6 +32,7 @@ require_once(__DIR__ . '/behat_config_manager.php');
 
 require_once(__DIR__ . '/../../filelib.php');
 require_once(__DIR__ . '/../../clilib.php');
+require_once(__DIR__ . '/../../csslib.php');
 
 use Behat\Mink\Session;
 use Behat\Mink\Exception\ExpectationException;
@@ -131,6 +132,35 @@ class behat_util extends testing_util {
         self::store_database_state();
     }
 
+    /**
+     * Build theme CSS.
+     */
+    public static function build_themes() {
+        global $CFG;
+        require_once("{$CFG->libdir}/outputlib.php");
+
+        $themenames = array_keys(\core_component::get_plugin_list('theme'));
+
+        // Load the theme configs.
+        $themeconfigs = array_map(function($themename) {
+            return \theme_config::load($themename);
+        }, $themenames);
+
+        // Build the list of themes and cache them in local cache.
+        $themes = theme_build_css_for_themes($themeconfigs, ['ltr'], true);
+
+        $framework = self::get_framework();
+        $storageroot = self::get_dataroot() . "/{$framework}/themedata";
+
+        foreach ($themes as $themename => $themedata) {
+            $dirname = "{$storageroot}/{$themename}";
+            check_dir_exists($dirname);
+            foreach ($themedata as $direction => $css) {
+                file_put_contents("{$dirname}/{$direction}.css", $css);
+            }
+        }
+    }
+
     /**
      * Drops dataroot and remove test database tables
      * @throws coding_exception
@@ -397,6 +427,37 @@ class behat_util extends testing_util {
         initialise_cfg();
     }
 
+    /**
+     * Restore theme CSS stored during behat setup.
+     */
+    public static function restore_saved_themes(): void {
+        global $CFG;
+
+        $themerev = theme_get_revision();
+
+        $framework = self::get_framework();
+        $storageroot = self::get_dataroot() . "/{$framework}/themedata";
+        $themenames = array_keys(\core_component::get_plugin_list('theme'));
+        $directions = ['ltr', 'rtl'];
+
+        $themeconfigs = array_map(function($themename) {
+            return \theme_config::load($themename);
+        }, $themenames);
+
+        foreach ($themeconfigs as $themeconfig) {
+            $themename = $themeconfig->name;
+            $themesubrev = theme_get_sub_revision_for_theme($themename);
+
+            $dirname = "{$storageroot}/{$themename}";
+            foreach ($directions as $direction) {
+                $cssfile = "{$dirname}/{$direction}.css";
+                if (file_exists($cssfile)) {
+                    $themeconfig->set_css_content_cache(file_get_contents($cssfile));
+                }
+            }
+        }
+    }
+
     /**
      * Pause execution immediately.
      *
index 63b5e43..1495aed 100644 (file)
@@ -145,6 +145,21 @@ function cli_get_params(array $longoptions, array $shortmapping=null) {
     return array($options, $unrecognized);
 }
 
+/**
+ * This sets the cli process title suffix
+ *
+ * An example is appending current Task API info so a sysadmin can immediately
+ * see what task a cron process is running at any given moment.
+ *
+ * @param string $suffix process suffix
+ */
+function cli_set_process_title_suffix(string $suffix) {
+    if (defined('CLI_SCRIPT') && isset($_SERVER['argv'])) {
+        $command = join(' ', $_SERVER['argv']);
+        @cli_set_process_title("php $command ($suffix)");
+    }
+}
+
 /**
  * Print or return section separator string
  * @param bool $return false means print, true return as string
index 88c7463..919043d 100644 (file)
@@ -196,10 +196,12 @@ function cron_run_adhoc_tasks(int $timenow, $keepalive = 0, $checklimits = true)
             }
             $waiting = false;
             cron_run_inner_adhoc_task($task);
+            cron_set_process_title("Waiting for next adhoc task");
             $taskcount++;
             unset($task);
         } else {
-            if (time() >= $finishtime) {
+            $timeleft = $finishtime - time();
+            if ($timeleft <= 0) {
                 break;
             }
             if (!$waiting) {
@@ -208,6 +210,7 @@ function cron_run_adhoc_tasks(int $timenow, $keepalive = 0, $checklimits = true)
                 mtrace('.', '');
             }
             $waiting = true;
+            cron_set_process_title("Waiting {$timeleft}s for next adhoc task");
             sleep(1);
         }
     }
@@ -238,6 +241,7 @@ function cron_run_inner_scheduled_task(\core\task\task_base $task) {
 
     $fullname = $task->get_name() . ' (' . get_class($task) . ')';
     mtrace('Execute scheduled task: ' . $fullname);
+    cron_set_process_title('Scheduled task: ' . get_class($task));
     cron_trace_time_and_memory();
     $predbqueries = null;
     $predbqueries = $DB->perf_get_queries();
@@ -277,6 +281,7 @@ function cron_run_inner_scheduled_task(\core\task\task_base $task) {
     } finally {
         // Reset back to the standard admin user.
         cron_setup_user();
+        cron_set_process_title('Waiting for next scheduled task');
         cron_prepare_core_renderer(true);
     }
     get_mailer('close');
@@ -293,6 +298,7 @@ function cron_run_inner_adhoc_task(\core\task\adhoc_task $task) {
     \core\task\logmanager::start_logging($task);
 
     mtrace("Execute adhoc task: " . get_class($task));
+    cron_set_process_title('Adhoc task: ' . $task->get_id() . ' ' . get_class($task));
     cron_trace_time_and_memory();
     $predbqueries = null;
     $predbqueries = $DB->perf_get_queries();
@@ -367,6 +373,23 @@ function cron_run_inner_adhoc_task(\core\task\adhoc_task $task) {
     get_mailer('close');
 }
 
+/**
+ * Sets the process title
+ *
+ * This makes it very easy for a sysadmin to immediately see what task
+ * a cron process is running at any given moment.
+ *
+ * @param string $title process status title
+ */
+function cron_set_process_title(string $title) {
+    global $CFG;
+    if (defined('CLI_SCRIPT')) {
+        require_once($CFG->libdir . '/clilib.php');
+        $datetime = userdate(time(), '%b %d, %H:%M:%S');
+        cli_set_process_title_suffix("$datetime $title");
+    }
+}
+
 /**
  * Output some standard information during cron runs. Specifically current time
  * and memory usage. This method also does gc_collect_cycles() (before displaying
index 8f91417..0e0c4f0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20200504" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20200625" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
         <KEY NAME="creatorid" TYPE="foreign" FIELDS="creatorid" REFTABLE="user" REFFIELDS="id"/>
       </KEYS>
+      <INDEXES>
+        <INDEX NAME="token" UNIQUE="false" FIELDS="token" COMMENT="This index will be used anytime a token is validated."/>
+      </INDEXES>
     </TABLE>
     <TABLE NAME="blog_association" COMMENT="Associations of blog entries with courses and module instances">
       <FIELDS>
index 53fa76a..977d7df 100644 (file)
@@ -2443,6 +2443,60 @@ function xmldb_main_upgrade($oldversion) {
 
     // Automatically generated Moodle v3.9.0 release upgrade line.
     // Put any upgrade step following this.
+    if ($oldversion < 2020061500.02) {
+        // Update default digital age consent map according to the current legislation on each country.
+
+        // The default age of digital consent map for 38 and below.
+        $oldageofdigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            'AT, 14',
+            'ES, 14',
+            'US, 13'
+        ]);
+
+        // Check if the current age of digital consent map matches the old one.
+        if (get_config('moodle', 'agedigitalconsentmap') === $oldageofdigitalconsentmap) {
+            // If the site is still using the old defaults, upgrade to the new default.
+            $ageofdigitalconsentmap = implode(PHP_EOL, [
+                '*, 16',
+                'AT, 14',
+                'BE, 13',
+                'BG, 14',
+                'CY, 14',
+                'CZ, 15',
+                'DK, 13',
+                'EE, 13',
+                'ES, 14',
+                'FI, 13',
+                'FR, 15',
+                'GB, 13',
+                'GR, 15',
+                'IT, 14',
+                'LT, 14',
+                'LV, 13',
+                'MT, 13',
+                'NO, 13',
+                'PT, 13',
+                'SE, 13',
+                'US, 13'
+            ]);
+            set_config('agedigitalconsentmap', $ageofdigitalconsentmap);
+        }
+
+        upgrade_main_savepoint(true, 2020061500.02);
+    }
+
+    if ($oldversion < 2020062600.01) {
+        // Add index to the token field in the external_tokens table.
+        $table = new xmldb_table('external_tokens');
+        $index = new xmldb_index('token', XMLDB_INDEX_NOTUNIQUE, ['token']);
+
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        upgrade_main_savepoint(true, 2020062600.01);
+    }
 
     return true;
 }
diff --git a/lib/dml/auroramysql_native_moodle_database.php b/lib/dml/auroramysql_native_moodle_database.php
new file mode 100644 (file)
index 0000000..3000913
--- /dev/null
@@ -0,0 +1,99 @@
+<?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/>.
+
+/**
+ * Native Aurora MySQL class representing moodle database interface.
+ *
+ * @package    core_dml
+ * @copyright  2020 Lafayette College ITS
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__.'/moodle_database.php');
+require_once(__DIR__.'/mysqli_native_moodle_database.php');
+require_once(__DIR__.'/mysqli_native_moodle_recordset.php');
+require_once(__DIR__.'/mysqli_native_moodle_temptables.php');
+
+/**
+ * Native Aurora MySQL class representing moodle database interface.
+ *
+ * @package    core_dml
+ * @copyright  2020 Lafayette College ITS
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class auroramysql_native_moodle_database extends mysqli_native_moodle_database {
+
+    /** @var bool is compressed row format supported cache */
+    protected $compressedrowformatsupported = false;
+
+    /**
+     * Returns localised database type name.
+     *
+     * Returns localised database type name. Can be used before connect().
+     * @return string
+     */
+    public function get_name(): ?string {
+        return get_string('nativeauroramysql', 'install');
+    }
+
+    /**
+     * Returns localised database configuration help.
+     *
+     * Returns localised database configuration help. Can be used before connect().
+     * @return string
+     */
+    public function get_configuration_help(): ?string {
+        return get_string('nativeauroramysql', 'install');
+    }
+
+    /**
+     * Returns the database vendor.
+     *
+     * Returns the database vendor. Can be used before connect().
+     * @return string The db vendor name, usually the same as db family name.
+     */
+    public function get_dbvendor(): ?string {
+        return 'mysql';
+    }
+
+    /**
+     * Returns more specific database driver type
+     *
+     * Returns more specific database driver type. Can be used before connect().
+     * @return string db type mysqli, pgsql, oci, mssql, sqlsrv
+     */
+    protected function get_dbtype(): ?string {
+        return 'auroramysql';
+    }
+
+    /**
+     * It is time to require transactions everywhere.
+     *
+     * MyISAM is NOT supported!
+     *
+     * @return bool
+     */
+    protected function transactions_supported(): ?bool {
+        if ($this->external) {
+            return parent::transactions_supported();
+        }
+        return true;
+    }
+
+
+}
index e7cb41e..fb4a29c 100644 (file)
@@ -179,12 +179,13 @@ function theme_get_css_filename($themename, $globalrevision, $themerevision, $di
  * @param theme_config[] $themeconfigs An array of theme_config instances.
  * @param array          $directions   Must be a subset of ['rtl', 'ltr'].
  * @param bool           $cache        Should the generated files be stored in local cache.
+ * @return array         The built theme content in a multi-dimensional array of name => direction => content
  */
-function theme_build_css_for_themes($themeconfigs = [], $directions = ['rtl', 'ltr'], $cache = true) {
+function theme_build_css_for_themes($themeconfigs = [], $directions = ['rtl', 'ltr'], $cache = true): array {
     global $CFG;
 
     if (empty($themeconfigs)) {
-        return;
+        return [];
     }
 
     require_once("{$CFG->libdir}/csslib.php");
@@ -212,7 +213,7 @@ function theme_build_css_for_themes($themeconfigs = [], $directions = ['rtl', 'l
                 css_store_css($themeconfig, $filename, $themecss[$direction]);
             }
         }
-        $themescss[] = $themecss;
+        $themescss[$themeconfig->name] = $themecss;
 
         if ($cache) {
             // Only update the theme revision after we've successfully created the
index 470b3be..8c89e04 100644 (file)
@@ -1813,6 +1813,8 @@ function question_get_question_capabilities() {
         'moodle/question:useall',
         'moodle/question:movemine',
         'moodle/question:moveall',
+        'moodle/question:tagmine',
+        'moodle/question:tagall',
     );
 }
 
index c1b7202..4111ea8 100644 (file)
     Example context (json):
     {
         "pagingbar": {
-            "itemsperpage": 1,
+            "showitemsperpageselector": true,
+            "itemsperpage": [
+                { "value": 5, "active": false },
+                { "value": 10, "active": true },
+                { "value": 15, "active": false }
+            ],
             "previous": true,
             "next": true,
             "first": true,
             "last": true,
+            "barsize": 5,
             "pages": [
                 {
                     "page": "1",
                 {
                     "url": "#",
                     "page": "2"
+                },
+                {
+                    "url": "#",
+                    "page": "3"
+                },
+                {
+                    "url": "#",
+                    "page": "4"
+                },
+                {
+                    "url": "#",
+                    "page": "5"
+                },
+                {
+                    "url": "#",
+                    "page": "6"
+                },
+                {
+                    "url": "#",
+                    "page": "7"
                 }
             ]
         },
             {
                 "page": 2,
                 "content": "<p>Some page 2 content</p>"
+            },
+            {
+                "page": 3,
+                "content": "<p>Some page 3 content</p>"
+            },
+            {
+                "page": 4,
+                "content": "<p>Some page 4 content</p>"
+            },
+            {
+                "page": 5,
+                "content": "<p>Some page 5 content</p>"
+            },
+            {
+                "page": 6,
+                "content": "<p>Some page 6 content</p>"
+            },
+            {
+                "page": 7,
+                "content": "<p>Some page 7 content</p>"
             }
         ]
     }
index b0b84f1..f8af357 100644 (file)
 
     Example context (json):
     {
-        "itemsperpage": 2,
+        "showitemsperpageselector": true,
+        "itemsperpage": [
+            { "value": 5, "active": false },
+            { "value": 10, "active": true },
+            { "value": 15, "active": false }
+        ],
         "previous": true,
         "next": true,
         "first": true,
@@ -89,7 +94,8 @@
                         <a
                             class="dropdown-item {{#active}}active{{/active}}"
                             href="#"
-                            data-limit={{value}}
+                            data-limit="{{value}}"
+                            role="menuitem"
                             {{#active}}aria-current="true"{{/active}}
                         >
                             {{#value}}{{.}}{{/value}}
         <ul class="pagination mb-0">
             {{#previous}}
                 {{< core/paged_content_paging_bar_item }}
+                    {{$linkattributes}}aria-label="{{#str}}previouspage{{/str}}"{{/linkattributes}}
                     {{$item-content}}
                         <span class="icon-no-margin dir-rtl-hide" aria-hidden="true">{{#pix}} i/previous, core {{/pix}}</span>
                         <span class="icon-no-margin dir-ltr-hide" aria-hidden="true">{{#pix}} i/next, core {{/pix}}</span>
             {{/previous}}
             {{#first}}
                 {{< core/paged_content_paging_bar_item }}
+                    {{$linkattributes}}aria-label="{{#str}}firstpage{{/str}}"{{/linkattributes}}
                     {{$item-content}}
-                        <span aria-hidden="true">{{#str}}first{{/str}}</span>
+                        {{#str}}first{{/str}}
                     {{/item-content}}
                     {{$attributes}}data-control="first"{{/attributes}}
                 {{/ core/paged_content_paging_bar_item }}
             {{/first}}
             {{#barsize}}
                 {{< core/paged_content_paging_bar_item }}
+                    {{$linkattributes}}aria-hidden="true"{{/linkattributes}}
                     {{$item-content}}
-                        <span aria-hidden="true">&hellip;</span>
+                        &hellip;
                     {{/item-content}}
                     {{$attributes}}data-dots="beginning"{{/attributes}}
                 {{/ core/paged_content_paging_bar_item }}
             {{/pages}}
             {{#barsize}}
                 {{< core/paged_content_paging_bar_item }}
+                    {{$linkattributes}}aria-hidden="true"{{/linkattributes}}
                     {{$item-content}}
-                        <span aria-hidden="true">&hellip;</span>
+                        &hellip;
                     {{/item-content}}
                     {{$attributes}}data-dots="ending"{{/attributes}}
                 {{/ core/paged_content_paging_bar_item }}
             {{/barsize}}
             {{#last}}
                 {{< core/paged_content_paging_bar_item }}
+                    {{$linkattributes}}aria-label="{{#str}}lastpage{{/str}}"{{/linkattributes}}
                     {{$item-content}}
-                        <span aria-hidden="true">{{#str}}last{{/str}}</span>
+                        {{#str}}last{{/str}}
                     {{/item-content}}
                     {{$attributes}}data-control="last"{{/attributes}}
                 {{/ core/paged_content_paging_bar_item }}
             {{/last}}
             {{#next}}
                 {{< core/paged_content_paging_bar_item }}
+                    {{$linkattributes}}aria-label="{{#str}}nextpage{{/str}}"{{/linkattributes}}
                     {{$item-content}}
                         <span class="icon-no-margin dir-rtl-hide" aria-hidden="true">{{#pix}} i/next, core {{/pix}}</span>
                         <span class="icon-no-margin dir-ltr-hide" aria-hidden="true">{{#pix}} i/previous, core {{/pix}}</span>
index e3ef1a2..ed50b10 100644 (file)
@@ -33,7 +33,7 @@
 
     <a href="{{#url}}{{.}}{{/url}}{{^url}}#{{/url}}"
        class="page-link"
-       data-region="page-link">
+       data-region="page-link" {{$linkattributes}}aria-label="{{#str}}pagea, moodle, {{page}}{{/str}}"{{/linkattributes}}>
         {{$item-content}}
             {{{page}}}
         {{/item-content}}
index c7b53df..2ba7b37 100644 (file)
@@ -63,11 +63,6 @@ use Behat\Testwork\Hook\Scope\BeforeSuiteScope,
  */
 class behat_hooks extends behat_base {
 
-    /**
-     * @var Last browser session start time.
-     */
-    protected static $lastbrowsersessionstart = 0;
-
     /**
      * @var For actions that should only run once.
      */
@@ -196,12 +191,6 @@ class behat_hooks extends behat_base {
         // Avoid parallel tests execution, it continues when the previous lock is released.
         test_lock::acquire('behat');
 
-        // Store the browser reset time if reset after N seconds is specified in config.php.
-        if (!empty($CFG->behat_restart_browser_after)) {
-            // Store the initial browser session opening.
-            self::$lastbrowsersessionstart = time();
-        }
-
         if (!empty($CFG->behat_faildump_path) && !is_writable($CFG->behat_faildump_path)) {
             throw new behat_stop_exception('You set $CFG->behat_faildump_path to a non-writable directory');
         }
@@ -367,19 +356,20 @@ class behat_hooks extends behat_base {
         behat_util::reset_all_data();
         error_reporting($errorlevel);
 
+        if ($this->running_javascript()) {
+            // Fetch the user agent.
+            // This isused to choose between the SVG/Non-SVG versions of themes.
+            $useragent = $this->getSession()->evaluateScript('return navigator.userAgent;');
+            \core_useragent::instance(true, $useragent);
+
+            // Restore the saved themes.
+            behat_util::restore_saved_themes();
+        }
+
         // Assign valid data to admin user (some generator-related code needs a valid user).
         $user = $DB->get_record('user', array('username' => 'admin'));
         \core\session\manager::set_user($user);
 
-        // Reset the browser if specified in config.php.
-        if (!empty($CFG->behat_restart_browser_after) && $this->running_javascript()) {
-            $now = time();
-            if (self::$lastbrowsersessionstart + $CFG->behat_restart_browser_after < $now) {
-                $session->restart();
-                self::$lastbrowsersessionstart = $now;
-            }
-        }
-
         // Set the theme if not default.
         if ($suitename !== "default") {
             set_config('theme', $suitename);
@@ -566,25 +556,13 @@ class behat_hooks extends behat_base {
     }
 
     /**
-     * Executed after scenario having switch window to restart session.
-     * This is needed to close all extra browser windows and starting
-     * one browser window.
+     * Reset the session between each scenario.
      *
      * @param AfterScenarioScope $scope scope passed by event fired after scenario.
-     * @AfterScenario @_switch_window
+     * @AfterScenario
      */
-    public function after_scenario_switchwindow(AfterScenarioScope $scope) {
-        for ($count = 0; $count < behat_base::get_extended_timeout(); $count++) {
-            try {
-                $this->getSession()->restart();
-                break;
-            } catch (DriverException $e) {
-                // Wait for timeout and try again.
-                sleep(self::get_timeout());
-            }
-        }
-        // If session is not restarted above then it will try to start session before next scenario
-        // and if that fails then exception will be thrown.
+    public function reset_webdriver_between_scenarios(AfterScenarioScope $scope) {
+        $this->getSession()->stop();
     }
 
     /**
index 4e79b93..fd1f97d 100644 (file)
@@ -1,6 +1,14 @@
 This files describes API changes in core libraries and APIs,
 information provided here is intended especially for developers.
 
+=== 4.0 ===
+* Added function setScrollable in core/modal. This function can be used to set the modal's body to be scrollable or not
+  when the modal's height exceeds the browser's height. This is also supported in core/modal_factory through the
+  'scrollable' config parameter which can be set to either true or false. If not explicitly defined, the default value
+  of 'scrollable' is true.
+* The `$CFG->behat_retart_browser_after` configuration setting has been removed.
+  The browser session is now restarted between all tests.
+
 === 3.9 ===
 * Following function has been deprecated, please use \core\task\manager::run_from_cli().
     - cron_run_single_task()
index 4ccb899..9d72b87 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js differ
index 90a6b75..9af91ef 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js differ
index 4ccb899..9d72b87 100644 (file)
Binary files a/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js and b/lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js differ
index a2df1f7..ddefaa5 100644 (file)
@@ -214,7 +214,9 @@ Y.extend(CHOOSERDIALOGUE, Y.Base, {
             newheight, totalheight;
 
         if (this.panel.shouldResizeFullscreen()) {
-            // No custom sizing required for a fullscreen dialog.
+            dialogue.setStyle('maxHeight', '100%');
+            dialogue.setStyle('height', 'auto');
+            this.panel.makeResponsive();
             return;
         }
 
index 90f397c..fe33a55 100644 (file)
@@ -59,6 +59,8 @@ if (strpos($CFG->airnotifierurl, message_airnotifier_manager::AIRNOTIFIER_PUBLIC
     }
 }
 
+echo $OUTPUT->header();
+
 $manager = new message_airnotifier_manager();
 $warnings = [];
 
@@ -107,6 +109,5 @@ foreach ($warnings as $warning) {
 
 $msg .= $OUTPUT->continue_button($returl);
 
-echo $OUTPUT->header();
 echo $OUTPUT->box($msg, 'generalbox ');
 echo $OUTPUT->footer();
index 558c6ae..91766bc 100644 (file)
@@ -26,7 +26,8 @@ defined('MOODLE_INTERNAL') || die;
 if ($ADMIN->fulltree) {
 
     $notify = new \core\output\notification(
-        get_string('moodleappsportallimitswarning', 'message_airnotifier'),
+        get_string('moodleappsportallimitswarning', 'message_airnotifier',
+            (new moodle_url('https://apps.moodle.com'))->out()),
         \core\output\notification::NOTIFY_WARNING);
     $settings->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesappearance', '', $OUTPUT->render($notify)));
 
index ebf3499..c2f3817 100644 (file)
@@ -61,4 +61,15 @@ if ($attemptobj->is_finished()) {
 
 $attemptobj->process_auto_save($timenow);
 $transaction->allow_commit();
-echo 'OK';
+
+// Calculate time remaining.
+$timeleft = $attemptobj->get_time_left_display($timenow);
+
+// Build response, only returning timeleft if quiz in-progress
+// has a time limit.
+$r = new stdClass();
+$r->status = "OK";
+if ($timeleft !== false) {
+    $r->timeleft = $timeleft;
+}
+echo json_encode($r);
index 46f5ab8..9ede921 100644 (file)
@@ -58,6 +58,9 @@ M.mod_quiz.timer = {
     // so we can cancel.
     timeoutid: null,
 
+    // Threshold for updating time remaining, in milliseconds.
+    threshold: 3000,
+
     /**
      * @param Y the YUI object
      * @param start, the timer starting time, in seconds.
@@ -130,6 +133,18 @@ M.mod_quiz.timer = {
 
         // Arrange for this method to be called again soon.
         M.mod_quiz.timer.timeoutid = setTimeout(M.mod_quiz.timer.update, 100);
+    },
+
+    // Allow the end time of the quiz to be updated.
+    updateEndTime: function(timeleft) {
+        var newtimeleft = new Date().getTime() + timeleft * 1000;
+
+        // Only update if change is greater than the threshold, so the
+        // time doesn't bounce around unnecessarily.
+        if (Math.abs(newtimeleft - M.mod_quiz.timer.endtime) > M.mod_quiz.timer.threshold) {
+            M.mod_quiz.timer.endtime = newtimeleft;
+            M.mod_quiz.timer.update();
+        }
     }
 };
 
index 99e5e2d..403fb96 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js and b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-debug.js differ
index 6d79032..393c2d9 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js and b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js differ
index 21abfc5..674d946 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js and b/mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave.js differ
index cce5017..9633f73 100644 (file)
@@ -356,13 +356,19 @@ M.mod_quiz.autosave = {
     },
 
     save_done: function(transactionid, response) {
-        if (response.responseText !== 'OK') {
+        var autosavedata = JSON.parse(response.responseText);
+        if (autosavedata.status !== 'OK') {
             // Because IIS is useless, Moodle can't send proper HTTP response
             // codes, so we have to detect failures manually.
             this.save_failed(transactionid, response);
             return;
         }
 
+        if (typeof autosavedata.timeleft !== 'undefined') {
+            Y.log('Updating timer: ' + autosavedata.timeleft + ' seconds remain.', 'debug', 'moodle-mod_quiz-timer');
+            M.mod_quiz.timer.updateEndTime(autosavedata.timeleft);
+        }
+
         Y.log('Save completed.', 'debug', 'moodle-mod_quiz-autosave');
         this.save_transaction = null;
 
diff --git a/mod/scorm/tests/behat/behat_mod_scorm.php b/mod/scorm/tests/behat/behat_mod_scorm.php
deleted file mode 100644 (file)
index ed7e16f..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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/>.
-
-/**
- * Steps definitions related to the SCORM activity module.
- *
- * @package    mod_scorm
- * @category   test
- * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
-
-require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
-
-use Behat\Behat\Hook\Scope\AfterScenarioScope;
-
-/**
- * Steps definitions related to the SCORM activity module.
- *
- * @package    mod_scorm
- * @category   test
- * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class behat_mod_scorm extends behat_base {
-
-    /**
-     * Restart the Seleium Session after each mod_scorm Scenario.
-     *
-     * This prevents issues with the scorm player's onbeforeunload event, and cached SCORM content being served to the
-     * browser in subsequent tests.
-     *
-     * @AfterScenario @mod_scorm
-     * @param AfterScenarioScope $scope The scenario scope
-     */
-    public function reset_after_scorm(AfterScenarioScope $scope) {
-        $this->getSession()->stop();
-    }
-}
index 9528995..0d5ade3 100644 (file)
@@ -58,7 +58,7 @@ class question_export_form extends moodleform {
             if (get_string_manager()->string_exists('pluginname_help', 'qformat_' . $shortname)) {
                 $separator .= $OUTPUT->help_icon('pluginname', 'qformat_' . $shortname);
             }
-            $separator .= '<br>';
+            $separator .= '<div class="w-100"></div>';
             $separators[] = $separator;
         }
 
index 6c1fc25..ccf0eb8 100644 (file)
@@ -76,7 +76,7 @@ $output = $PAGE->get_renderer('report_competency');
 echo $output->header();
 $baseurl = new moodle_url('/report/competency/index.php');
 $nav = new \report_competency\output\user_course_navigation($currentuser, $course->id, $baseurl, $currentmodule);
-echo $output->render($nav);
+$top = $output->render($nav);
 if ($currentuser > 0) {
     $user = core_user::get_user($currentuser);
     $usercontext = context_user::instance($currentuser);
@@ -88,9 +88,9 @@ if ($currentuser > 0) {
     if ($currentmodule > 0) {
         $title = get_string('filtermodule', 'report_competency', format_string($cm->name));
     }
-    echo $output->context_header($userheading, 3);
+    $top .= $output->context_header($userheading, 3);
 }
-echo $output->container('', 'clearfix');
+echo $output->container($top, 'clearfix');
 echo $output->heading($title, 3);
 
 if ($currentuser > 0) {
index ac58e81..4f10a37 100644 (file)
@@ -2603,6 +2603,9 @@ $picker-emojis-per-row: 7 !default;
         color: darken(theme-color-level($color, $alert-color-level), 10%);
     }
 }
+.alert a {
+    font-weight: $font-weight-bold;
+}
 
 @include media-breakpoint-down(sm) {
     #page-navbar {
index c9fc671..588a0de 100644 (file)
@@ -11746,6 +11746,9 @@ body.h5p-embed .h5pmessages {
 .alert-dark a {
   color: #040505; }
 
+.alert a {
+  font-weight: 700; }
+
 @media (max-width: 767.98px) {
   #page-navbar {
     width: 100%; }
index 7be6184..e9b5b03 100644 (file)
@@ -11960,6 +11960,9 @@ body.h5p-embed .h5pmessages {
 .alert-dark a {
   color: #040505; }
 
+.alert a {
+  font-weight: 700; }
+
 @media (max-width: 767.98px) {
   #page-navbar {
     width: 100%; }
index 54a463e..b997323 100644 (file)
@@ -29,9 +29,9 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2020061500.01;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2020062600.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
-$release  = '4.0dev (Build: 20200618)'; // Human-friendly version name
+$release  = '4.0dev (Build: 20200626)'; // Human-friendly version name
 $branch   = '40';                       // This version's branch.
 $maturity = MATURITY_ALPHA;             // This version's maturity level.