Merge branch 'MDL-67681_master' of https://github.com/marxjohnson/moodle
authorSara Arjona <sara@moodle.com>
Tue, 4 Feb 2020 11:16:39 +0000 (12:16 +0100)
committerSara Arjona <sara@moodle.com>
Tue, 4 Feb 2020 11:16:39 +0000 (12:16 +0100)
186 files changed:
admin/tool/behat/index.php
admin/tool/behat/locallib.php
admin/tool/behat/renderer.php
admin/tool/behat/steps_definitions_form.php
admin/tool/behat/styles.css
admin/tool/behat/tests/behat/list_steps.feature
admin/user/user_bulk.php
admin/user/user_bulk_forms.php
competency/classes/api.php
competency/tests/hooks_test.php
config-dist.php
course/classes/customfield/course_handler.php
course/format/renderer.php
course/lib.php
course/moodleform_mod.php
customfield/tests/api_test.php
customfield/tests/category_controller_test.php
customfield/tests/data_controller_test.php
customfield/tests/field_controller_test.php
customfield/tests/generator_test.php
customfield/tests/privacy_test.php
favourites/tests/component_favourite_service_test.php
favourites/tests/user_favourite_service_test.php
install/lang/el/error.php
install/lang/he_wp/admin.php [new file with mode: 0644]
lang/en/admin.php
lib/adminlib.php
lib/adodb/adodb-active-record.inc.php
lib/adodb/adodb-active-recordx.inc.php
lib/adodb/adodb-csvlib.inc.php
lib/adodb/adodb-datadict.inc.php
lib/adodb/adodb-error.inc.php
lib/adodb/adodb-errorhandler.inc.php
lib/adodb/adodb-errorpear.inc.php
lib/adodb/adodb-exceptions.inc.php
lib/adodb/adodb-iterator.inc.php
lib/adodb/adodb-lib.inc.php
lib/adodb/adodb-memcache.lib.inc.php
lib/adodb/adodb-pager.inc.php
lib/adodb/adodb-pear.inc.php
lib/adodb/adodb-perf.inc.php
lib/adodb/adodb-php4.inc.php
lib/adodb/adodb-time.inc.php
lib/adodb/adodb.inc.php
lib/adodb/datadict/datadict-access.inc.php
lib/adodb/datadict/datadict-db2.inc.php
lib/adodb/datadict/datadict-firebird.inc.php
lib/adodb/datadict/datadict-generic.inc.php
lib/adodb/datadict/datadict-ibase.inc.php
lib/adodb/datadict/datadict-informix.inc.php
lib/adodb/datadict/datadict-mssql.inc.php
lib/adodb/datadict/datadict-mssqlnative.inc.php
lib/adodb/datadict/datadict-mysql.inc.php
lib/adodb/datadict/datadict-oci8.inc.php
lib/adodb/datadict/datadict-postgres.inc.php
lib/adodb/datadict/datadict-sapdb.inc.php
lib/adodb/datadict/datadict-sqlite.inc.php
lib/adodb/datadict/datadict-sybase.inc.php
lib/adodb/drivers/adodb-access.inc.php
lib/adodb/drivers/adodb-ado.inc.php
lib/adodb/drivers/adodb-ado5.inc.php
lib/adodb/drivers/adodb-ado_access.inc.php
lib/adodb/drivers/adodb-ado_mssql.inc.php
lib/adodb/drivers/adodb-borland_ibase.inc.php
lib/adodb/drivers/adodb-csv.inc.php
lib/adodb/drivers/adodb-db2.inc.php
lib/adodb/drivers/adodb-db2oci.inc.php
lib/adodb/drivers/adodb-db2ora.inc.php
lib/adodb/drivers/adodb-fbsql.inc.php
lib/adodb/drivers/adodb-firebird.inc.php
lib/adodb/drivers/adodb-ibase.inc.php
lib/adodb/drivers/adodb-informix.inc.php
lib/adodb/drivers/adodb-informix72.inc.php
lib/adodb/drivers/adodb-ldap.inc.php
lib/adodb/drivers/adodb-mssql.inc.php
lib/adodb/drivers/adodb-mssqlnative.inc.php
lib/adodb/drivers/adodb-mssqlpo.inc.php
lib/adodb/drivers/adodb-mysql.inc.php
lib/adodb/drivers/adodb-mysqli.inc.php
lib/adodb/drivers/adodb-mysqlpo.inc.php
lib/adodb/drivers/adodb-mysqlt.inc.php
lib/adodb/drivers/adodb-netezza.inc.php
lib/adodb/drivers/adodb-oci8.inc.php
lib/adodb/drivers/adodb-oci805.inc.php
lib/adodb/drivers/adodb-oci8po.inc.php
lib/adodb/drivers/adodb-oci8quercus.inc.php
lib/adodb/drivers/adodb-odbc.inc.php
lib/adodb/drivers/adodb-odbc_db2.inc.php
lib/adodb/drivers/adodb-odbc_mssql.inc.php
lib/adodb/drivers/adodb-odbc_oracle.inc.php
lib/adodb/drivers/adodb-odbtp.inc.php
lib/adodb/drivers/adodb-odbtp_unicode.inc.php
lib/adodb/drivers/adodb-oracle.inc.php
lib/adodb/drivers/adodb-pdo.inc.php
lib/adodb/drivers/adodb-pdo_mssql.inc.php
lib/adodb/drivers/adodb-pdo_mysql.inc.php
lib/adodb/drivers/adodb-pdo_oci.inc.php
lib/adodb/drivers/adodb-pdo_pgsql.inc.php
lib/adodb/drivers/adodb-pdo_sqlite.inc.php
lib/adodb/drivers/adodb-postgres.inc.php
lib/adodb/drivers/adodb-postgres64.inc.php
lib/adodb/drivers/adodb-postgres7.inc.php
lib/adodb/drivers/adodb-postgres8.inc.php
lib/adodb/drivers/adodb-postgres9.inc.php
lib/adodb/drivers/adodb-proxy.inc.php
lib/adodb/drivers/adodb-sapdb.inc.php
lib/adodb/drivers/adodb-sqlanywhere.inc.php
lib/adodb/drivers/adodb-sqlite.inc.php
lib/adodb/drivers/adodb-sqlite3.inc.php
lib/adodb/drivers/adodb-sqlitepo.inc.php
lib/adodb/drivers/adodb-sybase.inc.php
lib/adodb/drivers/adodb-sybase_ase.inc.php
lib/adodb/drivers/adodb-vfp.inc.php
lib/adodb/perf/perf-db2.inc.php
lib/adodb/perf/perf-informix.inc.php
lib/adodb/perf/perf-mssql.inc.php
lib/adodb/perf/perf-mssqlnative.inc.php
lib/adodb/perf/perf-mysql.inc.php
lib/adodb/perf/perf-oci8.inc.php
lib/adodb/perf/perf-postgres.inc.php
lib/adodb/pivottable.inc.php
lib/adodb/readme_moodle.txt
lib/adodb/rsfilter.inc.php
lib/adodb/toexport.inc.php
lib/adodb/tohtml.inc.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-autocomplete.min.js.map
lib/amd/src/form-autocomplete.js
lib/antivirus/clamav/classes/scanner.php
lib/antivirus/clamav/lang/en/antivirus_clamav.php
lib/antivirus/clamav/settings.php
lib/antivirus/clamav/tests/scanner_test.php
lib/antivirus/clamav/version.php
lib/classes/antivirus/scanner_exception.php
lib/classes/component.php
lib/classes/lock/mysql_lock_factory.php [new file with mode: 0644]
lib/classes/message/manager.php
lib/classes/task/database_logger.php
lib/classes/task/file_temp_cleanup_task.php
lib/db/upgrade.php
lib/ddl/database_manager.php
lib/ddl/mssql_sql_generator.php
lib/ddl/mysql_sql_generator.php
lib/ddl/oracle_sql_generator.php
lib/ddl/postgres_sql_generator.php
lib/ddl/sql_generator.php
lib/ddl/tests/ddl_test.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/sqlite3_pdo_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/moodlelib.php
lib/phpunit/classes/util.php
lib/templates/form_autocomplete_selection.mustache
lib/templates/form_autocomplete_selection_items.mustache [new file with mode: 0644]
lib/templates/local/toast/wrapper.mustache
lib/tests/filterlib_test.php
lib/tests/lock_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
message/classes/api.php
message/externallib.php
message/lib.php
message/output/popup/amd/build/notification_popover_controller.min.js
message/output/popup/amd/build/notification_popover_controller.min.js.map
message/output/popup/amd/src/notification_popover_controller.js
message/output/popup/lib.php
message/output/popup/version.php
message/templates/message_popover.mustache
message/tests/behat/message_drawer_manage_contacts.feature
message/tests/externallib_test.php
mod/assign/styles.css
mod/forum/tests/behat/edit_post_student.feature
mod/forum/view.php
mod/quiz/mod_form.php
mod/quiz/settings.php
question/tests/behat/delete_question_activities.feature [new file with mode: 0644]
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/drawer.scss
theme/boost/scss/moodle/popover-region.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
version.php

index f3b1ac0..46d5706 100644 (file)
@@ -32,7 +32,7 @@ require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
 // systems, but let's allow room for expansion.
 core_php_time_limit::raise(300);
 
-$filter = optional_param('filter', '', PARAM_ALPHANUMEXT);
+$filter = optional_param('filter', '', PARAM_NOTAGS);
 $type = optional_param('type', false, PARAM_ALPHAEXT);
 $component = optional_param('component', '', PARAM_ALPHAEXT);
 
