Development:Adding question types to lesson

Jump to: navigation, search

Template:Moodle 2.0 Please note that this page deals with a future version of Moodle 2.0.

A regularly discussed topic is that Lesson should use the same question classes as Quiz. This page is geared toward this goal by explaining the different aspects required to complete this project.--Mark Nielsen

I think this would be a really good idea, and am willing to help--Tim Hunt, Quiz module/question bank maintainer.

Project goals

  • (Primary) Implement the question type classes in Lesson module.
  • (Primary) Reduce Moodle's code base. Instead of Lesson having its own question code, it can now use existing code.
  • (Primary) Increase the usability of the lesson module by making it easier for lesson authors to construct lessons.
  • Simplify Lesson's code. There is a high mix of presentation and logic and some rather confusing algorithms due to the nature of Lesson and how it is organized.

Primary coding tasks

Adding support for question class

Implement the question class code and the necessary interfaces for adding and editing questions within Lesson. The needed functionality is as follows:

  • Printing questions for attempts. Should be nothing to do if we are happy with the same presentation for both quiz and lesson--Tim Hunt
    • Will this include the shuffle answer option for each question, or will/can this be a Lesson setting?--chris collman 08:37, 10 December 2007 (CST)
  • Grading the questions. Again, should be fine, as long as the question_(attempts|sessions|states) model is flexible enough for the quiz--Tim Hunt
  • Provide a method of adding new questions to a Lesson. Hopefully question/showbank.php will work for you, perhaps with a few changs.--Tim Hunt
    • When adding a question, display questions in course question bank to select from.
      • Provide links/tabs for creating new questions.
    • After adding a question, provide an interface to define possible jumps (This step may not be needed. Depends on the solution to the questions and jumps problem). This will be new work--Tim Hunt

Changing the attempts logic

Among other Lesson table changes, one that is the most significant is changing the lesson_attempts table. Instead of storing one attempt record for every user answer, lesson_attempts table should store one record per Lesson attempt. The question_states table will replace the functionality of the original lesson_attempts table. Since this is changing such a fundamental part of Lesson, almost all of Lesson's code will have to be adapted to the new logic of attempts.

Not tracking individual student answers in a lesson would clearly separate Lesson from Quiz. I like it. I believe once we have at least some sort of course dependency in Moodle 2, this will take a great deal of pressure off of Lesson. The course dependency setting for any activity should at least have greater, less than choices from say a score or one or more activities/resources. (this seems similar to the State Checker idea). It would be nice if it had limited nested if/then statements to allow a ranges of dependency to be really adaptive. This will allow the teacher to break up big lessons into smaller ones and put a real quiz/test between them. So when the student gets a 100% on a following quiz, they go to Lesson 10, but the student who scores less than 50% goes back to the Lesson or to a special lesson before they will see Lesson 10. Under these conditions, we can confidently get rid of a history of answered questions in Lesson, and leave that function to Quiz. --chris collman 10:06, 8 November 2008 (CST)

Fixing code breaks

By switching to the question class, nearly all of the current Lesson code will be broken and must be replaced or fixed. Here is a quick breakdown of foreseeable code breaks:

Database migration

Lesson tables need to be removed or changed and their old data needs to be migrated to new tables. Some of this code will be used in the restore process as well. Here is a basic overview of the database migration process:

  • migrate lesson_pages and lesson_answer content to question tables. Note: this will only be the content, not the logic for ordering Lesson pages or any other Lesson specific data.
  • migrate lesson_attempts to question_states.
  • Re-organization of the lesson tables (see new database schema).
  • migrate lesson navigation.

Unsolved problems

Here are some tricky issues that do not have a solid or obvious solution. Please advise.

Questions and jumps

Page jumps determine the flow from one page to another and are a unique feature to Lesson. So, how does one figure out all the necessary jump definitions needed for a question? In Lesson, every answer has a jump, but this solution is not ideal. A multiple choice question for example would have two cases:

  • Single answer: a jump must be defined for each answer.
  • Multiple answer: a jump would be defined for the correct answer and a jump for the wrong answer.

The follow two sections discuss possible solutions, but please feel free to suggest others. When thinking about this problem and perhaps a new solution, remember the primary goals of this project: reduce code size and simplify the Lesson code to make it easier to maintain.

Invasive solution

An invasive solution would be to modify the question class code. This is invasive because the introduced code may only be used by Lesson unless it was implemented in such a way that it would be viable for other uses. Some of the foreseeable changes would include the following:

  • Add methods to the default_questiontype class that would handle the default behavior for defining jumps. Default behavior would be two jump definitions: one for correct answers and one for incorrect answers.
  • Provide a place to store the defined jumps.
  • In each question type, override the default methods in the default_questiontype class to suite the behavior of the question (optional for each question type).

