MDL-23510 lib.php files have to be as small as possible, the actual implementation...
authorPetr Skoda <skodak@moodle.org>
Sat, 24 Jul 2010 18:56:56 +0000 (18:56 +0000)
committerPetr Skoda <skodak@moodle.org>
Sat, 24 Jul 2010 18:56:56 +0000 (18:56 +0000)
20 files changed:
mod/lesson/editpage_form.php
mod/lesson/essay_form.php
mod/lesson/format.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lesson/mod_form.php
mod/lesson/pagetypes/branchtable.php
mod/lesson/pagetypes/cluster.php
mod/lesson/pagetypes/endofbranch.php
mod/lesson/pagetypes/endofcluster.php
mod/lesson/pagetypes/essay.php
mod/lesson/pagetypes/matching.php
mod/lesson/pagetypes/multichoice.php
mod/lesson/pagetypes/numerical.php
mod/lesson/pagetypes/shortanswer.php
mod/lesson/pagetypes/truefalse.php
mod/lesson/renderer.php
mod/lesson/tabs.php
mod/lesson/version.php
mod/lesson/view_form.php

index dd137cd..bfce3f3 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Question selection form
  *
index d5fc8d1..e2005fd 100644 (file)
@@ -26,9 +26,7 @@
 /**
  * Include formslib if it has not already been included
  */
-if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
 
 require_once($CFG->libdir.'/formslib.php');
 
index efa058c..3d842f3 100644 (file)
@@ -26,6 +26,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Given some question info and some data about the the answers
  * this function parses, organises and saves the question
index 8c336d4..c2f08ce 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
-/** Include required libraries */
-//TODO: these dumb includes have to be removed and this script minimised by moving stuff to locallib.php!!!
-require_once($CFG->libdir.'/eventslib.php');
-require_once($CFG->libdir.'/filelib.php');
-require_once($CFG->dirroot.'/calendar/lib.php');
-require_once($CFG->dirroot.'/course/moodleform_mod.php');
-
-/** LESSON_MAX_EVENT_LENGTH = 432000 ; 5 days maximum */
-define("LESSON_MAX_EVENT_LENGTH", "432000");
+/* Do not include any libraries here! */
 
 /**
  * Given an object containing all the necessary data,
@@ -45,7 +37,7 @@ define("LESSON_MAX_EVENT_LENGTH", "432000");
  * @return int
  **/
 function lesson_add_instance($data, $mform) {
-    global $SESSION, $DB;
+    global $DB;
 
     $cmid = $data->coursemodule;
 
@@ -89,9 +81,7 @@ function lesson_update_instance($data, $mform) {
     lesson_process_pre_save($data);
 
     unset($data->mediafile);
-    if (!$result = $DB->update_record("lesson", $data)) {
-        return false; // Awe man!
-    }
+    $DB->update_record("lesson", $data);
 
     $context = get_context_instance(CONTEXT_MODULE, $cmid);
     if ($filename = $mform->get_new_filename('mediafile')) {
@@ -108,7 +98,7 @@ function lesson_update_instance($data, $mform) {
     // update grades - TODO: do it only when grading style changes
     lesson_update_grades($data, 0, false);
 
-    return $result;
+    return true;
 }
 
 
@@ -511,7 +501,7 @@ function lesson_grade_item_delete($lesson) {
 /**
  * Must return an array of user records (all data) who are participants
  * for a given instance of lesson. Must include every user involved
- * in the instance, independient of his role (student, teacher, admin...)
+ * in the instance, independent of his role (student, teacher, admin...)
  *
  * @global stdClass
  * @global object
@@ -609,7 +599,9 @@ function lesson_process_pre_save(&$lesson) {
  * @return void
  **/
 function lesson_process_post_save(&$lesson) {
-    global $DB;
+    global $DB, $CFG;
+    require_once($CFG->dirroot.'/calendar/lib.php');
+    require_once($CFG->dirroot . '/mod/lesson/locallib.php');
 
     if ($events = $DB->get_records('event', array('modulename'=>'lesson', 'instance'=>$lesson->id))) {
         foreach($events as $event) {
@@ -859,6 +851,7 @@ function lesson_get_import_export_formats($type) {
             $provided = $format_class->provide_export();
         }
         if ($provided) {
+            //TODO: do NOT rely on [[]] any more!!
             $formatname = get_string($fileformat, 'quiz');
             if ($formatname == "[[$fileformat]]") {
                 $formatname = get_string($fileformat, 'qformat_'.$fileformat);
@@ -965,2072 +958,3 @@ function lesson_get_file_info($browser, $areas, $course, $cm, $context, $fileare
     }
     return new file_info_stored($browser, $context, $storedfile, $urlbase, $filearea, $itemid, true, true, false);
 }
-
-/**
- * Abstract class to provide a core functions to the all lesson classes
- *
- * This class should be abstracted by ALL classes with the lesson module to ensure
- * that all classes within this module can be interacted with in the same way.
- *
- * This class provides the user with a basic properties array that can be fetched
- * or set via magic methods, or alternativily by defining methods get_blah() or
- * set_blah() within the extending object.
- *
- * @copyright 2009 Sam Hemelryk
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class lesson_base {
-
-    /**
-     * An object containing properties
-     * @var stdClass
-     */
-    protected $properties;
-
-    /**
-     * The constructor
-     * @param stdClass $properties
-     */
-    public function __construct($properties) {
-        $this->properties = (object)$properties;
-    }
-
-    /**
-     * Magic property method
-     *
-     * Attempts to call a set_$key method if one exists otherwise falls back
-     * to simply set the property
-     *
-     * @param string $key
-     * @param mixed $value
-     */
-    public function __set($key, $value) {
-        if (method_exists($this, 'set_'.$key)) {
-            $this->{'set_'.$key}($value);
-        }
-        $this->properties->{$key} = $value;
-    }
-
-    /**
-     * Magic get method
-     *
-     * Attempts to call a get_$key method to return the property and ralls over
-     * to return the raw property
-     *
-     * @param str $key
-     * @return mixed
-     */
-    public function __get($key) {
-        if (method_exists($this, 'get_'.$key)) {
-            return $this->{'get_'.$key}();
-        }
-        return $this->properties->{$key};
-    }
-
-    /**
-     * Stupid PHP needs an isset magic method if you use the get magic method and
-     * still want empty calls to work.... blah ~!
-     *
-     * @param string $key
-     * @return bool
-     */
-    public function __isset($key) {
-        if (method_exists($this, 'get_'.$key)) {
-            $val = $this->{'get_'.$key}();
-            return !empty($val);
-        }
-        return !empty($this->properties->{$key});
-    }
-
-    /**
-     * If overriden should create a new instance, save it in the DB and return it
-     */
-    public static function create() {}
-    /**
-     * If overriden should load an instance from the DB and return it
-     */
-    public static function load() {}
-    /**
-     * Fetches all of the properties of the object
-     * @return stdClass
-     */
-    public function properties() {
-        return $this->properties;
-    }
-}
-
-/**
- * Class representation of a lesson
- *
- * This class is used the interact with, and manage a lesson once instantiated.
- * If you need to fetch a lesson object you can do so by calling
- *
- * <code>
- * lesson::load($lessonid);
- * // or
- * $lessonrecord = $DB->get_record('lesson', $lessonid);
- * $lesson = new lesson($lessonrecord);
- * </code>
- *
- * The class itself extends lesson_base as all classes within the lesson module should
- *
- * These properties are from the database
- * @property int $id The id of this lesson
- * @property int $course The ID of the course this lesson belongs to
- * @property string $name The name of this lesson
- * @property int $practice Flag to toggle this as a practice lesson
- * @property int $modattempts Toggle to allow the user to go back and review answers
- * @property int $usepassword Toggle the use of a password for entry
- * @property string $password The password to require users to enter
- * @property int $dependency ID of another lesson this lesson is dependant on
- * @property string $conditions Conditions of the lesson dependency
- * @property int $grade The maximum grade a user can achieve (%)
- * @property int $custom Toggle custom scoring on or off
- * @property int $ongoing Toggle display of an ongoing score
- * @property int $usemaxgrade How retakes are handled (max=1, mean=0)
- * @property int $maxanswers The max number of answers or branches
- * @property int $maxattempts The maximum number of attempts a user can record
- * @property int $review Toggle use or wrong answer review button
- * @property int $nextpagedefault Override the default next page
- * @property int $feedback Toggles display of default feedback
- * @property int $minquestions Sets a minimum value of pages seen when calculating grades
- * @property int $maxpages Maximum number of pages this lesson can contain
- * @property int $retake Flag to allow users to retake a lesson
- * @property int $activitylink Relate this lesson to another lesson
- * @property string $mediafile File to pop up to or webpage to display
- * @property int $mediaheight Sets the height of the media file popup
- * @property int $mediawidth Sets the width of the media file popup
- * @property int $mediaclose Toggle display of a media close button
- * @property int $slideshow Flag for whether branch pages should be shown as slideshows
- * @property int $width Width of slideshow
- * @property int $height Height of slideshow
- * @property string $bgcolor Background colour of slideshow
- * @property int $displayleft Display a left meun
- * @property int $displayleftif Sets the condition on which the left menu is displayed
- * @property int $progressbar Flag to toggle display of a lesson progress bar
- * @property int $highscores Flag to toggle collection of high scores
- * @property int $maxhighscores Number of high scores to limit to
- * @property int $available Timestamp of when this lesson becomes available
- * @property int $deadline Timestamp of when this lesson is no longer available
- * @property int $timemodified Timestamp when lesson was last modified
- *
- * These properties are calculated
- * @property int $firstpageid Id of the first page of this lesson (prevpageid=0)
- * @property int $lastpageid Id of the last page of this lesson (nextpageid=0)
- *
- * @copyright 2009 Sam Hemelryk
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class lesson extends lesson_base {
-
-    /**
-     * The id of the first page (where prevpageid = 0) gets set and retrieved by
-     * {@see get_firstpageid()} by directly calling <code>$lesson->firstpageid;</code>
-     * @var int
-     */
-    protected $firstpageid = null;
-    /**
-     * The id of the last page (where nextpageid = 0) gets set and retrieved by
-     * {@see get_lastpageid()} by directly calling <code>$lesson->lastpageid;</code>
-     * @var int
-     */
-    protected $lastpageid = null;
-    /**
-     * An array used to cache the pages associated with this lesson after the first
-     * time they have been loaded.
-     * A note to developers: If you are going to be working with MORE than one or
-     * two pages from a lesson you should probably call {@see $lesson->load_all_pages()}
-     * in order to save excess database queries.
-     * @var array An array of lesson_page objects
-     */
-    protected $pages = array();
-    /**
-     * Flag that gets set to true once all of the pages associated with the lesson
-     * have been loaded.
-     * @var bool
-     */
-    protected $loadedallpages = false;
-
-    /**
-     * Simply generates a lesson object given an array/object of properties
-     * Overrides {@see lesson_base->create()}
-     * @static
-     * @param object|array $properties
-     * @return lesson
-     */
-    public static function create($properties) {
-        return new lesson($properties);
-    }
-
-    /**
-     * Generates a lesson object from the database given its id
-     * @static
-     * @param int $lessonid
-     * @return lesson
-     */
-    public static function load($lessonid) {
-        if (!$lesson = $DB->get_record('lesson', array('id' => $lessonid))) {
-            print_error('invalidcoursemodule');
-        }
-        return new lesson($lesson);
-    }
-
-    /**
-     * Deletes this lesson from the database
-     */
-    public function delete() {
-        global $CFG, $DB;
-        require_once($CFG->libdir.'/gradelib.php');
-
-        $DB->delete_records("lesson", array("id"=>$this->properties->id));;
-        $DB->delete_records("lesson_pages", array("lessonid"=>$this->properties->id));
-        $DB->delete_records("lesson_answers", array("lessonid"=>$this->properties->id));
-        $DB->delete_records("lesson_attempts", array("lessonid"=>$this->properties->id));
-        $DB->delete_records("lesson_grades", array("lessonid"=>$this->properties->id));
-        $DB->delete_records("lesson_timer", array("lessonid"=>$this->properties->id));
-        $DB->delete_records("lesson_branch", array("lessonid"=>$this->properties->id));
-        $DB->delete_records("lesson_high_scores", array("lessonid"=>$this->properties->id));
-        if ($events = $DB->get_records('event', array("modulename"=>'lesson', "instance"=>$this->properties->id))) {
-            foreach($events as $event) {
-                $event = calendar_event::load($event);
-                $event->delete();
-            }
-        }
-
-        grade_update('mod/lesson', $this->properties->course, 'mod', 'lesson', $this->properties->id, 0, NULL, array('deleted'=>1));
-        return true;
-    }
-
-    /**
-     * Fetches messages from the session that may have been set in previous page
-     * actions.
-     *
-     * <code>
-     * // Do not call this method directly instead use
-     * $lesson->messages;
-     * </code>
-     *
-     * @return array
-     */
-    protected function get_messages() {
-        global $SESSION;
-
-        $messages = array();
-        if (!empty($SESSION->lesson_messages) && is_array($SESSION->lesson_messages) && array_key_exists($this->properties->id, $SESSION->lesson_messages)) {
-            $messages = $SESSION->lesson_messages[$this->properties->id];
-            unset($SESSION->lesson_messages[$this->properties->id]);
-        }
-
-        return $messages;
-    }
-
-    /**
-     * Get all of the attempts for the current user.
-     *
-     * @param int $retries
-     * @param bool $correct Optional: only fetch correct attempts
-     * @param int $pageid Optional: only fetch attempts at the given page
-     * @param int $userid Optional: defaults to the current user if not set
-     * @return array|false
-     */
-    public function get_attempts($retries, $correct=false, $pageid=null, $userid=null) {
-        global $USER, $DB;
-        $params = array("lessonid"=>$this->properties->id, "userid"=>$userid, "retry"=>$retries);
-        if ($correct) {
-            $params['correct'] = 1;
-        }
-        if ($pageid !== null) {
-            $params['pageid'] = $pageid;
-        }
-        if ($userid === null) {
-            $params['userid'] = $USER->id;
-        }
-        return $DB->get_records('lesson_attempts', $params, 'timeseen DESC');
-    }
-
-    /**
-     * Returns the first page for the lesson or false if there isn't one.
-     *
-     * This method should be called via the magic method __get();
-     * <code>
-     * $firstpage = $lesson->firstpage;
-     * </code>
-     *
-     * @return lesson_page|bool Returns the lesson_page specialised object or false
-     */
-    protected function get_firstpage() {
-        $pages = $this->load_all_pages();
-        if (count($pages) > 0) {
-            foreach ($pages as $page) {
-                if ((int)$page->prevpageid === 0) {
-                    return $page;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns the last page for the lesson or false if there isn't one.
-     *
-     * This method should be called via the magic method __get();
-     * <code>
-     * $lastpage = $lesson->lastpage;
-     * </code>
-     *
-     * @return lesson_page|bool Returns the lesson_page specialised object or false
-     */
-    protected function get_lastpage() {
-        $pages = $this->load_all_pages();
-        if (count($pages) > 0) {
-            foreach ($pages as $page) {
-                if ((int)$page->nextpageid === 0) {
-                    return $page;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns the id of the first page of this lesson. (prevpageid = 0)
-     * @return int
-     */
-    protected function get_firstpageid() {
-        global $DB;
-        if ($this->firstpageid == null) {
-            if (!$this->loadedallpages) {
-                $firstpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'prevpageid'=>0));
-                if (!$firstpageid) {
-                    print_error('cannotfindfirstpage', 'lesson');
-                }
-                $this->firstpageid = $firstpageid;
-            } else {
-                $firstpage = $this->get_firstpage();
-                $this->firstpageid = $firstpage->id;
-            }
-        }
-        return $this->firstpageid;
-    }
-
-    /**
-     * Returns the id of the last page of this lesson. (nextpageid = 0)
-     * @return int
-     */
-    public function get_lastpageid() {
-        global $DB;
-        if ($this->lastpageid == null) {
-            if (!$this->loadedallpages) {
-                $lastpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'nextpageid'=>0));
-                if (!$lastpageid) {
-                    print_error('cannotfindlastpage', 'lesson');
-                }
-                $this->lastpageid = $lastpageid;
-            } else {
-                $lastpageid = $this->get_lastpage();
-                $this->lastpageid = $lastpageid->id;
-            }
-        }
-
-        return $this->lastpageid;
-    }
-
-     /**
-     * Gets the next page to display after the one that is provided.
-     * @param int $nextpageid
-     * @return bool
-     */
-    public function get_next_page($nextpageid) {
-        global $USER;
-        $allpages = $this->load_all_pages();
-        if ($this->properties->nextpagedefault) {
-            // in Flash Card mode...first get number of retakes
-            shuffle($allpages);
-            $found = false;
-            if ($this->properties->nextpagedefault == LESSON_UNSEENPAGE) {
-                foreach ($allpages as $nextpage) {
-                    if (!$DB->count_records("lesson_attempts", array("pageid"=>$nextpage->id, "userid"=>$USER->id, "retry"=>$nretakes))) {
-                        $found = true;
-                        break;
-                    }
-                }
-            } elseif ($this->properties->nextpagedefault == LESSON_UNANSWEREDPAGE) {
-                foreach ($allpages as $nextpage) {
-                    if (!$DB->count_records("lesson_attempts", array('pageid'=>$nextpage->id, 'userid'=>$USER->id, 'correct'=>1, 'retry'=>$nretakes))) {
-                        $found = true;
-                        break;
-                    }
-                }
-            }
-            if ($found) {
-                if ($this->properties->maxpages) {
-                    // check number of pages viewed (in the lesson)
-                    $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->properties->id, "userid"=>$USER->id));
-                    if ($DB->count_records("lesson_attempts", array("lessonid"=>$this->properties->id, "userid"=>$USER->id, "retry"=>$nretakes)) >= $this->properties->maxpages) {
-                        return false;
-                    }
-                }
-                return $nextpage;
-            }
-        }
-        // In a normal lesson mode
-        foreach ($allpages as $nextpage) {
-            if ((int)$nextpage->id===(int)$nextpageid) {
-                return $nextpage;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Sets a message against the session for this lesson that will displayed next
-     * time the lesson processes messages
-     *
-     * @param string $message
-     * @param string $class
-     * @param string $align
-     * @return bool
-     */
-    public function add_message($message, $class="notifyproblem", $align='center') {
-        global $SESSION;
-
-        if (empty($SESSION->lesson_messages) || !is_array($SESSION->lesson_messages)) {
-            $SESSION->lesson_messages = array();
-            $SESSION->lesson_messages[$this->properties->id] = array();
-        } else if (!array_key_exists($this->properties->id, $SESSION->lesson_messages)) {
-            $SESSION->lesson_messages[$this->properties->id] = array();
-        }
-
-        $SESSION->lesson_messages[$this->properties->id][] = array($message, $class, $align);
-
-        return true;
-    }
-
-    /**
-     * Check if the lesson is accessible at the present time
-     * @return bool True if the lesson is accessible, false otherwise
-     */
-    public function is_accessible() {
-        $available = $this->properties->available;
-        $deadline = $this->properties->deadline;
-        return (($available == 0 || time() >= $available) && ($deadline == 0 || time() < $deadline));
-    }
-
-    /**
-     * Starts the lesson time for the current user
-     * @return bool Returns true
-     */
-    public function start_timer() {
-        global $USER, $DB;
-        $USER->startlesson[$this->properties->id] = true;
-        $startlesson = new stdClass;
-        $startlesson->lessonid = $this->properties->id;
-        $startlesson->userid = $USER->id;
-        $startlesson->starttime = time();
-        $startlesson->lessontime = time();
-        $DB->insert_record('lesson_timer', $startlesson);
-        if ($this->properties->timed) {
-            $this->add_message(get_string('maxtimewarning', 'lesson', $this->properties->maxtime), 'center');
-        }
-        return true;
-    }
-
-    /**
-     * Updates the timer to the current time and returns the new timer object
-     * @param bool $restart If set to true the timer is restarted
-     * @param bool $continue If set to true AND $restart=true then the timer
-     *                        will continue from a previous attempt
-     * @return stdClass The new timer
-     */
-    public function update_timer($restart=false, $continue=false) {
-        global $USER, $DB;
-        // clock code
-        // get time information for this user
-        if (!$timer = $DB->get_records('lesson_timer', array ("lessonid" => $this->properties->id, "userid" => $USER->id), 'starttime DESC', '*', 0, 1)) {
-            print_error('cannotfindtimer', 'lesson');
-        } else {
-            $timer = current($timer); // this will get the latest start time record
-        }
-
-        if ($restart) {
-            if ($continue) {
-                // continue a previous test, need to update the clock  (think this option is disabled atm)
-                $timer->starttime = time() - ($timer->lessontime - $timer->starttime);
-            } else {
-                // starting over, so reset the clock
-                $timer->starttime = time();
-            }
-        }
-
-        $timer->lessontime = time();
-        $DB->update_record('lesson_timer', $timer);
-        return $timer;
-    }
-
-    /**
-     * Updates the timer to the current time then stops it by unsetting the user var
-     * @return bool Returns true
-     */
-    public function stop_timer() {
-        global $USER, $DB;
-        unset($USER->startlesson[$this->properties->id]);
-        return $this->update_timer(false, false);
-    }
-
-    /**
-     * Checks to see if the lesson has pages
-     */
-    public function has_pages() {
-        global $DB;
-        $pagecount = $DB->count_records('lesson_pages', array('lessonid'=>$this->properties->id));
-        return ($pagecount>0);
-    }
-
-    /**
-     * Returns the link for the related activity
-     * @return array|false
-     */
-    public function link_for_activitylink() {
-        global $DB;
-        $module = $DB->get_record('course_modules', array('id' => $this->properties->activitylink));
-        if ($module) {
-            $modname = $DB->get_field('modules', 'name', array('id' => $module->module));
-            if ($modname) {
-                $instancename = $DB->get_field($modname, 'name', array('id' => $module->instance));
-                if ($instancename) {
-                    return html_writer::link(new moodle_url('/mod/'.$modname.'/view.php', array('id'=>$this->properties->activitylink)),
-                        get_string('returnto', 'lesson', get_string('activitylinkname', 'lesson', $instancename)),
-                        array('class'=>'centerpadded lessonbutton standardbutton'));
-                }
-            }
-        }
-        return '';
-    }
-
-    /**
-     * Loads the requested page.
-     *
-     * This function will return the requested page id as either a specialised
-     * lesson_page object OR as a generic lesson_page.
-     * If the page has been loaded previously it will be returned from the pages
-     * array, otherwise it will be loaded from the database first
-     *
-     * @param int $pageid
-     * @return lesson_page A lesson_page object or an object that extends it
-     */
-    public function load_page($pageid) {
-        if (!array_key_exists($pageid, $this->pages)) {
-            $manager = lesson_page_type_manager::get($this);
-            $this->pages[$pageid] = $manager->load_page($pageid, $this);
-        }
-        return $this->pages[$pageid];
-    }
-
-    /**
-     * Loads ALL of the pages for this lesson
-     *
-     * @return array An array containing all pages from this lesson
-     */
-    public function load_all_pages() {
-        if (!$this->loadedallpages) {
-            $manager = lesson_page_type_manager::get($this);
-            $this->pages = $manager->load_all_pages($this);
-            $this->loadedallpages = true;
-        }
-        return $this->pages;
-    }
-
-    /**
-     * Determins if a jumpto value is correct or not.
-     *
-     * returns true if jumpto page is (logically) after the pageid page or
-     * if the jumpto value is a special value.  Returns false in all other cases.
-     *
-     * @param int $pageid Id of the page from which you are jumping from.
-     * @param int $jumpto The jumpto number.
-     * @return boolean True or false after a series of tests.
-     **/
-    public function jumpto_is_correct($pageid, $jumpto) {
-        global $DB;
-
-        // first test the special values
-        if (!$jumpto) {
-            // same page
-            return false;
-        } elseif ($jumpto == LESSON_NEXTPAGE) {
-            return true;
-        } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
-            return true;
-        } elseif ($jumpto == LESSON_RANDOMPAGE) {
-            return true;
-        } elseif ($jumpto == LESSON_CLUSTERJUMP) {
-            return true;
-        } elseif ($jumpto == LESSON_EOL) {
-            return true;
-        }
-
-        $pages = $this->load_all_pages();
-        $apageid = $pages[$pageid]->nextpageid;
-        while ($apageid != 0) {
-            if ($jumpto == $apageid) {
-                return true;
-            }
-            $apageid = $pages[$apageid]->nextpageid;
-        }
-        return false;
-    }
-
-    /**
-     * Returns the time a user has remaining on this lesson
-     * @param int $starttime Starttime timestamp
-     * @return string
-     */
-    public function time_remaining($starttime) {
-        $timeleft = $starttime + $this->maxtime * 60 - time();
-        $hours = floor($timeleft/3600);
-        $timeleft = $timeleft - ($hours * 3600);
-        $minutes = floor($timeleft/60);
-        $secs = $timeleft - ($minutes * 60);
-
-        if ($minutes < 10) {
-            $minutes = "0$minutes";
-        }
-        if ($secs < 10) {
-            $secs = "0$secs";
-        }
-        $output   = array();
-        $output[] = $hours;
-        $output[] = $minutes;
-        $output[] = $secs;
-        $output = implode(':', $output);
-        return $output;
-    }
-
-    /**
-     * Interprets LESSON_CLUSTERJUMP jumpto value.
-     *
-     * This will select a page randomly
-     * and the page selected will be inbetween a cluster page and end of cluter or end of lesson
-     * and the page selected will be a page that has not been viewed already
-     * and if any pages are within a branch table or end of branch then only 1 page within
-     * the branch table or end of branch will be randomly selected (sub clustering).
-     *
-     * @param int $pageid Id of the current page from which we are jumping from.
-     * @param int $userid Id of the user.
-     * @return int The id of the next page.
-     **/
-    public function cluster_jump($pageid, $userid=null) {
-        global $DB, $USER;
-
-        if ($userid===null) {
-            $userid = $USER->id;
-        }
-        // get the number of retakes
-        if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->properties->id, "userid"=>$userid))) {
-            $retakes = 0;
-        }
-        // get all the lesson_attempts aka what the user has seen
-        $seenpages = array();
-        if ($attempts = $this->get_attempts($retakes)) {
-            foreach ($attempts as $attempt) {
-                $seenpages[$attempt->pageid] = $attempt->pageid;
-            }
-
-        }
-
-        // get the lesson pages
-        $lessonpages = $this->load_all_pages();
-        // find the start of the cluster
-        while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page
-            if ($lessonpages[$pageid]->qtype == LESSON_PAGE_CLUSTER) {
-                break;
-            }
-            $pageid = $lessonpages[$pageid]->prevpageid;
-        }
-
-        $clusterpages = array();
-        $clusterpages = $this->get_sub_pages_of($pageid, array(LESSON_PAGE_ENDOFCLUSTER));
-        $unseen = array();
-        foreach ($clusterpages as $key=>$cluster) {
-            if ($cluster->type !== lesson_page::TYPE_QUESTION) {
-                unset($clusterpages[$key]);
-            } elseif ($cluster->is_unseen($seenpages)) {
-                $unseen[] = $cluster;
-            }
-        }
-
-        if (count($unseen) > 0) {
-            // it does not contain elements, then use exitjump, otherwise find out next page/branch
-            $nextpage = $unseen[rand(0, count($unseen)-1)];
-            if ($nextpage->qtype == LESSON_PAGE_BRANCHTABLE) {
-                // if branch table, then pick a random page inside of it
-                $branchpages = $this->get_sub_pages_of($nextpage->id, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH));
-                return $branchpages[rand(0, count($branchpages)-1)]->id;
-            } else { // otherwise, return the page's id
-                return $nextpage->id;
-            }
-        } else {
-            // seen all there is to see, leave the cluster
-            if (end($clusterpages)->nextpageid == 0) {
-                return LESSON_EOL;
-            } else {
-                $clusterendid = $pageid;
-                while ($clusterendid != 0) { // this condition should not be satisfied... should be a cluster page
-                    if ($lessonpages[$clusterendid]->qtype == LESSON_PAGE_CLUSTER) {
-                        break;
-                    }
-                    $clusterendid = $lessonpages[$clusterendid]->prevpageid;
-                }
-                $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $clusterendid, "lessonid" => $this->properties->id));
-                if ($exitjump == LESSON_NEXTPAGE) {
-                    $exitjump = $lessonpages[$pageid]->nextpageid;
-                }
-                if ($exitjump == 0) {
-                    return LESSON_EOL;
-                } else if (in_array($exitjump, array(LESSON_EOL, LESSON_PREVIOUSPAGE))) {
-                    return $exitjump;
-                } else {
-                    if (!array_key_exists($exitjump, $lessonpages)) {
-                        $found = false;
-                        foreach ($lessonpages as $page) {
-                            if ($page->id === $clusterendid) {
-                                $found = true;
-                            } else if ($page->qtype == LESSON_PAGE_ENDOFCLUSTER) {
-                                $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $page->id, "lessonid" => $this->properties->id));
-                                break;
-                            }
-                        }
-                    }
-                    if (!array_key_exists($exitjump, $lessonpages)) {
-                        return LESSON_EOL;
-                    }
-                    return $exitjump;
-                }
-            }
-        }
-    }
-
-    /**
-     * Finds all pages that appear to be a subtype of the provided pageid until
-     * an end point specified within $ends is encountered or no more pages exist
-     *
-     * @param int $pageid
-     * @param array $ends An array of LESSON_PAGE_* types that signify an end of
-     *               the subtype
-     * @return array An array of specialised lesson_page objects
-     */
-    public function get_sub_pages_of($pageid, array $ends) {
-        $lessonpages = $this->load_all_pages();
-        $pageid = $lessonpages[$pageid]->nextpageid;  // move to the first page after the branch table
-        $pages = array();
-
-        while (true) {
-            if ($pageid == 0 || in_array($lessonpages[$pageid]->qtype, $ends)) {
-                break;
-            }
-            $pages[] = $lessonpages[$pageid];
-            $pageid = $lessonpages[$pageid]->nextpageid;
-        }
-
-        return $pages;
-    }
-
-    /**
-     * Checks to see if the specified page[id] is a subpage of a type specified in
-     * the $types array, until either there are no more pages of we find a type
-     * corrosponding to that of a type specified in $ends
-     *
-     * @param int $pageid The id of the page to check
-     * @param array $types An array of types that would signify this page was a subpage
-     * @param array $ends An array of types that mean this is not a subpage
-     * @return bool
-     */
-    public function is_sub_page_of_type($pageid, array $types, array $ends) {
-        $pages = $this->load_all_pages();
-        $pageid = $pages[$pageid]->prevpageid; // move up one
-
-        array_unshift($ends, 0);
-        // go up the pages till branch table
-        while (true) {
-            if ($pageid==0 || in_array($pages[$pageid]->qtype, $ends)) {
-                return false;
-            } else if (in_array($pages[$pageid]->qtype, $types)) {
-                return true;
-            }
-            $pageid = $pages[$pageid]->prevpageid;
-        }
-    }
-}
-
-/**
- * Abstract class representation of a page associated with a lesson.
- *
- * This class should MUST be extended by all specialised page types defined in
- * mod/lesson/pagetypes/.
- * There are a handful of abstract methods that need to be defined as well as
- * severl methods that can optionally be defined in order to make the page type
- * operate in the desired way
- *
- * Database properties
- * @property int $id The id of this lesson page
- * @property int $lessonid The id of the lesson this page belongs to
- * @property int $prevpageid The id of the page before this one
- * @property int $nextpageid The id of the next page in the page sequence
- * @property int $qtype Identifies the page type of this page
- * @property int $qoption Used to record page type specific options
- * @property int $layout Used to record page specific layout selections
- * @property int $display Used to record page specific display selections
- * @property int $timecreated Timestamp for when the page was created
- * @property int $timemodified Timestamp for when the page was last modified
- * @property string $title The title of this page
- * @property string $contents The rich content shown to describe the page
- * @property int $contentsformat The format of the contents field
- *
- * Calculated properties
- * @property-read array $answers An array of answers for this page
- * @property-read bool $displayinmenublock Toggles display in the left menu block
- * @property-read array $jumps An array containing all the jumps this page uses
- * @property-read lesson $lesson The lesson this page belongs to
- * @property-read int $type The type of the page [question | structure]
- * @property-read typeid The unique identifier for the page type
- * @property-read typestring The string that describes this page type
- *
- * @abstract
- * @copyright 2009 Sam Hemelryk
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class lesson_page extends lesson_base {
-
-    /**
-     * A reference to the lesson this page belongs to
-     * @var lesson
-     */
-    protected $lesson = null;
-    /**
-     * Contains the answers to this lesson_page once loaded
-     * @var null|array
-     */
-    protected $answers = null;
-    /**
-     * This sets the type of the page, can be one of the constants defined below
-     * @var int
-     */
-    protected $type = 0;
-
-    /**
-     * Constants used to identify the type of the page
-     */
-    const TYPE_QUESTION = 0;
-    const TYPE_STRUCTURE = 1;
-
-    /**
-     * This method should return the integer used to identify the page type within
-     * the database and thoughout code. This maps back to the defines used in 1.x
-     * @abstract
-     * @return int
-     */
-    abstract protected function get_typeid();
-    /**
-     * This method should return the string that describes the pagetype
-     * @abstract
-     * @return string
-     */
-    abstract protected function get_typestring();
-
-    /**
-     * This method gets called to display the page to the user taking the lesson
-     * @abstract
-     * @param object $renderer
-     * @param object $attempt
-     * @return string
-     */
-    abstract public function display($renderer, $attempt);
-
-    /**
-     * Creates a new lesson_page within the database and returns the correct pagetype
-     * object to use to interact with the new lesson
-     *
-     * @final
-     * @static
-     * @param object $properties
-     * @param lesson $lesson
-     * @return lesson_page Specialised object that extends lesson_page
-     */
-    final public static function create($properties, lesson $lesson, $context, $maxbytes) {
-        global $DB;
-        $newpage = new stdClass;
-        $newpage->title = $properties->title;
-        $newpage->contents = $properties->contents_editor['text'];
-        $newpage->contentsformat = $properties->contents_editor['format'];
-        $newpage->lessonid = $lesson->id;
-        $newpage->timecreated = time();
-        $newpage->qtype = $properties->qtype;
-        $newpage->qoption = (isset($properties->qoption))?1:0;
-        $newpage->layout = (isset($properties->layout))?1:0;
-        $newpage->display = (isset($properties->display))?1:0;
-        $newpage->prevpageid = 0; // this is a first page
-        $newpage->nextpageid = 0; // this is the only page
-
-        if ($properties->pageid) {
-            $prevpage = $DB->get_record("lesson_pages", array("id" => $properties->pageid), 'id, nextpageid');
-            if (!$prevpage) {
-                print_error('cannotfindpages', 'lesson');
-            }
-            $newpage->prevpageid = $prevpage->id;
-            $newpage->nextpageid = $prevpage->nextpageid;
-        } else {
-            $nextpage = $DB->get_record('lesson_pages', array('lessonid'=>$lesson->id, 'prevpageid'=>0), 'id');
-            if ($nextpage) {
-                // This is the first page, there are existing pages put this at the start
-                $newpage->nextpageid = $nextpage->id;
-            }
-        }
-
-        $newpage->id = $DB->insert_record("lesson_pages", $newpage);
-
-        $editor = new stdClass;
-        $editor->id = $newpage->id;
-        $editor->contents_editor = $properties->contents_editor;
-        $editor = file_postupdate_standard_editor($editor, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$maxbytes), $context, 'mod_lesson', 'page_contents', $editor->id);
-        $DB->update_record("lesson_pages", $editor);
-
-        if ($newpage->prevpageid > 0) {
-            $DB->set_field("lesson_pages", "nextpageid", $newpage->id, array("id" => $newpage->prevpageid));
-        }
-        if ($newpage->nextpageid > 0) {
-            $DB->set_field("lesson_pages", "prevpageid", $newpage->id, array("id" => $newpage->nextpageid));
-        }
-
-        $page = lesson_page::load($newpage, $lesson);
-        $page->create_answers($properties);
-
-        $lesson->add_message(get_string('insertedpage', 'lesson').': '.format_string($newpage->title, true), 'notifysuccess');
-
-        return $page;
-    }
-
-    /**
-     * This method loads a page object from the database and returns it as a
-     * specialised object that extends lesson_page
-     *
-     * @final
-     * @static
-     * @param int $id
-     * @param lesson $lesson
-     * @return lesson_page Specialised lesson_page object
-     */
-    final public static function load($id, lesson $lesson) {
-        global $DB;
-
-        if (is_object($id) && !empty($id->qtype)) {
-            $page = $id;
-        } else {
-            $page = $DB->get_record("lesson_pages", array("id" => $id));
-            if (!$page) {
-                print_error('cannotfindpages', 'lesson');
-            }
-        }
-        $manager = lesson_page_type_manager::get($lesson);
-
-        $class = 'lesson_page_type_'.$manager->get_page_type_idstring($page->qtype);
-        if (!class_exists($class)) {
-            $class = 'lesson_page';
-        }
-
-        return new $class($page, $lesson);
-    }
-
-    /**
-     * Deletes a lesson_page from the database as well as any associated records.
-     * @final
-     * @return bool
-     */
-    final public function delete() {
-        global $DB;
-        // first delete all the associated records...
-        $DB->delete_records("lesson_attempts", array("pageid" => $this->properties->id));
-        // ...now delete the answers...
-        $DB->delete_records("lesson_answers", array("pageid" => $this->properties->id));
-        // ..and the page itself
-        $DB->delete_records("lesson_pages", array("id" => $this->properties->id));
-
-        // repair the hole in the linkage
-        if (!$this->properties->prevpageid && !$this->properties->nextpageid) {
-            //This is the only page, no repair needed
-        } elseif (!$this->properties->prevpageid) {
-            // this is the first page...
-            $page = $this->lesson->load_page($this->properties->nextpageid);
-            $page->move(null, 0);
-        } elseif (!$this->properties->nextpageid) {
-            // this is the last page...
-            $page = $this->lesson->load_page($this->properties->prevpageid);
-            $page->move(0);
-        } else {
-            // page is in the middle...
-            $prevpage = $this->lesson->load_page($this->properties->prevpageid);
-            $nextpage = $this->lesson->load_page($this->properties->nextpageid);
-
-            $prevpage->move($nextpage->id);
-            $nextpage->move(null, $prevpage->id);
-        }
-        return true;
-    }
-
-    /**
-     * Moves a page by updating its nextpageid and prevpageid values within
-     * the database
-     *
-     * @final
-     * @param int $nextpageid
-     * @param int $prevpageid
-     */
-    final public function move($nextpageid=null, $prevpageid=null) {
-        global $DB;
-        if ($nextpageid === null) {
-            $nextpageid = $this->properties->nextpageid;
-        }
-        if ($prevpageid === null) {
-            $prevpageid = $this->properties->prevpageid;
-        }
-        $obj = new stdClass;
-        $obj->id = $this->properties->id;
-        $obj->prevpageid = $prevpageid;
-        $obj->nextpageid = $nextpageid;
-        $DB->update_record('lesson_pages', $obj);
-    }
-
-    /**
-     * Returns the answers that are associated with this page in the database
-     *
-     * @final
-     * @return array
-     */
-    final public function get_answers() {
-        global $DB;
-        if ($this->answers === null) {
-            $this->answers = array();
-            $answers = $DB->get_records('lesson_answers', array('pageid'=>$this->properties->id, 'lessonid'=>$this->lesson->id), 'id');
-            if (!$answers) {
-                debugging(get_string('cannotfindanswer', 'lesson'));
-                return array();
-            }
-            foreach ($answers as $answer) {
-                $this->answers[count($this->answers)] = new lesson_page_answer($answer);
-            }
-        }
-        return $this->answers;
-    }
-
-    /**
-     * Returns the lesson this page is associated with
-     * @final
-     * @return lesson
-     */
-    final protected function get_lesson() {
-        return $this->lesson;
-    }
-
-    /**
-     * Returns the type of page this is. Not to be confused with page type
-     * @final
-     * @return int
-     */
-    final protected function get_type() {
-        return $this->type;
-    }
-
-    /**
-     * Records an attempt at this page
-     *
-     * @final
-     * @param stdClass $context
-     * @return stdClass Returns the result of the attempt
-     */
-    final public function record_attempt($context) {
-        global $DB, $USER, $OUTPUT;
-
-        /**
-         * This should be overriden by each page type to actually check the response
-         * against what ever custom criteria they have defined
-         */
-        $result = $this->check_answer();
-
-        $result->attemptsremaining  = 0;
-        $result->maxattemptsreached = false;
-
-        if ($result->noanswer) {
-            $result->newpageid = $this->properties->id; // display same page again
-            $result->feedback  = get_string('noanswer', 'lesson');
-        } else {
-            if (!has_capability('mod/lesson:manage', $context)) {
-                $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->lesson->id, "userid"=>$USER->id));
-                // record student's attempt
-                $attempt = new stdClass;
-                $attempt->lessonid = $this->lesson->id;
-                $attempt->pageid = $this->properties->id;
-                $attempt->userid = $USER->id;
-                $attempt->answerid = $result->answerid;
-                $attempt->retry = $nretakes;
-                $attempt->correct = $result->correctanswer;
-                if($result->userresponse !== null) {
-                    $attempt->useranswer = $result->userresponse;
-                }
-
-                $attempt->timeseen = time();
-                // if allow modattempts, then update the old attempt record, otherwise, insert new answer record
-                if (isset($USER->modattempts[$this->lesson->id])) {
-                    $attempt->retry = $nretakes - 1; // they are going through on review, $nretakes will be too high
-                }
-
-                $DB->insert_record("lesson_attempts", $attempt);
-                // "number of attempts remaining" message if $this->lesson->maxattempts > 1
-                // displaying of message(s) is at the end of page for more ergonomic display
-                if (!$result->correctanswer && ($result->newpageid == 0)) {
-                    // wrong answer and student is stuck on this page - check how many attempts
-                    // the student has had at this page/question
-                    $nattempts = $DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id),"retry", $nretakes);
-                    // retreive the number of attempts left counter for displaying at bottom of feedback page
-                    if ($nattempts >= $this->lesson->maxattempts) {
-                        if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
-                            $result->maxattemptsreached = true;
-                        }
-                        $result->newpageid = LESSON_NEXTPAGE;
-                    } else if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
-                        $result->attemptsremaining = $this->lesson->maxattempts - $nattempts;
-                    }
-                }
-            }
-            // TODO: merge this code with the jump code below.  Convert jumpto page into a proper page id
-            if ($result->newpageid == 0) {
-                $result->newpageid = $this->properties->id;
-            } elseif ($result->newpageid == LESSON_NEXTPAGE) {
-                $nextpage = $this->lesson->get_next_page($this->properties->nextpageid);
-                if ($nextpage === false) {
-                    $result->newpageid = LESSON_EOL;
-                } else {
-                    $result->newpageid = $nextpage->id;
-                }
-            }
-
-            // Determine default feedback if necessary
-            if (empty($result->response)) {
-                if (!$this->lesson->feedback && !$result->noanswer && !($this->lesson->review & !$result->correctanswer && !$result->isessayquestion)) {
-                    // These conditions have been met:
-                    //  1. The lesson manager has not supplied feedback to the student
-                    //  2. Not displaying default feedback
-                    //  3. The user did provide an answer
-                    //  4. We are not reviewing with an incorrect answer (and not reviewing an essay question)
-
-                    $result->nodefaultresponse = true;  // This will cause a redirect below
-                } else if ($result->isessayquestion) {
-                    $result->response = get_string('defaultessayresponse', 'lesson');
-                } else if ($result->correctanswer) {
-                    $result->response = get_string('thatsthecorrectanswer', 'lesson');
-                } else {
-                    $result->response = get_string('thatsthewronganswer', 'lesson');
-                }
-            }
-
-            if ($result->response) {
-                if ($this->lesson->review && !$result->correctanswer && !$result->isessayquestion) {
-                    $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->lesson->id, "userid"=>$USER->id));
-                    $qattempts = $DB->count_records("lesson_attempts", array("userid"=>$USER->id, "retry"=>$nretakes, "pageid"=>$this->properties->id));
-                    if ($qattempts == 1) {
-                        $result->feedback = $OUTPUT->box(get_string("firstwrong", "lesson"), 'feedback');
-                    } else {
-                        $result->feedback = $OUTPUT->BOX(get_string("secondpluswrong", "lesson"), 'feedback');
-                    }
-                } else {
-                    $class = 'response';
-                    if ($result->correctanswer) {
-                        $class .= ' correct'; //CSS over-ride this if they exist (!important)
-                    } else if (!$result->isessayquestion) {
-                        $class .= ' incorrect'; //CSS over-ride this if they exist (!important)
-                    }
-                    $options = new stdClass;
-                    $options->noclean = true;
-                    $options->para = true;
-                    $result->feedback = $OUTPUT->box(format_text($this->properties->contents, $this->properties->contentsformat, $options), 'generalbox boxaligncenter');
-                    $result->feedback .= '<div class="correctanswer generalbox"><em>'.get_string("youranswer", "lesson").'</em> : '.$result->studentanswer; // already in clean html
-                    $result->feedback .= $OUTPUT->box($result->response, $class); // already conerted to HTML
-                    echo "</div>";
-                }
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Returns the string for a jump name
-     *
-     * @final
-     * @param int $jumpto Jump code or page ID
-     * @return string
-     **/
-    final protected function get_jump_name($jumpto) {
-        global $DB;
-        static $jumpnames = array();
-
-        if (!array_key_exists($jumpto, $jumpnames)) {
-            if ($jumpto == 0) {
-                $jumptitle = get_string('thispage', 'lesson');
-            } elseif ($jumpto == LESSON_NEXTPAGE) {
-                $jumptitle = get_string('nextpage', 'lesson');
-            } elseif ($jumpto == LESSON_EOL) {
-                $jumptitle = get_string('endoflesson', 'lesson');
-            } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
-                $jumptitle = get_string('unseenpageinbranch', 'lesson');
-            } elseif ($jumpto == LESSON_PREVIOUSPAGE) {
-                $jumptitle = get_string('previouspage', 'lesson');
-            } elseif ($jumpto == LESSON_RANDOMPAGE) {
-                $jumptitle = get_string('randompageinbranch', 'lesson');
-            } elseif ($jumpto == LESSON_RANDOMBRANCH) {
-                $jumptitle = get_string('randombranch', 'lesson');
-            } elseif ($jumpto == LESSON_CLUSTERJUMP) {
-                $jumptitle = get_string('clusterjump', 'lesson');
-            } else {
-                if (!$jumptitle = $DB->get_field('lesson_pages', 'title', array('id' => $jumpto))) {
-                    $jumptitle = '<strong>'.get_string('notdefined', 'lesson').'</strong>';
-                }
-            }
-            $jumpnames[$jumpto] = format_string($jumptitle,true);
-        }
-
-        return $jumpnames[$jumpto];
-    }
-
-    /**
-     * Construstor method
-     * @param object $properties
-     * @param lesson $lesson
-     */
-    public function __construct($properties, lesson $lesson) {
-        parent::__construct($properties);
-        $this->lesson = $lesson;
-    }
-
-    /**
-     * Returns the score for the attempt
-     * This may be overriden by page types that require manual grading
-     * @param array $answers
-     * @param object $attempt
-     * @return int
-     */
-    public function earned_score($answers, $attempt) {
-        return $answers[$attempt->answerid]->score;
-    }
-
-    /**
-     * This is a callback method that can be override and gets called when ever a page
-     * is viewed
-     *
-     * @param bool $canmanage True if the user has the manage cap
-     * @return mixed
-     */
-    public function callback_on_view($canmanage) {
-        return true;
-    }
-
-    /**
-     * Updates a lesson page and its answers within the database
-     *
-     * @param object $properties
-     * @return bool
-     */
-    public function update($properties, $context = null, $maxbytes = null) {
-        global $DB;
-        $answers  = $this->get_answers();
-        $properties->id = $this->properties->id;
-        $properties->lessonid = $this->lesson->id;
-        if (empty($properties->qoption)) {
-            $properties->qoption = '0';
-        }
-        if (empty($context)) {
-            $context = $PAGE->context;
-        }
-        if ($maxbytes === null) {
-            $maxbytes =get_max_upload_file_size();
-        }
-        $properties = file_postupdate_standard_editor($properties, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$maxbytes), $context, 'mod_lesson', 'page_contents', $properties->id);
-        $DB->update_record("lesson_pages", $properties);
-
-        for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
-            if (!array_key_exists($i, $this->answers)) {
-                $this->answers[$i] = new stdClass;
-                $this->answers[$i]->lessonid = $this->lesson->id;
-                $this->answers[$i]->pageid = $this->id;
-                $this->answers[$i]->timecreated = $this->timecreated;
-            }
-            if (!empty($properties->answer_editor[$i])) {
-                $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
-                $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
-                if (isset($properties->response_editor[$i])) {
-                    $this->answers[$i]->response = $properties->response_editor[$i]['text'];
-                    $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
-                }
-                if (isset($properties->jumpto[$i])) {
-                    $this->answers[$i]->jumpto = $properties->jumpto[$i];
-                }
-                if ($this->lesson->custom && isset($properties->score[$i])) {
-                    $this->answers[$i]->score = $properties->score[$i];
-                }
-                if (!isset($this->answers[$i]->id)) {
-                    $this->answers[$i]->id =  $DB->insert_record("lesson_answers", $this->answers[$i]);
-                } else {
-                    $DB->update_record("lesson_answers", $this->answers[$i]->properties());
-                }
-
-            } else {
-                break;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Can be set to true if the page requires a static link to create a new instance
-     * instead of simply being included in the dropdown
-     * @param int $previd
-     * @return bool
-     */
-    public function add_page_link($previd) {
-        return false;
-    }
-
-    /**
-     * Returns true if a page has been viewed before
-     *
-     * @param array|int $param Either an array of pages that have been seen or the
-     *                   number of retakes a user has had
-     * @return bool
-     */
-    public function is_unseen($param) {
-        global $USER, $DB;
-        if (is_array($param)) {
-            $seenpages = $param;
-            return (!array_key_exists($this->properties->id, $seenpages));
-        } else {
-            $nretakes = $param;
-            if (!$DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id, "retry"=>$nretakes))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Checks to see if a page has been answered previously
-     * @param int $nretakes
-     * @return bool
-     */
-    public function is_unanswered($nretakes) {
-        global $DB, $USER;
-        if (!$DB->count_records("lesson_attempts", array('pageid'=>$this->properties->id, 'userid'=>$USER->id, 'correct'=>1, 'retry'=>$nretakes))) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Creates answers within the database for this lesson_page. Usually only ever
-     * called when creating a new page instance
-     * @param object $properties
-     * @return array
-     */
-    public function create_answers($properties) {
-        global $DB;
-        // now add the answers
-        $newanswer = new stdClass;
-        $newanswer->lessonid = $this->lesson->id;
-        $newanswer->pageid = $this->properties->id;
-        $newanswer->timecreated = $this->properties->timecreated;
-
-        $answers = array();
-
-        for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
-            $answer = clone($newanswer);
-            if (!empty($properties->answer_editor[$i])) {
-                $answer->answer = $properties->answer_editor[$i]['text'];
-                $answer->answerformat = $properties->answer_editor[$i]['format'];
-                if (isset($properties->response_editor[$i])) {
-                    $answer->response = $properties->response_editor[$i]['text'];
-                    $answer->responseformat = $properties->response_editor[$i]['format'];
-                }
-                if (isset($properties->jumpto[$i])) {
-                    $answer->jumpto = $properties->jumpto[$i];
-                }
-                if ($this->lesson->custom && isset($properties->score[$i])) {
-                    $answer->score = $properties->score[$i];
-                }
-                $answer->id = $DB->insert_record("lesson_answers", $answer);
-                $answers[$answer->id] = new lesson_page_answer($answer);
-            } else {
-                break;
-            }
-        }
-
-        $this->answers = $answers;
-        return $answers;
-    }
-
-    /**
-     * This method MUST be overriden by all question page types, or page types that
-     * wish to score a page.
-     *
-     * The structure of result should always be the same so it is a good idea when
-     * overriding this method on a page type to call
-     * <code>
-     * $result = parent::check_answer();
-     * </code>
-     * before modifiying it as required.
-     *
-     * @return stdClass
-     */
-    public function check_answer() {
-        $result = new stdClass;
-        $result->answerid        = 0;
-        $result->noanswer        = false;
-        $result->correctanswer   = false;
-        $result->isessayquestion = false;   // use this to turn off review button on essay questions
-        $result->response        = '';
-        $result->newpageid       = 0;       // stay on the page
-        $result->studentanswer   = '';      // use this to store student's answer(s) in order to display it on feedback page
-        $result->userresponse    = null;
-        $result->feedback        = '';
-        $result->nodefaultresponse  = false; // Flag for redirecting when default feedback is turned off
-        return $result;
-    }
-
-    /**
-     * True if the page uses a custom option
-     *
-     * Should be override and set to true if the page uses a custom option.
-     *
-     * @return bool
-     */
-    public function has_option() {
-        return false;
-    }
-
-    /**
-     * Returns the maximum number of answers for this page given the maximum number
-     * of answers permitted by the lesson.
-     *
-     * @param int $default
-     * @return int
-     */
-    public function max_answers($default) {
-        return $default;
-    }
-
-    /**
-     * Returns the properties of this lesson page as an object
-     * @return stdClass;
-     */
-    public function properties() {
-        $properties = clone($this->properties);
-        if ($this->answers === null) {
-            $this->get_answers();
-        }
-        if (count($this->answers)>0) {
-            $count = 0;
-            foreach ($this->answers as $answer) {
-                $properties->{'answer_editor['.$count.']'} = array('text'=>$answer->answer, 'format'=>$answer->answerformat);
-                $properties->{'response_editor['.$count.']'} = array('text'=>$answer->response, 'format'=>$answer->responseformat);
-                $properties->{'jumpto['.$count.']'} = $answer->jumpto;
-                $properties->{'score['.$count.']'} = $answer->score;
-                $count++;
-            }
-        }
-        return $properties;
-    }
-
-    /**
-     * Returns an array of options to display whn choosing the jumpto for a page/answer
-     * @static
-     * @param int $pageid
-     * @param lesson $lesson
-     * @return array
-     */
-    public static function get_jumptooptions($pageid, lesson $lesson) {
-        global $DB;
-        $jump = array();
-        $jump[0] = get_string("thispage", "lesson");
-        $jump[LESSON_NEXTPAGE] = get_string("nextpage", "lesson");
-        $jump[LESSON_PREVIOUSPAGE] = get_string("previouspage", "lesson");
-        $jump[LESSON_EOL] = get_string("endoflesson", "lesson");
-
-        if ($pageid == 0) {
-            return $jump;
-        }
-
-        $pages = $lesson->load_all_pages();
-        if ($pages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE || $lesson->is_sub_page_of_type($pageid, array(LESSON_PAGE_BRANCHTABLE), array(LESSON_PAGE_ENDOFBRANCH, LESSON_PAGE_CLUSTER))) {
-            $jump[LESSON_UNSEENBRANCHPAGE] = get_string("unseenpageinbranch", "lesson");
-            $jump[LESSON_RANDOMPAGE] = get_string("randompageinbranch", "lesson");
-        }
-        if($pages[$pageid]->qtype == LESSON_PAGE_CLUSTER || $lesson->is_sub_page_of_type($pageid, array(LESSON_PAGE_CLUSTER), array(LESSON_PAGE_ENDOFCLUSTER))) {
-            $jump[LESSON_CLUSTERJUMP] = get_string("clusterjump", "lesson");
-        }
-        if (!optional_param('firstpage', 0, PARAM_INT)) {
-            $apageid = $DB->get_field("lesson_pages", "id", array("lessonid" => $lesson->id, "prevpageid" => 0));
-            while (true) {
-                if ($apageid) {
-                    $title = $DB->get_field("lesson_pages", "title", array("id" => $apageid));
-                    $jump[$apageid] = strip_tags(format_string($title,true));
-                    $apageid = $DB->get_field("lesson_pages", "nextpageid", array("id" => $apageid));
-                } else {
-                    // last page reached
-                    break;
-                }
-            }
-        }
-        return $jump;
-    }
-    /**
-     * Returns the contents field for the page properly formatted and with plugin
-     * file url's converted
-     * @return string
-     */
-    public function get_contents() {
-        global $PAGE;
-        if (!empty($this->properties->contents)) {
-            if (!isset($this->properties->contentsformat)) {
-                $this->properties->contentsformat = FORMAT_HTML;
-            }
-            $context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->id);
-            return file_rewrite_pluginfile_urls($this->properties->contents, 'pluginfile.php', $context->id, 'mod_lesson', 'page_contents', $this->properties->id);
-        } else {
-            return '';
-        }
-    }
-
-    /**
-     * Set to true if this page should display in the menu block
-     * @return bool
-     */
-    protected function get_displayinmenublock() {
-        return false;
-    }
-
-    /**
-     * Get the string that describes the options of this page type
-     * @return string
-     */
-    public function option_description_string() {
-        return '';
-    }
-
-    /**
-     * Updates a table with the answers for this page
-     * @param html_table $table
-     * @return html_table
-     */
-    public function display_answers(html_table $table) {
-        $answers = $this->get_answers();
-        $i = 1;
-        foreach ($answers as $answer) {
-            $cells = array();
-            $cells[] = "<span class=\"label\">".get_string("jump", "lesson")." $i<span>: ";
-            $cells[] = $this->get_jump_name($answer->jumpto);
-            $table->data[] = new html_table_row($cells);
-            if ($i === 1){
-                $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
-            }
-            $i++;
-        }
-        return $table;
-    }
-
-    /**
-     * Determines if this page should be grayed out on the management/report screens
-     * @return int 0 or 1
-     */
-    protected function get_grayout() {
-        return 0;
-    }
-
-    /**
-     * Adds stats for this page to the &pagestats object. This should be defined
-     * for all page types that grade
-     * @param array $pagestats
-     * @param int $tries
-     * @return bool
-     */
-    public function stats(array &$pagestats, $tries) {
-        return true;
-    }
-
-    /**
-     * Formats the answers of this page for a report
-     *
-     * @param object $answerpage
-     * @param object $answerdata
-     * @param object $useranswer
-     * @param array $pagestats
-     * @param int $i Count of first level answers
-     * @param int $n Count of second level answers
-     * @return object The answer page for this
-     */
-    public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
-        $answers = $this->get_answers();
-        $formattextdefoptions = new stdClass;
-        $formattextdefoptions->para = false;  //I'll use it widely in this page
-        foreach ($answers as $answer) {
-            $data = get_string('jumpsto', 'lesson', $this->get_jump_name($answer->jumpto));
-            $answerdata->answers[] = array($data, "");
-            $answerpage->answerdata = $answerdata;
-        }
-        return $answerpage;
-    }
-
-    /**
-     * Gets an array of the jumps used by the answers of this page
-     *
-     * @return array
-     */
-    public function get_jumps() {
-        global $DB;
-        $jumps = array();
-        $params = array ("lessonid" => $this->lesson->id, "pageid" => $this->properties->id);
-        if ($answers = $this->get_answers()) {
-            foreach ($answers as $answer) {
-                $jumps[] = $this->get_jump_name($answer->jumpto);
-            }
-        }
-        return $jumps;
-    }
-    /**
-     * Informs whether this page type require manual grading or not
-     * @return bool
-     */
-    public function requires_manual_grading() {
-        return false;
-    }
-
-    /**
-     * A callback method that allows a page to override the next page a user will
-     * see during when this page is being completed.
-     * @return false|int
-     */
-    public function override_next_page() {
-        return false;
-    }
-
-    /**
-     * This method is used to determine if this page is a valid page
-     *
-     * @param array $validpages
-     * @param array $pageviews
-     * @return int The next page id to check
-     */
-    public function valid_page_and_view(&$validpages, &$pageviews) {
-        $validpages[$this->properties->id] = 1;
-        return $this->properties->nextpageid;
-    }
-}
-
-/**
- * Class used to represent an answer to a page
- *
- * @property int $id The ID of this answer in the database
- * @property int $lessonid The ID of the lesson this answer belongs to
- * @property int $pageid The ID of the page this answer belongs to
- * @property int $jumpto Identifies where the user goes upon completing a page with this answer
- * @property int $grade The grade this answer is worth
- * @property int $score The score this answer will give
- * @property int $flags Used to store options for the answer
- * @property int $timecreated A timestamp of when the answer was created
- * @property int $timemodified A timestamp of when the answer was modified
- * @property string $answer The answer itself
- * @property string $response The response the user sees if selecting this answer
- *
- * @copyright 2009 Sam Hemelryk
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class lesson_page_answer extends lesson_base {
-
-    /**
-     * Loads an page answer from the DB
-     *
-     * @param int $id
-     * @return lesson_page_answer
-     */
-    public static function load($id) {
-        global $DB;
-        $answer = $DB->get_record("lesson_answers", array("id" => $id));
-        return new lesson_page_answer($answer);
-    }
-
-    /**
-     * Given an object of properties and a page created answer(s) and saves them
-     * in the database.
-     *
-     * @param stdClass $properties
-     * @param lesson_page $page
-     * @return array
-     */
-    public static function create($properties, lesson_page $page) {
-        return $page->create_answers($properties);
-    }
-
-}
-
-/**
- * A management class for page types
- *
- * This class is responsible for managing the different pages. A manager object can
- * be retrieved by calling the following line of code:
- * <code>
- * $manager  = lesson_page_type_manager::get($lesson);
- * </code>
- * The first time the page type manager is retrieved the it includes all of the
- * different page types located in mod/lesson/pagetypes.
- *
- * @copyright 2009 Sam Hemelryk
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class lesson_page_type_manager {
-
-    /**
-     * An array of different page type classes
-     * @var array
-     */
-    protected $types = array();
-
-    /**
-     * Retrieves the lesson page type manager object
-     *
-     * If the object hasn't yet been created it is created here.
-     *
-     * @staticvar lesson_page_type_manager $pagetypemanager
-     * @param lesson $lesson
-     * @return lesson_page_type_manager
-     */
-    public static function get(lesson $lesson) {
-        static $pagetypemanager;
-        if (!($pagetypemanager instanceof lesson_page_type_manager)) {
-            $pagetypemanager = new lesson_page_type_manager();
-            $pagetypemanager->load_lesson_types($lesson);
-        }
-        return $pagetypemanager;
-    }
-
-    /**
-     * Finds and loads all lesson page types in mod/lesson/pagetypes
-     *
-     * @param lesson $lesson
-     */
-    public function load_lesson_types(lesson $lesson) {
-        global $CFG;
-        $basedir = $CFG->dirroot.'/mod/lesson/pagetypes/';
-        $dir = dir($basedir);
-        while (false !== ($entry = $dir->read())) {
-            if (strpos($entry, '.')===0 || !preg_match('#^[a-zA-Z]+\.php#i', $entry)) {
-                continue;
-            }
-            require_once($basedir.$entry);
-            $class = 'lesson_page_type_'.strtok($entry,'.');
-            if (class_exists($class)) {
-                $pagetype = new $class(new stdClass, $lesson);
-                $this->types[$pagetype->typeid] = $pagetype;
-            }
-        }
-
-    }
-
-    /**
-     * Returns an array of strings to describe the loaded page types
-     *
-     * @param int $type Can be used to return JUST the string for the requested type
-     * @return array
-     */
-    public function get_page_type_strings($type=null, $special=true) {
-        $types = array();
-        foreach ($this->types as $pagetype) {
-            if (($type===null || $pagetype->type===$type) && ($special===true || $pagetype->is_standard())) {
-                $types[$pagetype->typeid] = $pagetype->typestring;
-            }
-        }
-        return $types;
-    }
-
-    /**
-     * Returns the basic string used to identify a page type provided with an id
-     *
-     * This string can be used to instantiate or identify the page type class.
-     * If the page type id is unknown then 'unknown' is returned
-     *
-     * @param int $id
-     * @return string
-     */
-    public function get_page_type_idstring($id) {
-        foreach ($this->types as $pagetype) {
-            if ((int)$pagetype->typeid === (int)$id) {
-                return $pagetype->idstring;
-            }
-        }
-        return 'unknown';
-    }
-
-    /**
-     * Loads a page for the provided lesson given it's id
-     *
-     * This function loads a page from the lesson when given both the lesson it belongs
-     * to as well as the page's id.
-     * If the page doesn't exist an error is thrown
-     *
-     * @param int $pageid The id of the page to load
-     * @param lesson $lesson The lesson the page belongs to
-     * @return lesson_page A class that extends lesson_page
-     */
-    public function load_page($pageid, lesson $lesson) {
-        global $DB;
-        if (!($page =$DB->get_record('lesson_pages', array('id'=>$pageid, 'lessonid'=>$lesson->id)))) {
-            print_error('cannotfindpages', 'lesson');
-        }
-        $pagetype = get_class($this->types[$page->qtype]);
-        $page = new $pagetype($page, $lesson);
-        return $page;
-    }
-
-    /**
-     * This function loads ALL pages that belong to the lesson.
-     *
-     * @param lesson $lesson
-     * @return array An array of lesson_page_type_*
-     */
-    public function load_all_pages(lesson $lesson) {
-        global $DB;
-        if (!($pages =$DB->get_records('lesson_pages', array('lessonid'=>$lesson->id)))) {
-            print_error('cannotfindpages', 'lesson');
-        }
-        foreach ($pages as $key=>$page) {
-            $pagetype = get_class($this->types[$page->qtype]);
-            $pages[$key] = new $pagetype($page, $lesson);
-        }
-
-        $orderedpages = array();
-        $lastpageid = 0;
-
-        while (true) {
-            foreach ($pages as $page) {
-                if ((int)$page->prevpageid === (int)$lastpageid) {
-                    $orderedpages[$page->id] = $page;
-                    unset($pages[$page->id]);
-                    $lastpageid = $page->id;
-                    if ((int)$page->nextpageid===0) {
-                        break 2;
-                    } else {
-                        break 1;
-                    }
-                }
-            }
-        }
-
-        return $orderedpages;
-    }
-
-    /**
-     * Fetchs an mform that can be used to create/edit an page
-     *
-     * @param int $type The id for the page type
-     * @param array $arguments Any arguments to pass to the mform
-     * @return lesson_add_page_form_base
-     */
-    public function get_page_form($type, $arguments) {
-        $class = 'lesson_add_page_form_'.$this->get_page_type_idstring($type);
-        if (!class_exists($class) || get_parent_class($class)!=='lesson_add_page_form_base') {
-            debugging('Lesson page type unknown class requested '.$class, DEBUG_DEVELOPER);
-            $class = 'lesson_add_page_form_selection';
-        } else if ($class === 'lesson_add_page_form_unknown') {
-            $class = 'lesson_add_page_form_selection';
-        }
-        return new $class(null, $arguments);
-    }
-
-    /**
-     * Returns an array of links to use as add page links
-     * @param int $previd The id of the previous page
-     * @return array
-     */
-    public function get_add_page_type_links($previd) {
-        global $OUTPUT;
-
-        $links = array();
-
-        foreach ($this->types as $key=>$type) {
-            if ($link = $type->add_page_link($previd)) {
-                $links[$key] = $link;
-            }
-        }
-
-        return $links;
-    }
-}
-
-/**
- * Abstract class that page type's MUST inherit from.
- *
- * This is the abstract class that ALL add page type forms must extend.
- * You will notice that all but two of the methods this class contains are final.
- * Essentially the only thing that extending classes can do is extend custom_definition.
- * OR if it has a special requirement on creation it can extend construction_override
- *
- * @abstract
- * @copyright 2009 Sam Hemelryk
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class lesson_add_page_form_base extends moodleform {
-
-    /**
-     * This is the classic define that is used to identify this pagetype.
-     * Will be one of LESSON_*
-     * @var int
-     */
-    public $qtype;
-
-    /**
-     * The simple string that describes the page type e.g. truefalse, multichoice
-     * @var string
-     */
-    public $qtypestring;
-
-    /**
-     * An array of options used in the htmleditor
-     * @var array
-     */
-    protected $editoroptions = array();
-
-    /**
-     * True if this is a standard page of false if it does something special.
-     * Questions are standard pages, branch tables are not
-     * @var bool
-     */
-    protected $standard = true;
-
-    /**
-     * Each page type can and should override this to add any custom elements to
-     * the basic form that they want
-     */
-    public function custom_definition() {}
-
-    /**
-     * Used to determine if this is a standard page or a special page
-     * @return bool
-     */
-    public final function is_standard() {
-        return (bool)$this->standard;
-    }
-
-    /**
-     * Add the required basic elements to the form.
-     *
-     * This method adds the basic elements to the form including title and contents
-     * and then calls custom_definition();
-     */
-    public final function definition() {
-        $mform = $this->_form;
-        $editoroptions = $this->_customdata['editoroptions'];
-
-        $mform->addElement('header', 'qtypeheading', get_string('addaquestionpage', 'lesson', get_string($this->qtypestring, 'lesson')));
-
-        $mform->addElement('hidden', 'id');
-        $mform->setType('id', PARAM_INT);
-
-        $mform->addElement('hidden', 'pageid');
-        $mform->setType('pageid', PARAM_INT);
-
-        if ($this->standard === true) {
-            $mform->addElement('hidden', 'qtype');
-            $mform->setType('qtype', PARAM_TEXT);
-
-            $mform->addElement('text', 'title', get_string("pagetitle", "lesson"), array('size'=>70));
-            $mform->setType('title', PARAM_TEXT);
-            $this->editoroptions = array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$this->_customdata['maxbytes']);
-            $mform->addElement('editor', 'contents_editor', get_string("pagecontents", "lesson"), null, $this->editoroptions);
-            $mform->setType('contents_editor', PARAM_CLEANHTML);
-        }
-
-        $this->custom_definition();
-
-        if ($this->_customdata['edit'] === true) {
-            $mform->addElement('hidden', 'edit', 1);
-            $this->add_action_buttons(get_string('cancel'), get_string("savepage", "lesson"));
-        } else {
-            $this->add_action_buttons(get_string('cancel'), get_string("addaquestionpage", "lesson"));
-        }
-    }
-
-    /**
-     * Convenience function: Adds a jumpto select element
-     *
-     * @param string $name
-     * @param string|null $label
-     * @param int $selected The page to select by default
-     */
-    protected final function add_jumpto($name, $label=null, $selected=LESSON_NEXTPAGE) {
-        $title = get_string("jump", "lesson");
-        if ($label === null) {
-            $label = $title;
-        }
-        if (is_int($name)) {
-            $name = "jumpto[$name]";
-        }
-        $this->_form->addElement('select', $name, $label, $this->_customdata['jumpto']);
-        $this->_form->setDefault($name, $selected);
-        $this->_form->addHelpButton($name, 'jumps', 'lesson');
-    }
-
-    /**
-     * Convenience function: Adds a score input element
-     *
-     * @param string $name
-     * @param string|null $label
-     * @param mixed $value The default value
-     */
-    protected final function add_score($name, $label=null, $value=null) {
-        if ($label === null) {
-            $label = get_string("score", "lesson");
-        }
-        if (is_int($name)) {
-            $name = "score[$name]";
-        }
-        $this->_form->addElement('text', $name, $label, array('size'=>5));
-        if ($value !== null) {
-            $this->_form->setDefault($name, $value);
-        }
-    }
-
-    /**
-     * Convenience function: Adds an answer editor
-     *
-     * @param int $count The count of the element to add
-     */
-    protected final function add_answer($count) {
-        $this->_form->addElement('editor', 'answer_editor['.$count.']', get_string('answer', 'lesson'), null, array('noclean'=>true));
-        $this->_form->setDefault('answer_editor['.$count.']', array('text'=>'', 'format'=>FORMAT_MOODLE));
-    }
-    /**
-     * Convenience function: Adds an response editor
-     *
-     * @param int $count The count of the element to add
-     */
-    protected final function add_response($count) {
-        $this->_form->addElement('editor', 'response_editor['.$count.']', get_string('response', 'lesson'), null, array('noclean'=>true));
-        $this->_form->setDefault('response_editor['.$count.']', array('text'=>'', 'format'=>FORMAT_MOODLE));
-    }
-
-    /**
-     * A function that gets called upon init of this object by the calling script.
-     *
-     * This can be used to process an immediate action if required. Currently it
-     * is only used in special cases by non-standard page types.
-     *
-     * @return bool
-     */
-    public function construction_override() {
-        return true;
-    }
-}
index 2eaded5..e30884a 100644 (file)
  **/
 
 /** Make sure this isn't being directly accessed */
