From 295e4fa8f49cab5ab5f17dfc235d6039b615c571 Mon Sep 17 00:00:00 2001 From: Dan Poltawski Date: Thu, 3 Oct 2013 11:22:50 +0800 Subject: [PATCH] MDL-29070 enrol_imsenterprise: more code cleanups Including: * fix whitespace * coding style fixes: * Declare method visibility * Don't have variable names with underscores * Comments without . * if/for loop spacing * Unused variables * Split long lines * Removed outdates files * moodlecheck warnings * code checker warnings --- enrol/imsenterprise/README.txt | 63 - enrol/imsenterprise/TODO.txt | 39 - enrol/imsenterprise/db/install.php | 3 + enrol/imsenterprise/db/upgrade.php | 22 +- .../entv1p1_conformance_summary.html | 246 --- enrol/imsenterprise/importnow.php | 32 +- enrol/imsenterprise/lib.php | 1330 +++++++++-------- enrol/imsenterprise/locallib.php | 69 +- enrol/imsenterprise/settings.php | 82 +- .../tests/imsenterprise_test.php | 27 +- enrol/imsenterprise/version.php | 6 +- 11 files changed, 829 insertions(+), 1090 deletions(-) delete mode 100644 enrol/imsenterprise/README.txt delete mode 100644 enrol/imsenterprise/TODO.txt delete mode 100644 enrol/imsenterprise/entv1p1_conformance_summary.html diff --git a/enrol/imsenterprise/README.txt b/enrol/imsenterprise/README.txt deleted file mode 100644 index ff5ae8c1fcd..00000000000 --- a/enrol/imsenterprise/README.txt +++ /dev/null @@ -1,63 +0,0 @@ - - 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 deleted file mode 100644 index f7f890e3328..00000000000 --- a/enrol/imsenterprise/TODO.txt +++ /dev/null @@ -1,39 +0,0 @@ -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/db/install.php b/enrol/imsenterprise/db/install.php index db69d201005..dcf3f7cdd02 100644 --- a/enrol/imsenterprise/db/install.php +++ b/enrol/imsenterprise/db/install.php @@ -24,6 +24,9 @@ defined('MOODLE_INTERNAL') || die(); +/** + * Post installation procedure + */ function xmldb_enrol_imsenterprise_install() { global $CFG, $DB; diff --git a/enrol/imsenterprise/db/upgrade.php b/enrol/imsenterprise/db/upgrade.php index 77a63ffc423..12c50c0ffeb 100644 --- a/enrol/imsenterprise/db/upgrade.php +++ b/enrol/imsenterprise/db/upgrade.php @@ -18,31 +18,31 @@ * This file keeps track of upgrades to the imsenterprise enrolment plugin * * @package enrol_imsenterprise - * @copyright 2011 Petr Skoda {@link http://skodak.org + * @copyright 2011 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); +/** + * Performs upgrade of the database structure and data + * + * @param int $oldversion the version we are upgrading from + * @return bool true + */ function xmldb_enrol_imsenterprise_upgrade($oldversion) { global $CFG, $DB; $dbman = $DB->get_manager(); + // Moodle v2.3.0 release upgrade line. + // Put any upgrade step following this. - // Moodle v2.3.0 release upgrade line - // Put any upgrade step following this - - - // Moodle v2.4.0 release upgrade line - // Put any upgrade step following this - + // Moodle v2.4.0 release upgrade line. + // Put any upgrade step following this. // Moodle v2.5.0 release upgrade line. // Put any upgrade step following this. - return true; } - - diff --git a/enrol/imsenterprise/entv1p1_conformance_summary.html b/enrol/imsenterprise/entv1p1_conformance_summary.html deleted file mode 100644 index 6d125e72534..00000000000 --- a/enrol/imsenterprise/entv1p1_conformance_summary.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - -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.

