From 8c25f6f227cf844277ac9e396d9cdc05e5dbc617 Mon Sep 17 00:00:00 2001 From: martinlanghoff Date: Fri, 10 Mar 2006 02:01:06 +0000 Subject: [PATCH] enrol/imsenrolment: Introducing IMS Enterprise enrolment module by Dan Stowell IMS Enterprise 1.1 file enrolment module for Moodle (also reported to work with v1.01 and v1.0 data) (c) 2005-2006 Dan Stowell Released under the Gnu Public Licence (GPL) This module was originally in contrib/enrol_imsenterprise, and a 1.5-compatible version of the module can still be found there. --- enrol/imsenterprise/README.txt | 63 ++ enrol/imsenterprise/TODO.txt | 39 + enrol/imsenterprise/config.html | 180 ++++ enrol/imsenterprise/enrol.php | 833 ++++++++++++++++++ .../entv1p1_conformance_summary.html | 244 +++++ .../examples/example-grouped.xml | 171 ++++ .../examples/example-oneline.xml | 1 + enrol/imsenterprise/examples/example.xml | 166 ++++ enrol/imsenterprise/importnow.php | 36 + lang/en_utf8/enrol_imsenterprise.php | 39 + .../help/enrol-imsenterprise/capita.html | 1 + .../enrol-imsenterprise/categorisation.html | 7 + .../enrol-imsenterprise/createnewcourses.html | 9 + .../enrol-imsenterprise/createnewusers.html | 8 + .../help/enrol-imsenterprise/deleteusers.html | 5 + .../enrol-imsenterprise/formatoverview.html | 86 ++ .../help/enrol-imsenterprise/photos.html | 6 + .../sourceddidfallback.html | 9 + .../help/enrol-imsenterprise/target.html | 4 + .../truncatecoursecodes.html | 1 + .../help/enrol-imsenterprise/unenrol.html | 17 + 21 files changed, 1925 insertions(+) create mode 100644 enrol/imsenterprise/README.txt create mode 100644 enrol/imsenterprise/TODO.txt create mode 100644 enrol/imsenterprise/config.html create mode 100644 enrol/imsenterprise/enrol.php create mode 100644 enrol/imsenterprise/entv1p1_conformance_summary.html create mode 100644 enrol/imsenterprise/examples/example-grouped.xml create mode 100644 enrol/imsenterprise/examples/example-oneline.xml create mode 100644 enrol/imsenterprise/examples/example.xml create mode 100644 enrol/imsenterprise/importnow.php create mode 100644 lang/en_utf8/enrol_imsenterprise.php create mode 100644 lang/en_utf8/help/enrol-imsenterprise/capita.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/categorisation.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/createnewcourses.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/createnewusers.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/deleteusers.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/formatoverview.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/photos.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/sourceddidfallback.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/target.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/truncatecoursecodes.html create mode 100644 lang/en_utf8/help/enrol-imsenterprise/unenrol.html diff --git a/enrol/imsenterprise/README.txt b/enrol/imsenterprise/README.txt new file mode 100644 index 0000000000..2f88dc144d --- /dev/null +++ b/enrol/imsenterprise/README.txt @@ -0,0 +1,63 @@ + + IMS Enterprise 1.1 file enrolment module for Moodle + (also reported to work with v1.01 and v1.0 data) + + (c) 2005-2006 Dan Stowell + Released under the Gnu Public Licence (GPL) + +INSTALLATION + +Please see INSTALL.txt. + + +DESCRIPTION + +This enrolment script will repeatedly read an XML file from a +specified location. The XML file should conform to the IMS Enterprise +1.1 specification, containing , , and +elements to specify which students/teachers should be added/removed +from the course. User accounts and/or Moodle courses can be created +by the script if they aren't yet registered (this is an option which +can be turned on/off). + +(The IMS 1.0 specification is significantly different from the 1.1 +spec. This code has been made flexible so it should in theory be +able to handle IMS 1.0 as well, but I haven't directly tested it +with v1.0 Enterprise data. +The one restriction that may be important is that the plugin assumes +that the elements come after the others. The 1.1 spec +demands this, but the 1.0 spec does not make this restriction.) + + +HOW USERS/COURSES ARE MATCHED AGAINST MOODLE'S DATABASE + +IMS Enterprise data typically contains a "sourcedid" for each person +or group (course) record, which represents the canonical identifier +used by the source system. This is separate from the "userid" for a +person, which is also present in the data and should represent the +login userid which a person is intended to use in Moodle. (In some +systems these may have the same value.) + +This script uses the "sourcedid" as the lookup to determine if the +user/course exists in the database, in both cases looking at the +"idnumber" field. This "idnumber" is not typically displayed in +Moodle. When creating a user, the "userid" field must not be blank, +because it is stored as the user's Moodle login ID. + + +TECHNICAL NOTE + +The script uses an optimised pattern-matching (regex) method for +processing the XML, rather than any built-in XML handling. This is for +two reasons: firstly, because some systems produce very sloppy +(even invalid) XML and we'd like to be able to process it anyway; and +secondly, because PHP 4 and PHP 5 handle XML differently, and we'd +like to be independent of that changeover. + + + +FOR MORE INFO / HELP + +Please visit the community forums at www.moodle.org and search to see +if any relevant help has already been posted. If not, ask away! + diff --git a/enrol/imsenterprise/TODO.txt b/enrol/imsenterprise/TODO.txt new file mode 100644 index 0000000000..d4b56d8def --- /dev/null +++ b/enrol/imsenterprise/TODO.txt @@ -0,0 +1,39 @@ +DEVELOPMENT PRIORITIES, IN ORDER OF PRIORITY: + + + +TO DO: + +- Handling of references to s as s + +- Support for enrolling using multiple files? At present only one filename + is supported. Some systems may wish/need to do multiple. This may be tricky + since multiple filepaths may be too long for a single moodle config + variable (255 chars max) + +- Process group's "relationship"? + +- Activate handling, dependent upon acceptance of + modification to Moodle's gdlib.php + + + +DONE: + +v0.6: + +- Reduce processing from two passes to one pass (dependent on tags + coming after any person/group tags to which they refer) + +- The log data should NOT contain any personal information! NO STUDENT NAMES + +- Support for restricting according to + +- Support for recstatus attribute on group/person/role + +- Processing of tag to add ability to specify start/end of enrolment + +- Write conformance summary + + + diff --git a/enrol/imsenterprise/config.html b/enrol/imsenterprise/config.html new file mode 100644 index 0000000000..028ef6c397 --- /dev/null +++ b/enrol/imsenterprise/config.html @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
: + +
: + +
: + enrol_mailadmins) echo "checked=\"true\"" ?> /> + + +
: + (Not yet configurable) +
: + enrol_createnewusers) echo "checked=\"true\"" ?> /> + + +
: + enrol_imsdeleteusers) echo "checked=\"true\"" ?> /> + + +
: + enrol_fixcaseusernames) echo "checked=\"true\"" ?> /> + + +
: + enrol_fixcasepersonalnames) echo "checked=\"true\"" ?> /> + + +
: + enrol_imssourcedidfallback) echo "checked=\"true\"" ?> /> + + +
: + name="enrol_truncatecoursecodes" size="3" maxlength="3" /> + + +
: + enrol_createnewcourses) echo "checked=\"true\"" ?> /> + + +
: + enrol_createnewcategories) echo "checked=\"true\"" ?> /> + + +
: + enrol_imsunenrol) echo "checked=\"true\"" ?> /> + + +
: + name="enrol_imsrestricttarget" size="20" maxlength="32" /> + +
: + enrol_imscapitafix) echo "checked=\"true\"" ?> /> + + +
+ +

.

diff --git a/enrol/imsenterprise/enrol.php b/enrol/imsenterprise/enrol.php new file mode 100644 index 0000000000..845311ece4 --- /dev/null +++ b/enrol/imsenterprise/enrol.php @@ -0,0 +1,833 @@ +libdir/blocklib.php"); + +// The following flags are set in the configuration +// $CFG->enrol_imsfilelocation: where is the file we are looking for? +// $CFG->enrol_logtolocation: if you want to store a log of processing, specify filepath here +// $CFG->enrol_allowinternal: allow internal enrolment in courses +// $CFG->enrol_emailadmins: email a notification to the admin +// $CFG->enrol_createnewusers: should this script create user accounts for those who don't seem to be registered yet? +// $CFG->enrol_imsdeleteusers: should this script mark user accounts as deleted, if the data requests this? +// $CFG->enrol_fixcaseusernames: whether to force all usernames to lowercase +// $CFG->enrol_fixcasepersonalnames: convert personal names, e.g. from "TOM VEK" to "Tom Vek" +// $CFG->enrol_truncatecoursecodes: if this number is greater than zero, truncate the codes found in the IMS data to the given number of characters +// $CFG->enrol_imsunenrol: allow this script to UNENROL students/tutors from courses (if the data marks them as having left the course) +// $CFG->enrol_createnewcourses: should this script create a (hidden, empty) course for any course that doesn't seem to have been registered yet? +// $CFG->enrol_createnewcategories: should this script create a (hidden) category if Moodle doesn't have one by the same name as the desired one? +// $CFG->enrol_imssourcedidfallback: some systems don't output a element (contrary to the specifications). If this is the case, activating this setting will cause us to use the element instead as the userid. This may or may not be desirable in your situation. +// $CFG->enrol_includephoto: Process IMS tag to create user photo. Be warned that this may add significant server load. + +/* + +Note for programmers: + +This class uses regular expressions to mine the data file. The main reason is +that XML handling changes from PHP 4 to PHP 5, so this should work on both. + +One drawback is that the pattern-matching doesn't (currently) handle XML +namespaces - it only copes with a tag if it says , and not +(for example) . + +This should also be able to handle VERY LARGE FILES - so the entire IMS file is +NOT loaded into memory at once. It's handled line-by-line, 'forgetting' tags as +soon as they are processed. + +N.B. The "sourcedid" ID code is translated to Moodle's "idnumber" field, both +for users and for courses. + +*/ + + + +class enrolment_plugin_imsenterprise { + + var $log; + +/// Override the base config_form() function +function config_form($frm) { + global $CFG; + + $vars = array('enrol_imsfilelocation', 'enrol_createnewusers', 'enrol_fixcaseusernames', 'enrol_fixcasepersonalnames', 'enrol_truncatecoursecodes', + 'enrol_createnewcourses', 'enrol_createnewcategories', 'enrol_createnewusers', 'enrol_mailadmins', + 'enrol_imsunenrol', 'enrol_imssourcedidfallback', 'enrol_imscapitafix', 'enrol_imsrestricttarget', 'enrol_imsdeleteusers'); + foreach ($vars as $var) { + if (!isset($frm->$var)) { + $frm->$var = ''; + } + } + include ("$CFG->dirroot/enrol/imsenterprise/config.html"); +} + + +/// Override the base process_config() function +function process_config($config) { + + if (!isset($config->enrol_imsfilelocation)) { + $config->enrol_imsfilelocation = ''; + } + set_config('enrol_imsfilelocation', $config->enrol_imsfilelocation); + + if (!isset($config->enrol_logtolocation)) { + $config->enrol_logtolocation = ''; + } + set_config('enrol_logtolocation', $config->enrol_logtolocation); + + if (!isset($config->enrol_fixcaseusernames)) { + $config->enrol_fixcaseusernames = ''; + } + set_config('enrol_fixcaseusernames', $config->enrol_fixcaseusernames); + + if (!isset($config->enrol_fixcasepersonalnames)) { + $config->enrol_fixcasepersonalnames = ''; + } + set_config('enrol_fixcasepersonalnames', $config->enrol_fixcasepersonalnames); + + if (!isset($config->enrol_truncatecoursecodes)) { + $config->enrol_truncatecoursecodes = 0; + } + set_config('enrol_truncatecoursecodes', intval($config->enrol_truncatecoursecodes)); + + if (!isset($config->enrol_createnewcourses)) { + $config->enrol_createnewcourses = ''; + } + set_config('enrol_createnewcourses', $config->enrol_createnewcourses); + + if (!isset($config->enrol_createnewcategories)) { + $config->enrol_createnewcategories = ''; + } + set_config('enrol_createnewcategories', $config->enrol_createnewcategories); + + if (!isset($config->enrol_createnewusers)) { + $config->enrol_createnewusers = ''; + } + set_config('enrol_createnewusers', $config->enrol_createnewusers); + + if (!isset($config->enrol_imsdeleteusers)) { + $config->enrol_imsdeleteusers = ''; + } + set_config('enrol_imsdeleteusers', $config->enrol_imsdeleteusers); + + if (!isset($config->enrol_mailadmins)) { + $config->enrol_mailadmins = ''; + } + set_config('enrol_mailadmins', $config->enrol_mailadmins); + + if (!isset($config->enrol_imsunenrol)) { + $config->enrol_imsunenrol = ''; + } + set_config('enrol_imsunenrol', $config->enrol_imsunenrol); + + if (!isset($config->enrol_imssourcedidfallback)) { + $config->enrol_imssourcedidfallback = ''; + } + set_config('enrol_imssourcedidfallback', $config->enrol_imssourcedidfallback); + + if (!isset($config->enrol_imscapitafix)) { + $config->enrol_imscapitafix = ''; + } + set_config('enrol_imscapitafix', $config->enrol_imscapitafix); + + //Antoni Mas. 07/12/2005. Incloem la opci de la foto dels usuaris + if (!isset($config->enrol_processphoto)) { + $config->enrol_processphoto = ''; + } + set_config('enrol_processphoto', $config->enrol_processphoto); + + if (!isset($config->enrol_imsrestricttarget)) { + $config->enrol_imsrestricttarget = ''; + } + set_config('enrol_imsrestricttarget', $config->enrol_imsrestricttarget); + + + return true; + +} + +function get_access_icons($course){} + +/** +* Read in an IMS Enterprise file. +* Originally designed to handle v1.1 files but should be able to handle +* earlier types as well, I believe. +* +*/ +function cron() { + global $CFG; + + if (empty($CFG->enrol_imsfilelocation)) { + // $filename = "$CFG->dirroot/enrol/imsenterprise/example.xml"; // Default location + $filename = "$CFG->dataroot/1/imsenterprise-enrol.xml"; // Default location + } else { + $filename = $CFG->enrol_imsfilelocation; + } + + $this->logfp = false; // File pointer for writing log data to + if(!empty($CFG->enrol_logtolocation)) { + $this->logfp = fopen($CFG->enrol_logtolocation, 'a'); + } + + + + if ( file_exists($filename) ) { + @set_time_limit(0); + $starttime = time(); + + $this->log_line('----------------------------------------------------------------------'); + $this->log_line("IMS Enterprise enrol cron process launched at " . userdate(time())); + $this->log_line('Found file '.$filename); + $this->xmlcache = ''; + + + $md5 = md5_file($filename); // NB We'll write this value back to the database at the end of the cron + $filemtime = filemtime($filename); + + // Decide if we want to process the file (based on filepath, modification time, and MD5 hash) + // This is so we avoid wasting the server's efforts processing a file unnecessarily + if(empty($CFG->enrol_ims_prev_path) || ($filename != $CFG->enrol_ims_prev_path)){ + $fileisnew = true; + }elseif(isset($CFG->enrol_ims_prev_time) && ($filemtime <= $CFG->enrol_ims_prev_time)){ + $fileisnew = false; + $this->log_line('File modification time is not more recent than last update - skipping processing.'); + }elseif(isset($CFG->enrol_ims_prev_md5) && ($md5 == $CFG->enrol_ims_prev_md5)){ + $fileisnew = false; + $this->log_line('File MD5 hash is same as on last update - skipping processing.'); + }else{ + $fileisnew = true; // Let's process it! + } + + if($fileisnew){ + + $listoftags = array('group', 'person', 'member', 'membership', 'comments', 'properties'); // The list of tags which should trigger action (even if only cache trimming) + $this->continueprocessing = true; // The tag is allowed to halt processing if we're demanding a matching target + + // FIRST PASS: Run through the file and process the group/person entries + if (($fh = fopen($filename, "r")) != false) { + + $line = 0; + while ((!feof($fh)) && $this->continueprocessing) { + + $line++; + $curline = fgets($fh); + $this->xmlcache .= $curline; // Add a line onto the XML cache + + while(true){ + // If we've got a full tag (i.e. the most recent line has closed the tag) then process-it-and-forget-it. + // Must always make sure to remove tags from cache so they don't clog up our memory + if($tagcontents = $this->full_tag_found_in_cache('group', $curline)){ + $this->process_group_tag($tagcontents); + $this->remove_tag_from_cache('group'); + }elseif($tagcontents = $this->full_tag_found_in_cache('person', $curline)){ + $this->process_person_tag($tagcontents); + $this->remove_tag_from_cache('person'); + }elseif($tagcontents = $this->full_tag_found_in_cache('membership', $curline)){ + $this->process_membership_tag($tagcontents); + $this->remove_tag_from_cache('membership'); + }elseif($tagcontents = $this->full_tag_found_in_cache('comments', $curline)){ + $this->remove_tag_from_cache('comments'); + }elseif($tagcontents = $this->full_tag_found_in_cache('properties', $curline)){ + $this->process_properties_tag($tagcontents); + $this->remove_tag_from_cache('properties'); + }else{ + break; + } + } // End of while-tags-are-detected + } // end of while loop + fclose($fh); + } // end of if(file_open) for first pass + + /* + + + SECOND PASS REMOVED + Since the IMS specification v1.1 insists that "memberships" should come last, + and since vendors seem to have done this anyway (even with 1.0), + we can sensibly perform the import in one fell swoop. + + + // SECOND PASS: Now go through the file and process the membership entries + $this->xmlcache = ''; + if (($fh = fopen($filename, "r")) != false) { + $line = 0; + while ((!feof($fh)) && $this->continueprocessing) { + $line++; + $curline = fgets($fh); + $this->xmlcache .= $curline; // Add a line onto the XML cache + + while(true){ + // Must always make sure to remove tags from cache so they don't clog up our memory + if($tagcontents = $this->full_tag_found_in_cache('group', $curline)){ + $this->remove_tag_from_cache('group'); + }elseif($tagcontents = $this->full_tag_found_in_cache('person', $curline)){ + $this->remove_tag_from_cache('person'); + }elseif($tagcontents = $this->full_tag_found_in_cache('membership', $curline)){ + $this->process_membership_tag($tagcontents); + $this->remove_tag_from_cache('membership'); + }elseif($tagcontents = $this->full_tag_found_in_cache('comments', $curline)){ + $this->remove_tag_from_cache('comments'); + }elseif($tagcontents = $this->full_tag_found_in_cache('properties', $curline)){ + $this->remove_tag_from_cache('properties'); + }else{ + break; + } + } + } // end of while loop + fclose($fh); + } // end of if(file_open) for second pass + + + */ + + $timeelapsed = time() - $starttime; + $this->log_line('Process has completed. Time taken: '.$timeelapsed.' seconds.'); + + + } // END of "if file is new" + + + // These variables are stored so we can compare them against the IMS file, next time round. + set_config('enrol_ims_prev_time', $filemtime); + set_config('enrol_ims_prev_md5', $md5); + set_config('enrol_ims_prev_path', $filename); + + + + }else{ // end of if(file_exists) + $this->log_line('File not found: '.$filename); + } + + if (!empty($CFG->enrol_mailadmins)) { + $msg = "An IMS enrolment has been carried out within Moodle.\nTime taken: $timeelapsed seconds.\n\n"; + if(!empty($CFG->enrol_logtolocation)){ + if($this->logfp){ + $msg .= "Log data has been written to:\n"; + $msg .= "$CFG->enrol_logtolocation\n"; + $msg .= "(Log file size: ".ceil(filesize($CFG->enrol_logtolocation)/1024)."Kb)\n\n"; + }else{ + $msg .= "The log file appears not to have been successfully written.\nCheck that the file is writeable by the server:\n"; + $msg .= "$CFG->enrol_logtolocation\n\n"; + } + }else{ + $msg .= "Logging is currently not active."; + } + + email_to_user(get_admin(), get_admin(), "Moodle IMS Enterprise enrolment notification", $msg); + $this->log_line('Notification email sent to administrator.'); + + } + + if($this->logfp){ + fclose($this->logfp); + } + + +} // end of cron() function + +/** +* Check if a complete tag is found in the cached data, which usually happens +* when the end of the tag has only just been loaded into the cache. +* Returns either false, or the contents of the tag (including start and end). +* @param string $tagname Name of tag to look for +* @param string $latestline The very last line in the cache (used for speeding up the match) +*/ +function full_tag_found_in_cache($tagname, $latestline){ // Return entire element if found. Otherwise return false. + if(strpos(strtolower($latestline), '')===false){ + return false; + }elseif(preg_match('{(<'.$tagname.'\b.*?>.*?)}is', $this->xmlcache, $matches)){ + return $matches[1]; + }else return false; +} + +/** +* Remove complete tag from the cached data (including all its contents) - so +* that the cache doesn't grow to unmanageable size +* @param string $tagname Name of tag to look for +*/ +function remove_tag_from_cache($tagname){ // Trim the cache so we're not in danger of running out of memory. + ///echo "

