Skip to content

Commit

Permalink
MDL-77544 enrol_database: include start and end dates
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitriim committed Jul 20, 2024
1 parent 554a790 commit 62d2528
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 4 deletions.
4 changes: 4 additions & 0 deletions enrol/database/lang/en/enrol_database.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,15 @@
$string['localcoursefield'] = 'Local course field';
$string['localrolefield'] = 'Local role field';
$string['localuserfield'] = 'Local user field';
$string['newcourseenddate'] = 'New course end date field';
$string['newcourseenddate_desc'] = 'Optional. If not set, then the course end date will be set as configured default course duration. If set, then template value will be overridden.';
$string['newcoursetable'] = 'Remote new courses table';
$string['newcoursetable_desc'] = 'Specify of the name of the table that contains list of courses that should be created automatically. Empty means no courses are created.';
$string['newcoursecategory'] = 'New course category field';
$string['newcoursefullname'] = 'New course full name field';
$string['newcourseidnumber'] = 'New course ID number field';
$string['newcoursestartdate'] = 'New course start date field';
$string['newcoursestartdate_desc'] = 'Optional. If not set then, the course start date will be set to the current date. If set, then template value will be overridden.';
$string['newcourseshortname'] = 'New course short name field';
$string['pluginname'] = 'External database';
$string['pluginname_desc'] = 'You can use an external database (of nearly any kind) to control your enrolments. It is assumed your external database contains at least a field containing a course ID, and a field containing a user ID. These are compared against fields that you choose in the local course and user tables.';
Expand Down
64 changes: 63 additions & 1 deletion enrol/database/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -669,17 +669,24 @@ public function sync_courses(progress_trace $trace) {
return 1;
}

$courseconfig = get_config('moodlecourse');

$table = $this->get_config('newcoursetable');
$fullname = trim($this->get_config('newcoursefullname'));
$shortname = trim($this->get_config('newcourseshortname'));
$idnumber = trim($this->get_config('newcourseidnumber'));
$category = trim($this->get_config('newcoursecategory'));

$startdate = trim($this->get_config('newcoursestartdate'));
$enddate = trim($this->get_config('newcourseenddate'));

// Lowercased versions - necessary because we normalise the resultset with array_change_key_case().
$fullname_l = strtolower($fullname);
$shortname_l = strtolower($shortname);
$idnumber_l = strtolower($idnumber);
$category_l = strtolower($category);
$startdatelowercased = strtolower($startdate);
$enddatelowercased = strtolower($enddate);

