Merge branch 'MDL-57643-master' of git://github.com/jleyva/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 15 Mar 2017 23:58:08 +0000 (07:58 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 15 Mar 2017 23:58:08 +0000 (07:58 +0800)
mod/lesson/classes/external.php
mod/lesson/continue.php
mod/lesson/db/services.php
mod/lesson/essay.php
mod/lesson/locallib.php
mod/lesson/tests/external_test.php
mod/lesson/version.php
mod/lesson/view.php

index f3d1051..70cc702 100644 (file)
@@ -209,4 +209,211 @@ class mod_lesson_external extends external_api {
             )
         );
     }
+
+    /**
+     * Utility function for validating a lesson.
+     *
+     * @param int $lessonid lesson instance id
+     * @return array array containing the lesson, course, context and course module objects
+     * @since  Moodle 3.3
+     */
+    protected static function validate_lesson($lessonid) {
+        global $DB, $USER;
+
+        // Request and permission validation.
+        $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->update_effective_access($USER->id);
+
+        $context = $lesson->context;
+        self::validate_context($context);
+
+        return array($lesson, $course, $cm, $context);
+    }
+
+    /**
+     * Validates a new attempt.
+     *
+     * @param  lesson  $lesson lesson instance
+     * @param  array   $params request parameters
+     * @param  boolean $return whether to return the errors or throw exceptions
+     * @return array          the errors (if return set to true)
+     * @since  Moodle 3.3
+     */
+    protected static function validate_attempt(lesson $lesson, $params, $return = false) {
+        global $USER;
+
+        $errors = array();
+
+        // Avoid checkings for managers.
+        if ($lesson->can_manage()) {
+            return [];
+        }
+
+        // Dead line.
+        if ($timerestriction = $lesson->get_time_restriction_status()) {
+            $error = ["$timerestriction->reason" => userdate($timerestriction->time)];
+            if (!$return) {
+                throw new moodle_exception(key($error), 'lesson', '', current($error));
+            }
+            $errors[key($error)] = current($error);
+        }
+
+        // Password protected lesson code.
+        if ($passwordrestriction = $lesson->get_password_restriction_status($params['password'])) {
+            $error = ["passwordprotectedlesson" => external_format_string($lesson->name, $lesson->context->id)];
+            if (!$return) {
+                throw new moodle_exception(key($error), 'lesson', '', current($error));
+            }
+            $errors[key($error)] = current($error);
+        }
+
+        // Check for dependencies.
+        if ($dependenciesrestriction = $lesson->get_dependencies_restriction_status()) {
+            $errorhtmllist = implode(get_string('and', 'lesson') . ', ', $dependenciesrestriction->errors);
+            $error = ["completethefollowingconditions" => $dependenciesrestriction->dependentlesson->name . $errorhtmllist];
+            if (!$return) {
+                throw new moodle_exception(key($error), 'lesson', '', current($error));
+            }
+            $errors[key($error)] = current($error);
+        }
+
+        // To check only when no page is set (starting or continuing a lesson).
+        if (empty($params['pageid'])) {
+            // To avoid multiple calls, store the magic property firstpage.
+            $lessonfirstpage = $lesson->firstpage;
+            $lessonfirstpageid = $lessonfirstpage ? $lessonfirstpage->id : false;
+
+            // Check if the lesson does not have pages.
+            if (!$lessonfirstpageid) {
+                $error = ["lessonnotready2" => null];
+                if (!$return) {
+                    throw new moodle_exception(key($error), 'lesson');
+                }
+                $errors[key($error)] = current($error);
+            }
+
+            // Get the number of retries (also referenced as attempts), and the last page seen.
+            $attemptscount = $lesson->count_user_retries($USER->id);
+            $lastpageseen = $lesson->get_last_page_seen($attemptscount);
+
+            // Check if the user left a timed session with no retakes.
+            if ($lastpageseen !== false && $lastpageseen != LESSON_EOL) {
+                if ($lesson->left_during_timed_session($attemptscount) && $lesson->timelimit && !$lesson->retake) {
+                    $error = ["leftduringtimednoretake" => null];
+                    if (!$return) {
+                        throw new moodle_exception(key($error), 'lesson');
+                    }
+                    $errors[key($error)] = current($error);
+                }
+            } else if ($attemptscount > 0 && !$lesson->retake) {
+                // The user finished the lesson and no retakes are allowed.
+                $error = ["noretake" => null];
+                if (!$return) {
+                    throw new moodle_exception(key($error), 'lesson');
+                }
+                $errors[key($error)] = current($error);
+            }
+        }
+
+        return $errors;
+    }
+
+    /**
+     * Describes the parameters for get_lesson_access_information.
+     *
+     * @return external_external_function_parameters
+     * @since Moodle 3.3
+     */
+    public static function get_lesson_access_information_parameters() {
+        return new external_function_parameters (
+            array(
+                'lessonid' => new external_value(PARAM_INT, 'lesson instance id')
+            )
+        );
+    }
+
+    /**
+     * Return access information for a given lesson.
+     *
+     * @param int $lessonid lesson instance id
+     * @return array of warnings and the access information
+     * @since Moodle 3.3
+     * @throws  moodle_exception
+     */
+    public static function get_lesson_access_information($lessonid) {
+        global $DB, $USER;
+
+        $warnings = array();
+
+        $params = array(
+            'lessonid' => $lessonid
+        );
+        $params = self::validate_parameters(self::get_lesson_access_information_parameters(), $params);
+
+        list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
+
+        $result = array();
+        // Capabilities first.
+        $result['canmanage'] = $lesson->can_manage();
+        $result['cangrade'] = has_capability('mod/lesson:grade', $context);
+        $result['canviewreports'] = has_capability('mod/lesson:viewreports', $context);
+
+        // Status information.
+        $result['reviewmode'] = $lesson->is_in_review_mode();
+        $result['attemptscount'] = $lesson->count_user_retries($USER->id);
+        $lastpageseen = $lesson->get_last_page_seen($result['attemptscount']);
+        $result['lastpageseen'] = ($lastpageseen !== false) ? $lastpageseen : 0;
+        $result['leftduringtimedsession'] = $lesson->left_during_timed_session($result['attemptscount']);
+        // To avoid multiple calls, store the magic property firstpage.
+        $lessonfirstpage = $lesson->firstpage;
+        $result['firstpageid'] = $lessonfirstpage ? $lessonfirstpage->id : 0;
+
+        // Access restrictions now, we emulate a new attempt access to get the possible warnings.
+        $result['preventaccessreasons'] = [];
+        $validationerrors = self::validate_attempt($lesson, ['password' => ''], true);
+        foreach ($validationerrors as $reason => $data) {
+            $result['preventaccessreasons'][] = [
+                'reason' => $reason,
+                'data' => $data,
+                'message' => get_string($reason, 'lesson', $data),
+            ];
+        }
+        $result['warnings'] = $warnings;
+        return $result;
+    }
+
+    /**
+     * Describes the get_lesson_access_information return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.3
+     */
+    public static function get_lesson_access_information_returns() {
+        return new external_single_structure(
+            array(
+                'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can manage the lesson or not.'),
+                'cangrade' => new external_value(PARAM_BOOL, 'Whether the user can grade the lesson or not.'),
+                'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the lesson reports or not.'),
+                'reviewmode' => new external_value(PARAM_BOOL, 'Whether the lesson is in review mode for the current user.'),
+                'attemptscount' => new external_value(PARAM_INT, 'The number of attempts done by the user.'),
+                'lastpageseen' => new external_value(PARAM_INT, 'The last page seen id.'),
+                'leftduringtimedsession' => new external_value(PARAM_BOOL, 'Whether the user left during a timed session.'),
+                'firstpageid' => new external_value(PARAM_INT, 'The lesson first page id.'),
+                'preventaccessreasons' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'reason' => new external_value(PARAM_ALPHANUMEXT, 'Reason lang string code'),
+                            'data' => new external_value(PARAM_RAW, 'Additional data'),
+                            'message' => new external_value(PARAM_RAW, 'Complete html message'),
+                        ),
+                        'The reasons why the user cannot attempt the lesson'
+                    )
+                ),
+                'warnings' => new external_warnings(),
+            )
+        );
+    }
 }