remove_tag_from_cache: $tagname

"; flush(); ob_flush(); + // echo "

remove_tag_from_cache:
".htmlspecialchars($this->xmlcache); + $this->xmlcache = trim(preg_replace('{<'.$tagname.'\b.*?>.*?}is', '', $this->xmlcache, 1)); // "1" so that we replace only the FIRST instance + // echo "
".htmlspecialchars($this->xmlcache)."

"; +} + +/** +* Very simple convenience function to return the "recstatus" found in person/group/role tags. +* 1=Add, 2=Update, 3=Delete, as specified by IMS, and we also use 0 to indicate "unspecified". +* @param string $tagdata the tag XML data +* @param string $tagname the name of the tag we're interested in +*/ +function get_recstatus($tagdata, $tagname){ + if(preg_match('{<'.$tagname.'\b[^>]*recstatus\s*=\s*["\'](\d)["\']}is', $tagdata, $matches)){ + // echo "

get_recstatus($tagname) found status of $matches[1]

"; + return intval($matches[1]); + }else{ + // echo "

get_recstatus($tagname) found nothing

"; + return 0; // Unspecified + } +} + +/** +* Process the group tag. This defines a Moodle course. +* @param string $tagconents The raw contents of the XML element +*/ +function process_group_tag($tagcontents){ + global $CFG; + + // Process tag contents + unset($group); + if(preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)){ + $group->coursecode = trim($matches[1]); + } + if(preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)){ + $group->description = trim($matches[1]); + } + if(preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)){ + $group->category = trim($matches[1]); + } + + $recstatus = ($this->get_recstatus($tagcontents, 'group')); + //echo "

get_recstatus for this group returned $recstatus

"; + + if(!(strlen($group->coursecode)>0)){ + $this->log_line('Error at line '.$line.': Unable to find course code in \'group\' element.'); + }else{ + // First, truncate the course code if desired + if(intval($CFG->enrol_truncatecoursecodes)>0){ + $group->coursecode = ($CFG->enrol_truncatecoursecodes > 0) + ? substr($group->coursecode, 0, intval($CFG->enrol_truncatecoursecodes)) + : $group->coursecode; + } + + /* -----------Course aliasing is DEACTIVATED until a more general method is in place--------------- + + // Second, look in the course alias table to see if the code should be translated to something else + if($aliases = get_field('enrol_coursealias', 'toids', 'fromid', $group->coursecode)){ + $this->log_line("Found alias of course code: Translated $group->coursecode to $aliases"); + // Alias is allowed to be a comma-separated list, so let's split it + $group->coursecode = explode(',', $aliases); + } + */ + + // For compatibility with the (currently inactive) course aliasing, we need this to be an array + $group->coursecode = array($group->coursecode); + + // Third, check if the course(s) exist + foreach($group->coursecode as $coursecode){ + $coursecode = trim($coursecode); + if(!get_field('course', 'id', 'idnumber', $coursecode) && $CFG->enrol_createnewcourses){ + // Create the (hidden) course(s) if not found + $course->fullname = $group->description; + $course->shortname = $coursecode; + $course->idnumber = $coursecode; + $course->format = 'topics'; + $course->visible = 0; + // Insert default names for teachers/students, from the current language + $site = get_site(); + if (current_language() == $CFG->lang) { + $course->teacher = $site->teacher; + $course->teachers = $site->teachers; + $course->student = $site->student; + $course->students = $site->students; + } else { + $course->teacher = get_string("defaultcourseteacher"); + $course->teachers = get_string("defaultcourseteachers"); + $course->student = get_string("defaultcoursestudent"); + $course->students = get_string("defaultcoursestudents"); + } + + // Handle course categorisation (taken from the group.org.orgunit field if present) + if(strlen($group->category)>0){ + // If the category is defined and exists in Moodle, we want to store it in that one + if($catid = get_field('course_categories', 'id', 'name', addslashes($group->category))){ + $course->category = $catid; + }elseif($CFG->enrol_createnewcategories){ + // Else if we're allowed to create new categories, let's create this one + $newcat->name = $group->category; + $newcat->visible = 0; + if($catid = insert_record('course_categories', $newcat)){ + $course->category = $catid; + $this->log_line("Created new (hidden) category, #$catid: $newcat->name"); + }else{ + $this->log_line('Failed to create new category: '.$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 = 1; + } + }else{ + $course->category = 1; + } + $course->timecreated = time(); + $course->startdate = time(); + $course->numsections = 1; + // Choose a sort order that puts us at the start of the list! + $sortinfo = get_record_sql('SELECT MIN(sortorder) AS min, + MAX(sortorder) AS max + FROM ' . $CFG->prefix . 'course WHERE category<>0'); + if (is_object($sortinfo)) { // no courses? + $max = $sortinfo->max; + $min = $sortinfo->min; + unset($sortinfo); + $course->sortorder = $min - 1; + }else{ + $course->sortorder = 1000; + } + if($course->id = insert_record('course', $course)){ + + // Setup the blocks + $page = page_create_object(PAGE_COURSE_VIEW, $course->id); + blocks_repopulate_page($page); // Return value not checked because you can always edit later + + $section = NULL; + $section->course = $course->id; // Create a default section. + $section->section = 0; + $section->id = insert_record("course_sections", $section); + + fix_course_sortorder(); + add_to_log(SITEID, "course", "new", "view.php?id=$course->id", "$course->fullname (ID $course->id)"); + + $this->log_line("Created course $coursecode in Moodle (Moodle ID is $course->id)"); + }else{ + $this->log_line('Failed to create course '.$coursecode.' in Moodle'); + } + }elseif($recstatus==3 && ($courseid = get_field('course', 'id', 'idnumber', $coursecode))){ + // If course does exist, but recstatus==3 (delete), then set the course as hidden + set_field('course', 'visible', '0', 'id', $courseid); + } + } // End of foreach(coursecode) + } +} // End process_group_tag() + +/** +* Process the person tag. This defines a Moodle user. +* @param string $tagconents The raw contents of the XML element +*/ +function process_person_tag($tagcontents){ + global $CFG; + + if(preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)){ + $person->idnumber = trim($matches[1]); + } + if(preg_match('{.*?.*?(.+?).*?.*?}is', $tagcontents, $matches)){ + $person->firstname = trim($matches[1]); + } + if(preg_match('{.*?.*?(.+?).*?.*?}is', $tagcontents, $matches)){ + $person->lastname = trim($matches[1]); + } + if(preg_match('{(.*?)}is', $tagcontents, $matches)){ + $person->username = trim($matches[1]); + }elseif($CFG->enrol_imssourcedidfallback){ + // This is the point where we can fall back to useing the "sourcedid" if "userid" is not supplied + $person->username = $person->idnumber; + } + if(preg_match('{(.*?)}is', $tagcontents, $matches)){ + $person->email = trim($matches[1]); + } + if(preg_match('{(.*?)}is', $tagcontents, $matches)){ + $person->url = trim($matches[1]); + } + if(preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)){ + $person->city = trim($matches[1]); + } + if(preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)){ + $person->country = trim($matches[1]); + } + + // Fix case of some of the fields if required + if($CFG->enrol_fixcaseusernames && isset($person->username)){ + $person->username = strtolower($person->username); + } + if($CFG->enrol_fixcasepersonalnames){ + if(isset($person->firstname)){ + $person->firstname = ucwords(strtolower($person->firstname)); + } + if(isset($person->lastname)){ + $person->lastname = ucwords(strtolower($person->lastname)); + } + } + + $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 + // In the "users" table we can do this by setting deleted=1 + if($recstatus==3){ + + if($CFG->enrol_imsdeleteusers){ // If we're allowed to delete user records + // Make sure their "deleted" field is set to one + set_field('user', 'deleted', 1, 'username', $person->username); + $this->log_line("Marked user record for user '$person->username' (ID number $person->idnumber) as deleted."); + }else{ + $this->log_line("Ignoring deletion request for user '$person->username' (ID number $person->idnumber)."); + } + + }else{ // Add or update record + + + // If the user exists (matching sourcedid) then we don't need to do anything. + if(!get_field('user', 'id', 'idnumber', $person->idnumber) && $CFG->enrol_createnewusers){ + // If they don't exist and haven't a defined username, we log this as a potential problem. + if((!isset($person->username)) || (strlen($person->username)==0)){ + $this->log_line("Cannot create new user for ID # $person->idnumber - no username listed in IMS data for this person."); + }elseif(get_field('user', 'id', 'username', $person->username)){ + // If their idnumber is not registered but their user ID is, then add their idnumber to their record + set_field('user', 'idnumber', addslashes($person->idnumber), 'username', $person->username); + }else{ + + // If they don't exist and they have a defined username, and $CFG->enrol_createnewusers == true, we create them. + $person->lang = $CFG->lang; + $person->confirmed = 1; + $person->timemodified = time(); + if($id = insert_record('user', addslashes_object($person))){ + /* + Photo processing is deactivated until we hear from Moodle dev forum about modification to gdlib. + + //Antoni Mas. 07/12/2005. If a photo URL is specified then we might want to load + // it into the user's profile. Beware that this may cause a heavy overhead on the server. + if($CFG->enrol_processphoto){ + if(preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)){ + $person->urlphoto = trim($matches[1]); + } + //Habilitam el flag que ens indica que el personatge t foto prpia. + $person->picture = 1; + //Llibreria creada per nosaltres mateixos. + require_once($CFG->dirroot.'/lib/gdlib.php'); + if ($usernew->picture = save_profile_image($id, $person->urlphoto,'users', true)) { + set_field('user', 'picture', $usernew->picture, 'id', $id); /// Note picture in DB + } + } + */ + $this->log_line("Created user record for user '$person->username' (ID number $person->idnumber)."); + }else{ + $this->log_line("Database error while trying to create user record for user '$person->username' (ID number $person->idnumber)."); + } + } + }elseif($CFG->enrol_createnewusers){ + $this->log_line("User record already exists for user '$person->username' (ID number $person->idnumber)."); + + // Make sure their "deleted" field is set to zero. + set_field('user', 'deleted', 0, 'username', $person->username); + }else{ + $this->log_line("No user record found for '$person->username' (ID number $person->idnumber)."); + } + + } // End of are-we-deleting-or-adding + +} // End process_person_tag() + +/** +* Process the membership tag. This defines whether the specified Moodle users +* should be added/removed as teachers/students. +* @param string $tagconents The raw contents of the XML element +*/ +function process_membership_tag($tagcontents){ + global $CFG; + $studentstally = 0; + $teacherstally = 0; + $studentsuntally = 0; + $teachersuntally = 0; + + // In order to reduce the number of db queries required, group name/id associations are cached in this array: + $groupids = array(); + + if(preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)){ + $ship->coursecode = ($CFG->enrol_truncatecoursecodes > 0) + ? substr(trim($matches[1]), 0, intval($CFG->enrol_truncatecoursecodes)) + : trim($matches[1]); + $ship->courseid = get_field('course', 'id', 'idnumber', $ship->coursecode); + } + if($ship->courseid && preg_match_all('{(.*?)}is', $tagcontents, $membermatches, PREG_SET_ORDER)){ + foreach($membermatches as $mmatch){ + unset($member); + unset($memberstoreobj); + if(preg_match('{.*?(.+?).*?}is', $mmatch[1], $matches)){ + $member->idnumber = trim($matches[1]); + } + if(preg_match('{}is', $mmatch[1], $matches)){ + $member->roletype = trim($matches[1]); // 01 means Student, 02 means Instructor, 3 means ContentDeveloper, and there are more besides + }elseif($CFG->enrol_imscapitafix && preg_match('{(.+?)}is', $mmatch[1], $matches)){ + // The XML that comes out of Capita Student Records seems to contain a misinterpretation of the IMS specification! + $member->roletype = trim($matches[1]); // 01 means Student, 02 means Instructor, 3 means ContentDeveloper, and there are more besides + } + if(preg_match('{(.+?).*?}is', $mmatch[1], $matches)){ + $member->status = trim($matches[1]); // 1 means active, 0 means inactive - treat this as enrol vs unenrol + } + + $recstatus = ($this->get_recstatus($mmatch[1], 'role')); + if($recstatus==3){ + $member->status = 0; // See above - recstatus of 3 (==delete) is treated the same as status of 0 + //echo "

process_membership_tag: unenrolling member due to recstatus of 3

"; + } + + $timeframe->begin = 0; + $timeframe->end = 0; + if(preg_match('{(.+?)
.*?}is', $mmatch[1], $matches)){ + $timeframe = $this->decode_timeframe($matches[1]); + } + if(preg_match('{.*?(.+?).*?.*?}is', $mmatch[1], $matches)){ + $member->groupname = trim($matches[1]); + // The actual processing (ensuring a group record exists, etc) occurs below, in the enrol-a-student clause + } + + // Add or remove this student or teacher to the course... + $memberstoreobj->userid = get_field('user', 'id', 'idnumber', $member->idnumber); + $memberstoreobj->enrol = 'imsenterprise'; + $memberstoreobj->course = $ship->courseid; + $memberstoreobj->time = time(); + $memberstoreobj->timemodified = time(); + switch($member->roletype){ + case '01': + case 'Student': + if(intval($member->status) == 1){ + // Enrol + if ((!enrol_student($memberstoreobj->userid, $memberstoreobj->course, $timeframe->begin, $timeframe->end, 'imsenterprise')) && (trim($memberstoreobj->userid)!='')) { + $this->log_line("Error enrolling $memberstoreobj->userid ($member->idnumber) in course $memberstoreobj->course"); + }else{ + $studentstally++; + + // At this point we can also ensure the group membership is recorded if present + if(isset($member->groupname)){ + // Create the group if it doesn't exist - either way, make sure we know the group ID + if(isset($groupids[$member->groupname])){ + $member->groupid = $groupids[$member->groupname]; // Recall the group ID from cache if available + }else{ + if($groupid = get_field('groups', 'id', 'name', addslashes($member->groupname), 'courseid', $ship->courseid)){ + $member->groupid = $groupid; + $groupids[$member->groupname] = $groupid; // Store ID in cache + }else{ + // Attempt to create the group + $group->name = addslashes($member->groupname); + $group->courseid = $ship->courseid; + $group->timecreated = time(); + $group->timemodified = time(); + $groupid = insert_record('groups', $group); + $this->log_line('Added a new group for this course: '.$group->name); + $groupids[$member->groupname] = $groupid; // Store ID in cache + $member->groupid = $groupid; + } + } + // Add the user-to-group association if it doesn't already exist + if($member->groupid && !get_field('groups_members', 'groupid', $member->groupid, 'userid', $memberstoreobj->userid)){ + $gm->groupid = $member->groupid; + $gm->userid = $memberstoreobj->userid; + $gm->timeadded = time(); + insert_record('groups_members', $gm); + } + } // End of group-enrolment (from member.role.extension.cohort tag) + + } + }elseif($CFG->enrol_imsunenrol){ + // Unenrol + if (! unenrol_student($memberstoreobj->userid, $memberstoreobj->course)) { + $this->log_line('Error unenrolling from course'); + }else{ + $studentsuntally++; + //$this->log_line('Unenrolled student '.$member->idnumber.' from course'); + } + } + break; + case '02': + case 'Instructor': + if(intval($member->status) == 1){ + // Enrol + if (! add_teacher($memberstoreobj->userid, $memberstoreobj->course, 0, '', $timeframe->begin, $timeframe->end, 'imsenterprise')) { + $this->log_line('Error adding teacher to course'); + }else{ + $teacherstally++; + } + }elseif($CFG->enrol_imsunenrol){ + // Unenrol + if (! remove_teacher($memberstoreobj->userid, $memberstoreobj->course)) { + $this->log_line('Error removing teacher from course'); + }else{ + $teachersuntally++; + //$this->log_line('Unenrolled teacher '.$member->idnumber.' from course'); + } + } + break; + case '03': + case 'ContentDeveloper': + if(intval($member->status) == 1){ + // Enrol + if (! add_teacher($memberstoreobj->userid, $memberstoreobj->course, 1, '', $timeframe->begin, $timeframe->end, 'imsenterprise')) { + $this->log_line('Error adding teacher to course'); + }else{ + $teacherstally++; + } + }elseif($CFG->enrol_imsunenrol){ + // Unenrol + if (! remove_teacher($memberstoreobj->userid, $memberstoreobj->course)) { + $this->log_line('Error removing teacher from course'); + }else{ + $teachersuntally++; + //$this->log_line('Unenrolled teacher '.$member->idnumber.' from course'); + } + } + break; + default: + $this->log_line('ERROR - Unhandled role type detected. Role code is '.$member->roletype.'. Please contact the developer to fix this.'); + break; + } + + } + $this->log_line("Added $studentstally student(s) and $teacherstally teacher(s) to course $ship->coursecode"); + if($studentsuntally + $teachersuntally > 0){ + $this->log_line("Removed $studentsuntally student(s) and $teachersuntally teacher(s) from course $ship->coursecode"); + } + } +} // End process_membership_tag() + +/** +* Process the properties tag. The only data from this element +* that is relevant is whether a is specified. +* @param string $tagconents The raw contents of the XML element +*/ +function process_properties_tag($tagcontents){ + global $CFG; + + if($CFG->enrol_imsrestricttarget){ + if(!(preg_match('{'.preg_quote($CFG->enrol_imsrestricttarget).'}is', $tagcontents, $matches))){ + $this->log_line("Skipping processing: required target \"$CFG->enrol_imsrestricttarget\" not specified in this data."); + $this->continueprocessing = false; + } + } +} + +/** +* Store logging information. This does two things: uses the {@link mtrace()} +* function to print info to screen/STDOUT, and also writes log to a text file +* if a path has been specified. +* @param string $string Text to write (newline will be added automatically) +*/ +function log_line($string){ + mtrace($string); + if($this->logfp) { + fwrite($this->logfp, $string . "\n"); + } +} + +/** +* Process the INNER contents of a tag, to return beginning/ending dates. +*/ +function decode_timeframe($string){ // Pass me the INNER CONTENTS of a tag - beginning and/or ending is returned, in unix time, zero indicating not specified + $ret->begin = $ret->end = 0; + // Explanatory note: The matching will ONLY match if the attribute restrict="1" + // because otherwise the time markers should be ignored (participation should be + // allowed outside the period) + if(preg_match('{(\d\d\d\d)-(\d\d)-(\d\d)}is', $string, $matches)){ + $ret->begin = mktime(0,0,0, $matches[2], $matches[3], $matches[1]); + } + if(preg_match('{(\d\d\d\d)-(\d\d)-(\d\d)}is', $string, $matches)){ + $ret->end = mktime(0,0,0, $matches[2], $matches[3], $matches[1]); + } + return $ret; +} // End decode_timeframe + +} // end of class + +?> diff --git a/enrol/imsenterprise/entv1p1_conformance_summary.html b/enrol/imsenterprise/entv1p1_conformance_summary.html new file mode 100644 index 0000000000..53f2fbea25 --- /dev/null +++ b/enrol/imsenterprise/entv1p1_conformance_summary.html @@ -0,0 +1,244 @@ + + + + +Enterprise Conformance Summary for v1.1 + + + + + +
+
Enterprise Conformance Summary for v1.1
+
+

This table gives a summmary of the elements that may be found in an IMS Enterprise 1.1 data file, + and Moodle's suppport for those elements via this enrolment plugin.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AcceptNotes
propertiesY
personY
  recstatusY
  sourcedidY
  useridY
  nameY
  demographicsN
  emailY
  urlY
  telN
  adrY
  photoY?This currently is possible but requires a small modification to one of Moodle's + library files, so for a standard simple installation, the import of user photos is + not supported.
  systemroleN
  institutionroleN
  datasourceN
groupY
  recstatusY"Delete" command will hide rather than permanently delete a course.
  sourcedidY
  grouptypeN
  descriptionY
  orgY
  timeframeN
  enrollcontrolN
  emailN
  urlN
  relationshipNThis could be useful to implement in future, to specify also-known-as (i.e. course aliases)...
  datasourceN
membershipY
  sourcedidY
  memberYNB: Can only enrol <person>s as members - not <group>s (idtype=2)
  sourcedidY
  roleY
    recstatusY
    subroleN
    statusY
    useridN
    datetimeN
    timeframeY
    imterimresultN
    finalresultN
    emailN
    datasourceN
+
+

Note: All the core data structures are supported.

+ + \ No newline at end of file diff --git a/enrol/imsenterprise/examples/example-grouped.xml b/enrol/imsenterprise/examples/example-grouped.xml new file mode 100644 index 0000000000..e42f00ce4b --- /dev/null +++ b/enrol/imsenterprise/examples/example-grouped.xml @@ -0,0 +1,171 @@ + + + + sits:vision + 28/Oct/2005 + moodle + + + + sits:vision + 91046433 + + + + Simon Shikalislami + + SHIKALISLAMI + SIMON + + + + + + + + + sits:vision + 90078058 + + IMGBY26 + + Chloe Eva Piotrowska + + PIOTROWSKA + CHLOE + + + + + + + + + sits:vision + 90182274 + + IMCAY21 + + Aliza Quist Yeboah + + YEBOAH + ALIZA + + + + + + + + + sits:vision + 90528553 + + IMGBX76 + + Miriam Rajakumar + + RAJAKUMAR + MIRIAM + + + + + + + + + sits:vision + DSTOW61 + + CCAADAS + + Dan Stowell + + STOWELL + DAN + + + + + + + + + sits:vision + PHRE1001A2005/06T1/2 + + + Phrenology + + + + + Inconsequential studies + + + + sits:vision + PHRE1001A2005/06T1/2 + + + + sits:vision + 91046433 + + + + + 1 + yellow team + + + + + sits:vision + 90078058 + + + + IMGBY26 + 1 + green team + + + + + sits:vision + 90182274 + + + + IMCAY21 + 1 + green team + + + + + sits:vision + 90528553 + + + + IMGBX76 + 12005-12-252005-12-31 + yellow team + + + + + sits:vision + DSTOW61 + + + + CCAADAS + 1 + + + + diff --git a/enrol/imsenterprise/examples/example-oneline.xml b/enrol/imsenterprise/examples/example-oneline.xml new file mode 100644 index 0000000000..8626c55f4e --- /dev/null +++ b/enrol/imsenterprise/examples/example-oneline.xml @@ -0,0 +1 @@ +sits:vision28/Oct/2005sits:vision91046433Simon Shikalislami SHIKALISLAMI SIMONsits:vision90078058IMGBY26Chloe Eva Piotrowska PIOTROWSKA CHLOEsits:vision90182274IMCAY21Aliza Quist Yeboah YEBOAH ALIZAsits:vision90528553IMGBX76Miriam Rajakumar RAJAKUMAR MIRIAMsits:visionDSTOW61CCAADASDan Stowell STOWELL DANsits:visionPHRE1001A2005/06T1/2Phrenologysits:visionPHRE1001A2005/06T1/2sits:vision 91046433 1sits:vision 90078058 IMGBY261sits:vision 90182274 IMCAY211sits:vision 90528553 IMGBX761sits:visionDSTOW61 CCAADAS1 \ No newline at end of file diff --git a/enrol/imsenterprise/examples/example.xml b/enrol/imsenterprise/examples/example.xml new file mode 100644 index 0000000000..7397b0c067 --- /dev/null +++ b/enrol/imsenterprise/examples/example.xml @@ -0,0 +1,166 @@ + + + + sits:vision + 28/Oct/2005 + + + + sits:vision + 91046433 + + + + Simon Shikalislami + + SHIKALISLAMI + SIMON + + + + + + + + + sits:vision + 90078058 + + IMGBY26 + + Chloe Eva Piotrowska + + PIOTROWSKA + CHLOE + + + + + + + + + sits:vision + 90182274 + + IMCAY21 + + Aliza Quist Yeboah + + YEBOAH + ALIZA + + + + + + + + + sits:vision + 90528553 + + IMGBX76 + + Miriam Rajakumar + + RAJAKUMAR + MIRIAM + + + + + + + + + sits:vision + DSTOW61 + + CCAADAS + + Dan Stowell + + STOWELL + DAN + + + + + + + + + sits:vision + PHRE1001A2005/06T1/2 + + + Phrenology + + + + + Inconsequential studies + + + + sits:vision + PHRE1001A2005/06T1/2 + + + + sits:vision + 91046433 + + + + + 1 + + + + + sits:vision + 90078058 + + + + IMGBY26 + 1 + + + + + sits:vision + 90182274 + + + + IMCAY21 + 1 + + + + + sits:vision + 90528553 + + + + IMGBX76 + 1 + + + + + sits:vision + DSTOW61 + + + + CCAADAS + 1 + + + + diff --git a/enrol/imsenterprise/importnow.php b/enrol/imsenterprise/importnow.php new file mode 100644 index 0000000000..cd8a0c5289 --- /dev/null +++ b/enrol/imsenterprise/importnow.php @@ -0,0 +1,36 @@ +shortname: $str->enrolments", "$site->fullname", + "$str->administration -> + $str->users -> + $str->enrolments -> IMS import"); + +require_once('enrol.php'); + +//echo "Creating the IMS Enterprise enroller object\n"; +$enrol = new enrolment_plugin_imsenterprise(); + +?> +

Launching the IMS Enterprise "cron" function. The import log will appear below (giving details of any +problems that might require attention).

+
cron();
+?>
\ No newline at end of file diff --git a/lang/en_utf8/enrol_imsenterprise.php b/lang/en_utf8/enrol_imsenterprise.php new file mode 100644 index 0000000000..aa61a5168d --- /dev/null +++ b/lang/en_utf8/enrol_imsenterprise.php @@ -0,0 +1,39 @@ +IMS Enterprise specifications containing person, group, and membership XML elements.'; +$string['createnewusers'] = 'Create user accounts for users not yet registered in Moodle'; +$string['deleteusers'] = 'Delete user accounts when specified in IMS data'; +$string['fixcaseusernames'] = 'Change usernames to lower case'; +$string['fixcasepersonalnames'] = 'Change personal names to Title Case'; +$string['truncatecoursecodes'] = 'Truncate course codes to this length'; +$string['createnewcourses'] = 'Create new (hidden) courses if not found in Moodle'; +$string['createnewcategories'] = 'Create new (hidden) course categories if not found in Moodle'; +$string['zeroisnotruncation'] = '0 indicates no truncation'; +$string['cronfrequency'] = 'Frequency of processing'; +$string['allowunenrol'] = 'Allow the IMS data to unenrol students/teachers'; +$string['sourcedidfallback'] = 'Use the "sourcedid" for a person\'s userid if the "userid" field is not found'; +$string['usecapitafix']= 'Tick this box if using "Capita" (their XML format is slightly wrong)'; +$string['location'] = 'File location'; +$string['mailusers'] = 'Notify users by email'; +$string['mailadmins'] = 'Notify admin by email'; +$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['logtolocation'] = 'Log file output location (blank for no logging)'; +$string['restricttarget'] = 'Only process data if the following target is specified'; + +$string['aftersaving...']= 'Once you have saved your settings, you may wish to'; +$string['doitnow']= 'perform an IMS Enterprise import right now'; + +$string['filelockedmailsubject'] = 'Important error: Enrolment file'; +$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.'; + +?> diff --git a/lang/en_utf8/help/enrol-imsenterprise/capita.html b/lang/en_utf8/help/enrol-imsenterprise/capita.html new file mode 100644 index 0000000000..d0ceabfe97 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/capita.html @@ -0,0 +1 @@ +

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 activate this option - otherwise leave it un-ticked.

diff --git a/lang/en_utf8/help/enrol-imsenterprise/categorisation.html b/lang/en_utf8/help/enrol-imsenterprise/categorisation.html new file mode 100644 index 0000000000..30b477757b --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/categorisation.html @@ -0,0 +1,7 @@ +

Automatic Categorisation

+ +

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.

+ +

If no category exists with the desired name, then a HIDDEN category will be created.

diff --git a/lang/en_utf8/help/enrol-imsenterprise/createnewcourses.html b/lang/en_utf8/help/enrol-imsenterprise/createnewcourses.html new file mode 100644 index 0000000000..a4dc16de85 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/createnewcourses.html @@ -0,0 +1,9 @@ +

Automatic creation of new courses

+ +

The IMS Enterprise enrolment plugin can create new courses for any it finds in the IMS data but not in Moodle's database, if this setting is activated.

+ +

Courses are first queried by their "idnumber" - an alphanumeric field in Moodle's course table, which can specify the code used to identify the course in the Student Information System (for example). If that is not found, the course table is searched for the "short description", which in Moodle is the short course identifier as displayed in the breadcrumbs etc. (In some systems these two fields may well be identical.) Only when that search has failed can the plugin optionally create new courses.

+ +

Any newly-generated courses are HIDDEN when created. This is to prevent the possibility of students wandering into completely empty courses that the teacher may be unaware of.

+ + diff --git a/lang/en_utf8/help/enrol-imsenterprise/createnewusers.html b/lang/en_utf8/help/enrol-imsenterprise/createnewusers.html new file mode 100644 index 0000000000..76907e594e --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/createnewusers.html @@ -0,0 +1,8 @@ +

Automatic creation of user accounts

+ +

IMS Enterprise enrolment data typically describes a set of users. If this setting is turned on, accounts can be created for any users not found in Moodle's database.

+ +

Users are searched for first by their "idnumber", and second by their Moodle username.

+ +

Passwords

+

Passwords are not imported by the IMS Enterprise plugin. We recommend using Moodle's authentication plugins to authenticate users.

\ No newline at end of file diff --git a/lang/en_utf8/help/enrol-imsenterprise/deleteusers.html b/lang/en_utf8/help/enrol-imsenterprise/deleteusers.html new file mode 100644 index 0000000000..0b95b5d198 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/deleteusers.html @@ -0,0 +1,5 @@ +

Automatic deletion of user accounts

+ +

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), if this setting is turned on.

