Merge branch 'MDL-68784' of https://github.com/Peterburnett/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Jul 2020 16:51:50 +0000 (18:51 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Jul 2020 16:51:50 +0000 (18:51 +0200)
76 files changed:
admin/cli/install.php
admin/settings/security.php
admin/tasklogs.php
admin/tool/customlang/lang/en/tool_customlang.php
admin/tool/log/store/legacy/tests/store_test.php
auth/oauth2/classes/auth.php
blocks/blog_menu/block_blog_menu.php
cache/upgrade.txt
config-dist.php
course/amd/build/activitychooser.min.js
course/amd/build/activitychooser.min.js.map
course/amd/src/activitychooser.js
enrol/imsenterprise/lang/en/enrol_imsenterprise.php
enrol/manual/ajax.php
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/ko/moodle.php
install/lang/zh_cn/langconfig.php
lang/en/admin.php
lang/en/badges.php
lang/en/h5p.php
lang/en/install.php
lang/en/mnet.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/behat_base.php
lib/classes/event/message_contact_blocked.php [deleted file]
lib/classes/event/message_contact_unblocked.php [deleted file]
lib/classes/lock/db_record_lock_factory.php
lib/classes/lock/file_lock_factory.php
lib/classes/lock/installation_lock_factory.php
lib/classes/lock/lock.php
lib/classes/lock/lock_factory.php
lib/classes/lock/mysql_lock_factory.php
lib/classes/lock/postgres_lock_factory.php
lib/clilib.php
lib/cronlib.php
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.php
lib/dml/auroramysql_native_moodle_database.php [new file with mode: 0644]
lib/editor/atto/plugins/image/lang/en/atto_image.php
lib/tablelib.php
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/event_test.php
lib/tests/lock_test.php
lib/upgrade.txt
lib/weblib.php
message/externallib.php
message/output/airnotifier/requestaccesskey.php
message/output/airnotifier/settings.php
message/tests/externallib_test.php
message/tests/messagelib_test.php
mod/assign/feedback/editpdf/classes/task/convert_submissions.php
mod/choice/lang/en/choice.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
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 d8dc48a..77dfb42 100644 (file)
@@ -160,6 +160,23 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configportlist('curlsecurityallowedport',
                new lang_string('curlsecurityallowedport', 'admin'),
                new lang_string('curlsecurityallowedportsyntax', 'admin'), ""));
+
+    // HTTP Header referrer policy settings.
+    $referreroptions = [
+        'default' => get_string('referrernone', 'admin'),
+        'no-referrer' => 'no-referrer',
+        'no-referrer-when-downgrade' => 'no-referrer-when-downgrade',
+        'origin' => 'origin',
+        'origin-when-cross-origin' => 'origin-when-cross-origin',
+        'same-origin' => 'same-origin',
+        'strict-origin' => 'strict-origin',
+        'strict-origin-when-cross-origin' => 'strict-origin-when-cross-origin',
+        'unsafe-url' => 'unsafe-url',
+    ];
+    $temp->add(new admin_setting_configselect('referrerpolicy',
+            new lang_string('referrerpolicy', 'admin'),
+            new lang_string('referrerpolicydesc', 'admin'), 'default', $referreroptions));
+
     $ADMIN->add('security', $temp);
 
     // "notifications" settingpage