index ba5e769..7db68d9 100644 (file)
@@ -71,11 +71,7 @@ if (!$canmanage) {
 // record answer (if necessary) and show response (if none say if answer is correct or not)
 $page = $lesson->load_page(required_param('pageid', PARAM_INT));
 
-$userhasgrade = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
-$reviewmode = false;
-if ($userhasgrade && !$lesson->retake) {
-    $reviewmode = true;
-}
+$reviewmode = $lesson->is_in_review_mode();
 
 // Check the page has answers [MDL-25632]
 if (count($page->answers) > 0) {
index 06284be..daa5dc6 100644 (file)
@@ -30,9 +30,18 @@ $functions = array(
     'mod_lesson_get_lessons_by_courses' => array(
         'classname'     => 'mod_lesson_external',
         'methodname'    => 'get_lessons_by_courses',
-        'description'   => 'Returns a list of lessons in a provided list of courses, if no list is provided all lessons that the user can view will be returned.',
+        'description'   => 'Returns a list of lessons in a provided list of courses,
+                            if no list is provided all lessons that the user can view will be returned.',
         'type'          => 'read',
         'capabilities'  => 'mod/lesson:view',
         'services'      => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
+    'mod_lesson_get_lesson_access_information' => array(
+        'classname'     => 'mod_lesson_external',
+        'methodname'    => 'get_lesson_access_information',
+        'description'   => 'Return access information for a given lesson.',
+        'type'          => 'read',
+        'capabilities'  => 'mod/lesson:view',
+        'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+    ),
 );
index e741343..e1fe9c3 100644 (file)
@@ -378,7 +378,7 @@ switch ($mode) {
             $essaylinks = array();
 
             // Number of attempts on the lesson
-            $attempts = $DB->count_records('lesson_grades', array('userid'=>$userid, 'lessonid'=>$lesson->id));
+            $attempts = $lesson->count_user_retries($userid);
 
             // Go through each essay page
             foreach ($studentessays[$userid] as $page => $tries) {
index e5e8962..dc2561d 100644 (file)
@@ -2255,6 +2255,108 @@ class lesson extends lesson_base {
         }
         return false;
     }
+
+    /**
+     * Check if the lesson is in review mode. (The user already finished it and retakes are not allowed).
+     *
+     * @return bool true if is in review mode
+     * @since  Moodle 3.3
+     */
+    public function is_in_review_mode() {
+        global $DB, $USER;
+
+        $userhasgrade = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id));
+        if ($userhasgrade && !$this->properties->retake) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Return the last page the current user saw.
+     *
+     * @param int $retriescount the number of retries for the lesson (the last retry number).
+     * @return mixed false if the user didn't see the lesson or the last page id
+     */
+    public function get_last_page_seen($retriescount) {
+        global $DB, $USER;
+
+        $lastpageseen = false;
+        $allattempts = $this->get_attempts($retriescount);
+        if (!empty($allattempts)) {
+            $attempt = end($allattempts);
+            $attemptpage = $this->load_page($attempt->pageid);
+            $jumpto = $DB->get_field('lesson_answers', 'jumpto', array('id' => $attempt->answerid));
+            // Convert the jumpto to a proper page id.
+            if ($jumpto == 0) {
+                // Check if a question has been incorrectly answered AND no more attempts at it are left.
+                $nattempts = $this->get_attempts($attempt->retry, false, $attempt->pageid, $USER->id);
+                if (count($nattempts) >= $this->properties->maxattempts) {
+                    $lastpageseen = $this->get_next_page($attemptpage->nextpageid);
+                } else {
+                    $lastpageseen = $attempt->pageid;
+                }
+            } else if ($jumpto == LESSON_NEXTPAGE) {
+                $lastpageseen = $this->get_next_page($attemptpage->nextpageid);
+            } else if ($jumpto == LESSON_CLUSTERJUMP) {
+                $lastpageseen = $this->cluster_jump($attempt->pageid);
+            } else {
+                $lastpageseen = $jumpto;
+            }
+        }
+
+        if ($branchtables = $DB->get_records('lesson_branch', array("lessonid" => $this->properties->id, "userid" => $USER->id,
+                "retry" => $retriescount), 'timeseen DESC')) {
+            // In here, user has viewed a branch table.
+            $lastbranchtable = current($branchtables);
+            if (count($allattempts) > 0) {
+                if ($lastbranchtable->timeseen > $attempt->timeseen) {
+                    // This branch table was viewed more recently than the question page.
+                    if (!empty($lastbranchtable->nextpageid)) {
+                        $lastpageseen = $lastbranchtable->nextpageid;
+                    } else {
+                        // Next page ID did not exist prior to MDL-34006.
+                        $lastpageseen = $lastbranchtable->pageid;
+                    }
+                }
+            } else {
+                // Has not answered any questions but has viewed a branch table.
+                if (!empty($lastbranchtable->nextpageid)) {
+                    $lastpageseen = $lastbranchtable->nextpageid;
+                } else {
+                    // Next page ID did not exist prior to MDL-34006.
+                    $lastpageseen = $lastbranchtable->pageid;
+                }
+            }
+        }
+        return $lastpageseen;
+    }
+
+    /**
+     * Return the number of retries in a lesson for a given user.
+     *
+     * @param  int $userid the user id
+     * @return int the retries count
+     * @since  Moodle 3.3
+     */
+    public function count_user_retries($userid) {
+        global $DB;
+
+        return $DB->count_records('lesson_grades', array("lessonid" => $this->properties->id, "userid" => $userid));
+    }
+
+    /**
+     * Check if a user left a timed session.
+     *
+     * @param int $retriescount the number of retries for the lesson (the last retry number).
+     * @return true if the user left the timed session
+     */
+    public function left_during_timed_session($retriescount) {
+        global $DB, $USER;
+
+        $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;
+    }
 }
 
 
index b27dc31..06267ff 100644 (file)
@@ -31,6 +31,30 @@ global $CFG;
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
 require_once($CFG->dirroot . '/mod/lesson/locallib.php');
 
+/**
+ * Silly class to access mod_lesson_external internal methods.
+ *
+ * @package mod_lesson
+ * @copyright 2017 Juan Leyva <juan@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since  Moodle 3.3
+ */
+class testable_mod_lesson_external extends mod_lesson_external {
+
+    /**
+     * Validates a new attempt.
+     *
+     * @param  lesson  $lesson lesson instance
+     * @param  array   $params request parameters
+     * @param  boolean $return whether to return the errors or throw exceptions
+     * @return [array          the errors (if return set to true)
+     * @since  Moodle 3.3
+     */
+    public static function validate_attempt(lesson $lesson, $params, $return = false) {
+        return parent::validate_attempt($lesson, $params, $return);
+    }
+}
+
 /**
  * Lesson module external functions tests
  *
@@ -53,6 +77,9 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
         // Setup test data.
         $this->course = $this->getDataGenerator()->create_course();
         $this->lesson = $this->getDataGenerator()->create_module('lesson', array('course' => $this->course->id));
+        $lessongenerator = $this->getDataGenerator()->get_plugin_generator('mod_lesson');
+        $this->page1 = $lessongenerator->create_content($this->lesson);
+        $this->page2 = $lessongenerator->create_question_truefalse($this->lesson);
         $this->context = context_module::instance($this->lesson->cmid);
         $this->cm = get_coursemodule_from_instance('lesson', $this->lesson->id);
 
@@ -192,4 +219,124 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
         $this->assertFalse(isset($lessons['lessons'][0]['intro']));
     }
 
+    /**
+     * Test the validate_attempt function.
+     */
+    public function test_validate_attempt() {
+        global $DB;
+
+        $this->setUser($this->student);
+        // Test deadline.
+        $oldtime = time() - DAYSECS;
+        $DB->set_field('lesson', 'deadline', $oldtime, array('id' => $this->lesson->id));
+
+        $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
+        $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
+        $this->assertEquals('lessonclosed', key($validation));
+        $this->assertCount(1, $validation);
+
+        // Test not available yet.
+        $futuretime = time() + DAYSECS;
+        $DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id));
+        $DB->set_field('lesson', 'available', $futuretime, array('id' => $this->lesson->id));
+
+        $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
+        $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
+        $this->assertEquals('lessonopen', key($validation));
+        $this->assertCount(1, $validation);
+
+        // Test password.
+        $DB->set_field('lesson', 'deadline', 0, array('id' => $this->lesson->id));
+        $DB->set_field('lesson', 'available', 0, array('id' => $this->lesson->id));
+        $DB->set_field('lesson', 'usepassword', 1, array('id' => $this->lesson->id));
+        $DB->set_field('lesson', 'password', 'abc', array('id' => $this->lesson->id));
+
+        $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
+        $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
+        $this->assertEquals('passwordprotectedlesson', key($validation));
+        $this->assertCount(1, $validation);
+
+        $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
+        $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => 'abc'], true);
+        $this->assertCount(0, $validation);
+
+        // Dependencies.
+        $record = new stdClass();
+        $record->course = $this->course->id;
+        $lesson2 = self::getDataGenerator()->create_module('lesson', $record);
+        $DB->set_field('lesson', 'usepassword', 0, array('id' => $this->lesson->id));
+        $DB->set_field('lesson', 'password', '', array('id' => $this->lesson->id));
+        $DB->set_field('lesson', 'dependency', $lesson->id, array('id' => $this->lesson->id));
+
+        $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
+        $lesson->conditions = serialize((object) ['completed' => true, 'timespent' => 0, 'gradebetterthan' => 0]);
+        $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
+        $this->assertEquals('completethefollowingconditions', key($validation));
+        $this->assertCount(1, $validation);
+
+        // Lesson withou pages.
+        $lesson = new lesson($lesson2);
+        $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
+        $this->assertEquals('lessonnotready2', key($validation));
+        $this->assertCount(1, $validation);
+
+        // Test retakes.
+        $DB->set_field('lesson', 'dependency', 0, array('id' => $this->lesson->id));
+        $DB->set_field('lesson', 'retake', 0, array('id' => $this->lesson->id));
+        $record = [
+            'lessonid' => $this->lesson->id,
+            'userid' => $this->student->id,
+            'grade' => 100,
+            'late' => 0,
+            'completed' => 1,
+        ];
+        $DB->insert_record('lesson_grades', (object) $record);
+        $lesson = new lesson($DB->get_record('lesson', array('id' => $this->lesson->id)));
+        $validation = testable_mod_lesson_external::validate_attempt($lesson, ['password' => ''], true);
+        $this->assertEquals('noretake', key($validation));
+        $this->assertCount(1, $validation);
+    }
+
+    /**
+     * Test the get_lesson_access_information function.
+     */
+    public function test_get_lesson_access_information() {
+        global $DB;
+
+        $this->setUser($this->student);
+        // Add previous attempt.
+        $record = [
+            'lessonid' => $this->lesson->id,
+            'userid' => $this->student->id,
+            'grade' => 100,
+            'late' => 0,
+            'completed' => 1,
+        ];
+        $DB->insert_record('lesson_grades', (object) $record);
+
+        $result = mod_lesson_external::get_lesson_access_information($this->lesson->id);
+        $result = external_api::clean_returnvalue(mod_lesson_external::get_lesson_access_information_returns(), $result);
+        $this->assertFalse($result['canmanage']);
+        $this->assertFalse($result['cangrade']);
+        $this->assertFalse($result['canviewreports']);
+
+        $this->assertFalse($result['leftduringtimedsession']);
+        $this->assertEquals(1, $result['reviewmode']);
+        $this->assertEquals(1, $result['attemptscount']);
+        $this->assertEquals(0, $result['lastpageseen']);
+        $this->assertEquals($this->page2->id, $result['firstpageid']);
+        $this->assertCount(1, $result['preventaccessreasons']);
+        $this->assertEquals('noretake', $result['preventaccessreasons'][0]['reason']);
+        $this->assertEquals(null, $result['preventaccessreasons'][0]['data']);
+        $this->assertEquals(get_string('noretake', 'lesson'), $result['preventaccessreasons'][0]['message']);
+
+        // Now check permissions as admin.
+        $this->setAdminUser();
+        $result = mod_lesson_external::get_lesson_access_information($this->lesson->id);
+        $result = external_api::clean_returnvalue(mod_lesson_external::get_lesson_access_information_returns(), $result);
+        $this->assertTrue($result['canmanage']);
+        $this->assertTrue($result['cangrade']);
+        $this->assertTrue($result['canviewreports']);
+    }
+
 }