index 83c3a8d..f4e6537 100644 (file)
@@ -55,13 +55,13 @@ class tool_behat {
         // The loaded steps depends on the component specified.
         behat_config_manager::update_config_file($component, false);
 
-        // The Moodle\BehatExtension\HelpPrinter\MoodleDefinitionsPrinter will parse this search format.
+        // The Moodle\BehatExtension\Definition\Printer\ConsoleDefinitionInformationPrinter will parse this search format.
         if ($type) {
             $filter .= '&&' . $type;
         }
 
         if ($filter) {
-            $filteroption = ' -d "' . $filter . '"';
+            $filteroption = ' -d ' . escapeshellarg($filter);
         } else {
             $filteroption = ' -di';
         }
index 7de834a..864dc53 100644 (file)
@@ -44,13 +44,9 @@ class tool_behat_renderer extends plugin_renderer_base {
         global $CFG;
         require_once($CFG->libdir . '/behat/classes/behat_selectors.php');
 
-        $html = $this->generic_info();
-
-        // Form.
-        ob_start();
-        $form->display();
-        $html .= ob_get_contents();
-        ob_end_clean();
+        $html = $this->output->header();
+        $html .= $this->output->heading(get_string('pluginname', 'tool_behat'));
+        $html .= $form->render();
 
         if (empty($stepsdefinitions)) {
             $stepsdefinitions = get_string('nostepsdefinitions', 'tool_behat');
@@ -128,7 +124,9 @@ class tool_behat_renderer extends plugin_renderer_base {
      */
     public function render_error($msg) {
 
-        $html = $this->generic_info();
+        $html = $this->output->header();
+        $html .= $this->output->heading(get_string('pluginname', 'tool_behat'));
+        $html .= $this->generic_info();
 
         $a = new stdClass();
         $a->errormsg = $msg;
@@ -153,13 +151,7 @@ class tool_behat_renderer extends plugin_renderer_base {
      *
      * @return string
      */
-    protected function generic_info() {
-
-        $title = get_string('pluginname', 'tool_behat');
-
-        // Header.
-        $html = $this->output->header();
-        $html .= $this->output->heading($title);
+    public function generic_info() {
 
         // Info.
         $installurl = behat_command::DOCS_URL;
@@ -175,8 +167,7 @@ class tool_behat_renderer extends plugin_renderer_base {
         );
 
         // List of steps.
-        $html .= $this->output->box_start();
-        $html .= html_writer::tag('h3', get_string('infoheading', 'tool_behat'));
+        $html = $this->output->box_start();
         $html .= html_writer::tag('div', get_string('aim', 'tool_behat'));
         $html .= html_writer::start_tag('div');
         $html .= html_writer::start_tag('ul');
index 12bbee6..f9bef39 100644 (file)
@@ -40,8 +40,14 @@ class steps_definitions_form extends moodleform {
      * @return void
      */
     public function definition() {
+        global $PAGE;
 
         $mform = $this->_form;
+        $output = $PAGE->get_renderer('tool_behat');
+
+        $mform->addElement('header', 'info', get_string('infoheading', 'tool_behat'));
+        $mform->setExpanded('info', false);
+        $mform->addElement('html', $output->generic_info());
 
         $mform->addElement('header', 'filters', get_string('stepsdefinitionsfilters', 'tool_behat'));
 
index 8c5634a..c4fdb63 100644 (file)
@@ -1,25 +1,31 @@
-.steps-definitions {
-    border-style: solid;
-    border-width: 1px;
-    border-color: #bbb;
-    padding: 5px;
-    margin: auto;
-    width: 50%;
+#page-admin-tool-behat-index .steps-definitions {
+    margin: 1rem auto;
 }
 
-.steps-definitions .step {
-    margin: 10px 0 10px 0;
+#page-admin-tool-behat-index .steps-definitions .step {
+    margin: 1rem 0 0 0;
+    border: 1px solid #eee;
+    padding: 1rem;
 }
 
-.steps-definitions .stepdescription {
-    color: #bf8c12;
+#page-admin-tool-behat-index .steps-definitions .stepdescription {
+    font-style: italic;
 }
 
-.steps-definitions .steptype {
+#page-admin-tool-behat-index .steps-definitions .stepcontent {
+    margin: 1rem 0;
+}
+
+#page-admin-tool-behat-index .steps-definitions .steptype {
     color: #1467a6;
-    margin-right: 5px;
+    margin-right: 1ex;
+}
+
+#page-admin-tool-behat-index .steps-definitions .stepapipath {
+    font-family: monospace;
+    font-size: smaller;
 }
 
-.steps-definitions .stepregex {
+#page-admin-tool-behat-index .steps-definitions .stepregex {
     color: #060;
 }
index 9ad81f9..8e59cb8 100644 (file)
@@ -26,3 +26,11 @@ Feature: List the system steps definitions
     Given I set the field "Contains" to "homepage"
     When I press "Filter"
     Then I should see "Opens Moodle homepage."
+
+  @javascript
+  Scenario: Filtering by the multiple words pattern
+    Given I set the field "Contains" to "should exist"
+    When I press "Filter"
+    Then I should not see "There aren't steps definitions matching this filter"
+    And I should see "Checks the provided element and selector type exists in the current page."
+    And I should see "Checks that an element and selector type exists in another element and selector type on the current page."
index 751a75b..90aeb02 100644 (file)
@@ -1,4 +1,26 @@
 <?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/>.
+
+/**
+ * Bulk user actions
+ *
+ * @package    core
+ * @copyright  Moodle
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 
 require_once('../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
@@ -10,28 +32,23 @@ admin_externalpage_setup('userbulk');
 if (!isset($SESSION->bulk_users)) {
     $SESSION->bulk_users = array();
 }
-// create the user filter form
+// Create the user filter form.
 $ufiltering = new user_filtering();
 
-// array of bulk operations
-// create the bulk operations form
-$action_form = new user_bulk_action_form();
-if ($data = $action_form->get_data()) {
-    // check if an action should be performed and do so
-    switch ($data->action) {
-        case 1: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_confirm.php');
-        case 2: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_message.php');
-        case 3: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_delete.php');
-        case 4: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_display.php');
-        case 5: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_download.php');
-        case 7: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_forcepasswordchange.php');
-        case 8: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_cohortadd.php');
+// Create the bulk operations form.
+$actionform = new user_bulk_action_form();
+if ($data = $actionform->get_data()) {
+    // Check if an action should be performed and do so.
+    $bulkactions = $actionform->get_actions();
+    if (array_key_exists($data->action, $bulkactions)) {
+        redirect($bulkactions[$data->action]->url);
     }
+
 }
 
-$user_bulk_form = new user_bulk_form(null, get_selection_data($ufiltering));
+$userbulkform = new user_bulk_form(null, get_selection_data($ufiltering));
 
-if ($data = $user_bulk_form->get_data()) {
+if ($data = $userbulkform->get_data()) {
     if (!empty($data->addall)) {
         add_selection_all($ufiltering);
 
@@ -40,7 +57,7 @@ if ($data = $user_bulk_form->get_data()) {
             if (in_array(0, $data->ausers)) {
                 add_selection_all($ufiltering);
             } else {
-                foreach($data->ausers as $userid) {
+                foreach ($data->ausers as $userid) {
                     if ($userid == -1) {
                         continue;
                     }
@@ -52,14 +69,14 @@ if ($data = $user_bulk_form->get_data()) {
         }
 
     } else if (!empty($data->removeall)) {
-        $SESSION->bulk_users= array();
+        $SESSION->bulk_users = array();
 
     } else if (!empty($data->removesel)) {
         if (!empty($data->susers)) {
             if (in_array(0, $data->susers)) {
-                $SESSION->bulk_users= array();
+                $SESSION->bulk_users = array();
             } else {
-                foreach($data->susers as $userid) {
+                foreach ($data->susers as $userid) {
                     if ($userid == -1) {
                         continue;
                     }
@@ -69,18 +86,17 @@ if ($data = $user_bulk_form->get_data()) {
         }
     }
 
-    // reset the form selections
+    // Reset the form selections.
     unset($_POST);
-    $user_bulk_form = new user_bulk_form(null, get_selection_data($ufiltering));
+    $userbulkform = new user_bulk_form(null, get_selection_data($ufiltering));
 }
-// do output
 echo $OUTPUT->header();
 
 $ufiltering->display_add();
 $ufiltering->display_active();
 
-$user_bulk_form->display();
+$userbulkform->display();
 
-$action_form->display();
+$actionform->display();
 
 echo $OUTPUT->footer();
index ee71a22..2dcf4c5 100644 (file)
 <?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/>.
+
+/**
+ * Bulk user action forms
+ *
+ * @package    core
+ * @copyright  Moodle
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
 
 require_once($CFG->libdir.'/formslib.php');
 require_once($CFG->libdir.'/datalib.php');
 
+/**
+ * Bulk user action form
+ *
+ * @copyright  Moodle
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class user_bulk_action_form extends moodleform {
-    function definition() {
-        global $CFG;
 
-        $mform =& $this->_form;
+    /**
+     * Returns an array of action_link's of all bulk actions available for this user.
+     *
+     * @return array of action_link objects
+     */
+    public function get_actions(): array {
+
+        global $CFG;
 
         $syscontext = context_system::instance();
-        $actions = array(0=>get_string('choose').'...');
+        $actions = [];
         if (has_capability('moodle/user:update', $syscontext)) {
-            $actions[1] = get_string('confirm');
+            $actions['confirm'] = new action_link(
+                new moodle_url('/admin/user/user_bulk_confirm.php'),
+                get_string('confirm'));
         }
         if (has_capability('moodle/site:readallmessages', $syscontext) && !empty($CFG->messaging)) {
-            $actions[2] = get_string('messageselectadd');
+            $actions['message'] = new action_link(
+                new moodle_url('/admin/user/user_bulk_message.php'),
+                get_string('messageselectadd'));
         }
         if (has_capability('moodle/user:delete', $syscontext)) {
-            $actions[3] = get_string('delete');
+            $actions['delete'] = new action_link(
+                new moodle_url('/admin/user/user_bulk_delete.php'),
+                get_string('delete'));
         }
-        $actions[4] = get_string('displayonpage');
+        $actions['displayonpage'] = new action_link(
+                new moodle_url('/admin/user/user_bulk_display.php'),
+                get_string('displayonpage'));
+
         if (has_capability('moodle/user:update', $syscontext)) {
-            $actions[5] = get_string('download', 'admin');
+            $actions['download'] = new action_link(
+                new moodle_url('/admin/user/user_bulk_download.php'),
+                get_string('download', 'admin'));
         }
+
         if (has_capability('moodle/user:update', $syscontext)) {
-            $actions[7] = get_string('forcepasswordchange');
+            $actions['forcepasswordchange'] = new action_link(
+                new moodle_url('/admin/user/user_bulk_forcepasswordchange.php'),
+                get_string('forcepasswordchange'));
         }
         if (has_capability('moodle/cohort:assign', $syscontext)) {
-            $actions[8] = get_string('bulkadd', 'core_cohort');
+            $actions['addtocohort'] = new action_link(
+                new moodle_url('/admin/user/user_bulk_cohortadd.php'),
+                get_string('bulkadd', 'core_cohort'));
+        }
+
+        // Any plugin can append actions to this list by implementing a callback
+        // <component>_bulk_user_actions() which returns an array of action_link.
+        // Each new action's key should have a frankenstyle prefix to avoid clashes.
+        // See MDL-38511 for more details.
+        $moreactions = get_plugins_with_function('bulk_user_actions', 'lib.php');
+        foreach ($moreactions as $plugintype => $plugins) {
+            foreach ($plugins as $pluginfunction) {
+                $actions += $pluginfunction();
+            }
+        }
+
+        return $actions;
+
+    }
+
+    /**
+     * Form definition
+     */
+    public function definition() {
+        global $CFG;
+
+        $mform =& $this->_form;
+
+        $actions = [0 => get_string('choose') . '...'];
+        $bulkactions = $this->get_actions();
+        foreach ($bulkactions as $key => $action) {
+            $actions[$key] = $action->text;
         }
         $objs = array();
         $objs[] =& $mform->createElement('select', 'action', null, $actions);
@@ -37,8 +117,18 @@ class user_bulk_action_form extends moodleform {
     }
 }
 
+/**
+ * Bulk user form
+ *
+ * @copyright  Moodle
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class user_bulk_form extends moodleform {
-    function definition() {
+
+    /**
+     * Form definition
+     */
+    public function definition() {
 
         $mform =& $this->_form;
         $acount =& $this->_customdata['acount'];
@@ -92,7 +182,6 @@ class user_bulk_form extends moodleform {
         $objs[1] =& $mform->createElement('select', 'susers', get_string('selected', 'bulkusers'), $schoices, 'size="15"');
         $objs[1]->setMultiple(true);
 
-
         $grp =& $mform->addElement('group', 'usersgrp', get_string('users', 'bulkusers'), $objs, ' ', false);
         $mform->addHelpButton('usersgrp', 'users', 'bulkusers');
 
index 600f8f8..ba22662 100644 (file)
@@ -4802,6 +4802,40 @@ class api {
         $DB->delete_records(template_cohort::TABLE, array('cohortid' => $cohort->id));
     }
 
+    /**
+     * Action to perform when a user is deleted.
+     *
+     * @param int $userid The user id.
+     */
+    public static function hook_user_deleted($userid) {
+        global $DB;
+
+        $usercompetencies = $DB->get_records(user_competency::TABLE, ['userid' => $userid], '', 'id');
+        foreach ($usercompetencies as $usercomp) {
+            $DB->delete_records(evidence::TABLE, ['usercompetencyid' => $usercomp->id]);
+        }
+
+        $DB->delete_records(user_competency::TABLE, ['userid' => $userid]);
+        $DB->delete_records(user_competency_course::TABLE, ['userid' => $userid]);
+        $DB->delete_records(user_competency_plan::TABLE, ['userid' => $userid]);
+
+        // Delete any associated files.
+        $fs = get_file_storage();
+        $context = context_user::instance($userid);
+        $userevidences = $DB->get_records(user_evidence::TABLE, ['userid' => $userid], '', 'id');
+        foreach ($userevidences as $userevidence) {
+            $DB->delete_records(user_evidence_competency::TABLE, ['userevidenceid' => $userevidence->id]);
+            $DB->delete_records(user_evidence::TABLE, ['id' => $userevidence->id]);
+            $fs->delete_area_files($context->id, 'core_competency', 'userevidence', $userevidence->id);
+        }
+
+        $userplans = $DB->get_records(plan::TABLE, ['userid' => $userid], '', 'id');
+        foreach ($userplans as $userplan) {
+            $DB->delete_records(plan_competency::TABLE, ['planid' => $userplan->id]);
+            $DB->delete_records(plan::TABLE, ['id' => $userplan->id]);
+        }
+    }
+
     /**
      * Manually grade a user competency.
      *
index e8be7c4..c1890ea 100644 (file)
@@ -207,4 +207,47 @@ class core_competency_hooks_testcase extends advanced_testcase {
         $this->assertEquals(1, \core_competency\template_cohort::count_records(array('templateid' => $t1->get('id'))));
         $this->assertEquals(0, \core_competency\template_cohort::count_records(array('templateid' => $t2->get('id'))));
     }
+
+    public function test_hook_user_deleted() {
+        $this->resetAfterTest();
+        $dg = $this->getDataGenerator();
+        $ccg = $dg->get_plugin_generator('core_competency');
+
+        $u1 = $dg->create_user();
+
+        $framework = $ccg->create_framework();
+        $comp1 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]);
+        $comp2 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]);
+
+        $c1 = $dg->create_course();
+        $cc1a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id]);
+        $cc1b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id]);
+        $assign1a = $dg->create_module('assign', ['course' => $c1]);
+        $assign1b = $dg->create_module('assign', ['course' => $c1]);
+        $cmc1a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]);
+        $cmc1b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]);
+        $ucc1a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id,
+            'userid' => $u1->id]);
+        $ucc1b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id,
+            'userid' => $u1->id]);
+
+        $c2 = $dg->create_course();
+        $cc2a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id]);
+        $cc2b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id]);
+        $assign2a = $dg->create_module('assign', ['course' => $c2]);
+        $assign2b = $dg->create_module('assign', ['course' => $c2]);
+        $cmc2a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2a->cmid]);
+        $cmc2b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2b->cmid]);
+        $ucc2a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id,
+            'userid' => $u1->id]);
+        $ucc2b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id,
+            'userid' => $u1->id]);
+
+        reset_course_userdata((object) ['id' => $c1->id, 'reset_competency_ratings' => true]);
+
+        delete_user($u1);
+
+        // Assert the records don't exist anymore.
+        $this->assertEquals(0, user_competency_course::count_records(['courseid' => $c1->id, 'userid' => $u1->id]));
+    }
 }
index 484a7d7..5da7105 100644 (file)
@@ -521,9 +521,9 @@ $CFG->admin = 'admin';
 //      $CFG->supportuserid = -20;
 //
 // Moodle 2.7 introduces a locking api for critical tasks (e.g. cron).
-// The default locking system to use is DB locking for Postgres, and file locking for
-// MySQL, Oracle and SQLServer. If $CFG->preventfilelocking is set, then the default
-// will always be DB locking. It can be manually set to one of the lock
+// The default locking system to use is DB locking for Postgres, MySQL, MariaDB and
+// file locking for Oracle and SQLServer. If $CFG->preventfilelocking is set, then the
+// default will always be DB locking. It can be manually set to one of the lock
 // factory classes listed below, or one of your own custom classes implementing the
 // \core\lock\lock_factory interface.
 //
@@ -537,6 +537,8 @@ $CFG->admin = 'admin';
 //
 // "\\core\\lock\\db_record_lock_factory" - DB locking based on table rows.
 //
+// "\\core\\lock\\mysql_lock_factory" - DB locking based on MySQL / MariaDB locks.
+//
 // "\\core\\lock\\postgres_lock_factory" - DB locking based on postgres advisory locks.
 //
 // Settings used by the lock factories
index d7badc8..dede44d 100644 (file)
@@ -68,6 +68,17 @@ class course_handler extends \core_customfield\handler {
         return self::$singleton;
     }
 
+    /**
+     * Run reset code after unit tests to reset the singleton usage.
+     */
+    public static function reset_caches(): void {
+        if (!PHPUNIT_TEST) {
+            throw new \coding_exception('This feature is only intended for use in unit tests');
+        }
+
+        static::$singleton = null;
+    }
+
     /**
      * The current user can configure custom fields on this component.
      *
index a33ecad..8adef40 100644 (file)
@@ -486,7 +486,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
 
         // Output section activities summary:
         $o = '';
-        $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities mdl-right'));
+        $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities pr-2 mdl-right'));
         foreach ($sectionmods as $mod) {
             $o.= html_writer::start_tag('span', array('class' => 'activity-count'));
             $o.= $mod['name'].': '.$mod['count'];
@@ -500,7 +500,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             $a->complete = $complete;
             $a->total = $total;
 
-            $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities mdl-right'));
+            $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities pr-2 mdl-right'));
             $o.= html_writer::tag('span', get_string('progresstotal', 'completion', $a), array('class' => 'activity-count'));
             $o.= html_writer::end_tag('div');
         }
index 9eca03f..1c6dd3a 100644 (file)
@@ -1151,7 +1151,9 @@ function course_delete_module($cmid, $async = false) {
     }
 
     // Delete activity context questions and question categories.
-    question_delete_activity($cm);
+    $showinfo = !defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0';
+
+    question_delete_activity($cm, $showinfo);
 
     // Call the delete_instance function, if it returns false throw an exception.
     if (!$deleteinstancefunction($cm->instance)) {
index b00cf76..34c25f0 100644 (file)
@@ -1179,7 +1179,7 @@ abstract class moodleform_mod extends moodleform {
     }
 
     /**
-     * Get the list of admin settings for this module and apply any defaults/advanced/locked settings.
+     * Get the list of admin settings for this module and apply any defaults/advanced/locked/required settings.
      *
      * @param $datetimeoffsets array - If passed, this is an array of fieldnames => times that the
      *                         default date/time value should be relative to. If not passed, all
@@ -1221,6 +1221,10 @@ abstract class moodleform_mod extends moodleform {
                 if (!empty($settings->$advancedsetting)) {
                     $mform->setAdvanced($name);
                 }
+                $requiredsetting = $name . '_required';
+                if (!empty($settings->$requiredsetting)) {
+                    $mform->addRule($name, null, 'required', null, 'client');
+                }
             }
         }
     }
index 2aa96e1..e76d410 100644 (file)
@@ -38,25 +38,11 @@ use \core_customfield\category_controller;
 class core_customfield_api_testcase extends advanced_testcase {
 
     /**
-     * This method is called after the last test of this test class is run.
-     */
-    public static function tearDownAfterClass() {
-        $handler = core_course\customfield\course_handler::create();
-        $handler->delete_all();
-    }
-
-    /**
-     * Tests set up.
-     */
-    public function setUp() {
-        $this->resetAfterTest();
-    }
-
-    /**
-     * Get generator
+     * Get generator.
+     *
      * @return core_customfield_generator
      */
-    protected function get_generator() : core_customfield_generator {
+    protected function get_generator(): core_customfield_generator {
         return $this->getDataGenerator()->get_plugin_generator('core_customfield');
     }
 
@@ -65,7 +51,7 @@ class core_customfield_api_testcase extends advanced_testcase {
      *
      * @param array $expected
      * @param array $array array of objects with "get($property)" method
-     * @param sring $propertyname
+     * @param string $propertyname
      */
     protected function assert_property_in_array($expected, $array, $propertyname) {
         $this->assertEquals($expected, array_values(array_map(function($a) use ($propertyname) {
@@ -80,6 +66,8 @@ class core_customfield_api_testcase extends advanced_testcase {
      * in the interface using drag-drop.
      */
     public function test_move_category() {
+        $this->resetAfterTest();
+
         // Create the categories.
         $params = ['component' => 'core_course', 'area' => 'course', 'itemid' => 0];
         $id0 = $this->get_generator()->create_category($params)->get('id');
@@ -129,6 +117,8 @@ class core_customfield_api_testcase extends advanced_testcase {
      * Tests for \core_customfield\api::get_categories_with_fields() behaviour.
      */
     public function test_get_categories_with_fields() {
+        $this->resetAfterTest();
+
         // Create the categories.
         $options = [
             'component' => 'core_course',
@@ -163,6 +153,8 @@ class core_customfield_api_testcase extends advanced_testcase {
      * Test for functions api::save_category() and rename_category)
      */
     public function test_save_category() {
+        $this->resetAfterTest();
+
         $params = ['component' => 'core_course', 'area' => 'course', 'itemid' => 0, 'name' => 'Cat1',
             'contextid' => context_system::instance()->id];
         $c1 = category_controller::create(0, (object)$params);
@@ -196,6 +188,8 @@ class core_customfield_api_testcase extends advanced_testcase {
      * Test for function handler::create_category
      */
     public function test_create_category() {
+        $this->resetAfterTest();
+
         $handler = \core_course\customfield\course_handler::create();
         $c1id = $handler->create_category();
         $c1 = $handler->get_categories_with_fields()[$c1id];
@@ -218,6 +212,8 @@ class core_customfield_api_testcase extends advanced_testcase {
      * Tests for \core_customfield\api::delete_category() behaviour.
      */
     public function test_delete_category_with_fields() {
+        $this->resetAfterTest();
+
         global $DB;
         // Create two categories with fields and data.
         $options = [
index 56aa2d0..04ce151 100644 (file)
@@ -37,29 +37,20 @@ use \core_customfield\field_controller;
 class core_customfield_category_controller_testcase extends advanced_testcase {
 
     /**
-     * This method is called after the last test of this test class is run.
+     * Get generator.
+     *
+     * @return core_customfield_generator
      */
-    public static function tearDownAfterClass() {
-        $handler = core_course\customfield\course_handler::create();
-        $handler->delete_all();
+    protected function get_generator(): core_customfield_generator {
+        return $this->getDataGenerator()->get_plugin_generator('core_customfield');
     }
 
     /**
-     * Tests set up.
+     * Test for the field_controller::__construct function.
      */
-    public function setUp() {
+    public function test_constructor() {
         $this->resetAfterTest();
-    }
-
-    /**
-     * Get generator
-     * @return core_customfield_generator
-     */
-    protected function get_generator() : core_customfield_generator {
-        return $this->getDataGenerator()->get_plugin_generator('core_customfield');
-    }
 
-    public function test_constructor() {
         $c = category_controller::create(0, (object)['component' => 'core_course', 'area' => 'course', 'itemid' => 0]);
         $handler = $c->get_handler();
         $this->assertTrue($c instanceof category_controller);
@@ -83,6 +74,8 @@ class core_customfield_category_controller_testcase extends advanced_testcase {
      */
     public function test_constructor_errors() {
         global $DB;
+        $this->resetAfterTest();
+
         $cat = $this->get_generator()->create_category();
         $catrecord = $cat->to_record();
 
@@ -181,6 +174,7 @@ class core_customfield_category_controller_testcase extends advanced_testcase {
      * \core_customfield\category_controller::get()
      */
     public function test_create_category() {
+        $this->resetAfterTest();
 
         // Create the category.
         $lpg = $this->get_generator();
@@ -209,6 +203,8 @@ class core_customfield_category_controller_testcase extends advanced_testcase {
      * Tests for \core_customfield\category_controller::set() behaviour.
      */
     public function test_rename_category() {
+        $this->resetAfterTest();
+
         // Create the category.
         $params = ['component' => 'core_course', 'area' => 'course', 'itemid' => 0, 'name' => 'Cat1',
             'contextid' => context_system::instance()->id];
@@ -232,6 +228,8 @@ class core_customfield_category_controller_testcase extends advanced_testcase {
      * Tests for \core_customfield\category_controller::delete() behaviour.
      */
     public function test_delete_category() {
+        $this->resetAfterTest();
+
         // Create the category.
         $lpg = $this->get_generator();
         $category0 = $lpg->create_category();
index 7cf0b7e..d125c5c 100644 (file)
@@ -35,25 +35,11 @@ use core_customfield\data_controller;
 class core_customfield_data_controller_testcase extends advanced_testcase {
 
     /**
-     * This method is called after the last test of this test class is run.
-     */
-    public static function tearDownAfterClass() {
-        $handler = core_course\customfield\course_handler::create();
-        $handler->delete_all();
-    }
-
-    /**
-     * Tests set up.
-     */
-    public function setUp() {
-        $this->resetAfterTest();
-    }
-
-    /**
-     * Get generator
+     * Get generator.
+     *
      * @return core_customfield_generator
      */
-    protected function get_generator() : core_customfield_generator {
+    protected function get_generator(): core_customfield_generator {
         return $this->getDataGenerator()->get_plugin_generator('core_customfield');
     }
 
@@ -62,6 +48,8 @@ class core_customfield_data_controller_testcase extends advanced_testcase {
      */
     public function test_constructor() {
         global $DB;
+        $this->resetAfterTest();
+
         // Create a course, fields category and fields.
         $course = $this->getDataGenerator()->create_course();
         $category0 = $this->get_generator()->create_category(['name' => 'aaaa']);
@@ -130,6 +118,8 @@ class core_customfield_data_controller_testcase extends advanced_testcase {
      */
     public function test_constructor_errors() {
         global $DB;
+        $this->resetAfterTest();
+
         // Create a category, field and data.
         $category = $this->get_generator()->create_category();
         $field = $this->get_generator()->create_field(['categoryid' => $category->get('id')]);
@@ -188,4 +178,4 @@ class core_customfield_data_controller_testcase extends advanced_testcase {
             $this->assertEquals(moodle_exception::class, get_class($e));
         }
     }
-}
\ No newline at end of file
+}
index e490ef1..a642919 100644 (file)
@@ -39,25 +39,11 @@ use \core_customfield\field_controller;
 class core_customfield_field_controller_testcase extends advanced_testcase {
 
     /**
-     * This method is called after the last test of this test class is run.
-     */
-    public static function tearDownAfterClass() {
-        $handler = core_course\customfield\course_handler::create();
-        $handler->delete_all();
-    }
-
-    /**
-     * Tests set up.
-     */
-    public function setUp() {
-        $this->resetAfterTest();
-    }
-
-    /**
-     * Get generator
+     * Get generator.
+     *
      * @return core_customfield_generator
      */
-    protected function get_generator() : core_customfield_generator {
+    protected function get_generator(): core_customfield_generator {
         return $this->getDataGenerator()->get_plugin_generator('core_customfield');
     }
 
@@ -66,6 +52,8 @@ class core_customfield_field_controller_testcase extends advanced_testcase {
      */
     public function test_constructor() {
         global $DB;
+        $this->resetAfterTest();
+
         // Create the category.
         $category0 = $this->get_generator()->create_category();
 
@@ -111,6 +99,8 @@ class core_customfield_field_controller_testcase extends advanced_testcase {
      */
     public function test_constructor_errors() {
         global $DB;
+        $this->resetAfterTest();
+
         // Create a category and a field.
         $category = $this->get_generator()->create_category();
         $field = $this->get_generator()->create_field(['categoryid' => $category->get('id')]);
@@ -183,6 +173,8 @@ class core_customfield_field_controller_testcase extends advanced_testcase {
      */
     public function test_create_field() {
         global $DB;
+        $this->resetAfterTest();
+
         $lpg = $this->get_generator();
         $category = $lpg->create_category();
         $fields = $DB->get_records(\core_customfield\field::TABLE, ['categoryid' => $category->get('id')]);
@@ -211,6 +203,8 @@ class core_customfield_field_controller_testcase extends advanced_testcase {
      */
     public function test_delete_field() {
         global $DB;
+        $this->resetAfterTest();
+
         $lpg = $this->get_generator();
         $category = $lpg->create_category();
         $fields = $DB->get_records(\core_customfield\field::TABLE, ['categoryid' => $category->get('id')]);
@@ -237,6 +231,8 @@ class core_customfield_field_controller_testcase extends advanced_testcase {
      * Tests for \core_customfield\field_controller::get_configdata_property() behaviour.
      */
     public function test_get_configdata_property() {
+        $this->resetAfterTest();
+
         $lpg = $this->get_generator();
         $category = $lpg->create_category();
         $configdata = ['a' => 'b', 'c' => ['d', 'e']];
@@ -251,4 +247,4 @@ class core_customfield_field_controller_testcase extends advanced_testcase {
         $this->assertEquals(['d', 'e'], $field->get_configdata_property('c'));
         $this->assertEquals(null, $field->get_configdata_property('x'));
     }
-}
\ No newline at end of file
+}
index 059c868..c5335c3 100644 (file)
@@ -35,19 +35,11 @@ defined('MOODLE_INTERNAL') || die();
  */
 class core_customfield_generator_testcase extends advanced_testcase {
 
-    /**
-     * This method is called after the last test of this test class is run.
-     */
-    public static function tearDownAfterClass() {
-        $handler = core_course\customfield\course_handler::create();
-        $handler->delete_all();
-    }
-
     /**
      * Get generator
      * @return core_customfield_generator
      */
-    protected function get_generator() : core_customfield_generator {
+    protected function get_generator(): core_customfield_generator {
         return $this->getDataGenerator()->get_plugin_generator('core_customfield');
     }
 
index 69ea102..4a63085 100644 (file)
@@ -38,71 +38,59 @@ use core_customfield\privacy\provider;
  */
 class core_customfield_privacy_testcase extends provider_testcase {
 
-    /** @var stdClass[]  */
-    private $courses = [];
-    /** @var \core_customfield\category_controller[] */
-    private $cfcats = [];
-    /** @var \core_customfield\field_controller[] */
-    private $cffields = [];
-
-    /**
-     * This method is called after the last test of this test class is run.
-     */
-    public static function tearDownAfterClass() {
-        $handler = core_course\customfield\course_handler::create();
-        $handler->delete_all();
-    }
-
     /**
-     * Set up
+     * Generate data.
+     *
+     * @return array
      */
-    public function setUp() {
+    protected function generate_test_data(): array {
         $this->resetAfterTest();
 
-        $this->cfcats[1] = $this->get_generator()->create_category();
-        $this->cfcats[2] = $this->get_generator()->create_category();
-        $this->cffields[11] = $this->get_generator()->create_field(
-            ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'checkbox']);
-        $this->cffields[12] = $this->get_generator()->create_field(
-            ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'date']);
-        $this->cffields[13] = $this->get_generator()->create_field(
-            ['categoryid' => $this->cfcats[1]->get('id'),
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
+        $cfcats[1] = $generator->create_category();
+        $cfcats[2] = $generator->create_category();
+        $cffields[11] = $generator->create_field(
+            ['categoryid' => $cfcats[1]->get('id'), 'type' => 'checkbox']);
+        $cffields[12] = $generator->create_field(
+            ['categoryid' => $cfcats[1]->get('id'), 'type' => 'date']);
+        $cffields[13] = $generator->create_field(
+            ['categoryid' => $cfcats[1]->get('id'),
             'type' => 'select', 'configdata' => ['options' => "a\nb\nc"]]);
-        $this->cffields[14] = $this->get_generator()->create_field(
-            ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'text']);
-        $this->cffields[15] = $this->get_generator()->create_field(
-            ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'textarea']);
-        $this->cffields[21] = $this->get_generator()->create_field(
-            ['categoryid' => $this->cfcats[2]->get('id')]);
-        $this->cffields[22] = $this->get_generator()->create_field(
-            ['categoryid' => $this->cfcats[2]->get('id')]);
-
-        $this->courses[1] = $this->getDataGenerator()->create_course();
-        $this->courses[2] = $this->getDataGenerator()->create_course();
-        $this->courses[3] = $this->getDataGenerator()->create_course();
-
-        $this->get_generator()->add_instance_data($this->cffields[11], $this->courses[1]->id, 1);
-        $this->get_generator()->add_instance_data($this->cffields[12], $this->courses[1]->id, 1546300800);
-        $this->get_generator()->add_instance_data($this->cffields[13], $this->courses[1]->id, 2);
-        $this->get_generator()->add_instance_data($this->cffields[14], $this->courses[1]->id, 'Hello1');
-        $this->get_generator()->add_instance_data($this->cffields[15], $this->courses[1]->id,
+        $cffields[14] = $generator->create_field(
+            ['categoryid' => $cfcats[1]->get('id'), 'type' => 'text']);
+        $cffields[15] = $generator->create_field(
+            ['categoryid' => $cfcats[1]->get('id'), 'type' => 'textarea']);
+        $cffields[21] = $generator->create_field(
+            ['categoryid' => $cfcats[2]->get('id')]);
+        $cffields[22] = $generator->create_field(
+            ['categoryid' => $cfcats[2]->get('id')]);
+
+        $courses[1] = $this->getDataGenerator()->create_course();
+        $courses[2] = $this->getDataGenerator()->create_course();
+        $courses[3] = $this->getDataGenerator()->create_course();
+
+        $generator->add_instance_data($cffields[11], $courses[1]->id, 1);
+        $generator->add_instance_data($cffields[12], $courses[1]->id, 1546300800);
+        $generator->add_instance_data($cffields[13], $courses[1]->id, 2);
+        $generator->add_instance_data($cffields[14], $courses[1]->id, 'Hello1');
+        $generator->add_instance_data($cffields[15], $courses[1]->id,
             ['text' => '<p>Hi there</p>', 'format' => FORMAT_HTML]);
 
-        $this->get_generator()->add_instance_data($this->cffields[21], $this->courses[1]->id, 'hihi1');
+        $generator->add_instance_data($cffields[21], $courses[1]->id, 'hihi1');
 
-        $this->get_generator()->add_instance_data($this->cffields[14], $this->courses[2]->id, 'Hello2');
+        $generator->add_instance_data($cffields[14], $courses[2]->id, 'Hello2');
 
-        $this->get_generator()->add_instance_data($this->cffields[21], $this->courses[2]->id, 'hihi2');
+        $generator->add_instance_data($cffields[21], $courses[2]->id, 'hihi2');
 
-        $this->setUser($this->getDataGenerator()->create_user());
-    }
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
 
-    /**
-     * Get generator
-     * @return core_customfield_generator
-     */
-    protected function get_generator() : core_customfield_generator {
-        return $this->getDataGenerator()->get_plugin_generator('core_customfield');
+        return [
+            'user' => $user,
+            'cfcats' => $cfcats,
+            'cffields' => $cffields,
+            'courses' => $courses,
+        ];
     }
 
     /**
@@ -119,11 +107,17 @@ class core_customfield_privacy_testcase extends provider_testcase {
      */
     public function test_get_customfields_data_contexts() {
         global $DB;
-        list($sql, $params) = $DB->get_in_or_equal([$this->courses[1]->id, $this->courses[2]->id], SQL_PARAMS_NAMED);
+        [
+            'cffields' => $cffields,
+            'cfcats' => $cfcats,
+            'courses' => $courses,
+        ] = $this->generate_test_data();
+
+        list($sql, $params) = $DB->get_in_or_equal([$courses[1]->id, $courses[2]->id], SQL_PARAMS_NAMED);
         $r = provider::get_customfields_data_contexts('core_course', 'course', '=0',
             $sql, $params);
-        $this->assertEquals([context_course::instance($this->courses[1]->id)->id,
-            context_course::instance($this->courses[2]->id)->id],
+        $this->assertEquals([context_course::instance($courses[1]->id)->id,
+            context_course::instance($courses[2]->id)->id],
             $r->get_contextids(), '', 0, 10, true);
     }
 
@@ -131,6 +125,8 @@ class core_customfield_privacy_testcase extends provider_testcase {
      * Test for provider::get_customfields_configuration_contexts()
      */
     public function test_get_customfields_configuration_contexts() {
+        $this->generate_test_data();
+
         $r = provider::get_customfields_configuration_contexts('core_course', 'course');
         $this->assertEquals([context_system::instance()->id], $r->get_contextids());
     }
@@ -140,13 +136,20 @@ class core_customfield_privacy_testcase extends provider_testcase {
      */
     public function test_export_customfields_data() {
         global $USER, $DB;
+        $this->resetAfterTest();
+        [
+            'cffields' => $cffields,
+            'cfcats' => $cfcats,
+            'courses' => $courses,
+        ] = $this->generate_test_data();
+
         // Hack one of the fields so it has an invalid field type.
-        $invalidfieldid = $this->cffields[21]->get('id');
+        $invalidfieldid = $cffields[21]->get('id');
         $DB->update_record('customfield_field', ['id' => $invalidfieldid, 'type' => 'invalid']);
 
-        $context = context_course::instance($this->courses[1]->id);
+        $context = context_course::instance($courses[1]->id);
         $contextlist = new approved_contextlist($USER, 'core_customfield', [$context->id]);
-        provider::export_customfields_data($contextlist, 'core_course', 'course', '=0', '=:i', ['i' => $this->courses[1]->id]);
+        provider::export_customfields_data($contextlist, 'core_course', 'course', '=0', '=:i', ['i' => $courses[1]->id]);
         /** @var core_privacy\tests\request\content_writer $writer */
         $writer = writer::with_context($context);
 
@@ -155,7 +158,7 @@ class core_customfield_privacy_testcase extends provider_testcase {
         $invaldfieldischecked = false;
         foreach ($DB->get_records('customfield_data', []) as $dbrecord) {
             $data = $writer->get_data(['Custom fields data', $dbrecord->id]);
-            if ($dbrecord->instanceid == $this->courses[1]->id) {
+            if ($dbrecord->instanceid == $courses[1]->id) {
                 $this->assertEquals($dbrecord->fieldid, $data->fieldid);
                 $this->assertNotEmpty($data->fieldtype);
                 $this->assertNotEmpty($data->fieldshortname);
@@ -175,10 +178,17 @@ class core_customfield_privacy_testcase extends provider_testcase {
      */
     public function test_delete_customfields_data() {
         global $USER, $DB;
-        $approvedcontexts = new approved_contextlist($USER, 'core_course', [context_course::instance($this->courses[1]->id)->id]);
+        $this->resetAfterTest();
+        [
+            'cffields' => $cffields,
+            'cfcats' => $cfcats,
+            'courses' => $courses,
+        ] = $this->generate_test_data();
+
+        $approvedcontexts = new approved_contextlist($USER, 'core_course', [context_course::instance($courses[1]->id)->id]);
         provider::delete_customfields_data($approvedcontexts, 'core_course', 'course');
-        $this->assertEmpty($DB->get_records('customfield_data', ['instanceid' => $this->courses[1]->id]));
-        $this->assertNotEmpty($DB->get_records('customfield_data', ['instanceid' => $this->courses[2]->id]));
+        $this->assertEmpty($DB->get_records('customfield_data', ['instanceid' => $courses[1]->id]));
+        $this->assertNotEmpty($DB->get_records('customfield_data', ['instanceid' => $courses[2]->id]));
     }
 
     /**
@@ -186,9 +196,16 @@ class core_customfield_privacy_testcase extends provider_testcase {
      */
     public function test_delete_customfields_configuration() {
         global $USER, $DB;
+        $this->resetAfterTest();
+        [
+            'cffields' => $cffields,
+            'cfcats' => $cfcats,
+            'courses' => $courses,
+        ] = $this->generate_test_data();
+
         // Remember the list of fields in the category 2 before we delete it.
-        $catid1 = $this->cfcats[1]->get('id');
-        $catid2 = $this->cfcats[2]->get('id');
+        $catid1 = $cfcats[1]->get('id');
+        $catid2 = $cfcats[2]->get('id');
         $fids2 = $DB->get_fieldset_select('customfield_field', 'id', 'categoryid=?', [$catid2]);
         $this->assertNotEmpty($fids2);
         list($fsql, $fparams) = $DB->get_in_or_equal($fids2, SQL_PARAMS_NAMED);
@@ -216,9 +233,16 @@ class core_customfield_privacy_testcase extends provider_testcase {
      */
     public function test_delete_customfields_configuration_for_context() {
         global $USER, $DB;
+        $this->resetAfterTest();
+        [
+            'cffields' => $cffields,
+            'cfcats' => $cfcats,
+            'courses' => $courses,
+        ] = $this->generate_test_data();
+
         // Remember the list of fields in the category 2 before we delete it.
-        $catid1 = $this->cfcats[1]->get('id');
-        $catid2 = $this->cfcats[2]->get('id');
+        $catid1 = $cfcats[1]->get('id');
+        $catid2 = $cfcats[2]->get('id');
         $fids2 = $DB->get_fieldset_select('customfield_field', 'id', 'categoryid=?', [$catid2]);
         $this->assertNotEmpty($fids2);
         list($fsql, $fparams) = $DB->get_in_or_equal($fids2, SQL_PARAMS_NAMED);
@@ -246,12 +270,19 @@ class core_customfield_privacy_testcase extends provider_testcase {
      */
     public function test_delete_customfields_data_for_context() {
         global $DB;
+        $this->resetAfterTest();
+        [
+            'cffields' => $cffields,
+            'cfcats' => $cfcats,
+            'courses' => $courses,
+        ] = $this->generate_test_data();
+
         provider::delete_customfields_data_for_context('core_course', 'course',
-            context_course::instance($this->courses[1]->id));
+            context_course::instance($courses[1]->id));
         $fids2 = $DB->get_fieldset_select('customfield_field', 'id', '1=1', []);
         list($fsql, $fparams) = $DB->get_in_or_equal($fids2, SQL_PARAMS_NAMED);
-        $fparams['course1'] = $this->courses[1]->id;
-        $fparams['course2'] = $this->courses[2]->id;
+        $fparams['course1'] = $courses[1]->id;
+        $fparams['course2'] = $courses[2]->id;
         $this->assertEmpty($DB->get_records_select('customfield_data', 'instanceid = :course1 AND fieldid ' . $fsql, $fparams));
         $this->assertNotEmpty($DB->get_records_select('customfield_data', 'instanceid = :course2 AND fieldid ' . $fsql, $fparams));
     }
index 78c43d2..b444007 100644 (file)
@@ -88,7 +88,7 @@ class component_favourite_service_testcase extends advanced_testcase {
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
-                    if (array_diff($criteria, $mockrowarr) == []) {
+                    if (array_diff_assoc($criteria, $mockrowarr) == []) {
                         $returns[$index] = $mockrow;
                     }
                 }
@@ -107,7 +107,7 @@ class component_favourite_service_testcase extends advanced_testcase {
                 $crit = ['userid' => $userid, 'component' => $comp, 'itemtype' => $type, 'itemid' => $id, 'contextid' => $ctxid];
                 foreach ($mockstore as $fakerow) {
                     $fakerowarr = (array)$fakerow;
-                    if (array_diff($crit, $fakerowarr) == []) {
+                    if (array_diff_assoc($crit, $fakerowarr) == []) {
                         return $fakerow;
                     }
                 }
@@ -133,7 +133,7 @@ class component_favourite_service_testcase extends advanced_testcase {
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
-                    if (array_diff($criteria, $mockrowarr) == []) {
+                    if (array_diff_assoc($criteria, $mockrowarr) == []) {
                         $count++;
                     }
                 }
@@ -156,7 +156,7 @@ class component_favourite_service_testcase extends advanced_testcase {
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
-                    if (array_diff($criteria, $mockrowarr) == []) {
+                    if (array_diff_assoc($criteria, $mockrowarr) == []) {
                         unset($mockstore[$index]);
                     }
                 }
@@ -169,7 +169,7 @@ class component_favourite_service_testcase extends advanced_testcase {
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
                     echo "Here";
-                    if (array_diff($criteria, $mockrowarr) == []) {
+                    if (array_diff_assoc($criteria, $mockrowarr) == []) {
                         return true;
                     }
                 }
index 3483a33..bc172af 100644 (file)
@@ -88,7 +88,7 @@ class user_favourite_service_testcase extends advanced_testcase {
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
-                    if (array_diff($criteria, $mockrowarr) == []) {
+                    if (array_diff_assoc($criteria, $mockrowarr) == []) {
                         $returns[$index] = $mockrow;
                     }
                 }
@@ -107,7 +107,7 @@ class user_favourite_service_testcase extends advanced_testcase {
                 $crit = ['userid' => $userid, 'component' => $comp, 'itemtype' => $type, 'itemid' => $id, 'contextid' => $ctxid];
                 foreach ($mockstore as $fakerow) {
                     $fakerowarr = (array)$fakerow;
-                    if (array_diff($crit, $fakerowarr) == []) {
+                    if (array_diff_assoc($crit, $fakerowarr) == []) {
                         return $fakerow;
                     }
                 }
@@ -133,7 +133,7 @@ class user_favourite_service_testcase extends advanced_testcase {
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
-                    if (array_diff($criteria, $mockrowarr) == []) {
+                    if (array_diff_assoc($criteria, $mockrowarr) == []) {
                         $count++;
                     }
                 }
@@ -156,7 +156,7 @@ class user_favourite_service_testcase extends advanced_testcase {
                 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria.
                 foreach ($mockstore as $index => $mockrow) {
                     $mockrowarr = (array)$mockrow;
-                    if (array_diff($criteria, $mockrowarr) == []) {
+                    if (array_diff_assoc($criteria, $mockrowarr) == []) {
                         return true;
                     }
                 }
index bb7a829..2966927 100644 (file)
@@ -45,6 +45,6 @@ $string['downloadedfilecheckfailed'] = 'Αποτυχία ελέγχου αρχε
 $string['invalidmd5'] = 'Η μεταβλητή ελέγχου ήταν λανθασμένη - δοκιμάστε ξανά';
 $string['missingrequiredfield'] = 'Κάποιο απαιτούμενο πεδίο λείπει';
 $string['remotedownloaderror'] = '<p>Η λήψη του στοιχείου λογισμικού στον εξυπηρετητή σας απέτυχε. Παρακαλούμε επαληθεύστε τις ρυθμίσεις του διακομιστή μεσολάβησης (proxy)· η επέκταση PHP cURL συνιστάται θερμά.</p><br /><p>Πρέπει να κατεβάσετε το αρχείο <a href="{$a->url}">{$a->url}</a> χειροκίνητα, να το αντιγράψετε στο «{$a->dest}» στον εξυπηρετητή σας και να το αποσυμπιέσετε εκεί.</p>';
-$string['wrongdestpath'] = 'Î\9bανθαÏ\83μένη Î´Î¹Î±Î´Ï\81ομή Ï\80Ï\81οοÏ\81ιÏ\83μοÏ\8d (Ï\80λήÏ\81εÏ\82 Ï\8cνομα).';
+$string['wrongdestpath'] = 'Î\9bανθαÏ\83μένο Î¼Î¿Î½Î¿Ï\80άÏ\84ι Ï\80Ï\81οοÏ\81ιÏ\83μοÏ\8d.';
 $string['wrongsourcebase'] = 'Λανθασμένη βάση πηγής URL.';
 $string['wrongzipfilename'] = 'Λανθασμένo όνομα αρχείου ZIP.';
diff --git a/install/lang/he_wp/admin.php b/install/lang/he_wp/admin.php
new file mode 100644 (file)
index 0000000..bdaed90
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['clianswerno'] = 'ל';
+$string['cliansweryes'] = 'כ';
+$string['cliincorrectvalueretry'] = 'ערך לא תקין, נסו שוב בבקשה';
index ef9c319..a29697c 100644 (file)
@@ -1039,6 +1039,7 @@ $string['reportsmanage'] = 'Manage reports';
 $string['requiredentrieschanged'] = 'Note: After upgrading, the setting \'Required entries before viewing\' is now enforced in the following database activities:<br/>{$a->text}<br/>';
 $string['requiremodintro'] = 'Require activity description';
 $string['requiremodintro_desc'] = 'If enabled, users will be forced to enter a description for each activity.';
+$string['required'] = 'Required';
 $string['requires'] = 'Requires';
 $string['purgecaches'] = 'Purge all caches';
 $string['purgecachesconfirm'] = 'Moodle can cache themes, javascript, language strings, filtered text, rss feeds and many other pieces of calculated data.  Purging these caches will delete that data from the server and force browsers to refetch data, so that you can be sure you are seeing the most up-to-date values produced by the current code.  There is no danger in purging caches, but your site may appear slower for a while until the server and clients calculate new information and cache it.';
index da19957..854486a 100644 (file)
@@ -1748,6 +1748,16 @@ abstract class admin_setting {
         $this->set_flag_options($enabled, $default, 'locked', new lang_string('locked', 'core_admin'));
     }
 
+    /**
+     * Set the required options flag on this admin setting.
+     *
+     * @param bool $enabled - One of self::OPTION_ENABLED or self::OPTION_DISABLED.
+     * @param bool $default - The default for the flag.
+     */
+    public function set_required_flag_options($enabled, $default) {
+        $this->set_flag_options($enabled, $default, 'required', new lang_string('required', 'core_admin'));
+    }
+
     /**
      * Get the currently saved value for a setting flag
      *
@@ -2074,7 +2084,7 @@ abstract class admin_setting {
 
 /**
  * An additional option that can be applied to an admin setting.
- * The currently supported options are 'ADVANCED' and 'LOCKED'.
+ * The currently supported options are 'ADVANCED', 'LOCKED' and 'REQUIRED'.
  *
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index e003f8f..304ff06 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
 
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Latest version is available at http://adodb.org/
index 735702b..7598f1d 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
 
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Latest version is available at http://adodb.org/
index 8db3984..f141258 100644 (file)
@@ -8,7 +8,7 @@ $ADODB_INCLUDED_CSV = 1;
 
 /*
 
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 2a9a104..f8a5b7e 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index fb4a588..dbec57e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index a1206c6..a66f848 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index d0fbd9e..f59f51d 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index fda6543..77d16f2 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index 5614134..f8c4fb0 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index cb0aebe..b9cfd3f 100644 (file)
@@ -6,7 +6,7 @@ global $ADODB_INCLUDED_LIB;
 $ADODB_INCLUDED_LIB = 1;
 
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index d21a1d4..394ce08 100644 (file)
@@ -11,7 +11,7 @@ if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
 
 /*
 
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index ceb6963..5d0ca6d 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-       @version   v5.20.15  24-Nov-2019
+       @version   v5.20.16  12-Jan-2020
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
          Released under both BSD license and Lesser GPL library license.
index 32a299b..d1037c0 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index 1810d1f..05a413b 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index acf4135..69f8149 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 8b3b551..af8db3e 100644 (file)
@@ -4,7 +4,7 @@ ADOdb Date Library, part of the ADOdb abstraction library
 
 Latest version is available at http://adodb.org/
 
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index 8ca4f58..74c52e0 100644 (file)
@@ -14,7 +14,7 @@
 /**
        \mainpage
 
-       @version   v5.20.15  24-Nov-2019
+       @version   v5.20.16  12-Jan-2020
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
@@ -224,7 +224,7 @@ if (!defined('_ADODB_LAYER')) {
                /**
                 * ADODB version as a string.
                 */
-               $ADODB_vers = 'v5.20.15  24-Nov-2019';
+               $ADODB_vers = 'v5.20.16  12-Jan-2020';
 
                /**
                 * Determines whether recordset->RecordCount() is used.
index 38282e6..6f68eb5 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 8222aa9..82f2f2b 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index a931cf7..77a954e 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b08bd95..7247544 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 24af41c..d1aa15b 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index a756e11..9fb481e 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 9ddc645..27b58c6 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b466cb7..be6a9a1 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 22329aa..e4f6e66 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 7b6bc92..b09fed5 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index d7add54..7641e33 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 973f9ed..6b6b15c 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 5de43e5..c67ee3a 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 3f42387..3623dc0 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index e74f217..730b874 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 584f1ed..3363876 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 583d887..0144327 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index f8641c8..58dd0c8 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 Released under both BSD license and Lesser GPL library license.
index 689f4d7..d2ae2a4 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 0b87706..45fdcbf 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b272744..371a5c7 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 9e8209f..20a1ecf 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index 201865f..c5fc168 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 387221d..2dc330c 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index af7a9e1..51b3214 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
- @version   v5.20.15  24-Nov-2019
+ @version   v5.20.16  12-Jan-2020
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  Released under both BSD license and Lesser GPL library license.
index fb286f5..06eabe5 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index fbf989f..95c1e7a 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 5f4f374..15e9dda 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-* @version   v5.20.15  24-Nov-2019
+* @version   v5.20.16  12-Jan-2020
 * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 * Released under both BSD license and Lesser GPL library license.
index 4899cb4..fb15409 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim. All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 53b0b45..6749144 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
    Released under both BSD license and Lesser GPL library license.
index 6630fb7..6f88939 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index d6ec3a2..72f1d7e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -1074,7 +1074,7 @@ class ADORecordset_mssqlnative extends ADORecordSet {
                is running. All associated result memory for the specified result identifier will automatically be freed.       */
        function _close()
        {
-               if(is_object($this->_queryID)) {
+               if(is_resource($this->_queryID)) {
                        $rez = sqlsrv_free_stmt($this->_queryID);
                        $this->_queryID = false;
                        return $rez;
index 5186696..8dd0e36 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-* @version   v5.20.15  24-Nov-2019
+* @version   v5.20.16  12-Jan-2020
 * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 * Released under both BSD license and Lesser GPL library license.
index 4e30510..98bd31a 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index b96fde3..6313861 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 5e15811..14e0c5f 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 2e9852b..925e1aa 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index c75de50..f63cc13 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index c840d25..5b4ff62 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
 
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim. All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index c401f87..bda486c 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index f3ec9fd..bbd6d26 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim. All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
@@ -120,7 +120,7 @@ class ADODB_oci8po extends ADODB_oci8 {
                        /*
                        * find the next character of the string
                        */
-                       $c = $sql[$i];
+            $c = $sql[$i];
 
                        if ($c == "'" && !$inString && $escaped==0)
                                /*
index 9a576bb..183ae17 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim. All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index c563cf9..6161e94 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 3b52702..aeae3fb 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 1184f12..520d1c0 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index e0e785a..0efa79b 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 391f9a7..116eb89 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 12687cf..9df0397 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-       @version   v5.20.15  24-Nov-2019
+       @version   v5.20.16  12-Jan-2020
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 6663f01..9f0919b 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index c0a2159..80f0992 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
-       @version   v5.20.15  24-Nov-2019
+       @version   v5.20.16  12-Jan-2020
        @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
        @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 
index 4c6f2f9..162cd59 100644 (file)
@@ -2,7 +2,7 @@
 
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 5270e74..eea92ae 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index c014ecf..4584f3f 100644 (file)
@@ -2,7 +2,7 @@
 
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 0496989..b5cd221 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 24eacdb..0694fb3 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
- @version   v5.20.15  24-Nov-2019
+ @version   v5.20.16  12-Jan-2020
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index be4a9cf..61e3987 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
- @version   v5.20.15  24-Nov-2019
+ @version   v5.20.16  12-Jan-2020
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 4343555..b78c5c1 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
- @version   v5.20.15  24-Nov-2019
+ @version   v5.20.16  12-Jan-2020
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 6907534..e5aaaa7 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
- @version   v5.20.15  24-Nov-2019
+ @version   v5.20.16  12-Jan-2020
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 7ffff8a..4e3ce8d 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
- @version   v5.20.15  24-Nov-2019
+ @version   v5.20.16  12-Jan-2020
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 536dd3c..a4ab85c 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
- @version   v5.20.15  24-Nov-2019
+ @version   v5.20.16  12-Jan-2020
  @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index afa921a..0d19912 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index d621cc2..f2a1b81 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 156129f..77b5a17 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013  John Lim (jlim#natsoft.com).  All rights
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
 reserved.
index f151a5b..2f52fe0 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 60b133b..d9c7c51 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 08daaf3..8b3ec9e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 38c940a..0b7d470 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim. All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 78b7bed..4e09f60 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 0b278aa..fe12f7f 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 01f8e1e..ddb8901 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 894cd05..5c0fa28 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 9886037..c1c87f9 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 58fb31f..55db774 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index d56d8ad..1009b37 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 7f09b0b..4c6a39f 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 20b31a8..b350f01 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /*
-@version   v5.20.15  24-Nov-2019
+@version   v5.20.16  12-Jan-2020
 @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
 @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 75ec106..261b30f 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index c8ba7b3..e0d1318 100644 (file)
@@ -1,4 +1,4 @@
-Description of ADODB V5.20.15 library import into Moodle
+Description of ADODB V5.20.16 library import into Moodle
 
 This library will be probably removed in Moodle 2.1,
 it is now used only in enrol and auth db plugins.
@@ -24,5 +24,3 @@ Added:
 
 Our changes:
  * MDL-67034 Fixes to make the library php74 compliant.
-
-skodak, iarenaza, moodler, stronk7, abgreeve, lameze, ankitagarwal, marinaglancy, rezaie9
index ed79446..2682122 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index e111e64..f248be3 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * @version   v5.20.15  24-Nov-2019
+ * @version   v5.20.16  12-Jan-2020
  * @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
  * @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
  * Released under both BSD license and Lesser GPL library license.
index 13bfc54..bc69a44 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /*
-  @version   v5.20.15  24-Nov-2019
+  @version   v5.20.16  12-Jan-2020
   @copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
   @copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
   Released under both BSD license and Lesser GPL library license.
index 45a80ae..7de1001 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js and b/lib/amd/build/form-autocomplete.min.js differ
index 2a0d468..98bedda 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js.map and b/lib/amd/build/form-autocomplete.min.js.map differ
index a3be9b6..2cffd17 100644 (file)
@@ -114,7 +114,7 @@ function($, log, str, templates, notification, LoadingIcon) {
         });
         var context = $.extend({items: items}, options, state);
         // Render the template.
-        return templates.render('core/form_autocomplete_selection', context)
+        return templates.render('core/form_autocomplete_selection_items', context)
         .then(function(html, js) {
             // Add it to the page.
             templates.replaceNodeContents(newSelection, html, js);
index 1bd1cdb..380f60a 100644 (file)
@@ -93,6 +93,11 @@ class scanner extends \core\antivirus\scanner {
             // return SCAN_RESULT_FOUND result.
             if ($this->get_config('clamfailureonupload') === 'actlikevirus') {
                 return self::SCAN_RESULT_FOUND;
+            } else if ($this->get_config('clamfailureonupload') === 'tryagain') {
+                // Do not upload the file, just give a message to the user to try again later.
+                unlink($file);
+                throw new \core\antivirus\scanner_exception('antivirusfailed', '', ['item' => $filename],
+                        null, 'antivirus_clamav');
             }
         }
         return $return;
index a7a397f..73145a2 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['antivirusfailed'] = 'There is a problem with AntiVirus scanning at the moment. Your file {$a->item} has not been uploaded. Please try again later.';
 $string['configclamactlikevirus'] = 'Treat files like viruses';
 $string['configclamdonothing'] = 'Treat files as OK';
-$string['configclamfailureonupload'] = 'If you have configured clam to scan uploaded files, but it is configured incorrectly or fails to run for some unknown reason, how should it behave?  If you choose \'Treat files like viruses\', they\'ll be moved into the quarantine area, or deleted. If you choose \'Treat files as OK\', the files will be moved to the destination directory like normal. Either way, admins will be alerted that clam has failed.  If you choose \'Treat files like viruses\' and for some reason clam fails to run (usually because you have entered an invalid pathtoclam), ALL files that are uploaded will be moved to the given quarantine area, or deleted. Be careful with this setting.';
+$string['configclamfailureonupload'] = 'If you have configured clam to scan uploaded files, but it is configured incorrectly or fails to run for some unknown reason, how should it behave?  If you choose \'Treat files like viruses\', they\'ll be moved into the quarantine area, or deleted. If you choose \'Treat files as OK\', the files will be moved to the destination directory like normal. If you choose \'Refuse upload, try again\' (useful if failures occur during regular virus updating periods) a try again later message will be displayed to the user. Either way, admins will be alerted that clam has failed.  If you choose \'Treat files like viruses\' and for some reason clam fails to run (usually because you have entered an invalid pathtoclam), ALL files that are uploaded will be moved to the given quarantine area, or deleted. Be careful with this setting.';
+$string['configclamtryagain'] = 'Refuse upload, try again';
 $string['clamfailed'] = 'ClamAV has failed to run.  The return error message was "{$a}". Here is the output from ClamAV:';
 $string['clamfailureonupload'] = 'On ClamAV failure';
 $string['errorcantopensocket'] = 'Connecting to Unix domain socket resulted in error {$a}';
index 2c895c9..855ad7f 100644 (file)
@@ -62,6 +62,7 @@ if ($ADMIN->fulltree) {
     $options = array(
         'donothing' => new lang_string('configclamdonothing', 'antivirus_clamav'),
         'actlikevirus' => new lang_string('configclamactlikevirus', 'antivirus_clamav'),
+        'tryagain' => new lang_string('configclamtryagain', 'antivirus_clamav')
     );
     $settings->add(new admin_setting_configselect('antivirus_clamav/clamfailureonupload',
             new lang_string('clamfailureonupload', 'antivirus_clamav'),
index 4a8b9c1..87a39b7 100644 (file)
@@ -234,6 +234,38 @@ class antivirus_clamav_scanner_testcase extends advanced_testcase {
         $this->assertEquals(1, $antivirus->scan_file($this->tempfile, ''));
     }
 
+    public function test_scan_file_error_tryagain() {
+        $methods = array(
+                'scan_file_execute_commandline',
+                'scan_file_execute_unixsocket',
+                'message_admins',
+                'get_config',
+                'get_scanning_notice',
+        );
+        $antivirus = $this->getMockBuilder('\antivirus_clamav\scanner')->setMethods($methods)->getMock();
+
+        // Configure scan_file_execute_commandline and scan_file_execute_unixsocket
+        // method stubs to behave as if there is a scanning error (SCAN_RESULT_ERROR).
+        $antivirus->method('scan_file_execute_commandline')->willReturn(2);
+        $antivirus->method('scan_file_execute_unixsocket')->willReturn(2);
+        $antivirus->method('get_scanning_notice')->willReturn('someerror');
+
+        // Set expectation that message_admins is called.
+        $antivirus->expects($this->atLeastOnce())->method('message_admins')->with($this->equalTo('someerror'));
+
+        // Initiate mock scanning with configuration setting to act like virus on
+        // scanning error and using commandline.
+        $configmap = array(array('clamfailureonupload', 'tryagain'), array('runningmethod', 'commandline'));
+        $antivirus->method('get_config')->will($this->returnValueMap($configmap));
+
+        // Run mock scanning.
+        $this->assertFileExists($this->tempfile);
+        $this->expectException(\core\antivirus\scanner_exception::class);
+        $antivirus->scan_file($this->tempfile, '');
+        $this->assertEquals('antivirusfailed', $this->getExpectedExceptionCode());
+        $this->assertFileNotExists($this->tempfile);
+    }
+
     public function test_scan_data_no_virus() {
         $methods = array(
             'scan_data_execute_socket',
index 5c52c89..e4c6503 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019122900;          // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2020012400;          // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019111200;          // Requires this Moodle version.
 $plugin->component = 'antivirus_clamav';  // Full name of the plugin (used for diagnostics).
index 138901c..89b9f72 100644 (file)
@@ -42,8 +42,9 @@ class scanner_exception extends \moodle_exception {
      * @param string $link
      * @param mixed $a
      * @param mixed $debuginfo
+     * @param string $module optional plugin name
      */
-    public function __construct($errorcode, $link = '', $a = null, $debuginfo = null) {
-        parent::__construct($errorcode, 'antivirus', $link, $a, $debuginfo);
+    public function __construct($errorcode, $link = '', $a = null, $debuginfo = null, $module = 'antivirus') {
+        parent::__construct($errorcode, $module, $link, $a, $debuginfo);
     }
 }
index 22edb6b..f898c36 100644 (file)
@@ -42,8 +42,21 @@ define('ANY_VERSION', 'any');
  * Collection of components related methods.
  */
 class core_component {
-    /** @var array list of ignored directories - watch out for auth/db exception */
-    protected static $ignoreddirs = array('CVS'=>true, '_vti_cnf'=>true, 'simpletest'=>true, 'db'=>true, 'yui'=>true, 'tests'=>true, 'classes'=>true, 'fonts'=>true);
+    /** @var array list of ignored directories in plugin type roots - watch out for auth/db exception */
+    protected static $ignoreddirs = [
+        'CVS' => true,
+        '_vti_cnf' => true,
+        'amd' => true,
+        'classes' => true,
+        'db' => true,
+        'fonts' => true,
+        'lang' => true,
+        'pix' => true,
+        'simpletest' => true,
+        'templates' => true,
+        'tests' => true,
+        'yui' => true,
+    ];
     /** @var array list plugin types that support subplugins, do not add more here unless absolutely necessary */
     protected static $supportsubplugins = array('mod', 'editor', 'tool', 'local');
 
diff --git a/lib/classes/lock/mysql_lock_factory.php b/lib/classes/lock/mysql_lock_factory.php
new file mode 100644 (file)
index 0000000..6f05924
--- /dev/null
@@ -0,0 +1,189 @@
+<?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/>.
+
+/**
+ * MySQL / MariaDB locking factory.
+ *
+ * @package    core
+ * @category   lock
+ * @copyright  Brendan Heywood <brendan@catalyst-au.net>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\lock;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * MySQL / MariaDB locking factory.
+ *
+ * @package   core
+ * @category  lock
+ * @copyright Brendan Heywood <brendan@catalyst-au.net>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mysql_lock_factory implements lock_factory {
+
+    /** @var string $dbprefix - used as a namespace for these types of locks */
+    protected $dbprefix = '';
+
+    /** @var \moodle_database $db Hold a reference to the global $DB */
+    protected $db;
+
+    /** @var array $openlocks - List of held locks - used by auto-release */
+    protected $openlocks = [];
+
+    /**
+     * Return a unique prefix based on the database name and prefix.
+     * @param string $type - Used to prefix lock keys.
+     * @return string.
+     */
+    protected function get_unique_db_prefix($type) {
+        global $CFG;
+        $prefix = $CFG->dbname . ':';
+        if (isset($CFG->prefix)) {
+            $prefix .= $CFG->prefix;
+        }
+        $prefix .= '_' . $type . '_';
+        return $prefix;
+    }
+
+    /**
+     * Lock constructor.
+     * @param string $type - Used to prefix lock keys.
+     */
+    public function __construct($type) {
+        global $DB;
+
+        $this->dbprefix = $this->get_unique_db_prefix($type);
+        // Save a reference to the global $DB so it will not be released while we still have open locks.
+        $this->db = $DB;
+
+        \core_shutdown_manager::register_function([$this, 'auto_release']);
+    }
+
+    /**
+     * Is available.
+     * @return boolean - True if this lock type is available in this environment.
+     */
+    public function is_available() {
+        return $this->db->get_dbfamily() === 'mysql';
+    }
+
+    /**
+     * Return information about the blocking behaviour of the lock type on this platform.
+     * @return boolean - Defer to the DB driver.
+     */
+    public function supports_timeout() {
+        return true;
+    }
+
+    /**
+     * Will this lock type will be automatically released when a process ends.
+     *
+     * @return boolean - Via shutdown handler.
+     */
+    public function supports_auto_release() {
+        return true;
+    }
+
+    /**
+     * Multiple locks for the same resource can NOT be held by a single process.
+     *
+     * Hard coded to false and workaround inconsistent support in different
+     * versions of MySQL / MariaDB.
+     *
+     * @return boolean - false
+     */
+    public function supports_recursion() {
+        return false;
+    }
+
+    /**
+     * 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.
+     * @return boolean - true if a lock was obtained.
+     */
+    public function get_lock($resource, $timeout, $maxlifetime = 86400) {
+
+        // We sha1 to avoid long key names hitting the mysql str limit.
+        $resourcekey = sha1($this->dbprefix . $resource);
+
+        // Even though some versions of MySQL and MariaDB can support stacked locks
+        // just never stack them and always fail fast.
+        if (isset($this->openlocks[$resourcekey])) {
+            return false;
+        }
+
+        $params = [
+            'resourcekey' => $resourcekey,
+            'timeout' => $timeout
+        ];
+
+        $result = $this->db->get_record_sql('SELECT GET_LOCK(:resourcekey, :timeout) AS locked', $params);
+        $locked = $result->locked == 1;
+
+        if ($locked) {
+            $this->openlocks[$resourcekey] = 1;
+            return new lock($resourcekey, $this);
+        }
+        return false;
+    }
+
+    /**
+     * Release a lock that was previously obtained with @lock.
+     * @param lock $lock - a lock obtained from this factory.
+     * @return boolean - true if the lock is no longer held (including if it was never held).
+     */
+    public function release_lock(lock $lock) {
+
+        $params = [
+            'resourcekey' => $lock->get_key()
+        ];
+        $result = $this->db->get_record_sql('SELECT RELEASE_LOCK(:resourcekey) AS unlocked', $params);
+        $result = $result->unlocked == 1;
+        if ($result) {
+            unset($this->openlocks[$lock->get_key()]);
+        }
+        return $result;
+    }
+
+    /**
+     * Extend a lock that was previously obtained with @lock.
+     * @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) {
+        // Not supported by this factory.
+        return false;
+    }
+
+    /**
+     * Auto release any open locks on shutdown.
+     * This is required, because we may be using persistent DB connections.
+     */
+    public function auto_release() {
+        // Called from the shutdown handler. Must release all open locks.
+        foreach ($this->openlocks as $key => $unused) {
+            $lock = new lock($key, $this);
+            $lock->release();
+        }
+    }
+
+}
index 66744b1..0a26e59 100644 (file)
@@ -331,15 +331,14 @@ class manager {
             // Trigger event for sending a message or notification - we need to do this before marking as read!
             self::trigger_message_events($eventdata, $savemessage);
 
-            if ($eventdata->notification or empty($CFG->messaging)) {
-                // If they have deselected all processors and its a notification mark it read. The user doesn't want to be bothered.
-                // The same goes if the messaging is completely disabled.
-                if ($eventdata->notification) {
-                    $savemessage->timeread = null;
-                    \core_message\api::mark_notification_as_read($savemessage);
-                } else {
-                    \core_message\api::mark_message_as_read($eventdata->userto->id, $savemessage);
-                }
+            if ($eventdata->notification) {
+                // If they have deselected all processors and it's a notification mark it read. The user doesn't want to be
+                // bothered.
+                $savemessage->timeread = null;
+                \core_message\api::mark_notification_as_read($savemessage);
+            } else if (empty($CFG->messaging)) {
+                // If it's a message and messaging is disabled mark it read.
+                \core_message\api::mark_message_as_read($eventdata->userto->id, $savemessage);
             }
 
             return $savemessage->id;
@@ -383,15 +382,9 @@ class manager {
         // Trigger event for sending a message or notification - we need to do this before marking as read!
         self::trigger_message_events($eventdata, $savemessage);
 
-        if (empty($CFG->messaging)) {
-            // If they have deselected all processors and its a notification mark it read. The user doesn't want to be bothered.
-            // The same goes if the messaging is completely disabled.
-            if ($eventdata->notification) {
-                $savemessage->timeread = null;
-                \core_message\api::mark_notification_as_read($savemessage);
-            } else {
-                \core_message\api::mark_message_as_read($eventdata->userto->id, $savemessage);
-            }
+        if (!$eventdata->notification && empty($CFG->messaging)) {
+            // If it's a message and messaging is disabled mark it read.
+            \core_message\api::mark_message_as_read($eventdata->userto->id, $savemessage);
         }
 
         return $savemessage->id;
index 4aa4885..4e25171 100644 (file)
@@ -163,6 +163,9 @@ class database_logger implements task_logger {
             return;
         }
 
-        $DB->delete_records_list('task_log', 'id', $logids);
+        $chunks = array_chunk($logids, 1000);
+        foreach ($chunks as $chunk) {
+            $DB->delete_records_list('task_log', 'id', $chunk);
+        }
     }
 }
index ac3359c..05bfd5c 100644 (file)
@@ -68,7 +68,7 @@ class file_temp_cleanup_task extends scheduled_task {
         // Now loop through again and remove old files and directories.
         for ($iter->rewind(); $iter->valid(); $iter->next()) {
             $node = $iter->getRealPath();
-            if (!is_readable($node)) {
+            if (!isset($modifieddateobject[$node]) || !is_readable($node)) {
                 continue;
             }
 
index 1a7ebc4..de135c8 100644 (file)
@@ -2172,5 +2172,48 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2020011700.02);
     }
 
+    if ($oldversion < 2020013000.01) {
+        global $DB;
+        // Delete any associated files.
+        $fs = get_file_storage();
+        $sql = "SELECT cuc.id, cuc.userid
+                  FROM {competency_usercomp} cuc
+             LEFT JOIN {user} u ON cuc.userid = u.id
+                 WHERE u.deleted = 1";
+        $usercompetencies = $DB->get_records_sql($sql);
+        foreach ($usercompetencies as $usercomp) {
+            $DB->delete_records('competency_evidence', ['usercompetencyid' => $usercomp->id]);
+            $DB->delete_records('competency_usercompcourse', ['userid' => $usercomp->userid]);
+            $DB->delete_records('competency_usercompplan', ['userid' => $usercomp->userid]);
+            $DB->delete_records('competency_usercomp', ['userid' => $usercomp->userid]);
+        }
+
+        $sql = "SELECT cue.id, cue.userid
+                  FROM {competency_userevidence} cue
+             LEFT JOIN {user} u ON cue.userid = u.id
+                 WHERE u.deleted = 1";
+        $userevidences = $DB->get_records_sql($sql);
+        foreach ($userevidences as $userevidence) {
+            $DB->delete_records('competency_userevidencecomp', ['userevidenceid' => $userevidence->id]);
+            $DB->delete_records('competency_userevidence', ['id' => $userevidence->id]);
+
+            $context = context_user::instance($userevidence->userid);
+            $fs->delete_area_files($context->id, 'core_competency', 'userevidence', $userevidence->id);
+        }
+
+        $sql = "SELECT cp.id
+                  FROM {competency_plan} cp
+             LEFT JOIN {user} u ON cp.userid = u.id
+                 WHERE u.deleted = 1";
+        $userplans = $DB->get_records_sql($sql);
+        foreach ($userplans as $userplan) {
+            $DB->delete_records('competency_plancomp', ['planid' => $userplan->id]);
+            $DB->delete_records('competency_plan', ['id' => $userplan->id]);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2020013000.01);
+    }
+
     return true;
 }
index 7ddf633..c74b839 100644 (file)
@@ -330,6 +330,8 @@ class database_manager {
             throw new ddl_exception('ddlunknownerror', null, 'table drop sql not generated');
         }
         $this->execute_sql_arr($sqlarr, array($xmldb_table->getName()));
+
+        $this->generator->cleanup_after_drop($xmldb_table);
     }
 
     /**
index ff672ed..8c0bd93 100644 (file)
@@ -183,21 +183,6 @@ class mssql_sql_generator extends sql_generator {
         return $sqlarr;
     }
 
-    /**
-     * Given one correct xmldb_table, returns the SQL statements
-     * to drop it (inside one array).
-     *
-     * @param xmldb_table $xmldb_table The table to drop.
-     * @return array SQL statement(s) for dropping the specified table.
-     */
-    public function getDropTableSQL($xmldb_table) {
-        $sqlarr = parent::getDropTableSQL($xmldb_table);
-        if ($this->temptables->is_temptable($xmldb_table->getName())) {
-            $this->temptables->delete_temptable($xmldb_table->getName());
-        }
-        return $sqlarr;
-    }
-
     /**
      * Given one XMLDB Type, length and decimals, returns the DB proper SQL type.
      *
index d768ca5..def4c20 100644 (file)
@@ -384,7 +384,6 @@ class mysql_sql_generator extends sql_generator {
         $sqlarr = parent::getDropTableSQL($xmldb_table);
         if ($this->temptables->is_temptable($xmldb_table->getName())) {
             $sqlarr = preg_replace('/^DROP TABLE/', "DROP TEMPORARY TABLE", $sqlarr);
-            $this->temptables->delete_temptable($xmldb_table->getName());
         }
         return $sqlarr;
     }
index 4fb03bd..e501316 100644 (file)
@@ -211,7 +211,6 @@ class oracle_sql_generator extends sql_generator {
         $sqlarr = parent::getDropTableSQL($xmldb_table);
         if ($this->temptables->is_temptable($xmldb_table->getName())) {
             array_unshift($sqlarr, "TRUNCATE TABLE ". $this->getTableName($xmldb_table)); // oracle requires truncate before being able to drop a temp table
-            $this->temptables->delete_temptable($xmldb_table->getName());
         }
         return $sqlarr;
     }
index eb2685e..eeb3354 100644 (file)
@@ -103,21 +103,6 @@ class postgres_sql_generator extends sql_generator {
         return $sqlarr;
     }
 
-    /**
-     * Given one correct xmldb_table, returns the SQL statements
-     * to drop it (inside one array).
-     *
-     * @param xmldb_table $xmldb_table The table to drop.
-     * @return array SQL statement(s) for dropping the specified table.
-     */
-    public function getDropTableSQL($xmldb_table) {
-        $sqlarr = parent::getDropTableSQL($xmldb_table);
-        if ($this->temptables->is_temptable($xmldb_table->getName())) {
-            $this->temptables->delete_temptable($xmldb_table->getName());
-        }
-        return $sqlarr;
-    }
-
     /**
      * Given one correct xmldb_index, returns the SQL statements
      * needed to create it (in array).
index 9ad419f..99fd21f 100644 (file)
@@ -653,7 +653,7 @@ abstract class sql_generator {
     }
 
     /**
-     * Given one correct xmldb_table and the new name, returns the SQL statements
+     * Given one correct xmldb_table, returns the SQL statements
      * to drop it (inside one array). Works also for temporary tables.
      *
      * @param xmldb_table $xmldb_table The table to drop.
@@ -674,6 +674,17 @@ abstract class sql_generator {
         return $results;
     }
 
+    /**
+     * Performs any clean up that needs to be done after a table is dropped.
+     *
+     * @param xmldb_table $table
+     */
+    public function cleanup_after_drop(xmldb_table $table): void {
+        if ($this->temptables->is_temptable($table->getName())) {
+            $this->temptables->delete_temptable($table->getName());
+        }
+    }
+
     /**
      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add the field to the table.
      *
index a913ce7..06fabd1 100644 (file)
@@ -1828,6 +1828,74 @@ class core_ddl_testcase extends database_driver_testcase {
         $this->assertFalse($dbman->table_exists('test_table1'));
     }
 
+    /**
+     * get_columns should return an empty array for ex-temptables.
+     */
+    public function test_leftover_temp_tables_columns() {
+        $DB = $this->tdb; // Do not use global $DB!
+        $dbman = $this->tdb->get_manager();
+
+        // Create temp table0.
+        $table0 = $this->tables['test_table0'];
+        $dbman->create_temp_table($table0);
+
+        $dbman->drop_table($table0);
+
+        // Get columns and perform some basic tests.
+        $columns = $DB->get_columns('test_table0');
+        $this->assertEquals([], $columns);
+    }
+
+    /**
+     * Deleting a temp table should not purge the whole cache
+     */
+    public function test_leftover_temp_tables_cache() {
+        $DB = $this->tdb; // Do not use global $DB!
+        $dbman = $this->tdb->get_manager();
+
+        // Create 2 temp tables.
+        $table0 = $this->tables['test_table0'];
+        $dbman->create_temp_table($table0);
+        $table1 = $this->tables['test_table1'];
+        $dbman->create_temp_table($table1);
+
+        // Create a normal table.
+        $table2 = new xmldb_table ('test_table2');
+        $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table2->setComment("This is a test'n drop table. You can drop it safely");
+        $this->tables[$table2->getName()] = $table2;
+        $dbman->create_table($table2);
+
+        // Get columns for the tables, so that relevant caches are populated with their data.
+        $DB->get_columns('test_table0');
+        $DB->get_columns('test_table1');
+        $DB->get_columns('test_table2');
+
+        $dbman->drop_table($table0);
+
+        $rc = new ReflectionClass('moodle_database');
+        $rcm = $rc->getMethod('get_temp_tables_cache');
+        $rcm->setAccessible(true);
+        $metacachetemp = $rcm->invokeArgs($DB, []);
+
+        // Data of test_table0 should be removed from the cache.
+        $this->assertEquals(false, $metacachetemp->has('test_table0'));
+
+        // Data of test_table1 should be intact.
+        $this->assertEquals(true, $metacachetemp->has('test_table1'));
+
+        $rc = new ReflectionClass('moodle_database');
+        $rcm = $rc->getMethod('get_metacache');
+        $rcm->setAccessible(true);
+        $metacache = $rcm->invokeArgs($DB, []);
+
+        // Data of test_table2 should be intact.
+        $this->assertEquals(true, $metacache->has('test_table2'));
+
+    }
+
     public function test_reset_sequence() {
         $DB = $this->tdb;
         $dbman = $DB->get_manager();
index 021706b..1d86919 100644 (file)
@@ -1094,11 +1094,48 @@ abstract class moodle_database {
 
     /**
      * Returns detailed information about columns in table. This information is cached internally.
+     *
      * @param string $table The table's name.
      * @param bool $usecache Flag to use internal cacheing. The default is true.
      * @return database_column_info[] of database_column_info objects indexed with column names
      */
-    public abstract function get_columns($table, $usecache=true);
+    public function get_columns($table, $usecache = true): array {
+        if (!$table) { // Table not specified, return empty array directly.
+            return [];
+        }
+
+        if ($usecache) {
+            if ($this->temptables->is_temptable($table)) {
+                if ($data = $this->get_temp_tables_cache()->get($table)) {
+                    return $data;
+                }
+            } else {
+                if ($data = $this->get_metacache()->get($table)) {
+                    return $data;
+                }
+            }
+        }
+
+        $structure = $this->fetch_columns($table);
+
+        if ($usecache) {
+            if ($this->temptables->is_temptable($table)) {
+                $this->get_temp_tables_cache()->set($table, $structure);
+            } else {
+                $this->get_metacache()->set($table, $structure);
+            }
+        }
+
+        return $structure;
+    }
+
+    /**
+     * Returns detailed information about columns in table. This information is cached internally.
+     *
+     * @param string $table The table's name.
+     * @return database_column_info[] of database_column_info objects indexed with column names
+     */
+    protected abstract function fetch_columns(string $table): array;
 
     /**
      * Normalise values based on varying RDBMS's dependencies (booleans, LOBs...)
index 8469c0d..0b58d07 100644 (file)
@@ -700,24 +700,12 @@ class mysqli_native_moodle_database extends moodle_database {
     }
 
     /**
-     * Returns detailed information about columns in table. This information is cached internally.
+     * Fetches detailed information about columns in table.
+     *
      * @param string $table name
-     * @param bool $usecache
      * @return database_column_info[] array of database_column_info objects indexed with column names
      */
-    public function get_columns($table, $usecache=true) {
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                if ($data = $this->get_temp_tables_cache()->get($table)) {
-                    return $data;
-                }
-            } else {
-                if ($data = $this->get_metacache()->get($table)) {
-                    return $data;
-                }
-            }
-        }
-
+    protected function fetch_columns(string $table): array {
         $structure = array();
 
         $sql = "SELECT column_name, data_type, character_maximum_length, numeric_precision,
@@ -821,14 +809,6 @@ class mysqli_native_moodle_database extends moodle_database {
             $result->close();
         }
 
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                $this->get_temp_tables_cache()->set($table, $structure);
-            } else {
-                $this->get_metacache()->set($table, $structure);
-            }
-        }
-
         return $structure;
     }
 
index 6f91610..5bffeb6 100644 (file)
@@ -468,29 +468,12 @@ class oci_native_moodle_database extends moodle_database {
     }
 
     /**
-     * Returns detailed information about columns in table. This information is cached internally.
+     * Fetches detailed information about columns in table.
+     *
      * @param string $table name
-     * @param bool $usecache
      * @return array array of database_column_info objects indexed with column names
      */
-    public function get_columns($table, $usecache=true) {
-
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                if ($data = $this->get_temp_tables_cache()->get($table)) {
-                    return $data;
-                }
-            } else {
-                if ($data = $this->get_metacache()->get($table)) {
-                    return $data;
-                }
-            }
-        }
-
-        if (!$table) { // table not specified, return empty array directly
-            return array();
-        }
-
+    protected function fetch_columns(string $table): array {
         $structure = array();
 
         // We give precedence to CHAR_LENGTH for VARCHAR2 columns over WIDTH because the former is always
@@ -673,14 +656,6 @@ class oci_native_moodle_database extends moodle_database {
             $structure[$info->name] = new database_column_info($info);
         }
 
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                $this->get_temp_tables_cache()->set($table, $structure);
-            } else {
-                $this->get_metacache()->set($table, $structure);
-            }
-        }
-
         return $structure;
     }
 
index f5d5690..9bd1c88 100644 (file)
@@ -388,29 +388,18 @@ class pgsql_native_moodle_database extends moodle_database {
     }
 
     /**
-     * Returns detailed information about columns in table. This information is cached internally.
+     * Returns detailed information about columns in table.
+     *
      * @param string $table name
-     * @param bool $usecache
      * @return database_column_info[] array of database_column_info objects indexed with column names
      */
-    public function get_columns($table, $usecache=true) {
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                if ($data = $this->get_temp_tables_cache()->get($table)) {
-                    return $data;
-                }
-            } else {
-                if ($data = $this->get_metacache()->get($table)) {
-                    return $data;
-                }
-            }
-        }
-
+    protected function fetch_columns(string $table): array {
         $structure = array();
 
         $tablename = $this->prefix.$table;
 
-        $sql = "SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, d.adsrc
+        $sql = "SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef,
+                       CASE WHEN a.atthasdef THEN pg_catalog.pg_get_expr(d.adbin, d.adrelid) END AS adsrc
                   FROM pg_catalog.pg_class c
                   JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.relnamespace
                   JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
@@ -604,14 +593,6 @@ class pgsql_native_moodle_database extends moodle_database {
 
         pg_free_result($result);
 
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                $this->get_temp_tables_cache()->set($table, $structure);
-            } else {
-                $this->get_metacache()->set($table, $structure);
-            }
-        }
-
         return $structure;
     }
 
index 58753bd..2a540b8 100644 (file)
@@ -193,25 +193,12 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database {
     }
 
     /**
-     * Returns detailed information about columns in table. This information is cached internally.
+     * Returns detailed information about columns in table.
+     *
      * @param string $table name
-     * @param bool $usecache
      * @return array array of database_column_info objects indexed with column names
      */
-    public function get_columns($table, $usecache=true) {
-
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                if ($data = $this->get_temp_tables_cache()->get($table)) {
-                    return $data;
-                }
-            } else {
-                if ($data = $this->get_metacache()->get($table)) {
-                    return $data;
-                }
-            }
-        }
-
+    protected function fetch_columns(string $table): array {
         $structure = array();
 
         // get table's CREATE TABLE command (we'll need it for autoincrement fields)
@@ -303,14 +290,6 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database {
             $structure[$columninfo['name']] = new database_column_info($columninfo);
         }
 
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                $this->get_temp_tables_cache()->set($table, $structure);
-            } else {
-                $this->get_metacache()->set($table, $structure);
-            }
-        }
-
         return $structure;
     }
 
index a91317d..8b1f156 100644 (file)
@@ -108,7 +108,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
         return 'mssql';
     }
 
-   /**
+    /**
      * Returns more specific database driver type
      * Note: can be used before connect()
      * @return string db type mysqli, pgsql, oci, mssql, sqlsrv
@@ -117,7 +117,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
         return 'sqlsrv';
     }
 
-   /**
+    /**
      * Returns general database library name
      * Note: can be used before connect()
      * @return string db type pdo, native
@@ -534,24 +534,12 @@ class sqlsrv_native_moodle_database extends moodle_database {
     }
 
     /**
-     * Returns detailed information about columns in table. This information is cached internally.
+     * Returns detailed information about columns in table.
+     *
      * @param string $table name
-     * @param bool $usecache
      * @return array array of database_column_info objects indexed with column names
      */
-    public function get_columns($table, $usecache = true) {
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                if ($data = $this->get_temp_tables_cache()->get($table)) {
-                    return $data;
-                }
-            } else {
-                if ($data = $this->get_metacache()->get($table)) {
-                    return $data;
-                }
-            }
-        }
-
+    protected function fetch_columns(string $table): array {
         $structure = array();
 
         if (!$this->temptables->is_temptable($table)) { // normal table, get metadata from own schema
@@ -642,14 +630,6 @@ class sqlsrv_native_moodle_database extends moodle_database {
         }
         $this->free_result($result);
 
-        if ($usecache) {
-            if ($this->temptables->is_temptable($table)) {
-                $this->get_temp_tables_cache()->set($table, $structure);
-            } else {
-                $this->get_metacache()->set($table, $structure);
-            }
-        }
-
         return $structure;
     }
 
index a87c362..609c200 100644 (file)
@@ -5712,7 +5712,9 @@ class moodle_database_for_testing extends moodle_database {
     public function get_last_error() {}
     public function get_tables($usecache=true) {}
     public function get_indexes($table) {}
-    public function get_columns($table, $usecache=true) {}
+    protected function fetch_columns(string $table): array {
+        return [];
+    }
     protected function normalise_value($column, $value) {}
     public function set_debug($state) {}
     public function get_debug() {}
index 4c6d4b9..8f86592 100644 (file)
@@ -4248,6 +4248,9 @@ function delete_user(stdClass $user) {
     // This might be slow but it is really needed - modules might do some extra cleanup!
     role_unassign_all(array('userid' => $user->id));
 
+    // Notify the competency subsystem.
+    \core_competency\api::hook_user_deleted($user->id);
+
     // Now do a brute force cleanup.
 
     // Delete all user events and subscription events.
index e69ae8c..2605f6f 100644 (file)
@@ -252,6 +252,9 @@ class phpunit_util extends testing_util {
         if (class_exists('\core\update\checker')) {
             \core\update\checker::reset_caches(true);
         }
+        if (class_exists('\core_course\customfield\course_handler')) {
+            \core_course\customfield\course_handler::reset_caches();
+        }
 
         // Clear static cache within restore.
         if (class_exists('restore_section_structure_step')) {
index 33c7b6c..932ad37 100644 (file)
@@ -17,7 +17,7 @@
 {{!
     @template core/form_autocomplete_selection
 
-    Moodle template for the currently selected items in an autocomplate form element.
+    Moodle template for the wrapper of currently selected items in an autocomplate form element.
 
     Classes required for JS:
     * none
@@ -28,8 +28,8 @@
     Context variables required for this template:
     * multiple True if this field allows multiple selections
     * selectionId The dom id of the current selection list.
-    * items List of items with label and value fields.
-    * noSelectionString String to use when no items are selected
+    * items List of items with label and value fields (used by the partial).
+    * noSelectionString String to use when no items are selected (used by the partial).
 
     Example context (json):
     { "multiple": true, "selectionId": 1, "items": [
 }}
 <div class="form-autocomplete-selection w-100 {{#multiple}}form-autocomplete-multiple{{/multiple}}" id="{{selectionId}}" role="list" aria-atomic="true" {{#multiple}}tabindex="0" aria-multiselectable="true"{{/multiple}}>
 <span class="accesshide">{{#str}}selecteditems, form{{/str}}</span>
-    {{#items}}
-        <span role="listitem" data-value="{{value}}" aria-selected="true" class="badge badge-info mb-3 mr-1" style="font-size: 100%">
-            <span aria-hidden="true">× </span>{{{label}}}
-        </span>
-    {{/items}}
-    {{^items}}
-        <span class="mb-3 mr-1">{{noSelectionString}}</span>
-    {{/items}}
+    {{> core/form_autocomplete_selection_items }}
 </div>
diff --git a/lib/templates/form_autocomplete_selection_items.mustache b/lib/templates/form_autocomplete_selection_items.mustache
new file mode 100644 (file)
index 0000000..22ff5f2
--- /dev/null
@@ -0,0 +1,47 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core/form_autocomplete_selection_items
+
+    Moodle template for the currently selected items in an autocomplate form element.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * data-value
+
+    Context variables required for this template:
+    * items List of items with label and value fields.
+    *   - value Value of the selected item.
+    *   - label HTML representing the value.
+    * noSelectionString String to use when no items are selected
+
+    Example context (json):
+    { "items": [
+        { "label": "Item label with <strong>tags</strong>", "value": "5" },
+        { "label": "Another item label with <strong>tags</strong>", "value": "4" }
+    ], "noSelectionString": "No selection" }
+}}
+{{#items}}
+    <span role="listitem" data-value="{{value}}" aria-selected="true" class="badge badge-info mb-3 mr-1" style="font-size: 100%">
+        <span aria-hidden="true">× </span>{{{label}}}
+    </span>
+{{/items}}
+{{^items}}
+    <span class="mb-3 mr-1">{{noSelectionString}}</span>
+{{/items}}
index d905905..6ab5b99 100644 (file)
@@ -1 +1 @@
-<div class="toast-wrapper mx-auto py-3 fixed-top" role="status" aria-live="polite"></div>
+<div class="toast-wrapper mx-auto py-0 fixed-top" role="status" aria-live="polite"></div>
index c3aab08..8f8e5fa 100644 (file)
@@ -33,23 +33,6 @@ require_once($CFG->libdir . '/filterlib.php');
  * Test filters.
  */
 class core_filterlib_testcase extends advanced_testcase {
-    private $syscontext;
-    private $childcontext;
-    private $childcontext2;
-    private $catcontext;
-    private $coursecontext;
-    private $course;
-    private $activity1context;
-    private $activity2context;
-
-    protected function setUp() {
-        global $DB;
-        parent::setUp();
-
-        $this->resetAfterTest();
-        $DB->delete_records('filter_active', array());
-        $DB->delete_records('filter_config', array());
-    }
 
     private function assert_only_one_filter_globally($filter, $state) {
         global $DB;
@@ -79,6 +62,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_set_filter_globally_on() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         // Exercise SUT.
         filter_set_global_state('name', TEXTFILTER_ON);
@@ -87,6 +72,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_set_filter_globally_off() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         // Exercise SUT.
         filter_set_global_state('name', TEXTFILTER_OFF);
@@ -95,6 +82,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_set_filter_globally_disabled() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         // Exercise SUT.
         filter_set_global_state('name', TEXTFILTER_DISABLED);
@@ -106,10 +95,13 @@ class core_filterlib_testcase extends advanced_testcase {
      * @expectedException coding_exception
      */
     public function test_global_config_exception_on_invalid_state() {
+        $this->resetAfterTest();
         filter_set_global_state('name', 0);
     }
 
     public function test_auto_sort_order() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         // Exercise SUT.
         filter_set_global_state('one', TEXTFILTER_DISABLED);
@@ -119,6 +111,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_auto_sort_order_enabled() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         // Exercise SUT.
         filter_set_global_state('one', TEXTFILTER_ON);
@@ -128,6 +122,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_update_existing_dont_duplicate() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         // Exercise SUT.
         filter_set_global_state('name', TEXTFILTER_ON);
@@ -137,6 +133,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_update_reorder_down() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         filter_set_global_state('one', TEXTFILTER_ON);
         filter_set_global_state('two', TEXTFILTER_ON);
@@ -148,6 +146,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_update_reorder_up() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         filter_set_global_state('one', TEXTFILTER_ON);
         filter_set_global_state('two', TEXTFILTER_ON);
@@ -160,6 +160,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_auto_sort_order_change_to_enabled() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         filter_set_global_state('one', TEXTFILTER_ON);
         filter_set_global_state('two', TEXTFILTER_DISABLED);
@@ -171,6 +173,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_auto_sort_order_change_to_disabled() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         filter_set_global_state('one', TEXTFILTER_ON);
         filter_set_global_state('two', TEXTFILTER_ON);
@@ -182,6 +186,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_filter_get_global_states() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         filter_set_global_state('one', TEXTFILTER_ON);
         filter_set_global_state('two', TEXTFILTER_OFF);
@@ -216,6 +222,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_local_on() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Exercise SUT.
         filter_set_local_state('name', 123, TEXTFILTER_ON);
         // Validate.
@@ -223,6 +231,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_local_off() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Exercise SUT.
         filter_set_local_state('name', 123, TEXTFILTER_OFF);
         // Validate.
@@ -230,6 +240,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_local_inherit() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Exercise SUT.
         filter_set_local_state('name', 123, TEXTFILTER_INHERIT);
         // Validate.
@@ -240,6 +252,7 @@ class core_filterlib_testcase extends advanced_testcase {
      * @expectedException coding_exception
      */
     public function test_local_invalid_state_throws_exception() {
+        $this->resetAfterTest();
         // Exercise SUT.
         filter_set_local_state('name', 123, -9999);
     }
@@ -248,11 +261,14 @@ class core_filterlib_testcase extends advanced_testcase {
      * @expectedException coding_exception
      */
     public function test_throws_exception_when_setting_global() {
+        $this->resetAfterTest();
         // Exercise SUT.
         filter_set_local_state('name', context_system::instance()->id, TEXTFILTER_INHERIT);
     }
 
     public function test_local_inherit_deletes_existing() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         filter_set_local_state('name', 123, TEXTFILTER_INHERIT);
         // Exercise SUT.
@@ -276,6 +292,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_set_new_config() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Exercise SUT.
         filter_set_local_config('name', 123, 'settingname', 'An arbitrary value');
         // Validate.
@@ -283,6 +301,8 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_update_existing_config() {
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
         // Setup fixture.
         filter_set_local_config('name', 123, 'settingname', 'An arbitrary value');
         // Exercise SUT.
@@ -292,6 +312,7 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_filter_get_local_config() {
+        $this->resetAfterTest();
         // Setup fixture.
         filter_set_local_config('name', 123, 'setting1', 'An arbitrary value');
         filter_set_local_config('name', 123, 'setting2', 'Another arbitrary value');
@@ -306,22 +327,37 @@ class core_filterlib_testcase extends advanced_testcase {
     protected function setup_available_in_context_tests() {
         $course = $this->getDataGenerator()->create_course(array('category'=>1));
 
-        $this->childcontext = context_coursecat::instance(1);
-        $this->childcontext2 = context_course::instance($course->id);
-        $this->syscontext = context_system::instance();
+        $childcontext = context_coursecat::instance(1);
+        $childcontext2 = context_course::instance($course->id);
+        $syscontext = context_system::instance();
+
+        return [
+            'syscontext' => $syscontext,
+            'childcontext' => $childcontext,
+            'childcontext2' => $childcontext2
+        ];
+    }
+
+    protected function remove_all_filters_from_config() {
+        global $DB;
+        $DB->delete_records('filter_active', array());
+        $DB->delete_records('filter_config', array());
     }
 
     private function assert_filter_list($expectedfilters, $filters) {
-        $this->setup_available_in_context_tests();
         $this->assertEquals($expectedfilters, array_keys($filters), '', 0, 10, true);
     }
 
     public function test_globally_on_is_returned() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'syscontext' => $syscontext
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->syscontext);
+        $filters = filter_get_active_in_context($syscontext);
         // Validate.
         $this->assert_filter_list(array('name'), $filters);
         // Check no config returned correctly.
@@ -329,97 +365,128 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_globally_off_not_returned() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'childcontext2' => $childcontext2
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_OFF);
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->childcontext2);
+        $filters = filter_get_active_in_context($childcontext2);
         // Validate.
         $this->assert_filter_list(array(), $filters);
     }
 
     public function test_globally_off_overridden() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'childcontext' => $childcontext,
+            'childcontext2' => $childcontext2
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_OFF);
-        filter_set_local_state('name', $this->childcontext->id, TEXTFILTER_ON);
+        filter_set_local_state('name', $childcontext->id, TEXTFILTER_ON);
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->childcontext2);
+        $filters = filter_get_active_in_context($childcontext2);
         // Validate.
         $this->assert_filter_list(array('name'), $filters);
     }
 
     public function test_globally_on_overridden() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'childcontext' => $childcontext,
+            'childcontext2' => $childcontext2
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
-        filter_set_local_state('name', $this->childcontext->id, TEXTFILTER_OFF);
+        filter_set_local_state('name', $childcontext->id, TEXTFILTER_OFF);
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->childcontext2);
+        $filters = filter_get_active_in_context($childcontext2);
         // Validate.
         $this->assert_filter_list(array(), $filters);
     }
 
     public function test_globally_disabled_not_overridden() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'syscontext' => $syscontext,
+            'childcontext' => $childcontext
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_DISABLED);
-        filter_set_local_state('name', $this->childcontext->id, TEXTFILTER_ON);
+        filter_set_local_state('name', $childcontext->id, TEXTFILTER_ON);
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->syscontext);
+        $filters = filter_get_active_in_context($syscontext);
         // Validate.
         $this->assert_filter_list(array(), $filters);
     }
 
     public function test_single_config_returned() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        [
+            'childcontext' => $childcontext
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
-        filter_set_local_config('name', $this->childcontext->id, 'settingname', 'A value');
+        filter_set_local_config('name', $childcontext->id, 'settingname', 'A value');
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->childcontext);
+        $filters = filter_get_active_in_context($childcontext);
         // Validate.
         $this->assertEquals(array('settingname' => 'A value'), $filters['name']);
     }
 
     public function test_multi_config_returned() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        [
+            'childcontext' => $childcontext
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
-        filter_set_local_config('name', $this->childcontext->id, 'settingname', 'A value');
-        filter_set_local_config('name', $this->childcontext->id, 'anothersettingname', 'Another value');
+        filter_set_local_config('name', $childcontext->id, 'settingname', 'A value');
+        filter_set_local_config('name', $childcontext->id, 'anothersettingname', 'Another value');
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->childcontext);
+        $filters = filter_get_active_in_context($childcontext);
         // Validate.
         $this->assertEquals(array('settingname' => 'A value', 'anothersettingname' => 'Another value'), $filters['name']);
     }
 
     public function test_config_from_other_context_not_returned() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        [
+            'childcontext' => $childcontext,
+            'childcontext2' => $childcontext2
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
-        filter_set_local_config('name', $this->childcontext->id, 'settingname', 'A value');
-        filter_set_local_config('name', $this->childcontext2->id, 'anothersettingname', 'Another value');
+        filter_set_local_config('name', $childcontext->id, 'settingname', 'A value');
+        filter_set_local_config('name', $childcontext2->id, 'anothersettingname', 'Another value');
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->childcontext2);
+        $filters = filter_get_active_in_context($childcontext2);
         // Validate.
         $this->assertEquals(array('anothersettingname' => 'Another value'), $filters['name']);
     }
 
     public function test_config_from_other_filter_not_returned() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        [
+            'childcontext' => $childcontext
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
-        filter_set_local_config('name', $this->childcontext->id, 'settingname', 'A value');
-        filter_set_local_config('other', $this->childcontext->id, 'anothersettingname', 'Another value');
+        filter_set_local_config('name', $childcontext->id, 'settingname', 'A value');
+        filter_set_local_config('other', $childcontext->id, 'anothersettingname', 'Another value');
         // Exercise SUT.
-        $filters = filter_get_active_in_context($this->childcontext);
+        $filters = filter_get_active_in_context($childcontext);
         // Validate.
         $this->assertEquals(array('settingname' => 'A value'), $filters['name']);
     }
 
     protected function assert_one_available_filter($filter, $localstate, $inheritedstate, $filters) {
-        $this->setup_available_in_context_tests();
         $this->assertEquals(1, count($filters), 'More than one record returned %s.');
         $rec = $filters[$filter];
         unset($rec->id);
@@ -431,34 +498,47 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_available_in_context_localoverride() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'childcontext' => $childcontext
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
-        filter_set_local_state('name', $this->childcontext->id, TEXTFILTER_OFF);
+        filter_set_local_state('name', $childcontext->id, TEXTFILTER_OFF);
         // Exercise SUT.
-        $filters = filter_get_available_in_context($this->childcontext);
+        $filters = filter_get_available_in_context($childcontext);
         // Validate.
         $this->assert_one_available_filter('name', TEXTFILTER_OFF, TEXTFILTER_ON, $filters);
     }
 
     public function test_available_in_context_nolocaloverride() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'childcontext' => $childcontext,
+            'childcontext2' => $childcontext2
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
-        filter_set_local_state('name', $this->childcontext->id, TEXTFILTER_OFF);
+        filter_set_local_state('name', $childcontext->id, TEXTFILTER_OFF);
         // Exercise SUT.
-        $filters = filter_get_available_in_context($this->childcontext2);
+        $filters = filter_get_available_in_context($childcontext2);
         // Validate.
         $this->assert_one_available_filter('name', TEXTFILTER_INHERIT, TEXTFILTER_OFF, $filters);
     }
 
     public function test_available_in_context_disabled_not_returned() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'childcontext' => $childcontext
+        ] = $this->setup_available_in_context_tests();
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_DISABLED);
-        filter_set_local_state('name', $this->childcontext->id, TEXTFILTER_ON);
+        filter_set_local_state('name', $childcontext->id, TEXTFILTER_ON);
         // Exercise SUT.
-        $filters = filter_get_available_in_context($this->childcontext);
+        $filters = filter_get_available_in_context($childcontext);
         // Validate.
         $this->assertEquals(array(), $filters);
     }
@@ -467,23 +547,34 @@ class core_filterlib_testcase extends advanced_testcase {
      * @expectedException coding_exception
      */
     public function test_available_in_context_exception_with_syscontext() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        [
+            'syscontext' => $syscontext
+        ] = $this->setup_available_in_context_tests();
         // Exercise SUT.
-        filter_get_available_in_context($this->syscontext);
+        filter_get_available_in_context($syscontext);
     }
 
     protected function setup_preload_activities_test() {
-        $this->syscontext = context_system::instance();
-        $this->catcontext = context_coursecat::instance(1);
-        $this->course = $this->getDataGenerator()->create_course(array('category'=>1));
-        $this->coursecontext = context_course::instance($this->course->id);
-        $page1 =  $this->getDataGenerator()->create_module('page', array('course'=>$this->course->id));
-        $this->activity1context = context_module::instance($page1->cmid);
-        $page2 =  $this->getDataGenerator()->create_module('page', array('course'=>$this->course->id));
-        $this->activity2context = context_module::instance($page2->cmid);
-    }
-
-    private function assert_matches($modinfo) {
+        $syscontext = context_system::instance();
+        $catcontext = context_coursecat::instance(1);
+        $course = $this->getDataGenerator()->create_course(array('category' => 1));
+        $coursecontext = context_course::instance($course->id);
+        $page1 = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+        $activity1context = context_module::instance($page1->cmid);
+        $page2 = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+        $activity2context = context_module::instance($page2->cmid);
+        return [
+            'syscontext' => $syscontext,
+            'catcontext' => $catcontext,
+            'course' => $course,
+            'coursecontext' => $coursecontext,
+            'activity1context' => $activity1context,
+            'activity2context' => $activity2context
+         ];
+    }
+
+    private function assert_matches($modinfo, $activity1context, $activity2context) {
         global $FILTERLIB_PRIVATE, $DB;
 
         // Use preload cache...
@@ -492,16 +583,16 @@ class core_filterlib_testcase extends advanced_testcase {
 
         // Get data and check no queries are made.
         $before = $DB->perf_get_reads();
-        $plfilters1 = filter_get_active_in_context($this->activity1context);
-        $plfilters2 = filter_get_active_in_context($this->activity2context);
+        $plfilters1 = filter_get_active_in_context($activity1context);
+        $plfilters2 = filter_get_active_in_context($activity2context);
         $after = $DB->perf_get_reads();
         $this->assertEquals($before, $after);
 
         // Repeat without cache and check it makes queries now.
         $FILTERLIB_PRIVATE = new stdClass;
         $before = $DB->perf_get_reads();
-        $filters1 = filter_get_active_in_context($this->activity1context);
-        $filters2 = filter_get_active_in_context($this->activity2context);
+        $filters1 = filter_get_active_in_context($activity1context);
+        $filters2 = filter_get_active_in_context($activity2context);
         $after = $DB->perf_get_reads();
         $this->assertTrue($after > $before);
 
@@ -511,60 +602,69 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_preload() {
-        $this->setup_preload_activities_test();
+        $this->resetAfterTest();
+        [
+            'catcontext' => $catcontext,
+            'course' => $course,
+            'coursecontext' => $coursecontext,
+            'activity1context' => $activity1context,
+            'activity2context' => $activity2context
+         ] = $this->setup_preload_activities_test();
         // Get course and modinfo.
-        $modinfo = new course_modinfo($this->course, 2);
+        $modinfo = new course_modinfo($course, 2);
 
         // Note: All the tests in this function check that the result from the
         // preloaded cache is the same as the result from calling the standard
         // function without preloading.
 
         // Initially, check with no filters enabled.
-        $this->assert_matches($modinfo);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Enable filter globally, check.
         filter_set_global_state('name', TEXTFILTER_ON);
-        $this->assert_matches($modinfo);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Disable for activity 2.
-        filter_set_local_state('name', $this->activity2context->id, TEXTFILTER_OFF);
-        $this->assert_matches($modinfo);
+        filter_set_local_state('name', $activity2context->id, TEXTFILTER_OFF);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Disable at category.
-        filter_set_local_state('name', $this->catcontext->id, TEXTFILTER_OFF);
-        $this->assert_matches($modinfo);
+        filter_set_local_state('name', $catcontext->id, TEXTFILTER_OFF);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Enable for activity 1.
-        filter_set_local_state('name', $this->activity1context->id, TEXTFILTER_ON);
-        $this->assert_matches($modinfo);
+        filter_set_local_state('name', $activity1context->id, TEXTFILTER_ON);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Disable globally.
         filter_set_global_state('name', TEXTFILTER_DISABLED);
-        $this->assert_matches($modinfo);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Add another 2 filters.
         filter_set_global_state('frog', TEXTFILTER_ON);
         filter_set_global_state('zombie', TEXTFILTER_ON);
-        $this->assert_matches($modinfo);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Disable random one of these in each context.
-        filter_set_local_state('zombie', $this->activity1context->id, TEXTFILTER_OFF);
-        filter_set_local_state('frog', $this->activity2context->id, TEXTFILTER_OFF);
-        $this->assert_matches($modinfo);
+        filter_set_local_state('zombie', $activity1context->id, TEXTFILTER_OFF);
+        filter_set_local_state('frog', $activity2context->id, TEXTFILTER_OFF);
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
 
         // Now do some filter options.
-        filter_set_local_config('name', $this->activity1context->id, 'a', 'x');
-        filter_set_local_config('zombie', $this->activity1context->id, 'a', 'y');
-        filter_set_local_config('frog', $this->activity1context->id, 'a', 'z');
+        filter_set_local_config('name', $activity1context->id, 'a', 'x');
+        filter_set_local_config('zombie', $activity1context->id, 'a', 'y');
+        filter_set_local_config('frog', $activity1context->id, 'a', 'z');
         // These last two don't do anything as they are not at final level but I
         // thought it would be good to have that verified in test.
-        filter_set_local_config('frog', $this->coursecontext->id, 'q', 'x');
-        filter_set_local_config('frog', $this->catcontext->id, 'q', 'z');
-        $this->assert_matches($modinfo);
+        filter_set_local_config('frog', $coursecontext->id, 'q', 'x');
+        filter_set_local_config('frog', $catcontext->id, 'q', 'z');
+        $this->assert_matches($modinfo, $activity1context, $activity2context);
     }
 
     public function test_filter_delete_all_for_filter() {
         global $DB;
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
 
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
@@ -588,6 +688,8 @@ class core_filterlib_testcase extends advanced_testcase {
 
     public function test_filter_delete_all_for_context() {
         global $DB;
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
 
         // Setup fixture.
         filter_set_global_state('name', TEXTFILTER_ON);
@@ -606,6 +708,7 @@ class core_filterlib_testcase extends advanced_testcase {
 
     public function test_set() {
         global $CFG;
+        $this->resetAfterTest();
 
         $this->assertFileExists("$CFG->dirroot/filter/emailprotect"); // Any standard filter.
         $this->assertFileExists("$CFG->dirroot/filter/tidy");         // Any standard filter.
@@ -631,6 +734,7 @@ class core_filterlib_testcase extends advanced_testcase {
 
     public function test_unset_to_empty() {
         global $CFG;
+        $this->resetAfterTest();
 
         $this->assertFileExists("$CFG->dirroot/filter/tidy"); // Any standard filter.
 
@@ -646,6 +750,7 @@ class core_filterlib_testcase extends advanced_testcase {
 
     public function test_unset_multi() {
         global $CFG;
+        $this->resetAfterTest();
 
         $this->assertFileExists("$CFG->dirroot/filter/emailprotect"); // Any standard filter.
         $this->assertFileExists("$CFG->dirroot/filter/tidy");         // Any standard filter.
@@ -662,6 +767,7 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_filter_manager_instance() {
+        $this->resetAfterTest();
 
         set_config('perfdebug', 7);
         filter_manager::reset_caches();
@@ -677,18 +783,26 @@ class core_filterlib_testcase extends advanced_testcase {
     }
 
     public function test_filter_get_globally_enabled_default() {
+        $this->resetAfterTest();
         $enabledfilters = filter_get_globally_enabled();
         $this->assertArrayNotHasKey('glossary', $enabledfilters);
     }
 
     public function test_filter_get_globally_enabled_after_change() {
+        $this->resetAfterTest();
         filter_set_global_state('glossary', TEXTFILTER_ON);
         $enabledfilters = filter_get_globally_enabled();
         $this->assertArrayHasKey('glossary', $enabledfilters);
     }
 
     public function test_filter_get_globally_enabled_filters_with_config() {
-        $this->setup_available_in_context_tests();
+        $this->resetAfterTest();
+        $this->remove_all_filters_from_config(); // Remove all filters.
+        [
+            'syscontext' => $syscontext,
+            'childcontext' => $childcontext
+        ] = $this->setup_available_in_context_tests();
+        $this->remove_all_filters_from_config(); // Remove all filters.
 
         // Set few filters.
         filter_set_global_state('one', TEXTFILTER_ON);
@@ -696,18 +810,18 @@ class core_filterlib_testcase extends advanced_testcase {
         filter_set_global_state('two', TEXTFILTER_DISABLED);
 
         // Set global config.
-        filter_set_local_config('one', $this->syscontext->id, 'test1a', 'In root');
-        filter_set_local_config('one', $this->syscontext->id, 'test1b', 'In root');
-        filter_set_local_config('two', $this->syscontext->id, 'test2a', 'In root');
-        filter_set_local_config('two', $this->syscontext->id, 'test2b', 'In root');
+        filter_set_local_config('one', $syscontext->id, 'test1a', 'In root');
+        filter_set_local_config('one', $syscontext->id, 'test1b', 'In root');
+        filter_set_local_config('two', $syscontext->id, 'test2a', 'In root');
+        filter_set_local_config('two', $syscontext->id, 'test2b', 'In root');
 
         // Set child config.
-        filter_set_local_config('one', $this->childcontext->id, 'test1a', 'In child');
-        filter_set_local_config('one', $this->childcontext->id, 'test1b', 'In child');
-        filter_set_local_config('two', $this->childcontext->id, 'test2a', 'In child');
-        filter_set_local_config('two', $this->childcontext->id, 'test2b', 'In child');
-        filter_set_local_config('three', $this->childcontext->id, 'test3a', 'In child');
-        filter_set_local_config('three', $this->childcontext->id, 'test3b', 'In child');
+        filter_set_local_config('one', $childcontext->id, 'test1a', 'In child');
+        filter_set_local_config('one', $childcontext->id, 'test1b', 'In child');
+        filter_set_local_config('two', $childcontext->id, 'test2a', 'In child');
+        filter_set_local_config('two', $childcontext->id, 'test2b', 'In child');
+        filter_set_local_config('three', $childcontext->id, 'test3a', 'In child');
+        filter_set_local_config('three', $childcontext->id, 'test3b', 'In child');
 
         // Check.
         $actual = filter_get_globally_enabled_filters_with_config();
index 1d2ae40..47a9fec 100644 (file)
@@ -91,7 +91,7 @@ class lock_testcase extends advanced_testcase {
                     $lock2 = $lockfactory->get_lock('abc', 2);
                     $duration += microtime(true);
                     $this->assertFalse($lock2, 'Cannot get a stacked lock');
-                    $this->assertTrue($duration > 1, 'Lock should timeout after more than 1 second');
+                    $this->assertTrue($duration < 2.5, 'Lock should timeout after no more than 2 seconds');
 
                     // This should timeout almost instantly.
                     $duration = -microtime(true);
index 9064aa2..63fcf1a 100644 (file)
@@ -4,7 +4,7 @@
     <location>adodb</location>
     <name>AdoDB</name>
     <license>BSD/GPL</license>
-    <version>5.20.15</version>
+    <version>5.20.16</version>
     <licenseversion>2.1+</licenseversion>
   </library>
   <library>
index c410cd0..58a2ef1 100644 (file)
@@ -2,6 +2,8 @@ This files describes API changes in core libraries and APIs,
 information provided here is intended especially for developers.
 
 === 3.9 ===
+* behat_data_generators::the_following_exist() has been removed, please use
+  behat_data_generators::the_following_entities_exist() instead. See MDL-67691 for more info.
 * admin/tool/task/cli/adhoc_task.php now observers the concurrency limits.
   If you want to get the previous (unlimited) behavior, use the --ignorelimits switch).
 * Removed the following deprecated functions:
@@ -19,6 +21,17 @@ information provided here is intended especially for developers.
   - $plugin->incompatible = 36;
     incompatible takes a single int corresponding to the first incompatible branch. Any Moodle versions including and
     above this will be prevented from installing the plugin, and a message will be given when attempting installation.
+* Added the <component>_bulk_user_actions() callback which returns a list of custom action_links objects
+* Add 'required' admin flag for mod forms allows elements to be toggled between being required or not in admin settings.
+  - In mod settings, along with lock, advanced flags, the required flag can now be set with $setting->set_required_flag_options().
+    The name of the admin setting must be exactly the same as the mod_form element.
+  - Currently supported by:
+    - mod_assign
+    - mod_quiz
+* Added a native MySQL / MariaDB lock implementation
+* The database drivers (moodle_database and subclasses) don't need to implement get_columns() anymore.
+  They have to implement fetch_columns instead.
+* Added function cleanup_after_drop to the database_manager class to take care of all the cleanups that need to be done after a table is dropped.
 
 === 3.8 ===
 * Add CLI option to notify all cron tasks to stop: admin/cli/cron.php --stop
index 0b29e7c..c26fe8b 100644 (file)
@@ -1730,9 +1730,10 @@ class api {
      *
      * @param int $touserid the id of the message recipient
      * @param int|null $fromuserid the id of the message sender, null if all messages
+     * @param int|null $timecreatedto mark notifications created before this time as read
      * @return void
      */
-    public static function mark_all_notifications_as_read($touserid, $fromuserid = null) {
+    public static function mark_all_notifications_as_read($touserid, $fromuserid = null, $timecreatedto = null) {
         global $DB;
 
         $notificationsql = "SELECT n.*
@@ -1744,6 +1745,10 @@ class api {
             $notificationsql .= " AND useridfrom = ?";
             $notificationsparams[] = $fromuserid;
         }
+        if (!empty($timecreatedto)) {
+            $notificationsql .= " AND timecreated <= ?";
+            $notificationsparams[] = $timecreatedto;
+        }
 
         $notifications = $DB->get_recordset_sql($notificationsql, $notificationsparams);
         foreach ($notifications as $notification) {
index 3fbff4d..0b2ac87 100644 (file)
@@ -3255,6 +3255,9 @@ class core_message_external extends external_api {
                 'useridfrom' => new external_value(
                     PARAM_INT, 'the user id who send the message, 0 for any user. -10 or -20 for no-reply or support user',
                     VALUE_DEFAULT, 0),
+                'timecreatedto' => new external_value(
+                    PARAM_INT, 'mark messages created before this time as read, 0 for all messages',
+                    VALUE_DEFAULT, 0),
             )
         );
     }
@@ -3267,9 +3270,10 @@ class core_message_external extends external_api {
      * @throws moodle_exception
      * @param  int      $useridto       the user id who received the message
      * @param  int      $useridfrom     the user id who send the message. -10 or -20 for no-reply or support user
+     * @param  int      $timecreatedto  mark message created before this time as read, 0 for all messages
      * @return external_description
      */
-    public static function mark_all_notifications_as_read($useridto, $useridfrom) {
+    public static function mark_all_notifications_as_read($useridto, $useridfrom, $timecreatedto = 0) {
         global $USER;
 
         $params = self::validate_parameters(
@@ -3277,6 +3281,7 @@ class core_message_external extends external_api {
             array(
                 'useridto' => $useridto,
                 'useridfrom' => $useridfrom,
+                'timecreatedto' => $timecreatedto,
             )
         );
 
@@ -3285,6 +3290,7 @@ class core_message_external extends external_api {
 
         $useridto = $params['useridto'];
         $useridfrom = $params['useridfrom'];
+        $timecreatedto = $params['timecreatedto'];
 
         if (!empty($useridto)) {
             if (core_user::is_real_user($useridto)) {
@@ -3306,7 +3312,7 @@ class core_message_external extends external_api {
             throw new moodle_exception('accessdenied', 'admin');
         }
 
-        \core_message\api::mark_all_notifications_as_read($useridto, $useridfrom);
+        \core_message\api::mark_all_notifications_as_read($useridto, $useridfrom, $timecreatedto);
 
         return true;
     }
index b850e7f..b3cb81d 100644 (file)
@@ -784,39 +784,6 @@ function core_message_user_preferences() {
     return $preferences;
 }
 
-/**
- * Renders the popup.
- *
- * @param renderer_base $renderer
- * @return string The HTML
- */
-function core_message_render_navbar_output(\renderer_base $renderer) {
-    global $USER, $CFG;
-
-    // Early bail out conditions.
-    if (!isloggedin() || isguestuser() || user_not_fully_set_up($USER) ||
-        get_user_preferences('auth_forcepasswordchange') ||
-        (!$USER->policyagreed && !is_siteadmin() &&
-            ($manager = new \core_privacy\local\sitepolicy\manager()) && $manager->is_defined())) {
-        return '';
-    }
-
-    $output = '';
-
-    // Add the messages popover.
-    if (!empty($CFG->messaging)) {
-        $unreadcount = \core_message\api::count_unread_conversations($USER);
-        $requestcount = \core_message\api::get_received_contact_requests_count($USER->id);
-        $context = [
-            'userid' => $USER->id,
-            'unreadcount' => $unreadcount + $requestcount
-        ];
-        $output .= $renderer->render_from_template('core_message/message_popover', $context);
-    }
-
-    return $output;
-}
-
 /**
  * Render the message drawer to be included in the top of the body of each page.
  *
index aca97e2..0032380 100644 (file)
Binary files a/message/output/popup/amd/build/notification_popover_controller.min.js and b/message/output/popup/amd/build/notification_popover_controller.min.js differ
index 9ccabb5..daeb2bc 100644 (file)
Binary files a/message/output/popup/amd/build/notification_popover_controller.min.js.map and b/message/output/popup/amd/build/notification_popover_controller.min.js.map differ
index c1e44e6..a7a9a55 100644 (file)
@@ -52,6 +52,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/str', 'core/url',
 
         this.markAllReadButton = this.root.find(SELECTORS.MARK_ALL_READ_BUTTON);
         this.unreadCount = 0;
+        this.lastQueried = 0;
         this.userId = this.root.attr('data-userid');
         this.container = this.root.find(SELECTORS.ALL_NOTIFICATIONS_CONTAINER);
         this.limit = 20;
@@ -277,6 +278,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/str', 'core/url',
         return NotificationRepo.query(request).then(function(result) {
             var notifications = result.notifications;
             this.unreadCount = result.unreadcount;
+            this.lastQueried = Math.floor(new Date().getTime() / 1000);
             this.setLoadedAllContent(!notifications.length || notifications.length < this.limit);
             this.initialLoad = true;
             this.updateButtonAriaLabel();
@@ -303,7 +305,12 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/str', 'core/url',
     NotificationPopoverController.prototype.markAllAsRead = function() {
         this.markAllReadButton.addClass('loading');
 
-        return NotificationRepo.markAllAsRead({useridto: this.userId})
+        var request = {
+            useridto: this.userId,
+            timecreatedto: this.lastQueried,
+        };
+
+        return NotificationRepo.markAllAsRead(request)
             .then(function() {
                 this.unreadCount = 0;
                 this.root.find(SELECTORS.UNREAD_NOTIFICATION).removeClass('unread');
index fcfb369..dd3964e 100644 (file)
@@ -58,5 +58,16 @@ function message_popup_render_navbar_output(\renderer_base $renderer) {
         $output .= $renderer->render_from_template('message_popup/notification_popover', $context);
     }
 
+    // Add the messages popover.
+    if (!empty($CFG->messaging)) {
+        $unreadcount = \core_message\api::count_unread_conversations($USER);
+        $requestcount = \core_message\api::get_received_contact_requests_count($USER->id);
+        $context = [
+            'userid' => $USER->id,
+            'unreadcount' => $unreadcount + $requestcount
+        ];
+        $output .= $renderer->render_from_template('core_message/message_popover', $context);
+    }
+
     return $output;
 }
index 74ab859..40cda62 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019111800;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2020012300;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2019111200;        // Requires this Moodle version
 $plugin->component = 'message_popup';  // Full name of the plugin (used for diagnostics)
index 72e541a..8f63132 100644 (file)
@@ -35,7 +35,7 @@
     }
 
 }}
-<div class="float-right popover-region collapsed">
+<div class="popover-region collapsed" data-region="popover-region-messages">
     <a id="message-drawer-toggle-{{uniqid}}" class="nav-link d-inline-block popover-region-toggle position-relative" href="#"
             role="button">
         {{#pix}} t/message, core, {{#str}} togglemessagemenu, message {{/str}} {{/pix}}
index 1b8c3b0..cd8cf1a 100644 (file)
@@ -44,7 +44,7 @@ Feature: Manage contacts
     And I should see "Contact request sent"
     And I log out
     And I log in as "student4"
-    Then I should see "2" in the "//*[@data-region='count-container']" "xpath_element"
+    Then I should see "2" in the "//div[@data-region='popover-region-messages']//*[@data-region='count-container']" "xpath_element"
     And I open messaging
     And I click on "Contacts" "link"
     Then I should see "2" in the "//div[@data-region='view-contacts']//*[@data-region='contact-request-count']" "xpath_element"
@@ -69,7 +69,7 @@ Feature: Manage contacts
     And I should see "Contact request sent"
     And I log out
     And I log in as "student3"
-    Then I should see "1" in the "//*[@data-region='count-container']" "xpath_element"
+    Then I should see "1" in the "//div[@data-region='popover-region-messages']//*[@data-region='count-container']" "xpath_element"
     And I open messaging
     And I click on "Contacts" "link"
     Then I should see "1" in the "//div[@data-region='view-contacts']//*[@data-region='contact-request-count']" "xpath_element"
index 224a587..1f14399 100644 (file)
@@ -2117,6 +2117,46 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $this->assertCount(0, $unreadnotifications);
     }
 
+    public function test_mark_all_notifications_as_read_time_created_to() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $sender1 = $this->getDataGenerator()->create_user();
+        $sender2 = $this->getDataGenerator()->create_user();
+
+        $recipient = $this->getDataGenerator()->create_user();
+        $this->setUser($recipient);
+
+        // Record messages as sent on one second intervals.
+        $time = time();
+
+        $this->send_message($sender1, $recipient, 'Message 1', 1, $time);
+        $this->send_message($sender2, $recipient, 'Message 2', 1, $time + 1);
+        $this->send_message($sender1, $recipient, 'Message 3', 1, $time + 2);
+        $this->send_message($sender2, $recipient, 'Message 4', 1, $time + 3);
+
+        // Mark notifications sent from sender1 up until the second message; should only mark the first notification as read.
+        core_message_external::mark_all_notifications_as_read($recipient->id, $sender1->id, $time + 1);
+
+        $params = [$recipient->id];
+
+        $this->assertEquals(1, $DB->count_records_select('notifications', 'useridto = ? AND timeread IS NOT NULL', $params));
+        $this->assertEquals(3, $DB->count_records_select('notifications', 'useridto = ? AND timeread IS NULL', $params));
+
+        // Mark all notifications as read from any sender up to the time the third message was sent.
+        core_message_external::mark_all_notifications_as_read($recipient->id, 0, $time + 2);
+
+        $this->assertEquals(3, $DB->count_records_select('notifications', 'useridto = ? AND timeread IS NOT NULL', $params));
+        $this->assertEquals(1, $DB->count_records_select('notifications', 'useridto = ? AND timeread IS NULL', $params));
+
+        // Mark all notifications as read from any sender with a time after all messages were sent.
+        core_message_external::mark_all_notifications_as_read($recipient->id, 0, $time + 10);
+
+        $this->assertEquals(4, $DB->count_records_select('notifications', 'useridto = ? AND timeread IS NOT NULL', $params));
+        $this->assertEquals(0, $DB->count_records_select('notifications', 'useridto = ? AND timeread IS NULL', $params));
+    }
+
     /**
      * Test get_user_notification_preferences
      */
index 66bae37..f3cc290 100644 (file)
 }
 
 .path-mod-assign.jsenabled .expandsummaryicon {
-    display: inline;
+    display: inline-block;
 }
 
 .path-mod-assign .hidefull {
index 7a44ae7..42e66bc 100644 (file)
@@ -44,9 +44,8 @@ Feature: Students can edit or delete their forum posts within a set time limit
   Scenario: Time limit expires
     Given I log out
     And I log in as "admin"
-    And I navigate to "Security > Site security settings" in site administration
-    And I set the field "Maximum time to edit posts" to "1 minutes"
-    And I press "Save changes"
+    And the following config values are set as admin:
+      | maxeditingtime | 1 |
     And I am on "Course 1" course homepage with editing mode on
     And I add the "Recent activity" block
     And I log out
@@ -54,7