index 796f323..a91faa3 100644 (file)
@@ -66,7 +66,7 @@ echo $OUTPUT->header();
 // Output the search form.
 echo $OUTPUT->render_from_template('core_admin/tasklogs', (object) [
     'action' => $pageurl->out(),
-    'filter' => $filter,
+    'filter' => htmlentities($filter),
     'resultfilteroptions' => [
         (object) [
             'value' => -1,
index 9e7a2d6..a7375f1 100644 (file)
@@ -36,7 +36,7 @@ $string['customlang:view'] = 'View local translation';
 $string['filter'] = 'Filter strings';
 $string['filtercomponent'] = 'Show strings of these components';
 $string['filtercustomized'] = 'Customised only';
-$string['filtermodified'] = 'Modified only';
+$string['filtermodified'] = 'Modified in this session only';
 $string['filteronlyhelps'] = 'Help only';
 $string['filtershowstrings'] = 'Show strings';
 $string['filterstringid'] = 'String identifier';
index a2aeb98..f0a95a3 100644 (file)
@@ -34,11 +34,9 @@ class logstore_legacy_store_testcase extends advanced_testcase {
 
         $this->setAdminUser();
         $user1 = $this->getDataGenerator()->create_user();
-        $user2 = $this->getDataGenerator()->create_user();
         $course1 = $this->getDataGenerator()->create_course();
         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
         $course2 = $this->getDataGenerator()->create_course();
-        $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
 
         // Enable legacy logging plugin.
         set_config('enabled_stores', 'logstore_legacy', 'tool_log');
@@ -68,15 +66,8 @@ class logstore_legacy_store_testcase extends advanced_testcase {
             array('context' => context_course::instance($course2->id), 'other' => array('sample' => 6, 'xx' => 11)));
         $event2->trigger();
 
-        $this->setUser($user2);
-        add_to_log($course1->id, 'xxxx', 'yyyy', '', '7', 0, 0);
-        $this->assertDebuggingCalled();
-
-        add_to_log($course2->id, 'aaa', 'bbb', 'info.php', '666', $module2->cmid, $user1->id);
-        $this->assertDebuggingCalled();
-
         $logs = $DB->get_records('log', array(), 'id ASC');
-        $this->assertCount(4, $logs);
+        $this->assertCount(2, $logs);
 
         $log = array_shift($logs);
         $this->assertNotEmpty($log->id);
@@ -104,34 +95,6 @@ class logstore_legacy_store_testcase extends advanced_testcase {
         $this->assertSame('unittest.php?id=6', $log->url);
         $this->assertSame('bbb', $log->info);
 
-        $oldlogid = $log->id;
-        $log = array_shift($logs);
-        $this->assertGreaterThan($oldlogid, $log->id);
-        $this->assertNotEmpty($log->id);
-        $this->assertTimeCurrent($log->time);
-        $this->assertEquals($user2->id, $log->userid);
-        $this->assertSame('0.0.0.0', $log->ip);
-        $this->assertEquals($course1->id, $log->course);
-        $this->assertSame('xxxx', $log->module);
-        $this->assertEquals(0, $log->cmid);
-        $this->assertSame('yyyy', $log->action);
-        $this->assertSame('', $log->url);
-        $this->assertSame('7', $log->info);
-
-        $oldlogid = $log->id;
-        $log = array_shift($logs);
-        $this->assertGreaterThan($oldlogid, $log->id);
-        $this->assertNotEmpty($log->id);
-        $this->assertTimeCurrent($log->time);
-        $this->assertEquals($user1->id, $log->userid);
-        $this->assertSame('0.0.0.0', $log->ip);
-        $this->assertEquals($course2->id, $log->course);
-        $this->assertSame('aaa', $log->module);
-        $this->assertEquals($module2->cmid, $log->cmid);
-        $this->assertSame('bbb', $log->action);
-        $this->assertSame('info.php', $log->url);
-        $this->assertSame('666', $log->info);
-
         // Test if disabling works.
         set_config('enabled_stores', 'logstore_legacy', 'tool_log');
         set_config('loglegacy', 0, 'logstore_legacy');
@@ -142,9 +105,7 @@ class logstore_legacy_store_testcase extends advanced_testcase {
 
         \logstore_legacy\event\unittest_executed::create(
             array('context' => \context_system::instance(), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
-        add_to_log($course1->id, 'xxxx', 'yyyy', '', '7', 0, 0);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(4, $DB->count_records('log'));
+        $this->assertEquals(2, $DB->count_records('log'));
 
         // Another way to disable legacy completely.
         set_config('enabled_stores', 'logstore_standard', 'tool_log');
@@ -153,9 +114,7 @@ class logstore_legacy_store_testcase extends advanced_testcase {
 
         \logstore_legacy\event\unittest_executed::create(
             array('context' => \context_system::instance(), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
-        add_to_log($course1->id, 'xxxx', 'yyyy', '', '7', 0, 0);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(4, $DB->count_records('log'));
+        $this->assertEquals(2, $DB->count_records('log'));
         // Set everything back.
         set_config('enabled_stores', '', 'tool_log');
         set_config('loglegacy', 0, 'logstore_legacy');
index 1d1ff6f..cf6fcfd 100644 (file)
@@ -455,8 +455,9 @@ class auth extends \auth_plugin_base {
             }
         }
 
+        $issuer = $client->get_issuer();
         // First we try and find a defined mapping.
-        $linkedlogin = api::match_username_to_user($userinfo['username'], $client->get_issuer());
+        $linkedlogin = api::match_username_to_user($userinfo['username'], $issuer);
 
         if (!empty($linkedlogin) && empty($linkedlogin->get('confirmtoken'))) {
             $mappeduser = get_complete_user_data('id', $linkedlogin->get('userid'));
@@ -474,7 +475,7 @@ class auth extends \auth_plugin_base {
                 $SESSION->loginerrormsg = get_string('invalidlogin');
                 $client->log_out();
                 redirect(new moodle_url('/login/index.php'));
-            } else if ($mappeduser && $mappeduser->confirmed) {
+            } else if ($mappeduser && ($mappeduser->confirmed || !$issuer->get('requireconfirmation'))) {
                 // Update user fields.
                 $userinfo = $this->update_user($userinfo, $mappeduser);
                 $userwasmapped = true;
@@ -503,7 +504,7 @@ class auth extends \auth_plugin_base {
             redirect(new moodle_url('/login/index.php'));
         }
 
-        $issuer = $client->get_issuer();
+
         if (!$issuer->is_valid_login_domain($oauthemail)) {
             // Trigger login failed event.
             $failurereason = AUTH_LOGIN_UNAUTHORISED;
index 71fd422..7bc53b1 100644 (file)
@@ -99,10 +99,14 @@ class block_blog_menu extends block_base {
         // Prepare the footer for this block
         if (has_capability('moodle/blog:search', context_system::instance())) {
             // Full-text search field
-            $form  = html_writer::tag('label', get_string('search', 'admin'), array('for'=>'blogsearchquery', 'class'=>'accesshide'));
-            $form .= html_writer::empty_tag('input', array('id'=>'blogsearchquery', 'type'=>'text', 'name'=>'search'));
-            $form .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('search')));
-            $this->content->footer = html_writer::tag('form', html_writer::tag('div', $form), array('class'=>'blogsearchform', 'method'=>'get', 'action'=>new moodle_url('/blog/index.php')));
+            $form  = html_writer::tag('label', get_string('search', 'admin'), array('for' => 'blogsearchquery',
+                'class' => 'accesshide'));
+            $form .= html_writer::empty_tag('input', array('id' => 'blogsearchquery', 'class' => 'form-control mr-1',
+                'type' => 'text', 'name' => 'search'));
+            $form .= html_writer::empty_tag('input', array('type' => 'submit', 'class' => 'btn btn-secondary',
+                'value' => get_string('search')));
+            $this->content->footer = html_writer::tag('form', html_writer::tag('div', $form), array(
+                'class' => 'blogsearchform form-inline', 'method' => 'get', 'action' => new moodle_url('/blog/index.php')));
         } else {
             // No footer to display
             $this->content->footer = '';
index 076bd53..7563077 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /cache/stores/* - cache store plugins.
 Information provided here is intended especially for developers.
 
+=== 4.0 ===
+* The function supports_recursion() from the lock_factory interface has been deprecated including the related implementations.
+* The function extend_lock() from the lock_factory interface has been deprecated without replacement including the related
+  implementations.
+* The function extend() from the lock class has been deprecated without replacement.
+
 === 3.9 ===
 * The record_cache_hit/miss/set methods now take a cache_store instead of a cache_definition object
 
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 b49f3dc..0ea080e 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js and b/course/amd/build/activitychooser.min.js differ
index a409f76..6519c83 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js.map and b/course/amd/build/activitychooser.min.js.map differ
index 5de0576..a1d1bbc 100644 (file)
@@ -248,6 +248,7 @@ const buildModal = (bodyPromise, footer) => {
         body: bodyPromise,
         footer: footer.customfootertemplate,
         large: true,
+        scrollable: false,
         templateContext: {
             classes: 'modchooser'
         }
index f1c3cdb..d9c1ada 100644 (file)
@@ -32,7 +32,7 @@ $string['categoryseparator'] = 'Category separator character';
 $string['categoryseparator_desc'] = 'Required when "Category idnumber" is enabled. Character to separate the category name and idnumber.';
 $string['coursesettings'] = 'Course data options';
 $string['createnewcategories'] = 'Create new (hidden) course categories if not found in Moodle';
-$string['createnewcategories_desc'] = 'If the <org><orgunit> element is present in a course\'s incoming data, its content will be used to specify a category if the course is to be created from scratch. The plugin will NOT re-categorise existing courses.
+$string['createnewcategories_desc'] = 'If the &lt;org&gt;&lt;orgunit&gt; element is present in a course\'s incoming data, its content will be used to specify a category if the course is to be created from scratch. The plugin will NOT re-categorise existing courses.
 
 If no category exists with the desired name, then a hidden category will be created.';
 $string['createnewcourses'] = 'Create new (hidden) courses if not found in Moodle';
index 29cb40e..22ccef2 100644 (file)
@@ -100,6 +100,13 @@ switch ($action) {
 
         if (empty($roleid)) {
             $roleid = null;
+        } else {
+            if (!has_capability('moodle/role:assign', $context)) {
+                throw new enrol_ajax_exception('assignnotpermitted');
+            }
+            if (!array_key_exists($roleid, get_assignable_roles($context, ROLENAME_ALIAS, false))) {
+                throw new enrol_ajax_exception('invalidrole');
+            }
         }
 
         if (empty($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 358febe..392abd3 100644 (file)
@@ -31,6 +31,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = '언어';
+$string['moodlelogo'] = '무들 로고';
 $string['next'] = '다음';
 $string['previous'] = '이전으로';
 $string['reload'] = '다시 로딩';
index a0956b4..0bc12e9 100644 (file)
@@ -31,5 +31,5 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['parentlanguage'] = '';
-$string['thisdirection'] = '符号(ltr)';
+$string['thisdirection'] = 'ltr';
 $string['thislanguage'] = '简体中文';
index 4080712..4b3c031 100644 (file)
@@ -1070,6 +1070,9 @@ $string['purgeselectedcaches'] = 'Purge selected caches';
 $string['purgeselectedcachesfinished'] = 'The selected caches were purged.';
 $string['purgetemplates'] = 'Templates';
 $string['purgethemecache'] = 'Themes';
+$string['referrernone'] = 'Browser default';
+$string['referrerpolicy'] = 'Referrer policy';
+$string['referrerpolicydesc'] = 'Set the referrer policy header to be included with responses from your site.';
 $string['restorecourse'] = 'Restore course';
 $string['restorernewroleid'] = 'Restorers\' role in courses';
 $string['restorernewroleid_help'] = 'If the user does not already have the permission to manage the newly restored course, the user is automatically assigned this role and enrolled if necessary. Select "None" if you do not want restorers to be able to manage every restored course.';
index 85abc8a..f0343ee 100644 (file)
@@ -82,7 +82,7 @@ $string['awardoncron'] = 'Access to the badges was successfully enabled. Too man
 $string['awards'] = 'Recipients';
 $string['backpackavailability'] = 'External badge verification';
 $string['backpackconnectionok'] = 'Backpack connection successfully established';
-$string['backpackconnectionnottested'] = 'Connection can not be tested for this backpack because only OBv2.0 backpacks support it.';
+$string['backpackconnectionnottested'] = 'The connection cannot be tested for this backpack because only Open Badges v2.0 backpacks support it.';
 $string['backpackneedsupdate'] = 'The backpack connected to this profile does not match the backpack for the site. You need to disconnect and reconnect the backpack.';
 $string['backpackavailability_help'] = 'For badge recipients to be able to prove they earned their badges from you, an external backpack service should be able to access your site and verify badges issued from it. Your site does not currently appear to be accessible, which means that badges you have already issued or will issue in the future cannot be verified.
 
index f2d73f2..f4b6a8f 100644 (file)
@@ -92,7 +92,7 @@ $string['h5ptitle'] = 'Visit h5p.org to check out more content.';
 $string['h5pfilenotfound'] = 'H5P file not found';
 $string['h5pinvalidurl'] = 'Invalid H5P content URL.';
 $string['h5plibraryhandler'] = 'H5P framework handler';
-$string['h5plibraryhandler_help'] = 'The H5P framework used to display any H5P content.';
+$string['h5plibraryhandler_help'] = 'The H5P framework used to display H5P content. The latest version is recommended.';
 $string['h5pprivatefile'] = 'This H5P content can\'t be displayed because you don\'t have access to the .h5p file.';
 $string['h5pmanage'] = 'Manage H5P content types';
 $string['h5poverview'] = 'H5P overview';
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 bb57111..b73fd12 100644 (file)
@@ -61,7 +61,7 @@ $string['enterausername'] = 'Please enter a username, or a list of usernames sep
 $string['error7020'] = 'This error normally occurs if the remote site has created a record for you with the wrong wwwroot, for example, https://yoursite.com instead of https://www.yoursite.com. Please contact the administrator of the remote site with your wwwroot (as specified in config.php) and ask them to update the record for your host.';
 $string['error7022'] = 'The message you sent to the remote site was encrypted properly, but not signed. This is very unexpected; you should probably file a bug if this occurs (giving as much information as possible about the application versions in question etc).';
 $string['error7023'] = 'The remote site has tried to decrypt your message with all the keys it has on record for your site. They have all failed. You might be able to fix this problem by manually re-keying with the remote site. This is unlikely to occur unless you\'ve been out of communication with the remote site for a few months.';
-$string['error7024'] = 'You send an unencrypted message to the remote site, but the remote site doesn\'t accept unencrypted communication from your site. This is very unexpected; you should probably file a bug if this occurs (giving as much information as possible about the application versions in question, etc.';
+$string['error7024'] = 'You send an unencrypted message to the remote site, but the remote site doesn\'t accept unencrypted communication from your site. This is very unexpected; you should probably file a bug if this occurs (giving as much information as possible about the application versions in question, etc).';
 $string['error7026'] = 'The key that your message was signed with differs from the key that the remote host has on file for your server. Further, the remote host attempted to fetch your current key and failed to do so. Please manually re-key with the remote host and try again.';
 $string['error709'] = 'The remote site failed to obtain a SSL key from you.';
 $string['eventaccesscontrolcreated'] = 'Access control created';
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 729ac1e..c97f492 100644 (file)
@@ -119,6 +119,11 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      * @return NodeElement
      */
     protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
+        if ($selector === 'NodeElement' && is_a($locator, NodeElement::class)) {
+            // Support a NodeElement being passed in for use in step chaining.
+            return $locator;
+        }
+
         // Returns the first match.
         $items = $this->find_all($selector, $locator, $exception, $node, $timeout);
         return count($items) ? reset($items) : null;
diff --git a/lib/classes/event/message_contact_blocked.php b/lib/classes/event/message_contact_blocked.php
deleted file mode 100644 (file)
index 1c207a3..0000000
+++ /dev/null
@@ -1,114 +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/>.
-
-/**
- * Message contact blocked event.
- *
- * @package    core
- * @copyright  2014 Mark Nelson <markn@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace core\event;
-
-defined('MOODLE_INTERNAL') || die();
-
-debugging('core\\event\\message_contact_blocked has been deprecated. Please use
-        core\\event\\message_user_blocked instead', DEBUG_DEVELOPER);
-
-/**
- * Message contact blocked event class.
- *
- * @package    core
- * @since      Moodle 2.7
- * @copyright  2014 Mark Nelson <markn@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class message_contact_blocked extends base {
-
-    /**
-     * Init method.
-     */
-    protected function init() {
-        $this->data['objecttable'] = 'message_contacts';
-        $this->data['crud'] = 'u';
-        $this->data['edulevel'] = self::LEVEL_OTHER;
-    }
-
-    /**
-     * Returns localised general event name.
-     *
-     * @return string
-     */
-    public static function get_name() {
-        return get_string('eventmessagecontactblocked', 'message');
-    }
-
-    /**
-     * Returns relevant URL.
-     *
-     * @return \moodle_url
-     */
-    public function get_url() {
-        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
-    }
-
-    /**
-     * Returns description of what happened.
-     *
-     * @return string
-     */
-    public function get_description() {
-        return "The user with id '$this->userid' blocked the user with id '$this->relateduserid' on their contact list.";
-    }
-
-    /**
-     * Return legacy data for add_to_log().
-     *
-     * @return array
-     */
-    protected function get_legacy_logdata() {
-        return array(SITEID, 'message', 'block contact', 'index.php?user1=' . $this->relateduserid . '&amp;user2=' .
-            $this->userid, $this->relateduserid);
-    }
-
-    /**
-     * Custom validation.
-     *
-     * @throws \coding_exception
-     */
-    protected function validate_data() {
-        parent::validate_data();
-
-        if (!isset($this->relateduserid)) {
-            throw new \coding_exception('The \'relateduserid\' must be set.');
-        }
-    }
-
-    public static function get_objectid_mapping() {
-        // Messaging contacts are not backed up, so no need to map them on restore.
-        return array('db' => 'message_contacts', 'restore' => base::NOT_MAPPED);
-    }
-
-    /**
-     * This event has been deprecated.
-     *
-     * @return boolean
-     */
-    public static function is_deprecated() {
-        return true;
-    }
-}
diff --git a/lib/classes/event/message_contact_unblocked.php b/lib/classes/event/message_contact_unblocked.php
deleted file mode 100644 (file)
index 61f83e6..0000000
+++ /dev/null
@@ -1,114 +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/>.
-
-/**
- * Message contact unblocked event.
- *
- * @package    core
- * @copyright  2014 Mark Nelson <markn@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace core\event;
-
-defined('MOODLE_INTERNAL') || die();
-
-debugging('core\\event\\message_contact_unblocked has been deperecated. Please use
-        core\\event\\message_user_unblocked instead', DEBUG_DEVELOPER);
-
-/**
- * Message contact unblocked event class.
- *
- * @package    core
- * @since      Moodle 2.7
- * @copyright  2014 Mark Nelson <markn@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class message_contact_unblocked extends base {
-
-    /**
-     * Init method.
-     */
-    protected function init() {
-        $this->data['objecttable'] = 'message_contacts';
-        $this->data['crud'] = 'u';
-        $this->data['edulevel'] = self::LEVEL_OTHER;
-    }
-
-    /**
-     * Returns localised general event name.
-     *
-     * @return string
-     */
-    public static function get_name() {
-        return get_string('eventmessagecontactunblocked', 'message');
-    }
-
-    /**
-     * Returns relevant URL.
-     *
-     * @return \moodle_url
-     */
-    public function get_url() {
-        return new \moodle_url('/message/index.php', array('user1' => $this->userid, 'user2' => $this->relateduserid));
-    }
-
-    /**
-     * Returns description of what happened.
-     *
-     * @return string
-     */
-    public function get_description() {
-        return "The user with id '$this->userid' unblocked the user with id '$this->relateduserid' on their contact list.";
-    }
-
-    /**
-     * Return legacy data for add_to_log().
-     *
-     * @return array
-     */
-    protected function get_legacy_logdata() {
-        return array(SITEID, 'message', 'unblock contact', 'index.php?user1=' . $this->relateduserid .
-            '&amp;user2=' . $this->userid, $this->relateduserid);
-    }
-
-    /**
-     * Custom validation.
-     *
-     * @throws \coding_exception
-     */
-    protected function validate_data() {
-        parent::validate_data();
-
-        if (!isset($this->relateduserid)) {
-            throw new \coding_exception('The \'relateduserid\' must be set.');
-        }
-    }
-
-    public static function get_objectid_mapping() {
-        // Messaging contacts are not backed up, so no need to map them on restore.
-        return array('db' => 'message_contacts', 'restore' => base::NOT_MAPPED);
-    }
-
-    /**
-     * This event has been deprecated.
-     *
-     * @return boolean
-     */
-    public static function is_deprecated() {
-        return true;
-    }
-}
index e430bc5..4dfec49 100644 (file)
@@ -91,9 +91,13 @@ class db_record_lock_factory implements lock_factory {
 
     /**
      * Multiple locks for the same resource can be held by a single process.
+     *
+     * @deprecated since Moodle 4.0.
      * @return boolean - False - not process specific.
      */
     public function supports_recursion() {
+        debugging('The function supports_recursion() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         return false;
     }
 
@@ -187,11 +191,16 @@ class db_record_lock_factory implements lock_factory {
 
     /**
      * Extend a lock that was previously obtained with @lock.
+     *
+     * @deprecated since Moodle 4.0.
      * @param lock $lock - a lock obtained from this factory.
      * @param int $maxlifetime - the new lifetime for the lock (in seconds).
      * @return boolean - true if the lock was extended.
      */
     public function extend_lock(lock $lock, $maxlifetime = 86400) {
+        debugging('The function extend_lock() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
+
         $now = time();
         $expires = $now + $maxlifetime;
         $params = array('expires' => $expires,
index dd20e9b..b771639 100644 (file)
@@ -108,9 +108,13 @@ class file_lock_factory implements lock_factory {
 
     /**
      * Multiple locks for the same resource cannot be held from a single process.
+     *
+     * @deprecated since Moodle 4.0.
      * @return boolean - False
      */
     public function supports_recursion() {
+        debugging('The function supports_recursion() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         return false;
     }
 
@@ -188,11 +192,15 @@ class file_lock_factory implements lock_factory {
 
     /**
      * Extend a lock that was previously obtained with @lock.
+     *
+     * @deprecated since Moodle 4.0.
      * @param lock $lock - not used
      * @param int $maxlifetime - not used
      * @return boolean - true if the lock was extended.
      */
     public function extend_lock(lock $lock, $maxlifetime = 86400) {
+        debugging('The function extend_lock() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         // Not supported by this factory.
         return false;
     }
index 5200cab..0874ced 100644 (file)
@@ -76,9 +76,12 @@ class installation_lock_factory implements lock_factory {
     /**
      * Multiple locks for the same resource cannot be held from a single process.
      *
+     * @deprecated since Moodle 4.0.
      * @return boolean - False
      */
     public function supports_recursion() {
+        debugging('The function supports_recursion() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         return false;
     }
 
@@ -115,11 +118,14 @@ class installation_lock_factory implements lock_factory {
     /**
      * Extend a lock that was previously obtained with @lock.
      *
+     * @deprecated since Moodle 4.0.
      * @param lock $lock - not used
      * @param int $maxlifetime - not used
      * @return boolean - true if the lock was extended.
      */
     public function extend_lock(lock $lock, $maxlifetime = 86400) {
+        debugging('The function extend_lock() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         // Not supported by this factory.
         return false;
     }
index 7b117c3..f67083c 100644 (file)
@@ -80,10 +80,15 @@ class lock {
 
     /**
      * Extend the lifetime of this lock. Not supported by all factories.
+     *
+     * @deprecated since Moodle 4.0.
      * @param int $maxlifetime - the new lifetime for the lock (in seconds).
      * @return bool
      */
     public function extend($maxlifetime = 86400) {
+        debugging('The function extend() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
+
         if ($this->factory) {
             return $this->factory->extend_lock($this, $maxlifetime);
         }
index b03e774..c0d32b0 100644 (file)
@@ -63,6 +63,7 @@ interface lock_factory {
     /**
      * Supports recursion.
      *
+     * @deprecated since Moodle 4.0.
      * @return boolean - True if attempting to get 2 locks on the same resource will "stack"
      */
     public function supports_recursion();
@@ -98,6 +99,7 @@ interface lock_factory {
     /**
      * Extend the timeout on a held lock.
      *
+     * @deprecated since Moodle 4.0.
      * @param lock $lock - lock obtained from this factory
      * @param int $maxlifetime - new max time to hold the lock
      * @return boolean - True if the lock was extended.
index 6f05924..753741b 100644 (file)
@@ -106,9 +106,12 @@ class mysql_lock_factory implements lock_factory {
      * Hard coded to false and workaround inconsistent support in different
      * versions of MySQL / MariaDB.
      *
+     * @deprecated since Moodle 4.0.
      * @return boolean - false
      */
     public function supports_recursion() {
+        debugging('The function supports_recursion() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         return false;
     }
 
@@ -165,11 +168,15 @@ class mysql_lock_factory implements lock_factory {
 
     /**
      * Extend a lock that was previously obtained with @lock.
+     *
+     * @deprecated since Moodle 4.0.
      * @param lock $lock - a lock obtained from this factory.
      * @param int $maxlifetime - the new lifetime for the lock (in seconds).
      * @return boolean - true if the lock was extended.
      */
     public function extend_lock(lock $lock, $maxlifetime = 86400) {
+        debugging('The function extend_lock() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         // Not supported by this factory.
         return false;
     }
index 0d48eaa..1d18967 100644 (file)
@@ -118,11 +118,15 @@ class postgres_lock_factory implements lock_factory {
     }
 
     /**
-     * Multiple locks for the same resource can be held by a single process.
-     * @return boolean - Defer to the DB driver.
+     * Multiple locks for the same resource can NOT be held by a single process.
+     *
+     * @deprecated since Moodle 4.0.
+     * @return boolean - false.
      */
     public function supports_recursion() {
-        return true;
+        debugging('The function supports_recursion() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
+        return false;
     }
 
     /**
@@ -145,6 +149,7 @@ class postgres_lock_factory implements lock_factory {
 
     /**
      * Create and get a lock
+     *
      * @param string $resource - The identifier for the lock. Should use frankenstyle prefix.
      * @param int $timeout - The number of seconds to wait for a lock before giving up.
      * @param int $maxlifetime - Unused by this lock type.
@@ -155,8 +160,14 @@ class postgres_lock_factory implements lock_factory {
 
         $token = $this->get_index_from_key($this->type . '_' . $resource);
 
-        $params = array('locktype' => $this->dblockid,
-                        'token' => $token);
+        if (isset($this->openlocks[$token])) {
+            return false;
+        }
+
+        $params = [
+            'locktype' => $this->dblockid,
+            'token' => $token
+        ];
 
         $locked = false;
 
@@ -194,11 +205,15 @@ class postgres_lock_factory implements lock_factory {
 
     /**
      * Extend a lock that was previously obtained with @lock.
+     *
+     * @deprecated since Moodle 4.0.
      * @param lock $lock - a lock obtained from this factory.
      * @param int $maxlifetime - the new lifetime for the lock (in seconds).
      * @return boolean - true if the lock was extended.
      */
     public function extend_lock(lock $lock, $maxlifetime = 86400) {
+        debugging('The function extend_lock() is deprecated, please do not use it anymore.',
+            DEBUG_DEVELOPER);
         // Not supported by this factory.
         return false;
     }
index 63b5e43..6cc767f 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 (CLI_SCRIPT && function_exists('cli_set_process_title') && 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..74a83d5 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 (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 dcc2f48..977d7df 100644 (file)
@@ -2486,5 +2486,17 @@ function xmldb_main_upgrade($oldversion) {
         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;
 }
index 1327d93..c28996f 100644 (file)
@@ -33,28 +33,10 @@ defined('MOODLE_INTERNAL') || die();
 /* === Functions that needs to be kept longer in deprecated lib than normal time period === */
 
 /**
- * Add an entry to the legacy log table.
- *
  * @deprecated since 2.7 use new events instead
- *
- * @param    int     $courseid  The course id
- * @param    string  $module  The module name  e.g. forum, journal, resource, course, user etc
- * @param    string  $action  'view', 'update', 'add' or 'delete', possibly followed by another word to clarify.
- * @param    string  $url     The file and parameters used to see the results of the action
- * @param    string  $info    Additional description information
- * @param    int     $cm      The course_module->id if there is one
- * @param    int|stdClass $user If log regards $user other than $USER
- * @return void
- */
-function add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user=0) {
-    debugging('add_to_log() has been deprecated, please rewrite your code to the new events API', DEBUG_DEVELOPER);
-
-    // This is a nasty hack that allows us to put all the legacy stuff into legacy storage,
-    // this way we may move all the legacy settings there too.
-    $manager = get_log_manager();
-    if (method_exists($manager, 'legacy_add_to_log')) {
-        $manager->legacy_add_to_log($courseid, $module, $action, $url, $info, $cm, $user);
-    }
+ */
+function add_to_log() {
+    throw new coding_exception('add_to_log() has been removed, please rewrite your code to the new events API');
 }
 
 /**
@@ -766,57 +748,12 @@ function print_side_block() {
 }
 
 /**
- * Prints a basic textarea field.
- *
- * This was 'deprecated' in 2.0, but not properly (there was no alternative) so the
- * debugging message was commented out.
- *
  * @deprecated since Moodle 3.6
- *
- * When using this function, you should
- *
- * @global object
- * @param bool $unused No longer used.
- * @param int $rows Number of rows to display  (minimum of 10 when $height is non-null)
- * @param int $cols Number of columns to display (minimum of 65 when $width is non-null)
- * @param null $width (Deprecated) Width of the element; if a value is passed, the minimum value for $cols will be 65. Value is otherwise ignored.
- * @param null $height (Deprecated) Height of the element; if a value is passe, the minimum value for $rows will be 10. Value is otherwise ignored.
- * @param string $name Name to use for the textarea element.
- * @param string $value Initial content to display in the textarea.
- * @param int $obsolete deprecated
- * @param bool $return If false, will output string. If true, will return string value.
- * @param string $id CSS ID to add to the textarea element.
- * @return string|void depending on the value of $return
- */
-function print_textarea($unused, $rows, $cols, $width, $height, $name, $value='', $obsolete=0, $return=false, $id='') {
-    /// $width and height are legacy fields and no longer used as pixels like they used to be.
-    /// However, you can set them to zero to override the mincols and minrows values below.
-
-    // Disabling because there is not yet a viable $OUTPUT option for cases when mforms can't be used
-    debugging('print_textarea() is deprecated. Please use $OUTPUT->print_textarea() instead.', DEBUG_DEVELOPER);
-
-    global $OUTPUT;
-
-    $mincols = 65;
-    $minrows = 10;
-
-    if ($id === '') {
-        $id = 'edit-'.$name;
-    }
-
-    if ($height && ($rows < $minrows)) {
-        $rows = $minrows;
-    }
-    if ($width && ($cols < $mincols)) {
-        $cols = $mincols;
-    }
-
-    $textarea = $OUTPUT->print_textarea($name, $id, $value, $rows, $cols);
-    if ($return) {
-        return $textarea;
-    }
-
-    echo $textarea;
+ */
+function print_textarea() {
+    throw new coding_exception(
+        'print_textarea() has been removed. Please use $OUTPUT->print_textarea() instead.'
+    );
 }
 
 /**
@@ -2686,542 +2623,133 @@ function message_delete_message() {
 }
 
 /**
- * Get all of the allowed types for all of the courses and groups
- * the logged in user belongs to.
- *
- * The returned array will optionally have 5 keys:
- *      'user' : true if the logged in user can create user events
- *      'site' : true if the logged in user can create site events
- *      'category' : array of course categories that the user can create events for
- *      'course' : array of courses that the user can create events for
- *      'group': array of groups that the user can create events for
- *      'groupcourses' : array of courses that the groups belong to (can
- *                       be different from the list in 'course'.
  * @deprecated since 3.6
- * @return array The array of allowed types.
  */
 function calendar_get_all_allowed_types() {
-    debugging('calendar_get_all_allowed_types() is deprecated. Please use calendar_get_allowed_types() instead.',
-        DEBUG_DEVELOPER);
-
-    global $CFG, $USER, $DB;
-
-    require_once($CFG->libdir . '/enrollib.php');
-
-    $types = [];
-
-    $allowed = new stdClass();
-
-    calendar_get_allowed_types($allowed);
-
-    if ($allowed->user) {
-        $types['user'] = true;
-    }
-
-    if ($allowed->site) {
-        $types['site'] = true;
-    }
-
-    if (core_course_category::has_manage_capability_on_any()) {
-        $types['category'] = core_course_category::make_categories_list('moodle/category:manage');
-    }
-
-    // This function warms the context cache for the course so the calls
-    // to load the course context in calendar_get_allowed_types don't result
-    // in additional DB queries.
-    $courses = calendar_get_default_courses(null, 'id, groupmode, groupmodeforce', true);
-
-    // We want to pre-fetch all of the groups for each course in a single
-    // query to avoid calendar_get_allowed_types from hitting the DB for
-    // each separate course.
-    $groups = groups_get_all_groups_for_courses($courses);
-
-    foreach ($courses as $course) {
-        $coursegroups = isset($groups[$course->id]) ? $groups[$course->id] : null;
-        calendar_get_allowed_types($allowed, $course, $coursegroups);
-
-        if (!empty($allowed->courses)) {
-            $types['course'][$course->id] = $course;
-        }
-
-        if (!empty($allowed->groups)) {
-            $types['groupcourses'][$course->id] = $course;
-
-            if (!isset($types['group'])) {
-                $types['group'] = array_values($allowed->groups);
-            } else {
-                $types['group'] = array_merge($types['group'], array_values($allowed->groups));
-            }
-        }
-    }
+    throw new coding_exception(
+        'calendar_get_all_allowed_types() has been removed. Please use calendar_get_allowed_types() instead.'
+    );
 
-    return $types;
 }
 
 /**
- * Gets array of all groups in a set of course.
- *
- * @category group
- * @param array $courses Array of course objects or course ids.
- * @return array Array of groups indexed by course id.
+ * @deprecated since Moodle 3.6.
  */
-function groups_get_all_groups_for_courses($courses) {
-    global $DB;
-
-    if (empty($courses)) {
-        return [];
-    }
-
-    $groups = [];
-    $courseids = [];
-
-    foreach ($courses as $course) {
-        $courseid = is_object($course) ? $course->id : $course;
-        $groups[$courseid] = [];
-        $courseids[] = $courseid;
-    }
-
-    $groupfields = [
-        'g.id as gid',
-        'g.courseid',
-        'g.idnumber',
-        'g.name',
-        'g.description',
-        'g.descriptionformat',
-        'g.enrolmentkey',
-        'g.picture',
-        'g.hidepicture',
-        'g.timecreated',
-        'g.timemodified'
-    ];
-
-    $groupsmembersfields = [
-        'gm.id as gmid',
-        'gm.groupid',
-        'gm.userid',
-        'gm.timeadded',
-        'gm.component',
-        'gm.itemid'
-    ];
-
-    $concatidsql = $DB->sql_concat_join("'-'", ['g.id', 'COALESCE(gm.id, 0)']) . ' AS uniqid';
-    list($courseidsql, $params) = $DB->get_in_or_equal($courseids);
-    $groupfieldssql = implode(',', $groupfields);
-    $groupmembersfieldssql = implode(',', $groupsmembersfields);
-    $sql = "SELECT {$concatidsql}, {$groupfieldssql}, {$groupmembersfieldssql}
-              FROM {groups} g
-         LEFT JOIN {groups_members} gm
-                ON gm.groupid = g.id
-             WHERE g.courseid {$courseidsql}";
-
-    $results = $DB->get_records_sql($sql, $params);
-
-    // The results will come back as a flat dataset thanks to the left
-    // join so we will need to do some post processing to blow it out
-    // into a more usable data structure.
-    //
-    // This loop will extract the distinct groups from the result set
-    // and add it's list of members to the object as a property called
-    // 'members'. Then each group will be added to the result set indexed
-    // by it's course id.
-    //
-    // The resulting data structure for $groups should be:
-    // $groups = [
-    //      '1' = [
-    //          '1' => (object) [
-    //              'id' => 1,
-    //              <rest of group properties>
-    //              'members' => [
-    //                  '1' => (object) [
-    //                      <group member properties>
-    //                  ],
-    //                  '2' => (object) [
-    //                      <group member properties>
-    //                  ]
-    //              ]
-    //          ],
-    //          '2' => (object) [
-    //              'id' => 2,
-    //              <rest of group properties>
-    //              'members' => [
-    //                  '1' => (object) [
-    //                      <group member properties>
-    //                  ],
-    //                  '3' => (object) [
-    //                      <group member properties>
-    //                  ]
-    //              ]
-    //          ]
-    //      ]
-    // ]
-    //
-    foreach ($results as $key => $result) {
-        $groupid = $result->gid;
-        $courseid = $result->courseid;
-        $coursegroups = $groups[$courseid];
-        $groupsmembersid = $result->gmid;
-        $reducefunc = function($carry, $field) use ($result) {
-            // Iterate over the groups properties and pull
-            // them out into a separate object.
-            list($prefix, $field) = explode('.', $field);
-
-            if (property_exists($result, $field)) {
-                $carry[$field] = $result->{$field};
-            }
-
-            return $carry;
-        };
-
-        if (isset($coursegroups[$groupid])) {
-            $group = $coursegroups[$groupid];
-        } else {
-            $initial = [
-                'id' => $groupid,
-                'members' => []
-            ];
-            $group = (object) array_reduce(
-                $groupfields,
-                $reducefunc,
-                $initial
-            );
-        }
-
-        if (!empty($groupsmembersid)) {
-            $initial = ['id' => $groupsmembersid];
-            $groupsmembers = (object) array_reduce(
-                $groupsmembersfields,
-                $reducefunc,
-                $initial
-            );
-
-            $group->members[$groupsmembers->userid] = $groupsmembers;
-        }
-
-        $coursegroups[$groupid] = $group;
-        $groups[$courseid] = $coursegroups;
-    }
-
-    return $groups;
+function groups_get_all_groups_for_courses() {
+    throw new coding_exception(
+        'groups_get_all_groups_for_courses() has been removed and can not be used anymore.'
+    );
 }
 
 /**
- * Gets the capabilities that have been cached in the database for this
- * component.
  * @deprecated since Moodle 3.6. Please use the Events 2 API.
- * @todo final deprecation. To be removed in Moodle 4.0
- *
- * @access protected To be used from eventslib only
- *
- * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
- * @return array of events
  */
-function events_get_cached($component) {
-    global $DB;
-
-    debugging('Events API using $handlers array has been deprecated in favour of Events 2 API, please use it instead.',
-            DEBUG_DEVELOPER);
-
-    $cachedhandlers = array();
-
-    if ($storedhandlers = $DB->get_records('events_handlers', array('component'=>$component))) {
-        foreach ($storedhandlers as $handler) {
-            $cachedhandlers[$handler->eventname] = array (
-                'id'              => $handler->id,
-                'handlerfile'     => $handler->handlerfile,
-                'handlerfunction' => $handler->handlerfunction,
-                'schedule'        => $handler->schedule,
-                'internal'        => $handler->internal);
-        }
-    }
-
-    return $cachedhandlers;
+function events_get_cached() {
+    throw new coding_exception(
+        'Events API using $handlers array has been removed in favour of Events 2 API, please use it instead.'
+    );
 }
 
 /**
- * Remove all event handlers and queued events
  * @deprecated since Moodle 3.6. Please use the Events 2 API.
- * @todo final deprecation. To be removed in Moodle 4.0
- *
- * @category event
- * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
  */
-function events_uninstall($component) {
-    debugging('Events API using $handlers array has been deprecated in favour of Events 2 API, please use it instead.',
-            DEBUG_DEVELOPER);
-    $cachedhandlers = events_get_cached($component);
-    events_cleanup($component, $cachedhandlers);
-
-    events_get_handlers('reset');
+function events_uninstall() {
+    throw new coding_exception(
+        'Events API using $handlers array has been removed in favour of Events 2 API, please use it instead.'
+    );
 }
 
 /**
- * Deletes cached events that are no longer needed by the component.
  * @deprecated since Moodle 3.6. Please use the Events 2 API.
- * @todo final deprecation. To be removed in Moodle 4.0
- *
- * @access protected To be used from eventslib only
- *
- * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
- * @param array $cachedhandlers array of the cached events definitions that will be
- * @return int number of unused handlers that have been removed
  */
-function events_cleanup($component, $cachedhandlers) {
-    global $DB;
-    debugging('Events API using $handlers array has been deprecated in favour of Events 2 API, please use it instead.',
-            DEBUG_DEVELOPER);
-    $deletecount = 0;
-    foreach ($cachedhandlers as $eventname => $cachedhandler) {
-        if ($qhandlers = $DB->get_records('events_queue_handlers', array('handlerid'=>$cachedhandler['id']))) {
-            //debugging("Removing pending events from queue before deleting of event handler: $component - $eventname");
-            foreach ($qhandlers as $qhandler) {
-                events_dequeue($qhandler);
-            }
-        }
-        $DB->delete_records('events_handlers', array('eventname'=>$eventname, 'component'=>$component));
-        $deletecount++;
-    }
-
-    return $deletecount;
+function events_cleanup() {
+    throw new coding_exception(
+        'Events API using $handlers array has been removed in favour of Events 2 API, please use it instead.'
+    );
 }
 
 /**
- * Removes this queued handler from the events_queued_handler table
- *
- * Removes events_queue record from events_queue if no more references to this event object exists
  * @deprecated since Moodle 3.6. Please use the Events 2 API.
- * @todo final deprecation. To be removed in Moodle 4.0
- *
- * @access protected To be used from eventslib only
- *
- * @param stdClass $qhandler A row from the events_queued_handler table
  */
-function events_dequeue($qhandler) {
-    global $DB;
-    debugging('Events API using $handlers array has been deprecated in favour of Events 2 API, please use it instead.',
-            DEBUG_DEVELOPER);
-    // first delete the queue handler
-    $DB->delete_records('events_queue_handlers', array('id'=>$qhandler->id));
-
-    // if no more queued handler is pointing to the same event - delete the event too
-    if (!$DB->record_exists('events_queue_handlers', array('queuedeventid'=>$qhandler->queuedeventid))) {
-        $DB->delete_records('events_queue', array('id'=>$qhandler->queuedeventid));
-    }
+function events_dequeue() {
+    throw new coding_exception(
+        'Events API using $handlers array has been removed in favour of Events 2 API, please use it instead.'
+    );
 }
 
 /**
- * Returns handlers for given event. Uses caching for better perf.
  * @deprecated since Moodle 3.6. Please use the Events 2 API.
- * @todo final deprecation. To be removed in Moodle 4.0
- *
- * @access protected To be used from eventslib only
- *
- * @staticvar array $handlers
- * @param string $eventname name of event or 'reset'
- * @return array|false array of handlers or false otherwise
  */
-function events_get_handlers($eventname) {
-    global $DB;
-    static $handlers = array();
-    debugging('Events API using $handlers array has been deprecated in favour of Events 2 API, please use it instead.',
-            DEBUG_DEVELOPER);
-
-    if ($eventname === 'reset') {
-        $handlers = array();
-        return false;
-    }
-
-    if (!array_key_exists($eventname, $handlers)) {
-        $handlers[$eventname] = $DB->get_records('events_handlers', array('eventname'=>$eventname));
-    }
-
-    return $handlers[$eventname];
+function events_get_handlers() {
+    throw new coding_exception(
+        'Events API using $handlers array has been removed in favour of Events 2 API, please use it instead.'
+    );
 }
 
 /**
- * This function finds the roles assigned directly to this context only
- * i.e. no roles in parent contexts
- *
  * @deprecated since Moodle 3.6. Please use the get_roles_used_in_context().
- * @todo final deprecation. To be removed in Moodle 4.0
- * @param context $context
- * @return array
  */
-function get_roles_on_exact_context(context $context) {
-    debugging('get_roles_on_exact_context() is deprecated, please use get_roles_used_in_context() instead.',
-        DEBUG_DEVELOPER);
-
-    return get_roles_used_in_context($context, false);
+function get_roles_on_exact_context() {
+    throw new coding_exception(
+        'get_roles_on_exact_context() has been removed, please use get_roles_used_in_context() instead.'
+    );
 }
 
 /**
- * Find out which roles has assignment on this context
- *
  * @deprecated since Moodle 3.6. Please use the get_roles_used_in_context().
- * @todo final deprecation. To be removed in Moodle 4.0
- * @param context $context
- * @return array
  */
-function get_roles_with_assignment_on_context(context $context) {
-    debugging('get_roles_with_assignment_on_context() is deprecated, please use get_roles_used_in_context() instead.',
-        DEBUG_DEVELOPER);
-
-    return get_roles_used_in_context($context, false);
+function get_roles_with_assignment_on_context() {
+    throw new coding_exception(
+        'get_roles_with_assignment_on_context() has been removed, please use get_roles_used_in_context() instead.'
+    );
 }
 
 /**
- * Add the selected user as a contact for the current user
- *
  * @deprecated since Moodle 3.6
- * @param int $contactid the ID of the user to add as a contact
- * @param int $blocked 1 if you wish to block the contact
- * @param int $userid the user ID of the user we want to add the contact for, defaults to current user if not specified.
- * @return bool/int false if the $contactid isnt a valid user id. True if no changes made.
- *                  Otherwise returns the result of update_record() or insert_record()
- */
-function message_add_contact($contactid, $blocked = 0, $userid = 0) {
-    debugging('message_add_contact() is deprecated. Please use \core_message\api::create_contact_request() instead. ' .
+ */
+function message_add_contact() {
+    throw new coding_exception(
+        'message_add_contact() has been removed. Please use \core_message\api::create_contact_request() instead. ' .
         'If you wish to block or unblock a user please use \core_message\api::is_blocked() and ' .
-        '\core_message\api::block_user() or \core_message\api::unblock_user() respectively.', DEBUG_DEVELOPER);
-
-    global $USER, $DB;
-
-    if (!$DB->record_exists('user', array('id' => $contactid))) {
-        return false;
-    }
-
-    if (empty($userid)) {
-        $userid = $USER->id;
-    }
-
-    // Check if a record already exists as we may be changing blocking status.
-    if (\core_message\api::is_contact($userid, $contactid)) {
-        $isblocked = \core_message\api::is_blocked($userid, $contactid);
-        // Check if blocking status has been changed.
-        if ($isblocked != $blocked) {
-            if ($blocked == 1) {
-                if (!$isblocked) {
-                    \core_message\api::block_user($userid, $contactid);
-                }
-            } else {
-                \core_message\api::unblock_user($userid, $contactid);
-            }
-
-            return true;
-        } else {
-            // No change to blocking status.
-            return true;
-        }
-    } else {
-        if ($blocked == 1) {
-            if (!\core_message\api::is_blocked($userid, $contactid)) {
-                \core_message\api::block_user($userid, $contactid);
-            }
-        } else {
-            \core_message\api::unblock_user($userid, $contactid);
-            if (!\core_message\api::does_contact_request_exist($userid, $contactid)) {
-                \core_message\api::create_contact_request($userid, $contactid);
-            }
-        }
-
-        return true;
-    }
+        '\core_message\api::block_user() or \core_message\api::unblock_user() respectively.'
+    );
 }
 
 /**
- * Remove a contact.
- *
  * @deprecated since Moodle 3.6
- * @param int $contactid the user ID of the contact to remove
- * @param int $userid the user ID of the user we want to remove the contacts for, defaults to current user if not specified.
- * @return bool returns the result of delete_records()
  */
-function message_remove_contact($contactid, $userid = 0) {
-    debugging('message_remove_contact() is deprecated. Please use \core_message\api::remove_contact() instead.',
-        DEBUG_DEVELOPER);
-
-    global $USER;
-
-    if (empty($userid)) {
-        $userid = $USER->id;
-    }
-
-    \core_message\api::remove_contact($userid, $contactid);
-
-    return true;
+function message_remove_contact() {
+    throw new coding_exception(
+        'message_remove_contact() has been removed. Please use \core_message\api::remove_contact() instead.'
+    );
 }
 
 /**
- * Unblock a contact.
- *
  * @deprecated since Moodle 3.6
- * @param int $contactid the user ID of the contact to unblock
- * @param int $userid the user ID of the user we want to unblock the contact for, defaults to current user
- *  if not specified.
- * @return bool returns the result of delete_records()
  */
-function message_unblock_contact($contactid, $userid = 0) {
-    debugging('message_unblock_contact() is deprecated. Please use \core_message\api::unblock_user() instead.',
-        DEBUG_DEVELOPER);
-
-    global $DB, $USER;
-
-    if (!$DB->record_exists('user', array('id' => $contactid))) {
-        return false;
-    }
-
-    if (empty($userid)) {
-        $userid = $USER->id;
-    }
-
-    \core_message\api::unblock_user($userid, $contactid);
-
-    return true;
+function message_unblock_contact() {
+    throw new coding_exception(
+        'message_unblock_contact() has been removed. Please use \core_message\api::unblock_user() instead.'
+    );
 }
 
 /**
- * Block a user.
- *
  * @deprecated since Moodle 3.6
- * @param int $contactid the user ID of the user to block
- * @param int $userid the user ID of the user we want to unblock the contact for, defaults to current user
- *  if not specified.
- * @return bool
  */
-function message_block_contact($contactid, $userid = 0) {
-    debugging('message_block_contact() is deprecated. Please use \core_message\api::is_blocked() and ' .
-        '\core_message\api::block_user() instead.', DEBUG_DEVELOPER);
-
-    global $DB, $USER;
-
-    if (!$DB->record_exists('user', array('id' => $contactid))) {
-        return false;
-    }
-
-    if (empty($userid)) {
-        $userid = $USER->id;
-    }
-
-    if (!\core_message\api::is_blocked($userid, $contactid)) {
-        \core_message\api::block_user($userid, $contactid);
-    }
-
-    return true;
+function message_block_contact() {
+    throw new coding_exception(
+        'message_block_contact() has been removed. Please use \core_message\api::is_blocked() and ' .
+        '\core_message\api::block_user() instead.'
+    );
 }
 
 /**
- * Load a user's contact record
- *
  * @deprecated since Moodle 3.6
- * @param int $contactid the user ID of the user whose contact record you want
- * @return array message contacts
  */
-function message_get_contact($contactid) {
-    debugging('message_get_contact() is deprecated. Please use \core_message\api::get_contact() instead.',
-        DEBUG_DEVELOPER);
-
-    global $USER;
-
-    return \core_message\api::get_contact($USER->id, $contactid);
+function message_get_contact() {
+    throw new coding_exception(
+        'message_get_contact() has been removed. Please use \core_message\api::get_contact() instead.'
+    );
 }
 
 /**
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 624247e..b5e540d 100644 (file)
@@ -38,7 +38,7 @@ $string['height'] = 'Height';
 $string['imageproperties'] = 'Image properties';
 $string['presentation'] = 'This image is decorative only';
 $string['pluginname'] = 'Insert or edit image';
-$string['presentationoraltrequired'] = 'Images must have a description, except if the description is marked as not necessary.';
+$string['presentationoraltrequired'] = 'An image must have a description, unless it is marked as decorative only.';
 $string['preview'] = 'Preview';
 $string['saveimage'] = 'Save image';
 $string['size'] = 'Size';
index 8e28ab4..78bade9 100644 (file)
@@ -1306,8 +1306,7 @@ class flexible_table {
 
             if (array_key_exists($sortby, $sortdata)) {
                 // This key already exists somewhere. Change its sortorder and bring it to the top.
-                //$sortorder = $sortdata[$sortby] = $sortorder;
-                unset($sortdata['sortby'][$sortby]);
+                unset($sortdata[$sortby]);
             }
             $sortdata = array_merge([$sortby => $sortorder], $sortdata);
         }
index e9284f7..8906527 100644 (file)
@@ -41,59 +41,6 @@ use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
  */
 class behat_deprecated extends behat_base {
 
-    /**
-     * Click link in navigation tree that matches the text in parentnode/s (seperated using greater-than character if more than one)
-     *
-     * @throws ExpectationException
-     * @param string $nodetext navigation node to click.
-     * @param string $parentnodes comma seperated list of parent nodes.
-     * @return void
-     * @deprecated since Moodle 3.6 MDL-57281 - please do not use this definition step any more.
-     * @todo MDL-63004 This will be deleted in Moodle 4.0.
-     */
-    public function i_navigate_to_node_in($nodetext, $parentnodes) {
-        $alternative[] = 'I navigate to "PATH" in current page administration';
-        $alternative[] = 'I navigate to "PATH" in site administration';
-        $alternative[] = 'I navigate to "TAB1 > TAB2" in the course gradebook';
-        $alternative[] = 'I navigate to course participants';
-        $alternative[] = 'If some items are not available without Navigation block at all, one can use combination of:
-                              I add the "Navigation" block if not present
-                              I click on "LINK" "link" in the "Navigation" "block"';
-
-        $this->deprecated_message($alternative);
-
-        $parentnodes = array_map('trim', explode('>', $parentnodes));
-        $nodelist = array_merge($parentnodes, [$nodetext]);
-        $firstnode = array_shift($nodelist);
-
-        if ($firstnode === get_string('administrationsite')) {
-            $this->execute('behat_theme_boost_behat_navigation::i_select_from_flat_navigation_drawer',
-                    array(get_string('administrationsite')));
-            $this->execute('behat_theme_boost_behat_navigation::select_on_administration_page', array($nodelist));
-            return;
-        }
-
-        if ($firstnode === get_string('sitepages')) {
-            if ($nodetext === get_string('calendar', 'calendar')) {
-                $this->execute('behat_theme_boost_behat_navigation::i_select_from_flat_navigation_drawer',
-                        array(($nodetext)));
-            } else {
-                // TODO MDL-57120 other links under "Site pages" are not accessible without navigation block.
-                $this->execute('behat_theme_boost_behat_navigation::select_node_in_navigation',
-                        array($nodetext, $parentnodes));
-            }
-            return;
-        }
-
-        if ($firstnode === get_string('courseadministration')) {
-            // Administration menu is available only on the main course page where settings in Administration
-            // block (original purpose of the step) are available on every course page.
-            $this->execute('behat_theme_boost_behat_navigation::go_to_main_course_page', array());
-        }
-
-        $this->execute('behat_theme_boost_behat_navigation::select_from_administration_menu', array($nodelist));
-    }
-
     /**
      * Docks a block. Editing mode should be previously enabled.
      * @throws ExpectationException
index 115765a..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');
         }
@@ -381,15 +370,6 @@ class behat_hooks extends behat_base {
         $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);
@@ -576,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 3eed52f..052fe30 100644 (file)
@@ -194,7 +194,7 @@ class behat_navigation extends behat_base {
             // We just want to expand the node, we don't want to follow it.
             $node = $node->getParent();
         }
-        $node->click();
+        $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
     }
 
     /**
@@ -218,7 +218,7 @@ class behat_navigation extends behat_base {
             // We just want to expand the node, we don't want to follow it.
             $node = $node->getParent();
         }
-        $node->click();
+        $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
     }
 
     /**
@@ -245,7 +245,7 @@ class behat_navigation extends behat_base {
                 // don't wait, it is non-JS and we already waited for the DOM.
                 $siteadminlink = $this->getSession()->getPage()->find('named_exact', array('link', "'" . $siteadminstr . "'"));
                 if ($siteadminlink) {
-                    $siteadminlink->click();
+                    $this->execute('behat_general::i_click_on', [$siteadminlink, 'NodeElement']);
                 }
             }
         }
@@ -302,7 +302,7 @@ class behat_navigation extends behat_base {
             throw new ExpectationException('Navigation node "' . $nodetext . '" not found under "' .
                 implode(' > ', $parentnodes) . '"', $this->getSession());
         }
-        $nodetoclick->click();
+        $this->execute('behat_general::i_click_on', [$nodetoclick, 'NodeElement']);
     }
 
     /**
@@ -791,9 +791,8 @@ class behat_navigation extends behat_base {
         $node = $this->find('xpath', $xpath);
         $expanded = $node->getAttribute('aria-expanded');
         if ($expanded === 'false') {
-            $node->click();
+            $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
             $this->ensure_node_attribute_is_set($node, 'aria-expanded', 'true');
-            $this->wait_for_pending_js();
         }
     }
 
@@ -812,8 +811,7 @@ class behat_navigation extends behat_base {
         $node = $this->find('xpath', $xpath);
         $expanded = $node->getAttribute('aria-expanded');
         if ($expanded === 'true') {
-            $node->click();
-            $this->wait_for_pending_js();
+            $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
         }
     }
 
@@ -835,8 +833,8 @@ class behat_navigation extends behat_base {
     protected function go_to_main_course_page() {
         $url = $this->getSession()->getCurrentUrl();
         if (!preg_match('|/course/view.php\?id=[\d]+$|', $url)) {
-            $this->find('xpath', '//header//div[@id=\'page-navbar\']//a[contains(@href,\'/course/view.php?id=\')]')->click();
-            $this->execute('behat_general::wait_until_the_page_is_ready');
+            $node = $this->find('xpath', '//header//div[@id=\'page-navbar\']//a[contains(@href,\'/course/view.php?id=\')]');
+            $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
         }
     }
 
@@ -856,8 +854,8 @@ class behat_navigation extends behat_base {
             $tabxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
             if ($node = $this->getSession()->getPage()->find('xpath', $tabxpath)) {
                 if ($this->running_javascript()) {
+                    $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
                     // Click on the tab and add 'active' tab to the xpath.
-                    $node->click();
                     $xpath .= '//div[contains(@class,\'active\')]';
                 } else {
                     // Add the tab content selector to the xpath.
@@ -881,8 +879,7 @@ class behat_navigation extends behat_base {
         if (!$node = $this->getSession()->getPage()->find('xpath', $xpath)) {
             throw new ElementNotFoundException($this->getSession(), 'Link "' . join(' > ', $nodelist) . '"');
         }
-        $node->click();
-        $this->wait_for_pending_js();
+        $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
     }
 
     /**
@@ -929,8 +926,8 @@ class behat_navigation extends behat_base {
             $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu();
         }
         if ($menuxpath && $this->running_javascript()) {
-            $this->find('xpath', $menuxpath . '//a[@data-toggle=\'dropdown\']')->click();
-            $this->wait_for_pending_js();
+            $node = $this->find('xpath', $menuxpath . '//a[@data-toggle=\'dropdown\']');
+            $this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
         }
     }
 
@@ -952,15 +949,14 @@ class behat_navigation extends behat_base {
             $isheader = false;
         }
 
-        $this->toggle_page_administration_menu($menuxpath);
+        $this->execute('behat_navigation::toggle_page_administration_menu', [$menuxpath]);
 
         if (!$isheader || count($nodelist) == 1) {
             $lastnode = end($nodelist);
             $linkname = behat_context_helper::escape($lastnode);
             $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]');
             if ($link) {
-                $link->click();
-                $this->wait_for_pending_js();
+                $this->execute('behat_general::i_click_on', [$link, 'NodeElement']);
                 return;
             }
         }
@@ -970,8 +966,7 @@ class behat_navigation extends behat_base {
             $linkname = behat_context_helper::escape(get_string('morenavigationlinks'));
             $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]');
             if ($link) {
-                $link->click();
-                $this->execute('behat_general::wait_until_the_page_is_ready');
+                $this->execute('behat_general::i_click_on', [$link, 'NodeElement']);
                 $this->select_on_administration_page($nodelist);
                 return;
             }
index 8c094f3..9e71470 100644 (file)
@@ -596,7 +596,7 @@ class core_event_testcase extends advanced_testcase {
         events_update_definition('unittest');
 
         $DB->delete_records_select('events_handlers', "component <> 'unittest'");
-        events_get_handlers('reset');
+
         $this->assertDebuggingCalled(self::DEBUGGING_MSG, DEBUG_DEVELOPER);
         $this->assertEquals(3, $DB->count_records('events_handlers'));
         set_config('loglifetime', 60*60*24*5);
index 47a9fec..2064621 100644 (file)
@@ -70,35 +70,35 @@ class lock_testcase extends advanced_testcase {
             $this->assertNotEmpty($lock1, 'Get a lock');
 
             if ($lockfactory->supports_timeout()) {
-                if ($lockfactory->supports_recursion()) {
-                    $lock2 = $lockfactory->get_lock('abc', 2);
+                // Attempt to obtain a lock within a 2 sec timeout.
+                $durationlock2 = -microtime(true);
+                $lock2 = $lockfactory->get_lock('abc', 2);
+                $durationlock2 += microtime(true);
+
+                if (!$lock2) { // If the lock was not obtained.
+                    $this->assertFalse($lock2, 'Cannot get a stacked lock');
+                    // This should timeout after 2 seconds.
+                    $this->assertTrue($durationlock2 < 2.5, 'Lock should timeout after no more than 2 seconds');
+                } else {
                     $this->assertNotEmpty($lock2, 'Get a stacked lock');
                     $this->assertTrue($lock2->release(), 'Release a stacked lock');
+                }
+
+                // Attempt to obtain a lock within a 0 sec timeout.
+                $durationlock2 = -microtime(true);
+                $lock2 = $lockfactory->get_lock('abc', 0);
+                $durationlock2 += microtime(true);
 
+                if (!$lock2) { // If the lock was not obtained.
+                    // This should timeout almost instantly.
+                    $this->assertTrue($durationlock2 < 0.100, 'Lock should timeout almost instantly < 100ms');
+                } else {
                     // This stacked lock should be gained almost instantly.
-                    $duration = -microtime(true);
-                    $lock3 = $lockfactory->get_lock('abc', 0);
-                    $duration += microtime(true);
-                    $lock3->release();
-                    $this->assertTrue($duration < 0.100, 'Lock should be gained almost instantly');
+                    $this->assertTrue($durationlock2 < 0.100, 'Lock should be gained almost instantly');
+                    $lock2->release();
 
                     // We should also assert that locks fail instantly if locked
                     // from another process but this is hard to unit test.
-
-                } else {
-                    // This should timeout after 2 seconds.
-                    $duration = -microtime(true);
-                    $lock2 = $lockfactory->get_lock('abc', 2);
-                    $duration += microtime(true);
-                    $this->assertFalse($lock2, 'Cannot get a stacked lock');
-                    $this->assertTrue($duration < 2.5, 'Lock should timeout after no more than 2 seconds');
-
-                    // This should timeout almost instantly.
-                    $duration = -microtime(true);
-                    $lock2 = $lockfactory->get_lock('abc', 0);
-                    $duration += microtime(true);
-                    $this->assertFalse($lock2, 'Cannot get a stacked lock');
-                    $this->assertTrue($duration < 0.100, 'Lock should timeout almost instantly < 100ms');
                 }
             }
             // Release the lock.
index 4e79b93..8ddc5de 100644 (file)
@@ -1,6 +1,31 @@
 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.
+* add_to_log() has been through final deprecation, please rewrite your code to the new events API.
+* The following functions have been finally deprecated and can not be used anymore:
+  - print_textarea
+  - calendar_get_all_allowed_types
+  - groups_get_all_groups_for_courses
+  - events_get_cached
+  - events_uninstall
+  - events_cleanup
+  - events_dequeue
+  - events_get_handlers
+  - get_roles_on_exact_context
+  - get_roles_with_assignment_on_context
+  - message_add_contact
+  - message_remove_contact
+  - message_unblock_contact
+  - message_block_contact
+  - message_get_contact
+
 === 3.9 ===
 * Following function has been deprecated, please use \core\task\manager::run_from_cli().
     - cron_run_single_task()
index c7d3d2b..29ef0e7 100644 (file)
@@ -2285,6 +2285,11 @@ function send_headers($contenttype, $cacheable = true) {
     if (empty($CFG->allowframembedding) && !core_useragent::is_moodle_app()) {
         @header('X-Frame-Options: sameorigin');
     }
+
+    // If referrer policy is set, add a referrer header.
+    if (!empty($CFG->referrerpolicy) && ($CFG->referrerpolicy !== 'default')) {
+        @header('Referrer-Policy: ' . $CFG->referrerpolicy);
+    }
 }
 
 /**
index 3e101d5..d942821 100644 (file)
@@ -292,6 +292,7 @@ class core_message_external extends external_api {
      * Create contacts.
      *
      * @deprecated since Moodle 3.6
+     * TODO: MDL-63261
      * @param array $userids array of user IDs.
      * @param int $userid The id of the user we are creating the contacts for
      * @return external_description
@@ -323,7 +324,7 @@ class core_message_external extends external_api {
 
         $warnings = array();
         foreach ($params['userids'] as $id) {
-            if (!message_add_contact($id, 0, $params['userid'])) {
+            if (!\core_message\api::create_contact_request($params['userid'], $id)) {
                 $warnings[] = array(
                     'item' => 'user',
                     'itemid' => $id,
@@ -682,6 +683,7 @@ class core_message_external extends external_api {
      * Block contacts.
      *
      * @deprecated since Moodle 3.6
+     * TODO: MDL-63261
      * @param array $userids array of user IDs.
      * @param int $userid The id of the user we are blocking the contacts for
      * @return external_description
@@ -713,7 +715,7 @@ class core_message_external extends external_api {
 
         $warnings = array();
         foreach ($params['userids'] as $id) {
-            if (!message_block_contact($id, $params['userid'])) {
+            if (!\core_message\api::block_user($params['userid'], $id)) {
                 $warnings[] = array(
                     'item' => 'user',
                     'itemid' => $id,
@@ -798,7 +800,7 @@ class core_message_external extends external_api {
         }
 
         foreach ($params['userids'] as $id) {
-            message_unblock_contact($id, $params['userid']);
+            core_message\api::unblock_user($params['userid'], $id);
         }
 
         return null;
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 1f14399..9a61444 100644 (file)
@@ -340,6 +340,8 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
     /**
      * Test create_contacts.
+     *
+     * TODO: MDL-63261
      */
     public function test_create_contacts() {
         $this->resetAfterTest(true);
@@ -351,41 +353,17 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $user5 = self::getDataGenerator()->create_user();
         $this->setUser($user1);
 
-        // Adding a contact.
-        $return = core_message_external::create_contacts(array($user2->id));
-        $this->assertDebuggingCalled();
-        $return = external_api::clean_returnvalue(core_message_external::create_contacts_returns(), $return);
-        $this->assertEquals(array(), $return);
-
         // Adding a contact who is already a contact.
         $return = core_message_external::create_contacts(array($user2->id));
-        $this->assertDebuggingCalled();
         $return = external_api::clean_returnvalue(core_message_external::create_contacts_returns(), $return);
         $this->assertEquals(array(), $return);
 
         // Adding multiple contacts.
         $return = core_message_external::create_contacts(array($user3->id, $user4->id));
-        $this->assertDebuggingCalledCount(2);
         $return = external_api::clean_returnvalue(core_message_external::create_contacts_returns(), $return);
         $this->assertEquals(array(), $return);
 
-        // Adding a non-existing user.
-        $return = core_message_external::create_contacts(array(99999));
-        $this->assertDebuggingCalled();
-        $return = external_api::clean_returnvalue(core_message_external::create_contacts_returns(), $return);
-        $this->assertCount(1, $return);
-        $return = array_pop($return);
-        $this->assertEquals($return['warningcode'], 'contactnotcreated');
-        $this->assertEquals($return['itemid'], 99999);
-
-        // Adding contacts with valid and invalid parameters.
-        $return = core_message_external::create_contacts(array($user5->id, 99999));
-        $this->assertDebuggingCalledCount(2);
-        $return = external_api::clean_returnvalue(core_message_external::create_contacts_returns(), $return);
-        $this->assertCount(1, $return);
-        $return = array_pop($return);
-        $this->assertEquals($return['warningcode'], 'contactnotcreated');
-        $this->assertEquals($return['itemid'], 99999);
+        // Note: We should add real user checks in api L:2656.
 
         // Try to add a contact to another user, should throw an exception.
         // All assertions must be added before this point.
@@ -455,36 +433,36 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         \core_message\api::add_contact($user1->id, $user4->id);
         \core_message\api::add_contact($user1->id, $user5->id);
 
-        // Blocking a contact.
-        $return = core_message_external::block_contacts(array($user2->id));
-        $this->assertDebuggingCalled();
-        $return = external_api::clean_returnvalue(core_message_external::block_contacts_returns(), $return);
-        $this->assertEquals(array(), $return);
-
         // Blocking a contact who is already a contact.
         $return = core_message_external::block_contacts(array($user2->id));
-        $this->assertDebuggingCalled();
         $return = external_api::clean_returnvalue(core_message_external::block_contacts_returns(), $return);
-        $this->assertEquals(array(), $return);
+        $this->assertEquals(array(array(
+            'item' => 'user',
+            'itemid' => $user2->id,
+            'warningcode' => 'contactnotblocked',
+            'message' => 'The contact could not be blocked'
+        )), $return);
 
         // Blocking multiple contacts.
         $return = core_message_external::block_contacts(array($user3->id, $user4->id));
-        $this->assertDebuggingCalledCount(2);
         $return = external_api::clean_returnvalue(core_message_external::block_contacts_returns(), $return);
-        $this->assertEquals(array(), $return);
+        $this->assertEquals(array(
+            array(
+                'item' => 'user',
+                'itemid' => $user3->id,
+                'warningcode' => 'contactnotblocked',
+                'message' => 'The contact could not be blocked'
+            ),
+            array(
+                'item' => 'user',
+                'itemid' => $user4->id,
+                'warningcode' => 'contactnotblocked',
+                'message' => 'The contact could not be blocked'
+            )
+        ), $return);
 
         // Blocking a non-existing user.
         $return = core_message_external::block_contacts(array(99999));
-        $this->assertDebuggingCalled();
-        $return = external_api::clean_returnvalue(core_message_external::block_contacts_returns(), $return);
-        $this->assertCount(1, $return);
-        $return = array_pop($return);
-        $this->assertEquals($return['warningcode'], 'contactnotblocked');
-        $this->assertEquals($return['itemid'], 99999);
-
-        // Blocking contacts with valid and invalid parameters.
-        $return = core_message_external::block_contacts(array($user5->id, 99999));
-        $this->assertDebuggingCalledCount(2);
         $return = external_api::clean_returnvalue(core_message_external::block_contacts_returns(), $return);
         $this->assertCount(1, $return);
         $return = array_pop($return);
@@ -518,34 +496,28 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Removing a non-contact.
         $return = core_message_external::unblock_contacts(array($user2->id));
-        $this->assertDebuggingCalled();
         $this->assertNull($return);
 
         // Removing one contact.
         $return = core_message_external::unblock_contacts(array($user3->id));
-        $this->assertDebuggingCalled();
         $this->assertNull($return);
 
         // Removing multiple contacts.
         $return = core_message_external::unblock_contacts(array($user4->id, $user5->id));
-        $this->assertDebuggingCalledCount(2);
         $this->assertNull($return);
 
         // Removing contact from unexisting user.
         $return = core_message_external::unblock_contacts(array(99999));
-        $this->assertDebuggingCalled();
         $this->assertNull($return);
 
         // Removing mixed valid and invalid data.
         $return = core_message_external::unblock_contacts(array($user6->id, 99999));
-        $this->assertDebuggingCalledCount(2);
         $this->assertNull($return);
 
         // Try to unblock a contact of another user contact list, should throw an exception.
         // All assertions must be added before this point.
         $this->expectException('required_capability_exception');
         core_message_external::unblock_contacts(array($user2->id), $user3->id);
-        $this->assertDebuggingCalled();
     }
 
     /**
@@ -1392,7 +1364,6 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertCount(1, $contacts['online']);
         $this->assertCount(3, $contacts['strangers']);
         core_message_external::block_contacts(array($user_blocked->id));
-        $this->assertDebuggingCalled();
         $contacts = core_message_external::get_contacts();
         $contacts = external_api::clean_returnvalue(core_message_external::get_contacts_returns(), $contacts);
         $this->assertCount(3, $contacts['offline']);
@@ -1822,7 +1793,6 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
 
         // Block the $userblocked and retrieve again the list.
         core_message_external::block_contacts(array($userblocked->id));
-        $this->assertDebuggingCalled();
         $blockedusers = core_message_external::get_blocked_users($user1->id);
         $blockedusers = external_api::clean_returnvalue(core_message_external::get_blocked_users_returns(), $blockedusers);
         $this->assertCount(1, $blockedusers['users']);
index 8b0e8fd..c02a610 100644 (file)
@@ -223,102 +223,6 @@ class core_message_messagelib_testcase extends advanced_testcase {
         $this->assertEquals(0, message_count_unread_messages($userfrom));
     }
 
-    /**
-     * Test message_add_contact.
-     */
-    public function test_message_add_contact() {
-        global $DB, $USER;
-
-        // Set this user as the admin.
-        $this->setAdminUser();
-
-        // Create a user to add to the admin's contact list.
-        $user1 = $this->getDataGenerator()->create_user();
-        $user2 = $this->getDataGenerator()->create_user();
-
-        message_add_contact($user1->id);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(1, $DB->count_records('message_contact_requests'));
-
-        message_add_contact($user2->id, 1);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(1, $DB->count_records('message_users_blocked'));
-
-        message_add_contact($user2->id, 0);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(0, $DB->count_records('message_users_blocked'));
-    }
-
-    /**
-     * Test message_remove_contact.
-     */
-    public function test_message_remove_contact() {
-        global $USER;
-
-        // Set this user as the admin.
-        $this->setAdminUser();
-
-        // Create a user to add to the admin's contact list.
-        $user = $this->getDataGenerator()->create_user();
-
-        // Add the user to the admin's contact list.
-        \core_message\api::add_contact($USER->id, $user->id);
-
-        // Remove user from admin's contact list.
-        message_remove_contact($user->id);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(false, message_get_contact($user->id));
-        $this->assertDebuggingCalled();
-    }
-
-    /**
-     * Test message_block_contact.
-     */
-    public function test_message_block_contact() {
-        global $USER;
-
-        // Set this user as the admin.
-        $this->setAdminUser();
-
-        // Create a user to add to the admin's contact list.
-        $user1 = $this->getDataGenerator()->create_user();
-        $user2 = $this->getDataGenerator()->create_user();
-
-        // Add users to the admin's contact list.
-        \core_message\api::add_contact($USER->id, $user1->id);
-        \core_message\api::add_contact($USER->id, $user2->id);
-
-        $this->assertEquals(0, \core_message\api::count_blocked_users());
-
-        // Block 1 user.
-        message_block_contact($user2->id);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(1, \core_message\api::count_blocked_users());
-
-    }
-
-    /**
-     * Test message_unblock_contact.
-     */
-    public function test_message_unblock_contact() {
-        global $USER;
-
-        // Set this user as the admin.
-        $this->setAdminUser();
-
-        // Create a user to add to the admin's contact list.
-        $user1 = $this->getDataGenerator()->create_user();
-
-        // Add users to the admin's blocked list.
-        \core_message\api::block_user($USER->id, $user1->id);
-        $this->assertEquals(1, \core_message\api::count_blocked_users());
-
-        // Unblock user.
-        message_unblock_contact($user1->id);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(0, \core_message\api::count_blocked_users());
-    }
-
     /**
      * Test message_search_users.
      */
index be6b0a1..86b4151 100644 (file)
@@ -98,6 +98,7 @@ class convert_submissions extends scheduled_task {
             }
 
             mtrace('Convert ' . count($users) . ' submission attempt(s) for assignment ' . $assignmentid);
+            $conversionrequirespolling = false;
 
             foreach ($users as $userid) {
                 try {
@@ -107,6 +108,7 @@ class convert_submissions extends scheduled_task {
                         case combined_document::STATUS_READY_PARTIAL:
                         case combined_document::STATUS_PENDING_INPUT:
                             // The document has not been converted yet or is somehow still ready.
+                            $conversionrequirespolling = true;
                             continue 2;
                     }
                     document_services::get_page_images_for_attempt(
@@ -127,7 +129,9 @@ class convert_submissions extends scheduled_task {
             }
 
             // Remove from queue.
-            $DB->delete_records('assignfeedback_editpdf_queue', array('id' => $record->id));
+            if (!$conversionrequirespolling) {
+                $DB->delete_records('assignfeedback_editpdf_queue', array('id' => $record->id));
+            }
 
         }
     }
index 0288cb1..a56b121 100644 (file)
@@ -88,7 +88,7 @@ $string['limitanswers'] = 'Limit the number of responses allowed';
 $string['modulename'] = 'Choice';
 $string['modulename_help'] = 'The choice activity module enables a teacher to ask a single question and offer a selection of possible responses.
 
-Choice results may be published after students have answered, after a certain date, or not at all. Results may be published with student names or anonymously.
+Choice results may be published after students have answered, after a certain date, or not at all. Results may be published with student names or anonymously (though teachers always see student names and their responses).
 
 A choice activity may be used
 
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 104a139..002a769 100644 (file)
@@ -29,9 +29,9 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2020062600.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2020070400.00;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
-$release  = '4.0dev (Build: 20200626)'; // Human-friendly version name
+$release  = '4.0dev (Build: 20200704)'; // Human-friendly version name
 $branch   = '40';                       // This version's branch.
 $maturity = MATURITY_ALPHA;             // This version's maturity level.