index 4b0e158..d6ebb85 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2016120501;     // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2016120502;     // 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;
index 0d42a6a..50fca69 100644 (file)
@@ -64,11 +64,7 @@ $canmanage = $lesson->can_manage();
 
 $lessonoutput = $PAGE->get_renderer('mod_lesson');
 
-$reviewmode = false;
-$userhasgrade = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id));
-if ($userhasgrade && !$lesson->retake) {
-    $reviewmode = true;
-}
+$reviewmode = $lesson->is_in_review_mode();
 
 if ($lesson->usepassword && !empty($userpassword)) {
     require_sesskey();
@@ -97,13 +93,17 @@ if ($pageid == LESSON_UNSEENBRANCHPAGE) {
     $pageid = lesson_unseen_question_jump($lesson, $USER->id, $pageid);
 }
 
+// To avoid multiple calls, store the magic property firstpage.
+$lessonfirstpage = $lesson->firstpage;
+$lessonfirstpageid = $lessonfirstpage ? $lessonfirstpage->id : false;
+
 // display individual pages and their sets of answers
 // if pageid is EOL then the end of the lesson has been reached
 // for flow, changed to simple echo for flow styles, michaelp, moved lesson name and page title down
 $attemptflag = false;
 if (empty($pageid)) {
     // make sure there are pages to view
-    if (!$DB->get_field('lesson_pages', 'id', array('lessonid' => $lesson->id, 'prevpageid' => 0))) {
+    if (!$lessonfirstpageid) {
         if (!$canmanage) {
             $lesson->add_message(get_string('lessonnotready2', 'lesson')); // a nice message to the student
         } else {
@@ -116,7 +116,7 @@ if (empty($pageid)) {
     }
 
     // if no pageid given see if the lesson has been started
-    $retries = $DB->count_records('lesson_grades', array("lessonid" => $lesson->id, "userid" => $USER->id));
+    $retries = $lesson->count_user_retries($USER->id);
     if ($retries > 0) {
         $attemptflag = true;
     }
@@ -125,57 +125,12 @@ if (empty($pageid)) {
         unset($USER->modattempts[$lesson->id]);  // if no pageid, then student is NOT reviewing
     }
 
-    // If there are any questions that have been answered correctly (or not) in this attempt.
-    $allattempts = $lesson->get_attempts($retries);
-    if (!empty($allattempts)) {
-        $attempt = end($allattempts);
-        $attemptpage = $lesson->load_page($attempt->pageid);
-        $jumpto = $DB->get_field('lesson_answers', 'jumpto', array('id' => $attempt->answerid));
-        // convert the jumpto to a proper page id
-        if ($jumpto == 0) {
-            // Check if a question has been incorrectly answered AND no more attempts at it are left.
-            $nattempts = $lesson->get_attempts($attempt->retry, false, $attempt->pageid, $USER->id);
-            if (count($nattempts) >= $lesson->maxattempts) {
-                $lastpageseen = $lesson->get_next_page($attemptpage->nextpageid);
-            } else {
-                $lastpageseen = $attempt->pageid;
-            }
-        } elseif ($jumpto == LESSON_NEXTPAGE) {
-            $lastpageseen = $lesson->get_next_page($attemptpage->nextpageid);
-        } else if ($jumpto == LESSON_CLUSTERJUMP) {
-            $lastpageseen = $lesson->cluster_jump($attempt->pageid);
-        } else {
-            $lastpageseen = $jumpto;
-        }
-    }
+    $lastpageseen = $lesson->get_last_page_seen($retries);
 
-    if ($branchtables = $DB->get_records('lesson_branch', array("lessonid" => $lesson->id, "userid" => $USER->id, "retry" => $retries), 'timeseen DESC')) {
-        // in here, user has viewed a branch table
-        $lastbranchtable = current($branchtables);
-        if (count($allattempts) > 0) {
-            if ($lastbranchtable->timeseen > $attempt->timeseen) {
-                // This branch table was viewed more recently than the question page.
-                if (!empty($lastbranchtable->nextpageid)) {
-                    $lastpageseen = $lastbranchtable->nextpageid;
-                } else {
-                    // Next page ID did not exist prior to MDL-34006.
-                    $lastpageseen = $lastbranchtable->pageid;
-                }
-            }
-        } else {
-            // Has not answered any questions but has viewed a branch table.
-            if (!empty($lastbranchtable->nextpageid)) {
-                $lastpageseen = $lastbranchtable->nextpageid;
-            } else {
-                // Next page ID did not exist prior to MDL-34006.
-                $lastpageseen = $lastbranchtable->pageid;
-            }
-        }
-    }
     // Check to see if end of lesson was reached.
-    if ((isset($lastpageseen) && ($lastpageseen != LESSON_EOL))) {
-        if (($DB->count_records('lesson_attempts', array('lessonid' => $lesson->id, 'userid' => $USER->id, 'retry' => $retries)) > 0)
-                || $DB->count_records('lesson_branch', array("lessonid" => $lesson->id, "userid" => $USER->id, "retry" => $retries)) > 0) {
+    if (($lastpageseen !== false && ($lastpageseen != LESSON_EOL))) {
+        // End not reached. Check if the user left.
+        if ($lesson->left_during_timed_session($retries)) {
 
             echo $lessonoutput->header($lesson, $cm, '', false, null, get_string('leftduringtimedsession', 'lesson'));
             if ($lesson->timelimit) {
@@ -212,7 +167,7 @@ if (empty($pageid)) {
         }
     }
     // start at the first page
-    if (!$pageid = $DB->get_field('lesson_pages', 'id', array('lessonid' => $lesson->id, 'prevpageid' => 0))) {
+    if (!$pageid = $lessonfirstpageid) {
         echo $lessonoutput->header($lesson, $cm, 'view', '', null);
         // Lesson currently has no content. A message for display has been prepared and will be displayed by the header method
         // of the lesson renderer.
@@ -326,7 +281,7 @@ if ($pageid != LESSON_EOL) {
         // this is for modattempts option.  Find the users previous answer to this page,
         //   and then display it below in answer processing
         if (isset($USER->modattempts[$lesson->id])) {
-            $retries = $DB->count_records('lesson_grades', array("lessonid"=>$lesson->id, "userid"=>$USER->id));
+            $retries = $lesson->count_user_retries($USER->id);
             if (!$attempts = $lesson->get_attempts($retries-1, false, $page->id)) {
                 print_error('cannotfindpreattempt', 'lesson');
             }