$localcategoryfield = $this->get_config('localcategoryfield', 'id');
$defaultcategory = $this->get_config('defaultcategory');
Expand All @@ -698,6 +705,13 @@ public function sync_courses(progress_trace $trace) {
if ($idnumber) {
$sqlfields[] = $idnumber;
}
if ($startdate) {
$sqlfields[] = $startdate;
}
if ($enddate) {
$sqlfields[] = $enddate;
}

$sql = $this->db_get_sql($table, array(), $sqlfields, true);
$createcourses = array();
if ($rs = $extdb->Execute($sql)) {
Expand All @@ -722,6 +736,7 @@ public function sync_courses(progress_trace $trace) {
$course->fullname = $fields[$fullname_l];
$course->shortname = $fields[$shortname_l];
$course->idnumber = $idnumber ? $fields[$idnumber_l] : '';

if ($category) {
if (empty($fields[$category_l])) {
// Empty category means use default.
Expand All @@ -738,6 +753,35 @@ public function sync_courses(progress_trace $trace) {
} else {
$course->category = $defaultcategory;
}

if ($startdate) {
if (!empty($fields[$startdatelowercased])) {
$course->startdate = is_number($fields[$startdatelowercased])
? $fields[$startdatelowercased]
: strtotime($fields[$startdatelowercased]);

// Broken start date. Stop syncing this course.
if ($course->startdate === false) {
$trace->output('error: invalid external course start date value: ' . json_encode($fields), 1);
continue;
}
}
}

if ($enddate) {
if (!empty($fields[$enddatelowercased])) {
$course->enddate = is_number($fields[$enddatelowercased])
? $fields[$enddatelowercased]
: strtotime($fields[$enddatelowercased]);

// Broken end date. Stop syncing this course.
if ($course->enddate === false) {
$trace->output('error: invalid external course end date value: ' . json_encode($fields), 1);
continue;
}
}
}

$createcourses[] = $course;
}
}
Expand Down Expand Up @@ -769,7 +813,6 @@ public function sync_courses(progress_trace $trace) {
}
}
if (!$template) {
$courseconfig = get_config('moodlecourse');
$template = new stdClass();
$template->summary = '';
$template->summaryformat = FORMAT_HTML;
Expand Down Expand Up @@ -798,6 +841,25 @@ public function sync_courses(progress_trace $trace) {
$newcourse->idnumber = $fields->idnumber;
$newcourse->category = $fields->category;

if (isset($fields->startdate)) {
$newcourse->startdate = $fields->startdate;
}

if (isset($fields->enddate)) {
// Validating end date.
if ($fields->enddate > 0 && $newcourse->startdate > $fields->enddate) {
$trace->output(
"can not insert new course, the end date must be after the start date: " . $newcourse->shortname, 1
);
continue;
}
$newcourse->enddate = $fields->enddate;
} else {
if ($courseconfig->courseenddateenabled) {
$newcourse->enddate = $newcourse->startdate + $courseconfig->courseduration;
}
}

// Detect duplicate data once again, above we can not find duplicates
// in external data using DB collation rules...
if ($DB->record_exists('course', array('shortname' => $newcourse->shortname))) {
Expand Down
10 changes: 10 additions & 0 deletions enrol/database/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@

$settings->add(new admin_setting_configtext('enrol_database/newcourseidnumber', get_string('newcourseidnumber', 'enrol_database'), '', 'idnumber'));

$settings->add(new admin_setting_configtext(
'enrol_database/newcoursestartdate',
get_string('newcoursestartdate', 'enrol_database'),
get_string('newcoursestartdate_desc', 'enrol_database'), ''));

$settings->add(new admin_setting_configtext(
'enrol_database/newcourseenddate',
get_string('newcourseenddate', 'enrol_database'),
get_string('newcourseenddate_desc', 'enrol_database'), ''));

$settings->add(new admin_setting_configtext('enrol_database/newcoursecategory', get_string('newcoursecategory', 'enrol_database'), '', ''));

$settings->add(new admin_settings_coursecat_select('enrol_database/defaultcategory',
Expand Down
145 changes: 143 additions & 2 deletions enrol/database/tests/sync_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ protected function init_enrol_database() {
$table->add_field('shortname', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('idnumber', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('category', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('startdate', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_field('enddate', XMLDB_TYPE_CHAR, '255', null, null, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
Expand Down Expand Up @@ -441,7 +443,6 @@ public function test_sync_users(): void {

// Test basic enrol sync for one user after login.

$this->reset_enrol_database();
$plugin->set_config('localcoursefield', 'idnumber');
$plugin->set_config('localuserfield', 'idnumber');
$plugin->set_config('localrolefield', 'shortname');
Expand Down Expand Up @@ -799,8 +800,148 @@ public function test_sync_courses(): void {
$this->assertEquals(1+2+1+4+1+count(self::$courses), $DB->count_records('course'));
$this->assertTrue($DB->record_exists('course', array('idnumber' => 'ncid9')));


// Final cleanup - remove extra tables, fixtures and caches.
$this->cleanup_enrol_database();
}

/**
* Test syncing courses with start and end dates.
*
* @covers \enrol_database_plugin::sync_courses
*/
public function test_sync_courses_start_end_dates(): void {
global $DB;

$this->resetAfterTest();
$this->preventResetByRollback();
$this->init_enrol_database();

$courseconfig = get_config('moodlecourse');
$nextyear = (int) date('Y') + 1;
$prev = (int) date('Y') - 1;

$midnightstartdate = usergetmidnight(time());
$midnightenddate = usergetmidnight(time()) + $courseconfig->courseduration;

$plugin = enrol_get_plugin('database');

$trace = new \null_progress_trace();

$course1 = ['fullname' => 'C1', 'shortname' => 'c1', 'idnumber' => '', 'startdate' => 0,
'enddate' => 0];
$course2 = ['fullname' => 'C2', 'shortname' => 'c2', 'idnumber' => '', 'startdate' => null,
'enddate' => null];
// This course won't be created. Broken start date.
$course3 = ['fullname' => 'C3', 'shortname' => 'c3', 'idnumber' => '', 'startdate' => 'not date',
'enddate' => 0];
// This course won't be created. Broken end date.
$course4 = ['fullname' => 'C4', 'shortname' => 'c4', 'idnumber' => '', 'startdate' => 0,
'enddate' => 'not date'];
// This course won't be created. Start date after end date.
$course5 = ['fullname' => 'C5', 'shortname' => 'c5', 'idnumber' => '', 'startdate' => '12.05.2024',
'enddate' => '12.05.2021'];
$course6 = ['fullname' => 'C6', 'shortname' => 'c6', 'idnumber' => '', 'startdate' => '2024-05-22',
'enddate' => '2027-05-12'];
$course7 = ['fullname' => 'C7', 'shortname' => 'c7', 'idnumber' => '', 'startdate' => null,
'enddate' => '12.05.' . $nextyear];
$course8 = ['fullname' => 'C8', 'shortname' => 'c8', 'idnumber' => '', 'startdate' => '12.05.2024',
'enddate' => null];
// This course won't be created. Start date is not set, but it should be set to date after end date.
$course9 = ['fullname' => 'C9', 'shortname' => 'c9', 'idnumber' => '', 'startdate' => null,
'enddate' => '12.05.' . $prev];

$DB->insert_record('enrol_database_test_courses', $course1);
$DB->insert_record('enrol_database_test_courses', $course2);
$DB->insert_record('enrol_database_test_courses', $course3);
$DB->insert_record('enrol_database_test_courses', $course4);
$DB->insert_record('enrol_database_test_courses', $course5);
$DB->insert_record('enrol_database_test_courses', $course6);
$DB->insert_record('enrol_database_test_courses', $course7);
$DB->insert_record('enrol_database_test_courses', $course8);
$DB->insert_record('enrol_database_test_courses', $course9);

// Mess with case as we need to check that fields are lower cased.
$plugin->set_config('newcoursestartdate', 'StartDaTE');
$plugin->set_config('newcourseenddate', 'ENDdATE');

$plugin->sync_courses($trace);

// Course 3, course 4, course 5 and course 9 should not be created.
$this->assertTrue($DB->record_exists('course', ['shortname' => $course1['shortname']]));
$this->assertTrue($DB->record_exists('course', ['shortname' => $course2['shortname']]));
$this->assertFalse($DB->record_exists('course', ['shortname' => $course3['shortname']]));
$this->assertFalse($DB->record_exists('course', ['shortname' => $course4['shortname']]));
$this->assertFalse($DB->record_exists('course', ['shortname' => $course5['shortname']]));
$this->assertTrue($DB->record_exists('course', ['shortname' => $course6['shortname']]));
$this->assertTrue($DB->record_exists('course', ['shortname' => $course7['shortname']]));
$this->assertTrue($DB->record_exists('course', ['shortname' => $course8['shortname']]));
$this->assertFalse($DB->record_exists('course', ['shortname' => $course9['shortname']]));

// Check dates for created courses.
$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course1['shortname']]));
$this->assertEquals($midnightenddate, $DB->get_field('course', 'enddate', ['shortname' => $course1['shortname']]));

$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course2['shortname']]));
$this->assertEquals($midnightenddate, $DB->get_field('course', 'enddate', ['shortname' => $course2['shortname']]));

$this->assertEquals(strtotime('22.05.2024'), $DB->get_field('course', 'startdate', ['shortname' => $course6['shortname']]));
$this->assertEquals(strtotime('12.05.2027'), $DB->get_field('course', 'enddate', ['shortname' => $course6['shortname']]));

$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course7['shortname']]));
$expected = strtotime('12.05.' . $nextyear);
$this->assertEquals($expected, $DB->get_field('course', 'enddate', ['shortname' => $course7['shortname']]));

$this->assertEquals(strtotime('12.05.2024'), $DB->get_field('course', 'startdate', ['shortname' => $course8['shortname']]));
$expected = strtotime('12.05.2024') + $courseconfig->courseduration;
$this->assertEquals($expected, $DB->get_field('course', 'enddate', ['shortname' => $course8['shortname']]));

// Push course with dates as timestamp.
$course10 = ['fullname' => 'C10', 'shortname' => 'c10', 'idnumber' => '', 'startdate' => 1810051200,
'enddate' => 1810051211];
$DB->insert_record('enrol_database_test_courses', $course10);

$plugin->sync_courses($trace);

$this->assertTrue($DB->record_exists('course', ['shortname' => $course10['shortname']]));
$this->assertEquals(1810051200, $DB->get_field('course', 'startdate', ['shortname' => $course10['shortname']]));
$this->assertEquals(1810051211, $DB->get_field('course', 'enddate', ['shortname' => $course10['shortname']]));

// Push course with broken dates, but delete dates from plugin configuration before syncing.
$course11 = ['fullname' => 'C11', 'shortname' => 'c11', 'idnumber' => '', 'startdate' => 'not date',
'enddate' => 'not date'];
$DB->insert_record('enrol_database_test_courses', $course11);

$plugin->set_config('newcoursestartdate', '');
$plugin->set_config('newcourseenddate', '');
$plugin->sync_courses($trace);

$this->assertTrue($DB->record_exists('course', ['shortname' => $course11['shortname']]));
$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course11['shortname']]));
$this->assertEquals($midnightenddate, $DB->get_field('course', 'enddate', ['shortname' => $course11['shortname']]));

// Push courses with correct dates, but set date configuration to not existing date fields.
$course12 = ['fullname' => 'C12', 'shortname' => 'c12', 'idnumber' => '', 'startdate' => '2024-05-22',
'enddate' => '2027-05-12'];
$DB->insert_record('enrol_database_test_courses', $course11);

$plugin->set_config('newcoursestartdate', 'startdate');
$plugin->set_config('newcourseenddate', 'ed');
$plugin->sync_courses($trace);

// Course should not be synced to prevent setting up incorrect dates.
$this->assertFalse($DB->record_exists('course', ['shortname' => $course12['shortname']]));

$course13 = ['fullname' => 'C13', 'shortname' => 'c13', 'idnumber' => '', 'startdate' => '2024-05-22',
'enddate' => '2027-05-12'];
$DB->insert_record('enrol_database_test_courses', $course11);

$plugin->set_config('newcoursestartdate', 'sd');
$plugin->set_config('newcourseenddate', 'enddate');
$plugin->sync_courses($trace);

// Course should not be synced to prevent setting up incorrect dates.
$this->assertFalse($DB->record_exists('course', ['shortname' => $course13['shortname']]));

$this->cleanup_enrol_database();
}
}
2 changes: 1 addition & 1 deletion enrol/database/version.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@

defined('MOODLE_INTERNAL') || die();

$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2024042201; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'enrol_database'; // Full name of the plugin (used for diagnostics)

0 comments on commit 62d2528

Please sign in to comment.