[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/enrol/imsenterprise/ -> enrol.php (source)

   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  ?>


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7