| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * @author Dan Stowell 4 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 5 * @package enrol_imsenterprise 6 */ 7 require_once("$CFG->libdir/blocklib.php"); 8 require_once($CFG->dirroot.'/group/lib.php'); 9 10 // The following flags are set in the configuration 11 // $CFG->enrol_imsfilelocation: where is the file we are looking for? 12 // $CFG->enrol_logtolocation: if you want to store a log of processing, specify filepath here 13 // $CFG->enrol_allowinternal: allow internal enrolment in courses 14 // $CFG->enrol_emailadmins: email a notification to the admin 15 // $CFG->enrol_createnewusers: should this script create user accounts for those who don't seem to be registered yet? 16 // $CFG->enrol_imsdeleteusers: should this script mark user accounts as deleted, if the data requests this? 17 // $CFG->enrol_fixcaseusernames: whether to force all usernames to lowercase 18 // $CFG->enrol_fixcasepersonalnames: convert personal names, e.g. from "TOM VEK" to "Tom Vek" 19 // $CFG->enrol_truncatecoursecodes: if this number is greater than zero, truncate the codes found in the IMS data to the given number of characters 20 // $CFG->enrol_imsunenrol: allow this script to UNENROL students/tutors from courses (if the data marks them as having left the course) 21 // $CFG->enrol_createnewcourses: should this script create a (hidden, empty) course for any course that doesn't seem to have been registered yet? 22 // $CFG->enrol_createnewcategories: should this script create a (hidden) category if Moodle doesn't have one by the same name as the desired one? 23 // $CFG->enrol_imssourcedidfallback: some systems don't output a <userid> element (contrary to the specifications). If this is the case, activating this setting will cause us to use the <sourcedid><id> element instead as the userid. This may or may not be desirable in your situation. 24 // $CFG->enrol_includephoto: Process IMS <photo> tag to create user photo. Be warned that this may add significant server load. 25 26 /* 27 28 Note for programmers: 29 30 This class uses regular expressions to mine the data file. The main reason is 31 that XML handling changes from PHP 4 to PHP 5, so this should work on both. 32 33 One drawback is that the pattern-matching doesn't (currently) handle XML 34 namespaces - it only copes with a <group> tag if it says <group>, and not 35 (for example) <ims:group>. 36 37 This should also be able to handle VERY LARGE FILES - so the entire IMS file is 38 NOT loaded into memory at once. It's handled line-by-line, 'forgetting' tags as 39 soon as they are processed. 40 41 N.B. The "sourcedid" ID code is translated to Moodle's "idnumber" field, both 42 for users and for courses. 43 44 */ 45 46 47 class enrolment_plugin_imsenterprise { 48 49 var $log; 50 51 // The "roles" hard-coded in the IMS specification are: 52 var $imsroles = array( 53 '01'=>'Learner', 54 '02'=>'Instructor', 55 '03'=>'Content Developer', 56 '04'=>'Member', 57 '05'=>'Manager', 58 '06'=>'Mentor', 59 '07'=>'Administrator', 60 '08'=>'TeachingAssistant', 61 ); 62 // PLEASE NOTE: It may seem odd that "Content Developer" has a space in it 63 // but "TeachingAssistant" doesn't. That's what the spec says though!!! 64 65 66 /** 67 * This function is only used when first setting up the plugin, to 68 * decide which role assignments to recommend by default. 69 * For example, IMS role '01' is 'Learner', so may map to 'student' in Moodle. 70 */ 71 function determine_default_rolemapping($imscode) { 72 switch($imscode) { 73 case '01': 74 case '04': 75 $shortname = 'student'; 76 break; 77 case '06': 78 case '08': 79 $shortname = 'teacher'; 80 break; 81 case '02': 82 case '03': 83 $shortname = 'editingteacher'; 84 break; 85 case '05': 86 case '07': 87 $shortname = 'admin'; 88 break; 89 default: 90 return 0; // Zero for no match 91 } 92 return get_field('role', 'id', 'shortname', $shortname); 93 } 94 95 96 97 /// Override the base config_form() function 98 function config_form($frm) { 99 global $CFG, $imsroles; 100 101 $vars = array('enrol_imsfilelocation', 'enrol_logtolocation', 'enrol_createnewusers', 'enrol_fixcaseusernames', 'enrol_fixcasepersonalnames', 'enrol_truncatecoursecodes', 102 'enrol_createnewcourses', 'enrol_createnewcategories', 'enrol_createnewusers', 'enrol_mailadmins', 103 'enrol_imsunenrol', 'enrol_imssourcedidfallback', 'enrol_imscapitafix', 'enrol_imsrestricttarget', 'enrol_imsdeleteusers', 104 'enrol_imse_imsrolemap01','enrol_imse_imsrolemap02','enrol_imse_imsrolemap03','enrol_imse_imsrolemap04', 105 'enrol_imse_imsrolemap05','enrol_imse_imsrolemap06','enrol_imse_imsrolemap07','enrol_imse_imsrolemap08'); 106 foreach ($vars as $var) { 107 if (!isset($frm->$var)) { 108 $frm->$var = ''; 109 } 110 } 111 include ("$CFG->dirroot/enrol/imsenterprise/config.html"); 112 } 113 114 115 /// Override the base process_config() function 116 function process_config($config) { 117 118 if (!isset($config->enrol_imsfilelocation)) { 119 $config->enrol_imsfilelocation = ''; 120 } 121 set_config('enrol_imsfilelocation', $config->enrol_imsfilelocation); 122 123 if (!isset($config->enrol_logtolocation)) { 124 $config->enrol_logtolocation = ''; 125 } 126 set_config('enrol_logtolocation', $config->enrol_logtolocation); 127 128 if (!isset($config->enrol_fixcaseusernames)) { 129 $config->enrol_fixcaseusernames = ''; 130 } 131 set_config('enrol_fixcaseusernames', $config->enrol_fixcaseusernames); 132 133 if (!isset($config->enrol_fixcasepersonalnames)) { 134 $config->enrol_fixcasepersonalnames = ''; 135 } 136 set_config('enrol_fixcasepersonalnames', $config->enrol_fixcasepersonalnames); 137 138 if (!isset($config->enrol_truncatecoursecodes)) { 139 $config->enrol_truncatecoursecodes = 0; 140 } 141 set_config('enrol_truncatecoursecodes', intval($config->enrol_truncatecoursecodes)); 142 143 if (!isset($config->enrol_createnewcourses)) { 144 $config->enrol_createnewcourses = ''; 145 } 146 set_config('enrol_createnewcourses', $config->enrol_createnewcourses); 147 148 if (!isset($config->enrol_createnewcategories)) { 149 $config->enrol_createnewcategories = ''; 150 } 151 set_config('enrol_createnewcategories', $config->enrol_createnewcategories); 152 153 if (!isset($config->enrol_createnewusers)) { 154 $config->enrol_createnewusers = ''; 155 } 156 set_config('enrol_createnewusers', $config->enrol_createnewusers); 157 158 if (!isset($config->enrol_imsdeleteusers)) { 159 $config->enrol_imsdeleteusers = ''; 160 } 161 set_config('enrol_imsdeleteusers', $config->enrol_imsdeleteusers); 162 163 if (!isset($config->enrol_mailadmins)) { 164 $config->enrol_mailadmins = ''; 165 } 166 set_config('enrol_mailadmins', $config->enrol_mailadmins); 167 168 if (!isset($config->enrol_imsunenrol)) { 169 $config->enrol_imsunenrol = ''; 170 } 171 set_config('enrol_imsunenrol', $config->enrol_imsunenrol); 172 173 if (!isset($config->enrol_imssourcedidfallback)) { 174 $config->enrol_imssourcedidfallback = ''; 175 } 176 set_config('enrol_imssourcedidfallback', $config->enrol_imssourcedidfallback); 177 178 if (!isset($config->enrol_imscapitafix)) { 179 $config->enrol_imscapitafix = ''; 180 } 181 set_config('enrol_imscapitafix', $config->enrol_imscapitafix); 182 183 //Antoni Mas. 07/12/2005. Incloem la opci de la foto dels usuaris 184 if (!isset($config->enrol_processphoto)) { 185 $config->enrol_processphoto = ''; 186 } 187 set_config('enrol_processphoto', $config->enrol_processphoto); 188 189 if (!isset($config->enrol_imsrestricttarget)) { 190 $config->enrol_imsrestricttarget = ''; 191 } 192 set_config('enrol_imsrestricttarget', $config->enrol_imsrestricttarget); 193 194 195 196 foreach($this->imsroles as $imsrolenum=>$imsrolename){ 197 $configref = 'enrol_imse_imsrolemap' . $imsrolenum; 198 if (!isset($config->$configref)) { 199 echo "<p>Resetting config->$configref</p>"; 200 $config->$configref = 0; 201 } 202 set_config('enrol_imse_imsrolemap' . $imsrolenum, $config->$configref); 203 } 204 205 206 set_config('enrol_ims_prev_md5', ''); // Forget the MD5 - to force re-processing if we change the config setting 207 set_config('enrol_ims_prev_time', ''); // Ditto 208 return true; 209 210 } 211 212 function get_access_icons($course){} 213 214 /** 215 * Read in an IMS Enterprise file. 216 * Originally designed to handle v1.1 files but should be able to handle 217 * earlier types as well, I believe. 218 * 219 */ 220 function cron() { 221 global $CFG; 222 223 if (empty($CFG->enrol_imsfilelocation)) { 224 // $filename = "$CFG->dirroot/enrol/imsenterprise/example.xml"; // Default location 225 $filename = "$CFG->dataroot/1/imsenterprise-enrol.xml"; // Default location 226 } else { 227 $filename = $CFG->enrol_imsfilelocation; 228 } 229 230 $this->logfp = false; // File pointer for writing log data to 231 if(!empty($CFG->enrol_logtolocation)) { 232 $this->logfp = fopen($CFG->enrol_logtolocation, 'a'); 233 } 234 235 236 237 if ( file_exists($filename) ) { 238 @set_time_limit(0); 239 $starttime = time(); 240 241 $this->log_line('----------------------------------------------------------------------'); 242 $this->log_line("IMS Enterprise enrol cron process launched at " . userdate(time())); 243 $this->log_line('Found file '.$filename); 244 $this->xmlcache = ''; 245 246 // Make sure we understand how to map the IMS-E roles to Moodle roles 247 $this->load_role_mappings(); 248 249 $md5 = md5_file($filename); // NB We'll write this value back to the database at the end of the cron 250 $filemtime = filemtime($filename); 251 252 // Decide if we want to process the file (based on filepath, modification time, and MD5 hash) 253 // This is so we avoid wasting the server's efforts processing a file unnecessarily 254 if(empty($CFG->enrol_ims_prev_path) || ($filename != $CFG->enrol_ims_prev_path)){ 255 $fileisnew = true; 256 }elseif(isset($CFG->enrol_ims_prev_time) && ($filemtime <= $CFG->enrol_ims_prev_time)){ 257 $fileisnew = false; 258 $this->log_line('File modification time is not more recent than last update - skipping processing.'); 259 }elseif(isset($CFG->enrol_ims_prev_md5) && ($md5 == $CFG->enrol_ims_prev_md5)){ 260 $fileisnew = false; 261 $this->log_line('File MD5 hash is same as on last update - skipping processing.'); 262 }else{ 263 $fileisnew = true; // Let's process it! 264 } 265 266 if($fileisnew){ 267 268 $listoftags = array('group', 'person', 'member', 'membership', 'comments', 'properties'); // The list of tags which should trigger action (even if only cache trimming) 269 $this->continueprocessing = true; // The <properties> tag is allowed to halt processing if we're demanding a matching target 270 271 // FIRST PASS: Run through the file and process the group/person entries 272 if (($fh = fopen($filename, "r")) != false) { 273 274 $line = 0; 275 while ((!feof($fh)) && $this->continueprocessing) { 276 277 $line++; 278 $curline = fgets($fh); 279 $this->xmlcache .= $curline; // Add a line onto the XML cache 280 281 while(true){ 282 // If we've got a full tag (i.e. the most recent line has closed the tag) then process-it-and-forget-it. 283 // Must always make sure to remove tags from cache so they don't clog up our memory 284 if($tagcontents = $this->full_tag_found_in_cache('group', $curline)){ 285 $this->process_group_tag($tagcontents); 286 $this->remove_tag_from_cache('group'); 287 }elseif($tagcontents = $this->full_tag_found_in_cache('person', $curline)){ 288 $this->process_person_tag($tagcontents); 289 $this->remove_tag_from_cache('person'); 290 }elseif($tagcontents = $this->full_tag_found_in_cache('membership', $curline)){ 291 $this->process_membership_tag($tagcontents); 292 $this->remove_tag_from_cache('membership'); 293 }elseif($tagcontents = $this->full_tag_found_in_cache('comments', $curline)){ 294 $this->remove_tag_from_cache('comments'); 295 }elseif($tagcontents = $this->full_tag_found_in_cache('properties', $curline)){ 296 $this->process_properties_tag($tagcontents); 297 $this->remove_tag_from_cache('properties'); 298 }else{ 299 break; 300 } 301 } // End of while-tags-are-detected 302 } // end of while loop 303 fclose($fh); 304 fix_course_sortorder(); 305 } // end of if(file_open) for first pass 306 307 /* 308 309 310 SECOND PASS REMOVED 311 Since the IMS specification v1.1 insists that "memberships" should come last, 312 and since vendors seem to have done this anyway (even with 1.0), 313 we can sensibly perform the import in one fell swoop. 314 315 316 // SECOND PASS: Now go through the file and process the membership entries 317 $this->xmlcache = ''; 318 if (($fh = fopen($filename, "r")) != false) { 319 $line = 0; 320 while ((!feof($fh)) && $this->continueprocessing) { 321 $line++; 322 $curline = fgets($fh); 323 $this->xmlcache .= $curline; // Add a line onto the XML cache 324 325 while(true){ 326 // Must always make sure to remove tags from cache so they don't clog up our memory 327 if($tagcontents = $this->full_tag_found_in_cache('group', $curline)){ 328 $this->remove_tag_from_cache('group'); 329 }elseif($tagcontents = $this->full_tag_found_in_cache('person', $curline)){ 330 $this->remove_tag_from_cache('person'); 331 }elseif($tagcontents = $this->full_tag_found_in_cache('membership', $curline)){ 332 $this->process_membership_tag($tagcontents); 333 $this->remove_tag_from_cache('membership'); 334 }elseif($tagcontents = $this->full_tag_found_in_cache('comments', $curline)){ 335 $this->remove_tag_from_cache('comments'); 336 }elseif($tagcontents = $this->full_tag_found_in_cache('properties', $curline)){ 337 $this->remove_tag_from_cache('properties'); 338 }else{ 339 break; 340 } 341 } 342 } // end of while loop 343 fclose($fh); 344 } // end of if(file_open) for second pass 345 346 347 */ 348 349 $timeelapsed = time() - $starttime; 350 $this->log_line('Process has completed. Time taken: '.$timeelapsed.' seconds.'); 351 352 353 } // END of "if file is new" 354 355 356 // These variables are stored so we can compare them against the IMS file, next time round. 357 set_config('enrol_ims_prev_time', $filemtime); 358 set_config('enrol_ims_prev_md5', $md5); 359 set_config('enrol_ims_prev_path', $filename); 360 361 362 363 }else{ // end of if(file_exists) 364 $this->log_line('File not found: '.$filename); 365 } 366 367 if (!empty($CFG->enrol_mailadmins)) { 368 $msg = "An IMS enrolment has been carried out within Moodle.\nTime taken: $timeelapsed seconds.\n\n"; 369 if(!empty($CFG->enrol_logtolocation)){ 370 if($this->logfp){ 371 $msg .= "Log data has been written to:\n"; 372 $msg .= "$CFG->enrol_logtolocation\n"; 373 $msg .= "(Log file size: ".ceil(filesize($CFG->enrol_logtolocation)/1024)."Kb)\n\n"; 374 }else{ 375 $msg .= "The log file appears not to have been successfully written.\nCheck that the file is writeable by the server:\n"; 376 $msg .= "$CFG->enrol_logtolocation\n\n"; 377 } 378 }else{ 379 $msg .= "Logging is currently not active."; 380 } 381 382 email_to_user(get_admin(), get_admin(), "Moodle IMS Enterprise enrolment notification", $msg); 383 $this->log_line('Notification email sent to administrator.'); 384 385 } 386 387 if($this->logfp){ 388 fclose($this->logfp); 389 } 390 391 392 } // end of cron() function 393 394 /** 395 * Check if a complete tag is found in the cached data, which usually happens 396 * when the end of the tag has only just been loaded into the cache. 397 * Returns either false, or the contents of the tag (including start and end). 398 * @param string $tagname Name of tag to look for 399 * @param string $latestline The very last line in the cache (used for speeding up the match) 400 */ 401 function full_tag_found_in_cache($tagname, $latestline){ // Return entire element if found. Otherwise return false. 402 if(strpos(strtolower($latestline), '</'.strtolower($tagname).'>')===false){ 403 return false; 404 }elseif(preg_match('{(<'.$tagname.'\b.*?>.*?</'.$tagname.'>)}is', $this->xmlcache, $matches)){ 405 return $matches[1]; 406 }else return false; 407 } 408 409 /** 410 * Remove complete tag from the cached data (including all its contents) - so 411 * that the cache doesn't grow to unmanageable size 412 * @param string $tagname Name of tag to look for 413 */ 414 function remove_tag_from_cache($tagname){ // Trim the cache so we're not in danger of running out of memory. 415 ///echo "<p>remove_tag_from_cache: $tagname</p>"; flush(); ob_flush(); 416 // echo "<p>remove_tag_from_cache:<br />".htmlspecialchars($this->xmlcache); 417 $this->xmlcache = trim(preg_replace('{<'.$tagname.'\b.*?>.*?</'.$tagname.'>}is', '', $this->xmlcache, 1)); // "1" so that we replace only the FIRST instance 418 // echo "<br />".htmlspecialchars($this->xmlcache)."</p>"; 419 } 420 421 /** 422 * Very simple convenience function to return the "recstatus" found in person/group/role tags. 423 * 1=Add, 2=Update, 3=Delete, as specified by IMS, and we also use 0 to indicate "unspecified". 424 * @param string $tagdata the tag XML data 425 * @param string $tagname the name of the tag we're interested in 426 */ 427 function get_recstatus($tagdata, $tagname){ 428 if(preg_match('{<'.$tagname.'\b[^>]*recstatus\s*=\s*["\'](\d)["\']}is', $tagdata, $matches)){ 429 // echo "<p>get_recstatus($tagname) found status of $matches[1]</p>"; 430 return intval($matches[1]); 431 }else{ 432 // echo "<p>get_recstatus($tagname) found nothing</p>"; 433 return 0; // Unspecified 434 } 435 } 436 437 /** 438 * Process the group tag. This defines a Moodle course. 439 * @param string $tagconents The raw contents of the XML element 440 */ 441 function process_group_tag($tagcontents){ 442 global $CFG; 443 444 // Process tag contents 445 unset($group); 446 if(preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)){ 447 $group->coursecode = trim($matches[1]); 448 } 449 if(preg_match('{<description>.*?<short>(.*?)</short>.*?</description>}is', $tagcontents, $matches)){ 450 $group->description = trim($matches[1]); 451 } 452 if(preg_match('{<org>.*?<orgunit>(.*?)</orgunit>.*?</org>}is', $tagcontents, $matches)){ 453 $group->category = trim($matches[1]); 454 } 455 456 $recstatus = ($this->get_recstatus($tagcontents, 'group')); 457 //echo "<p>get_recstatus for this group returned $recstatus</p>"; 458 459 if(!(strlen($group->coursecode)>0)){ 460 $this->log_line('Error at line '.$line.': Unable to find course code in \'group\' element.'); 461 }else{ 462 // First, truncate the course code if desired 463 if(intval($CFG->enrol_truncatecoursecodes)>0){ 464 $group->coursecode = ($CFG->enrol_truncatecoursecodes > 0) 465 ? substr($group->coursecode, 0, intval($CFG->enrol_truncatecoursecodes)) 466 : $group->coursecode; 467 } 468 469 /* -----------Course aliasing is DEACTIVATED until a more general method is in place--------------- 470 471 // Second, look in the course alias table to see if the code should be translated to something else 472 if($aliases = get_field('enrol_coursealias', 'toids', 'fromid', $group->coursecode)){ 473 $this->log_line("Found alias of course code: Translated $group->coursecode to $aliases"); 474 // Alias is allowed to be a comma-separated list, so let's split it 475 $group->coursecode = explode(',', $aliases); 476 } 477 */ 478 479 // For compatibility with the (currently inactive) course aliasing, we need this to be an array 480 $group->coursecode = array($group->coursecode); 481 482 // Third, check if the course(s) exist 483 foreach($group->coursecode as $coursecode){ 484 $coursecode = trim($coursecode); 485 if(!get_field('course', 'id', 'idnumber', $coursecode)) { 486 if(!$CFG->enrol_createnewcourses) { 487 $this->log_line("Course $coursecode not found in Moodle's course idnumbers."); 488 } else { 489 // Create the (hidden) course(s) if not found 490 $course = new object(); 491 $course->fullname = $group->description; 492 $course->shortname = $coursecode; 493 $course->idnumber = $coursecode; 494 $course->format = 'topics'; 495 $course->visible = 0; 496 // Insert default names for teachers/students, from the current language 497 $site = get_site(); 498 if (current_language() == $CFG->lang) { 499 $course->teacher = $site->teacher; 500 $course->teachers = $site->teachers; 501 $course->student = $site->student; 502 $course->students = $site->students; 503 } else { 504 $course->teacher = get_string("defaultcourseteacher"); 505 $course->teachers = get_string("defaultcourseteachers"); 506 $course->student = get_string("defaultcoursestudent"); 507 $course->students = get_string("defaultcoursestudents"); 508 } 509 510 // Handle course categorisation (taken from the group.org.orgunit field if present) 511 if(strlen($group->category)>0){ 512 // If the category is defined and exists in Moodle, we want to store it in that one 513 if($catid = get_field('course_categories', 'id', 'name', addslashes($group->category))){ 514 $course->category = $catid; 515 }elseif($CFG->enrol_createnewcategories){ 516 // Else if we're allowed to create new categories, let's create this one 517 $newcat->name = $group->category; 518 $newcat->visible = 0; 519 if($catid = insert_record('course_categories', $newcat)){ 520 $course->category = $catid; 521 $this->log_line("Created new (hidden) category, #$catid: $newcat->name"); 522 }else{ 523 $this->log_line('Failed to create new category: '.$newcat->name); 524 } 525 }else{ 526 // If not found and not allowed to create, stick with default 527 $this->log_line('Category '.$group->category.' not found in Moodle database, so using default category instead.'); 528 $course->category = 1; 529 } 530 }else{ 531 $course->category = 1; 532 } 533 $course->timecreated = time(); 534 $course->startdate = time(); 535 $course->numsections = 1; 536 // Choose a sort order that puts us at the start of the list! 537 $sortinfo = get_record_sql('SELECT MIN(sortorder) AS min, 538 MAX(sortorder) AS max 539 FROM ' . $CFG->prefix . 'course WHERE category<>0'); 540 if (is_object($sortinfo)) { // no courses? 541 $max = $sortinfo->max; 542 $min = $sortinfo->min; 543 unset($sortinfo); 544 $course->sortorder = $min - 1; 545 }else{ 546 $course->sortorder = 1000; 547 } 548 if($course->id = insert_record('course', addslashes_object($course))){ 549 550 // Setup the blocks 551 $page = page_create_object(PAGE_COURSE_VIEW, $course->id); 552 blocks_repopulate_page($page); // Return value not checked because you can always edit later 553 554 $section = new object(); 555 $section->course = $course->id; // Create a default section. 556 $section->section = 0; 557 $section->id = insert_record("course_sections", $section); 558 559 add_to_log(SITEID, "course", "new", "view.php?id=$course->id", "$course->fullname (ID $course->id)"); 560 561 $this->log_line("Created course $coursecode in Moodle (Moodle ID is $course->id)"); 562 }else{ 563 $this->log_line('Failed to create course '.$coursecode.' in Moodle'); 564 } 565 } 566 }elseif($recstatus==3 && ($courseid = get_field('course', 'id', 'idnumber', $coursecode))){ 567 // If course does exist, but recstatus==3 (delete), then set the course as hidden 568 set_field('course', 'visible', '0', 'id', $courseid); 569 } 570 } // End of foreach(coursecode) 571 } 572 } // End process_group_tag() 573 574 /** 575 * Process the person tag. This defines a Moodle user. 576 * @param string $tagconents The raw contents of the XML element 577 */ 578 function process_person_tag($tagcontents){ 579 global $CFG; 580 581 if(preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)){ 582 $person->idnumber = trim($matches[1]); 583 } 584 if(preg_match('{<name>.*?<n>.*?<given>(.+?)</given>.*?</n>.*?</name>}is', $tagcontents, $matches)){ 585 $person->firstname = trim($matches[1]); 586 } 587 if(preg_match('{<name>.*?<n>.*?<family>(.+?)</family>.*?</n>.*?</name>}is', $tagcontents, $matches)){ 588 $person->lastname = trim($matches[1]); 589 } 590 if(preg_match('{<userid>(.*?)</userid>}is', $tagcontents, $matches)){ 591 $person->username = trim($matches[1]); 592 } 593 if($CFG->enrol_imssourcedidfallback && trim($person->username)==''){ 594 // This is the point where we can fall back to useing the "sourcedid" if "userid" is not supplied 595 // NB We don't use an "elseif" because the tag may be supplied-but-empty 596 $person->username = $person->idnumber; 597 } 598 if(preg_match('{<email>(.*?)</email>}is', $tagcontents, $matches)){ 599 $person->email = trim($matches[1]); 600 } 601 if(preg_match('{<url>(.*?)</url>}is', $tagcontents, $matches)){ 602 $person->url = trim($matches[1]); 603 } 604 if(preg_match('{<adr>.*?<locality>(.+?)</locality>.*?</adr>}is', $tagcontents, $matches)){ 605 $person->city = trim($matches[1]); 606 } 607 if(preg_match('{<adr>.*?<country>(.+?)</country>.*?</adr>}is', $tagcontents, $matches)){ 608 $person->country = trim($matches[1]); 609 } 610 611 // Fix case of some of the fields if required 612 if($CFG->enrol_fixcaseusernames && isset($person->username)){ 613 $person->username = strtolower($person->username); 614 } 615 if($CFG->enrol_fixcasepersonalnames){ 616 if(isset($person->firstname)){ 617 $person->firstname = ucwords(strtolower($person->firstname)); 618 } 619 if(isset($person->lastname)){ 620 $person->lastname = ucwords(strtolower($person->lastname)); 621 } 622 } 623 624 $recstatus = ($this->get_recstatus($tagcontents, 'person')); 625 626 627 // Now if the recstatus is 3, we should delete the user if-and-only-if the setting for delete users is turned on 628 // In the "users" table we can do this by setting deleted=1 629 if($recstatus==3){ 630 631 if($CFG->enrol_imsdeleteusers){ // If we're allowed to delete user records 632 // Make sure their "deleted" field is set to one 633 set_field('user', 'deleted', 1, 'username', $person->username); 634 $this->log_line("Marked user record for user '$person->username' (ID number $person->idnumber) as deleted."); 635 }else{ 636 $this->log_line("Ignoring deletion request for user '$person->username' (ID number $person->idnumber)."); 637 } 638 639 }else{ // Add or update record 640 641 642 // If the user exists (matching sourcedid) then we don't need to do anything. 643 if(!get_field('user', 'id', 'idnumber', $person->idnumber) && $CFG->enrol_createnewusers){ 644 // If they don't exist and haven't a defined username, we log this as a potential problem. 645 if((!isset($person->username)) || (strlen($person->username)==0)){ 646 $this->log_line("Cannot create new user for ID # $person->idnumber - no username listed in IMS data for this person."); 647 }elseif(get_field('user', 'id', 'username', $person->username)){ 648 // If their idnumber is not registered but their user ID is, then add their idnumber to their record 649 set_field('user', 'idnumber', addslashes($person->idnumber), 'username', $person->username); 650 }else{ 651 652 // If they don't exist and they have a defined username, and $CFG->enrol_createnewusers == true, we create them. 653 $person->lang = 'manual'; //TODO: this needs more work due tu multiauth changes 654 $person->auth = $CFG->auth; 655 $person->confirmed = 1; 656 $person->timemodified = time(); 657 $person->mnethostid = $CFG->mnet_localhost_id; 658 if($id = insert_record('user', addslashes_object($person))){ 659 /* 660 Photo processing is deactivated until we hear from Moodle dev forum about modification to gdlib. 661 662 //Antoni Mas. 07/12/2005. If a photo URL is specified then we might want to load 663 // it into the user's profile. Beware that this may cause a heavy overhead on the server. 664 if($CFG->enrol_processphoto){ 665 if(preg_match('{<photo>.*?<extref>(.*?)</extref>.*?</photo>}is', $tagcontents, $matches)){ 666 $person->urlphoto = trim($matches[1]); 667 } 668 //Habilitam el flag que ens indica que el personatge t foto prpia. 669 $person->picture = 1; 670 //Llibreria creada per nosaltres mateixos. 671 require_once($CFG->dirroot.'/lib/gdlib.php'); 672 if ($usernew->picture = save_profile_image($id, $person->urlphoto,'user')) { 673 set_field('user', 'picture', $usernew->picture, 'id', $id); /// Note picture in DB 674 } 675 } 676 */ 677 $this->log_line("Created user record for user '$person->username' (ID number $person->idnumber)."); 678 }else{ 679 $this->log_line("Database error while trying to create user record for user '$person->username' (ID number $person->idnumber)."); 680 } 681 } 682 }elseif($CFG->enrol_createnewusers){ 683 $this->log_line("User record already exists for user '$person->username' (ID number $person->idnumber)."); 684 685 // Make sure their "deleted" field is set to zero. 686 set_field('user', 'deleted', 0, 'idnumber', $person->idnumber); 687 }else{ 688 $this->log_line("No user record found for '$person->username' (ID number $person->idnumber)."); 689 } 690 691 } // End of are-we-deleting-or-adding 692 693 } // End process_person_tag() 694 695 /** 696 * Process the membership tag. This defines whether the specified Moodle users 697 * should be added/removed as teachers/students. 698 * @param string $tagconents The raw contents of the XML element 699 */ 700 function process_membership_tag($tagcontents){ 701 global $CFG; 702 $memberstally = 0; 703 $membersuntally = 0; 704 705 // In order to reduce the number of db queries required, group name/id associations are cached in this array: 706 $groupids = array(); 707 708 if(preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $tagcontents, $matches)){ 709 $ship->coursecode = ($CFG->enrol_truncatecoursecodes > 0) 710 ? substr(trim($matches[1]), 0, intval($CFG->enrol_truncatecoursecodes)) 711 : trim($matches[1]); 712 $ship->courseid = get_field('course', 'id', 'idnumber', $ship->coursecode); 713 } 714 if($ship->courseid && preg_match_all('{<member>(.*?)</member>}is', $tagcontents, $membermatches, PREG_SET_ORDER)){ 715 foreach($membermatches as $mmatch){ 716 unset($member); 717 unset($memberstoreobj); 718 if(preg_match('{<sourcedid>.*?<id>(.+?)</id>.*?</sourcedid>}is', $mmatch[1], $matches)){ 719 $member->idnumber = trim($matches[1]); 720 } 721 if(preg_match('{<role\s+roletype=["\'](.+?)["\'].*?>}is', $mmatch[1], $matches)){ 722 $member->roletype = trim($matches[1]); // 01 means Student, 02 means Instructor, 3 means ContentDeveloper, and there are more besides 723 }elseif($CFG->enrol_imscapitafix && preg_match('{<roletype>(.+?)</roletype>}is', $mmatch[1], $matches)){ 724 // The XML that comes out of Capita Student Records seems to contain a misinterpretation of the IMS specification! 725 $member->roletype = trim($matches[1]); // 01 means Student, 02 means Instructor, 3 means ContentDeveloper, and there are more besides 726 } 727 if(preg_match('{<role\b.*?<status>(.+?)</status>.*?</role>}is', $mmatch[1], $matches)){ 728 $member->status = trim($matches[1]); // 1 means active, 0 means inactive - treat this as enrol vs unenrol 729 } 730 731 $recstatus = ($this->get_recstatus($mmatch[1], 'role')); 732 if($recstatus==3){ 733 $member->status = 0; // See above - recstatus of 3 (==delete) is treated the same as status of 0 734 //echo "<p>process_membership_tag: unenrolling member due to recstatus of 3</p>"; 735 } 736 737 $timeframe->begin = 0; 738 $timeframe->end = 0; 739 if(preg_match('{<role\b.*?<timeframe>(.+?)</timeframe>.*?</role>}is', $mmatch[1], $matches)){ 740 $timeframe = $this->decode_timeframe($matches[1]); 741 } 742 if(preg_match('{<role\b.*?<extension>.*?<cohort>(.+?)</cohort>.*?</extension>.*?</role>}is', $mmatch[1], $matches)){ 743 $member->groupname = trim($matches[1]); 744 // The actual processing (ensuring a group record exists, etc) occurs below, in the enrol-a-student clause 745 } 746 747 $rolecontext = get_context_instance(CONTEXT_COURSE, $ship->courseid); 748 $rolecontext = $rolecontext->id; // All we really want is the ID 749 //$this->log_line("Context instance for course $ship->courseid is..."); 750 //print_r($rolecontext); 751 752 // Add or remove this student or teacher to the course... 753 $memberstoreobj->userid = get_field('user', 'id', 'idnumber', $member->idnumber); 754 $memberstoreobj->enrol = 'imsenterprise'; 755 $memberstoreobj->course = $ship->courseid; 756 $memberstoreobj->time = time(); 757 $memberstoreobj->timemodified = time(); 758 if($memberstoreobj->userid){ 759 760 // Decide the "real" role (i.e. the Moodle role) that this user should be assigned to. 761 // Zero means this roletype is supposed to be skipped. 762 $moodleroleid = $this->rolemappings[$member->roletype]; 763 if(!$moodleroleid){ 764 $this->log_line("SKIPPING role $member->roletype for $memberstoreobj->userid ($member->idnumber) in course $memberstoreobj->course"); 765 continue; 766 } 767 768 if(intval($member->status) == 1){ 769 770 // Enrol unsing the generic role_assign() function 771 772 if ((!role_assign($moodleroleid, $memberstoreobj->userid, 0, $rolecontext, $timeframe->begin, $timeframe->end, 0, 'imsenterprise')) && (trim($memberstoreobj->userid)!='')) { 773 $this->log_line("Error enrolling user #$memberstoreobj->userid ($member->idnumber) to role $member->roletype in course $memberstoreobj->course"); 774 }else{ 775 $this->log_line("Enrolled user #$memberstoreobj->userid ($member->idnumber) to role $member->roletype in course $memberstoreobj->course"); 776 $memberstally++; 777 778 // At this point we can also ensure the group membership is recorded if present 779 if(isset($member->groupname)){ 780 // Create the group if it doesn't exist - either way, make sure we know the group ID 781 if(isset($groupids[$member->groupname])){ 782 $member->groupid = $groupids[$member->groupname]; // Recall the group ID from cache if available 783 }else{ 784 if($groupid = get_field('groups', 'id', 'name', addslashes($member->groupname), 'courseid', $ship->courseid)){ 785 $member->groupid = $groupid; 786 $groupids[$member->groupname] = $groupid; // Store ID in cache 787 }else{ 788 // Attempt to create the group 789 $group->name = addslashes($member->groupname); 790 $group->courseid = $ship->courseid; 791 $group->timecreated = time(); 792 $group->timemodified = time(); 793 $groupid = insert_record('groups', $group); 794 $this->log_line('Added a new group for this course: '.$group->name); 795 $groupids[$member->groupname] = $groupid; // Store ID in cache 796 $member->groupid = $groupid; 797 } 798 } 799 // Add the user-to-group association if it doesn't already exist 800 if($member->groupid) { 801 groups_add_member($member->groupid, $memberstoreobj->userid); 802 } 803 } // End of group-enrolment (from member.role.extension.cohort tag) 804 805 } 806 }elseif($CFG->enrol_imsunenrol){ 807 // Unenrol 808 809 if (! role_unassign($moodleroleid, $memberstoreobj->userid, 0, $rolecontext, 'imsenterprise')) { 810 $this->log_line("Error unenrolling $memberstoreobj->userid from role $moodleroleid in course"); 811 }else{ 812 $membersuntally++; 813 $this->log_line("Unenrolled $member->idnumber from role $moodleroleid in course"); 814 } 815 } 816 817 } 818 } 819 $this->log_line("Added $memberstally users to course $ship->coursecode"); 820 if($membersuntally > 0){ 821 $this->log_line("Removed $membersuntally users from course $ship->coursecode"); 822 } 823 } 824 } // End process_membership_tag() 825 826 /** 827 * Process the properties tag. The only data from this element 828 * that is relevant is whether a <target> is specified. 829 * @param string $tagconents The raw contents of the XML element 830 */ 831 function process_properties_tag($tagcontents){ 832 global $CFG; 833 834 if($CFG->enrol_imsrestricttarget){ 835 if(!(preg_match('{<target>'.preg_quote($CFG->enrol_imsrestricttarget).'</target>}is', $tagcontents, $matches))){ 836 $this->log_line("Skipping processing: required target \"$CFG->enrol_imsrestricttarget\" not specified in this data."); 837 $this->continueprocessing = false; 838 } 839 } 840 } 841 842 /** 843 * Store logging information. This does two things: uses the {@link mtrace()} 844 * function to print info to screen/STDOUT, and also writes log to a text file 845 * if a path has been specified. 846 * @param string $string Text to write (newline will be added automatically) 847 */ 848 function log_line($string){ 849 mtrace($string); 850 if($this->logfp) { 851 fwrite($this->logfp, $string . "\n"); 852 } 853 } 854 855 /** 856 * Process the INNER contents of a <timeframe> tag, to return beginning/ending dates. 857 */ 858 function decode_timeframe($string){ // Pass me the INNER CONTENTS of a <timeframe> tag - beginning and/or ending is returned, in unix time, zero indicating not specified 859 $ret->begin = $ret->end = 0; 860 // Explanatory note: The matching will ONLY match if the attribute restrict="1" 861 // because otherwise the time markers should be ignored (participation should be 862 // allowed outside the period) 863 if(preg_match('{<begin\s+restrict="1">(\d\d\d\d)-(\d\d)-(\d\d)</begin>}is', $string, $matches)){ 864 $ret->begin = mktime(0,0,0, $matches[2], $matches[3], $matches[1]); 865 } 866 if(preg_match('{<end\s+restrict="1">(\d\d\d\d)-(\d\d)-(\d\d)</end>}is', $string, $matches)){ 867 $ret->end = mktime(0,0,0, $matches[2], $matches[3], $matches[1]); 868 } 869 return $ret; 870 } // End decode_timeframe 871 872 /** 873 * Load the role mappings (from the config), so we can easily refer to 874 * how an IMS-E role corresponds to a Moodle role 875 */ 876 function load_role_mappings() { 877 $this->rolemappings = array(); 878 foreach($this->imsroles as $imsrolenum=>$imsrolename) { 879 $this->rolemappings[$imsrolenum] = $this->rolemappings[$imsrolename] 880 = get_field('config', 'value', 'name', 'enrol_imse_imsrolemap' . $imsrolenum); 881 } 882 } 883 884 } // end of class 885 886 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |