[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * IMS Enterprise file enrolment plugin. 19 * 20 * This plugin lets the user specify an IMS Enterprise file to be processed. 21 * The IMS Enterprise file is mainly parsed on a regular cron, 22 * but can also be imported via the UI (Admin Settings). 23 * @package enrol_imsenterprise 24 * @copyright 2010 Eugene Venter 25 * @author Eugene Venter - based on code by Dan Stowell 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 29 defined('MOODLE_INTERNAL') || die(); 30 31 require_once($CFG->dirroot.'/group/lib.php'); 32 33 34 /** 35 * IMS Enterprise file enrolment plugin. 36 * 37 * @copyright 2010 Eugene Venter 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class enrol_imsenterprise_plugin extends enrol_plugin { 41 42 /** 43 * @var $logfp resource file pointer for writing log data to. 44 */ 45 protected $logfp; 46 47 /** 48 * @var $continueprocessing bool flag to determine if processing should continue. 49 */ 50 protected $continueprocessing; 51 52 /** 53 * @var $xmlcache string cache of xml lines. 54 */ 55 protected $xmlcache; 56 57 /** 58 * @var $coursemappings array of mappings between IMS data fields and moodle course fields. 59 */ 60 protected $coursemappings; 61 62 /** 63 * @var $rolemappings array of mappings between IMS roles and moodle roles. 64 */ 65 protected $rolemappings; 66 67 /** 68 * Read in an IMS Enterprise file. 69 * Originally designed to handle v1.1 files but should be able to handle 70 * earlier types as well, I believe. 71 * This cron feature has been converted to a scheduled task and it can now be scheduled 72 * from the UI. 73 */ 74 public function cron() { 75 global $CFG; 76 77 // Get configs. 78 $imsfilelocation = $this->get_config('imsfilelocation'); 79 $logtolocation = $this->get_config('logtolocation'); 80 $mailadmins = $this->get_config('mailadmins'); 81 $prevtime = $this->get_config('prev_time'); 82 $prevmd5 = $this->get_config('prev_md5'); 83 $prevpath = $this->get_config('prev_path'); 84 85 if (empty($imsfilelocation)) { 86 $filename = "$CFG->dataroot/1/imsenterprise-enrol.xml"; // Default location. 87 } else { 88 $filename = $imsfilelocation; 89 } 90 91 $this->logfp = false; 92 if (!empty($logtolocation)) { 93 $this->logfp = fopen($logtolocation, 'a'); 94 } 95 96 $fileisnew = false; 97 if ( file_exists($filename) ) { 98 core_php_time_limit::raise(); 99 $starttime = time(); 100 101 $this->log_line('----------------------------------------------------------------------'); 102 $this->log_line("IMS Enterprise enrol cron process launched at " . userdate(time())); 103 $this->log_line('Found file '.$filename); 104 $this->xmlcache = ''; 105 106 // Make sure we understand how to map the IMS-E roles to Moodle roles. 107 $this->load_role_mappings(); 108 // Make sure we understand how to map the IMS-E course names to Moodle course names. 109 $this->load_course_mappings(); 110 111 $md5 = md5_file($filename); // NB We'll write this value back to the database at the end of the cron. 112 $filemtime = filemtime($filename); 113 114 // Decide if we want to process the file (based on filepath, modification time, and MD5 hash) 115 // This is so we avoid wasting the server's efforts processing a file unnecessarily. 116 if (empty($prevpath) || ($filename != $prevpath)) { 117 $fileisnew = true; 118 } else if (isset($prevtime) && ($filemtime <= $prevtime)) { 119 $this->log_line('File modification time is not more recent than last update - skipping processing.'); 120 } else if (isset($prevmd5) && ($md5 == $prevmd5)) { 121 $this->log_line('File MD5 hash is same as on last update - skipping processing.'); 122 } else { 123 $fileisnew = true; // Let's process it! 124 } 125 126 if ($fileisnew) { 127 128 // The <properties> tag is allowed to halt processing if we're demanding a matching target. 129 $this->continueprocessing = true; 130 131 // Run through the file and process the group/person entries. 132 if (($fh = fopen($filename, "r")) != false) { 133 134 $line = 0; 135 while ((!feof($fh)) && $this->continueprocessing) { 136 137 $line++; 138 $curline = fgets($fh); 139 $this->xmlcache .= $curline; // Add a line onto the XML cache. 140 141 while (true) { 142 // If we've got a full tag (i.e. the most recent line has closed the tag) then process-it-and-forget-it. 143 // Must always make sure to remove tags from cache so they don't clog up our memory. 144 if ($tagcontents = $this->full_tag_found_in_cache('group', $curline)) { 145 $this->process_group_tag($tagcontents); 146 $this->remove_tag_from_cache('group'); 147 } else if ($tagcontents = $this->full_tag_found_in_cache('person', $curline)) { 148 $this->process_person_tag($tagcontents); 149 $this->remove_tag_from_cache('person'); 150 } else if ($tagcontents = $this->full_tag_found_in_cache('membership', $curline)) { 151 $this->process_membership_tag($tagcontents); 152 $this->remove_tag_from_cache('membership'); 153 } else if ($tagcontents = $this->full_tag_found_in_cache('comments', $curline)) { 154 $this->remove_tag_from_cache('comments'); 155 } else if ($tagcontents = $this->full_tag_found_in_cache('properties', $curline)) { 156 $this->process_properties_tag($tagcontents); 157 $this->remove_tag_from_cache('properties'); 158 } else { 159 break; 160 } 161 } 162 } 163 fclose($fh); 164 fix_course_sortorder(); 165 } 166 167 $timeelapsed = time() - $starttime; 168 $this->log_line('Process has completed. Time taken: '.$timeelapsed.' seconds.'); 169 170 } 171 172 // These variables are stored so we can compare them against the IMS file, next time round. 173 $this->set_config('prev_time', $filemtime); 174 $this->set_config('prev_md5', $md5); 175 $this->set_config('prev_path', $filename); 176 177 } else { 178 $this->log_line('File not found: '.$filename); 179 } 180 181 if (!empty($mailadmins) && $fileisnew) { 182 $timeelapsed = isset($timeelapsed) ? $timeelapsed : 0; 183 $msg = "An IMS enrolment has been carried out within Moodle.\nTime taken: $timeelapsed seconds.\n\n"; 184 if (!empty($logtolocation)) { 185 if ($this->logfp) { 186 $msg .= "Log data has been written to:\n"; 187 $msg .= "$logtolocation\n"; 188 $msg .= "(Log file size: ".ceil(filesize($logtolocation) / 1024)."Kb)\n\n"; 189 } else { 190 $msg .= "The log file appears not to have been successfully written.\n"; 191 $msg .= "Check that the file is writeable by the server:\n"; 192 $msg .= "$logtolocation\n\n"; 193 } 194 } else { 195 $msg .= "Logging is currently not active."; 196 } 197 198 $eventdata = new stdClass(); 199 $eventdata->modulename = 'moodle'; 200 $eventdata->component = 'enrol_imsenterprise'; 201 $eventdata->name = 'imsenterprise_enrolment'; 202 $eventdata->userfrom = get_admin(); 203 $eventdata->userto = get_admin(); 204 $eventdata->subject = "Moodle IMS Enterprise enrolment notification"; 205 $eventdata->fullmessage = $msg; 206 $eventdata->fullmessageformat = FORMAT_PLAIN; 207 $eventdata->fullmessagehtml = ''; 208 $eventdata->smallmessage = ''; 209 message_send($eventdata); 210 211 $this->log_line('Notification email sent to administrator.'); 212 213 } 214 215 if ($this->logfp) { 216 fclose($this->logfp); 217 } 218 219 } 220 221 /** 222 * Check if a complete tag is found in the cached data, which usually happens 223 * when the end of the tag has only just been loaded into the cache. 224 * 225 * @param string $tagname Name of tag to look for 226 * @param string $latestline The very last line in the cache (used for speeding up the match) 227 * @return bool|string false, or the contents of the tag (including start and end). 228 */ 229 protected function full_tag_found_in_cache($tagname, $latestline) { 230 // Return entire element if found. Otherwise return false. 231 if (strpos(strtolower($latestline), '</'.strtolower($tagname).'>') === false) { 232 return false; 233 } else if (preg_match('{(<'.$tagname.'\b.*?>.*?</'.$tagname.'>)}is', $this->xmlcache, $matches)) { 234 return $matches[1]; 235 } else { 236 return false; 237 } 238 } 239 240 /** 241 * Remove complete tag from the cached data (including all its contents) - so 242 * that the cache doesn't grow to unmanageable size 243 * 244 * @param string $tagname Name of tag to look for 245 */ 246 protected function remove_tag_from_cache($tagname) { 247 // Trim the cache so we're not in danger of running out of memory. 248 // "1" so that we replace only the FIRST instance. 249 $this->xmlcache = trim(preg_replace('{<'.$tagname.'\b.*?>.*?</'.$tagname.'>}is', '', $this->xmlcache, 1)); 250 } 251 252 /** 253 * Very simple convenience function to return the "recstatus" found in person/group/role tags. 254 * 1=Add, 2=Update, 3=Delete, as specified by IMS, and we also use 0 to indicate "unspecified". 255 * 256 * @param string $tagdata the tag XML data 257 * @param string $tagname the name of the tag we're interested in 258 * @return int recstatus value 259 */ 260 protected static function get_recstatus($tagdata, $tagname) { 261 if (preg_match('{<'.$tagname.'\b[^>]*recstatus\s*=\s*["\'](\d)["\']}is', $tagdata, $matches)) { 262 return intval($matches[1]); 263 } else { 264 return 0; // Unspecified. 265 } 266 } 267 268 /** 269 * Process the group tag. This defines a Moodle course. 270 * 271 * @param string $tagcontents The raw contents of the XML element 272 */ 273 protected function process_group_tag($tagcontents) { 274 global $DB, $CFG; 275 276 // Get configs. 277 $truncatecoursecodes = $this->get_config('truncatecoursecodes'); 278 $createnewcourses = $this->get_config('createnewcourses'); 279 $createnewcategories = $this->get_config('createnewcategories'); 280 281 if ($createnewcourses) { 282 require_once("$CFG->dirroot/course/lib.php"); 283 } 284 285 // Process tag contents. 286 $group = new stdClass(); 287 if (preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)) { 288 $group->coursecode = trim($matches[1]); 289 } 290 291 if (preg_match('{<description>.*?<long>(.*?)</long>.*?</description>}is', $tagcontents, $matches)) { 292 $group->long = trim($matches[1]); 293 } 294 if (preg_match('{<description>.*?<short>(.*?)</short>.*?</description>}is', $tagcontents, $matches)) { 295 $group->short = trim($matches[1]); 296 } 297 if (preg_match('{<description>.*?<full>(.*?)</full>.*?</description>}is', $tagcontents, $matches)) { 298 $group->full = trim($matches[1]); 299 } 300 301 if (preg_match('{<org>.*?<orgunit>(.*?)</orgunit>.*?</org>}is', $tagcontents, $matches)) { 302 $group->category = trim($matches[1]); 303 } 304 305 $recstatus = ($this->get_recstatus($tagcontents, 'group')); 306 307 if (empty($group->coursecode)) { 308 $this->log_line('Error: Unable to find course code in \'group\' element.'); 309 } else { 310 // First, truncate the course code if desired. 311 if (intval($truncatecoursecodes) > 0) { 312 $group->coursecode = ($truncatecoursecodes > 0) 313 ? substr($group->coursecode, 0, intval($truncatecoursecodes)) 314 : $group->coursecode; 315 } 316 317 // For compatibility with the (currently inactive) course aliasing, we need this to be an array. 318 $group->coursecode = array($group->coursecode); 319 320 // Third, check if the course(s) exist. 321 foreach ($group->coursecode as $coursecode) { 322 $coursecode = trim($coursecode); 323 if (!$DB->get_field('course', 'id', array('idnumber' => $coursecode))) { 324 if (!$createnewcourses) { 325 $this->log_line("Course $coursecode not found in Moodle's course idnumbers."); 326 } else { 327 328 // Create the (hidden) course(s) if not found 329 $courseconfig = get_config('moodlecourse'); // Load Moodle Course shell defaults. 330 331 // New course. 332 $course = new stdClass(); 333 foreach ($this->coursemappings as $courseattr => $imsname) { 334 335 if ($imsname == 'ignore') { 336 continue; 337 } 338 339 // Check if the IMS file contains the mapped tag, otherwise fallback on coursecode. 340 if ($imsname == 'coursecode') { 341 $course->{$courseattr} = $coursecode; 342 } else if (!empty($group->{$imsname})) { 343 $course->{$courseattr} = $group->{$imsname}; 344 } else { 345 $this->log_line('No ' . $imsname . ' description tag found for ' 346 .$coursecode . ' coursecode, using ' . $coursecode . ' instead'); 347 $course->{$courseattr} = $coursecode; 348 } 349 } 350 351 $course->idnumber = $coursecode; 352 $course->format = $courseconfig->format; 353 $course->visible = $courseconfig->visible; 354 $course->newsitems = $courseconfig->newsitems; 355 $course->showgrades = $courseconfig->showgrades; 356 $course->showreports = $courseconfig->showreports; 357 $course->maxbytes = $courseconfig->maxbytes; 358 $course->groupmode = $courseconfig->groupmode; 359 $course->groupmodeforce = $courseconfig->groupmodeforce; 360 $course->enablecompletion = $courseconfig->enablecompletion; 361 // Insert default names for teachers/students, from the current language. 362 363 // Handle course categorisation (taken from the group.org.orgunit field if present). 364 if (!empty($group->category)) { 365 // If the category is defined and exists in Moodle, we want to store it in that one. 366 if ($catid = $DB->get_field('course_categories', 'id', array('name' => $group->category))) { 367 $course->category = $catid; 368 } else if ($createnewcategories) { 369 // Else if we're allowed to create new categories, let's create this one. 370 $newcat = new stdClass(); 371 $newcat->name = $group->category; 372 $newcat->visible = 0; 373 $catid = $DB->insert_record('course_categories', $newcat); 374 $course->category = $catid; 375 $this->log_line("Created new (hidden) category, #$catid: $newcat->name"); 376 } else { 377 // If not found and not allowed to create, stick with default. 378 $this->log_line('Category '.$group->category.' not found in Moodle database, so using '. 379 'default category instead.'); 380 $course->category = $this->get_default_category_id(); 381 } 382 } else { 383 $course->category = $this->get_default_category_id(); 384 } 385 $course->startdate = time(); 386 // Choose a sort order that puts us at the start of the list! 387 $course->sortorder = 0; 388 389 $course = create_course($course); 390 391 $this->log_line("Created course $coursecode in Moodle (Moodle ID is $course->id)"); 392 } 393 } else if ($recstatus == 3 && ($courseid = $DB->get_field('course', 'id', array('idnumber' => $coursecode)))) { 394 // If course does exist, but recstatus==3 (delete), then set the course as hidden. 395 $DB->set_field('course', 'visible', '0', array('id' => $courseid)); 396 } 397 } 398 } 399 } 400 401 /** 402 * Process the person tag. This defines a Moodle user. 403 * 404 * @param string $tagcontents The raw contents of the XML element 405 */ 406 protected function process_person_tag($tagcontents) { 407 global $CFG, $DB; 408 409 // Get plugin configs. 410 $imssourcedidfallback = $this->get_config('imssourcedidfallback'); 411 $fixcaseusernames = $this->get_config('fixcaseusernames'); 412 $fixcasepersonalnames = $this->get_config('fixcasepersonalnames'); 413 $imsdeleteusers = $this->get_config('imsdeleteusers'); 414 $createnewusers = $this->get_config('createnewusers'); 415 416 $person = new stdClass(); 417 if (preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)) { 418 $person->idnumber = trim($matches[1]); 419 } 420 if (preg_match('{<name>.*?<n>.*?<given>(.+?)</given>.*?</n>.*?</name>}is', $tagcontents, $matches)) { 421 $person->firstname = trim($matches[1]); 422 } 423 if (preg_match('{<name>.*?<n>.*?<family>(.+?)</family>.*?</n>.*?</name>}is', $tagcontents, $matches)) { 424 $person->lastname = trim($matches[1]); 425 } 426 if (preg_match('{<userid>(.*?)</userid>}is', $tagcontents, $matches)) { 427 $person->username = trim($matches[1]); 428 } 429 if ($imssourcedidfallback && trim($person->username) == '') { 430 // This is the point where we can fall back to useing the "sourcedid" if "userid" is not supplied 431 // NB We don't use an "elseif" because the tag may be supplied-but-empty. 432 $person->username = $person->idnumber; 433 } 434 if (preg_match('{<email>(.*?)</email>}is', $tagcontents, $matches)) { 435 $person->email = trim($matches[1]); 436 } 437 if (preg_match('{<url>(.*?)</url>}is', $tagcontents, $matches)) { 438 $person->url = trim($matches[1]); 439 } 440 if (preg_match('{<adr>.*?<locality>(.+?)</locality>.*?</adr>}is', $tagcontents, $matches)) { 441 $person->city = trim($matches[1]); 442 } 443 if (preg_match('{<adr>.*?<country>(.+?)</country>.*?</adr>}is', $tagcontents, $matches)) { 444 $person->country = trim($matches[1]); 445 } 446 447 // Fix case of some of the fields if required. 448 if ($fixcaseusernames && isset($person->username)) { 449 $person->username = strtolower($person->username); 450 } 451 if ($fixcasepersonalnames) { 452 if (isset($person->firstname)) { 453 $person->firstname = ucwords(strtolower($person->firstname)); 454 } 455 if (isset($person->lastname)) { 456 $person->lastname = ucwords(strtolower($person->lastname)); 457 } 458 } 459 460 $recstatus = ($this->get_recstatus($tagcontents, 'person')); 461 462 // Now if the recstatus is 3, we should delete the user if-and-only-if the setting for delete users is turned on. 463 if ($recstatus == 3) { 464 465 if ($imsdeleteusers) { // If we're allowed to delete user records. 466 // Do not dare to hack the user.deleted field directly in database!!! 467 $params = array('username' => $person->username, 'mnethostid' => $CFG->mnet_localhost_id, 'deleted ' => 0); 468 if ($user = $DB->get_record('user', $params)) { 469 if (delete_user($user)) { 470 $this->log_line("Deleted user '$person->username' (ID number $person->idnumber)."); 471 } else { 472 $this->log_line("Error deleting '$person->username' (ID number $person->idnumber)."); 473 } 474 } else { 475 $this->log_line("Can not delete user '$person->username' (ID number $person->idnumber) - user does not exist."); 476 } 477 } else { 478 $this->log_line("Ignoring deletion request for user '$person->username' (ID number $person->idnumber)."); 479 } 480 481 } else { // Add or update record. 482 483 // If the user exists (matching sourcedid) then we don't need to do anything. 484 if (!$DB->get_field('user', 'id', array('idnumber' => $person->idnumber)) && $createnewusers) { 485 // If they don't exist and haven't a defined username, we log this as a potential problem. 486 if ((!isset($person->username)) || (strlen($person->username) == 0)) { 487 $this->log_line("Cannot create new user for ID # $person->idnumber". 488 "- no username listed in IMS data for this person."); 489 } else if ($DB->get_field('user', 'id', array('username' => $person->username))) { 490 // If their idnumber is not registered but their user ID is, then add their idnumber to their record. 491 $DB->set_field('user', 'idnumber', $person->idnumber, array('username' => $person->username)); 492 } else { 493 494 // If they don't exist and they have a defined username, and $createnewusers == true, we create them. 495 $person->lang = $CFG->lang; 496 // TODO: MDL-15863 this needs more work due to multiauth changes, use first auth for now. 497 $auth = explode(',', $CFG->auth); 498 $auth = reset($auth); 499 $person->auth = $auth; 500 $person->confirmed = 1; 501 $person->timemodified = time(); 502 $person->mnethostid = $CFG->mnet_localhost_id; 503 $id = $DB->insert_record('user', $person); 504 $this->log_line("Created user record ('.$id.') for user '$person->username' (ID number $person->idnumber)."); 505 } 506 } else if ($createnewusers) { 507 $this->log_line("User record already exists for user '$person->username' (ID number $person->idnumber)."); 508 509 // It is totally wrong to mess with deleted users flag directly in database!!! 510 // There is no official way to undelete user, sorry.. 511 } else { 512 $this->log_line("No user record found for '$person->username' (ID number $person->idnumber)."); 513 } 514 515 } 516 517 } 518 519 /** 520 * Process the membership tag. This defines whether the specified Moodle users 521 * should be added/removed as teachers/students. 522 * 523 * @param string $tagcontents The raw contents of the XML element 524 */ 525 protected function process_membership_tag($tagcontents) { 526 global $DB; 527 528 // Get plugin configs. 529 $truncatecoursecodes = $this->get_config('truncatecoursecodes'); 530 $imscapitafix = $this->get_config('imscapitafix'); 531 532 $memberstally = 0; 533 $membersuntally = 0; 534 535 // In order to reduce the number of db queries required, group name/id associations are cached in this array. 536 $groupids = array(); 537 538 $ship = new stdClass(); 539 540 if (preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)) { 541 $ship->coursecode = ($truncatecoursecodes > 0) 542 ? substr(trim($matches[1]), 0, intval($truncatecoursecodes)) 543 : trim($matches[1]); 544 $ship->courseid = $DB->get_field('course', 'id', array('idnumber' => $ship->coursecode)); 545 } 546 if ($ship->courseid && preg_match_all('{<member>(.*?)</member>}is', $tagcontents, $membermatches, PREG_SET_ORDER)) { 547 $courseobj = new stdClass(); 548 $courseobj->id = $ship->courseid; 549 550 foreach ($membermatches as $mmatch) { 551 $member = new stdClass(); 552 $memberstoreobj = new stdClass(); 553 if (preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $mmatch[1], $matches)) { 554 $member->idnumber = trim($matches[1]); 555 } 556 if (preg_match('{<role\s+roletype=["\'](.+?)["\'].*?>}is', $mmatch[1], $matches)) { 557 // 01 means Student, 02 means Instructor, 3 means ContentDeveloper, and there are more besides. 558 $member->roletype = trim($matches[1]); 559 } else if ($imscapitafix && preg_match('{<roletype>(.+?)</roletype>}is', $mmatch[1], $matches)) { 560 // The XML that comes out of Capita Student Records seems to contain a misinterpretation of 561 // the IMS specification! 01 means Student, 02 means Instructor, 3 means ContentDeveloper, 562 // and there are more besides. 563 $member->roletype = trim($matches[1]); 564 } 565 if (preg_match('{<role\b.*?<status>(.+?)</status>.*?</role>}is', $mmatch[1], $matches)) { 566 // 1 means active, 0 means inactive - treat this as enrol vs unenrol. 567 $member->status = trim($matches[1]); 568 } 569 570 $recstatus = ($this->get_recstatus($mmatch[1], 'role')); 571 if ($recstatus == 3) { 572 // See above - recstatus of 3 (==delete) is treated the same as status of 0. 573 $member->status = 0; 574 } 575 576 $timeframe = new stdClass(); 577 $timeframe->begin = 0; 578 $timeframe->end = 0; 579 if (preg_match('{<role\b.*?<timeframe>(.+?)</timeframe>.*?</role>}is', $mmatch[1], $matches)) { 580 $timeframe = $this->decode_timeframe($matches[1]); 581 } 582 if (preg_match('{<role\b.*?<extension>.*?<cohort>(.+?)</cohort>.*?</extension>.*?</role>}is', 583 $mmatch[1], $matches)) { 584 $member->groupname = trim($matches[1]); 585 // The actual processing (ensuring a group record exists, etc) occurs below, in the enrol-a-student clause. 586 } 587 588 // Add or remove this student or teacher to the course... 589 $memberstoreobj->userid = $DB->get_field('user', 'id', array('idnumber' => $member->idnumber)); 590 $memberstoreobj->enrol = 'imsenterprise'; 591 $memberstoreobj->course = $ship->courseid; 592 $memberstoreobj->time = time(); 593 $memberstoreobj->timemodified = time(); 594 if ($memberstoreobj->userid) { 595 596 // Decide the "real" role (i.e. the Moodle role) that this user should be assigned to. 597 // Zero means this roletype is supposed to be skipped. 598 $moodleroleid = $this->rolemappings[$member->roletype]; 599 if (!$moodleroleid) { 600 $this->log_line("SKIPPING role $member->roletype for $memberstoreobj->userid " 601 ."($member->idnumber) in course $memberstoreobj->course"); 602 continue; 603 } 604 605 if (intval($member->status) == 1) { 606 // Enrol the member. 607 608 $einstance = $DB->get_record('enrol', 609 array('courseid' => $courseobj->id, 'enrol' => $memberstoreobj->enrol)); 610 if (empty($einstance)) { 611 // Only add an enrol instance to the course if non-existent. 612 $enrolid = $this->add_instance($courseobj); 613 $einstance = $DB->get_record('enrol', array('id' => $enrolid)); 614 } 615 616 $this->enrol_user($einstance, $memberstoreobj->userid, $moodleroleid, $timeframe->begin, $timeframe->end); 617 618 $this->log_line("Enrolled user #$memberstoreobj->userid ($member->idnumber) " 619 ."to role $member->roletype in course $memberstoreobj->course"); 620 $memberstally++; 621 622 // At this point we can also ensure the group membership is recorded if present. 623 if (isset($member->groupname)) { 624 // Create the group if it doesn't exist - either way, make sure we know the group ID. 625 if (isset($groupids[$member->groupname])) { 626 $member->groupid = $groupids[$member->groupname]; // Recall the group ID from cache if available. 627 } else { 628 $params = array('courseid' => $ship->courseid, 'name' => $member->groupname); 629 if ($groupid = $DB->get_field('groups', 'id', $params)) { 630 $member->groupid = $groupid; 631 $groupids[$member->groupname] = $groupid; // Store ID in cache. 632 } else { 633 // Attempt to create the group. 634 $group = new stdClass(); 635 $group->name = $member->groupname; 636 $group->courseid = $ship->courseid; 637 $group->timecreated = time(); 638 $group->timemodified = time(); 639 $groupid = $DB->insert_record('groups', $group); 640 $this->log_line('Added a new group for this course: '.$group->name); 641 $groupids[$member->groupname] = $groupid; // Store ID in cache. 642 $member->groupid = $groupid; 643 // Invalidate the course group data cache just in case. 644 cache_helper::invalidate_by_definition('core', 'groupdata', array(), array($ship->courseid)); 645 } 646 } 647 // Add the user-to-group association if it doesn't already exist. 648 if ($member->groupid) { 649 groups_add_member($member->groupid, $memberstoreobj->userid, 650 'enrol_imsenterprise', $einstance->id); 651 } 652 } 653 654 } else if ($this->get_config('imsunenrol')) { 655 // Unenrol member. 656 657 $einstances = $DB->get_records('enrol', 658 array('enrol' => $memberstoreobj->enrol, 'courseid' => $courseobj->id)); 659 foreach ($einstances as $einstance) { 660 // Unenrol the user from all imsenterprise enrolment instances. 661 $this->unenrol_user($einstance, $memberstoreobj->userid); 662 } 663 664 $membersuntally++; 665 $this->log_line("Unenrolled $member->idnumber from role $moodleroleid in course"); 666 } 667 668 } 669 } 670 $this->log_line("Added $memberstally users to course $ship->coursecode"); 671 if ($membersuntally > 0) { 672 $this->log_line("Removed $membersuntally users from course $ship->coursecode"); 673 } 674 } 675 } // End process_membership_tag(). 676 677 /** 678 * Process the properties tag. The only data from this element 679 * that is relevant is whether a <target> is specified. 680 * 681 * @param string $tagcontents The raw contents of the XML element 682 */ 683 protected function process_properties_tag($tagcontents) { 684 $imsrestricttarget = $this->get_config('imsrestricttarget'); 685 686 if ($imsrestricttarget) { 687 if (!(preg_match('{<target>'.preg_quote($imsrestricttarget).'</target>}is', $tagcontents, $matches))) { 688 $this->log_line("Skipping processing: required target \"$imsrestricttarget\" not specified in this data."); 689 $this->continueprocessing = false; 690 } 691 } 692 } 693 694 /** 695 * Store logging information. This does two things: uses the {@link mtrace()} 696 * function to print info to screen/STDOUT, and also writes log to a text file 697 * if a path has been specified. 698 * @param string $string Text to write (newline will be added automatically) 699 */ 700 protected function log_line($string) { 701 702 if (!PHPUNIT_TEST) { 703 mtrace($string); 704 } 705 if ($this->logfp) { 706 fwrite($this->logfp, $string . "\n"); 707 } 708 } 709 710 /** 711 * Process the INNER contents of a <timeframe> tag, to return beginning/ending dates. 712 * 713 * @param string $string tag to decode. 714 * @return stdClass beginning and/or ending is returned, in unix time, zero indicating not specified. 715 */ 716 protected static function decode_timeframe($string) { 717 $ret = new stdClass(); 718 $ret->begin = $ret->end = 0; 719 // Explanatory note: The matching will ONLY match if the attribute restrict="1" 720 // because otherwise the time markers should be ignored (participation should be 721 // allowed outside the period). 722 if (preg_match('{<begin\s+restrict="1">(\d\d\d\d)-(\d\d)-(\d\d)</begin>}is', $string, $matches)) { 723 $ret->begin = mktime(0, 0, 0, $matches[2], $matches[3], $matches[1]); 724 } 725 if (preg_match('{<end\s+restrict="1">(\d\d\d\d)-(\d\d)-(\d\d)</end>}is', $string, $matches)) { 726 $ret->end = mktime(0, 0, 0, $matches[2], $matches[3], $matches[1]); 727 } 728 return $ret; 729 } 730 731 /** 732 * Load the role mappings (from the config), so we can easily refer to 733 * how an IMS-E role corresponds to a Moodle role 734 */ 735 protected function load_role_mappings() { 736 require_once ('locallib.php'); 737 738 $imsroles = new imsenterprise_roles(); 739 $imsroles = $imsroles->get_imsroles(); 740 741 $this->rolemappings = array(); 742 foreach ($imsroles as $imsrolenum => $imsrolename) { 743 $this->rolemappings[$imsrolenum] = $this->rolemappings[$imsrolename] = $this->get_config('imsrolemap' . $imsrolenum); 744 } 745 } 746 747 /** 748 * Load the name mappings (from the config), so we can easily refer to 749 * how an IMS-E course properties corresponds to a Moodle course properties 750 */ 751 protected function load_course_mappings() { 752 require_once ('locallib.php'); 753 754 $imsnames = new imsenterprise_courses(); 755 $courseattrs = $imsnames->get_courseattrs(); 756 757 $this->coursemappings = array(); 758 foreach ($courseattrs as $courseattr) { 759 $this->coursemappings[$courseattr] = $this->get_config('imscoursemap' . $courseattr); 760 } 761 } 762 763 /** 764 * Called whenever anybody tries (from the normal interface) to remove a group 765 * member which is registered as being created by this component. (Not called 766 * when deleting an entire group or course at once.) 767 * @param int $itemid Item ID that was stored in the group_members entry 768 * @param int $groupid Group ID 769 * @param int $userid User ID being removed from group 770 * @return bool True if the remove is permitted, false to give an error 771 */ 772 public function enrol_imsenterprise_allow_group_member_remove($itemid, $groupid, $userid) { 773 return false; 774 } 775 776 777 /** 778 * Get the default category id (often known as 'Miscellaneous'), 779 * statically cached to avoid multiple DB lookups on big imports. 780 * 781 * @return int id of default category. 782 */ 783 private function get_default_category_id() { 784 global $CFG; 785 require_once($CFG->libdir.'/coursecatlib.php'); 786 787 static $defaultcategoryid = null; 788 789 if ($defaultcategoryid === null) { 790 $category = coursecat::get_default(); 791 $defaultcategoryid = $category->id; 792 } 793 794 return $defaultcategoryid; 795 } 796 797 /** 798 * Is it possible to delete enrol instance via standard UI? 799 * 800 * @param object $instance 801 * @return bool 802 */ 803 public function can_delete_instance($instance) { 804 $context = context_course::instance($instance->courseid); 805 return has_capability('enrol/imsenterprise:config', $context); 806 } 807 808 /** 809 * Is it possible to hide/show enrol instance via standard UI? 810 * 811 * @param stdClass $instance 812 * @return bool 813 */ 814 public function can_hide_show_instance($instance) { 815 $context = context_course::instance($instance->courseid); 816 return has_capability('enrol/imsenterprise:config', $context); 817 } 818 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |