Skip to content

Commit 304e629

Browse files
committed
MDL-77544 enrol_database: include start and end dates
1 parent a75365f commit 304e629

File tree

5 files changed

+221
-4
lines changed

5 files changed

+221
-4
lines changed

enrol/database/lang/en/enrol_database.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,15 @@
4949
$string['localcoursefield'] = 'Local course field';
5050
$string['localrolefield'] = 'Local role field';
5151
$string['localuserfield'] = 'Local user field';
52+
$string['newcourseenddate'] = 'New course end date field';
53+
$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.';
5254
$string['newcoursetable'] = 'Remote new courses table';
5355
$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.';
5456
$string['newcoursecategory'] = 'New course category field';
5557
$string['newcoursefullname'] = 'New course full name field';
5658
$string['newcourseidnumber'] = 'New course ID number field';
59+
$string['newcoursestartdate'] = 'New course start date field';
60+
$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.';
5761
$string['newcourseshortname'] = 'New course short name field';
5862
$string['pluginname'] = 'External database';
5963
$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.';

enrol/database/lib.php

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,17 +669,24 @@ public function sync_courses(progress_trace $trace) {
669669
return 1;
670670
}
671671

672+
$courseconfig = get_config('moodlecourse');
673+
672674
$table = $this->get_config('newcoursetable');
673675
$fullname = trim($this->get_config('newcoursefullname'));
674676
$shortname = trim($this->get_config('newcourseshortname'));
675677
$idnumber = trim($this->get_config('newcourseidnumber'));
676678
$category = trim($this->get_config('newcoursecategory'));
677679

680+
$startdate = trim($this->get_config('newcoursestartdate'));
681+
$enddate = trim($this->get_config('newcourseenddate'));
682+
678683
// Lowercased versions - necessary because we normalise the resultset with array_change_key_case().
679684
$fullname_l = strtolower($fullname);
680685
$shortname_l = strtolower($shortname);
681686
$idnumber_l = strtolower($idnumber);
682687
$category_l = strtolower($category);
688+
$startdatelowercased = strtolower($startdate);
689+
$enddatelowercased = strtolower($enddate);
683690

684691
$localcategoryfield = $this->get_config('localcategoryfield', 'id');
685692
$defaultcategory = $this->get_config('defaultcategory');
@@ -698,6 +705,13 @@ public function sync_courses(progress_trace $trace) {
698705
if ($idnumber) {
699706
$sqlfields[] = $idnumber;
700707
}
708+
if ($startdate) {
709+
$sqlfields[] = $startdate;
710+
}
711+
if ($enddate) {
712+
$sqlfields[] = $enddate;
713+
}
714+
701715
$sql = $this->db_get_sql($table, array(), $sqlfields, true);
702716
$createcourses = array();
703717
if ($rs = $extdb->Execute($sql)) {
@@ -722,6 +736,7 @@ public function sync_courses(progress_trace $trace) {
722736
$course->fullname = $fields[$fullname_l];
723737
$course->shortname = $fields[$shortname_l];
724738
$course->idnumber = $idnumber ? $fields[$idnumber_l] : '';
739+
725740
if ($category) {
726741
if (empty($fields[$category_l])) {
727742
// Empty category means use default.
@@ -738,6 +753,35 @@ public function sync_courses(progress_trace $trace) {
738753
} else {
739754
$course->category = $defaultcategory;
740755
}
756+
757+
if ($startdate) {
758+
if (!empty($fields[$startdatelowercased])) {
759+
$course->startdate = is_number($fields[$startdatelowercased])
760+
? $fields[$startdatelowercased]
761+
: strtotime($fields[$startdatelowercased]);
762+
763+
// Broken start date. Stop syncing this course.
764+
if ($course->startdate === false) {
765+
$trace->output('error: invalid external course start date value: ' . json_encode($fields), 1);
766+
continue;
767+
}
768+
}
769+
}
770+
771+
if ($enddate) {
772+
if (!empty($fields[$enddatelowercased])) {
773+
$course->enddate = is_number($fields[$enddatelowercased])
774+
? $fields[$enddatelowercased]
775+
: strtotime($fields[$enddatelowercased]);
776+
777+
// Broken end date. Stop syncing this course.
778+
if ($course->enddate === false) {
779+
$trace->output('error: invalid external course end date value: ' . json_encode($fields), 1);
780+
continue;
781+
}
782+
}
783+
}
784+
741785
$createcourses[] = $course;
742786
}
743787
}
@@ -769,7 +813,6 @@ public function sync_courses(progress_trace $trace) {
769813
}
770814
}
771815
if (!$template) {
772-
$courseconfig = get_config('moodlecourse');
773816
$template = new stdClass();
774817
$template->summary = '';
775818
$template->summaryformat = FORMAT_HTML;
@@ -798,6 +841,25 @@ public function sync_courses(progress_trace $trace) {
798841
$newcourse->idnumber = $fields->idnumber;
799842
$newcourse->category = $fields->category;
800843

844+
if (isset($fields->startdate)) {
845+
$newcourse->startdate = $fields->startdate;
846+
}
847+
848+
if (isset($fields->enddate)) {
849+
// Validating end date.
850+
if ($fields->enddate > 0 && $newcourse->startdate > $fields->enddate) {
851+
$trace->output(
852+
"can not insert new course, the end date must be after the start date: " . $newcourse->shortname, 1
853+
);
854+
continue;
855+
}
856+
$newcourse->enddate = $fields->enddate;
857+
} else {
858+
if ($courseconfig->courseenddateenabled) {
859+
$newcourse->enddate = $newcourse->startdate + $courseconfig->courseduration;
860+
}
861+
}
862+
801863
// Detect duplicate data once again, above we can not find duplicates
802864
// in external data using DB collation rules...
803865
if ($DB->record_exists('course', array('shortname' => $newcourse->shortname))) {

enrol/database/settings.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@
113113

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

116+
$settings->add(new admin_setting_configtext(
117+
'enrol_database/newcoursestartdate',
118+
get_string('newcoursestartdate', 'enrol_database'),
119+
get_string('newcoursestartdate_desc', 'enrol_database'), ''));
120+
121+
$settings->add(new admin_setting_configtext(
122+
'enrol_database/newcourseenddate',
123+
get_string('newcourseenddate', 'enrol_database'),
124+
get_string('newcourseenddate_desc', 'enrol_database'), ''));
125+
116126
$settings->add(new admin_setting_configtext('enrol_database/newcoursecategory', get_string('newcoursecategory', 'enrol_database'), '', ''));
117127

118128
$settings->add(new admin_settings_coursecat_select('enrol_database/defaultcategory',

enrol/database/tests/sync_test.php

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ protected function init_enrol_database() {
151151
$table->add_field('shortname', XMLDB_TYPE_CHAR, '255', null, null, null);
152152
$table->add_field('idnumber', XMLDB_TYPE_CHAR, '255', null, null, null);
153153
$table->add_field('category', XMLDB_TYPE_CHAR, '255', null, null, null);
154+
$table->add_field('startdate', XMLDB_TYPE_CHAR, '255', null, null, null);
155+
$table->add_field('enddate', XMLDB_TYPE_CHAR, '255', null, null, null);
154156
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
155157
if ($dbman->table_exists($table)) {
156158
$dbman->drop_table($table);
@@ -441,7 +443,6 @@ public function test_sync_users(): void {
441443

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

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

802-
803803
// Final cleanup - remove extra tables, fixtures and caches.
804804
$this->cleanup_enrol_database();
805805
}
806+
807+
/**
808+
* Test syncing courses with start and end dates.
809+
*
810+
* @covers \enrol_database_plugin::sync_courses
811+
*/
812+
public function test_sync_courses_start_end_dates(): void {
813+
global $DB;
814+
815+
$this->resetAfterTest();
816+
$this->preventResetByRollback();
817+
$this->init_enrol_database();
818+
819+
$courseconfig = get_config('moodlecourse');
820+
$nextyear = (int) date('Y') + 1;
821+
$prev = (int) date('Y') - 1;
822+
823+
$midnightstartdate = usergetmidnight(time());
824+
$midnightenddate = usergetmidnight(time()) + $courseconfig->courseduration;
825+
826+
$plugin = enrol_get_plugin('database');
827+
828+
$trace = new \null_progress_trace();
829+
830+
$course1 = ['fullname' => 'C1', 'shortname' => 'c1', 'idnumber' => '', 'startdate' => 0,
831+
'enddate' => 0];
832+
$course2 = ['fullname' => 'C2', 'shortname' => 'c2', 'idnumber' => '', 'startdate' => null,
833+
'enddate' => null];
834+
// This course won't be created. Broken start date.
835+
$course3 = ['fullname' => 'C3', 'shortname' => 'c3', 'idnumber' => '', 'startdate' => 'not date',
836+
'enddate' => 0];
837+
// This course won't be created. Broken end date.
838+
$course4 = ['fullname' => 'C4', 'shortname' => 'c4', 'idnumber' => '', 'startdate' => 0,
839+
'enddate' => 'not date'];
840+
// This course won't be created. Start date after end date.
841+
$course5 = ['fullname' => 'C5', 'shortname' => 'c5', 'idnumber' => '', 'startdate' => '12.05.2024',
842+
'enddate' => '12.05.2021'];
843+
$course6 = ['fullname' => 'C6', 'shortname' => 'c6', 'idnumber' => '', 'startdate' => '2024-05-22',
844+
'enddate' => '2027-05-12'];
845+
$course7 = ['fullname' => 'C7', 'shortname' => 'c7', 'idnumber' => '', 'startdate' => null,
846+
'enddate' => '12.05.' . $nextyear];
847+
$course8 = ['fullname' => 'C8', 'shortname' => 'c8', 'idnumber' => '', 'startdate' => '12.05.2024',
848+
'enddate' => null];
849+
// This course won't be created. Start date is not set, but it should be set to date after end date.
850+
$course9 = ['fullname' => 'C9', 'shortname' => 'c9', 'idnumber' => '', 'startdate' => null,
851+
'enddate' => '12.05.' . $prev];
852+
853+
$DB->insert_record('enrol_database_test_courses', $course1);
854+
$DB->insert_record('enrol_database_test_courses', $course2);
855+
$DB->insert_record('enrol_database_test_courses', $course3);
856+
$DB->insert_record('enrol_database_test_courses', $course4);
857+
$DB->insert_record('enrol_database_test_courses', $course5);
858+
$DB->insert_record('enrol_database_test_courses', $course6);
859+
$DB->insert_record('enrol_database_test_courses', $course7);
860+
$DB->insert_record('enrol_database_test_courses', $course8);
861+
$DB->insert_record('enrol_database_test_courses', $course9);
862+
863+
// Mess with case as we need to check that fields are lower cased.
864+
$plugin->set_config('newcoursestartdate', 'StartDaTE');
865+
$plugin->set_config('newcourseenddate', 'ENDdATE');
866+
867+
$plugin->sync_courses($trace);
868+
869+
// Course 3, course 4, course 5 and course 9 should not be created.
870+
$this->assertTrue($DB->record_exists('course', ['shortname' => $course1['shortname']]));
871+
$this->assertTrue($DB->record_exists('course', ['shortname' => $course2['shortname']]));
872+
$this->assertFalse($DB->record_exists('course', ['shortname' => $course3['shortname']]));
873+
$this->assertFalse($DB->record_exists('course', ['shortname' => $course4['shortname']]));
874+
$this->assertFalse($DB->record_exists('course', ['shortname' => $course5['shortname']]));
875+
$this->assertTrue($DB->record_exists('course', ['shortname' => $course6['shortname']]));
876+
$this->assertTrue($DB->record_exists('course', ['shortname' => $course7['shortname']]));
877+
$this->assertTrue($DB->record_exists('course', ['shortname' => $course8['shortname']]));
878+
$this->assertFalse($DB->record_exists('course', ['shortname' => $course9['shortname']]));
879+
880+
// Check dates for created courses.
881+
$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course1['shortname']]));
882+
$this->assertEquals($midnightenddate, $DB->get_field('course', 'enddate', ['shortname' => $course1['shortname']]));
883+
884+
$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course2['shortname']]));
885+
$this->assertEquals($midnightenddate, $DB->get_field('course', 'enddate', ['shortname' => $course2['shortname']]));
886+
887+
$this->assertEquals(strtotime('22.05.2024'), $DB->get_field('course', 'startdate', ['shortname' => $course6['shortname']]));
888+
$this->assertEquals(strtotime('12.05.2027'), $DB->get_field('course', 'enddate', ['shortname' => $course6['shortname']]));
889+
890+
$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course7['shortname']]));
891+
$expected = strtotime('12.05.' . $nextyear);
892+
$this->assertEquals($expected, $DB->get_field('course', 'enddate', ['shortname' => $course7['shortname']]));
893+
894+
$this->assertEquals(strtotime('12.05.2024'), $DB->get_field('course', 'startdate', ['shortname' => $course8['shortname']]));
895+
$expected = strtotime('12.05.2024') + $courseconfig->courseduration;
896+
$this->assertEquals($expected, $DB->get_field('course', 'enddate', ['shortname' => $course8['shortname']]));
897+
898+
// Push course with dates as timestamp.
899+
$course10 = ['fullname' => 'C10', 'shortname' => 'c10', 'idnumber' => '', 'startdate' => 1810051200,
900+
'enddate' => 1810051211];
901+
$DB->insert_record('enrol_database_test_courses', $course10);
902+
903+
$plugin->sync_courses($trace);
904+
905+
$this->assertTrue($DB->record_exists('course', ['shortname' => $course10['shortname']]));
906+
$this->assertEquals(1810051200, $DB->get_field('course', 'startdate', ['shortname' => $course10['shortname']]));
907+
$this->assertEquals(1810051211, $DB->get_field('course', 'enddate', ['shortname' => $course10['shortname']]));
908+
909+
// Push course with broken dates, but delete dates from plugin configuration before syncing.
910+
$course11 = ['fullname' => 'C11', 'shortname' => 'c11', 'idnumber' => '', 'startdate' => 'not date',
911+
'enddate' => 'not date'];
912+
$DB->insert_record('enrol_database_test_courses', $course11);
913+
914+
$plugin->set_config('newcoursestartdate', '');
915+
$plugin->set_config('newcourseenddate', '');
916+
$plugin->sync_courses($trace);
917+
918+
$this->assertTrue($DB->record_exists('course', ['shortname' => $course11['shortname']]));
919+
$this->assertEquals($midnightstartdate, $DB->get_field('course', 'startdate', ['shortname' => $course11['shortname']]));
920+
$this->assertEquals($midnightenddate, $DB->get_field('course', 'enddate', ['shortname' => $course11['shortname']]));
921+
922+
// Push courses with correct dates, but set date configuration to not existing date fields.
923+
$course12 = ['fullname' => 'C12', 'shortname' => 'c12', 'idnumber' => '', 'startdate' => '2024-05-22',
924+
'enddate' => '2027-05-12'];
925+
$DB->insert_record('enrol_database_test_courses', $course11);
926+
927+
$plugin->set_config('newcoursestartdate', 'startdate');
928+
$plugin->set_config('newcourseenddate', 'ed');
929+
$plugin->sync_courses($trace);
930+
931+
// Course should not be synced to prevent setting up incorrect dates.
932+
$this->assertFalse($DB->record_exists('course', ['shortname' => $course12['shortname']]));
933+
934+
$course13 = ['fullname' => 'C13', 'shortname' => 'c13', 'idnumber' => '', 'startdate' => '2024-05-22',
935+
'enddate' => '2027-05-12'];
936+
$DB->insert_record('enrol_database_test_courses', $course11);
937+
938+
$plugin->set_config('newcoursestartdate', 'sd');
939+
$plugin->set_config('newcourseenddate', 'enddate');
940+
$plugin->sync_courses($trace);
941+
942+
// Course should not be synced to prevent setting up incorrect dates.
943+
$this->assertFalse($DB->record_exists('course', ['shortname' => $course13['shortname']]));
944+
945+
$this->cleanup_enrol_database();
946+
}
806947
}

enrol/database/version.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424

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

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

0 commit comments

Comments
 (0)