Skip to content

Commit

Permalink
MDL-31356 enrol_imsenterprise: New Features
Browse files Browse the repository at this point in the history
* Feature 1: Allow nested categories when creating courses.
* Feature 2: Allow updates to course Full Name and Short Name.
* Feature 3: Allow setting authentication type for users.
* Feature 4: Allow updates to users.

Also added tests and updated course update routines so it doesn't
muck with DB directly.
  • Loading branch information
perlwiz authored and taboubi committed Jul 14, 2016
1 parent 36a19ec commit 93fdbb4
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 33 deletions.
6 changes: 6 additions & 0 deletions enrol/imsenterprise/lang/en/enrol_imsenterprise.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
$string['allowunenrol'] = 'Allow the IMS data to <strong>unenrol</strong> students/teachers';
$string['allowunenrol_desc'] = 'If enabled, course enrolments will be removed when specified in the Enterprise data.';
$string['basicsettings'] = 'Basic settings';
$string['categoryseparator'] = 'Category Separator Character';
$string['categoryseparator_desc'] = 'You can create nested categories (if you allow category creation). ';
$string['coursesettings'] = 'Course data options';
$string['createnewcategories'] = 'Create new (hidden) course categories if not found in Moodle';
$string['createnewcategories_desc'] = 'If the <org><orgunit> element is present in a course\'s incoming data, its content will be used to specify a category if the course is to be created from scratch. The plugin will NOT re-categorise existing courses.
Expand Down Expand Up @@ -75,6 +77,10 @@
Some student information systems fail to output the <userid> field. If this is the case, you should enable this setting to allow for using the <sourcedid> as the Moodle user ID. Otherwise, leave this setting disabled.';
$string['truncatecoursecodes'] = 'Truncate course codes to this length';
$string['truncatecoursecodes_desc'] = 'In some situations you may have course codes which you wish to truncate to a specified length before processing. If so, enter the number of characters in this box. Otherwise, leave the box blank and no truncation will occur.';
$string['updatecourses'] = 'Update course full names';
$string['updatecourses_desc'] = 'If enabled, the IMS Enterprise enrolment plugin can update course full names (if the "recstatus" flag is set to 2, which represents an update).';
$string['updateusers'] = 'Update user accounts when specified in IMS data';
$string['updateusers_desc'] = 'If enabled, IMS Enterprise enrolment data can specify changes to user accounts (if the "recstatus" flag is set to 2, which represents an update).';
$string['usecapitafix'] = 'Tick this box if using &quot;Capita&quot; (their XML format is slightly wrong)';
$string['usecapitafix_desc'] = 'The student data system produced by Capita has been found to have one slight error in its XML output. If you are using Capita you should enable this setting - otherwise leave it un-ticked.';
$string['usersettings'] = 'User data options';
Expand Down
154 changes: 124 additions & 30 deletions enrol/imsenterprise/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@
*/
class enrol_imsenterprise_plugin extends enrol_plugin {

/**
* @var IMSENTERPRISE_ADD imsenterprise add action.
*/
const IMSENTERPRISE_ADD = 1;

/**
* @var IMSENTERPRISE_UPDATE imsenterprise update action.
*/
const IMSENTERPRISE_UPDATE = 2;

/**
* @var IMSENTERPRISE_DELETE imsenterprise delete action.
*/
const IMSENTERPRISE_DELETE = 3;

/**
* @var $logfp resource file pointer for writing log data to.
*/
Expand Down Expand Up @@ -276,7 +291,14 @@ protected function process_group_tag($tagcontents) {
// Get configs.
$truncatecoursecodes = $this->get_config('truncatecoursecodes');
$createnewcourses = $this->get_config('createnewcourses');
$updatecourses = $this->get_config('updatecourses');
$createnewcategories = $this->get_config('createnewcategories');
$categoryseparator = trim($this->get_config('categoryseparator'));

// Ensure a default is set for the category separator.
if (empty($categoryseparator)) {
$categoryseparator = '|';
}

if ($createnewcourses) {
require_once("$CFG->dirroot/course/lib.php");
Expand Down Expand Up @@ -320,12 +342,13 @@ protected function process_group_tag($tagcontents) {
// Third, check if the course(s) exist.
foreach ($group->coursecode as $coursecode) {
$coursecode = trim($coursecode);
if (!$DB->get_field('course', 'id', array('idnumber' => $coursecode))) {
$dbcourse = $DB->get_field('course', 'id', array('idnumber' => $coursecode));
if (!$dbcourse) {
if (!$createnewcourses) {
$this->log_line("Course $coursecode not found in Moodle's course idnumbers.");
} else {

// Create the (hidden) course(s) if not found
// Create the (hidden) course(s) if not found.
$courseconfig = get_config('moodlecourse'); // Load Moodle Course shell defaults.

// New course.
Expand Down Expand Up @@ -361,27 +384,49 @@ protected function process_group_tag($tagcontents) {
// Insert default names for teachers/students, from the current language.

// Handle course categorisation (taken from the group.org.orgunit field if present).
if (!empty($group->category)) {
// If the category is defined and exists in Moodle, we want to store it in that one.
if ($catid = $DB->get_field('course_categories', 'id', array('name' => $group->category))) {
$course->category = $catid;
} else if ($createnewcategories) {
// Else if we're allowed to create new categories, let's create this one.
$newcat = new stdClass();
$newcat->name = $group->category;
$newcat->visible = 0;
$catid = $DB->insert_record('course_categories', $newcat);
$course->category = $catid;
$this->log_line("Created new (hidden) category, #$catid: $newcat->name");
} else {
// If not found and not allowed to create, stick with default.
$this->log_line('Category '.$group->category.' not found in Moodle database, so using '.
'default category instead.');
$course->category = $this->get_default_category_id();
if (strlen($group->category) > 0) {
$sep = '{\\'.$categoryseparator.'}';
$matches = preg_split($sep, $group->category, -1, PREG_SPLIT_NO_EMPTY);

// Categories can be nested.
// For example: "Fall 2013|Biology" is the "Biology" category nested under the "Fall 2013" category.
// Iterate through each category and create it if necessary.

$catid = 0;
$fullnestedcatname = '';
foreach ($matches as $catname) {
$catname = trim($catname);
if (strlen($fullnestedcatname)) {
$fullnestedcatname .= ' / ';
}
$fullnestedcatname .= $catname;
$parentid = $catid;
if ($catid = $DB->get_field('course_categories', 'id',
array('name' => $catname, 'parent' => $parentid))) {
$course->category = $catid;
continue; // This category already exists.
}
if ($createnewcategories) {
// Else if we're allowed to create new categories, let's create this one.
$newcat = new stdClass();
$newcat->name = $catname;
$newcat->visible = 0;
$newcat->parent = $parentid;
$catid = $DB->insert_record('course_categories', $newcat);
$this->log_line("Created new (hidden) category '$fullnestedcatname'");
$course->category = $catid;
} else {
// If not found and not allowed to create, stick with default.
$this->log_line('Category '.$group->category.' not found in Moodle database, so using '.
'default category instead.');
$course->category = $this->get_default_category_id();
break;
}
}
} else {
$course->category = $this->get_default_category_id();
}

$course->startdate = time();
// Choose a sort order that puts us at the start of the list!
$course->sortorder = 0;
Expand All @@ -390,18 +435,49 @@ protected function process_group_tag($tagcontents) {

$this->log_line("Created course $coursecode in Moodle (Moodle ID is $course->id)");
}
} else if ($recstatus == 3 && ($courseid = $DB->get_field('course', 'id', array('idnumber' => $coursecode)))) {
} else if (($recstatus == self::IMSENTERPRISE_UPDATE) && $dbcourse) {
if ($updatecourses) {
// Update course. Allowed fields to be updated are:
// Short Name, and Full Name.
$hasupdates = false;
if (!empty($group->short)) {
if ($group->short != $dbcourse->shortname) {
$dbcourse->shortname = $group->short;
$hasupdates = true;
}
}
if (!empty($group->full)) {
if ($group->full != $dbcourse->fullname) {
$dbcourse->fullname = $group->full;
$hasupdates = true;
}
}
if ($hasupdates) {
$DB->update_record('course', $dbcourse);
$courseid = $dbcourse->id;
add_to_log(SITEID, "course", "update", "view.php?id=$courseid", "ID $courseid");
$this->log_line("Updated course $coursecode in Moodle (Moodle ID is $courseid)");
}
} else {
// Update courses option is not enabled. Ignore.
$this->log_line("Ignoring update to course $coursecode");
}
} else if (($recstatus == self::IMSENTERPRISE_DELETE) && $dbcourse) {
// If course does exist, but recstatus==3 (delete), then set the course as hidden.
$DB->set_field('course', 'visible', '0', array('id' => $courseid));
$courseid = $dbcourse->id;
$dbcourse->visible = 0;
$DB->update_record('course', $dbcourse);
add_to_log(SITEID, "course", "update", "view.php?id=$courseid",
"Updated (set to hidden) course $coursecode (Moodle ID is $courseid)");
$this->log_line("Updated (set to hidden) course $coursecode in Moodle (Moodle ID is $courseid)");
}
}
}
}

/**
* Process the person tag. This defines a Moodle user.
*
* @param string $tagcontents The raw contents of the XML element
* @param string $tagconents The raw contents of the XML element
*/
protected function process_person_tag($tagcontents) {
global $CFG, $DB;
Expand All @@ -412,6 +488,7 @@ protected function process_person_tag($tagcontents) {
$fixcasepersonalnames = $this->get_config('fixcasepersonalnames');
$imsdeleteusers = $this->get_config('imsdeleteusers');
$createnewusers = $this->get_config('createnewusers');
$imsupdateusers = $this->get_config('imsupdateusers');

$person = new stdClass();
if (preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)) {
Expand All @@ -423,11 +500,14 @@ protected function process_person_tag($tagcontents) {
if (preg_match('{<name>.*?<n>.*?<family>(.+?)</family>.*?</n>.*?</name>}is', $tagcontents, $matches)) {
$person->lastname = trim($matches[1]);
}
if (preg_match('{<userid>(.*?)</userid>}is', $tagcontents, $matches)) {
if (preg_match('{<userid.*?>(.*?)</userid>}is', $tagcontents, $matches)) {
$person->username = trim($matches[1]);
}
if (preg_match('{<userid\s+authenticationtype\s*=\s*"*(.+?)"*>.*?</userid>}is', $tagcontents, $matches)) {
$person->auth = trim($matches[1]);
}
if ($imssourcedidfallback && trim($person->username) == '') {
// This is the point where we can fall back to useing the "sourcedid" if "userid" is not supplied
// This is the point where we can fall back to useing the "sourcedid" if "userid" is not supplied.
// NB We don't use an "elseif" because the tag may be supplied-but-empty.
$person->username = $person->idnumber;
}
Expand Down Expand Up @@ -460,7 +540,7 @@ protected function process_person_tag($tagcontents) {
$recstatus = ($this->get_recstatus($tagcontents, 'person'));

// Now if the recstatus is 3, we should delete the user if-and-only-if the setting for delete users is turned on.
if ($recstatus == 3) {
if ($recstatus == self::IMSENTERPRISE_DELETE) {

if ($imsdeleteusers) { // If we're allowed to delete user records.
// Do not dare to hack the user.deleted field directly in database!!!
Expand All @@ -477,6 +557,18 @@ protected function process_person_tag($tagcontents) {
} else {
$this->log_line("Ignoring deletion request for user '$person->username' (ID number $person->idnumber).");
}
} else if ($recstatus == self::IMSENTERPRISE_UPDATE) { // Update user.
if ($imsupdateusers) {
if ($id = $DB->get_field('user', 'id', array('idnumber' => $person->idnumber))) {
$person->id = $id;
$DB->update_record('user', $person);
$this->log_line("Updated user $person->username");
} else {
$this->log_line("Ignoring update request for non-existent user $person->username");
}
} else {
$this->log_line("Ignoring update request for user $person->username");
}

} else { // Add or update record.

Expand All @@ -494,9 +586,11 @@ protected function process_person_tag($tagcontents) {
// If they don't exist and they have a defined username, and $createnewusers == true, we create them.
$person->lang = $CFG->lang;
// TODO: MDL-15863 this needs more work due to multiauth changes, use first auth for now.
$auth = explode(',', $CFG->auth);
$auth = reset($auth);
$person->auth = $auth;
if (empty($person->auth)) {
$auth = explode(',', $CFG->auth);
$auth = reset($auth);
$person->auth = $auth;
}
$person->confirmed = 1;
$person->timemodified = time();
$person->mnethostid = $CFG->mnet_localhost_id;
Expand Down Expand Up @@ -568,7 +662,7 @@ protected function process_membership_tag($tagcontents) {
}

$recstatus = ($this->get_recstatus($mmatch[1], 'role'));
if ($recstatus == 3) {
if ($recstatus == self::IMSENTERPRISE_DELETE) {
// See above - recstatus of 3 (==delete) is treated the same as status of 0.
$member->status = 0;
}
Expand Down
10 changes: 10 additions & 0 deletions enrol/imsenterprise/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
$settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/createnewusers',
get_string('createnewusers', 'enrol_imsenterprise'), get_string('createnewusers_desc', 'enrol_imsenterprise'), 0));

$settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imsupdateusers',
get_string('updateusers', 'enrol_imsenterprise'), get_string('updateusers_desc', 'enrol_imsenterprise'), 0));

$settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imsdeleteusers',
get_string('deleteusers', 'enrol_imsenterprise'), get_string('deleteusers_desc', 'enrol_imsenterprise'), 0));

Expand Down Expand Up @@ -88,10 +91,17 @@
$settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/createnewcourses',
get_string('createnewcourses', 'enrol_imsenterprise'), get_string('createnewcourses_desc', 'enrol_imsenterprise'), 0));

$settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/updatecourses',
get_string('updatecourses', 'enrol_imsenterprise'), get_string('updatecourses_desc', 'enrol_imsenterprise'), 0));

$settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/createnewcategories',
get_string('createnewcategories', 'enrol_imsenterprise'), get_string('createnewcategories_desc', 'enrol_imsenterprise'),
0));

$settings->add(new admin_setting_configtext('enrol_imsenterprise/categoryseparator',
get_string('categoryseparator', 'enrol_imsenterprise'), get_string('categoryseparator_desc', 'enrol_imsenterprise'), '|',
PARAM_TEXT, 3));

$settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imsunenrol',
get_string('allowunenrol', 'enrol_imsenterprise'), get_string('allowunenrol_desc', 'enrol_imsenterprise'), 0));

Expand Down
Loading

0 comments on commit 93fdbb4

Please sign in to comment.