diff --git a/mod/lesson/classes/external.php b/mod/lesson/classes/external.php index 70cc7028bd1..8c7f522297d 100644 --- a/mod/lesson/classes/external.php +++ b/mod/lesson/classes/external.php @@ -224,7 +224,7 @@ class mod_lesson_external extends external_api { $lesson = $DB->get_record('lesson', array('id' => $lessonid), '*', MUST_EXIST); list($course, $cm) = get_course_and_cm_from_instance($lesson, 'lesson'); - $lesson = new lesson($lesson, $cm); + $lesson = new lesson($lesson, $cm, $course); $lesson->update_effective_access($USER->id); $context = $lesson->context; @@ -416,4 +416,62 @@ class mod_lesson_external extends external_api { ) ); } + + /** + * Describes the parameters for view_lesson. + * + * @return external_external_function_parameters + * @since Moodle 3.3 + */ + public static function view_lesson_parameters() { + return new external_function_parameters ( + array( + 'lessonid' => new external_value(PARAM_INT, 'lesson instance id'), + 'password' => new external_value(PARAM_RAW, 'lesson password', VALUE_DEFAULT, ''), + ) + ); + } + + /** + * Trigger the course module viewed event and update the module completion status. + * + * @param int $lessonid lesson instance id + * @param str $password optional password (the lesson may be protected) + * @return array of warnings and status result + * @since Moodle 3.3 + * @throws moodle_exception + */ + public static function view_lesson($lessonid, $password = '') { + global $DB; + + $params = array('lessonid' => $lessonid, 'password' => $password); + $params = self::validate_parameters(self::view_lesson_parameters(), $params); + $warnings = array(); + + list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']); + self::validate_attempt($lesson, $params); + + $lesson->set_module_viewed(); + + $result = array(); + $result['status'] = true; + $result['warnings'] = $warnings; + return $result; + } + + /** + * Describes the view_lesson return value. + * + * @return external_single_structure + * @since Moodle 3.3 + */ + public static function view_lesson_returns() { + return new external_single_structure( + array( + 'status' => new external_value(PARAM_BOOL, 'status: true if success'), + 'warnings' => new external_warnings(), + ) + ); + } + } diff --git a/mod/lesson/db/services.php b/mod/lesson/db/services.php index daa5dc61890..f5c9c760042 100644 --- a/mod/lesson/db/services.php +++ b/mod/lesson/db/services.php @@ -44,4 +44,12 @@ $functions = array( 'capabilities' => 'mod/lesson:view', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + 'mod_lesson_view_lesson' => array( + 'classname' => 'mod_lesson_external', + 'methodname' => 'view_lesson', + 'description' => 'Trigger the course module viewed event and update the module completion status.', + 'type' => 'write', + 'capabilities' => 'mod/lesson:view', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) + ), ); diff --git a/mod/lesson/locallib.php b/mod/lesson/locallib.php index dc2561dbe53..9e56fb2435c 100644 --- a/mod/lesson/locallib.php +++ b/mod/lesson/locallib.php @@ -1017,6 +1017,13 @@ class lesson extends lesson_base { */ protected $cm = null; + /** + * Course object gets set and retrieved by directly calling $lesson->courserecord; + * @see get_courserecord() + * @var stdClass + */ + protected $courserecord = null; + /** * Context object gets set and retrieved by directly calling $lesson->context; * @see get_context() @@ -1029,11 +1036,13 @@ class lesson extends lesson_base { * * @param object $properties * @param stdClass $cm course module object + * @param stdClass $course course object * @since Moodle 3.3 */ - public function __construct($properties, $cm = null) { + public function __construct($properties, $cm = null, $course = null) { parent::__construct($properties); $this->cm = $cm; + $this->courserecord = $course; } /** @@ -2129,6 +2138,31 @@ class lesson extends lesson_base { return $this->cm; } + /** + * Set the lesson course object. + * + * @param stdClass $course course objct + * @since Moodle 3.3 + */ + private function set_courserecord($course) { + $this->courserecord = $course; + } + + /** + * Return the lesson course object. + * + * @return stdClass course + * @since Moodle 3.3 + */ + public function get_courserecord() { + global $DB; + + if ($this->courserecord == null) { + $this->courserecord = $DB->get_record('course', array('id' => $this->properties->course)); + } + return $this->courserecord; + } + /** * Check if the user can manage the lesson activity. * @@ -2350,6 +2384,7 @@ class lesson extends lesson_base { * * @param int $retriescount the number of retries for the lesson (the last retry number). * @return true if the user left the timed session + * @since Moodle 3.3 */ public function left_during_timed_session($retriescount) { global $DB, $USER; @@ -2357,6 +2392,29 @@ class lesson extends lesson_base { $conditions = array('lessonid' => $this->properties->id, 'userid' => $USER->id, 'retry' => $retriescount); return $DB->count_records('lesson_attempts', $conditions) > 0 || $DB->count_records('lesson_branch', $conditions) > 0; } + + /** + * Trigger module viewed event and set the module viewed for completion. + * + * @since Moodle 3.3 + */ + public function set_module_viewed() { + global $CFG; + require_once($CFG->libdir . '/completionlib.php'); + + // Trigger module viewed event. + $event = \mod_lesson\event\course_module_viewed::create(array( + 'objectid' => $this->properties->id, + 'context' => $this->get_context() + )); + $event->add_record_snapshot('course_modules', $this->get_cm()); + $event->add_record_snapshot('course', $this->get_courserecord()); + $event->trigger(); + + // Mark as viewed. + $completion = new completion_info($this->get_courserecord()); + $completion->set_module_viewed($this->get_cm()); + } } diff --git a/mod/lesson/tests/external_test.php b/mod/lesson/tests/external_test.php index 06267ff95fc..776ce3a67fa 100644 --- a/mod/lesson/tests/external_test.php +++ b/mod/lesson/tests/external_test.php @@ -339,4 +339,66 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase { $this->assertTrue($result['canviewreports']); } + /** + * Test test_view_lesson invalid id. + */ + public function test_view_lesson_invalid_id() { + $this->setExpectedException('moodle_exception'); + mod_lesson_external::view_lesson(0); + } + + /** + * Test test_view_lesson user not enrolled. + */ + public function test_view_lesson_user_not_enrolled() { + // Test not-enrolled user. + $usernotenrolled = self::getDataGenerator()->create_user(); + $this->setUser($usernotenrolled); + $this->setExpectedException('moodle_exception'); + mod_lesson_external::view_lesson($this->lesson->id); + } + + /** + * Test test_view_lesson user student. + */ + public function test_view_lesson_user_student() { + // Test user with full capabilities. + $this->setUser($this->student); + + // Trigger and capture the event. + $sink = $this->redirectEvents(); + + $result = mod_lesson_external::view_lesson($this->lesson->id); + $result = external_api::clean_returnvalue(mod_lesson_external::view_lesson_returns(), $result); + $this->assertTrue($result['status']); + + $events = $sink->get_events(); + $this->assertCount(1, $events); + $event = array_shift($events); + + // Checking that the event contains the expected values. + $this->assertInstanceOf('\mod_lesson\event\course_module_viewed', $event); + $this->assertEquals($this->context, $event->get_context()); + $moodlelesson = new \moodle_url('/mod/lesson/view.php', array('id' => $this->cm->id)); + $this->assertEquals($moodlelesson, $event->get_url()); + $this->assertEventContextNotUsed($event); + $this->assertNotEmpty($event->get_name()); + } + + /** + * Test test_view_lesson user missing capabilities. + */ + public function test_view_lesson_user_missing_capabilities() { + // Test user with no capabilities. + // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles. + assign_capability('mod/lesson:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id); + // Empty all the caches that may be affected by this change. + accesslib_clear_all_caches_for_unit_testing(); + course_modinfo::clear_instance_cache(); + + $this->setUser($this->student); + $this->setExpectedException('moodle_exception'); + mod_lesson_external::view_lesson($this->lesson->id); + } + } diff --git a/mod/lesson/version.php b/mod/lesson/version.php index d6ebb85cb13..9ede9fa4bbf 100644 --- a/mod/lesson/version.php +++ b/mod/lesson/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016120502; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2016120503; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2016112900; // Requires this Moodle version $plugin->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics) $plugin->cron = 0; diff --git a/mod/lesson/view.php b/mod/lesson/view.php index 50fca69a1ef..74a2ae45595 100644 --- a/mod/lesson/view.php +++ b/mod/lesson/view.php @@ -26,7 +26,6 @@ require_once(__DIR__ . '/../../config.php'); require_once($CFG->dirroot.'/mod/lesson/locallib.php'); require_once($CFG->dirroot.'/mod/lesson/view_form.php'); -require_once($CFG->libdir . '/completionlib.php'); require_once($CFG->libdir . '/grade/constants.php'); $id = required_param('id', PARAM_INT); // Course Module ID @@ -37,7 +36,7 @@ $backtocourse = optional_param('backtocourse', false, PARAM_RAW); $cm = get_coursemodule_from_id('lesson', $id, 0, false, MUST_EXIST); $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); -$lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST), $cm); +$lesson = new lesson($DB->get_record('lesson', array('id' => $cm->instance), '*', MUST_EXIST), $cm, $course); require_login($course, false, $cm); @@ -48,10 +47,6 @@ if ($backtocourse) { // Apply overrides. $lesson->update_effective_access($USER->id); -// Mark as viewed -$completion = new completion_info($course); -$completion->set_module_viewed($cm); - $url = new moodle_url('/mod/lesson/view.php', array('id'=>$id)); if ($pageid !== null) { $url->param('pageid', $pageid); @@ -196,14 +191,7 @@ if ($pageid != LESSON_EOL) { $page = $lesson->load_page($newpageid); } - // Trigger module viewed event. - $event = \mod_lesson\event\course_module_viewed::create(array( - 'objectid' => $lesson->id, - 'context' => $context - )); - $event->add_record_snapshot('course_modules', $cm); - $event->add_record_snapshot('course', $course); - $event->trigger(); + $lesson->set_module_viewed(); // This is where several messages (usually warnings) are displayed // all of this is displayed above the actual page