Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   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   * Flatfile enrolment plugin.
  19   *
  20   * This plugin lets the user specify a "flatfile" (CSV) containing enrolment information.
  21   * On a regular cron cycle, the specified file is parsed and then deleted.
  22   *
  23   * @package    enrol_flatfile
  24   * @copyright  2010 Eugene Venter
  25   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  
  31  /**
  32   * Flatfile enrolment plugin implementation.
  33   *
  34   * Comma separated file assumed to have four or six fields per line:
  35   *   operation, role, idnumber(user), idnumber(course) [, starttime [, endtime]]
  36   * where:
  37   *   operation        = add | del
  38   *   role             = student | teacher | teacheredit
  39   *   idnumber(user)   = idnumber in the user table NB not id
  40   *   idnumber(course) = idnumber in the course table NB not id
  41   *   starttime        = start time (in seconds since epoch) - optional
  42   *   endtime          = end time (in seconds since epoch) - optional
  43   *
  44   * @author  Eugene Venter - based on code by Petr Skoda, Martin Dougiamas, Martin Langhoff and others
  45   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  46   */
  47  class enrol_flatfile_plugin extends enrol_plugin {
  48      protected $lasternoller = null;
  49      protected $lasternollercourseid = 0;
  50  
  51      /**
  52       * Does this plugin assign protected roles are can they be manually removed?
  53       * @return bool - false means anybody may tweak roles, it does not use itemid and component when assigning roles
  54       */
  55      public function roles_protected() {
  56          return false;
  57      }
  58  
  59      /**
  60       * Does this plugin allow manual unenrolment of all users?
  61       * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
  62       *
  63       * @param stdClass $instance course enrol instance
  64       * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely, false means nobody may touch user_enrolments
  65       */
  66      public function allow_unenrol(stdClass $instance) {
  67          return true;
  68      }
  69  
  70      /**
  71       * Does this plugin allow manual unenrolment of a specific user?
  72       * All plugins allowing this must implement 'enrol/xxx:unenrol' capability
  73       *
  74       * This is useful especially for synchronisation plugins that
  75       * do suspend instead of full unenrolment.
  76       *
  77       * @param stdClass $instance course enrol instance
  78       * @param stdClass $ue record from user_enrolments table, specifies user
  79       *
  80       * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
  81       */
  82      public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
  83          return true;
  84      }
  85  
  86      /**
  87       * Does this plugin allow manual changes in user_enrolments table?
  88       *
  89       * All plugins allowing this must implement 'enrol/xxx:manage' capability
  90       *
  91       * @param stdClass $instance course enrol instance
  92       * @return bool - true means it is possible to change enrol period and status in user_enrolments table
  93       */
  94      public function allow_manage(stdClass $instance) {
  95          return true;
  96      }
  97  
  98      /**
  99       * Is it possible to delete enrol instance via standard UI?
 100       *
 101       * @param object $instance
 102       * @return bool
 103       */
 104      public function can_delete_instance($instance) {
 105          $context = context_course::instance($instance->courseid);
 106          return has_capability('enrol/flatfile:manage', $context);
 107      }
 108  
 109      /**
 110       * Is it possible to hide/show enrol instance via standard UI?
 111       *
 112       * @param stdClass $instance
 113       * @return bool
 114       */
 115      public function can_hide_show_instance($instance) {
 116          $context = context_course::instance($instance->courseid);
 117          return has_capability('enrol/flatfile:manage', $context);
 118      }
 119  
 120      /**
 121       * Enrol user into course via enrol instance.
 122       *
 123       * @param stdClass $instance
 124       * @param int $userid
 125       * @param int $roleid optional role id
 126       * @param int $timestart 0 means unknown
 127       * @param int $timeend 0 means forever
 128       * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
 129       * @param bool $recovergrades restore grade history
 130       * @return void
 131       */
 132      public function enrol_user(stdClass $instance, $userid, $roleid = null, $timestart = 0, $timeend = 0, $status = null, $recovergrades = null) {
 133          parent::enrol_user($instance, $userid, null, $timestart, $timeend, $status, $recovergrades);
 134          if ($roleid) {
 135              $context = context_course::instance($instance->courseid, MUST_EXIST);
 136              role_assign($roleid, $userid, $context->id, 'enrol_'.$this->get_name(), $instance->id);
 137          }
 138      }
 139  
 140      /**
 141       * Execute synchronisation.
 142       * @param progress_trace
 143       * @return int exit code, 0 means ok, 2 means plugin disabled
 144       */
 145      public function sync(progress_trace $trace) {
 146          if (!enrol_is_enabled('flatfile')) {
 147              return 2;
 148          }
 149  
 150          $mailadmins = $this->get_config('mailadmins', 0);
 151  
 152          if ($mailadmins) {
 153              $buffer = new progress_trace_buffer(new text_progress_trace(), false);
 154              $trace = new combined_progress_trace(array($trace, $buffer));
 155          }
 156  
 157          $processed = false;
 158  
 159          $processed = $this->process_file($trace) || $processed;
 160          $processed = $this->process_buffer($trace) || $processed;
 161          $processed = $this->process_expirations($trace) || $processed;
 162  
 163          if ($processed and $mailadmins) {
 164              if ($log = $buffer->get_buffer()) {
 165                  $eventdata = new \core\message\message();
 166                  $eventdata->courseid          = SITEID;
 167                  $eventdata->modulename        = 'moodle';
 168                  $eventdata->component         = 'enrol_flatfile';
 169                  $eventdata->name              = 'flatfile_enrolment';
 170                  $eventdata->userfrom          = get_admin();
 171                  $eventdata->userto            = get_admin();
 172                  $eventdata->subject           = 'Flatfile Enrolment Log';
 173                  $eventdata->fullmessage       = $log;
 174                  $eventdata->fullmessageformat = FORMAT_PLAIN;
 175                  $eventdata->fullmessagehtml   = '';
 176                  $eventdata->smallmessage      = '';
 177                  message_send($eventdata);
 178              }
 179              $buffer->reset_buffer();
 180          }
 181  
 182          return 0;
 183      }
 184  
 185      /**
 186       * Sorry, we do not want to show paths in cron output.
 187       *
 188       * @param string $filepath
 189       * @return string
 190       */
 191      protected function obfuscate_filepath($filepath) {
 192          global $CFG;
 193  
 194          if (strpos($filepath, $CFG->dataroot.'/') === 0 or strpos($filepath, $CFG->dataroot.'\\') === 0) {
 195              $disclosefile = '$CFG->dataroot'.substr($filepath, strlen($CFG->dataroot));
 196  
 197          } else if (strpos($filepath, $CFG->dirroot.'/') === 0 or strpos($filepath, $CFG->dirroot.'\\') === 0) {
 198              $disclosefile = '$CFG->dirroot'.substr($filepath, strlen($CFG->dirroot));
 199  
 200          } else {
 201              $disclosefile = basename($filepath);
 202          }
 203  
 204          return $disclosefile;
 205      }
 206  
 207      /**
 208       * Process flatfile.
 209       * @param progress_trace $trace
 210       * @return bool true if any data processed, false if not
 211       */
 212      protected function process_file(progress_trace $trace) {
 213          global $CFG, $DB;
 214  
 215          // We may need more memory here.
 216          core_php_time_limit::raise();
 217          raise_memory_limit(MEMORY_HUGE);
 218  
 219          $filelocation = $this->get_config('location');
 220          if (empty($filelocation)) {
 221              // Default legacy location.
 222              $filelocation = "$CFG->dataroot/1/enrolments.txt";
 223          }
 224          $disclosefile = $this->obfuscate_filepath($filelocation);
 225  
 226          if (!file_exists($filelocation)) {
 227              $trace->output("Flatfile enrolments file not found: $disclosefile");
 228              $trace->finished();
 229              return false;
 230          }
 231          $trace->output("Processing flat file enrolments from: $disclosefile ...");
 232  
 233          $content = file_get_contents($filelocation);
 234  
 235          if ($content !== false) {
 236  
 237              $rolemap = $this->get_role_map($trace);
 238  
 239              $content = core_text::convert($content, $this->get_config('encoding', 'utf-8'), 'utf-8');
 240              $content = str_replace("\r", '', $content);
 241              $content = explode("\n", $content);
 242  
 243              $line = 0;
 244              foreach($content as $fields) {
 245                  $line++;
 246  
 247                  if (trim($fields) === '') {
 248                      // Empty lines are ignored.
 249                      continue;
 250                  }
 251  
 252                  // Deal with different separators.
 253                  if (strpos($fields, ',') !== false) {
 254                      $fields = explode(',', $fields);
 255                  } else {
 256                      $fields = explode(';', $fields);
 257                  }
 258  
 259                  // If a line is incorrectly formatted ie does not have 4 comma separated fields then ignore it.
 260                  if (count($fields) < 4 or count($fields) > 6) {
 261                      $trace->output("Line incorrectly formatted - ignoring $line", 1);
 262                      continue;
 263                  }
 264  
 265                  $fields[0] = trim(core_text::strtolower($fields[0]));
 266                  $fields[1] = trim(core_text::strtolower($fields[1]));
 267                  $fields[2] = trim($fields[2]);
 268                  $fields[3] = trim($fields[3]);
 269                  $fields[4] = isset($fields[4]) ? (int)trim($fields[4]) : 0;
 270                  $fields[5] = isset($fields[5]) ? (int)trim($fields[5]) : 0;
 271  
 272                  // Deal with quoted values - all or nothing, we need to support "' in idnumbers, sorry.
 273                  if (strpos($fields[0], "'") === 0) {
 274                      foreach ($fields as $k=>$v) {
 275                          $fields[$k] = trim($v, "'");
 276                      }
 277                  } else if (strpos($fields[0], '"') === 0) {
 278                      foreach ($fields as $k=>$v) {
 279                          $fields[$k] = trim($v, '"');
 280                      }
 281                  }
 282  
 283                  $trace->output("$line: $fields[0], $fields[1], $fields[2], $fields[3], $fields[4], $fields[5]", 1);
 284  
 285                  // Check correct formatting of operation field.
 286                  if ($fields[0] !== "add" and $fields[0] !== "del") {
 287                      $trace->output("Unknown operation in field 1 - ignoring line $line", 1);
 288                      continue;
 289                  }
 290  
 291                  // Check correct formatting of role field.
 292                  if (!isset($rolemap[$fields[1]])) {
 293                      $trace->output("Unknown role in field2 - ignoring line $line", 1);
 294                      continue;
 295                  }
 296                  $roleid = $rolemap[$fields[1]];
 297  
 298                  if (empty($fields[2]) or !$user = $DB->get_record("user", array("idnumber"=>$fields[2], 'deleted'=>0))) {
 299                      $trace->output("Unknown user idnumber or deleted user in field 3 - ignoring line $line", 1);
 300                      continue;
 301                  }
 302  
 303                  if (!$course = $DB->get_record("course", array("idnumber"=>$fields[3]))) {
 304                      $trace->output("Unknown course idnumber in field 4 - ignoring line $line", 1);
 305                      continue;
 306                  }
 307  
 308                  if ($fields[4] > $fields[5] and $fields[5] != 0) {
 309                      $trace->output("Start time was later than end time - ignoring line $line", 1);
 310                      continue;
 311                  }
 312  
 313                  $this->process_records($trace, $fields[0], $roleid, $user, $course, $fields[4], $fields[5]);
 314              }
 315  
 316              unset($content);
 317          }
 318  
 319          if (!unlink($filelocation)) {
 320              $eventdata = new \core\message\message();
 321              $eventdata->courseid          = SITEID;
 322              $eventdata->modulename        = 'moodle';
 323              $eventdata->component         = 'enrol_flatfile';
 324              $eventdata->name              = 'flatfile_enrolment';
 325              $eventdata->userfrom          = get_admin();
 326              $eventdata->userto            = get_admin();
 327              $eventdata->subject           = get_string('filelockedmailsubject', 'enrol_flatfile');
 328              $eventdata->fullmessage       = get_string('filelockedmail', 'enrol_flatfile', $filelocation);
 329              $eventdata->fullmessageformat = FORMAT_PLAIN;
 330              $eventdata->fullmessagehtml   = '';
 331              $eventdata->smallmessage      = '';
 332              message_send($eventdata);
 333              $trace->output("Error deleting enrolment file: $disclosefile", 1);
 334          } else {
 335              $trace->output("Deleted enrolment file", 1);
 336          }
 337  
 338          $trace->output("...finished enrolment file processing.");
 339          $trace->finished();
 340  
 341          return true;
 342      }
 343  
 344      /**
 345       * Process any future enrollments stored in the buffer.
 346       * @param progress_trace $trace
 347       * @return bool true if any data processed, false if not
 348       */
 349      protected function process_buffer(progress_trace $trace) {
 350          global $DB;
 351  
 352          if (!$future_enrols = $DB->get_records_select('enrol_flatfile', "timestart < ?", array(time()))) {
 353              $trace->output("No enrolments to be processed in flatfile buffer");
 354              $trace->finished();
 355              return false;
 356          }
 357  
 358          $trace->output("Starting processing of flatfile buffer");
 359          foreach($future_enrols as $en) {
 360              $user = $DB->get_record('user', array('id'=>$en->userid));
 361              $course = $DB->get_record('course', array('id'=>$en->courseid));
 362              if ($user and $course) {
 363                  $trace->output("buffer: $en->action $en->roleid $user->id $course->id $en->timestart $en->timeend", 1);
 364                  $this->process_records($trace, $en->action, $en->roleid, $user, $course, $en->timestart, $en->timeend, false);
 365              }
 366              $DB->delete_records('enrol_flatfile', array('id'=>$en->id));
 367          }
 368          $trace->output("Finished processing of flatfile buffer");
 369          $trace->finished();
 370  
 371          return true;
 372      }
 373  
 374      /**
 375       * Process user enrolment line.
 376       *
 377       * @param progress_trace $trace
 378       * @param string $action
 379       * @param int $roleid
 380       * @param stdClass $user
 381       * @param stdClass $course
 382       * @param int $timestart
 383       * @param int $timeend
 384       * @param bool $buffer_if_future
 385       */
 386      protected function process_records(progress_trace $trace, $action, $roleid, $user, $course, $timestart, $timeend, $buffer_if_future = true) {
 387          global $CFG, $DB;
 388  
 389          // Check if timestart is for future processing.
 390          if ($timestart > time() and $buffer_if_future) {
 391              // Populate into enrol_flatfile table as a future role to be assigned by cron.
 392              // Note: since 2.0 future enrolments do not cause problems if you disable guest access.
 393              $future_en = new stdClass();
 394              $future_en->action       = $action;
 395              $future_en->roleid       = $roleid;
 396              $future_en->userid       = $user->id;
 397              $future_en->courseid     = $course->id;
 398              $future_en->timestart    = $timestart;
 399              $future_en->timeend      = $timeend;
 400              $future_en->timemodified = time();
 401              $DB->insert_record('enrol_flatfile', $future_en);
 402              $trace->output("User $user->id will be enrolled later into course $course->id using role $roleid ($timestart, $timeend)", 1);
 403              return;
 404          }
 405  
 406          $context = context_course::instance($course->id);
 407  
 408          if ($action === 'add') {
 409              // Clear the buffer just in case there were some future enrolments.
 410              $DB->delete_records('enrol_flatfile', array('userid'=>$user->id, 'courseid'=>$course->id, 'roleid'=>$roleid));
 411  
 412              $instance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'flatfile'));
 413              if (empty($instance)) {
 414                  // Only add an enrol instance to the course if non-existent.
 415                  $enrolid = $this->add_instance($course);
 416                  $instance = $DB->get_record('enrol', array('id' => $enrolid));
 417              }
 418  
 419              $notify = false;
 420              if ($ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$user->id))) {
 421                  // Update only.
 422                  $this->update_user_enrol($instance, $user->id, ENROL_USER_ACTIVE, $timestart, $timeend);
 423                  if (!$DB->record_exists('role_assignments', array('contextid'=>$context->id, 'roleid'=>$roleid, 'userid'=>$user->id, 'component'=>'enrol_flatfile', 'itemid'=>$instance->id))) {
 424                      role_assign($roleid, $user->id, $context->id, 'enrol_flatfile', $instance->id);
 425                  }
 426                  $trace->output("User $user->id enrolment updated in course $course->id using role $roleid ($timestart, $timeend)", 1);
 427  
 428              } else {
 429                  // Enrol the user with this plugin instance.
 430                  $this->enrol_user($instance, $user->id, $roleid, $timestart, $timeend);
 431                  $trace->output("User $user->id enrolled in course $course->id using role $roleid ($timestart, $timeend)", 1);
 432                  $notify = true;
 433              }
 434  
 435              if ($notify and $this->get_config('mailstudents')) {
 436                  $oldforcelang = force_current_language($user->lang);
 437  
 438                  // Send welcome notification to enrolled users.
 439                  $a = new stdClass();
 440                  $a->coursename = format_string($course->fullname, true, array('context' => $context));
 441                  $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&amp;course=$course->id";
 442                  $subject = get_string('enrolmentnew', 'enrol', format_string($course->shortname, true, array('context' => $context)));
 443  
 444                  $eventdata = new \core\message\message();
 445                  $eventdata->courseid          = $course->id;
 446                  $eventdata->modulename        = 'moodle';
 447                  $eventdata->component         = 'enrol_flatfile';
 448                  $eventdata->name              = 'flatfile_enrolment';
 449                  $eventdata->userfrom          = $this->get_enroller($course->id);
 450                  $eventdata->userto            = $user;
 451                  $eventdata->subject           = $subject;
 452                  $eventdata->fullmessage       = get_string('welcometocoursetext', '', $a);
 453                  $eventdata->fullmessageformat = FORMAT_PLAIN;
 454                  $eventdata->fullmessagehtml   = '';
 455                  $eventdata->smallmessage      = '';
 456                  if (message_send($eventdata)) {
 457                      $trace->output("Notified enrolled user", 1);
 458                  } else {
 459                      $trace->output("Failed to notify enrolled user", 1);
 460                  }
 461  
 462                  force_current_language($oldforcelang);
 463              }
 464  
 465              if ($notify and $this->get_config('mailteachers', 0)) {
 466                  // Notify person responsible for enrolments.
 467                  $enroller = $this->get_enroller($course->id);
 468  
 469                  $oldforcelang = force_current_language($enroller->lang);
 470  
 471                  $a = new stdClass();
 472                  $a->course = format_string($course->fullname, true, array('context' => $context));
 473                  $a->user = fullname($user);
 474                  $subject = get_string('enrolmentnew', 'enrol', format_string($course->shortname, true, array('context' => $context)));
 475  
 476                  $eventdata = new \core\message\message();
 477                  $eventdata->courseid          = $course->id;
 478                  $eventdata->modulename        = 'moodle';
 479                  $eventdata->component         = 'enrol_flatfile';
 480                  $eventdata->name              = 'flatfile_enrolment';
 481                  $eventdata->userfrom          = get_admin();
 482                  $eventdata->userto            = $enroller;
 483                  $eventdata->subject           = $subject;
 484                  $eventdata->fullmessage       = get_string('enrolmentnewuser', 'enrol', $a);
 485                  $eventdata->fullmessageformat = FORMAT_PLAIN;
 486                  $eventdata->fullmessagehtml   = '';
 487                  $eventdata->smallmessage      = '';
 488                  if (message_send($eventdata)) {
 489                      $trace->output("Notified enroller {$eventdata->userto->id}", 1);
 490                  } else {
 491                      $trace->output("Failed to notify enroller {$eventdata->userto->id}", 1);
 492                  }
 493  
 494                  force_current_language($oldforcelang);
 495              }
 496              return;
 497  
 498          } else if ($action === 'del') {
 499              // Clear the buffer just in case there were some future enrolments.
 500              $DB->delete_records('enrol_flatfile', array('userid'=>$user->id, 'courseid'=>$course->id, 'roleid'=>$roleid));
 501  
 502              $action = $this->get_config('unenrolaction');
 503              if ($action == ENROL_EXT_REMOVED_KEEP) {
 504                  $trace->output("del action is ignored", 1);
 505                  return;
 506              }
 507  
 508              // Loops through all enrolment methods, try to unenrol if roleid somehow matches.
 509              $instances = $DB->get_records('enrol', array('courseid' => $course->id));
 510              $unenrolled = false;
 511              foreach ($instances as $instance) {
 512                  if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$user->id))) {
 513                      continue;
 514                  }
 515                  if ($instance->enrol === 'flatfile') {
 516                      $plugin = $this;
 517                  } else {
 518                      if (!enrol_is_enabled($instance->enrol)) {
 519                          continue;
 520                      }
 521                      if (!$plugin = enrol_get_plugin($instance->enrol)) {
 522                          continue;
 523                      }
 524                      if (!$plugin->allow_unenrol_user($instance, $ue)) {
 525                          continue;
 526                      }
 527                  }
 528  
 529                  // For some reason the del action includes a role name, this complicates everything.
 530                  $componentroles = array();
 531                  $manualroles = array();
 532                  $ras = $DB->get_records('role_assignments', array('userid'=>$user->id, 'contextid'=>$context->id));
 533                  foreach ($ras as $ra) {
 534                      if ($ra->component === '') {
 535                          $manualroles[$ra->roleid] = $ra->roleid;
 536                      } else if ($ra->component === 'enrol_'.$instance->enrol and $ra->itemid == $instance->id) {
 537                          $componentroles[$ra->roleid] = $ra->roleid;
 538                      }
 539                  }
 540  
 541                  if ($componentroles and !isset($componentroles[$roleid])) {
 542                      // Do not unenrol using this method, user has some other protected role!
 543                      continue;
 544  
 545                  } else if (empty($ras)) {
 546                      // If user does not have any roles then let's just suspend as many methods as possible.
 547  
 548                  } else if (!$plugin->roles_protected()) {
 549                      if (!$componentroles and $manualroles and !isset($manualroles[$roleid])) {
 550                          // Most likely we want to keep users enrolled because they have some other course roles.
 551                          continue;
 552                      }
 553                  }
 554  
 555                  if ($action == ENROL_EXT_REMOVED_UNENROL) {
 556                      $unenrolled = true;
 557                      if (!$plugin->roles_protected()) {
 558                          role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'roleid'=>$roleid, 'component'=>'', 'itemid'=>0), true);
 559                      }
 560                      $plugin->unenrol_user($instance, $user->id);
 561                      $trace->output("User $user->id was unenrolled from course $course->id (enrol_$instance->enrol)", 1);
 562  
 563                  } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
 564                      if ($plugin->allow_manage($instance)) {
 565                          if ($ue->status == ENROL_USER_ACTIVE) {
 566                              $unenrolled = true;
 567                              $plugin->update_user_enrol($instance, $user->id, ENROL_USER_SUSPENDED);
 568                              if (!$plugin->roles_protected()) {
 569                                  role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_'.$instance->enrol, 'itemid'=>$instance->id), true);
 570                                  role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'roleid'=>$roleid, 'component'=>'', 'itemid'=>0), true);
 571                              }
 572                              $trace->output("User $user->id enrolment was suspended in course $course->id (enrol_$instance->enrol)", 1);
 573                          }
 574                      }
 575                  }
 576              }
 577  
 578              if (!$unenrolled) {
 579                  if (0 == $DB->count_records('role_assignments', array('userid'=>$user->id, 'contextid'=>$context->id))) {
 580                      role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'', 'itemid'=>0), true);
 581                  }
 582                  $trace->output("User $user->id (with role $roleid) not unenrolled from course $course->id", 1);
 583              }
 584  
 585              return;
 586          }
 587      }
 588  
 589      /**
 590       * Returns the user who is responsible for flatfile enrolments in given curse.
 591       *
 592       * Usually it is the first editing teacher - the person with "highest authority"
 593       * as defined by sort_by_roleassignment_authority() having 'enrol/flatfile:manage'
 594       * or 'moodle/role:assign' capability.
 595       *
 596       * @param int $courseid enrolment instance id
 597       * @return stdClass user record
 598       */
 599      protected function get_enroller($courseid) {
 600          if ($this->lasternollercourseid == $courseid and $this->lasternoller) {
 601              return $this->lasternoller;
 602          }
 603  
 604          $context = context_course::instance($courseid);
 605  
 606          $users = get_enrolled_users($context, 'enrol/flatfile:manage');
 607          if (!$users) {
 608              $users = get_enrolled_users($context, 'moodle/role:assign');
 609          }
 610  
 611          if ($users) {
 612              $users = sort_by_roleassignment_authority($users, $context);
 613              $this->lasternoller = reset($users);
 614              unset($users);
 615          } else {
 616              $this->lasternoller = get_admin();
 617          }
 618  
 619          $this->lasternollercourseid == $courseid;
 620  
 621          return $this->lasternoller;
 622      }
 623  
 624      /**
 625       * Returns a mapping of ims roles to role ids.
 626       *
 627       * @param progress_trace $trace
 628       * @return array imsrolename=>roleid
 629       */
 630      protected function get_role_map(progress_trace $trace) {
 631          global $DB;
 632  
 633          // Get all roles.
 634          $rolemap = array();
 635          $roles = $DB->get_records('role', null, '', 'id, name, shortname');
 636          foreach ($roles as $id=>$role) {
 637              $alias = $this->get_config('map_'.$id, $role->shortname, '');
 638              $alias = trim(core_text::strtolower($alias));
 639              if ($alias === '') {
 640                  // Either not configured yet or somebody wants to skip these intentionally.
 641                  continue;
 642              }
 643              if (isset($rolemap[$alias])) {
 644                  $trace->output("Duplicate role alias $alias detected!");
 645              } else {
 646                  $rolemap[$alias] = $id;
 647              }
 648          }
 649  
 650          return $rolemap;
 651      }
 652  
 653      /**
 654       * Restore instance and map settings.
 655       *
 656       * @param restore_enrolments_structure_step $step
 657       * @param stdClass $data
 658       * @param stdClass $course
 659       * @param int $oldid
 660       */
 661      public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
 662          global $DB;
 663  
 664          if ($instance = $DB->get_record('enrol', array('courseid'=>$course->id, 'enrol'=>$this->get_name()))) {
 665              $instanceid = $instance->id;
 666          } else {
 667              $instanceid = $this->add_instance($course);
 668          }
 669          $step->set_mapping('enrol', $oldid, $instanceid);
 670      }
 671  
 672      /**
 673       * Restore user enrolment.
 674       *
 675       * @param restore_enrolments_structure_step $step
 676       * @param stdClass $data
 677       * @param stdClass $instance
 678       * @param int $oldinstancestatus
 679       * @param int $userid
 680       */
 681      public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
 682          $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status);
 683      }
 684  
 685      /**
 686       * Restore role assignment.
 687       *
 688       * @param stdClass $instance
 689       * @param int $roleid
 690       * @param int $userid
 691       * @param int $contextid
 692       */
 693      public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
 694          role_assign($roleid, $userid, $contextid, 'enrol_'.$instance->enrol, $instance->id);
 695      }
 696  }