Pros for this implementation:

  • All question type code is in one place.
  • Clean implementation
  • Better work-flow for question authoring


  • Potentially introduce code that is unusable by other modules, etc.

Noninvasive solution

Lesson would extend all question types that to handle jumps. Lesson would have its own type folder (mod/lesson/type). This type folder would be organized the same as question/type directory. Each question type would have its own folder and in that folder a questiontype.php. So, mod/lesson/type/{questiontype}/questiontype.php where {questiontype} is replaced with each question type name. The questiontype.php would extend the original question type class and add the necessary methods for handling jumps. The following methods would be added:

  • print_jump_form: accepts the possible jump values from Lesson and then print (or return) the contents of a form. This form would then be presented to the user to define the possible jumps.
  • process_jump_form: accepts the form data from print_jump_form and organize it. It then returns the organized data so that Lesson can store it.
  • interpret_jump: accepts the user's answer to the question and the returned value from process_jump_form. This method would determine which jump Lesson should use.
  • restore_jumps: restore the returned value from process_jump_form during Lesson's restore routine. Returns the restored value so that Lesson can store it.

Example use:

Two jump definitions: correct or incorrect:

  • print_jump_form would return the following form:
 Correct answer jump: [drop-down-menu-with-lesson-jumps]
 Wrong answer jump: [drop-down-menu-with-lesson-jumps]
  • process_jump_form would accept the POST data from the above form and organize it in an array like this: array('correct' => [lesson-jump-code], 'wrong' => [lesson-jump-code]). Where [lesson-jump-code] is a Lesson page id or jump code from the [drop-down-menu-with-lesson-jumps]. This array would be returned so that Lesson could store it.
  • interpret_jump would accept the students answer and the above array. It would return the [lesson-jump-code] either associated with correct or wrong based on the student's answer.
  • restore_jumps would be called during the Lesson restore process and it would return the restored array so that Lesson could store it. Example: if each answer had a jump then the array would be defined like this: array(answerid => [lesson-jump-code] ... ). The answerid would have to be mapped to the new answerid during the restore process.

Pros for this implementation:

  • Potential Lesson specific code remains in Lesson.


  • Work-flow would have an extra step (create question then define jumps).
  • Creating new question types is more difficult.

A middle way

I (Tim) don't like either of the above two solutions. I don't like excessively lesson specific code in the question types, but it would be really bad if the code for a particular question type was not all in one place.

I think that the solution is to add a new concept: question outcomes. It will take me a couple of paragraphs to explain what I mean with this.

Currently, after a student attempts a question, you get stuff that falls into two categories:

  1. generic stuff, like the grade (both before and after applying the penalty factor), the penalty factor itself, the classification of the before grade as Correct/Partially Correct/Incorrect, a possible manual comment added by the teacher, the state of the question (saved, graded, manual graded, closed)
  2. question-type specific stuff, like the data in question_states->answer column, or the feedback that is displayed on-screen.

I think that in the generic stuff category, we need to add the new concept of 'outcome'. This would be what the lesson module uses to select branches, and would probably also be useful to the quiz reports. It would categorise the student's answer into one of a number of categories, and the question type class would need to implement two new methods:

array of strings all_outcomes($question)

string outcome($question, $state)

For multiple-choice (single response) this would return the answer selected. For shortanswer, numerical and calculated, this would return the answer that was matched. The generic implementation in the base class would probably have to just return 'incorrect'/'partiallycorrect'/'correct' (that is the ids of the strings that are looked up in the language file.

The lesson would then need to do branching be specifying where to go for each outcome. It would requre a multi-stage UI (create the question using the standard questionbank code, add the question to the lesson, link up the outcomes to jump targets). However, done well, this could be a very natural UI.

The quiz UI is not perfect, but I think the way it is now: create a question, then add the question to the quiz, then adjust the grade for that question within the quiz, is very natural to use.


This sounds like a generalization of the first solution. The question types would provide the options (outcomes) that need jump associations that are defined by lesson. The workflow is far different from what lesson currently has, but it sounds necessary and reasonable.
It also appears that lesson would be storing the outcome/jump associations, which is fine a long as the outcome does not change with question editing. Not sure how this would work for multi-choice (single response).
This is why I posted my solutions :) I did not like them either, but I was hoping that they would inspire better solutions. Thank you Tim, I think your solution is much closer to the final solution than mine were.

</comment> - Mark Nielsen 23:42, 30 October 2006 (CST)


