diff --git a/enrol/database/db/upgrade.php b/enrol/database/db/upgrade.php index ce8703aa542..d3db1dedcbd 100644 --- a/enrol/database/db/upgrade.php +++ b/enrol/database/db/upgrade.php @@ -23,6 +23,7 @@ */ function xmldb_enrol_database_upgrade($oldversion) { + global $DB; // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. @@ -38,5 +39,55 @@ function xmldb_enrol_database_upgrade($oldversion) { // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2025041401) { + // Remove duplicated enrolment records, keeping only the earliest records. + $transaction = $DB->start_delegated_transaction(); + $courses = $DB->get_records_sql( + " + SELECT courseid + FROM {enrol} + WHERE enrol = 'database' + GROUP BY courseid + HAVING COUNT(*) > 1" + ); + foreach ($courses as $course) { + $instances = $DB->get_records('enrol', ['enrol' => 'database', 'courseid' => $course->courseid], 'id ASC'); + $idtokeep = array_key_first($instances); + $idstodelete = array_slice(array_keys($instances), 1); + [$insql, $inparams] = $DB->get_in_or_equal($idstodelete, SQL_PARAMS_NAMED); + + // Migrate enrolments where possible. + $DB->execute( + " + UPDATE {user_enrolments} ue + SET enrolid = :idtokeep1 + WHERE ue.enrolid $insql + AND NOT EXISTS ( + SELECT 1 + FROM {user_enrolments} ue2 + WHERE ue2.userid = ue.userid + AND ue2.enrolid = :idtokeep2)", + array_merge($inparams, ['idtokeep1' => $idtokeep, 'idtokeep2' => $idtokeep]) + ); + + $DB->delete_records_select('user_enrolments', "enrolid $insql", $inparams); + + // Migrate role assignments. + $DB->execute( + " + UPDATE {role_assignments} + SET itemid = :idtokeep + WHERE component = :component + AND itemid $insql", + array_merge($inparams, ['component' => 'enrol_database', 'idtokeep' => $idtokeep]) + ); + $DB->delete_records_select('role_assignments', "itemid $insql", $inparams); + + $DB->delete_records_list('enrol', 'id', $idstodelete); + } + $transaction->allow_commit(); + upgrade_plugin_savepoint(true, 2025041401, 'enrol', 'database'); + } + return true; } diff --git a/enrol/database/lib.php b/enrol/database/lib.php index ec3d8d65e64..5b2173189f1 100644 --- a/enrol/database/lib.php +++ b/enrol/database/lib.php @@ -186,8 +186,37 @@ class enrol_database_plugin extends enrol_plugin { continue; } - $enrolid = $this->add_instance($course); - $instances[$course->id] = $DB->get_record('enrol', array('id'=>$enrolid)); + $timeout = 5; + $locktype = 'enrol_database_user_enrolments'; + $resource = 'course:' . $course->id; + $lockfactory = \core\lock\lock_config::get_lock_factory($locktype); + if ($lock = $lockfactory->get_lock($resource, $timeout)) { + try { + $instance = $DB->get_record('enrol', ['enrol' => 'database', 'courseid' => $course->id]); + if (!$instance) { + $enrolid = $this->add_instance($course); + $instance = $DB->get_record('enrol', ['id' => $enrolid]); + } + } finally { + $lock->release(); + } + } else { + // Attempt to reuse an existing record added by another process during race condition. + if ($instance = $DB->get_record('enrol', ['enrol' => 'database', 'courseid' => $course->id])) { + $instances[$course->id] = $instance; + continue; + } else { + // Give up. + throw new moodle_exception( + 'locktimeout', + 'enrol_database', + '', + null, + 'Could not create database enrolment instance for course ' . $course->id + ); + } + } + $instances[$course->id] = $instance; } } $rs->Close(); diff --git a/enrol/database/version.php b/enrol/database/version.php index 1af8d05844e..f405d70e2d3 100644 --- a/enrol/database/version.php +++ b/enrol/database/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2025041401; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'enrol_database'; // Full name of the plugin (used for diagnostics)