Quiz rewrite
Below are some details of the rewrite that the quiz module code received in early 2005. These notes were written while the work was in progress and I intend to go through them soon and update them --Gustav Delius 19:34, 28 January 2006 (WST)
Question sessions and question states
The concept of a question session is made much more explicit. The question session runs from the moment the student first sees the question in a quiz attempt to the close of the attempt (or the close of the quiz in the case where further attempts at the quiz are allowed and are set to build upon previous attempts). Every interaction the student makes with the quiz can update the question sessions, creating a new question state. These question states are also saved in the database. The quiz_responses table has been renamed and adapted for this; it is now called quiz_states. It is possible to load a quiz attempt with the questions in any of the saved states.
The question types are now asked to save any session information, including the most recent student responses, which may be specific to them via some new methods. The newly saved state is passed so that the question type may define its own tables to save this information in using the state id to reference which attempt and state the information is for. This deprecates the response answer field mechanism for response storage (although the field remains and can be used if desired).
Creating new question states
New states can be created without a question being graded. This occurs for example when a student navigates between pages (when the quiz is set to display a limited number of questions per page). Therefore another state object is loaded giving the most recent state for which grading occurred (or the initial state if grading has not occurred yet). Some parts of the rendered output for a question will depend on the current state while others will depend on the most recently graded state. For example the feedback (if any), grading details and correct answers (if any) will depend on the most recently graded state whereas the answer filled into any interactions will depend on the current state. The state object for the current state contains the most recently graded state.
Note that grading always occurs and the raw grade (the grade for the individual submission ignoring any previous attempts at the question and any penalties incurred for them) and penalty are always saved. This information has been found to be useful in the past; eg. credit can be awarded when a student saves the correct answer but never submits it for grading and they have a good reason why they were unable to. Each state indicates the kind of event which occurred to create it. As well as the obvious events (creation of the question session, navigating between pages, saving the answers and grading the answers) we also provide support for question types which want to implement validation (where the syntax of an answer is checked and any syntax errors are displayed but the question is not graded and no feedback is displayed) with a validation event. Furthermore, the immediate resubmission of the same (or an equivalent) answer is checked for using a question type specific comparison method (which might ignore whitespace changes for example). In this case the submission is not graded (a new state is created as for a save but with a special event code) and a warning is printed (unless the question type overrides the print_question method). A quiz option has been added which causes the entire history of states for which marking was requested to be walked with the same behaviour if a match is found.
Question object and State object
We are attempting to keep the question information and the session (runtime) information clearly separated in the code. Many of the question type object methods have been changed to accept a question object and a state object (giving the most recent state of the session). These are passed by reference in most cases allowing any updates to the information to be made and retained between method calls. In particular the question type specific question information is loaded once for each page by a new get_options method (a counterpart to save_options) so that the database query need not be repeated in every method call. As part of the separation the responses are put in the state object rather than the question object.
Towards question type objects
We are attempting to make it easier to change the object model used to one where the question type objects are used directly. In other words the question object parameter could be removed and changed to to $this in the code. Then each question type would define the data elements it requires and would inherit standard ones from the default question type. PHP class support is probably not ready for this change yet though.
Towards dropping the quiz questions field
We will not be dropping the quiz questions field yet as this requires too much work to update the editing interface etc. However we will be making the code ready for this change by using the legacy field only for the order of the questions. The quiz_question_grades table (which really defines the question instances, i.e. which questions are used in each quiz) is used to load the question ids for the quiz and the maximum grades which have been assigned to them. The array is then ordered using the quiz questions field. In the future a field could be added to the quiz_question_grades table which we could order by in the database query and then the quiz questions field could be removed.
Towards sections and subquestions
The work towards allowing question types which wrap a number of others and display all of them (eg. to create sections) has been incorporated so that the methods to print a question / part of a question are now passed either an index to the wrapped question to print or two indices indicating the range of questions to be printed.
The improved support for questions of different lengths will allow question types to be developed to provide sections (eg. to group a description and a question so that they are always displayed together and in the same order even when shuffling is enabled), subquestions (i.e. a question type with length one which wraps multiple questions and numbers them "1. a)", "1. b)" etc.) and question types for using a sequence of questions from a content package (note that the quiz object containing all the settings is now passed to many of the question type methods so that they can adapt to the quiz settings - in this case the order could be shuffled if shuffling is enabled for the quiz with the order chosen saved as part of the question session). Note that we shall not actually implement any of these!
The length of a question (i.e. how many question numbers are to be assigned to the question - this can be zero, eg. for the description type) has been made question specific (to allow for question types which wrap a variable number of questions into one unit) by adding a field to the quiz_questions table. The actual_number_of_questions method remains in the question types and is called by the default implementation of the save_question method to set the new field.
The code to display a limited number of questions on each page has also been improved. When each attempt is started the question order and page breaks are worked out (the questions are shuffled if this is turned on for the quiz). This information is saved when the attempt is created. Hence the same order is used when the student returns to the attempt and when the student or the teacher reviews the attempt (at present the review page order changes every time when the quiz is set to shuffle). Questions with length greater than one are allowed to span page breaks (using the new indices to the question type printing methods). In other words the "questionsperpage" property is taken to mean the number of question numbers per page and so questions with length zero do not count.
Once the layout of every page has been calculated (or reloaded), only those questions which are required for the current page are loaded. Those question objects then have the question type specific information loaded into them and the question sessions are restored to the relevant state. The state objects are given the question number assigned and the name prefixes at this stage so there is no need to calculate them later. The maximum available grades are also loaded into the question object (actually the "instance" objects - i.e. rows from the quiz_question_grades table - are loaded first but the effect is the same). The currently obtained grades are already stored in the database for each state.
Having restored the attempt and the question sessions, grading (if required) and printing can occur. Since all the required data is already loaded and made available the code in the question types to do this can be simplified slightly and the process is made more efficient (especially since only those questions which are used are considered).