Quiz attempt
The attempt.php script is one of the most complicated scripts of the quiz module. It is responsible for displaying questions to a user, to evaluate and grade the users' responses and additionally it needs to take various quiz settings into account and change the behaviour of the script accordingly. This script, which is vital for the functioning of the quiz module, is explained in this document in some detail in order to provide some context and examples for the use of the question/state model and to illustrate some of the functions provided in locallib.php.
In addition to the immediate benefit of explaining what happens in attempt.php, this piece of documentation should provide an entry point to the quiz module code. A number of functions are used in attempt.php that are likely to be used in other scripts as well.
Although the attempt.php script is useful for understanding how user interactions are handled, in order to gain an understanding of the teacher interface for editing quizzes and questions you should look at edit.php.
This file describes the situation up to Moodle 1.9. For Moodle 2.0 and later, Attempt.php was split into several bits as shown on Quiz_user_interface_overview. However, the code still does the same things, just in smaller, better organised pieces. (See also http://moodle.org/mod/forum/discuss.php?d=68922.)
A simplified perspective
From a simple functional point of view, the responsibilities of attempt.php is the handling of attempts at a quiz. This includes displaying questions and storing data provided or generated by the users. The interaction could be limited to opening the page and thus creating a new attempt and submitting the responses back to the server, which causes the responses to be graded. While the script is actually more sophisticated and allows to interrupt and continue attempts, this two step scenario provides a good starting point and covers the core functionality.
Starting a new attempt
When a new attempt is started the attempt object is created by the function
quiz_create_attempt()
(line 183). It returns an empty
$attempt
object, which is stored in the database immediately to record the fact that the student has seen the questions. The next step is to load up all questions and to create initial states for all of them. Loading up the questions is a two step process: first the question records are extracted from the database, then the function
quiz_get_questions_options()
is called. This attaches the name_prefix field and calls the questiontype specific
get_question_options()
method for each question record in order to add any required data.
// Load the questions if (!$questions = get_records_sql($sql)) { ... } // Load the question type specific information if (!quiz_get_question_options($questions)) { ... } // Restore the question sessions to their most recent states // creating new sessions where required if (!$states = quiz_get_states($questions, $attempt)) { ... }
After all questions are correctly initialised a state object is created for each by the function
quiz_get_states()
. This function is responsible for providing a state for each question and creates a new state if there isn't one to be restored. Any newly states are added to the database right away. At this point all the required data has been generated or loaded, so the last step is to print the page. The printing happens towards the end of the attempt.php script and, apart from a simple call to
print_heading()
(line 409), is done in a loop through all the question records by calling
quiz_print_quiz_question()
(line 461), which is just a shorthand for calling the questiontype specific
print_question()
method. So, quite conveniently, each question is responsible for printing itself. To round up the printing, submit buttons and a footer are printed to the bottom of the page.
Processing responses
The next logical step after creating and displaying an attempt is that the user enters answers for all questions and submits them for marking. Now it is not necessary to create a new attempt, because there is already an open existing one in the database and the same holds for each question's state. So this time the attempt record is loaded from the database. The questions are loaded in the same way, by querying for the question records an then attaching any required questiontype specific fields by calling the function
quiz_get_question_options()
. Now the call to the function
quiz_get_states()
does actually restore the states from the database rather than generate new ones, so the same code works for the two scenarios, creating and closing an attempt. Now that all data concerning the attempt under discussion has been loaded, the responses submitted by the student come to the scene. For each question they need to be evaluated and graded. The first step here is to determine how to deal with each question and the associated responses. In the simplified case this is clear; all responses need to be graded, the grades stored and then the attempt needs to be closed. However, there are more complicated cases, so the function
quiz_extract_responses()
is called to create the
$actions
array, which acts as a set of instructions for the function
quiz_process_responses()
, a quite complicated function, which encapsulates the entire response processing for one question and calls out to the questiontype specific
grade_responses()
method for grading. After all submitted responses have been processed, the questions are rendered in the new states. An exception is if the student has requested to close the attempt (or if it is closed automatically due to a time limit). In this case the attempt is closed by setting the
timefinish
field to the current time. After this the user is redirected to review.php, which, depending on the quiz review settings, shows the questions including the student's responses, feedback, grades and correct answers.
A more complicated perspective
Before an attempt may be started there are a series of mandatory and optional checks, to determine if the user is allowed to attempt the quiz. In addition to these checks, the page display and functionality are slightly different for users with and without teacher privileges. Also, when the quiz is set to start in "secure" mode (the $quiz->popup option), the printing of the page is slightly different. Including all of these scenarios evidently complicates the structure of the attempt.php script, deviating from the simple scenario described above.
First of all, access to the quiz is denied to guests. Additionally it is possible to restrict access to an IP range ($quiz->subnet) and to set up a password for the quiz ($quiz->password). In both cases users that can't pass the required tests are denied access.
When a teacher "attempts" a quiz there is a tab navigation facility at the top of the page, which allows the teacher to jump between reviewing, previewing and editing the quiz (and possibly even more options). The teacher interface also contains a button to start a new attempt, which is not present on the student interface. Teachers' attempts are automatically marked as previews, which means that old attempts are automatically deleted when a new attempt is started. It also prevents previews to show up in the students' answers review and preview attempts don't block the possibility of editing the quiz, while students' attempts do.
Further complication is introduced by the feature to allow multiple pages with questions and navigation between these pages. This requieres mechanisms to determine which questions are on which page, which page is currently displayed and which page needs to be viewed next. This is not too hard to achieve, however, one subtlety should be noted: when the attempt is closed it is necessary to load all questions and their most recent states before they are marked, whereas in the case of navigation only the questions and related states of the page that was displayed before are loaded, processed and saved; and the questions and states for the next page are loaded.
Towards the end of the script there are two blocks of code that are responsible for timed quizzes ($quiz->timelimit). The first block prints the start element of the form using javascript to make sure that javascript is enabled (which obviously doesn't help a lot, because the quiz is printed as usual, only the submit won't work). The second block includes the file jstimer.php, which prints a timer that counts down and causes an auto-submit when time is up.