+ +

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.

\ No newline at end of file diff --git a/lang/en_utf8/help/enrol-imsenterprise/formatoverview.html b/lang/en_utf8/help/enrol-imsenterprise/formatoverview.html new file mode 100644 index 0000000000..cfc7f232e1 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/formatoverview.html @@ -0,0 +1,86 @@ +

Below is a simple guide to the basic structure of a typical IMS Enterprise +data file. Much more information is available on the IMS Enterprise official website.

+ +

You may like to read the conformance summary which describes which IMS data elements this plugin can process.

+ + +

Basic guide to IMS Enterprise file format

+ +

For any IMS-style enrolment you need a <group> tag which specifies the course, a <person> tag which specifies the user account, and a <membership> tag containing <member> tags which specify a person's role within a given course.

+

+ +

+Remember that the numeric keys used in the Moodle databases are not the interoperable data - a student data system is never going to know in advance that Joe is the 20th user added to the Moodle database - so those aren't the keys exchanged in this type of data.

+

+ +

+Typically a course would have a reference code as well as a name, so let's assume its code is MOODLE101. To define your course you could use

+

+ +

+  <group>
+    <sourcedid>
+      <source>MyDataSystem</source>
+      <id>MOODLE101</id>
+    </sourcedid>
+    <description>
+      <short>Moodle 101</short>
+    </description>
+  </group>
+

+ +

+The enrolment script will look for a course with code MOODLE101, and (optionally) create it if it doesn't exist. Similarly for the person - let's assume it's "jmoodle":

+

+ +

+  <person>
+    <sourcedid>
+      <source>MyDataSystem</source>
+      <id>jmoodle</id>
+    </sourcedid>
+    <userid>jmoodle</userid>
+    <name>
+      <fn>Joe Moodle</fn>
+      <n>
+        <family>MOODLE</family>
+        <given>JOE</given>
+      </n>
+    </name>
+  </person>
+

+ +

+If Joe doesn't already have an account, the script can (optionally) create an account for him.

+ + +

+Let's now look at the membership, adding the person to the course:

+

+ +

+  <membership>
+    <sourcedid>
+      <source>MyDataSystem</source>
+      <id>MOODLE101</id>
+    </sourcedid>
+    <member>
+      <sourcedid>
+        <source>MyDataSystem</source>
+        <id>jmoodle</id>
+      </sourcedid>
+      <role roletype="01">
+        <status>1</status>
+        <extension><cohort>unit 2</cohort></extension>
+      </role>
+    </member>
+  </membership>
+

+ +

+The IMS Enterprise specification does offer a facility for specifying start/end dates for enrolments, so those can be included using the <timeframe> tag if needed.

+

+ +

+If a person is already added to a group within the course, the script won't actually modify that. If they are not grouped, however, then the specified grouping will be applied.

+ diff --git a/lang/en_utf8/help/enrol-imsenterprise/photos.html b/lang/en_utf8/help/enrol-imsenterprise/photos.html new file mode 100644 index 0000000000..8b2b24ee02 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/photos.html @@ -0,0 +1,6 @@ +

When creating a new user account based on the Enterprise data, it's possible that a photo is specified for the user - usually by giving a URL to the image file.

+ +

If <photo> data is supplied, then Moodle can attempt to download the image file and turn it into the user-picture displayed within Moodle.

+ +

PLEASE NOTE: This can add a significant additional burden to the process if there are a lot of user accounts, because of the need to download, modify, and store the images. It is not recommended to activate this option in a context with a very large number of accounts to be created.

+ diff --git a/lang/en_utf8/help/enrol-imsenterprise/sourceddidfallback.html b/lang/en_utf8/help/enrol-imsenterprise/sourceddidfallback.html new file mode 100644 index 0000000000..2efb21c1c7 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/sourceddidfallback.html @@ -0,0 +1,9 @@ +

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.

+

Some student information systems fail to output the <userid> field. +If this is the case, you should activate this setting to allow for using the +<sourcedid> as the Moodle user ID.

