From 3a41f730ac2e65580e416d35362a86df4a772b67 Mon Sep 17 00:00:00 2001 From: Mark Nelson Date: Fri, 3 Mar 2017 16:52:46 +0800 Subject: [PATCH] MDL-57998 mod_scorm: added action events Part of MDL-55611 epic. --- mod/scorm/lang/en/scorm.php | 2 + mod/scorm/lib.php | 65 ++++++++ mod/scorm/locallib.php | 101 ++++++++++++ mod/scorm/tests/externallib_test.php | 12 +- mod/scorm/tests/lib_test.php | 136 +++++++++++++++ mod/scorm/tests/locallib_test.php | 237 +++++++++++++++++++++++++++ 6 files changed, 547 insertions(+), 6 deletions(-) create mode 100644 mod/scorm/tests/locallib_test.php diff --git a/mod/scorm/lang/en/scorm.php b/mod/scorm/lang/en/scorm.php index 35a334934c6..ca54ba67979 100644 --- a/mod/scorm/lang/en/scorm.php +++ b/mod/scorm/lang/en/scorm.php @@ -72,6 +72,8 @@ $string['browsed'] = 'Browsed'; $string['browsemode'] = 'Preview mode'; $string['browserepository'] = 'Browse repository'; $string['calculatedweight'] = 'Calculated weight'; +$string['calendarend'] = 'The SCORM activity \'{$a}\' closes'; +$string['calendarstart'] = 'The SCORM activity \'{$a}\' opens'; $string['cannotaccess'] = 'You cannot call this script in that way'; $string['cannotfindsco'] = 'Could not find SCO'; $string['closebeforeopen'] = 'You have specified a close date before the open date.'; diff --git a/mod/scorm/lib.php b/mod/scorm/lib.php index d4ff57ee15a..efe0dbdcb3f 100644 --- a/mod/scorm/lib.php +++ b/mod/scorm/lib.php @@ -165,6 +165,7 @@ function scorm_add_instance($scorm, $mform=null) { scorm_parse($record, true); scorm_grade_item_update($record); + scorm_update_calendar($record, $cmid); return $record->id; } @@ -255,6 +256,7 @@ function scorm_update_instance($scorm, $mform=null) { scorm_grade_item_update($scorm); scorm_update_grades($scorm); + scorm_update_calendar($scorm, $cmid); return true; } @@ -1553,3 +1555,66 @@ function mod_scorm_get_fontawesome_icon_map() { 'mod_scorm:wait' => 'fa-clock-o', ]; } + +/** + * This standard function will check all instances of this module + * and make sure there are up-to-date events created for each of them. + * If courseid = 0, then every scorm event in the site is checked, else + * only scorm events belonging to the course specified are checked. + * + * @param int $courseid + * @return bool + */ +function scorm_refresh_events($courseid = 0) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/mod/scorm/locallib.php'); + + if ($courseid) { + // Make sure that the course id is numeric. + if (!is_numeric($courseid)) { + return false; + } + if (!$scorms = $DB->get_records('scorm', array('course' => $courseid))) { + return false; + } + } else { + if (!$scorms = $DB->get_records('scorm')) { + return false; + } + } + + foreach ($scorms as $scorm) { + $cm = get_coursemodule_from_instance('scorm', $scorm->id); + scorm_update_calendar($scorm, $cm->id); + } + + return true; +} + +/** + * Handles creating actions for events. + * + * @param \core_calendar\event $event + * @param \core_calendar\action_factory $factory + * @return \core_calendar\local\event\value_objects\action|\core_calendar\local\interfaces\action_interface|null + */ +function mod_scorm_core_calendar_provide_event_action(\core_calendar\event $event, + \core_calendar\action_factory $factory) { + global $CFG, $DB; + + require_once($CFG->dirroot . '/mod/scorm/locallib.php'); + + $cm = get_fast_modinfo($event->courseid)->instances['scorm'][$event->instance]; + $scorm = $DB->get_record('scorm', array('id' => $event->instance)); + + // Check that the SCORM activity is open. + list($actionable, $warnings) = scorm_get_availability_status($scorm); + + return $factory->create_instance( + get_string('enter', 'scorm'), + new \moodle_url('/mod/scorm/view.php', array('id' => $cm->id)), + 1, + $actionable + ); +} \ No newline at end of file diff --git a/mod/scorm/locallib.php b/mod/scorm/locallib.php index 1cfa4e72c0b..4f08a3bf245 100644 --- a/mod/scorm/locallib.php +++ b/mod/scorm/locallib.php @@ -51,6 +51,9 @@ define('LASTATTEMPT', '3'); define('TOCJSLINK', 1); define('TOCFULLURL', 2); +define('SCORM_EVENT_TYPE_OPEN', 'open'); +define('SCORM_EVENT_TYPE_CLOSE', 'close'); + // Local Library of functions for module scorm. /** @@ -2352,3 +2355,101 @@ function scorm_eval_prerequisites($prerequisites, $usertracks) { } return eval('return '.implode($stack).';'); } + +/** + * Update the calendar entries for this scorm activity. + * + * @param stdClass $scorm the row from the database table scorm. + * @param int $cmid The coursemodule id + * @return bool + */ +function scorm_update_calendar(stdClass $scorm, $cmid) { + global $DB, $CFG; + + require_once($CFG->dirroot.'/calendar/lib.php'); + + // Scorm start calendar events. + $event = new stdClass(); + $event->eventtype = SCORM_EVENT_TYPE_OPEN; + // The SCORM_EVENT_TYPE_OPEN event should only be an action event if no close time is specified. + $event->type = empty($scorm->timeclose) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD; + if ($event->id = $DB->get_field('event', 'id', + array('modulename' => 'scorm', 'instance' => $scorm->id, 'eventtype' => $event->eventtype))) { + if ((!empty($scorm->timeopen)) && ($scorm->timeopen > 0)) { + // Calendar event exists so update it. + $event->name = get_string('calendarstart', 'scorm', $scorm->name); + $event->description = format_module_intro('scorm', $scorm, $cmid); + $event->timestart = $scorm->timeopen; + $event->timesort = $scorm->timeopen; + $event->visible = instance_is_visible('scorm', $scorm); + $event->timeduration = 0; + + $calendarevent = \core_calendar\event::load($event->id); + $calendarevent->update($event); + } else { + // Calendar event is on longer needed. + $calendarevent = \core_calendar\event::load($event->id); + $calendarevent->delete(); + } + } else { + // Event doesn't exist so create one. + if ((!empty($scorm->timeopen)) && ($scorm->timeopen > 0)) { + $event->name = get_string('calendarstart', 'scorm', $scorm->name); + $event->description = format_module_intro('scorm', $scorm, $cmid); + $event->courseid = $scorm->course; + $event->groupid = 0; + $event->userid = 0; + $event->modulename = 'scorm'; + $event->instance = $scorm->id; + $event->timestart = $scorm->timeopen; + $event->timesort = $scorm->timeopen; + $event->visible = instance_is_visible('scorm', $scorm); + $event->timeduration = 0; + + \core_calendar\event::create($event); + } + } + + // Scorm end calendar events. + $event = new stdClass(); + $event->type = CALENDAR_EVENT_TYPE_ACTION; + $event->eventtype = SCORM_EVENT_TYPE_CLOSE; + if ($event->id = $DB->get_field('event', 'id', + array('modulename' => 'scorm', 'instance' => $scorm->id, 'eventtype' => $event->eventtype))) { + if ((!empty($scorm->timeclose)) && ($scorm->timeclose > 0)) { + // Calendar event exists so update it. + $event->name = get_string('calendarend', 'scorm', $scorm->name); + $event->description = format_module_intro('scorm', $scorm, $cmid); + $event->timestart = $scorm->timeclose; + $event->timesort = $scorm->timeclose; + $event->visible = instance_is_visible('scorm', $scorm); + $event->timeduration = 0; + + $calendarevent = \core_calendar\event::load($event->id); + $calendarevent->update($event); + } else { + // Calendar event is on longer needed. + $calendarevent = \core_calendar\event::load($event->id); + $calendarevent->delete(); + } + } else { + // Event doesn't exist so create one. + if ((!empty($scorm->timeclose)) && ($scorm->timeclose > 0)) { + $event->name = get_string('calendarend', 'scorm', $scorm->name); + $event->description = format_module_intro('scorm', $scorm, $cmid); + $event->courseid = $scorm->course; + $event->groupid = 0; + $event->userid = 0; + $event->modulename = 'scorm'; + $event->instance = $scorm->id; + $event->timestart = $scorm->timeclose; + $event->timesort = $scorm->timeclose; + $event->visible = instance_is_visible('scorm', $scorm); + $event->timeduration = 0; + + \core_calendar\event::create($event); + } + } + + return true; +} diff --git a/mod/scorm/tests/externallib_test.php b/mod/scorm/tests/externallib_test.php index 645241eb77d..9beb1c27870 100644 --- a/mod/scorm/tests/externallib_test.php +++ b/mod/scorm/tests/externallib_test.php @@ -220,9 +220,6 @@ class mod_scorm_external_testcase extends externallib_advanced_testcase { $student = self::getDataGenerator()->create_user(); $teacher = self::getDataGenerator()->create_user(); - // Set to the student user. - self::setUser($student); - // Create courses to add the modules. $course = self::getDataGenerator()->create_course(); @@ -233,6 +230,9 @@ class mod_scorm_external_testcase extends externallib_advanced_testcase { $record->timeclose = $record->timeopen + DAYSECS; $scorm = self::getDataGenerator()->create_module('scorm', $record); + // Set to the student user. + self::setUser($student); + // Users enrolments. $studentrole = $DB->get_record('role', array('shortname' => 'student')); $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); @@ -421,9 +421,6 @@ class mod_scorm_external_testcase extends externallib_advanced_testcase { // Create users. $student = self::getDataGenerator()->create_user(); - // Set to the student user. - self::setUser($student); - // Create courses to add the modules. $course = self::getDataGenerator()->create_course(); @@ -449,6 +446,9 @@ class mod_scorm_external_testcase extends externallib_advanced_testcase { 'value' => '80' ); + // Set to the student user. + self::setUser($student); + // Users enrolments. $studentrole = $DB->get_record('role', array('shortname' => 'student')); $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual'); diff --git a/mod/scorm/tests/lib_test.php b/mod/scorm/tests/lib_test.php index 174e567636f..893d5d5adf8 100644 --- a/mod/scorm/tests/lib_test.php +++ b/mod/scorm/tests/lib_test.php @@ -191,4 +191,140 @@ class mod_scorm_lib_testcase extends externallib_advanced_testcase { public function test_scorm_get_last_completed_attempt() { $this->assertEquals(1, scorm_get_last_completed_attempt($this->scorm->id, $this->student->id)); } + + public function test_scorm_core_calendar_provide_event_action_open() { + $this->resetAfterTest(); + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id, + 'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS)); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Decorate action event. + $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertTrue($actionevent->is_actionable()); + } + + public function test_scorm_core_calendar_provide_event_action_closed() { + $this->resetAfterTest(); + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id, + 'timeclose' => time() - DAYSECS)); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Decorate action event. + $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertFalse($actionevent->is_actionable()); + } + + public function test_scorm_core_calendar_provide_event_action_open_in_future() { + $this->resetAfterTest(); + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id, + 'timeopen' => time() + DAYSECS)); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Decorate action event. + $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertFalse($actionevent->is_actionable()); + } + + public function test_scorm_core_calendar_provide_event_action_no_time_specified() { + $this->resetAfterTest(); + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id)); + + // Create a calendar event. + $event = $this->create_action_event($course->id, $scorm->id, SCORM_EVENT_TYPE_OPEN); + + // Create an action factory. + $factory = new \core_calendar\action_factory(); + + // Decorate action event. + $actionevent = mod_scorm_core_calendar_provide_event_action($event, $factory); + + // Confirm the event was decorated. + $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); + $this->assertEquals(get_string('enter', 'scorm'), $actionevent->get_name()); + $this->assertInstanceOf('moodle_url', $actionevent->get_url()); + $this->assertEquals(1, $actionevent->get_item_count()); + $this->assertTrue($actionevent->is_actionable()); + } + + /** + * Creates an action event. + * + * @param int $courseid + * @param int $instanceid The data id. + * @param string $eventtype The event type. eg. DATA_EVENT_TYPE_OPEN. + * @return bool|\core_calendar\event + */ + private function create_action_event($courseid, $instanceid, $eventtype) { + $event = new stdClass(); + $event->name = 'Calendar event'; + $event->modulename = 'scorm'; + $event->courseid = $courseid; + $event->instance = $instanceid; + $event->type = CALENDAR_EVENT_TYPE_ACTION; + $event->eventtype = $eventtype; + $event->timestart = time(); + + return \core_calendar\event::create($event); + } } diff --git a/mod/scorm/tests/locallib_test.php b/mod/scorm/tests/locallib_test.php new file mode 100644 index 00000000000..f33791ed56d --- /dev/null +++ b/mod/scorm/tests/locallib_test.php @@ -0,0 +1,237 @@ +. + +/** + * File containing the SCORM module local library function tests. + * + * @package mod_scorm + * @category test + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->dirroot . '/mod/scorm/lib.php'); + +/** + * Class containing the SCORM module local library function tests. + * + * @package mod_scorm + * @category test + * @copyright 2017 Mark Nelson + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_scorm_locallib_testcase extends advanced_testcase { + + public function setUp() { + $this->resetAfterTest(); + } + + public function test_scorm_update_calendar() { + global $DB; + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $time = time(); + $scorm = $this->getDataGenerator()->create_module('scorm', + array( + 'course' => $course->id, + 'timeopen' => $time + ) + ); + + // Check that there is now an event in the database. + $events = $DB->get_records('event'); + $this->assertCount(1, $events); + + // Get the event. + $event = reset($events); + + // Confirm the event is correct. + $this->assertEquals('scorm', $event->modulename); + $this->assertEquals($scorm->id, $event->instance); + $this->assertEquals(CALENDAR_EVENT_TYPE_ACTION, $event->type); + $this->assertEquals(DATA_EVENT_TYPE_OPEN, $event->eventtype); + $this->assertEquals($time, $event->timestart); + $this->assertEquals($time, $event->timesort); + } + + public function test_scorm_update_calendar_time_open_update() { + global $DB; + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $time = time(); + $scorm = $this->getDataGenerator()->create_module('scorm', + array( + 'course' => $course->id, + 'timeopen' => $time + ) + ); + + // Set the time open and update the event. + $scorm->timeopen = $time + DAYSECS; + scorm_update_calendar($scorm, $scorm->cmid); + + // Check that there is an event in the database. + $events = $DB->get_records('event'); + $this->assertCount(1, $events); + + // Get the event. + $event = reset($events); + + // Confirm the event time was updated. + $this->assertEquals('scorm', $event->modulename); + $this->assertEquals($scorm->id, $event->instance); + $this->assertEquals(CALENDAR_EVENT_TYPE_ACTION, $event->type); + $this->assertEquals(DATA_EVENT_TYPE_OPEN, $event->eventtype); + $this->assertEquals($time + DAYSECS, $event->timestart); + $this->assertEquals($time + DAYSECS, $event->timesort); + } + + public function test_scorm_update_calendar_time_open_delete() { + global $DB; + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $course->id)); + + // Create a scorm activity. + $time = time(); + $scorm = $this->getDataGenerator()->create_module('scorm', + array( + 'course' => $course->id, + 'timeopen' => $time + ) + ); + + // Set the time open to 0 and update the event. + $scorm->timeopen = 0; + scorm_update_calendar($scorm, $scorm->cmid); + + // Confirm the event was deleted. + $this->assertEquals(0, $DB->count_records('event')); + } + + public function test_scorm_update_calendar_time_close() { + global $DB; + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $time = time(); + $scorm = $this->getDataGenerator()->create_module('scorm', + array( + 'course' => $course->id, + 'timeclose' => $time + ) + ); + + // Check that there is now an event in the database. + $events = $DB->get_records('event'); + $this->assertCount(1, $events); + + // Get the event. + $event = reset($events); + + // Confirm the event is correct. + $this->assertEquals('scorm', $event->modulename); + $this->assertEquals($scorm->id, $event->instance); + $this->assertEquals(CALENDAR_EVENT_TYPE_ACTION, $event->type); + $this->assertEquals(DATA_EVENT_TYPE_CLOSE, $event->eventtype); + $this->assertEquals($time, $event->timestart); + $this->assertEquals($time, $event->timesort); + } + + public function test_scorm_update_calendar_time_close_update() { + global $DB; + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $time = time(); + $scorm = $this->getDataGenerator()->create_module('scorm', + array( + 'course' => $course->id, + 'timeclose' => $time + ) + ); + + // Set the time close and update the event. + $scorm->timeclose = $time + DAYSECS; + scorm_update_calendar($scorm, $scorm->cmid); + + // Check that there is an event in the database. + $events = $DB->get_records('event'); + $this->assertCount(1, $events); + + // Get the event. + $event = reset($events); + + // Confirm the event time was updated. + $this->assertEquals('scorm', $event->modulename); + $this->assertEquals($scorm->id, $event->instance); + $this->assertEquals(CALENDAR_EVENT_TYPE_ACTION, $event->type); + $this->assertEquals(DATA_EVENT_TYPE_CLOSE, $event->eventtype); + $this->assertEquals($time + DAYSECS, $event->timestart); + $this->assertEquals($time + DAYSECS, $event->timesort); + } + + public function test_scorm_update_calendar_time_close_delete() { + global $DB; + + $this->setAdminUser(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(); + + // Create a scorm activity. + $scorm = $this->getDataGenerator()->create_module('scorm', + array( + 'course' => $course->id, + 'timeclose' => time() + ) + ); + + // Set the time close to 0 and update the event. + $scorm->timeclose = 0; + scorm_update_calendar($scorm, $scorm->cmid); + + // Confirm the event time was deleted. + $this->assertEquals(0, $DB->count_records('event')); + } +}