I think you can use this without invasions. The questions contain feedback (which is a rought equivalent of outcome), and the answers with equivalent feedbacks are very likely to give equivalent jumps (if they still not, user can easily rewrite it using different words or just add some spaces or punctuation). You may not invade in the question types, but use feedback they return instead. You can use a table (apart from question editing interface) that allows author to specify jumps for feedbacks (or place jump right in feedback in some sort of you own tag - but it'll restricts using one question on several pages or lessons with different jumps). This solution is quite easy to implement, probably an easier way than anyone mentioned there.

You can get problem with some question types using this, but they are easily solved:

  • essay - you probably will want to suppress using of manual graded question types in lessons anyway;
  • multichoice in several choices mode - you can use correct/partial/incorrect feedbacks;
  • match - it lacks feedbacks now, but probably it must have just correct/partial/incorrect as multichoice question, they are needed in it anyway, not only for the lessons.

You may want to use feedback text only as values to communicate with user thought, replacing it in database by subquestion id or code for correct, partial or incorrect values. This allows you to avoid problems with editing feedbacks for existing questions.

</comment> --Oleg Sychev 08:52, 13 November 2008 (CST)

A new path

by Mark Nielsen 13:43, 13 November 2006 (CST)

After a nice discussion with Michael Penney, a new solution to this problem arose. Instead of working with the current method of each question having a set of defined jumps, instead introduce a new feature called state checker (the name at the moment, could change later). These state checkers could be inserted throughout the lesson and are used to determine the navigation. These state checkers could be set to say, if student has more than 70% correct then send the student to location X, otherwise send student to location Y. Alternatively, the state checker could be set to say if last question was correct, go to X otherwise go to Y. These examples are binary, but the state checker, when fully implemented, should allow several more options and more granular control.

Pros for this implementation:

  • No major changes are required to the question bank code.
  • Since the navigational control logic is separate from the question logic, one cannot accidentally modify or break a lesson's navigation by editing a question.
  • Lesson navigation can be controlled via one screen and is independent of questions.
  • More possibilities for lesson navigation control.
  • Could reduce the complexity of the multi-stage UI that the other solutions require.


  • Migration of old navigation structure could prove to be complicated depending on how the state checker would be developed (NOTE: this is already a problem. See next section Migration of jumps).


If State Checkers are used, then there should be an overall default for how one navigates from one page/question to the next. EG: Correct answer goes to next page in the logical order, Wrong answer goes to same page. Then one adds State Checkers to change the default navigation of the lesson. There should be several configurable default navigations to choose from in the lesson settings.

</comment> - Mark Nielsen 16:17, 24 March 2007 (CDT)

Migration of jumps

Regardless of implementation, one problem remains: if each question potentially does not have a jump defined for each answer, then how are the currently defined jumps supposed to be migrated to the new question implementation (remember, Lesson questions have one jump defined for each answer regardless if it makes sense at all)? The only solution that comes to mind is to have strict definitions for migration for the following question types that exist in Lesson:

  • Multichoice (single and multianswer)
  • Matching
  • Numerical
  • Short Answer
  • True/False
  • Essay

Note: the use of migration here refers to upgrading old Lessons and restoring older Lessons.

Comments on questions and jumps

I think the adaptive jumps of Lesson are a key feature. If I read the above correctly, I don't like limiting this to either a correct answer jump or a wrong answer jump. I do like the concept of a jump form. --chris collman 09:49, 8 November 2008 (CST)

This also begs the special navigation features used in clusters and 'branches" with end of Branch which Mark has addressed with Check Points which I assume are different that State Checkers.I know nothing about elegance, and that is why I like to write about what developers have created :)--chris collman 09:49, 8 November 2008 (CST)

Branch tables

Once a solution for the above problem has been found, I would like to discuss the possibility of replacing the functionality of branch tables with the description question type. Reason for discussing this after a solution has been found is because the solution may influence the outcome to this problem.

Branch tables are grade neutral. That means neither 0% nor 100% credit for any answers or else it will count towards the grade/score. I do not have a issue with a descriptive page, except I would call it a "Choice page" and make it essentially a grade neutral multiple choice question. This would also allow graphics next to a choice, something not possible with a Branch Table.--chris collman 09:33, 8 November 2008 (CST)
Branch tables, offer the student choices and description question types do not offer any choices. This is a big difference. Perhaps a description question needs the ever present "continue" choice when it will be imported into Lesson from Question Bank.--chris collman 09:33, 8 November 2008 (CST)
This could also make importing OOwriter and OOimpress pages easier into Lesson. I am still hopeful that something like the GIFT format will allow pages to be imported and exported from Lesson. With the State checker and Check Points kind of concepts, this would make that task easier. Perhaps come up with some standard code to attach a state checker to the page? --chris collman 09:33, 8 November 2008 (CST)
Wanted to jot down a random though, a branch table could perhaps just be a visible Check Point with buttons to allow the user to choose where to go next. More I think about this, the better it gets, code wise and lesson editing wise. Mark Nielsen 12:46, 21 January 2009 (CST)

New database schema

 ------------------      ------------------
 |                |      |                |
 | course_modules |      | lesson_default |
 |                |      |                |
 ------------------      ------------------
         |                        |
         |                        |
  --------------           --------------
  |            |           |            |
  |   lesson   |-----------|   course   |
  |            |           |            |
  --------------           --------------
         |      ----------------------      ----------
         |      |                    |      |        |
         |------| lesson_high_scores |------|  user  |
         |      |                    |      |        |
         |      ----------------------      ----------
         |                                       |
         |      -----------------                |
         |      |               |                |
         |------| lesson_grades |----------------|
         |      |               |                |
         |      -----------------                |
         |                                       |
         |      -------------------              |
         |      |                 |--------------|    ---------------------
         |------| lesson_attempts |                   |                   |
         |      |                 |-------------------| question_attempts |
         |      -------------------                   |                   |
         |                                            ---------------------
         |      ----------------
         |      |              |
         |------| lesson_pages |
                |              |
                       |      -----------------------------      ------------
                       |      |                           |      |          |
                       |------| lesson_question_instances |------| question |
                       |      |                           |      |          |
                       |      -----------------------------      ------------
                       |      -------------------
                       |      |                 |
                       |------| lesson_branches |
                              |                 |
  • Note: tables in bold are standard in Moodle and are there only to show relations.

Table Descriptions


  • PK = primary key
  • FK = foreign key


Primary module table


  • id: PK
  • course: FK to course
  • name: Name of the lesson
  • practice: Flag for practice lessons
  • modattempts
  • password: Stores password for password protected lessons
  • dependency: FK to lesson table. The lesson that this lesson is dependent upon
  • conditions: conditions for the dependency
  • grade: Max grade for the lesson
  • cusom: custom scoring flag
  • ongoing: display on going score flag
  • usemaxgrade: use max grade or mean flag
  • maxanswers: maximum answers for a question (would only be used by branch tables, remove?)
  • maxattempts: number of attempts on a question
  • review
  • nextpagedefault: default flow control
  • minquestions: minimum questions to answer
  • maxpages
  • time: allowed time in the lesson per attempt
  • retake: allow student to retake
  • activitylink: FK to course_modules table
  • mediafile
  • mediaheight: height of pop-up
  • mediawidth: width of pop-up
  • mediaclose: display close button for media
  • slideshow: display branches in slide show mode flag
  • width: width of slide show
  • height: height of slide show
  • bgcolor: background color of slide show
  • displayleft: display left menu
  • displayleftif: display left menu if student has already completed lesson flag
  • progressbar: display progress bar flag
  • highscores: high scores
  • available: when lesson is available to student
  • deadline: when lesson closes to student
  • timemodified: last updated


Lesson default settings for a course.


  • same as the lesson table minus the following fields: name, dependency, activity, mediafile, available, deadline, and modified


Keep track of the lesson top scores.


  • id: PK
  • lessonid: FK to lesson table
  • userid: FK to user table
  • gradeid: FK to lesson_grades table
  • nickname: user's nickname


User attempt grades.


  • id: PK
  • lessonid: FK to lesson table
  • userid: FK to user table
  • grade: attempt grade
  • completed: time of completion


Lesson attempts by users.


  • id: PK
  • uniqueid: FK to question_sessions table
  • lessonid: FK to lesson table
  • userid: FK to user table
  • path: users path through the lesson (comma separated list of page ids from the lesson_pages table)
  • attempt: user's attempt count
  • sumgrade: current total for grades
  • timestart: starting time of attempt
  • timefinish: ending time of attempt
  • timemodified: last time updated


All content pages in lesson.


  • id: PK
  • lessonid: FK to lesson table
  • prevpageid: FK to lesson_pages table
  • nextpageid: FK to lesson_pages table
  • type: values include
    • branch
    • endofbranch
    • cluster
    • endofcluster
    • question
  • display: display in left menu flag
  • jumps: jumps related to this page
  • timemodified: time last updated


Store relation between lesson pages that are questions and the question table. (does not exist in moodle 1.6.1 standard)


  • id: PK
  • lessonid: FK to lesson table
  • pageid: FK to lesson_pages table
  • questionid: FK to question table
  • grade: point value of the question


Lesson pages that are branch tables. (does not exist in moodle 1.6.1 but there is different set of fields in lesson_branch)


  • id: PK
  • lessonid: FK to lesson table
  • pageid: FK to lesson_pages table
  • boilerplates: text of branch buttons
  • layout: layout of branch buttons

See discussion about making branch pages more like description questions in Quiz. Or converting Branch tables to a grade neutral format similar to a multiple choice question, where there are no buttons but graphics are allowed.


If you would like to discuss topics on this page, please make a post in the lesson forum.

See also