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  namespace enrol_imsenterprise;
  18  
  19  use core_course_category;
  20  use enrol_imsenterprise_plugin;
  21  use stdClass;
  22  
  23  defined('MOODLE_INTERNAL') || die();
  24  
  25  global $CFG;
  26  require_once($CFG->dirroot . '/enrol/imsenterprise/locallib.php');
  27  require_once($CFG->dirroot . '/enrol/imsenterprise/lib.php');
  28  
  29  /**
  30   * IMS Enterprise test case
  31   *
  32   * @package    enrol_imsenterprise
  33   * @category   test
  34   * @copyright  2019 Segun Babalola
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   *
  37   * @covers \enrol_imsenterprise_plugin
  38   */
  39  class imsenterprise_unenrol_test extends \advanced_testcase {
  40  
  41      /**
  42       * @var $imsplugin enrol_imsenterprise_plugin IMS plugin instance.
  43       */
  44      public $imsplugin;
  45  
  46      /**
  47       * Setup required for all tests.
  48       */
  49      protected function setUp(): void {
  50          $this->resetAfterTest(true);
  51          $this->imsplugin = enrol_get_plugin('imsenterprise');
  52          $this->set_test_config();
  53      }
  54  
  55      /**
  56       * Sets the plugin configuration for testing
  57       */
  58      public function set_test_config() {
  59          $this->imsplugin->set_config('mailadmins', false);
  60          $this->imsplugin->set_config('prev_path', '');
  61          $this->imsplugin->set_config('createnewusers', true);
  62          $this->imsplugin->set_config('imsupdateusers', true);
  63          $this->imsplugin->set_config('createnewcourses', true);
  64          $this->imsplugin->set_config('updatecourses', true);
  65          $this->imsplugin->set_config('createnewcategories', true);
  66          $this->imsplugin->set_config('categoryseparator', '');
  67          $this->imsplugin->set_config('categoryidnumber', false);
  68          $this->imsplugin->set_config('nestedcategories', false);
  69      }
  70  
  71  
  72      /**
  73       * Creates an IMS enterprise XML file and adds it's path to config settings.
  74       *
  75       * @param bool|array $users false or array of users StdClass
  76       * @param bool|array $courses false or of courses StdClass
  77       * @param bool|array $usercoursemembership false or of courses StdClass
  78       */
  79      public function set_xml_file($users = false, $courses = false, $usercoursemembership = false) {
  80  
  81          $xmlcontent = '<enterprise>';
  82  
  83          // Users.
  84          if (!empty($users) && is_array($users)) {
  85              foreach ($users as $user) {
  86                  $xmlcontent .= '<person' . (!empty($user->recstatus) ? ' recstatus="'.$user->recstatus.'"' : '').'>';
  87                  $xmlcontent .= '<sourcedid><source>TestSource</source><id>'.$user->idnumber.'</id></sourcedid>';
  88                  $xmlcontent .= '<userid' . (!empty($user->auth) ? ' authenticationtype="'.$user->auth.'"' : '');
  89                  $xmlcontent .= '>'.$user->username.'</userid>';
  90                  $xmlcontent .= '<name>'
  91                                  .'<fn>'.$user->firstname.' '.$user->lastname.'</fn>'
  92                                  .'<n><family>'.$user->lastname.'</family><given>'.$user->firstname.'</given></n>'
  93                                  .'</name>'
  94                                  .'<email>'.$user->email.'</email>';
  95                  $xmlcontent .= '</person>';
  96              }
  97          }
  98  
  99          // Courses.
 100          // Mapping based on default course attributes - IMS group tags mapping.
 101          if (!empty($courses) && is_array($courses)) {
 102              foreach ($courses as $course) {
 103  
 104                  $xmlcontent .= '<group' . (!empty($course->recstatus) ? ' recstatus="'.$course->recstatus.'"' : '').'>';
 105                  $xmlcontent .= '<sourcedid><source>TestSource</source><id>'.$course->idnumber.'</id></sourcedid>';
 106                  $xmlcontent .= '<description>'.(!empty($course->imsshort) ? '<short>'.$course->imsshort.'</short>' : '');
 107                  $xmlcontent .= (!empty($course->imslong) ? '<long>'.$course->imslong.'</long>' : '');
 108                  $xmlcontent .= (!empty($course->imsfull) ? '<full>'.$course->imsfull.'</full>' : '');
 109                  $xmlcontent .= '</description>';
 110  
 111                  // The orgunit tag value is used by moodle as category name.
 112                  $xmlcontent .= '<org>';
 113  
 114                  // Optional category name.
 115                  if (isset($course->category)) {
 116                      if (is_array($course->category)) {
 117                          foreach ($course->category as $category) {
 118                              $xmlcontent .= '<orgunit>' . $category . '</orgunit>';
 119                          }
 120                      } else if (is_object($course->category)) {
 121                          $xmlcontent .= '<orgunit>' . $course->category->name . '</orgunit>';
 122                      } else if (!empty($course->category)) {
 123                          $xmlcontent .= '<orgunit>' . $course->category . '</orgunit>';
 124                      }
 125                  }
 126  
 127                  $xmlcontent .= '</org>';
 128                  $xmlcontent .= '</group>';
 129              }
 130          }
 131  
 132          // User course membership (i.e. roles and enrolments).
 133          if (!empty($usercoursemembership) && is_array($usercoursemembership)) {
 134              foreach ($usercoursemembership as $crsemship) {
 135  
 136                  // Only process records that have a source/id (i.e. course code) set in the IMS file.
 137                  // Note that we could also check that there is a corresponding $course with the course code given here,
 138                  // however it is possible that we want to test the behaviour of orphan  membership elements in future,
 139                  // so leaving the check out for now.
 140                  if (isset($crsemship->crseidnumber) && isset($crsemship->member) && is_array($crsemship->member)
 141                      && count($crsemship->member)) {
 142                      $xmlcontent .= '<membership><sourcedid><source>TestSource</source><id>'
 143                          .$crsemship->crseidnumber . '</id></sourcedid>';
 144  
 145                      foreach ($crsemship->member as $crsemember) {
 146                          if (!empty($crsemember->useridnumber)) {
 147                              $xmlcontent .= '<member>';
 148                              $xmlcontent .= '<sourcedid><source>TestSource</source><id>'. $crsemember->useridnumber
 149                                  .'</id></sourcedid>';
 150  
 151                              // Indicates whether the member is a Person (1) or another Group (2).
 152                              // We're only handling user membership here, so hard-code value of 1.
 153                              $xmlcontent .= '<idtype>1</idtype>';
 154  
 155                              if (isset($crsemember->role) && is_array($crsemember->role)) {
 156                                  foreach ($crsemember->role as $role) {
 157                                      $xmlcontent .= '<role roletype="'.$role->roletype.'" recstatus="'.$role->recstatus.'">';
 158                                      $xmlcontent .= '<userid/>';
 159                                      $xmlcontent .= '<status>' . $role->rolestatus . '</status>';
 160                                      $xmlcontent .= '</role>';
 161                                  }
 162                              }
 163  
 164                              $xmlcontent .= '</member>';
 165                          }
 166                      }
 167  
 168                      $xmlcontent .= '</membership>';
 169                  }
 170              }
 171          }
 172  
 173          $xmlcontent .= '</enterprise>';
 174  
 175          // Creating the XML file.
 176          $filename = 'ims_' . rand(1000, 9999) . '.xml';
 177          $tmpdir = make_temp_directory('enrol_imsenterprise');
 178          $xmlfilepath = $tmpdir . '/' . $filename;
 179          file_put_contents($xmlfilepath, $xmlcontent);
 180  
 181          // Setting the file path in CFG.
 182          $this->imsplugin->set_config('imsfilelocation', $xmlfilepath);
 183      }
 184  
 185      /**
 186       * Utility function for generating test user records
 187       *
 188       * @param int $numberofrecordsrequired - number of test users required
 189       * @return array of StdClass objects representing test user records
 190       */
 191      private function generate_test_user_records($numberofrecordsrequired) {
 192          $users = [];
 193          for ($i = 0; $i < $numberofrecordsrequired; $i++) {
 194              $usernumber = $i + 101;
 195              $users[] = (object)[
 196                  'recstatus' => enrol_imsenterprise_plugin::IMSENTERPRISE_ADD,
 197                  'idnumber' => $usernumber,
 198                  'username' => 'UID' .$usernumber,
 199                  'email' => 'user' . $usernumber . '@moodle.org',
 200                  'firstname' => 'User' . $usernumber . ' firstname',
 201                  'lastname' => 'User' . $usernumber . ' lastname'
 202              ];
 203          }
 204  
 205          return $users;
 206      }
 207  
 208      /**
 209       * Utility function for generating test course records
 210       *
 211       * @param int $numberofrecordsrequired - number of test course records required
 212       * @return array of StdClass objects representing test course records
 213       */
 214      private function generate_test_course_records($numberofrecordsrequired) {
 215          $courses = [];
 216          for ($i = 0; $i < $numberofrecordsrequired; $i++) {
 217              $coursenumber = $i + 101;
 218              $courses[] = (object)[
 219                  'recstatus' => enrol_imsenterprise_plugin::IMSENTERPRISE_ADD,
 220                  'idnumber' => 'CID' . $coursenumber,
 221                  'imsshort' => 'Course ' . $coursenumber,
 222                  'category' => core_course_category::get_default()
 223              ];
 224          }
 225  
 226          return $courses;
 227      }
 228  
 229      /**
 230       * Utility function for generating test membership structure for given users and courses.
 231       * Linkmatrix is expected to be in [row, col] format, where courses are rows and users are columns.
 232       * Each element of the link matrix is expected to contain <roletype>:<role status>:<role recstatus>.
 233       *
 234       * @param array $users
 235       * @param array $courses
 236       * @param array $linkmatrix - matrix/two dimensional array of required user course enrolments
 237       * @return array
 238       */
 239      private function link_users_with_courses($users, $courses, $linkmatrix) {
 240  
 241          $memberships = [];
 242  
 243          foreach ($courses as $i => $c) {
 244  
 245              $membership = new stdClass();
 246              $membership->member = [];
 247              $membership->crseidnumber = $c->idnumber;
 248  
 249              foreach ($users as $j => $u) {
 250                  if (isset($linkmatrix[$i][$j])) {
 251  
 252                      list($roletype, $rolestatus, $rolerecstatus) = explode(':', $linkmatrix[$i][$j]);
 253  
 254                      if (strlen($rolerecstatus) && strlen($roletype) && strlen($rolestatus)) {
 255                          $membership->member[] = (object)[
 256                              'useridnumber' => $u->idnumber,
 257                              'role' => [(object)[
 258                                  'roletype' => $roletype,
 259                                  'rolestatus' => $rolestatus,
 260                                  'recstatus' => $rolerecstatus
 261                              ]]
 262                          ];
 263                      }
 264                  }
 265              }
 266  
 267              $memberships[] = $membership;
 268          }
 269  
 270          return $memberships;
 271      }
 272  
 273      /**
 274       * Add new users, courses and enrolments
 275       */
 276      public function test_users_are_enroled_on_courses() {
 277          global $DB;
 278  
 279          $prevnuserenrolments = $DB->count_records('user_enrolments');
 280          $prevnusers = $DB->count_records('user');
 281          $prevncourses = $DB->count_records('course');
 282  
 283          $courses = $this->generate_test_course_records(1);
 284          $users = $this->generate_test_user_records(1);
 285          $coursemembership = $this->link_users_with_courses(
 286              $users,
 287              $courses,
 288              [
 289                  ['01:1:1'] // First course.
 290              ]
 291          );
 292  
 293          $this->set_xml_file($users, $courses, $coursemembership);
 294          $this->imsplugin->cron();
 295  
 296          $this->assertEquals(($prevnuserenrolments + 1), $DB->count_records('user_enrolments'));
 297          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 298          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 299      }
 300  
 301      /**
 302       * Check that the unenrol actions are completely ignored when "unenrol" setting is disabled
 303       */
 304      public function test_no_action_when_unenrol_disabled() {
 305          global $DB;
 306  
 307          $prevnuserenrolments = $DB->count_records('user_enrolments');
 308          $prevnusers = $DB->count_records('user');
 309          $prevncourses = $DB->count_records('course');
 310  
 311          // Create user and course.
 312          $courses = $this->generate_test_course_records(3);
 313          $users = $this->generate_test_user_records(2);
 314          $coursemembership = $this->link_users_with_courses(
 315              $users,
 316              $courses,
 317              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 318              // Role statuses: 0=Inactive, 1=Active.
 319              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 320              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 321              [
 322                  ['01:1:1', '01:1:1'], // Course 1.
 323                  ['01:1:1', '01:1:1'], // Course 2.
 324                  ['::', '01:1:1'], // Course 3.
 325              ]
 326          );
 327  
 328          $this->set_xml_file($users, $courses, $coursemembership);
 329          $this->imsplugin->cron();
 330  
 331          $this->assertEquals(($prevnuserenrolments + 5), $DB->count_records('user_enrolments'));
 332          $this->assertEquals(($prevnusers + 2), $DB->count_records('user'));
 333          $this->assertEquals(($prevncourses + 3), $DB->count_records('course'));
 334  
 335          // Disallow unenrolment, and check that unenroling has no effect.
 336          $this->imsplugin->set_config('imsunenrol', 0);
 337  
 338          $coursemembership = $this->link_users_with_courses(
 339              $users,
 340              $courses,
 341              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 342              // Role statuses: 0=Inactive, 1=Active.
 343              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 344              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 345              [
 346                  ['01:1:3', '01:1:3'], // Course 1.
 347                  ['::', '01:1:3'], // Course 2.
 348                  ['::', '01:1:3'], // Course 3.
 349              ]
 350          );
 351  
 352          $this->set_xml_file($users, $courses, $coursemembership);
 353          $this->imsplugin->cron();
 354  
 355          $this->assertEquals(($prevnuserenrolments + 5), $DB->count_records('user_enrolments'));
 356          $this->assertEquals(($prevnusers + 2), $DB->count_records('user'));
 357          $this->assertEquals(($prevncourses + 3), $DB->count_records('course'));
 358      }
 359  
 360      /**
 361       * When a user has existing roles and enrolments, they are unaffected by IMS instructions for other courses
 362       */
 363      public function test_existing_roles_and_enrolments_unaffected() {
 364  
 365          global $DB;
 366  
 367          $this->imsplugin->set_config('imsunenrol', 1);
 368          $this->imsplugin->set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
 369  
 370          $prevnuserenrolments = $DB->count_records('user_enrolments');
 371          $prevnusers = $DB->count_records('user');
 372          $prevncourses = $DB->count_records('course');
 373  
 374          $courses = $this->generate_test_course_records(2);
 375  
 376          // Create_course seems to expect the category to be passed as ID, so extract from the object.
 377          $course1 = $courses[0];
 378          $course1->category = $course1->category->id;
 379          $course1 = $this->getDataGenerator()->create_course($courses[0]);
 380  
 381          // Enrol user1 on course1.
 382          $DB->insert_record('enrol', (object)['enrol' => 'imsenterprise',
 383              'courseid' => $course1->id, 'status' => 1, 'roleid' => 5
 384          ], true);
 385  
 386          $user1 = $this->getDataGenerator()->create_and_enrol($course1, 'student',
 387              ['idnumber' => 'UserIDNumber100'], 'imsenterprise');
 388          $user1->username = $user1->idnumber;
 389  
 390          // Confirm user was added and that the enrolment happened.
 391          $this->assertEquals(($prevnuserenrolments + 1), $DB->count_records('user_enrolments'));
 392          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 393          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 394  
 395          // Capture DB id of enrolment record.
 396          $initialusernerolment = $DB->get_record('user_enrolments', ['userid' => $user1->id],
 397              '*', MUST_EXIST);
 398          $initialroleassigned = $DB->get_record('role_assignments', ['userid' => $user1->id],
 399              '*', MUST_EXIST);
 400  
 401          // Add a new enrolment for the same user via IMS file.
 402          $coursemembership = $this->link_users_with_courses(
 403              [$user1],
 404              $courses,
 405              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 406              // Role statuses: 0=Inactive, 1=Active.
 407              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 408              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 409              [
 410                  ['::'], // Course 1.
 411                  ['01:1:1'], // Course 2.
 412              ]
 413          );
 414  
 415          $this->set_xml_file([$user1], $courses, $coursemembership);
 416          $this->imsplugin->cron();
 417  
 418          $this->assertEquals(2, $DB->count_records('user_enrolments', ['userid' => $user1->id]));
 419          $this->assertEquals(($prevncourses + 2), $DB->count_records('course'));
 420  
 421          // Unenrol the user from course2 via IMS file.
 422          $coursemembership = $this->link_users_with_courses(
 423              [$user1],
 424              $courses,
 425              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 426              // Role statuses: 0=Inactive, 1=Active.
 427              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 428              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 429              [
 430                  ['::'], // Course 1.
 431                  ['01:0:3'], // Course 2.
 432              ]
 433          );
 434  
 435          $this->set_xml_file([$user1], $courses, $coursemembership);
 436          $this->imsplugin->cron();
 437  
 438          $this->assertEquals(1, $DB->count_records('user_enrolments', ['userid' => $user1->id]));
 439          $this->assertTrue($DB->record_exists('user_enrolments', ['id' => $initialusernerolment->id,
 440              'userid' => $initialusernerolment->userid]));
 441          $this->assertTrue($DB->record_exists('role_assignments', ['id' => $initialroleassigned->id,
 442              'userid' => $initialusernerolment->userid]));
 443      }
 444  
 445      /**
 446       * Enrolments alone are disabled
 447       */
 448      public function test_disable_enrolments_only() {
 449  
 450          global $DB;
 451  
 452          $this->imsplugin->set_config('imsunenrol', 1);
 453          $this->imsplugin->set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPEND);
 454  
 455          $prevnuserenrolments = $DB->count_records('user_enrolments');
 456          $prevnroles = $DB->count_records('role_assignments');
 457          $prevnusers = $DB->count_records('user');
 458          $prevncourses = $DB->count_records('course');
 459  
 460          $courses = $this->generate_test_course_records(1);
 461          $users = $this->generate_test_user_records(1);
 462  
 463          // Add a new enrolment for the same user via IMS file.
 464          $coursemembership = $this->link_users_with_courses(
 465              $users,
 466              $courses,
 467              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 468              // Role statuses: 0=Inactive, 1=Active.
 469              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 470              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 471              [
 472                  ['01:1:1'], // Course 1.
 473              ]
 474          );
 475  
 476          $this->set_xml_file($users, $courses, $coursemembership);
 477          $this->imsplugin->cron();
 478  
 479          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 480          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 481          $this->assertEquals(($prevnuserenrolments + 1), $DB->count_records('user_enrolments'));
 482          $this->assertEquals(($prevnroles + 1), $DB->count_records('role_assignments'));
 483  
 484          // Capture DB ids.
 485          $dbuser = $DB->get_record('user', ['idnumber' => $users[0]->idnumber], '*', MUST_EXIST);
 486  
 487          $dbenrolment = $DB->get_record('user_enrolments',
 488              ['userid' => $dbuser->id, 'status' => ENROL_USER_ACTIVE],
 489              '*', MUST_EXIST
 490          );
 491  
 492          $dbrole = $DB->get_record('role_assignments', ['userid' => $dbuser->id], '*', MUST_EXIST);
 493  
 494          // Unenrol the user, check that the enrolment and role exist, but the enrolment is suspended.
 495          $coursemembership = $this->link_users_with_courses(
 496              $users,
 497              $courses,
 498              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 499              // Role statuses: 0=Inactive, 1=Active.
 500              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 501              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 502              [
 503                  ['01:0:3'], // Course 1.
 504              ]
 505          );
 506  
 507          $this->set_xml_file($users, $courses, $coursemembership);
 508          $this->imsplugin->cron();
 509  
 510          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 511          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 512          $this->assertEquals(($prevnuserenrolments + 1), $DB->count_records('user_enrolments'));
 513          $this->assertEquals(($prevnroles + 1), $DB->count_records('role_assignments'));
 514  
 515          $this->assertEquals(1, $DB->count_records('user_enrolments',
 516              ['userid' => $dbuser->id, 'id' => $dbenrolment->id, 'status' => ENROL_USER_SUSPENDED]));
 517  
 518          $this->assertEquals(1, $DB->count_records('role_assignments',
 519              ['userid' => $dbuser->id, 'id' => $dbrole->id]));
 520      }
 521  
 522      /**
 523       * Enrolments are disabled but retained) and roles removed
 524       */
 525      public function test_disable_enrolments_and_remove_roles() {
 526  
 527          global $DB;
 528  
 529          $this->imsplugin->set_config('imsunenrol', 1);
 530          $this->imsplugin->set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
 531  
 532          $prevnuserenrolments = $DB->count_records('user_enrolments');
 533          $prevnroles = $DB->count_records('role_assignments');
 534          $prevnusers = $DB->count_records('user');
 535          $prevncourses = $DB->count_records('course');
 536  
 537          $courses = $this->generate_test_course_records(1);
 538          $users = $this->generate_test_user_records(1);
 539  
 540          // Add a new enrolment for the same user via IMS file.
 541          $coursemembership = $this->link_users_with_courses(
 542              $users,
 543              $courses,
 544              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 545              // Role statuses: 0=Inactive, 1=Active.
 546              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 547              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 548              [
 549                  ['01:1:1'], // Course 1.
 550              ]
 551          );
 552  
 553          $this->set_xml_file($users, $courses, $coursemembership);
 554          $this->imsplugin->cron();
 555  
 556          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 557          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 558          $this->assertEquals(($prevnuserenrolments + 1), $DB->count_records('user_enrolments'));
 559          $this->assertEquals(($prevnroles + 1), $DB->count_records('role_assignments'));
 560  
 561          // Capture DB ids.
 562          $dbuser = $DB->get_record('user', ['idnumber' => $users[0]->idnumber], '*', MUST_EXIST);
 563  
 564          $dbenrolment = $DB->get_record('user_enrolments',
 565              ['userid' => $dbuser->id, 'status' => ENROL_USER_ACTIVE],
 566              '*', MUST_EXIST
 567          );
 568  
 569          $dbrole = $DB->get_record('role_assignments', ['userid' => $dbuser->id], '*', MUST_EXIST);
 570  
 571          // Unenrol the user, check that the enrolment and role exist, but the enrolment is suspended.
 572          $coursemembership = $this->link_users_with_courses(
 573              $users,
 574              $courses,
 575              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 576              // Role statuses: 0=Inactive, 1=Active.
 577              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 578              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 579              [
 580                  ['01:0:3'], // Course 1.
 581              ]
 582          );
 583  
 584          $this->set_xml_file($users, $courses, $coursemembership);
 585          $this->imsplugin->cron();
 586  
 587          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 588          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 589          $this->assertEquals(($prevnuserenrolments + 1), $DB->count_records('user_enrolments'));
 590          $this->assertEquals(($prevnroles), $DB->count_records('role_assignments'));
 591  
 592          $this->assertEquals(1, $DB->count_records('user_enrolments',
 593              ['userid' => $dbuser->id, 'id' => $dbenrolment->id, 'status' => ENROL_USER_SUSPENDED]));
 594  
 595          $this->assertEquals(0, $DB->count_records('role_assignments',
 596              ['userid' => $dbuser->id, 'id' => $dbrole->id]));
 597  
 598      }
 599  
 600      /**
 601       * Enrolments and roles are deleted for specified user
 602       */
 603      public function test_delete_roles_and_enrolments() {
 604  
 605          global $DB;
 606  
 607          $this->imsplugin->set_config('imsunenrol', 1);
 608          $this->imsplugin->set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
 609  
 610          $prevnuserenrolments = $DB->count_records('user_enrolments');
 611          $prevnroles = $DB->count_records('role_assignments');
 612          $prevnusers = $DB->count_records('user');
 613          $prevncourses = $DB->count_records('course');
 614  
 615          $courses = $this->generate_test_course_records(1);
 616          $users = $this->generate_test_user_records(1);
 617  
 618          // Add a new enrolment for the same user via IMS file.
 619          $coursemembership = $this->link_users_with_courses(
 620              $users,
 621              $courses,
 622              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 623              // Role statuses: 0=Inactive, 1=Active.
 624              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 625              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 626              [
 627                  ['01:1:1'], // Course 1.
 628              ]
 629          );
 630  
 631          $this->set_xml_file($users, $courses, $coursemembership);
 632          $this->imsplugin->cron();
 633  
 634          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 635          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 636          $this->assertEquals(($prevnuserenrolments + 1), $DB->count_records('user_enrolments'));
 637          $this->assertEquals(($prevnroles + 1), $DB->count_records('role_assignments'));
 638  
 639          // Capture DB ids.
 640          $dbuser = $DB->get_record('user', ['idnumber' => $users[0]->idnumber], '*', MUST_EXIST);
 641  
 642          $dbenrolment = $DB->get_record('user_enrolments',
 643              ['userid' => $dbuser->id, 'status' => ENROL_USER_ACTIVE],
 644              '*', MUST_EXIST
 645          );
 646  
 647          $dbrole = $DB->get_record('role_assignments', ['userid' => $dbuser->id], '*', MUST_EXIST);
 648  
 649          // Unenrol the user, check that the enrolment and role exist, but the enrolment is suspended.
 650          $coursemembership = $this->link_users_with_courses(
 651              $users,
 652              $courses,
 653              // Role types: 01=Learner, 02=Instructor, 03=Content Dev, 04=Member, 05=Manager, 06=Mentor, 07=Admin, 08=TA.
 654              // Role statuses: 0=Inactive, 1=Active.
 655              // Role recstatus:  1=Add, 2=Update, 3=Delete.
 656              // Format of matrix elements: <roletype>:<role status>:<role recstatus>.
 657              [
 658                  ['01:1:3'], // Course 1.
 659              ]
 660          );
 661  
 662          $this->set_xml_file($users, $courses, $coursemembership);
 663          $this->imsplugin->cron();
 664  
 665          $this->assertEquals(($prevncourses + 1), $DB->count_records('course'));
 666          $this->assertEquals(($prevnusers + 1), $DB->count_records('user'));
 667          $this->assertEquals(($prevnuserenrolments), $DB->count_records('user_enrolments'));
 668          $this->assertEquals(($prevnroles), $DB->count_records('role_assignments'));
 669  
 670          $this->assertEquals(0, $DB->count_records('user_enrolments',
 671              ['userid' => $dbuser->id, 'id' => $dbenrolment->id, 'status' => ENROL_USER_SUSPENDED]));
 672  
 673          $this->assertEquals(0, $DB->count_records('role_assignments',
 674              ['userid' => $dbuser->id, 'id' => $dbrole->id]));
 675      }
 676  }