Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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   * SCORM module library functions tests
  19   *
  20   * @package    mod_scorm
  21   * @category   test
  22   * @copyright  2015 Juan Leyva <juan@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since      Moodle 3.0
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  global $CFG;
  30  
  31  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  32  require_once($CFG->dirroot . '/mod/scorm/lib.php');
  33  
  34  /**
  35   * SCORM module library functions tests
  36   *
  37   * @package    mod_scorm
  38   * @category   test
  39   * @copyright  2015 Juan Leyva <juan@moodle.com>
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   * @since      Moodle 3.0
  42   */
  43  class mod_scorm_lib_testcase extends externallib_advanced_testcase {
  44  
  45      /**
  46       * Set up for every test
  47       */
  48      public function setUp(): void {
  49          global $DB;
  50          $this->resetAfterTest();
  51          $this->setAdminUser();
  52  
  53          // Setup test data.
  54          $this->course = $this->getDataGenerator()->create_course();
  55          $this->scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $this->course->id));
  56          $this->context = context_module::instance($this->scorm->cmid);
  57          $this->cm = get_coursemodule_from_instance('scorm', $this->scorm->id);
  58  
  59          // Create users.
  60          $this->student = self::getDataGenerator()->create_user();
  61          $this->teacher = self::getDataGenerator()->create_user();
  62  
  63          // Users enrolments.
  64          $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
  65          $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
  66          $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
  67          $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
  68      }
  69  
  70      /** Test scorm_check_mode
  71       *
  72       * @return void
  73       */
  74      public function test_scorm_check_mode() {
  75          global $CFG;
  76  
  77          $newattempt = 'on';
  78          $attempt = 1;
  79          $mode = 'normal';
  80          scorm_check_mode($this->scorm, $newattempt, $attempt, $this->student->id, $mode);
  81          $this->assertEquals('off', $newattempt);
  82  
  83          $scoes = scorm_get_scoes($this->scorm->id);
  84          $sco = array_pop($scoes);
  85          scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
  86          $newattempt = 'on';
  87          scorm_check_mode($this->scorm, $newattempt, $attempt, $this->student->id, $mode);
  88          $this->assertEquals('on', $newattempt);
  89  
  90          // Now do the same with a SCORM 2004 package.
  91          $record = new stdClass();
  92          $record->course = $this->course->id;
  93          $record->packagefilepath = $CFG->dirroot.'/mod/scorm/tests/packages/RuntimeBasicCalls_SCORM20043rdEdition.zip';
  94          $scorm13 = $this->getDataGenerator()->create_module('scorm', $record);
  95          $newattempt = 'on';
  96          $attempt = 1;
  97          $mode = 'normal';
  98          scorm_check_mode($scorm13, $newattempt, $attempt, $this->student->id, $mode);
  99          $this->assertEquals('off', $newattempt);
 100  
 101          $scoes = scorm_get_scoes($scorm13->id);
 102          $sco = array_pop($scoes);
 103          scorm_insert_track($this->student->id, $scorm13->id, $sco->id, 1, 'cmi.completion_status', 'completed');
 104  
 105          $newattempt = 'on';
 106          $attempt = 1;
 107          $mode = 'normal';
 108          scorm_check_mode($scorm13, $newattempt, $attempt, $this->student->id, $mode);
 109          $this->assertEquals('on', $newattempt);
 110      }
 111  
 112      /**
 113       * Test scorm_view
 114       * @return void
 115       */
 116      public function test_scorm_view() {
 117          global $CFG;
 118  
 119          // Trigger and capture the event.
 120          $sink = $this->redirectEvents();
 121  
 122          scorm_view($this->scorm, $this->course, $this->cm, $this->context);
 123  
 124          $events = $sink->get_events();
 125          $this->assertCount(1, $events);
 126          $event = array_shift($events);
 127  
 128          // Checking that the event contains the expected values.
 129          $this->assertInstanceOf('\mod_scorm\event\course_module_viewed', $event);
 130          $this->assertEquals($this->context, $event->get_context());
 131          $url = new \moodle_url('/mod/scorm/view.php', array('id' => $this->cm->id));
 132          $this->assertEquals($url, $event->get_url());
 133          $this->assertEventContextNotUsed($event);
 134          $this->assertNotEmpty($event->get_name());
 135      }
 136  
 137      /**
 138       * Test scorm_get_availability_status and scorm_require_available
 139       * @return void
 140       */
 141      public function test_scorm_check_and_require_available() {
 142          global $DB;
 143  
 144          $this->setAdminUser();
 145  
 146          // User override case.
 147          $this->scorm->timeopen = time() + DAYSECS;
 148          $this->scorm->timeclose = time() - DAYSECS;
 149          list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
 150          $this->assertEquals(true, $status);
 151          $this->assertCount(0, $warnings);
 152  
 153          // Now check with a student.
 154          list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context, $this->student->id);
 155          $this->assertEquals(false, $status);
 156          $this->assertCount(2, $warnings);
 157          $this->assertArrayHasKey('notopenyet', $warnings);
 158          $this->assertArrayHasKey('expired', $warnings);
 159          $this->assertEquals(userdate($this->scorm->timeopen), $warnings['notopenyet']);
 160          $this->assertEquals(userdate($this->scorm->timeclose), $warnings['expired']);
 161  
 162          // Reset the scorm's times.
 163          $this->scorm->timeopen = $this->scorm->timeclose = 0;
 164  
 165          // Set to the student user.
 166          self::setUser($this->student);
 167  
 168          // Usual case.
 169          list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
 170          $this->assertEquals(true, $status);
 171          $this->assertCount(0, $warnings);
 172  
 173          // SCORM not open.
 174          $this->scorm->timeopen = time() + DAYSECS;
 175          list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
 176          $this->assertEquals(false, $status);
 177          $this->assertCount(1, $warnings);
 178  
 179          // SCORM closed.
 180          $this->scorm->timeopen = 0;
 181          $this->scorm->timeclose = time() - DAYSECS;
 182          list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
 183          $this->assertEquals(false, $status);
 184          $this->assertCount(1, $warnings);
 185  
 186          // SCORM not open and closed.
 187          $this->scorm->timeopen = time() + DAYSECS;
 188          list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
 189          $this->assertEquals(false, $status);
 190          $this->assertCount(2, $warnings);
 191  
 192          // Now additional checkings with different parameters values.
 193          list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
 194          $this->assertEquals(false, $status);
 195          $this->assertCount(2, $warnings);
 196  
 197          // SCORM not open.
 198          $this->scorm->timeopen = time() + DAYSECS;
 199          $this->scorm->timeclose = 0;
 200          list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
 201          $this->assertEquals(false, $status);
 202          $this->assertCount(1, $warnings);
 203  
 204          // SCORM closed.
 205          $this->scorm->timeopen = 0;
 206          $this->scorm->timeclose = time() - DAYSECS;
 207          list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
 208          $this->assertEquals(false, $status);
 209          $this->assertCount(1, $warnings);
 210  
 211          // SCORM not open and closed.
 212          $this->scorm->timeopen = time() + DAYSECS;
 213          list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
 214          $this->assertEquals(false, $status);
 215          $this->assertCount(2, $warnings);
 216  
 217          // As teacher now.
 218          self::setUser($this->teacher);
 219  
 220          // SCORM not open and closed.
 221          $this->scorm->timeopen = time() + DAYSECS;
 222          list($status, $warnings) = scorm_get_availability_status($this->scorm, false);
 223          $this->assertEquals(false, $status);
 224          $this->assertCount(2, $warnings);
 225  
 226          // Now, we use the special capability.
 227          // SCORM not open and closed.
 228          $this->scorm->timeopen = time() + DAYSECS;
 229          list($status, $warnings) = scorm_get_availability_status($this->scorm, true, $this->context);
 230          $this->assertEquals(true, $status);
 231          $this->assertCount(0, $warnings);
 232  
 233          // Check exceptions does not broke anything.
 234          scorm_require_available($this->scorm, true, $this->context);
 235          // Now, expect exceptions.
 236          $this->expectException('moodle_exception');
 237          $this->expectExceptionMessage(get_string("notopenyet", "scorm", userdate($this->scorm->timeopen)));
 238  
 239          // Now as student other condition.
 240          self::setUser($this->student);
 241          $this->scorm->timeopen = 0;
 242          $this->scorm->timeclose = time() - DAYSECS;
 243  
 244          $this->expectException('moodle_exception');
 245          $this->expectExceptionMessage(get_string("expired", "scorm", userdate($this->scorm->timeclose)));
 246          scorm_require_available($this->scorm, false);
 247      }
 248  
 249      /**
 250       * Test scorm_get_last_completed_attempt
 251       *
 252       * @return void
 253       */
 254      public function test_scorm_get_last_completed_attempt() {
 255          $this->assertEquals(1, scorm_get_last_completed_attempt($this->scorm->id, $this->student->id));
 256      }
 257  
 258      public function test_scorm_core_calendar_provide_event_action_open() {
 259          $this->resetAfterTest();
 260  
 261          $this->setAdminUser();
 262  
 263          // Create a course.
 264          $course = $this->getDataGenerator()->create_course();
 265  
 266          // Create a scorm activity.
 267          $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
 268              'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS));
 269  
 270          // Create a calendar event.
 271          $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
 272  
 273          // Only students see scorm events.
 274          $this->setUser($this->student);
 275  
 276          // Create an action factory.
 277          $factory = new \core_calendar\action_factory();
 278  
 279          // Decorate action event.
 280          $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
 281  
 282          // Confirm the event was decorated.
 283          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 284          $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name());
 285          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 286          $this->assertEquals(1, $actionevent->get_item_count());
 287          $this->assertTrue($actionevent->is_actionable());
 288      }
 289  
 290      public function test_scorm_core_calendar_provide_event_action_closed() {
 291          $this->resetAfterTest();
 292  
 293          $this->setAdminUser();
 294  
 295          // Create a course.
 296          $course = $this->getDataGenerator()->create_course();
 297  
 298          // Create a scorm activity.
 299          $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
 300              'timeclose' => time() - DAYSECS));
 301  
 302          // Create a calendar event.
 303          $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
 304  
 305          // Create an action factory.
 306          $factory = new \core_calendar\action_factory();
 307  
 308          // Decorate action event.
 309          $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
 310  
 311          // No event on the dashboard if module is closed.
 312          $this->assertNull($actionevent);
 313      }
 314  
 315      public function test_scorm_core_calendar_provide_event_action_open_in_future() {
 316          $this->resetAfterTest();
 317  
 318          $this->setAdminUser();
 319  
 320          // Create a course.
 321          $course = $this->getDataGenerator()->create_course();
 322  
 323          // Create a scorm activity.
 324          $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
 325              'timeopen' => time() + DAYSECS));
 326  
 327          // Create a calendar event.
 328          $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
 329  
 330          // Only students see scorm events.
 331          $this->setUser($this->student);
 332  
 333          // Create an action factory.
 334          $factory = new \core_calendar\action_factory();
 335  
 336          // Decorate action event.
 337          $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
 338  
 339          // Confirm the event was decorated.
 340          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 341          $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name());
 342          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 343          $this->assertEquals(1, $actionevent->get_item_count());
 344          $this->assertFalse($actionevent->is_actionable());
 345      }
 346  
 347      public function test_scorm_core_calendar_provide_event_action_with_different_user_as_admin() {
 348          $this->resetAfterTest();
 349  
 350          $this->setAdminUser();
 351  
 352          // Create a course.
 353          $course = $this->getDataGenerator()->create_course();
 354  
 355          // Create a scorm activity.
 356          $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id,
 357              'timeopen' => time() + DAYSECS));
 358  
 359          // Create a calendar event.
 360          $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
 361  
 362          // Create an action factory.
 363          $factory = new \core_calendar\action_factory();
 364  
 365          // Decorate action event override with a passed in user.
 366          $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory, $this->student->id);
 367          $actionevent2 = mod_scorm_core_calendar_provide_event_action($event, $factory);
 368  
 369          // Only students see scorm events.
 370          $this->assertNull($actionevent2);
 371  
 372          // Confirm the event was decorated.
 373          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 374          $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name());
 375          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 376          $this->assertEquals(1, $actionevent->get_item_count());
 377          $this->assertFalse($actionevent->is_actionable());
 378      }
 379  
 380      public function test_scorm_core_calendar_provide_event_action_no_time_specified() {
 381          $this->resetAfterTest();
 382  
 383          $this->setAdminUser();
 384  
 385          // Create a course.
 386          $course = $this->getDataGenerator()->create_course();
 387  
 388          // Create a scorm activity.
 389          $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id));
 390  
 391          // Create a calendar event.
 392          $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN);
 393  
 394          // Only students see scorm events.
 395          $this->setUser($this->student);
 396  
 397          // Create an action factory.
 398          $factory = new \core_calendar\action_factory();
 399  
 400          // Decorate action event.
 401          $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
 402  
 403          // Confirm the event was decorated.
 404          $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
 405          $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name());
 406          $this->assertInstanceOf('moodle_url', $actionevent->get_url());
 407          $this->assertEquals(1, $actionevent->get_item_count());
 408          $this->assertTrue($actionevent->is_actionable());
 409      }
 410  
 411      public function test_scorm_core_calendar_provide_event_action_already_completed() {
 412          $this->resetAfterTest();
 413          set_config('enablecompletion', 1);
 414          $this->setAdminUser();
 415  
 416          // Create the activity.
 417          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 418          $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id),
 419              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 420  
 421          // Get some additional data.
 422          $cm = get_coursemodule_from_instance('scorm', $scorm->id);
 423  
 424          // Create a calendar event.
 425          $event = $this->create_action_event($course->id, $scorm->id,
 426              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 427  
 428          // Mark the activity as completed.
 429          $completion = new completion_info($course);
 430          $completion->set_module_viewed($cm);
 431  
 432          // Create an action factory.
 433          $factory = new \core_calendar\action_factory();
 434  
 435          // Decorate action event.
 436          $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory);
 437  
 438          // Ensure result was null.
 439          $this->assertNull($actionevent);
 440      }
 441  
 442      public function test_scorm_core_calendar_provide_event_action_already_completed_for_user() {
 443          $this->resetAfterTest();
 444          set_config('enablecompletion', 1);
 445          $this->setAdminUser();
 446  
 447          // Create the activity.
 448          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 449          $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id),
 450              array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
 451  
 452          // Enrol a student in the course.
 453          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 454  
 455          // Get some additional data.
 456          $cm = get_coursemodule_from_instance('scorm', $scorm->id);
 457  
 458          // Create a calendar event.
 459          $event = $this->create_action_event($course->id, $scorm->id,
 460              \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
 461  
 462          // Mark the activity as completed for the student.
 463          $completion = new completion_info($course);
 464          $completion->set_module_viewed($cm, $student->id);
 465  
 466          // Create an action factory.
 467          $factory = new \core_calendar\action_factory();
 468  
 469          // Decorate action event for the student.
 470          $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory, $student->id);
 471  
 472          // Ensure result was null.
 473          $this->assertNull($actionevent);
 474      }
 475  
 476      /**
 477       * Creates an action event.
 478       *
 479       * @param int $courseid
 480       * @param int $instanceid The data id.
 481       * @param string $eventtype The event type. eg. DATA_EVENT_TYPE_OPEN.
 482       * @param int|null $timestart The start timestamp for the event
 483       * @return bool|calendar_event
 484       */
 485      private function create_action_event($courseid, $instanceid, $eventtype, $timestart = null) {
 486          $event = new stdClass();
 487          $event->name = 'Calendar event';
 488          $event->modulename = 'scorm';
 489          $event->courseid = $courseid;
 490          $event->instance = $instanceid;
 491          $event->type = CALENDAR_EVENT_TYPE_ACTION;
 492          $event->eventtype = $eventtype;
 493          $event->eventtype = $eventtype;
 494  
 495          if ($timestart) {
 496              $event->timestart = $timestart;
 497          } else {
 498              $event->timestart = time();
 499          }
 500  
 501          return calendar_event::create($event);
 502      }
 503  
 504      /**
 505       * Test the callback responsible for returning the completion rule descriptions.
 506       * This function should work given either an instance of the module (cm_info), such as when checking the active rules,
 507       * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type.
 508       */
 509      public function test_mod_scorm_completion_get_active_rule_descriptions() {
 510          $this->resetAfterTest();
 511          $this->setAdminUser();
 512  
 513          // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't.
 514          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]);
 515          $scorm1 = $this->getDataGenerator()->create_module('scorm', [
 516              'course' => $course->id,
 517              'completion' => 2,
 518              'completionstatusrequired' => 6,
 519              'completionscorerequired' => 5,
 520              'completionstatusallscos' => 1
 521          ]);
 522          $scorm2 = $this->getDataGenerator()->create_module('scorm', [
 523              'course' => $course->id,
 524              'completion' => 2,
 525              'completionstatusrequired' => null,
 526              'completionscorerequired' => null,
 527              'completionstatusallscos' => null
 528          ]);
 529          $cm1 = cm_info::create(get_coursemodule_from_instance('scorm', $scorm1->id));
 530          $cm2 = cm_info::create(get_coursemodule_from_instance('scorm', $scorm2->id));
 531  
 532          // Data for the stdClass input type.
 533          // This type of input would occur when checking the default completion rules for an activity type, where we don't have
 534          // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info.
 535          $moddefaults = new stdClass();
 536          $moddefaults->customdata = ['customcompletionrules' => [
 537              'completionstatusrequired' => 6,
 538              'completionscorerequired' => 5,
 539              'completionstatusallscos' => 1
 540          ]];
 541          $moddefaults->completion = 2;
 542  
 543          // Determine the selected statuses using a bitwise operation.
 544          $cvalues = array();
 545          foreach (scorm_status_options(true) as $key => $value) {
 546              if (($scorm1->completionstatusrequired & $key) == $key) {
 547                  $cvalues[] = $value;
 548              }
 549          }
 550          $statusstring = implode(', ', $cvalues);
 551  
 552          $activeruledescriptions = [
 553              get_string('completionstatusrequireddesc', 'scorm', $statusstring),
 554              get_string('completionscorerequireddesc', 'scorm', $scorm1->completionscorerequired),
 555              get_string('completionstatusallscos', 'scorm'),
 556          ];
 557          $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions($cm1), $activeruledescriptions);
 558          $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions($cm2), []);
 559          $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
 560          $this->assertEquals(mod_scorm_get_completion_active_rule_descriptions(new stdClass()), []);
 561      }
 562  
 563      /**
 564       * An unkown event type should not change the scorm instance.
 565       */
 566      public function test_mod_scorm_core_calendar_event_timestart_updated_unknown_event() {
 567          global $CFG, $DB;
 568          require_once($CFG->dirroot . "/calendar/lib.php");
 569  
 570          $this->resetAfterTest(true);
 571          $this->setAdminUser();
 572          $generator = $this->getDataGenerator();
 573          $course = $generator->create_course();
 574          $scormgenerator = $generator->get_plugin_generator('mod_scorm');
 575          $timeopen = time();
 576          $timeclose = $timeopen + DAYSECS;
 577          $scorm = $scormgenerator->create_instance(['course' => $course->id]);
 578          $scorm->timeopen = $timeopen;
 579          $scorm->timeclose = $timeclose;
 580          $DB->update_record('scorm', $scorm);
 581  
 582          // Create a valid event.
 583          $event = new \calendar_event([
 584              'name' => 'Test event',
 585              'description' => '',
 586              'format' => 1,
 587              'courseid' => $course->id,
 588              'groupid' => 0,
 589              'userid' => 2,
 590              'modulename' => 'scorm',
 591              'instance' => $scorm->id,
 592              'eventtype' => SCORM_EVENT_TYPE_OPEN . "SOMETHING ELSE",
 593              'timestart' => 1,
 594              'timeduration' => 86400,
 595              'visible' => 1
 596          ]);
 597  
 598          mod_scorm_core_calendar_event_timestart_updated($event, $scorm);
 599  
 600          $scorm = $DB->get_record('scorm', ['id' => $scorm->id]);
 601          $this->assertEquals($timeopen, $scorm->timeopen);
 602          $this->assertEquals($timeclose, $scorm->timeclose);
 603      }
 604  
 605      /**
 606       * A SCORM_EVENT_TYPE_OPEN event should update the timeopen property of
 607       * the scorm activity.
 608       */
 609      public function test_mod_scorm_core_calendar_event_timestart_updated_open_event() {
 610          global $CFG, $DB;
 611          require_once($CFG->dirroot . "/calendar/lib.php");
 612  
 613          $this->resetAfterTest(true);
 614          $this->setAdminUser();
 615          $generator = $this->getDataGenerator();
 616          $course = $generator->create_course();
 617          $scormgenerator = $generator->get_plugin_generator('mod_scorm');
 618          $timeopen = time();
 619          $timeclose = $timeopen + DAYSECS;
 620          $timemodified = 1;
 621          $newtimeopen = $timeopen - DAYSECS;
 622          $scorm = $scormgenerator->create_instance(['course' => $course->id]);
 623          $scorm->timeopen = $timeopen;
 624          $scorm->timeclose = $timeclose;
 625          $scorm->timemodified = $timemodified;
 626          $DB->update_record('scorm', $scorm);
 627  
 628          // Create a valid event.
 629          $event = new \calendar_event([
 630              'name' => 'Test event',
 631              'description' => '',
 632              'format' => 1,
 633              'courseid' => $course->id,
 634              'groupid' => 0,
 635              'userid' => 2,
 636              'modulename' => 'scorm',
 637              'instance' => $scorm->id,
 638              'eventtype' => SCORM_EVENT_TYPE_OPEN,
 639              'timestart' => $newtimeopen,
 640              'timeduration' => 86400,
 641              'visible' => 1
 642          ]);
 643  
 644          // Trigger and capture the event when adding a contact.
 645          $sink = $this->redirectEvents();
 646  
 647          mod_scorm_core_calendar_event_timestart_updated($event, $scorm);
 648  
 649          $triggeredevents = $sink->get_events();
 650          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
 651              return is_a($e, 'core\event\course_module_updated');
 652          });
 653  
 654          $scorm = $DB->get_record('scorm', ['id' => $scorm->id]);
 655          // Ensure the timeopen property matches the event timestart.
 656          $this->assertEquals($newtimeopen, $scorm->timeopen);
 657          // Ensure the timeclose isn't changed.
 658          $this->assertEquals($timeclose, $scorm->timeclose);
 659          // Ensure the timemodified property has been changed.
 660          $this->assertNotEquals($timemodified, $scorm->timemodified);
 661          // Confirm that a module updated event is fired when the module
 662          // is changed.
 663          $this->assertNotEmpty($moduleupdatedevents);
 664      }
 665  
 666      /**
 667       * A SCORM_EVENT_TYPE_CLOSE event should update the timeclose property of
 668       * the scorm activity.
 669       */
 670      public function test_mod_scorm_core_calendar_event_timestart_updated_close_event() {
 671          global $CFG, $DB;
 672          require_once($CFG->dirroot . "/calendar/lib.php");
 673  
 674          $this->resetAfterTest(true);
 675          $this->setAdminUser();
 676          $generator = $this->getDataGenerator();
 677          $course = $generator->create_course();
 678          $scormgenerator = $generator->get_plugin_generator('mod_scorm');
 679          $timeopen = time();
 680          $timeclose = $timeopen + DAYSECS;
 681          $timemodified = 1;
 682          $newtimeclose = $timeclose + DAYSECS;
 683          $scorm = $scormgenerator->create_instance(['course' => $course->id]);
 684          $scorm->timeopen = $timeopen;
 685          $scorm->timeclose = $timeclose;
 686          $scorm->timemodified = $timemodified;
 687          $DB->update_record('scorm', $scorm);
 688  
 689          // Create a valid event.
 690          $event = new \calendar_event([
 691              'name' => 'Test event',
 692              'description' => '',
 693              'format' => 1,
 694              'courseid' => $course->id,
 695              'groupid' => 0,
 696              'userid' => 2,
 697              'modulename' => 'scorm',
 698              'instance' => $scorm->id,
 699              'eventtype' => SCORM_EVENT_TYPE_CLOSE,
 700              'timestart' => $newtimeclose,
 701              'timeduration' => 86400,
 702              'visible' => 1
 703          ]);
 704  
 705          // Trigger and capture the event when adding a contact.
 706          $sink = $this->redirectEvents();
 707  
 708          mod_scorm_core_calendar_event_timestart_updated($event, $scorm);
 709  
 710          $triggeredevents = $sink->get_events();
 711          $moduleupdatedevents = array_filter($triggeredevents, function($e) {
 712              return is_a($e, 'core\event\course_module_updated');
 713          });
 714  
 715          $scorm = $DB->get_record('scorm', ['id' => $scorm->id]);
 716          // Ensure the timeclose property matches the event timestart.
 717          $this->assertEquals($newtimeclose, $scorm->timeclose);
 718          // Ensure the timeopen isn't changed.
 719          $this->assertEquals($timeopen, $scorm->timeopen);
 720          // Ensure the timemodified property has been changed.
 721          $this->assertNotEquals($timemodified, $scorm->timemodified);
 722          // Confirm that a module updated event is fired when the module
 723          // is changed.
 724          $this->assertNotEmpty($moduleupdatedevents);
 725      }
 726  
 727      /**
 728       * An unkown event type should not have any limits
 729       */
 730      public function test_mod_scorm_core_calendar_get_valid_event_timestart_range_unknown_event() {
 731          global $CFG, $DB;
 732          require_once($CFG->dirroot . "/calendar/lib.php");
 733  
 734          $this->resetAfterTest(true);
 735          $this->setAdminUser();
 736          $generator = $this->getDataGenerator();
 737          $course = $generator->create_course();
 738          $timeopen = time();
 739          $timeclose = $timeopen + DAYSECS;
 740          $scorm = new \stdClass();
 741          $scorm->timeopen = $timeopen;
 742          $scorm->timeclose = $timeclose;
 743  
 744          // Create a valid event.
 745          $event = new \calendar_event([
 746              'name' => 'Test event',
 747              'description' => '',
 748              'format' => 1,
 749              'courseid' => $course->id,
 750              'groupid' => 0,
 751              'userid' => 2,
 752              'modulename' => 'scorm',
 753              'instance' => 1,
 754              'eventtype' => SCORM_EVENT_TYPE_OPEN . "SOMETHING ELSE",
 755              'timestart' => 1,
 756              'timeduration' => 86400,
 757              'visible' => 1
 758          ]);
 759  
 760          list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
 761          $this->assertNull($min);
 762          $this->assertNull($max);
 763      }
 764  
 765      /**
 766       * The open event should be limited by the scorm's timeclose property, if it's set.
 767       */
 768      public function test_mod_scorm_core_calendar_get_valid_event_timestart_range_open_event() {
 769          global $CFG, $DB;
 770          require_once($CFG->dirroot . "/calendar/lib.php");
 771  
 772          $this->resetAfterTest(true);
 773          $this->setAdminUser();
 774          $generator = $this->getDataGenerator();
 775          $course = $generator->create_course();
 776          $timeopen = time();
 777          $timeclose = $timeopen + DAYSECS;
 778          $scorm = new \stdClass();
 779          $scorm->timeopen = $timeopen;
 780          $scorm->timeclose = $timeclose;
 781  
 782          // Create a valid event.
 783          $event = new \calendar_event([
 784              'name' => 'Test event',
 785              'description' => '',
 786              'format' => 1,
 787              'courseid' => $course->id,
 788              'groupid' => 0,
 789              'userid' => 2,
 790              'modulename' => 'scorm',
 791              'instance' => 1,
 792              'eventtype' => SCORM_EVENT_TYPE_OPEN,
 793              'timestart' => 1,
 794              'timeduration' => 86400,
 795              'visible' => 1
 796          ]);
 797  
 798          // The max limit should be bounded by the timeclose value.
 799          list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
 800  
 801          $this->assertNull($min);
 802          $this->assertEquals($timeclose, $max[0]);
 803  
 804          // No timeclose value should result in no upper limit.
 805          $scorm->timeclose = 0;
 806          list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
 807  
 808          $this->assertNull($min);
 809          $this->assertNull($max);
 810      }
 811  
 812      /**
 813       * The close event should be limited by the scorm's timeopen property, if it's set.
 814       */
 815      public function test_mod_scorm_core_calendar_get_valid_event_timestart_range_close_event() {
 816          global $CFG, $DB;
 817          require_once($CFG->dirroot . "/calendar/lib.php");
 818  
 819          $this->resetAfterTest(true);
 820          $this->setAdminUser();
 821          $generator = $this->getDataGenerator();
 822          $course = $generator->create_course();
 823          $timeopen = time();
 824          $timeclose = $timeopen + DAYSECS;
 825          $scorm = new \stdClass();
 826          $scorm->timeopen = $timeopen;
 827          $scorm->timeclose = $timeclose;
 828  
 829          // Create a valid event.
 830          $event = new \calendar_event([
 831              'name' => 'Test event',
 832              'description' => '',
 833              'format' => 1,
 834              'courseid' => $course->id,
 835              'groupid' => 0,
 836              'userid' => 2,
 837              'modulename' => 'scorm',
 838              'instance' => 1,
 839              'eventtype' => SCORM_EVENT_TYPE_CLOSE,
 840              'timestart' => 1,
 841              'timeduration' => 86400,
 842              'visible' => 1
 843          ]);
 844  
 845          // The max limit should be bounded by the timeclose value.
 846          list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
 847  
 848          $this->assertEquals($timeopen, $min[0]);
 849          $this->assertNull($max);
 850  
 851          // No timeclose value should result in no upper limit.
 852          $scorm->timeopen = 0;
 853          list ($min, $max) = mod_scorm_core_calendar_get_valid_event_timestart_range($event, $scorm);
 854  
 855          $this->assertNull($min);
 856          $this->assertNull($max);
 857      }
 858  
 859      /**
 860       * A user who does not have capabilities to add events to the calendar should be able to create a SCORM.
 861       */
 862      public function test_creation_with_no_calendar_capabilities() {
 863          $this->resetAfterTest();
 864          $course = self::getDataGenerator()->create_course();
 865          $context = context_course::instance($course->id);
 866          $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
 867          $roleid = self::getDataGenerator()->create_role();
 868          self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
 869          assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
 870          $generator = self::getDataGenerator()->get_plugin_generator('mod_scorm');
 871          // Create an instance as a user without the calendar capabilities.
 872          $this->setUser($user);
 873          $time = time();
 874          $params = array(
 875              'course' => $course->id,
 876              'timeopen' => $time + 200,
 877              'timeclose' => $time + 2000,
 878          );
 879          $generator->create_instance($params);
 880      }
 881  }