$string['cronfrequency'] = 'Frequency of processing';
$string['deleteusers'] = 'Delete user accounts when specified in IMS data';
$string['deleteusers_desc'] = 'If enabled, IMS Enterprise enrolment data can specify the deletion of user accounts (if the "recstatus" flag is set to 3, which represents deletion of an account). As is standard in Moodle, the user record isn\'t actually deleted from Moodle\'s database, but a flag is set to mark the account as deleted.';
-$string['pluginname_desc'] = 'This method will repeatedly check for and process a specially-formatted text file in the location that you specify. The file must follow the IMS Enterprise specifications containing person, group, and membership XML elements.';
$string['doitnow'] = 'perform an IMS Enterprise import right now';
-$string['pluginname'] = 'IMS Enterprise file';
+$string['emptyattribute'] = 'Leave it empty';
$string['filelockedmail'] = 'The text file you are using for IMS-file-based enrolments ({$a}) can not be deleted by the cron process. This usually means the permissions are wrong on it. Please fix the permissions so that Moodle can delete the file, otherwise it might be processed repeatedly.';
$string['filelockedmailsubject'] = 'Important error: Enrolment file';
$string['fixcasepersonalnames'] = 'Change personal names to Title Case';
$string['mailusers'] = 'Notify users by email';
$string['messageprovider:imsenterprise_enrolment'] = 'IMS Enterprise enrolment messages';
$string['miscsettings'] = 'Miscellaneous';
+$string['pluginname'] = 'IMS Enterprise file';
+$string['pluginname_desc'] = 'This method will repeatedly check for and process a specially-formatted text file in the location that you specify. The file must follow the IMS Enterprise specifications containing person, group, and membership XML elements.';
$string['processphoto'] = 'Add user photo data to profile';
$string['processphotowarning'] = 'Warning: Image processing is likely to add a significant burden to the server. You are recommended not to activate this option if large numbers of students are expected to be processed.';
$string['restricttarget'] = 'Only process data if the following target is specified';
$string['restricttarget_desc'] = 'An IMS Enterprise data file could be intended for multiple "targets" - different LMSes, or different systems within a school/university. It\'s possible to specify in the Enterprise file that the data is intended for one or more named target systems, by naming them in <target> tags contained within the <properties> tag.
In general you don\'t need to worry about this. Leave the setting blank and Moodle will always process the data file, no matter whether a target is specified or not. Otherwise, fill in the exact name that will be output inside the <target> tag.';
+$string['settingfullname'] = 'IMS description tag for the course full name';
+$string['settingfullnamedescription'] = 'The full name is a required course field so you have to define the selected description tag in your IMS Enterprise file';
+$string['settingshortname'] = 'IMS description tag for the course short name';
+$string['settingshortnamedescription'] = 'The short name is a required course field so you have to define the selected description tag in your IMS Enterprise file';
+$string['settingsummary'] = 'IMS description tag for the course summary';
+$string['settingsummarydescription'] = 'Is an optional field, select \'Leave it empty\' if you dont\'t want to specify a course summary';
$string['sourcedidfallback'] = 'Use the "sourcedid" for a person\'s userid if the "userid" field is not found';
$string['sourcedidfallback_desc'] = 'In IMS data, the <sourcedid> field represents the persistent ID code for a person as used in the source system. The <userid> field is a separate field which should contain the ID code used by the user when logging in. In many cases these two codes may be the same - but not always.
// Make sure we understand how to map the IMS-E roles to Moodle roles
$this->load_role_mappings();
+ // Make sure we understand how to map the IMS-E course names to Moodle course names.
+ $this->load_course_mappings();
$md5 = md5_file($filename); // NB We'll write this value back to the database at the end of the cron
$filemtime = filemtime($filename);
if (preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)) {
$group->coursecode = trim($matches[1]);
}
- if (preg_match('{<description>.*?<long>(.*?)</long>.*?</description>}is', $tagcontents, $matches)){
- $group->description = trim($matches[1]);
+
+ if (preg_match('{<description>.*?<long>(.*?)</long>.*?</description>}is', $tagcontents, $matches)) {
+ $group->long = trim($matches[1]);
}
if (preg_match('{<description>.*?<short>(.*?)</short>.*?</description>}is', $tagcontents, $matches)) {
- $group->shortName = trim($matches[1]);
+ $group->short = trim($matches[1]);
}
if (preg_match('{<description>.*?<full>(.*?)</full>.*?</description>}is', $tagcontents, $matches)) {
- $group->fulldescription = trim($matches[1]);
+ $group->full = trim($matches[1]);
}
+
if (preg_match('{<org>.*?<orgunit>(.*?)</orgunit>.*?</org>}is', $tagcontents, $matches)) {
$group->category = trim($matches[1]);
}
if (!$createnewcourses) {
$this->log_line("Course $coursecode not found in Moodle's course idnumbers.");
} else {
- // Set shortname to description or description to shortname if one is set but not the other.
- $nodescription = !isset($group->description);
- $noshortname = !isset($group->shortName);
- if ( $nodescription && $noshortname) {
- // If neither short nor long description are set let if fail
- $this->log_line("Neither long nor short name are set for $coursecode");
- } else if ($nodescription) {
- // If short and ID exist, then give the long short's value, then give short the ID's value
- $group->description = $group->shortName;
- $group->shortName = $coursecode;
- } else if ($noshortname) {
- // If long and ID exist, then map long to long, then give short the ID's value.
- $group->shortName = $coursecode;
- }
+
// Create the (hidden) course(s) if not found
$courseconfig = get_config('moodlecourse'); // Load Moodle Course shell defaults
+
+ // New course.
$course = new stdClass();
- $course->fullname = $group->description;
- $course->shortname = $group->shortName;
- if (!empty($group->fulldescription)) {
- $course->summary = format_text($group->fulldescription, FORMAT_HTML);
+ foreach ($this->coursemappings as $courseattr => $imsname) {
+
+ if ($imsname == 'ignore') {
+ continue;
+ }
+
+ // Check if the IMS file contains the mapped tag, otherwise fallback on coursecode.
+ if ($imsname == 'coursecode') {
+ $course->{$courseattr} = $coursecode;
+ } else if (!empty($group->{$imsname})) {
+ $course->{$courseattr} = $group->{$imsname};
+ } else {
+ $this->log_line('No ' . $imsname . ' description tag found for ' . $coursecode . ' coursecode, using ' . $coursecode . ' instead');
+ $course->{$courseattr} = $coursecode;
+ }
+
+ if ($courseattr == 'summary') {
+ $format = FORMAT_HTML;
+ } else {
+ $format = FORMAT_PLAIN;
+ }
+ $course->{$courseattr} = format_text($course->$courseattr, $format);
}
+
$course->idnumber = $coursecode;
$course->format = $courseconfig->format;
$course->visible = $courseconfig->visible;
$course->startdate = time();
// Choose a sort order that puts us at the start of the list!
$course->sortorder = 0;
-
$courseid = $DB->insert_record('course', $course);
// Setup default enrolment plugins
* @param string $string Text to write (newline will be added automatically)
*/
function log_line($string){
- mtrace($string);
+
+ if (!PHPUNIT_TEST) {
+ mtrace($string);
+ }
if($this->logfp) {
fwrite($this->logfp, $string . "\n");
}
}
}
+ /**
+ * Load the name mappings (from the config), so we can easily refer to
+ * how an IMS-E course properties corresponds to a Moodle course properties
+ */
+ function load_course_mappings() {
+ require_once('locallib.php');
+
+ $imsnames = new imsenterprise_courses();
+ $courseattrs = $imsnames->get_courseattrs();
+
+ $this->coursemappings = array();
+ foreach($courseattrs as $courseattr) {
+ $this->coursemappings[$courseattr] = $this->get_config('imscoursemap' . $courseattr);
+ }
+ }
+
/**
* Called whenever anybody tries (from the normal interface) to remove a group
* member which is registered as being created by this component. (Not called
return false;
}
+
} // end of class
--- /dev/null
+<?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/>.
+
+/**
+ * IMS Enterprise enrolment tests.
+ *
+ * @package enrol_imsenterprise
+ * @category phpunit
+ * @copyright 2012 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/enrol/imsenterprise/locallib.php');
+require_once($CFG->dirroot . '/enrol/imsenterprise/lib.php');
+
+/**
+ * IMS Enterprise test case
+ *
+ * @package enrol_imsenterprise
+ * @category phpunit
+ * @copyright 2012 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class enrol_imsenterprise_testcase extends advanced_testcase {
+
+ protected $imsplugin;
+
+ protected function setUp() {
+ $this->resetAfterTest(true);
+ $this->imsplugin = enrol_get_plugin('imsenterprise');
+ $this->set_test_config();
+ }
+
+ /**
+ * With an empty IMS enterprise file
+ */
+ public function test_emptyfile() {
+ global $DB;
+
+ $prevncourses = $DB->count_records('course');
+ $prevnusers = $DB->count_records('user');
+
+ $this->set_xml_file(false, false);
+ $this->imsplugin->cron();
+
+ $this->assertEquals($prevncourses, $DB->count_records('course'));
+ $this->assertEquals($prevnusers, $DB->count_records('user'));
+ }
+
+
+ /**
+ * Existing users are not created again
+ */
+ public function test_users_existing() {
+ global $DB;
+
+ $user1 = $this->getDataGenerator()->create_user();
+ $user2 = $this->getDataGenerator()->create_user();
+
+ $prevnusers = $DB->count_records('user');
+
+ $users = array($user1, $user2);
+ $this->set_xml_file($users);
+ $this->imsplugin->cron();
+
+ $this->assertEquals($prevnusers, $DB->count_records('user'));
+ }
+
+
+ /**
+ * Add new users
+ */
+ public function test_users_add() {
+ global $DB;
+
+ $prevnusers = $DB->count_records('user');
+
+ $user1 = new StdClass();
+ $user1->username = 'u1';
+ $user1->email = 'u1@u1.org';
+ $user1->firstname = 'U';
+ $user1->lastname = '1';
+
+ $users = array($user1);
+ $this->set_xml_file($users);
+ $this->imsplugin->cron();
+
+ $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
+ }
+
+
+ /**
+ * Existing courses are not created again
+ */
+ public function test_courses_existing() {
+ global $DB;
+
+ $course1 = $this->getDataGenerator()->create_course(array('idnumber' => 'id1'));
+ $course2 = $this->getDataGenerator()->create_course(array('idnumber' => 'id2'));
+
+ // Default mapping according to default course attributes - IMS description tags mapping.
+ $course1->imsshort = $course1->fullname;
+ $course2->imsshort = $course2->fullname;
+
+ $prevncourses = $DB->count_records('course');
+
+ $courses = array($course1, $course2);
+ $this->set_xml_file(false, $courses);
+ $this->imsplugin->cron();
+
+ $this->assertEquals($prevncourses, $DB->count_records('course'));
+ }
+
+
+ /**
+ * Add new courses
+ */
+ public function test_courses_add() {
+ global $DB;
+
+ $prevncourses = $DB->count_records('course');
+
+ $course1 = new StdClass();
+ $course1->idnumber = 'id1';
+ $course1->imsshort = 'id1';
+ $course1->category = 'DEFAULT CATNAME';
+
+ $course2 = new StdClass();
+ $course2->idnumber = 'id2';
+ $course2->imsshort = 'id2';
+ $course2->category = 'DEFAULT CATNAME';
+
+ $courses = array($course1, $course2);
+ $this->set_xml_file(false, $courses);
+ $this->imsplugin->cron();
+
+ $this->assertEquals(($prevncourses + 2), $DB->count_records('course'));
+ }
+
+
+ /**
+ * Course attributes mapping to IMS enterprise group description tags
+ */
+ public function test_courses_attrmapping() {
+ global $DB;
+
+ // Setting a all = coursecode (idnumber) mapping.
+ $this->imsplugin->set_config('imscoursemapshortname', 'coursecode');
+ $this->imsplugin->set_config('imscoursemapfullname', 'coursecode');
+ $this->imsplugin->set_config('imscoursemapsummary', 'coursecode');
+
+ $course1 = new StdClass();
+ $course1->idnumber = 'id1';
+ $course1->imsshort = 'description_short1';
+ $course1->imslong = 'description_long';
+ $course1->imsfull = 'description_full';
+ $course1->category = 'DEFAULT CATNAME';
+
+ $this->set_xml_file(false, array($course1));
+ $this->imsplugin->cron();
+
+ $dbcourse = $DB->get_record('course', array('idnumber' => $course1->idnumber));
+ $this->assertFalse(!$dbcourse);
+ $this->assertEquals($dbcourse->shortname, $course1->idnumber);
+ $this->assertEquals($dbcourse->fullname, $course1->idnumber);
+ $this->assertEquals($dbcourse->summary, $course1->idnumber);
+
+
+ // Setting a mapping using all the description tags.
+ $this->imsplugin->set_config('imscoursemapshortname', 'short');
+ $this->imsplugin->set_config('imscoursemapfullname', 'long');
+ $this->imsplugin->set_config('imscoursemapsummary', 'full');
+
+ $course2 = new StdClass();
+ $course2->idnumber = 'id2';
+ $course2->imsshort = 'description_short2';
+ $course2->imslong = 'description_long';
+ $course2->imsfull = 'description_full';
+ $course2->category = 'DEFAULT CATNAME';
+
+ $this->set_xml_file(false, array($course2));
+ $this->imsplugin->cron();
+
+ $dbcourse = $DB->get_record('course', array('idnumber' => $course2->idnumber));
+ $this->assertFalse(!$dbcourse);
+ $this->assertEquals($dbcourse->shortname, $course2->imsshort);
+ $this->assertEquals($dbcourse->fullname, $course2->imslong);
+ $this->assertEquals($dbcourse->summary, $course2->imsfull);
+
+
+ // Setting a mapping where the specified description tags doesn't exist in the XML file (must delegate into idnumber).
+ $this->imsplugin->set_config('imscoursemapshortname', 'short');
+ $this->imsplugin->set_config('imscoursemapfullname', 'long');
+ $this->imsplugin->set_config('imscoursemapsummary', 'full');
+
+ $course3 = new StdClass();
+ $course3->idnumber = 'id3';
+ $course3->imsshort = 'description_short3';
+ $course3->category = 'DEFAULT CATNAME';
+
+ $this->set_xml_file(false, array($course3));
+ $this->imsplugin->cron();
+
+ $dbcourse = $DB->get_record('course', array('idnumber' => $course3->idnumber));
+ $this->assertFalse(!$dbcourse);
+ $this->assertEquals($dbcourse->shortname, $course3->imsshort);
+ $this->assertEquals($dbcourse->fullname, $course3->idnumber);
+ $this->assertEquals($dbcourse->summary, $course3->idnumber);
+
+ }
+
+
+ /**
+ * Sets the plugin configuration for testing
+ */
+ protected function set_test_config() {
+ $this->imsplugin->set_config('mailadmins', false);
+ $this->imsplugin->set_config('prev_path', '');
+ $this->imsplugin->set_config('createnewusers', true);
+ $this->imsplugin->set_config('createnewcourses', true);
+ $this->imsplugin->set_config('createnewcategories', true);
+ }
+
+
+ /**
+ * Creates an IMS enterprise XML file and adds it's path to config settings
+ *
+ * @param array Array of users StdClass
+ * @param array Array of courses StdClass
+ */
+ protected function set_xml_file($users = false, $courses = false) {
+ global $DB;
+
+ $xmlcontent = '<enterprise>';
+
+ // Users.
+ if (!empty($users)) {
+ foreach ($users as $user) {
+ $xmlcontent .= '
+ <person>
+ <sourcedid>
+ <source>TestSource</source>
+ <id>'.$user->username.'</id>
+ </sourcedid>
+ <userid>'.$user->username.'</userid>
+ <name>
+ <fn>'.$user->firstname.' '.$user->lastname.'</fn>
+ <n>
+ <family>'.$user->lastname.'</family>
+ <given>'.$user->firstname.'</given>
+ </n>
+ </name>
+ <email>'.$user->email.'</email>
+ </person>';
+ }
+ }
+
+ // Courses.
+ // Mapping based on default course attributes - IMS group tags mapping.
+ if (!empty($courses)) {
+ foreach ($courses as $course) {
+
+ // orgunit tag value is used by moodle as category name.
+ // If the category does not exists the id is used as name.
+ if ($categoryname = $DB->get_field('course_categories', 'name', array('id' => $course->category))) {
+ $categoryname = $course->category;
+ }
+
+ $xmlcontent .= '
+ <group>
+ <sourcedid>
+ <source>TestSource</source>
+ <id>'.$course->idnumber.'</id>
+ </sourcedid>
+ <description>';
+
+ // Optional to test course attributes mappings.
+ if (!empty($course->imsshort)) {
+ $xmlcontent .= '
+ <short>'.$course->imsshort.'</short>';
+ }
+
+ // Optional to test course attributes mappings.
+ if (!empty($course->imslong)) {
+ $xmlcontent .= '
+ <long>'.$course->imslong.'</long>';
+ }
+
+ // Optional to test course attributes mappings.
+ if (!empty($course->imsfull)) {
+ $xmlcontent .= '
+ <full>'.$course->imsfull.'</full>';
+ }
+
+ $xmlcontent .= '
+ </description>
+ <org>
+ <orgunit>'.$categoryname.'</orgunit>
+ </org>
+ </group>';
+ }
+ }
+
+ $xmlcontent .= '
+</enterprise>';
+
+ // Creating the XML file.
+ $filename = 'ims_' . rand(1000, 9999) . '.xml';
+ $tmpdir = make_temp_directory('enrol_imsenterprise');
+ $xmlfilepath = $tmpdir . '/' . $filename;
+ file_put_contents($xmlfilepath, $xmlcontent);
+
+ // Setting the file path in CFG.
+ $this->imsplugin->set_config('imsfilelocation', $xmlfilepath);
+ }
+}