+

Otherwise, leave this setting turned off.

\ No newline at end of file diff --git a/lang/en_utf8/help/enrol-imsenterprise/target.html b/lang/en_utf8/help/enrol-imsenterprise/target.html new file mode 100644 index 0000000000..0e8a9344ec --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/target.html @@ -0,0 +1,4 @@ +

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 many cases you don't need to worry about this. Leave the config 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. +

diff --git a/lang/en_utf8/help/enrol-imsenterprise/truncatecoursecodes.html b/lang/en_utf8/help/enrol-imsenterprise/truncatecoursecodes.html new file mode 100644 index 0000000000..399ca1c6e9 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/truncatecoursecodes.html @@ -0,0 +1 @@ +

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.

\ No newline at end of file diff --git a/lang/en_utf8/help/enrol-imsenterprise/unenrol.html b/lang/en_utf8/help/enrol-imsenterprise/unenrol.html new file mode 100644 index 0000000000..d54a592391 --- /dev/null +++ b/lang/en_utf8/help/enrol-imsenterprise/unenrol.html @@ -0,0 +1,17 @@ +

Unenrolling students/teachers

+ +

The Enterprise data can add as well as remove course enrolments - for students and for teachers. If this setting is turned on, then Moodle will carry out unenrolments when specified in the data.

+ +

There are three ways of unenrolling students within the IMS data:

+ +
  • A <member> element which specifies the given student and course, and with the "recstatus" attribute of the <role> element set to 3 (which means "delete"). THIS IS NOT YET IMPLEMENTED IN THE MOODLE PLUGIN.
  • + +
  • A <member> element which specifies the given student and course, and with the <status> element set to 0 (which means "inactive").
+ + +

The third method is slightly different. It does not require this config setting to be activated, and can be specified well in advance of the unenrolment date:

+ +
  • A element which specifies a for the enrolment can specify the begin and/or end dates for enrolment of this particular student. These dates are loaded into Moodle's enrolment data table if present, and so after the end-date, a student will no longer be able to access that particular course.
+ + + -- 2.39.5