- - diff --git a/enrol/imsenterprise/importnow.php b/enrol/imsenterprise/importnow.php index 102d038ea93..f3feda77c68 100644 --- a/enrol/imsenterprise/importnow.php +++ b/enrol/imsenterprise/importnow.php @@ -1,11 +1,33 @@ . + +/** + * Import IMS Enterprise file immediately. + * + * @package enrol_imsenterprise + * @copyright 2006 Dan Stowell + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); require_login(0, false); require_capability('moodle/site:config', context_system::instance()); $site = get_site(); -/// get language strings +// Get language strings. $PAGE->set_context(context_system::instance()); $PAGE->set_url('/enrol/imsenterprise/importnow.php'); @@ -14,7 +36,8 @@ $PAGE->set_heading(get_string('importimsfile', 'enrol_imsenterprise')); $PAGE->navbar->add(get_string('administrationsite')); $PAGE->navbar->add(get_string('plugins', 'admin')); $PAGE->navbar->add(get_string('enrolments', 'enrol')); -$PAGE->navbar->add(get_string('pluginname', 'enrol_imsenterprise'), new moodle_url('/admin/settings.php', array('section'=>'enrolsettingsimsenterprise'))); +$PAGE->navbar->add(get_string('pluginname', 'enrol_imsenterprise'), + new moodle_url('/admin/settings.php', array('section' => 'enrolsettingsimsenterprise'))); $PAGE->navbar->add(get_string('importimsfile', 'enrol_imsenterprise')); $PAGE->navigation->clear_cache(); @@ -22,17 +45,12 @@ echo $OUTPUT->header(); require_once('lib.php'); -//echo "Creating the IMS Enterprise enroller object\n"; $enrol = new enrol_imsenterprise_plugin(); ?>

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

cron();
 ?>
footer(); - -exit; -?> diff --git a/enrol/imsenterprise/lib.php b/enrol/imsenterprise/lib.php index 6d1ee95a1f5..5528e55bf2b 100644 --- a/enrol/imsenterprise/lib.php +++ b/enrol/imsenterprise/lib.php @@ -28,712 +28,742 @@ defined('MOODLE_INTERNAL') || die(); -/* - -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. - -*/ - require_once($CFG->dirroot.'/group/lib.php'); +/** + * IMS Enterprise file enrolment plugin. + * + * @copyright 2010 Eugene Venter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class enrol_imsenterprise_plugin extends enrol_plugin { -/** -* 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; + /** + * @var $logfp resource file pointer for writing log data to. + */ + protected $logfp; - // Get configs - $imsfilelocation = $this->get_config('imsfilelocation'); - $logtolocation = $this->get_config('logtolocation'); - $mailadmins = $this->get_config('mailadmins'); - $prev_time = $this->get_config('prev_time'); - $prev_md5 = $this->get_config('prev_md5'); - $prev_path = $this->get_config('prev_path'); + /** + * @var $continueprocessing bool flag to determine if processing should continue. + */ + protected $continueprocessing; - if (empty($imsfilelocation)) { - $filename = "$CFG->dataroot/1/imsenterprise-enrol.xml"; // Default location - } else { - $filename = $imsfilelocation; - } + /** + * @var $xmlcache string cache of xml lines. + */ + protected $xmlcache; - $this->logfp = false; // File pointer for writing log data to - if(!empty($logtolocation)) { - $this->logfp = fopen($logtolocation, 'a'); - } + /** + * @var $coursemappings array of mappings between IMS data fields and moodle course fields. + */ + protected $coursemappings; - $fileisnew = false; - if ( file_exists($filename) ) { - @set_time_limit(0); - $starttime = time(); + /** + * @var $rolemappings array of mappings between IMS roles and moodle roles. + */ + protected $rolemappings; - $this->log_line('----------------------------------------------------------------------'); - $this->log_line("IMS Enterprise enrol cron process launched at " . userdate(time())); - $this->log_line('Found file '.$filename); - $this->xmlcache = ''; + /** + * 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. + * + */ + public function cron() { + global $CFG; - // 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(); + // Get configs. + $imsfilelocation = $this->get_config('imsfilelocation'); + $logtolocation = $this->get_config('logtolocation'); + $mailadmins = $this->get_config('mailadmins'); + $prevtime = $this->get_config('prev_time'); + $prevmd5 = $this->get_config('prev_md5'); + $prevpath = $this->get_config('prev_path'); - $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($prev_path) || ($filename != $prev_path)) { - $fileisnew = true; - } elseif(isset($prev_time) && ($filemtime <= $prev_time)) { - $this->log_line('File modification time is not more recent than last update - skipping processing.'); - } elseif(isset($prev_md5) && ($md5 == $prev_md5)) { - $this->log_line('File MD5 hash is same as on last update - skipping processing.'); + if (empty($imsfilelocation)) { + $filename = "$CFG->dataroot/1/imsenterprise-enrol.xml"; // Default location. } else { - $fileisnew = true; // Let's process it! + $filename = $imsfilelocation; } - 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 - - // 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); - fix_course_sortorder(); - } // End of if(file_open). - - $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. - $this->set_config('prev_time', $filemtime); - $this->set_config('prev_md5', $md5); - $this->set_config('prev_path', $filename); - - }else{ // end of if(file_exists) - $this->log_line('File not found: '.$filename); - } - - if (!empty($mailadmins) && $fileisnew) { - $msg = "An IMS enrolment has been carried out within Moodle.\nTime taken: $timeelapsed seconds.\n\n"; - if(!empty($logtolocation)){ - if($this->logfp){ - $msg .= "Log data has been written to:\n"; - $msg .= "$logtolocation\n"; - $msg .= "(Log file size: ".ceil(filesize($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 .= "$logtolocation\n\n"; - } - }else{ - $msg .= "Logging is currently not active."; + $this->logfp = false; + if (!empty($logtolocation)) { + $this->logfp = fopen($logtolocation, 'a'); } - $eventdata = new stdClass(); - $eventdata->modulename = 'moodle'; - $eventdata->component = 'enrol_imsenterprise'; - $eventdata->name = 'imsenterprise_enrolment'; - $eventdata->userfrom = get_admin(); - $eventdata->userto = get_admin(); - $eventdata->subject = "Moodle IMS Enterprise enrolment notification"; - $eventdata->fullmessage = $msg; - $eventdata->fullmessageformat = FORMAT_PLAIN; - $eventdata->fullmessagehtml = ''; - $eventdata->smallmessage = ''; - message_send($eventdata); + $fileisnew = false; + if ( file_exists($filename) ) { + @set_time_limit(0); + $starttime = time(); - $this->log_line('Notification email sent to administrator.'); + $this->log_line('----------------------------------------------------------------------'); + $this->log_line("IMS Enterprise enrol cron process launched at " . userdate(time())); + $this->log_line('Found file '.$filename); + $this->xmlcache = ''; - } + // 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(); - if($this->logfp){ - fclose($this->logfp); - } + $md5 = md5_file($filename); // NB We'll write this value back to the database at the end of the cron. + $filemtime = filemtime($filename); - -} // 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 $DB; - - // Get configs - $truncatecoursecodes = $this->get_config('truncatecoursecodes'); - $createnewcourses = $this->get_config('createnewcourses'); - $createnewcategories = $this->get_config('createnewcategories'); - - // Process tag contents - $group = new stdClass(); - if (preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)) { - $group->coursecode = trim($matches[1]); - } - - if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { - $group->long = trim($matches[1]); - } - if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { - $group->short = trim($matches[1]); - } - if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { - $group->full = trim($matches[1]); - } - - if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { - $group->category = trim($matches[1]); - } - - $recstatus = ($this->get_recstatus($tagcontents, 'group')); - - 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($truncatecoursecodes)>0) { - $group->coursecode = ($truncatecoursecodes > 0) - ? substr($group->coursecode, 0, intval($truncatecoursecodes)) - : $group->coursecode; - } - - // 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 (!$DB->get_field('course', 'id', array('idnumber'=>$coursecode))) { - if (!$createnewcourses) { - $this->log_line("Course $coursecode not found in Moodle's course idnumbers."); - } else { - - // Create the (hidden) course(s) if not found - $courseconfig = get_config('moodlecourse'); // Load Moodle Course shell defaults - - // New course. - $course = new stdClass(); - 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; - } - } - - $course->idnumber = $coursecode; - $course->format = $courseconfig->format; - $course->visible = $courseconfig->visible; - $course->newsitems = $courseconfig->newsitems; - $course->showgrades = $courseconfig->showgrades; - $course->showreports = $courseconfig->showreports; - $course->maxbytes = $courseconfig->maxbytes; - $course->groupmode = $courseconfig->groupmode; - $course->groupmodeforce = $courseconfig->groupmodeforce; - $course->enablecompletion = $courseconfig->enablecompletion; - // 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(); - } - } else { - $course->category = $this->get_default_category_id(); - } - $course->timecreated = time(); - $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 - $course->id = $courseid; - enrol_course_updated(true, $course, null); - - // Setup the blocks - $course = $DB->get_record('course', array('id' => $courseid)); - blocks_add_default_course_blocks($course); - - // Create default 0-section - course_create_sections_if_missing($course, 0); - - 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 if ($recstatus==3 && ($courseid = $DB->get_field('course', 'id', array('idnumber'=>$coursecode)))) { - // If course does exist, but recstatus==3 (delete), then set the course as hidden - $DB->set_field('course', 'visible', '0', array('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, $DB; - - // Get plugin configs - $imssourcedidfallback = $this->get_config('imssourcedidfallback'); - $fixcaseusernames = $this->get_config('fixcaseusernames'); - $fixcasepersonalnames = $this->get_config('fixcasepersonalnames'); - $imsdeleteusers = $this->get_config('imsdeleteusers'); - $createnewusers = $this->get_config('createnewusers'); - - $person = new stdClass(); - 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]); - } - if($imssourcedidfallback && trim($person->username)==''){ - // 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; - } - 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($fixcaseusernames && isset($person->username)){ - $person->username = strtolower($person->username); - } - if($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 - if($recstatus==3){ - - if($imsdeleteusers){ // If we're allowed to delete user records - // Do not dare to hack the user.deleted field directly in database!!! - if ($user = $DB->get_record('user', array('username'=>$person->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0))) { - if (delete_user($user)) { - $this->log_line("Deleted user '$person->username' (ID number $person->idnumber)."); - } else { - $this->log_line("Error deleting '$person->username' (ID number $person->idnumber)."); - } + // 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($prevpath) || ($filename != $prevpath)) { + $fileisnew = true; + } else if (isset($prevtime) && ($filemtime <= $prevtime)) { + $this->log_line('File modification time is not more recent than last update - skipping processing.'); + } else if (isset($prevmd5) && ($md5 == $prevmd5)) { + $this->log_line('File MD5 hash is same as on last update - skipping processing.'); } else { - $this->log_line("Can not delete user '$person->username' (ID number $person->idnumber) - user does not exist."); - } - }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(!$DB->get_field('user', 'id', array('idnumber'=>$person->idnumber)) && $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."); - } else if ($DB->get_field('user', 'id', array('username'=>$person->username))){ - // If their idnumber is not registered but their user ID is, then add their idnumber to their record - $DB->set_field('user', 'idnumber', $person->idnumber, array('username'=>$person->username)); - } else { - - // If they don't exist and they have a defined username, and $createnewusers == true, we create them. - $person->lang = $CFG->lang; - $auth = explode(',', $CFG->auth); //TODO: this needs more work due tu multiauth changes, use first auth for now - $auth = reset($auth); - $person->auth = $auth; - $person->confirmed = 1; - $person->timemodified = time(); - $person->mnethostid = $CFG->mnet_localhost_id; - $id = $DB->insert_record('user', $person); - $this->log_line("Created user record for user '$person->username' (ID number $person->idnumber)."); - } - } elseif ($createnewusers) { - $this->log_line("User record already exists for user '$person->username' (ID number $person->idnumber)."); - - // It is totally wrong to mess with deleted users flag directly in database!!! - // There is no official way to undelete user, sorry.. - }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 $DB; - - // Get plugin configs - $truncatecoursecodes = $this->get_config('truncatecoursecodes'); - $imscapitafix = $this->get_config('imscapitafix'); - - $memberstally = 0; - $membersuntally = 0; - - // In order to reduce the number of db queries required, group name/id associations are cached in this array: - $groupids = array(); - - $ship = new stdClass(); - - if(preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)){ - $ship->coursecode = ($truncatecoursecodes > 0) - ? substr(trim($matches[1]), 0, intval($truncatecoursecodes)) - : trim($matches[1]); - $ship->courseid = $DB->get_field('course', 'id', array('idnumber'=>$ship->coursecode)); - } - if($ship->courseid && preg_match_all('{(.*?)}is', $tagcontents, $membermatches, PREG_SET_ORDER)){ - $courseobj = new stdClass(); - $courseobj->id = $ship->courseid; - - foreach($membermatches as $mmatch){ - $member = new stdClass(); - $memberstoreobj = new stdClass(); - 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($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 + $fileisnew = true; // Let's process it! } - $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 - } + if ($fileisnew) { - $timeframe = new stdClass(); - $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 - } + // The tag is allowed to halt processing if we're demanding a matching target. + $this->continueprocessing = true; - $rolecontext = context_course::instance($ship->courseid); - $rolecontext = $rolecontext->id; // All we really want is the ID + // Run through the file and process the group/person entries. + if (($fh = fopen($filename, "r")) != false) { - // Add or remove this student or teacher to the course... - $memberstoreobj->userid = $DB->get_field('user', 'id', array('idnumber'=>$member->idnumber)); - $memberstoreobj->enrol = 'imsenterprise'; - $memberstoreobj->course = $ship->courseid; - $memberstoreobj->time = time(); - $memberstoreobj->timemodified = time(); - if($memberstoreobj->userid){ + $line = 0; + while ((!feof($fh)) && $this->continueprocessing) { - // Decide the "real" role (i.e. the Moodle role) that this user should be assigned to. - // Zero means this roletype is supposed to be skipped. - $moodleroleid = $this->rolemappings[$member->roletype]; - if(!$moodleroleid) { - $this->log_line("SKIPPING role $member->roletype for $memberstoreobj->userid ($member->idnumber) in course $memberstoreobj->course"); - continue; - } + $line++; + $curline = fgets($fh); + $this->xmlcache .= $curline; // Add a line onto the XML cache. - if(intval($member->status) == 1) { - // Enrol the member - - $einstance = $DB->get_record('enrol', - array('courseid' => $courseobj->id, 'enrol' => $memberstoreobj->enrol)); - if (empty($einstance)) { - // Only add an enrol instance to the course if non-existent - $enrolid = $this->add_instance($courseobj); - $einstance = $DB->get_record('enrol', array('id' => $enrolid)); - } - - $this->enrol_user($einstance, $memberstoreobj->userid, $moodleroleid, $timeframe->begin, $timeframe->end); - - $this->log_line("Enrolled user #$memberstoreobj->userid ($member->idnumber) to role $member->roletype in course $memberstoreobj->course"); - $memberstally++; - - // 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 = $DB->get_field('groups', 'id', array('courseid'=>$ship->courseid, 'name'=>$member->groupname))){ - $member->groupid = $groupid; - $groupids[$member->groupname] = $groupid; // Store ID in 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'); + } else if ($tagcontents = $this->full_tag_found_in_cache('person', $curline)) { + $this->process_person_tag($tagcontents); + $this->remove_tag_from_cache('person'); + } else if ($tagcontents = $this->full_tag_found_in_cache('membership', $curline)) { + $this->process_membership_tag($tagcontents); + $this->remove_tag_from_cache('membership'); + } else if ($tagcontents = $this->full_tag_found_in_cache('comments', $curline)) { + $this->remove_tag_from_cache('comments'); + } else if ($tagcontents = $this->full_tag_found_in_cache('properties', $curline)) { + $this->process_properties_tag($tagcontents); + $this->remove_tag_from_cache('properties'); } else { - // Attempt to create the group - $group = new stdClass(); - $group->name = $member->groupname; - $group->courseid = $ship->courseid; - $group->timecreated = time(); - $group->timemodified = time(); - $groupid = $DB->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; - // Invalidate the course group data cache just in case. - cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($ship->courseid)); + break; } } - // Add the user-to-group association if it doesn't already exist - if($member->groupid) { - groups_add_member($member->groupid, $memberstoreobj->userid, - 'enrol_imsenterprise', $einstance->id); - } - } // End of group-enrolment (from member.role.extension.cohort tag) - - } elseif ($this->get_config('imsunenrol')) { - // Unenrol member - - $einstances = $DB->get_records('enrol', - array('enrol' => $memberstoreobj->enrol, 'courseid' => $courseobj->id)); - foreach ($einstances as $einstance) { - // Unenrol the user from all imsenterprise enrolment instances - $this->unenrol_user($einstance, $memberstoreobj->userid); } - - $membersuntally++; - $this->log_line("Unenrolled $member->idnumber from role $moodleroleid in course"); + fclose($fh); + fix_course_sortorder(); } + $timeelapsed = time() - $starttime; + $this->log_line('Process has completed. Time taken: '.$timeelapsed.' seconds.'); + + } + + // These variables are stored so we can compare them against the IMS file, next time round. + $this->set_config('prev_time', $filemtime); + $this->set_config('prev_md5', $md5); + $this->set_config('prev_path', $filename); + + } else { + $this->log_line('File not found: '.$filename); + } + + if (!empty($mailadmins) && $fileisnew) { + $timeelapsed = isset($timeelapsed) ? $timeelapsed : 0; + $msg = "An IMS enrolment has been carried out within Moodle.\nTime taken: $timeelapsed seconds.\n\n"; + if (!empty($logtolocation)) { + if ($this->logfp) { + $msg .= "Log data has been written to:\n"; + $msg .= "$logtolocation\n"; + $msg .= "(Log file size: ".ceil(filesize($logtolocation) / 1024)."Kb)\n\n"; + } else { + $msg .= "The log file appears not to have been successfully written.\n"; + $msg .= "Check that the file is writeable by the server:\n"; + $msg .= "$logtolocation\n\n"; + } + } else { + $msg .= "Logging is currently not active."; + } + + $eventdata = new stdClass(); + $eventdata->modulename = 'moodle'; + $eventdata->component = 'enrol_imsenterprise'; + $eventdata->name = 'imsenterprise_enrolment'; + $eventdata->userfrom = get_admin(); + $eventdata->userto = get_admin(); + $eventdata->subject = "Moodle IMS Enterprise enrolment notification"; + $eventdata->fullmessage = $msg; + $eventdata->fullmessageformat = FORMAT_PLAIN; + $eventdata->fullmessagehtml = ''; + $eventdata->smallmessage = ''; + message_send($eventdata); + + $this->log_line('Notification email sent to administrator.'); + + } + + if ($this->logfp) { + fclose($this->logfp); + } + + } + + /** + * 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. + * + * @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) + * @return bool|string false, or the contents of the tag (including start and end). + */ + protected function full_tag_found_in_cache($tagname, $latestline) { + // Return entire element if found. Otherwise return false. + if (strpos(strtolower($latestline), '') === false) { + return false; + } else if (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 + */ + protected function remove_tag_from_cache($tagname) { + // Trim the cache so we're not in danger of running out of memory. + // "1" so that we replace only the FIRST instance. + $this->xmlcache = trim(preg_replace('{<'.$tagname.'\b.*?>.*?}is', '', $this->xmlcache, 1)); + } + + /** + * 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 + * @return int recstatus value + */ + protected static function get_recstatus($tagdata, $tagname) { + if (preg_match('{<'.$tagname.'\b[^>]*recstatus\s*=\s*["\'](\d)["\']}is', $tagdata, $matches)) { + return intval($matches[1]); + } else { + return 0; // Unspecified. + } + } + + /** + * Process the group tag. This defines a Moodle course. + * + * @param string $tagcontents The raw contents of the XML element + */ + protected function process_group_tag($tagcontents) { + global $DB; + + // Get configs. + $truncatecoursecodes = $this->get_config('truncatecoursecodes'); + $createnewcourses = $this->get_config('createnewcourses'); + $createnewcategories = $this->get_config('createnewcategories'); + + // Process tag contents. + $group = new stdClass(); + if (preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)) { + $group->coursecode = trim($matches[1]); + } + + if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { + $group->long = trim($matches[1]); + } + if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { + $group->short = trim($matches[1]); + } + if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { + $group->full = trim($matches[1]); + } + + if (preg_match('{.*?(.*?).*?}is', $tagcontents, $matches)) { + $group->category = trim($matches[1]); + } + + $recstatus = ($this->get_recstatus($tagcontents, 'group')); + + if (empty($group->coursecode)) { + $this->log_line('Error: Unable to find course code in \'group\' element.'); + } else { + // First, truncate the course code if desired. + if (intval($truncatecoursecodes) > 0) { + $group->coursecode = ($truncatecoursecodes > 0) + ? substr($group->coursecode, 0, intval($truncatecoursecodes)) + : $group->coursecode; + } + + // 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 (!$DB->get_field('course', 'id', array('idnumber' => $coursecode))) { + if (!$createnewcourses) { + $this->log_line("Course $coursecode not found in Moodle's course idnumbers."); + } else { + + // Create the (hidden) course(s) if not found + $courseconfig = get_config('moodlecourse'); // Load Moodle Course shell defaults. + + // New course. + $course = new stdClass(); + 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; + } + } + + $course->idnumber = $coursecode; + $course->format = $courseconfig->format; + $course->visible = $courseconfig->visible; + $course->newsitems = $courseconfig->newsitems; + $course->showgrades = $courseconfig->showgrades; + $course->showreports = $courseconfig->showreports; + $course->maxbytes = $courseconfig->maxbytes; + $course->groupmode = $courseconfig->groupmode; + $course->groupmodeforce = $courseconfig->groupmodeforce; + $course->enablecompletion = $courseconfig->enablecompletion; + // 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(); + } + } else { + $course->category = $this->get_default_category_id(); + } + $course->timecreated = time(); + $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. + $course->id = $courseid; + enrol_course_updated(true, $course, null); + + // Setup the blocks. + $course = $DB->get_record('course', array('id' => $courseid)); + blocks_add_default_course_blocks($course); + + // Create default 0-section. + course_create_sections_if_missing($course, 0); + + 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 if ($recstatus == 3 && ($courseid = $DB->get_field('course', 'id', array('idnumber' => $coursecode)))) { + // If course does exist, but recstatus==3 (delete), then set the course as hidden. + $DB->set_field('course', 'visible', '0', array('id' => $courseid)); + } } } - $this->log_line("Added $memberstally users to course $ship->coursecode"); - if($membersuntally > 0){ - $this->log_line("Removed $membersuntally users from course $ship->coursecode"); + } + + /** + * Process the person tag. This defines a Moodle user. + * + * @param string $tagcontents The raw contents of the XML element + */ + protected function process_person_tag($tagcontents) { + global $CFG, $DB; + + // Get plugin configs. + $imssourcedidfallback = $this->get_config('imssourcedidfallback'); + $fixcaseusernames = $this->get_config('fixcaseusernames'); + $fixcasepersonalnames = $this->get_config('fixcasepersonalnames'); + $imsdeleteusers = $this->get_config('imsdeleteusers'); + $createnewusers = $this->get_config('createnewusers'); + + $person = new stdClass(); + 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]); + } + if ($imssourcedidfallback && trim($person->username) == '') { + // 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; + } + 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 ($fixcaseusernames && isset($person->username)) { + $person->username = strtolower($person->username); + } + if ($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. + if ($recstatus == 3) { + + if ($imsdeleteusers) { // If we're allowed to delete user records. + // Do not dare to hack the user.deleted field directly in database!!! + $params = array('username' => $person->username, 'mnethostid' => $CFG->mnet_localhost_id, 'deleted ' => 0); + if ($user = $DB->get_record('user', $params)) { + if (delete_user($user)) { + $this->log_line("Deleted user '$person->username' (ID number $person->idnumber)."); + } else { + $this->log_line("Error deleting '$person->username' (ID number $person->idnumber)."); + } + } else { + $this->log_line("Can not delete user '$person->username' (ID number $person->idnumber) - user does not exist."); + } + } 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 (!$DB->get_field('user', 'id', array('idnumber' => $person->idnumber)) && $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."); + } else if ($DB->get_field('user', 'id', array('username' => $person->username))) { + // If their idnumber is not registered but their user ID is, then add their idnumber to their record. + $DB->set_field('user', 'idnumber', $person->idnumber, array('username' => $person->username)); + } else { + + // 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; + $person->confirmed = 1; + $person->timemodified = time(); + $person->mnethostid = $CFG->mnet_localhost_id; + $id = $DB->insert_record('user', $person); + $this->log_line("Created user record ('.$id.') for user '$person->username' (ID number $person->idnumber)."); + } + } else if ($createnewusers) { + $this->log_line("User record already exists for user '$person->username' (ID number $person->idnumber)."); + + // It is totally wrong to mess with deleted users flag directly in database!!! + // There is no official way to undelete user, sorry.. + } else { + $this->log_line("No user record found for '$person->username' (ID number $person->idnumber)."); + } + + } + + } + + /** + * Process the membership tag. This defines whether the specified Moodle users + * should be added/removed as teachers/students. + * + * @param string $tagcontents The raw contents of the XML element + */ + protected function process_membership_tag($tagcontents) { + global $DB; + + // Get plugin configs. + $truncatecoursecodes = $this->get_config('truncatecoursecodes'); + $imscapitafix = $this->get_config('imscapitafix'); + + $memberstally = 0; + $membersuntally = 0; + + // In order to reduce the number of db queries required, group name/id associations are cached in this array. + $groupids = array(); + + $ship = new stdClass(); + + if (preg_match('{.*?(.+?).*?}is', $tagcontents, $matches)) { + $ship->coursecode = ($truncatecoursecodes > 0) + ? substr(trim($matches[1]), 0, intval($truncatecoursecodes)) + : trim($matches[1]); + $ship->courseid = $DB->get_field('course', 'id', array('idnumber' => $ship->coursecode)); + } + if ($ship->courseid && preg_match_all('{(.*?)}is', $tagcontents, $membermatches, PREG_SET_ORDER)) { + $courseobj = new stdClass(); + $courseobj->id = $ship->courseid; + + foreach ($membermatches as $mmatch) { + $member = new stdClass(); + $memberstoreobj = new stdClass(); + if (preg_match('{.*?(.+?).*?}is', $mmatch[1], $matches)) { + $member->idnumber = trim($matches[1]); + } + if (preg_match('{}is', $mmatch[1], $matches)) { + // 01 means Student, 02 means Instructor, 3 means ContentDeveloper, and there are more besides. + $member->roletype = trim($matches[1]); + } else if ($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! 01 means Student, 02 means Instructor, 3 means ContentDeveloper, + // and there are more besides. + $member->roletype = trim($matches[1]); + } + if (preg_match('{(.+?).*?}is', $mmatch[1], $matches)) { + // 1 means active, 0 means inactive - treat this as enrol vs unenrol. + $member->status = trim($matches[1]); + } + + $recstatus = ($this->get_recstatus($mmatch[1], 'role')); + if ($recstatus == 3) { + // See above - recstatus of 3 (==delete) is treated the same as status of 0. + $member->status = 0; + } + + $timeframe = new stdClass(); + $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 = $DB->get_field('user', 'id', array('idnumber' => $member->idnumber)); + $memberstoreobj->enrol = 'imsenterprise'; + $memberstoreobj->course = $ship->courseid; + $memberstoreobj->time = time(); + $memberstoreobj->timemodified = time(); + if ($memberstoreobj->userid) { + + // Decide the "real" role (i.e. the Moodle role) that this user should be assigned to. + // Zero means this roletype is supposed to be skipped. + $moodleroleid = $this->rolemappings[$member->roletype]; + if (!$moodleroleid) { + $this->log_line("SKIPPING role $member->roletype for $memberstoreobj->userid " + ."($member->idnumber) in course $memberstoreobj->course"); + continue; + } + + if (intval($member->status) == 1) { + // Enrol the member. + + $einstance = $DB->get_record('enrol', + array('courseid' => $courseobj->id, 'enrol' => $memberstoreobj->enrol)); + if (empty($einstance)) { + // Only add an enrol instance to the course if non-existent. + $enrolid = $this->add_instance($courseobj); + $einstance = $DB->get_record('enrol', array('id' => $enrolid)); + } + + $this->enrol_user($einstance, $memberstoreobj->userid, $moodleroleid, $timeframe->begin, $timeframe->end); + + $this->log_line("Enrolled user #$memberstoreobj->userid ($member->idnumber) " + ."to role $member->roletype in course $memberstoreobj->course"); + $memberstally++; + + // 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 { + $params = array('courseid' => $ship->courseid, 'name' => $member->groupname); + if ($groupid = $DB->get_field('groups', 'id', $params)) { + $member->groupid = $groupid; + $groupids[$member->groupname] = $groupid; // Store ID in cache. + } else { + // Attempt to create the group. + $group = new stdClass(); + $group->name = $member->groupname; + $group->courseid = $ship->courseid; + $group->timecreated = time(); + $group->timemodified = time(); + $groupid = $DB->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; + // Invalidate the course group data cache just in case. + cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($ship->courseid)); + } + } + // Add the user-to-group association if it doesn't already exist. + if ($member->groupid) { + groups_add_member($member->groupid, $memberstoreobj->userid, + 'enrol_imsenterprise', $einstance->id); + } + } + + } else if ($this->get_config('imsunenrol')) { + // Unenrol member. + + $einstances = $DB->get_records('enrol', + array('enrol' => $memberstoreobj->enrol, 'courseid' => $courseobj->id)); + foreach ($einstances as $einstance) { + // Unenrol the user from all imsenterprise enrolment instances. + $this->unenrol_user($einstance, $memberstoreobj->userid); + } + + $membersuntally++; + $this->log_line("Unenrolled $member->idnumber from role $moodleroleid in course"); + } + + } + } + $this->log_line("Added $memberstally users to course $ship->coursecode"); + if ($membersuntally > 0) { + $this->log_line("Removed $membersuntally users 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 $tagcontents The raw contents of the XML element + */ + protected function process_properties_tag($tagcontents) { + $imsrestricttarget = $this->get_config('imsrestricttarget'); + + if ($imsrestricttarget) { + if (!(preg_match('{'.preg_quote($imsrestricttarget).'}is', $tagcontents, $matches))) { + $this->log_line("Skipping processing: required target \"$imsrestricttarget\" not specified in this data."); + $this->continueprocessing = false; + } } } -} // 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){ - $imsrestricttarget = $this->get_config('imsrestricttarget'); + /** + * 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) + */ + protected function log_line($string) { - if ($imsrestricttarget) { - if(!(preg_match('{'.preg_quote($imsrestricttarget).'}is', $tagcontents, $matches))){ - $this->log_line("Skipping processing: required target \"$imsrestricttarget\" not specified in this data."); - $this->continueprocessing = false; + if (!PHPUNIT_TEST) { + mtrace($string); + } + if ($this->logfp) { + fwrite($this->logfp, $string . "\n"); } } -} -/** -* 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){ - - if (!PHPUNIT_TEST) { - mtrace($string); + /** + * Process the INNER contents of a tag, to return beginning/ending dates. + * + * @param string $string tag to decode. + * @return stdClass beginning and/or ending is returned, in unix time, zero indicating not specified. + */ + protected static function decode_timeframe($string) { + $ret = new stdClass(); + $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; } - 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 = new stdClass(); - $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 + /** + * Load the role mappings (from the config), so we can easily refer to + * how an IMS-E role corresponds to a Moodle role + */ + protected function load_role_mappings() { + require_once('locallib.php'); -/** -* Load the role mappings (from the config), so we can easily refer to -* how an IMS-E role corresponds to a Moodle role -*/ -function load_role_mappings() { - require_once('locallib.php'); - global $DB; + $imsroles = new imsenterprise_roles(); + $imsroles = $imsroles->get_imsroles(); - $imsroles = new imsenterprise_roles(); - $imsroles = $imsroles->get_imsroles(); - - $this->rolemappings = array(); - foreach($imsroles as $imsrolenum=>$imsrolename) { - $this->rolemappings[$imsrolenum] = $this->rolemappings[$imsrolename] = $this->get_config('imsrolemap' . $imsrolenum); + $this->rolemappings = array(); + foreach ($imsroles as $imsrolenum => $imsrolename) { + $this->rolemappings[$imsrolenum] = $this->rolemappings[$imsrolename] = $this->get_config('imsrolemap' . $imsrolenum); + } } -} /** * 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() { + protected function load_course_mappings() { require_once('locallib.php'); $imsnames = new imsenterprise_courses(); $courseattrs = $imsnames->get_courseattrs(); $this->coursemappings = array(); - foreach($courseattrs as $courseattr) { + foreach ($courseattrs as $courseattr) { $this->coursemappings[$courseattr] = $this->get_config('imscoursemap' . $courseattr); } } @@ -747,7 +777,7 @@ function load_role_mappings() { * @param int $userid User ID being removed from group * @return bool True if the remove is permitted, false to give an error */ - function enrol_imsenterprise_allow_group_member_remove($itemid, $groupid, $userid) { + public function enrol_imsenterprise_allow_group_member_remove($itemid, $groupid, $userid) { return false; } @@ -771,8 +801,4 @@ function load_role_mappings() { return $defaultcategoryid; } - - -} // end of class - - +} diff --git a/enrol/imsenterprise/locallib.php b/enrol/imsenterprise/locallib.php index f203dc89184..acaaa12ba1c 100644 --- a/enrol/imsenterprise/locallib.php +++ b/enrol/imsenterprise/locallib.php @@ -25,34 +25,51 @@ defined('MOODLE_INTERNAL') || die(); +/** + * Class for dealing with role mappings in IMS Enterprise. + * + * @copyright 2010 Eugene Venter + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class imsenterprise_roles { + /** @var imscode => ims role name. Role name mapping. */ private $imsroles; - function __construct() { + /** + * Constructor. + */ + public function __construct() { $this->imsroles = array( - '01'=>'Learner', - '02'=>'Instructor', - '03'=>'Content Developer', - '04'=>'Member', - '05'=>'Manager', - '06'=>'Mentor', - '07'=>'Administrator', - '08'=>'TeachingAssistant', + '01' => 'Learner', + '02' => 'Instructor', + '03' => 'Content Developer', + '04' => 'Member', + '05' => 'Manager', + '06' => 'Mentor', + '07' => 'Administrator', + '08' => 'TeachingAssistant', ); // PLEASE NOTE: It may seem odd that "Content Developer" has a space in it // but "TeachingAssistant" doesn't. That's what the spec says though!!! } - function get_imsroles() { + /** + * Returns the mapped roles + * + * @return array of IMS roles indexed by IMS code. + */ + public function get_imsroles() { return $this->imsroles; } /** - * This function is only used when first setting up the plugin, to - * decide which role assignments to recommend by default. - * For example, IMS role '01' is 'Learner', so may map to 'student' in Moodle. - */ - function determine_default_rolemapping($imscode) { + * This function is only used when first setting up the plugin, to + * decide which role assignments to recommend by default. + * For example, IMS role '01' is 'Learner', so may map to 'student' in Moodle. + * + * @param string $imscode + */ + public function determine_default_rolemapping($imscode) { global $DB; switch($imscode) { @@ -73,13 +90,13 @@ class imsenterprise_roles { $shortname = 'admin'; break; default: - return 0; // Zero for no match + return 0; // Zero for no match. } - return (string)$DB->get_field('role', 'id', array('shortname'=>$shortname)); + return (string)$DB->get_field('role', 'id', array('shortname' => $shortname)); } -} // class +} /** @@ -90,14 +107,15 @@ class imsenterprise_roles { * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class imsenterprise_courses { - + /** @var array IMS group description names */ private $imsnames; + /** @var array moodle course field names */ private $courseattrs; /** * Loads default */ - function __construct() { + public function __construct() { $this->imsnames = array( 'short' => 'short', 'long' => 'long', @@ -111,7 +129,7 @@ class imsenterprise_courses { * @param string $courseattr The course attribute (shortname, fullname...) * @return array Array of assignable values */ - function get_imsnames($courseattr) { + public function get_imsnames($courseattr) { $values = $this->imsnames; if ($courseattr == 'summary') { @@ -124,7 +142,7 @@ class imsenterprise_courses { * courseattrs getter * @return array */ - function get_courseattrs() { + public function get_courseattrs() { return $this->courseattrs; } @@ -132,10 +150,10 @@ class imsenterprise_courses { * This function is only used when first setting up the plugin, to * decide which name assignments to recommend by default. * - * @param string $coursename + * @param string $courseattr * @return string */ - function determine_default_coursemapping($courseattr) { + public function determine_default_coursemapping($courseattr) { switch($courseattr) { case 'fullname': $imsname = 'short'; @@ -149,5 +167,4 @@ class imsenterprise_courses { return $imsname; } - -} // class +} diff --git a/enrol/imsenterprise/settings.php b/enrol/imsenterprise/settings.php index 5b7fbb65fa9..2ecf5759ceb 100644 --- a/enrol/imsenterprise/settings.php +++ b/enrol/imsenterprise/settings.php @@ -27,31 +27,43 @@ defined('MOODLE_INTERNAL') || die(); if ($ADMIN->fulltree) { require_once($CFG->dirroot.'/enrol/imsenterprise/locallib.php'); - $settings->add(new admin_setting_heading('enrol_imsenterprise_settings', '', get_string('pluginname_desc', 'enrol_imsenterprise'))); + $settings->add(new admin_setting_heading('enrol_imsenterprise_settings', '', + get_string('pluginname_desc', 'enrol_imsenterprise'))); - //--- general settings ----------------------------------------------------------------------------------- - $settings->add(new admin_setting_heading('enrol_imsenterprise_basicsettings', get_string('basicsettings', 'enrol_imsenterprise'), '')); + // General settings. + $settings->add(new admin_setting_heading('enrol_imsenterprise_basicsettings', + get_string('basicsettings', 'enrol_imsenterprise'), '')); - $settings->add(new admin_setting_configtext('enrol_imsenterprise/imsfilelocation', get_string('location', 'enrol_imsenterprise'), '', '')); + $settings->add(new admin_setting_configtext('enrol_imsenterprise/imsfilelocation', + get_string('location', 'enrol_imsenterprise'), '', '')); - $settings->add(new admin_setting_configtext('enrol_imsenterprise/logtolocation', get_string('logtolocation', 'enrol_imsenterprise'), '', '')); + $settings->add(new admin_setting_configtext('enrol_imsenterprise/logtolocation', + get_string('logtolocation', 'enrol_imsenterprise'), '', '')); - $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/mailadmins', get_string('mailadmins', 'enrol_imsenterprise'), '', 0)); + $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/mailadmins', + get_string('mailadmins', 'enrol_imsenterprise'), '', 0)); - //--- user data options --------------------------------------------------------------------------------- - $settings->add(new admin_setting_heading('enrol_imsenterprise_usersettings', get_string('usersettings', 'enrol_imsenterprise'), '')); + // User data options. + $settings->add(new admin_setting_heading('enrol_imsenterprise_usersettings', + get_string('usersettings', 'enrol_imsenterprise'), '')); - $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/createnewusers', + get_string('createnewusers', 'enrol_imsenterprise'), get_string('createnewusers_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)); + $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imsdeleteusers', + get_string('deleteusers', 'enrol_imsenterprise'), get_string('deleteusers_desc', 'enrol_imsenterprise'), 0)); - $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/fixcaseusernames', get_string('fixcaseusernames', 'enrol_imsenterprise'), '', 0)); + $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/fixcaseusernames', + get_string('fixcaseusernames', 'enrol_imsenterprise'), '', 0)); - $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/fixcasepersonalnames', get_string('fixcasepersonalnames', 'enrol_imsenterprise'), '', 0)); + $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/fixcasepersonalnames', + get_string('fixcasepersonalnames', 'enrol_imsenterprise'), '', 0)); - $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imssourcedidfallback', get_string('sourcedidfallback', 'enrol_imsenterprise'), get_string('sourcedidfallback_desc', 'enrol_imsenterprise'), 0)); + $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imssourcedidfallback', + get_string('sourcedidfallback', 'enrol_imsenterprise'), get_string('sourcedidfallback_desc', 'enrol_imsenterprise'), 0)); - $settings->add(new admin_setting_heading('enrol_imsenterprise_usersettings_roles', get_string('roles', 'enrol_imsenterprise'), get_string('imsrolesdescription', 'enrol_imsenterprise'))); + $settings->add(new admin_setting_heading('enrol_imsenterprise_usersettings_roles', + get_string('roles', 'enrol_imsenterprise'), get_string('imsrolesdescription', 'enrol_imsenterprise'))); if (!during_initial_install()) { $coursecontext = context_course::instance(SITEID); @@ -59,41 +71,55 @@ if ($ADMIN->fulltree) { $assignableroles = array('0' => get_string('ignore', 'enrol_imsenterprise')) + $assignableroles; $imsroles = new imsenterprise_roles(); foreach ($imsroles->get_imsroles() as $imsrolenum => $imsrolename) { - $settings->add(new admin_setting_configselect('enrol_imsenterprise/imsrolemap'.$imsrolenum, format_string('"'.$imsrolename.'" ('.$imsrolenum.')'), '', (int)$imsroles->determine_default_rolemapping($imsrolenum), $assignableroles)); + $settings->add(new admin_setting_configselect('enrol_imsenterprise/imsrolemap'.$imsrolenum, + format_string('"'.$imsrolename.'" ('.$imsrolenum.')'), '', + (int)$imsroles->determine_default_rolemapping($imsrolenum), $assignableroles)); } } - //--- course data options ------------------------------------------------------------------------------- - $settings->add(new admin_setting_heading('enrol_imsenterprise_coursesettings', get_string('coursesettings', 'enrol_imsenterprise'), '')); + // Course data options. + $settings->add(new admin_setting_heading('enrol_imsenterprise_coursesettings', + get_string('coursesettings', 'enrol_imsenterprise'), '')); - $settings->add(new admin_setting_configtext('enrol_imsenterprise/truncatecoursecodes', get_string('truncatecoursecodes', 'enrol_imsenterprise'), get_string('truncatecoursecodes_desc', 'enrol_imsenterprise'), 0, PARAM_INT, 2)); + $settings->add(new admin_setting_configtext('enrol_imsenterprise/truncatecoursecodes', + get_string('truncatecoursecodes', 'enrol_imsenterprise'), get_string('truncatecoursecodes_desc', 'enrol_imsenterprise'), + 0, PARAM_INT, 2)); - $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/createnewcourses', + get_string('createnewcourses', 'enrol_imsenterprise'), get_string('createnewcourses_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_configcheckbox('enrol_imsenterprise/createnewcategories', + get_string('createnewcategories', 'enrol_imsenterprise'), get_string('createnewcategories_desc', 'enrol_imsenterprise'), + 0)); - $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imsunenrol', get_string('allowunenrol', 'enrol_imsenterprise'), get_string('allowunenrol_desc', 'enrol_imsenterprise'), 0)); + $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imsunenrol', + get_string('allowunenrol', 'enrol_imsenterprise'), get_string('allowunenrol_desc', 'enrol_imsenterprise'), 0)); if (!during_initial_install()) { $imscourses = new imsenterprise_courses(); foreach ($imscourses->get_courseattrs() as $courseattr) { - // The assignable values of this course attribute + // The assignable values of this course attribute. $assignablevalues = $imscourses->get_imsnames($courseattr); $name = get_string('setting' . $courseattr, 'enrol_imsenterprise'); $description = get_string('setting' . $courseattr . 'description', 'enrol_imsenterprise'); $defaultvalue = (string) $imscourses->determine_default_coursemapping($courseattr); - $settings->add(new admin_setting_configselect('enrol_imsenterprise/imscoursemap' . $courseattr, $name, $description, $defaultvalue, $assignablevalues)); + $settings->add(new admin_setting_configselect('enrol_imsenterprise/imscoursemap' . $courseattr, $name, + $description, $defaultvalue, $assignablevalues)); } } - //--- miscellaneous ------------------------------------------------------------------------------------- - $settings->add(new admin_setting_heading('enrol_imsenterprise_miscsettings', get_string('miscsettings', 'enrol_imsenterprise'), '')); + // Miscellaneous. + $settings->add(new admin_setting_heading('enrol_imsenterprise_miscsettings', + get_string('miscsettings', 'enrol_imsenterprise'), '')); - $settings->add(new admin_setting_configtext('enrol_imsenterprise/imsrestricttarget', get_string('restricttarget', 'enrol_imsenterprise'), get_string('restricttarget_desc', 'enrol_imsenterprise'), '')); + $settings->add(new admin_setting_configtext('enrol_imsenterprise/imsrestricttarget', + get_string('restricttarget', 'enrol_imsenterprise'), get_string('restricttarget_desc', 'enrol_imsenterprise'), '')); - $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imscapitafix', get_string('usecapitafix', 'enrol_imsenterprise'), get_string('usecapitafix_desc', 'enrol_imsenterprise'), 0)); + $settings->add(new admin_setting_configcheckbox('enrol_imsenterprise/imscapitafix', + get_string('usecapitafix', 'enrol_imsenterprise'), get_string('usecapitafix_desc', 'enrol_imsenterprise'), 0)); - $importnowstring = get_string('aftersaving...', 'enrol_imsenterprise').' '.get_string('doitnow', 'enrol_imsenterprise').''; + $importnowstring = get_string('aftersaving...', 'enrol_imsenterprise').' '; + $importnowstring .= get_string('doitnow', 'enrol_imsenterprise').''; $settings->add(new admin_setting_heading('enrol_imsenterprise_doitnowmessage', '', $importnowstring)); } diff --git a/enrol/imsenterprise/tests/imsenterprise_test.php b/enrol/imsenterprise/tests/imsenterprise_test.php index f160955fb19..390f276836a 100644 --- a/enrol/imsenterprise/tests/imsenterprise_test.php +++ b/enrol/imsenterprise/tests/imsenterprise_test.php @@ -18,7 +18,7 @@ * IMS Enterprise enrolment tests. * * @package enrol_imsenterprise - * @category phpunit + * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -33,14 +33,20 @@ require_once($CFG->dirroot . '/enrol/imsenterprise/lib.php'); * IMS Enterprise test case * * @package enrol_imsenterprise - * @category phpunit + * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class enrol_imsenterprise_testcase extends advanced_testcase { + /** + * @var $imsplugin enrol_imsenterprise_plugin IMS plugin instance. + */ protected $imsplugin; + /** + * Setup required for all tests. + */ protected function setUp() { $this->resetAfterTest(true); $this->imsplugin = enrol_get_plugin('imsenterprise'); @@ -63,7 +69,6 @@ class enrol_imsenterprise_testcase extends advanced_testcase { $this->assertEquals($prevnusers, $DB->count_records('user')); } - /** * Existing users are not created again */ @@ -82,7 +87,6 @@ class enrol_imsenterprise_testcase extends advanced_testcase { $this->assertEquals($prevnusers, $DB->count_records('user')); } - /** * Add new users */ @@ -104,7 +108,6 @@ class enrol_imsenterprise_testcase extends advanced_testcase { $this->assertEquals(($prevnusers + 1), $DB->count_records('user')); } - /** * Existing courses are not created again */ @@ -127,7 +130,6 @@ class enrol_imsenterprise_testcase extends advanced_testcase { $this->assertEquals($prevncourses, $DB->count_records('course')); } - /** * Add new courses */ @@ -207,7 +209,6 @@ class enrol_imsenterprise_testcase extends advanced_testcase { $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'); @@ -229,7 +230,6 @@ class enrol_imsenterprise_testcase extends advanced_testcase { $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'); @@ -251,7 +251,6 @@ class enrol_imsenterprise_testcase extends advanced_testcase { } - /** * Sets the plugin configuration for testing */ @@ -263,15 +262,13 @@ class enrol_imsenterprise_testcase extends advanced_testcase { $this->imsplugin->set_config('createnewcategories', true); } - /** - * Creates an IMS enterprise XML file and adds it's path to config settings + * 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 + * @param bool|array $users false or array of users StdClass + * @param bool|array $courses false or of courses StdClass */ protected function set_xml_file($users = false, $courses = false) { - global $DB; $xmlcontent = ''; @@ -328,7 +325,7 @@ class enrol_imsenterprise_testcase extends advanced_testcase { '.$course->imsfull.''; } - // orgunit tag value is used by moodle as category name. + // The orgunit tag value is used by moodle as category name. $xmlcontent .= ' diff --git a/enrol/imsenterprise/version.php b/enrol/imsenterprise/version.php index 49375b6333a..2bb13b2f395 100644 --- a/enrol/imsenterprise/version.php +++ b/enrol/imsenterprise/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2013050100; // The current plugin version (Date: YYYYMMDDXX) -$plugin->requires = 2013050100; // Requires this Moodle version -$plugin->component = 'enrol_imsenterprise'; // Full name of the plugin (used for diagnostics) +$plugin->version = 2013050100; +$plugin->requires = 2013050100; +$plugin->component = 'enrol_imsenterprise'; $plugin->cron = 60;