-if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
+defined('MOODLE_INTERNAL') || die();
 
 /** Include the files that are required by this module */
+require_once($CFG->dirroot.'/course/moodleform_mod.php');
 require_once($CFG->dirroot . '/mod/lesson/lib.php');
 
 /** Next page -> any page not seen before */
@@ -53,6 +52,10 @@ define("LESSON_CLUSTERJUMP", -80);
 /** Undefined */
 define("LESSON_UNDEFINED", -99);
 
+/** LESSON_MAX_EVENT_LENGTH = 432000 ; 5 days maximum */
+define("LESSON_MAX_EVENT_LENGTH", "432000");
+
+
 //////////////////////////////////////////////////////////////////////////////////////
 /// Any other lesson functions go here.  Each of them must have a name that
 /// starts with lesson_
@@ -631,3 +634,2080 @@ function lesson_get_media_html($lesson, $context) {
 
     return $code;
 }
+
+
+/**
+ * Abstract class that page type's MUST inherit from.
+ *
+ * This is the abstract class that ALL add page type forms must extend.
+ * You will notice that all but two of the methods this class contains are final.
+ * Essentially the only thing that extending classes can do is extend custom_definition.
+ * OR if it has a special requirement on creation it can extend construction_override
+ *
+ * @abstract
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class lesson_add_page_form_base extends moodleform {
+
+    /**
+     * This is the classic define that is used to identify this pagetype.
+     * Will be one of LESSON_*
+     * @var int
+     */
+    public $qtype;
+
+    /**
+     * The simple string that describes the page type e.g. truefalse, multichoice
+     * @var string
+     */
+    public $qtypestring;
+
+    /**
+     * An array of options used in the htmleditor
+     * @var array
+     */
+    protected $editoroptions = array();
+
+    /**
+     * True if this is a standard page of false if it does something special.
+     * Questions are standard pages, branch tables are not
+     * @var bool
+     */
+    protected $standard = true;
+
+    /**
+     * Each page type can and should override this to add any custom elements to
+     * the basic form that they want
+     */
+    public function custom_definition() {}
+
+    /**
+     * Used to determine if this is a standard page or a special page
+     * @return bool
+     */
+    public final function is_standard() {
+        return (bool)$this->standard;
+    }
+
+    /**
+     * Add the required basic elements to the form.
+     *
+     * This method adds the basic elements to the form including title and contents
+     * and then calls custom_definition();
+     */
+    public final function definition() {
+        $mform = $this->_form;
+        $editoroptions = $this->_customdata['editoroptions'];
+
+        $mform->addElement('header', 'qtypeheading', get_string('addaquestionpage', 'lesson', get_string($this->qtypestring, 'lesson')));
+
+        $mform->addElement('hidden', 'id');
+        $mform->setType('id', PARAM_INT);
+
+        $mform->addElement('hidden', 'pageid');
+        $mform->setType('pageid', PARAM_INT);
+
+        if ($this->standard === true) {
+            $mform->addElement('hidden', 'qtype');
+            $mform->setType('qtype', PARAM_TEXT);
+
+            $mform->addElement('text', 'title', get_string("pagetitle", "lesson"), array('size'=>70));
+            $mform->setType('title', PARAM_TEXT);
+            $this->editoroptions = array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$this->_customdata['maxbytes']);
+            $mform->addElement('editor', 'contents_editor', get_string("pagecontents", "lesson"), null, $this->editoroptions);
+            $mform->setType('contents_editor', PARAM_CLEANHTML);
+        }
+
+        $this->custom_definition();
+
+        if ($this->_customdata['edit'] === true) {
+            $mform->addElement('hidden', 'edit', 1);
+            $this->add_action_buttons(get_string('cancel'), get_string("savepage", "lesson"));
+        } else {
+            $this->add_action_buttons(get_string('cancel'), get_string("addaquestionpage", "lesson"));
+        }
+    }
+
+    /**
+     * Convenience function: Adds a jumpto select element
+     *
+     * @param string $name
+     * @param string|null $label
+     * @param int $selected The page to select by default
+     */
+    protected final function add_jumpto($name, $label=null, $selected=LESSON_NEXTPAGE) {
+        $title = get_string("jump", "lesson");
+        if ($label === null) {
+            $label = $title;
+        }
+        if (is_int($name)) {
+            $name = "jumpto[$name]";
+        }
+        $this->_form->addElement('select', $name, $label, $this->_customdata['jumpto']);
+        $this->_form->setDefault($name, $selected);
+        $this->_form->addHelpButton($name, 'jumps', 'lesson');
+    }
+
+    /**
+     * Convenience function: Adds a score input element
+     *
+     * @param string $name
+     * @param string|null $label
+     * @param mixed $value The default value
+     */
+    protected final function add_score($name, $label=null, $value=null) {
+        if ($label === null) {
+            $label = get_string("score", "lesson");
+        }
+        if (is_int($name)) {
+            $name = "score[$name]";
+        }
+        $this->_form->addElement('text', $name, $label, array('size'=>5));
+        if ($value !== null) {
+            $this->_form->setDefault($name, $value);
+        }
+    }
+
+    /**
+     * Convenience function: Adds an answer editor
+     *
+     * @param int $count The count of the element to add
+     */
+    protected final function add_answer($count) {
+        $this->_form->addElement('editor', 'answer_editor['.$count.']', get_string('answer', 'lesson'), null, array('noclean'=>true));
+        $this->_form->setDefault('answer_editor['.$count.']', array('text'=>'', 'format'=>FORMAT_MOODLE));
+    }
+    /**
+     * Convenience function: Adds an response editor
+     *
+     * @param int $count The count of the element to add
+     */
+    protected final function add_response($count) {
+        $this->_form->addElement('editor', 'response_editor['.$count.']', get_string('response', 'lesson'), null, array('noclean'=>true));
+        $this->_form->setDefault('response_editor['.$count.']', array('text'=>'', 'format'=>FORMAT_MOODLE));
+    }
+
+    /**
+     * A function that gets called upon init of this object by the calling script.
+     *
+     * This can be used to process an immediate action if required. Currently it
+     * is only used in special cases by non-standard page types.
+     *
+     * @return bool
+     */
+    public function construction_override() {
+        return true;
+    }
+}
+
+
+
+/**
+ * Class representation of a lesson
+ *
+ * This class is used the interact with, and manage a lesson once instantiated.
+ * If you need to fetch a lesson object you can do so by calling
+ *
+ * <code>
+ * lesson::load($lessonid);
+ * // or
+ * $lessonrecord = $DB->get_record('lesson', $lessonid);
+ * $lesson = new lesson($lessonrecord);
+ * </code>
+ *
+ * The class itself extends lesson_base as all classes within the lesson module should
+ *
+ * These properties are from the database
+ * @property int $id The id of this lesson
+ * @property int $course The ID of the course this lesson belongs to
+ * @property string $name The name of this lesson
+ * @property int $practice Flag to toggle this as a practice lesson
+ * @property int $modattempts Toggle to allow the user to go back and review answers
+ * @property int $usepassword Toggle the use of a password for entry
+ * @property string $password The password to require users to enter
+ * @property int $dependency ID of another lesson this lesson is dependant on
+ * @property string $conditions Conditions of the lesson dependency
+ * @property int $grade The maximum grade a user can achieve (%)
+ * @property int $custom Toggle custom scoring on or off
+ * @property int $ongoing Toggle display of an ongoing score
+ * @property int $usemaxgrade How retakes are handled (max=1, mean=0)
+ * @property int $maxanswers The max number of answers or branches
+ * @property int $maxattempts The maximum number of attempts a user can record
+ * @property int $review Toggle use or wrong answer review button
+ * @property int $nextpagedefault Override the default next page
+ * @property int $feedback Toggles display of default feedback
+ * @property int $minquestions Sets a minimum value of pages seen when calculating grades
+ * @property int $maxpages Maximum number of pages this lesson can contain
+ * @property int $retake Flag to allow users to retake a lesson
+ * @property int $activitylink Relate this lesson to another lesson
+ * @property string $mediafile File to pop up to or webpage to display
+ * @property int $mediaheight Sets the height of the media file popup
+ * @property int $mediawidth Sets the width of the media file popup
+ * @property int $mediaclose Toggle display of a media close button
+ * @property int $slideshow Flag for whether branch pages should be shown as slideshows
+ * @property int $width Width of slideshow
+ * @property int $height Height of slideshow
+ * @property string $bgcolor Background colour of slideshow
+ * @property int $displayleft Display a left meun
+ * @property int $displayleftif Sets the condition on which the left menu is displayed
+ * @property int $progressbar Flag to toggle display of a lesson progress bar
+ * @property int $highscores Flag to toggle collection of high scores
+ * @property int $maxhighscores Number of high scores to limit to
+ * @property int $available Timestamp of when this lesson becomes available
+ * @property int $deadline Timestamp of when this lesson is no longer available
+ * @property int $timemodified Timestamp when lesson was last modified
+ *
+ * These properties are calculated
+ * @property int $firstpageid Id of the first page of this lesson (prevpageid=0)
+ * @property int $lastpageid Id of the last page of this lesson (nextpageid=0)
+ *
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class lesson extends lesson_base {
+
+    /**
+     * The id of the first page (where prevpageid = 0) gets set and retrieved by
+     * {@see get_firstpageid()} by directly calling <code>$lesson->firstpageid;</code>
+     * @var int
+     */
+    protected $firstpageid = null;
+    /**
+     * The id of the last page (where nextpageid = 0) gets set and retrieved by
+     * {@see get_lastpageid()} by directly calling <code>$lesson->lastpageid;</code>
+     * @var int
+     */
+    protected $lastpageid = null;
+    /**
+     * An array used to cache the pages associated with this lesson after the first
+     * time they have been loaded.
+     * A note to developers: If you are going to be working with MORE than one or
+     * two pages from a lesson you should probably call {@see $lesson->load_all_pages()}
+     * in order to save excess database queries.
+     * @var array An array of lesson_page objects
+     */
+    protected $pages = array();
+    /**
+     * Flag that gets set to true once all of the pages associated with the lesson
+     * have been loaded.
+     * @var bool
+     */
+    protected $loadedallpages = false;
+
+    /**
+     * Simply generates a lesson object given an array/object of properties
+     * Overrides {@see lesson_base->create()}
+     * @static
+     * @param object|array $properties
+     * @return lesson
+     */
+    public static function create($properties) {
+        return new lesson($properties);
+    }
+
+    /**
+     * Generates a lesson object from the database given its id
+     * @static
+     * @param int $lessonid
+     * @return lesson
+     */
+    public static function load($lessonid) {
+        if (!$lesson = $DB->get_record('lesson', array('id' => $lessonid))) {
+            print_error('invalidcoursemodule');
+        }
+        return new lesson($lesson);
+    }
+
+    /**
+     * Deletes this lesson from the database
+     */
+    public function delete() {
+        global $CFG, $DB;
+        require_once($CFG->libdir.'/gradelib.php');
+        require_once($CFG->dirroot.'/calendar/lib.php');
+
+        $DB->delete_records("lesson", array("id"=>$this->properties->id));;
+        $DB->delete_records("lesson_pages", array("lessonid"=>$this->properties->id));
+        $DB->delete_records("lesson_answers", array("lessonid"=>$this->properties->id));
+        $DB->delete_records("lesson_attempts", array("lessonid"=>$this->properties->id));
+        $DB->delete_records("lesson_grades", array("lessonid"=>$this->properties->id));
+        $DB->delete_records("lesson_timer", array("lessonid"=>$this->properties->id));
+        $DB->delete_records("lesson_branch", array("lessonid"=>$this->properties->id));
+        $DB->delete_records("lesson_high_scores", array("lessonid"=>$this->properties->id));
+        if ($events = $DB->get_records('event', array("modulename"=>'lesson', "instance"=>$this->properties->id))) {
+            foreach($events as $event) {
+                $event = calendar_event::load($event);
+                $event->delete();
+            }
+        }
+
+        grade_update('mod/lesson', $this->properties->course, 'mod', 'lesson', $this->properties->id, 0, NULL, array('deleted'=>1));
+        return true;
+    }
+
+    /**
+     * Fetches messages from the session that may have been set in previous page
+     * actions.
+     *
+     * <code>
+     * // Do not call this method directly instead use
+     * $lesson->messages;
+     * </code>
+     *
+     * @return array
+     */
+    protected function get_messages() {
+        global $SESSION;
+
+        $messages = array();
+        if (!empty($SESSION->lesson_messages) && is_array($SESSION->lesson_messages) && array_key_exists($this->properties->id, $SESSION->lesson_messages)) {
+            $messages = $SESSION->lesson_messages[$this->properties->id];
+            unset($SESSION->lesson_messages[$this->properties->id]);
+        }
+
+        return $messages;
+    }
+
+    /**
+     * Get all of the attempts for the current user.
+     *
+     * @param int $retries
+     * @param bool $correct Optional: only fetch correct attempts
+     * @param int $pageid Optional: only fetch attempts at the given page
+     * @param int $userid Optional: defaults to the current user if not set
+     * @return array|false
+     */
+    public function get_attempts($retries, $correct=false, $pageid=null, $userid=null) {
+        global $USER, $DB;
+        $params = array("lessonid"=>$this->properties->id, "userid"=>$userid, "retry"=>$retries);
+        if ($correct) {
+            $params['correct'] = 1;
+        }
+        if ($pageid !== null) {
+            $params['pageid'] = $pageid;
+        }
+        if ($userid === null) {
+            $params['userid'] = $USER->id;
+        }
+        return $DB->get_records('lesson_attempts', $params, 'timeseen DESC');
+    }
+
+    /**
+     * Returns the first page for the lesson or false if there isn't one.
+     *
+     * This method should be called via the magic method __get();
+     * <code>
+     * $firstpage = $lesson->firstpage;
+     * </code>
+     *
+     * @return lesson_page|bool Returns the lesson_page specialised object or false
+     */
+    protected function get_firstpage() {
+        $pages = $this->load_all_pages();
+        if (count($pages) > 0) {
+            foreach ($pages as $page) {
+                if ((int)$page->prevpageid === 0) {
+                    return $page;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the last page for the lesson or false if there isn't one.
+     *
+     * This method should be called via the magic method __get();
+     * <code>
+     * $lastpage = $lesson->lastpage;
+     * </code>
+     *
+     * @return lesson_page|bool Returns the lesson_page specialised object or false
+     */
+    protected function get_lastpage() {
+        $pages = $this->load_all_pages();
+        if (count($pages) > 0) {
+            foreach ($pages as $page) {
+                if ((int)$page->nextpageid === 0) {
+                    return $page;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the id of the first page of this lesson. (prevpageid = 0)
+     * @return int
+     */
+    protected function get_firstpageid() {
+        global $DB;
+        if ($this->firstpageid == null) {
+            if (!$this->loadedallpages) {
+                $firstpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'prevpageid'=>0));
+                if (!$firstpageid) {
+                    print_error('cannotfindfirstpage', 'lesson');
+                }
+                $this->firstpageid = $firstpageid;
+            } else {
+                $firstpage = $this->get_firstpage();
+                $this->firstpageid = $firstpage->id;
+            }
+        }
+        return $this->firstpageid;
+    }
+
+    /**
+     * Returns the id of the last page of this lesson. (nextpageid = 0)
+     * @return int
+     */
+    public function get_lastpageid() {
+        global $DB;
+        if ($this->lastpageid == null) {
+            if (!$this->loadedallpages) {
+                $lastpageid = $DB->get_field('lesson_pages', 'id', array('lessonid'=>$this->properties->id, 'nextpageid'=>0));
+                if (!$lastpageid) {
+                    print_error('cannotfindlastpage', 'lesson');
+                }
+                $this->lastpageid = $lastpageid;
+            } else {
+                $lastpageid = $this->get_lastpage();
+                $this->lastpageid = $lastpageid->id;
+            }
+        }
+
+        return $this->lastpageid;
+    }
+
+     /**
+     * Gets the next page to display after the one that is provided.
+     * @param int $nextpageid
+     * @return bool
+     */
+    public function get_next_page($nextpageid) {
+        global $USER;
+        $allpages = $this->load_all_pages();
+        if ($this->properties->nextpagedefault) {
+            // in Flash Card mode...first get number of retakes
+            shuffle($allpages);
+            $found = false;
+            if ($this->properties->nextpagedefault == LESSON_UNSEENPAGE) {
+                foreach ($allpages as $nextpage) {
+                    if (!$DB->count_records("lesson_attempts", array("pageid"=>$nextpage->id, "userid"=>$USER->id, "retry"=>$nretakes))) {
+                        $found = true;
+                        break;
+                    }
+                }
+            } elseif ($this->properties->nextpagedefault == LESSON_UNANSWEREDPAGE) {
+                foreach ($allpages as $nextpage) {
+                    if (!$DB->count_records("lesson_attempts", array('pageid'=>$nextpage->id, 'userid'=>$USER->id, 'correct'=>1, 'retry'=>$nretakes))) {
+                        $found = true;
+                        break;
+                    }
+                }
+            }
+            if ($found) {
+                if ($this->properties->maxpages) {
+                    // check number of pages viewed (in the lesson)
+                    $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->properties->id, "userid"=>$USER->id));
+                    if ($DB->count_records("lesson_attempts", array("lessonid"=>$this->properties->id, "userid"=>$USER->id, "retry"=>$nretakes)) >= $this->properties->maxpages) {
+                        return false;
+                    }
+                }
+                return $nextpage;
+            }
+        }
+        // In a normal lesson mode
+        foreach ($allpages as $nextpage) {
+            if ((int)$nextpage->id===(int)$nextpageid) {
+                return $nextpage;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sets a message against the session for this lesson that will displayed next
+     * time the lesson processes messages
+     *
+     * @param string $message
+     * @param string $class
+     * @param string $align
+     * @return bool
+     */
+    public function add_message($message, $class="notifyproblem", $align='center') {
+        global $SESSION;
+
+        if (empty($SESSION->lesson_messages) || !is_array($SESSION->lesson_messages)) {
+            $SESSION->lesson_messages = array();
+            $SESSION->lesson_messages[$this->properties->id] = array();
+        } else if (!array_key_exists($this->properties->id, $SESSION->lesson_messages)) {
+            $SESSION->lesson_messages[$this->properties->id] = array();
+        }
+
+        $SESSION->lesson_messages[$this->properties->id][] = array($message, $class, $align);
+
+        return true;
+    }
+
+    /**
+     * Check if the lesson is accessible at the present time
+     * @return bool True if the lesson is accessible, false otherwise
+     */
+    public function is_accessible() {
+        $available = $this->properties->available;
+        $deadline = $this->properties->deadline;
+        return (($available == 0 || time() >= $available) && ($deadline == 0 || time() < $deadline));
+    }
+
+    /**
+     * Starts the lesson time for the current user
+     * @return bool Returns true
+     */
+    public function start_timer() {
+        global $USER, $DB;
+        $USER->startlesson[$this->properties->id] = true;
+        $startlesson = new stdClass;
+        $startlesson->lessonid = $this->properties->id;
+        $startlesson->userid = $USER->id;
+        $startlesson->starttime = time();
+        $startlesson->lessontime = time();
+        $DB->insert_record('lesson_timer', $startlesson);
+        if ($this->properties->timed) {
+            $this->add_message(get_string('maxtimewarning', 'lesson', $this->properties->maxtime), 'center');
+        }
+        return true;
+    }
+
+    /**
+     * Updates the timer to the current time and returns the new timer object
+     * @param bool $restart If set to true the timer is restarted
+     * @param bool $continue If set to true AND $restart=true then the timer
+     *                        will continue from a previous attempt
+     * @return stdClass The new timer
+     */
+    public function update_timer($restart=false, $continue=false) {
+        global $USER, $DB;
+        // clock code
+        // get time information for this user
+        if (!$timer = $DB->get_records('lesson_timer', array ("lessonid" => $this->properties->id, "userid" => $USER->id), 'starttime DESC', '*', 0, 1)) {
+            print_error('cannotfindtimer', 'lesson');
+        } else {
+            $timer = current($timer); // this will get the latest start time record
+        }
+
+        if ($restart) {
+            if ($continue) {
+                // continue a previous test, need to update the clock  (think this option is disabled atm)
+                $timer->starttime = time() - ($timer->lessontime - $timer->starttime);
+            } else {
+                // starting over, so reset the clock
+                $timer->starttime = time();
+            }
+        }
+
+        $timer->lessontime = time();
+        $DB->update_record('lesson_timer', $timer);
+        return $timer;
+    }
+
+    /**
+     * Updates the timer to the current time then stops it by unsetting the user var
+     * @return bool Returns true
+     */
+    public function stop_timer() {
+        global $USER, $DB;
+        unset($USER->startlesson[$this->properties->id]);
+        return $this->update_timer(false, false);
+    }
+
+    /**
+     * Checks to see if the lesson has pages
+     */
+    public function has_pages() {
+        global $DB;
+        $pagecount = $DB->count_records('lesson_pages', array('lessonid'=>$this->properties->id));
+        return ($pagecount>0);
+    }
+
+    /**
+     * Returns the link for the related activity
+     * @return array|false
+     */
+    public function link_for_activitylink() {
+        global $DB;
+        $module = $DB->get_record('course_modules', array('id' => $this->properties->activitylink));
+        if ($module) {
+            $modname = $DB->get_field('modules', 'name', array('id' => $module->module));
+            if ($modname) {
+                $instancename = $DB->get_field($modname, 'name', array('id' => $module->instance));
+                if ($instancename) {
+                    return html_writer::link(new moodle_url('/mod/'.$modname.'/view.php', array('id'=>$this->properties->activitylink)),
+                        get_string('returnto', 'lesson', get_string('activitylinkname', 'lesson', $instancename)),
+                        array('class'=>'centerpadded lessonbutton standardbutton'));
+                }
+            }
+        }
+        return '';
+    }
+
+    /**
+     * Loads the requested page.
+     *
+     * This function will return the requested page id as either a specialised
+     * lesson_page object OR as a generic lesson_page.
+     * If the page has been loaded previously it will be returned from the pages
+     * array, otherwise it will be loaded from the database first
+     *
+     * @param int $pageid
+     * @return lesson_page A lesson_page object or an object that extends it
+     */
+    public function load_page($pageid) {
+        if (!array_key_exists($pageid, $this->pages)) {
+            $manager = lesson_page_type_manager::get($this);
+            $this->pages[$pageid] = $manager->load_page($pageid, $this);
+        }
+        return $this->pages[$pageid];
+    }
+
+    /**
+     * Loads ALL of the pages for this lesson
+     *
+     * @return array An array containing all pages from this lesson
+     */
+    public function load_all_pages() {
+        if (!$this->loadedallpages) {
+            $manager = lesson_page_type_manager::get($this);
+            $this->pages = $manager->load_all_pages($this);
+            $this->loadedallpages = true;
+        }
+        return $this->pages;
+    }
+
+    /**
+     * Determins if a jumpto value is correct or not.
+     *
+     * returns true if jumpto page is (logically) after the pageid page or
+     * if the jumpto value is a special value.  Returns false in all other cases.
+     *
+     * @param int $pageid Id of the page from which you are jumping from.
+     * @param int $jumpto The jumpto number.
+     * @return boolean True or false after a series of tests.
+     **/
+    public function jumpto_is_correct($pageid, $jumpto) {
+        global $DB;
+
+        // first test the special values
+        if (!$jumpto) {
+            // same page
+            return false;
+        } elseif ($jumpto == LESSON_NEXTPAGE) {
+            return true;
+        } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
+            return true;
+        } elseif ($jumpto == LESSON_RANDOMPAGE) {
+            return true;
+        } elseif ($jumpto == LESSON_CLUSTERJUMP) {
+            return true;
+        } elseif ($jumpto == LESSON_EOL) {
+            return true;
+        }
+
+        $pages = $this->load_all_pages();
+        $apageid = $pages[$pageid]->nextpageid;
+        while ($apageid != 0) {
+            if ($jumpto == $apageid) {
+                return true;
+            }
+            $apageid = $pages[$apageid]->nextpageid;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the time a user has remaining on this lesson
+     * @param int $starttime Starttime timestamp
+     * @return string
+     */
+    public function time_remaining($starttime) {
+        $timeleft = $starttime + $this->maxtime * 60 - time();
+        $hours = floor($timeleft/3600);
+        $timeleft = $timeleft - ($hours * 3600);
+        $minutes = floor($timeleft/60);
+        $secs = $timeleft - ($minutes * 60);
+
+        if ($minutes < 10) {
+            $minutes = "0$minutes";
+        }
+        if ($secs < 10) {
+            $secs = "0$secs";
+        }
+        $output   = array();
+        $output[] = $hours;
+        $output[] = $minutes;
+        $output[] = $secs;
+        $output = implode(':', $output);
+        return $output;
+    }
+
+    /**
+     * Interprets LESSON_CLUSTERJUMP jumpto value.
+     *
+     * This will select a page randomly
+     * and the page selected will be inbetween a cluster page and end of cluter or end of lesson
+     * and the page selected will be a page that has not been viewed already
+     * and if any pages are within a branch table or end of branch then only 1 page within
+     * the branch table or end of branch will be randomly selected (sub clustering).
+     *
+     * @param int $pageid Id of the current page from which we are jumping from.
+     * @param int $userid Id of the user.
+     * @return int The id of the next page.
+     **/
+    public function cluster_jump($pageid, $userid=null) {
+        global $DB, $USER;
+
+        if ($userid===null) {
+            $userid = $USER->id;
+        }
+        // get the number of retakes
+        if (!$retakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->properties->id, "userid"=>$userid))) {
+            $retakes = 0;
+        }
+        // get all the lesson_attempts aka what the user has seen
+        $seenpages = array();
+        if ($attempts = $this->get_attempts($retakes)) {
+            foreach ($attempts as $attempt) {
+                $seenpages[$attempt->pageid] = $attempt->pageid;
+            }
+
+        }
+
+        // get the lesson pages
+        $lessonpages = $this->load_all_pages();
+        // find the start of the cluster
+        while ($pageid != 0) { // this condition should not be satisfied... should be a cluster page
+            if ($lessonpages[$pageid]->qtype == LESSON_PAGE_CLUSTER) {
+                break;
+            }
+            $pageid = $lessonpages[$pageid]->prevpageid;
+        }
+
+        $clusterpages = array();
+        $clusterpages = $this->get_sub_pages_of($pageid, array(LESSON_PAGE_ENDOFCLUSTER));
+        $unseen = array();
+        foreach ($clusterpages as $key=>$cluster) {
+            if ($cluster->type !== lesson_page::TYPE_QUESTION) {
+                unset($clusterpages[$key]);
+            } elseif ($cluster->is_unseen($seenpages)) {
+                $unseen[] = $cluster;
+            }
+        }
+
+        if (count($unseen) > 0) {
+            // it does not contain elements, then use exitjump, otherwise find out next page/branch
+            $nextpage = $unseen[rand(0, count($unseen)-1)];
+            if ($nextpage->qtype == LESSON_PAGE_BRANCHTABLE) {
+                // if branch table, then pick a random page inside of it
+                $branchpages = $this->get_sub_pages_of($nextpage->id, array(LESSON_PAGE_BRANCHTABLE, LESSON_PAGE_ENDOFBRANCH));
+                return $branchpages[rand(0, count($branchpages)-1)]->id;
+            } else { // otherwise, return the page's id
+                return $nextpage->id;
+            }
+        } else {
+            // seen all there is to see, leave the cluster
+            if (end($clusterpages)->nextpageid == 0) {
+                return LESSON_EOL;
+            } else {
+                $clusterendid = $pageid;
+                while ($clusterendid != 0) { // this condition should not be satisfied... should be a cluster page
+                    if ($lessonpages[$clusterendid]->qtype == LESSON_PAGE_CLUSTER) {
+                        break;
+                    }
+                    $clusterendid = $lessonpages[$clusterendid]->prevpageid;
+                }
+                $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $clusterendid, "lessonid" => $this->properties->id));
+                if ($exitjump == LESSON_NEXTPAGE) {
+                    $exitjump = $lessonpages[$pageid]->nextpageid;
+                }
+                if ($exitjump == 0) {
+                    return LESSON_EOL;
+                } else if (in_array($exitjump, array(LESSON_EOL, LESSON_PREVIOUSPAGE))) {
+                    return $exitjump;
+                } else {
+                    if (!array_key_exists($exitjump, $lessonpages)) {
+                        $found = false;
+                        foreach ($lessonpages as $page) {
+                            if ($page->id === $clusterendid) {
+                                $found = true;
+                            } else if ($page->qtype == LESSON_PAGE_ENDOFCLUSTER) {
+                                $exitjump = $DB->get_field("lesson_answers", "jumpto", array("pageid" => $page->id, "lessonid" => $this->properties->id));
+                                break;
+                            }
+                        }
+                    }
+                    if (!array_key_exists($exitjump, $lessonpages)) {
+                        return LESSON_EOL;
+                    }
+                    return $exitjump;
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds all pages that appear to be a subtype of the provided pageid until
+     * an end point specified within $ends is encountered or no more pages exist
+     *
+     * @param int $pageid
+     * @param array $ends An array of LESSON_PAGE_* types that signify an end of
+     *               the subtype
+     * @return array An array of specialised lesson_page objects
+     */
+    public function get_sub_pages_of($pageid, array $ends) {
+        $lessonpages = $this->load_all_pages();
+        $pageid = $lessonpages[$pageid]->nextpageid;  // move to the first page after the branch table
+        $pages = array();
+
+        while (true) {
+            if ($pageid == 0 || in_array($lessonpages[$pageid]->qtype, $ends)) {
+                break;
+            }
+            $pages[] = $lessonpages[$pageid];
+            $pageid = $lessonpages[$pageid]->nextpageid;
+        }
+
+        return $pages;
+    }
+
+    /**
+     * Checks to see if the specified page[id] is a subpage of a type specified in
+     * the $types array, until either there are no more pages of we find a type
+     * corrosponding to that of a type specified in $ends
+     *
+     * @param int $pageid The id of the page to check
+     * @param array $types An array of types that would signify this page was a subpage
+     * @param array $ends An array of types that mean this is not a subpage
+     * @return bool
+     */
+    public function is_sub_page_of_type($pageid, array $types, array $ends) {
+        $pages = $this->load_all_pages();
+        $pageid = $pages[$pageid]->prevpageid; // move up one
+
+        array_unshift($ends, 0);
+        // go up the pages till branch table
+        while (true) {
+            if ($pageid==0 || in_array($pages[$pageid]->qtype, $ends)) {
+                return false;
+            } else if (in_array($pages[$pageid]->qtype, $types)) {
+                return true;
+            }
+            $pageid = $pages[$pageid]->prevpageid;
+        }
+    }
+}
+
+
+/**
+ * Abstract class to provide a core functions to the all lesson classes
+ *
+ * This class should be abstracted by ALL classes with the lesson module to ensure
+ * that all classes within this module can be interacted with in the same way.
+ *
+ * This class provides the user with a basic properties array that can be fetched
+ * or set via magic methods, or alternativily by defining methods get_blah() or
+ * set_blah() within the extending object.
+ *
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class lesson_base {
+
+    /**
+     * An object containing properties
+     * @var stdClass
+     */
+    protected $properties;
+
+    /**
+     * The constructor
+     * @param stdClass $properties
+     */
+    public function __construct($properties) {
+        $this->properties = (object)$properties;
+    }
+
+    /**
+     * Magic property method
+     *
+     * Attempts to call a set_$key method if one exists otherwise falls back
+     * to simply set the property
+     *
+     * @param string $key
+     * @param mixed $value
+     */
+    public function __set($key, $value) {
+        if (method_exists($this, 'set_'.$key)) {
+            $this->{'set_'.$key}($value);
+        }
+        $this->properties->{$key} = $value;
+    }
+
+    /**
+     * Magic get method
+     *
+     * Attempts to call a get_$key method to return the property and ralls over
+     * to return the raw property
+     *
+     * @param str $key
+     * @return mixed
+     */
+    public function __get($key) {
+        if (method_exists($this, 'get_'.$key)) {
+            return $this->{'get_'.$key}();
+        }
+        return $this->properties->{$key};
+    }
+
+    /**
+     * Stupid PHP needs an isset magic method if you use the get magic method and
+     * still want empty calls to work.... blah ~!
+     *
+     * @param string $key
+     * @return bool
+     */
+    public function __isset($key) {
+        if (method_exists($this, 'get_'.$key)) {
+            $val = $this->{'get_'.$key}();
+            return !empty($val);
+        }
+        return !empty($this->properties->{$key});
+    }
+
+    /**
+     * If overriden should create a new instance, save it in the DB and return it
+     */
+    public static function create() {}
+    /**
+     * If overriden should load an instance from the DB and return it
+     */
+    public static function load() {}
+    /**
+     * Fetches all of the properties of the object
+     * @return stdClass
+     */
+    public function properties() {
+        return $this->properties;
+    }
+}
+
+
+/**
+ * Abstract class representation of a page associated with a lesson.
+ *
+ * This class should MUST be extended by all specialised page types defined in
+ * mod/lesson/pagetypes/.
+ * There are a handful of abstract methods that need to be defined as well as
+ * severl methods that can optionally be defined in order to make the page type
+ * operate in the desired way
+ *
+ * Database properties
+ * @property int $id The id of this lesson page
+ * @property int $lessonid The id of the lesson this page belongs to
+ * @property int $prevpageid The id of the page before this one
+ * @property int $nextpageid The id of the next page in the page sequence
+ * @property int $qtype Identifies the page type of this page
+ * @property int $qoption Used to record page type specific options
+ * @property int $layout Used to record page specific layout selections
+ * @property int $display Used to record page specific display selections
+ * @property int $timecreated Timestamp for when the page was created
+ * @property int $timemodified Timestamp for when the page was last modified
+ * @property string $title The title of this page
+ * @property string $contents The rich content shown to describe the page
+ * @property int $contentsformat The format of the contents field
+ *
+ * Calculated properties
+ * @property-read array $answers An array of answers for this page
+ * @property-read bool $displayinmenublock Toggles display in the left menu block
+ * @property-read array $jumps An array containing all the jumps this page uses
+ * @property-read lesson $lesson The lesson this page belongs to
+ * @property-read int $type The type of the page [question | structure]
+ * @property-read typeid The unique identifier for the page type
+ * @property-read typestring The string that describes this page type
+ *
+ * @abstract
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class lesson_page extends lesson_base {
+
+    /**
+     * A reference to the lesson this page belongs to
+     * @var lesson
+     */
+    protected $lesson = null;
+    /**
+     * Contains the answers to this lesson_page once loaded
+     * @var null|array
+     */
+    protected $answers = null;
+    /**
+     * This sets the type of the page, can be one of the constants defined below
+     * @var int
+     */
+    protected $type = 0;
+
+    /**
+     * Constants used to identify the type of the page
+     */
+    const TYPE_QUESTION = 0;
+    const TYPE_STRUCTURE = 1;
+
+    /**
+     * This method should return the integer used to identify the page type within
+     * the database and thoughout code. This maps back to the defines used in 1.x
+     * @abstract
+     * @return int
+     */
+    abstract protected function get_typeid();
+    /**
+     * This method should return the string that describes the pagetype
+     * @abstract
+     * @return string
+     */
+    abstract protected function get_typestring();
+
+    /**
+     * This method gets called to display the page to the user taking the lesson
+     * @abstract
+     * @param object $renderer
+     * @param object $attempt
+     * @return string
+     */
+    abstract public function display($renderer, $attempt);
+
+    /**
+     * Creates a new lesson_page within the database and returns the correct pagetype
+     * object to use to interact with the new lesson
+     *
+     * @final
+     * @static
+     * @param object $properties
+     * @param lesson $lesson
+     * @return lesson_page Specialised object that extends lesson_page
+     */
+    final public static function create($properties, lesson $lesson, $context, $maxbytes) {
+        global $DB;
+        $newpage = new stdClass;
+        $newpage->title = $properties->title;
+        $newpage->contents = $properties->contents_editor['text'];
+        $newpage->contentsformat = $properties->contents_editor['format'];
+        $newpage->lessonid = $lesson->id;
+        $newpage->timecreated = time();
+        $newpage->qtype = $properties->qtype;
+        $newpage->qoption = (isset($properties->qoption))?1:0;
+        $newpage->layout = (isset($properties->layout))?1:0;
+        $newpage->display = (isset($properties->display))?1:0;
+        $newpage->prevpageid = 0; // this is a first page
+        $newpage->nextpageid = 0; // this is the only page
+
+        if ($properties->pageid) {
+            $prevpage = $DB->get_record("lesson_pages", array("id" => $properties->pageid), 'id, nextpageid');
+            if (!$prevpage) {
+                print_error('cannotfindpages', 'lesson');
+            }
+            $newpage->prevpageid = $prevpage->id;
+            $newpage->nextpageid = $prevpage->nextpageid;
+        } else {
+            $nextpage = $DB->get_record('lesson_pages', array('lessonid'=>$lesson->id, 'prevpageid'=>0), 'id');
+            if ($nextpage) {
+                // This is the first page, there are existing pages put this at the start
+                $newpage->nextpageid = $nextpage->id;
+            }
+        }
+
+        $newpage->id = $DB->insert_record("lesson_pages", $newpage);
+
+        $editor = new stdClass;
+        $editor->id = $newpage->id;
+        $editor->contents_editor = $properties->contents_editor;
+        $editor = file_postupdate_standard_editor($editor, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$maxbytes), $context, 'mod_lesson', 'page_contents', $editor->id);
+        $DB->update_record("lesson_pages", $editor);
+
+        if ($newpage->prevpageid > 0) {
+            $DB->set_field("lesson_pages", "nextpageid", $newpage->id, array("id" => $newpage->prevpageid));
+        }
+        if ($newpage->nextpageid > 0) {
+            $DB->set_field("lesson_pages", "prevpageid", $newpage->id, array("id" => $newpage->nextpageid));
+        }
+
+        $page = lesson_page::load($newpage, $lesson);
+        $page->create_answers($properties);
+
+        $lesson->add_message(get_string('insertedpage', 'lesson').': '.format_string($newpage->title, true), 'notifysuccess');
+
+        return $page;
+    }
+
+    /**
+     * This method loads a page object from the database and returns it as a
+     * specialised object that extends lesson_page
+     *
+     * @final
+     * @static
+     * @param int $id
+     * @param lesson $lesson
+     * @return lesson_page Specialised lesson_page object
+     */
+    final public static function load($id, lesson $lesson) {
+        global $DB;
+
+        if (is_object($id) && !empty($id->qtype)) {
+            $page = $id;
+        } else {
+            $page = $DB->get_record("lesson_pages", array("id" => $id));
+            if (!$page) {
+                print_error('cannotfindpages', 'lesson');
+            }
+        }
+        $manager = lesson_page_type_manager::get($lesson);
+
+        $class = 'lesson_page_type_'.$manager->get_page_type_idstring($page->qtype);
+        if (!class_exists($class)) {
+            $class = 'lesson_page';
+        }
+
+        return new $class($page, $lesson);
+    }
+
+    /**
+     * Deletes a lesson_page from the database as well as any associated records.
+     * @final
+     * @return bool
+     */
+    final public function delete() {
+        global $DB;
+        // first delete all the associated records...
+        $DB->delete_records("lesson_attempts", array("pageid" => $this->properties->id));
+        // ...now delete the answers...
+        $DB->delete_records("lesson_answers", array("pageid" => $this->properties->id));
+        // ..and the page itself
+        $DB->delete_records("lesson_pages", array("id" => $this->properties->id));
+
+        // repair the hole in the linkage
+        if (!$this->properties->prevpageid && !$this->properties->nextpageid) {
+            //This is the only page, no repair needed
+        } elseif (!$this->properties->prevpageid) {
+            // this is the first page...
+            $page = $this->lesson->load_page($this->properties->nextpageid);
+            $page->move(null, 0);
+        } elseif (!$this->properties->nextpageid) {
+            // this is the last page...
+            $page = $this->lesson->load_page($this->properties->prevpageid);
+            $page->move(0);
+        } else {
+            // page is in the middle...
+            $prevpage = $this->lesson->load_page($this->properties->prevpageid);
+            $nextpage = $this->lesson->load_page($this->properties->nextpageid);
+
+            $prevpage->move($nextpage->id);
+            $nextpage->move(null, $prevpage->id);
+        }
+        return true;
+    }
+
+    /**
+     * Moves a page by updating its nextpageid and prevpageid values within
+     * the database
+     *
+     * @final
+     * @param int $nextpageid
+     * @param int $prevpageid
+     */
+    final public function move($nextpageid=null, $prevpageid=null) {
+        global $DB;
+        if ($nextpageid === null) {
+            $nextpageid = $this->properties->nextpageid;
+        }
+        if ($prevpageid === null) {
+            $prevpageid = $this->properties->prevpageid;
+        }
+        $obj = new stdClass;
+        $obj->id = $this->properties->id;
+        $obj->prevpageid = $prevpageid;
+        $obj->nextpageid = $nextpageid;
+        $DB->update_record('lesson_pages', $obj);
+    }
+
+    /**
+     * Returns the answers that are associated with this page in the database
+     *
+     * @final
+     * @return array
+     */
+    final public function get_answers() {
+        global $DB;
+        if ($this->answers === null) {
+            $this->answers = array();
+            $answers = $DB->get_records('lesson_answers', array('pageid'=>$this->properties->id, 'lessonid'=>$this->lesson->id), 'id');
+            if (!$answers) {
+                debugging(get_string('cannotfindanswer', 'lesson'));
+                return array();
+            }
+            foreach ($answers as $answer) {
+                $this->answers[count($this->answers)] = new lesson_page_answer($answer);
+            }
+        }
+        return $this->answers;
+    }
+
+    /**
+     * Returns the lesson this page is associated with
+     * @final
+     * @return lesson
+     */
+    final protected function get_lesson() {
+        return $this->lesson;
+    }
+
+    /**
+     * Returns the type of page this is. Not to be confused with page type
+     * @final
+     * @return int
+     */
+    final protected function get_type() {
+        return $this->type;
+    }
+
+    /**
+     * Records an attempt at this page
+     *
+     * @final
+     * @param stdClass $context
+     * @return stdClass Returns the result of the attempt
+     */
+    final public function record_attempt($context) {
+        global $DB, $USER, $OUTPUT;
+
+        /**
+         * This should be overriden by each page type to actually check the response
+         * against what ever custom criteria they have defined
+         */
+        $result = $this->check_answer();
+
+        $result->attemptsremaining  = 0;
+        $result->maxattemptsreached = false;
+
+        if ($result->noanswer) {
+            $result->newpageid = $this->properties->id; // display same page again
+            $result->feedback  = get_string('noanswer', 'lesson');
+        } else {
+            if (!has_capability('mod/lesson:manage', $context)) {
+                $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->lesson->id, "userid"=>$USER->id));
+                // record student's attempt
+                $attempt = new stdClass;
+                $attempt->lessonid = $this->lesson->id;
+                $attempt->pageid = $this->properties->id;
+                $attempt->userid = $USER->id;
+                $attempt->answerid = $result->answerid;
+                $attempt->retry = $nretakes;
+                $attempt->correct = $result->correctanswer;
+                if($result->userresponse !== null) {
+                    $attempt->useranswer = $result->userresponse;
+                }
+
+                $attempt->timeseen = time();
+                // if allow modattempts, then update the old attempt record, otherwise, insert new answer record
+                if (isset($USER->modattempts[$this->lesson->id])) {
+                    $attempt->retry = $nretakes - 1; // they are going through on review, $nretakes will be too high
+                }
+
+                $DB->insert_record("lesson_attempts", $attempt);
+                // "number of attempts remaining" message if $this->lesson->maxattempts > 1
+                // displaying of message(s) is at the end of page for more ergonomic display
+                if (!$result->correctanswer && ($result->newpageid == 0)) {
+                    // wrong answer and student is stuck on this page - check how many attempts
+                    // the student has had at this page/question
+                    $nattempts = $DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id),"retry", $nretakes);
+                    // retreive the number of attempts left counter for displaying at bottom of feedback page
+                    if ($nattempts >= $this->lesson->maxattempts) {
+                        if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
+                            $result->maxattemptsreached = true;
+                        }
+                        $result->newpageid = LESSON_NEXTPAGE;
+                    } else if ($this->lesson->maxattempts > 1) { // don't bother with message if only one attempt
+                        $result->attemptsremaining = $this->lesson->maxattempts - $nattempts;
+                    }
+                }
+            }
+            // TODO: merge this code with the jump code below.  Convert jumpto page into a proper page id
+            if ($result->newpageid == 0) {
+                $result->newpageid = $this->properties->id;
+            } elseif ($result->newpageid == LESSON_NEXTPAGE) {
+                $nextpage = $this->lesson->get_next_page($this->properties->nextpageid);
+                if ($nextpage === false) {
+                    $result->newpageid = LESSON_EOL;
+                } else {
+                    $result->newpageid = $nextpage->id;
+                }
+            }
+
+            // Determine default feedback if necessary
+            if (empty($result->response)) {
+                if (!$this->lesson->feedback && !$result->noanswer && !($this->lesson->review & !$result->correctanswer && !$result->isessayquestion)) {
+                    // These conditions have been met:
+                    //  1. The lesson manager has not supplied feedback to the student
+                    //  2. Not displaying default feedback
+                    //  3. The user did provide an answer
+                    //  4. We are not reviewing with an incorrect answer (and not reviewing an essay question)
+
+                    $result->nodefaultresponse = true;  // This will cause a redirect below
+                } else if ($result->isessayquestion) {
+                    $result->response = get_string('defaultessayresponse', 'lesson');
+                } else if ($result->correctanswer) {
+                    $result->response = get_string('thatsthecorrectanswer', 'lesson');
+                } else {
+                    $result->response = get_string('thatsthewronganswer', 'lesson');
+                }
+            }
+
+            if ($result->response) {
+                if ($this->lesson->review && !$result->correctanswer && !$result->isessayquestion) {
+                    $nretakes = $DB->count_records("lesson_grades", array("lessonid"=>$this->lesson->id, "userid"=>$USER->id));
+                    $qattempts = $DB->count_records("lesson_attempts", array("userid"=>$USER->id, "retry"=>$nretakes, "pageid"=>$this->properties->id));
+                    if ($qattempts == 1) {
+                        $result->feedback = $OUTPUT->box(get_string("firstwrong", "lesson"), 'feedback');
+                    } else {
+                        $result->feedback = $OUTPUT->BOX(get_string("secondpluswrong", "lesson"), 'feedback');
+                    }
+                } else {
+                    $class = 'response';
+                    if ($result->correctanswer) {
+                        $class .= ' correct'; //CSS over-ride this if they exist (!important)
+                    } else if (!$result->isessayquestion) {
+                        $class .= ' incorrect'; //CSS over-ride this if they exist (!important)
+                    }
+                    $options = new stdClass;
+                    $options->noclean = true;
+                    $options->para = true;
+                    $result->feedback = $OUTPUT->box(format_text($this->properties->contents, $this->properties->contentsformat, $options), 'generalbox boxaligncenter');
+                    $result->feedback .= '<div class="correctanswer generalbox"><em>'.get_string("youranswer", "lesson").'</em> : '.$result->studentanswer; // already in clean html
+                    $result->feedback .= $OUTPUT->box($result->response, $class); // already conerted to HTML
+                    echo "</div>";
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns the string for a jump name
+     *
+     * @final
+     * @param int $jumpto Jump code or page ID
+     * @return string
+     **/
+    final protected function get_jump_name($jumpto) {
+        global $DB;
+        static $jumpnames = array();
+
+        if (!array_key_exists($jumpto, $jumpnames)) {
+            if ($jumpto == 0) {
+                $jumptitle = get_string('thispage', 'lesson');
+            } elseif ($jumpto == LESSON_NEXTPAGE) {
+                $jumptitle = get_string('nextpage', 'lesson');
+            } elseif ($jumpto == LESSON_EOL) {
+                $jumptitle = get_string('endoflesson', 'lesson');
+            } elseif ($jumpto == LESSON_UNSEENBRANCHPAGE) {
+                $jumptitle = get_string('unseenpageinbranch', 'lesson');
+            } elseif ($jumpto == LESSON_PREVIOUSPAGE) {
+                $jumptitle = get_string('previouspage', 'lesson');
+            } elseif ($jumpto == LESSON_RANDOMPAGE) {
+                $jumptitle = get_string('randompageinbranch', 'lesson');
+            } elseif ($jumpto == LESSON_RANDOMBRANCH) {
+                $jumptitle = get_string('randombranch', 'lesson');
+            } elseif ($jumpto == LESSON_CLUSTERJUMP) {
+                $jumptitle = get_string('clusterjump', 'lesson');
+            } else {
+                if (!$jumptitle = $DB->get_field('lesson_pages', 'title', array('id' => $jumpto))) {
+                    $jumptitle = '<strong>'.get_string('notdefined', 'lesson').'</strong>';
+                }
+            }
+            $jumpnames[$jumpto] = format_string($jumptitle,true);
+        }
+
+        return $jumpnames[$jumpto];
+    }
+
+    /**
+     * Construstor method
+     * @param object $properties
+     * @param lesson $lesson
+     */
+    public function __construct($properties, lesson $lesson) {
+        parent::__construct($properties);
+        $this->lesson = $lesson;
+    }
+
+    /**
+     * Returns the score for the attempt
+     * This may be overriden by page types that require manual grading
+     * @param array $answers
+     * @param object $attempt
+     * @return int
+     */
+    public function earned_score($answers, $attempt) {
+        return $answers[$attempt->answerid]->score;
+    }
+
+    /**
+     * This is a callback method that can be override and gets called when ever a page
+     * is viewed
+     *
+     * @param bool $canmanage True if the user has the manage cap
+     * @return mixed
+     */
+    public function callback_on_view($canmanage) {
+        return true;
+    }
+
+    /**
+     * Updates a lesson page and its answers within the database
+     *
+     * @param object $properties
+     * @return bool
+     */
+    public function update($properties, $context = null, $maxbytes = null) {
+        global $DB;
+        $answers  = $this->get_answers();
+        $properties->id = $this->properties->id;
+        $properties->lessonid = $this->lesson->id;
+        if (empty($properties->qoption)) {
+            $properties->qoption = '0';
+        }
+        if (empty($context)) {
+            $context = $PAGE->context;
+        }
+        if ($maxbytes === null) {
+            $maxbytes =get_max_upload_file_size();
+        }
+        $properties = file_postupdate_standard_editor($properties, 'contents', array('noclean'=>true, 'maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$maxbytes), $context, 'mod_lesson', 'page_contents', $properties->id);
+        $DB->update_record("lesson_pages", $properties);
+
+        for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
+            if (!array_key_exists($i, $this->answers)) {
+                $this->answers[$i] = new stdClass;
+                $this->answers[$i]->lessonid = $this->lesson->id;
+                $this->answers[$i]->pageid = $this->id;
+                $this->answers[$i]->timecreated = $this->timecreated;
+            }
+            if (!empty($properties->answer_editor[$i])) {
+                $this->answers[$i]->answer = $properties->answer_editor[$i]['text'];
+                $this->answers[$i]->answerformat = $properties->answer_editor[$i]['format'];
+                if (isset($properties->response_editor[$i])) {
+                    $this->answers[$i]->response = $properties->response_editor[$i]['text'];
+                    $this->answers[$i]->responseformat = $properties->response_editor[$i]['format'];
+                }
+                if (isset($properties->jumpto[$i])) {
+                    $this->answers[$i]->jumpto = $properties->jumpto[$i];
+                }
+                if ($this->lesson->custom && isset($properties->score[$i])) {
+                    $this->answers[$i]->score = $properties->score[$i];
+                }
+                if (!isset($this->answers[$i]->id)) {
+                    $this->answers[$i]->id =  $DB->insert_record("lesson_answers", $this->answers[$i]);
+                } else {
+                    $DB->update_record("lesson_answers", $this->answers[$i]->properties());
+                }
+
+            } else {
+                break;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Can be set to true if the page requires a static link to create a new instance
+     * instead of simply being included in the dropdown
+     * @param int $previd
+     * @return bool
+     */
+    public function add_page_link($previd) {
+        return false;
+    }
+
+    /**
+     * Returns true if a page has been viewed before
+     *
+     * @param array|int $param Either an array of pages that have been seen or the
+     *                   number of retakes a user has had
+     * @return bool
+     */
+    public function is_unseen($param) {
+        global $USER, $DB;
+        if (is_array($param)) {
+            $seenpages = $param;
+            return (!array_key_exists($this->properties->id, $seenpages));
+        } else {
+            $nretakes = $param;
+            if (!$DB->count_records("lesson_attempts", array("pageid"=>$this->properties->id, "userid"=>$USER->id, "retry"=>$nretakes))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks to see if a page has been answered previously
+     * @param int $nretakes
+     * @return bool
+     */
+    public function is_unanswered($nretakes) {
+        global $DB, $USER;
+        if (!$DB->count_records("lesson_attempts", array('pageid'=>$this->properties->id, 'userid'=>$USER->id, 'correct'=>1, 'retry'=>$nretakes))) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Creates answers within the database for this lesson_page. Usually only ever
+     * called when creating a new page instance
+     * @param object $properties
+     * @return array
+     */
+    public function create_answers($properties) {
+        global $DB;
+        // now add the answers
+        $newanswer = new stdClass;
+        $newanswer->lessonid = $this->lesson->id;
+        $newanswer->pageid = $this->properties->id;
+        $newanswer->timecreated = $this->properties->timecreated;
+
+        $answers = array();
+
+        for ($i = 0; $i < $this->lesson->maxanswers; $i++) {
+            $answer = clone($newanswer);
+            if (!empty($properties->answer_editor[$i])) {
+                $answer->answer = $properties->answer_editor[$i]['text'];
+                $answer->answerformat = $properties->answer_editor[$i]['format'];
+                if (isset($properties->response_editor[$i])) {
+                    $answer->response = $properties->response_editor[$i]['text'];
+                    $answer->responseformat = $properties->response_editor[$i]['format'];
+                }
+                if (isset($properties->jumpto[$i])) {
+                    $answer->jumpto = $properties->jumpto[$i];
+                }
+                if ($this->lesson->custom && isset($properties->score[$i])) {
+                    $answer->score = $properties->score[$i];
+                }
+                $answer->id = $DB->insert_record("lesson_answers", $answer);
+                $answers[$answer->id] = new lesson_page_answer($answer);
+            } else {
+                break;
+            }
+        }
+
+        $this->answers = $answers;
+        return $answers;
+    }
+
+    /**
+     * This method MUST be overriden by all question page types, or page types that
+     * wish to score a page.
+     *
+     * The structure of result should always be the same so it is a good idea when
+     * overriding this method on a page type to call
+     * <code>
+     * $result = parent::check_answer();
+     * </code>
+     * before modifiying it as required.
+     *
+     * @return stdClass
+     */
+    public function check_answer() {
+        $result = new stdClass;
+        $result->answerid        = 0;
+        $result->noanswer        = false;
+        $result->correctanswer   = false;
+        $result->isessayquestion = false;   // use this to turn off review button on essay questions
+        $result->response        = '';
+        $result->newpageid       = 0;       // stay on the page
+        $result->studentanswer   = '';      // use this to store student's answer(s) in order to display it on feedback page
+        $result->userresponse    = null;
+        $result->feedback        = '';
+        $result->nodefaultresponse  = false; // Flag for redirecting when default feedback is turned off
+        return $result;
+    }
+
+    /**
+     * True if the page uses a custom option
+     *
+     * Should be override and set to true if the page uses a custom option.
+     *
+     * @return bool
+     */
+    public function has_option() {
+        return false;
+    }
+
+    /**
+     * Returns the maximum number of answers for this page given the maximum number
+     * of answers permitted by the lesson.
+     *
+     * @param int $default
+     * @return int
+     */
+    public function max_answers($default) {
+        return $default;
+    }
+
+    /**
+     * Returns the properties of this lesson page as an object
+     * @return stdClass;
+     */
+    public function properties() {
+        $properties = clone($this->properties);
+        if ($this->answers === null) {
+            $this->get_answers();
+        }
+        if (count($this->answers)>0) {
+            $count = 0;
+            foreach ($this->answers as $answer) {
+                $properties->{'answer_editor['.$count.']'} = array('text'=>$answer->answer, 'format'=>$answer->answerformat);
+                $properties->{'response_editor['.$count.']'} = array('text'=>$answer->response, 'format'=>$answer->responseformat);
+                $properties->{'jumpto['.$count.']'} = $answer->jumpto;
+                $properties->{'score['.$count.']'} = $answer->score;
+                $count++;
+            }
+        }
+        return $properties;
+    }
+
+    /**
+     * Returns an array of options to display whn choosing the jumpto for a page/answer
+     * @static
+     * @param int $pageid
+     * @param lesson $lesson
+     * @return array
+     */
+    public static function get_jumptooptions($pageid, lesson $lesson) {
+        global $DB;
+        $jump = array();
+        $jump[0] = get_string("thispage", "lesson");
+        $jump[LESSON_NEXTPAGE] = get_string("nextpage", "lesson");
+        $jump[LESSON_PREVIOUSPAGE] = get_string("previouspage", "lesson");
+        $jump[LESSON_EOL] = get_string("endoflesson", "lesson");
+
+        if ($pageid == 0) {
+            return $jump;
+        }
+
+        $pages = $lesson->load_all_pages();
+        if ($pages[$pageid]->qtype == LESSON_PAGE_BRANCHTABLE || $lesson->is_sub_page_of_type($pageid, array(LESSON_PAGE_BRANCHTABLE), array(LESSON_PAGE_ENDOFBRANCH, LESSON_PAGE_CLUSTER))) {
+            $jump[LESSON_UNSEENBRANCHPAGE] = get_string("unseenpageinbranch", "lesson");
+            $jump[LESSON_RANDOMPAGE] = get_string("randompageinbranch", "lesson");
+        }
+        if($pages[$pageid]->qtype == LESSON_PAGE_CLUSTER || $lesson->is_sub_page_of_type($pageid, array(LESSON_PAGE_CLUSTER), array(LESSON_PAGE_ENDOFCLUSTER))) {
+            $jump[LESSON_CLUSTERJUMP] = get_string("clusterjump", "lesson");
+        }
+        if (!optional_param('firstpage', 0, PARAM_INT)) {
+            $apageid = $DB->get_field("lesson_pages", "id", array("lessonid" => $lesson->id, "prevpageid" => 0));
+            while (true) {
+                if ($apageid) {
+                    $title = $DB->get_field("lesson_pages", "title", array("id" => $apageid));
+                    $jump[$apageid] = strip_tags(format_string($title,true));
+                    $apageid = $DB->get_field("lesson_pages", "nextpageid", array("id" => $apageid));
+                } else {
+                    // last page reached
+                    break;
+                }
+            }
+        }
+        return $jump;
+    }
+    /**
+     * Returns the contents field for the page properly formatted and with plugin
+     * file url's converted
+     * @return string
+     */
+    public function get_contents() {
+        global $PAGE;
+        if (!empty($this->properties->contents)) {
+            if (!isset($this->properties->contentsformat)) {
+                $this->properties->contentsformat = FORMAT_HTML;
+            }
+            $context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->id);
+            return file_rewrite_pluginfile_urls($this->properties->contents, 'pluginfile.php', $context->id, 'mod_lesson', 'page_contents', $this->properties->id);
+        } else {
+            return '';
+        }
+    }
+
+    /**
+     * Set to true if this page should display in the menu block
+     * @return bool
+     */
+    protected function get_displayinmenublock() {
+        return false;
+    }
+
+    /**
+     * Get the string that describes the options of this page type
+     * @return string
+     */
+    public function option_description_string() {
+        return '';
+    }
+
+    /**
+     * Updates a table with the answers for this page
+     * @param html_table $table
+     * @return html_table
+     */
+    public function display_answers(html_table $table) {
+        $answers = $this->get_answers();
+        $i = 1;
+        foreach ($answers as $answer) {
+            $cells = array();
+            $cells[] = "<span class=\"label\">".get_string("jump", "lesson")." $i<span>: ";
+            $cells[] = $this->get_jump_name($answer->jumpto);
+            $table->data[] = new html_table_row($cells);
+            if ($i === 1){
+                $table->data[count($table->data)-1]->cells[0]->style = 'width:20%;';
+            }
+            $i++;
+        }
+        return $table;
+    }
+
+    /**
+     * Determines if this page should be grayed out on the management/report screens
+     * @return int 0 or 1
+     */
+    protected function get_grayout() {
+        return 0;
+    }
+
+    /**
+     * Adds stats for this page to the &pagestats object. This should be defined
+     * for all page types that grade
+     * @param array $pagestats
+     * @param int $tries
+     * @return bool
+     */
+    public function stats(array &$pagestats, $tries) {
+        return true;
+    }
+
+    /**
+     * Formats the answers of this page for a report
+     *
+     * @param object $answerpage
+     * @param object $answerdata
+     * @param object $useranswer
+     * @param array $pagestats
+     * @param int $i Count of first level answers
+     * @param int $n Count of second level answers
+     * @return object The answer page for this
+     */
+    public function report_answers($answerpage, $answerdata, $useranswer, $pagestats, &$i, &$n) {
+        $answers = $this->get_answers();
+        $formattextdefoptions = new stdClass;
+        $formattextdefoptions->para = false;  //I'll use it widely in this page
+        foreach ($answers as $answer) {
+            $data = get_string('jumpsto', 'lesson', $this->get_jump_name($answer->jumpto));
+            $answerdata->answers[] = array($data, "");
+            $answerpage->answerdata = $answerdata;
+        }
+        return $answerpage;
+    }
+
+    /**
+     * Gets an array of the jumps used by the answers of this page
+     *
+     * @return array
+     */
+    public function get_jumps() {
+        global $DB;
+        $jumps = array();
+        $params = array ("lessonid" => $this->lesson->id, "pageid" => $this->properties->id);
+        if ($answers = $this->get_answers()) {
+            foreach ($answers as $answer) {
+                $jumps[] = $this->get_jump_name($answer->jumpto);
+            }
+        }
+        return $jumps;
+    }
+    /**
+     * Informs whether this page type require manual grading or not
+     * @return bool
+     */
+    public function requires_manual_grading() {
+        return false;
+    }
+
+    /**
+     * A callback method that allows a page to override the next page a user will
+     * see during when this page is being completed.
+     * @return false|int
+     */
+    public function override_next_page() {
+        return false;
+    }
+
+    /**
+     * This method is used to determine if this page is a valid page
+     *
+     * @param array $validpages
+     * @param array $pageviews
+     * @return int The next page id to check
+     */
+    public function valid_page_and_view(&$validpages, &$pageviews) {
+        $validpages[$this->properties->id] = 1;
+        return $this->properties->nextpageid;
+    }
+}
+
+
+
+/**
+ * Class used to represent an answer to a page
+ *
+ * @property int $id The ID of this answer in the database
+ * @property int $lessonid The ID of the lesson this answer belongs to
+ * @property int $pageid The ID of the page this answer belongs to
+ * @property int $jumpto Identifies where the user goes upon completing a page with this answer
+ * @property int $grade The grade this answer is worth
+ * @property int $score The score this answer will give
+ * @property int $flags Used to store options for the answer
+ * @property int $timecreated A timestamp of when the answer was created
+ * @property int $timemodified A timestamp of when the answer was modified
+ * @property string $answer The answer itself
+ * @property string $response The response the user sees if selecting this answer
+ *
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class lesson_page_answer extends lesson_base {
+
+    /**
+     * Loads an page answer from the DB
+     *
+     * @param int $id
+     * @return lesson_page_answer
+     */
+    public static function load($id) {
+        global $DB;
+        $answer = $DB->get_record("lesson_answers", array("id" => $id));
+        return new lesson_page_answer($answer);
+    }
+
+    /**
+     * Given an object of properties and a page created answer(s) and saves them
+     * in the database.
+     *
+     * @param stdClass $properties
+     * @param lesson_page $page
+     * @return array
+     */
+    public static function create($properties, lesson_page $page) {
+        return $page->create_answers($properties);
+    }
+
+}
+
+/**
+ * A management class for page types
+ *
+ * This class is responsible for managing the different pages. A manager object can
+ * be retrieved by calling the following line of code:
+ * <code>
+ * $manager  = lesson_page_type_manager::get($lesson);
+ * </code>
+ * The first time the page type manager is retrieved the it includes all of the
+ * different page types located in mod/lesson/pagetypes.
+ *
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class lesson_page_type_manager {
+
+    /**
+     * An array of different page type classes
+     * @var array
+     */
+    protected $types = array();
+
+    /**
+     * Retrieves the lesson page type manager object
+     *
+     * If the object hasn't yet been created it is created here.
+     *
+     * @staticvar lesson_page_type_manager $pagetypemanager
+     * @param lesson $lesson
+     * @return lesson_page_type_manager
+     */
+    public static function get(lesson $lesson) {
+        static $pagetypemanager;
+        if (!($pagetypemanager instanceof lesson_page_type_manager)) {
+            $pagetypemanager = new lesson_page_type_manager();
+            $pagetypemanager->load_lesson_types($lesson);
+        }
+        return $pagetypemanager;
+    }
+
+    /**
+     * Finds and loads all lesson page types in mod/lesson/pagetypes
+     *
+     * @param lesson $lesson
+     */
+    public function load_lesson_types(lesson $lesson) {
+        global $CFG;
+        $basedir = $CFG->dirroot.'/mod/lesson/pagetypes/';
+        $dir = dir($basedir);
+        while (false !== ($entry = $dir->read())) {
+            if (strpos($entry, '.')===0 || !preg_match('#^[a-zA-Z]+\.php#i', $entry)) {
+                continue;
+            }
+            require_once($basedir.$entry);
+            $class = 'lesson_page_type_'.strtok($entry,'.');
+            if (class_exists($class)) {
+                $pagetype = new $class(new stdClass, $lesson);
+                $this->types[$pagetype->typeid] = $pagetype;
+            }
+        }
+
+    }
+
+    /**
+     * Returns an array of strings to describe the loaded page types
+     *
+     * @param int $type Can be used to return JUST the string for the requested type
+     * @return array
+     */
+    public function get_page_type_strings($type=null, $special=true) {
+        $types = array();
+        foreach ($this->types as $pagetype) {
+            if (($type===null || $pagetype->type===$type) && ($special===true || $pagetype->is_standard())) {
+                $types[$pagetype->typeid] = $pagetype->typestring;
+            }
+        }
+        return $types;
+    }
+
+    /**
+     * Returns the basic string used to identify a page type provided with an id
+     *
+     * This string can be used to instantiate or identify the page type class.
+     * If the page type id is unknown then 'unknown' is returned
+     *
+     * @param int $id
+     * @return string
+     */
+    public function get_page_type_idstring($id) {
+        foreach ($this->types as $pagetype) {
+            if ((int)$pagetype->typeid === (int)$id) {
+                return $pagetype->idstring;
+            }
+        }
+        return 'unknown';
+    }
+
+    /**
+     * Loads a page for the provided lesson given it's id
+     *
+     * This function loads a page from the lesson when given both the lesson it belongs
+     * to as well as the page's id.
+     * If the page doesn't exist an error is thrown
+     *
+     * @param int $pageid The id of the page to load
+     * @param lesson $lesson The lesson the page belongs to
+     * @return lesson_page A class that extends lesson_page
+     */
+    public function load_page($pageid, lesson $lesson) {
+        global $DB;
+        if (!($page =$DB->get_record('lesson_pages', array('id'=>$pageid, 'lessonid'=>$lesson->id)))) {
+            print_error('cannotfindpages', 'lesson');
+        }
+        $pagetype = get_class($this->types[$page->qtype]);
+        $page = new $pagetype($page, $lesson);
+        return $page;
+    }
+
+    /**
+     * This function loads ALL pages that belong to the lesson.
+     *
+     * @param lesson $lesson
+     * @return array An array of lesson_page_type_*
+     */
+    public function load_all_pages(lesson $lesson) {
+        global $DB;
+        if (!($pages =$DB->get_records('lesson_pages', array('lessonid'=>$lesson->id)))) {
+            print_error('cannotfindpages', 'lesson');
+        }
+        foreach ($pages as $key=>$page) {
+            $pagetype = get_class($this->types[$page->qtype]);
+            $pages[$key] = new $pagetype($page, $lesson);
+        }
+
+        $orderedpages = array();
+        $lastpageid = 0;
+
+        while (true) {
+            foreach ($pages as $page) {
+                if ((int)$page->prevpageid === (int)$lastpageid) {
+                    $orderedpages[$page->id] = $page;
+                    unset($pages[$page->id]);
+                    $lastpageid = $page->id;
+                    if ((int)$page->nextpageid===0) {
+                        break 2;
+                    } else {
+                        break 1;
+                    }
+                }
+            }
+        }
+
+        return $orderedpages;
+    }
+
+    /**
+     * Fetchs an mform that can be used to create/edit an page
+     *
+     * @param int $type The id for the page type
+     * @param array $arguments Any arguments to pass to the mform
+     * @return lesson_add_page_form_base
+     */
+    public function get_page_form($type, $arguments) {
+        $class = 'lesson_add_page_form_'.$this->get_page_type_idstring($type);
+        if (!class_exists($class) || get_parent_class($class)!=='lesson_add_page_form_base') {
+            debugging('Lesson page type unknown class requested '.$class, DEBUG_DEVELOPER);
+            $class = 'lesson_add_page_form_selection';
+        } else if ($class === 'lesson_add_page_form_unknown') {
+            $class = 'lesson_add_page_form_selection';
+        }
+        return new $class(null, $arguments);
+    }
+
+    /**
+     * Returns an array of links to use as add page links
+     * @param int $previd The id of the previous page
+     * @return array
+     */
+    public function get_add_page_type_links($previd) {
+        global $OUTPUT;
+
+        $links = array();
+
+        foreach ($this->types as $key=>$type) {
+            if ($link = $type->add_page_link($previd)) {
+                $links[$key] = $link;
+            }
+        }
+
+        return $links;
+    }
+}
index 4a1fc60..2c330d4 100644 (file)
@@ -24,9 +24,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
  **/
 
-if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
-}
+defined('MOODLE_INTERNAL') || die();
 
 require_once($CFG->dirroot.'/course/moodleform_mod.php');
 require_once($CFG->dirroot.'/mod/lesson/locallib.php');
index df9bc54..ebee75e 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
  /** Branch Table page */
 define("LESSON_PAGE_BRANCHTABLE",   "20");
 
index afb6c14..b21c9e4 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
  /** Start of Cluster page */
 define("LESSON_PAGE_CLUSTER",   "30");
 
index 5082815..7d949d7 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
  /** End of Branch page */
 define("LESSON_PAGE_ENDOFBRANCH",   "21");
 
index d502629..014d7f4 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
  /** End of Cluster page */
 define("LESSON_PAGE_ENDOFCLUSTER",   "31");
 
index 4471fe4..999e80b 100644 (file)
@@ -23,7 +23,9 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
- /** Essay question type */
+defined('MOODLE_INTERNAL') || die();
+
+/** Essay question type */
 define("LESSON_PAGE_ESSAY", "10");
 
 class lesson_page_type_essay extends lesson_page {
index b21145f..47cd755 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 /** Matching question type */
 define("LESSON_PAGE_MATCHING",      "5");
 
index 51fee35..e5864c8 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 /** Multichoice question type */
 define("LESSON_PAGE_MULTICHOICE",   "3");
 
index 3385f46..9826bdc 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 /** Numerical question type */
 define("LESSON_PAGE_NUMERICAL",     "8");
 
index 3f99cc6..ea9c1a3 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
  /** Short answer question type */
 define("LESSON_PAGE_SHORTANSWER",   "1");
 
index 0ee41ab..bac6ad6 100644 (file)
@@ -23,7 +23,9 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
- /** True/False question type */
+defined('MOODLE_INTERNAL') || die();
+
+/** True/False question type */
 define("LESSON_PAGE_TRUEFALSE",     "2");
 
 class lesson_page_type_truefalse extends lesson_page {
index a86c25c..239ff9b 100644 (file)
@@ -23,6 +23,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 class mod_lesson_renderer extends plugin_renderer_base {
     /**
      * Returns the header for the lesson module
index 2b17446..02e25db 100644 (file)
@@ -25,6 +25,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
 */
 
+defined('MOODLE_INTERNAL') || die();
+
 /// This file to be included so we can assume config.php has already been included.
 global $DB;
 if (empty($lesson)) {
index 77bb32f..b64ab96 100644 (file)
@@ -24,6 +24,8 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 $module->version  = 2010072003;  // The current module version (Date: YYYYMMDDXX)
 $module->requires = 2010071800;  // Requires this Moodle version
 $module->cron     = 0;           // Period for cron to check this module (secs)
index 7f713fe..7146dd5 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  **/
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Include formslib if it has not already been included
  */
-if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
-}
 
 require_once($CFG->libdir.'/formslib.php');