3 * This page prints a particular instance of quiz
6 * @author Martin Dougiamas and many others. This has recently been completely
7 * rewritten by Alex Smith, Julian Sedding and Gustav Delius as part of
8 * the Serving Mathematics project
9 * {@link http://maths.york.ac.uk/serving_maths}
10 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
14 require_once("../../config.php");
15 require_once("locallib.php");
17 $id = optional_param('id', 0, PARAM_INT
); // Course Module ID
18 $q = optional_param('q', 0, PARAM_INT
); // or quiz ID
19 $page = optional_param('page', 0, PARAM_INT
);
20 $questionids = optional_param('questionids', '');
21 $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL
);
22 $timeup = optional_param('timeup', 0, PARAM_BOOL
); // True if form was submitted by timer.
23 $forcenew = optional_param('forcenew', false, PARAM_BOOL
); // Teacher has requested new preview
25 // remember the current time as the time any responses were submitted
26 // (so as to make sure students don't get penalized for slow processing on this page)
29 // We treat automatically closed attempts just like normally closed attempts
35 if (! $cm = get_record("course_modules", "id", $id)) {
36 error("There is no coursemodule with id $id");
39 if (! $course = get_record("course", "id", $cm->course
)) {
40 error("Course is misconfigured");
43 if (! $quiz = get_record("quiz", "id", $cm->instance
)) {
44 error("The quiz with id $cm->instance corresponding to this coursemodule $id is missing");
48 if (! $quiz = get_record("quiz", "id", $q)) {
49 error("There is no quiz with id $q");
51 if (! $course = get_record("course", "id", $quiz->course
)) {
52 error("The course with id $quiz->course that the quiz with id $q belongs to is missing");
54 if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id
, $course->id
)) {
55 error("The course module for the quiz with id $q is missing");
59 require_login($course->id
, false, $cm);
60 $isteacher = isteacher($course->id
);
62 // Get number for the next or unfinished attempt
63 if(!$attemptnumber = (int)get_field_sql('SELECT MAX(attempt)+1 FROM ' .
64 "{$CFG->prefix}quiz_attempts WHERE quiz = '{$quiz->id}' AND " .
65 "userid = '{$USER->id}' AND timefinish > 0")) {
69 $strattemptnum = get_string('attempt', 'quiz', $attemptnumber);
70 $strquizzes = get_string("modulenameplural", "quiz");
71 $popup = $isteacher ?
0 : $quiz->popup
; // Controls whether this is shown in a javascript-protected window.
73 /// Print the page header
75 define('MESSAGE_WINDOW', true); // This prevents the message window coming up
76 print_header($course->shortname
.': '.format_string($quiz->name
), '', '', '', '', false, '', '', false, '');
77 include('protect_js.php');
79 $strupdatemodule = isteacheredit($course->id
)
80 ?
update_module_button($cm->id
, $course->id
, get_string('modulename', 'quiz'))
82 print_header_simple(format_string($quiz->name
), "",
83 "<a href=\"index.php?id=$course->id\">$strquizzes</a> ->
84 <a href=\"view.php?id=$cm->id\">".format_string($quiz->name
)."</a> -> $strattemptnum",
85 "", "", true, $strupdatemodule);
88 echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
90 /// Print the quiz name heading and tabs for teacher
92 $currenttab = 'preview';
95 print_heading(format_string($quiz->name
));
98 /// Check availability
100 print_heading(get_string('guestsno', 'quiz'));
102 print_footer($course);
107 if ($quiz->attempts
and $attemptnumber > $quiz->attempts
) {
108 error(get_string('nomoreattempts', 'quiz'), "view.php?id={$cm->id}");
111 /// Check subnet access
112 if ($quiz->subnet
and !address_in_subnet(getremoteaddr(), $quiz->subnet
)) {
114 notify(get_string('subnetnotice', 'quiz'));
116 error(get_string("subneterror", "quiz"), "view.php?id=$cm->id");
120 /// Check password access
121 if ($quiz->password
and empty($_POST['q'])) {
122 if (empty($_POST['quizpassword'])) {
124 if (trim(strip_tags($quiz->intro
))) {
125 print_simple_box(format_text($quiz->intro
), "center");
129 echo "<form name=\"passwordform\" method=\"post\" action=\"attempt.php?id=$cm->id\">\n";
130 print_simple_box_start("center");
132 echo "<div align=\"center\">\n";
133 print_string("requirepasswordmessage", "quiz");
134 echo "<br /><br />\n";
135 echo " <input name=\"quizpassword\" type=\"password\" value=\"\" alt=\"password\" />";
136 echo " <input type=\"submit\" value=\"".get_string("ok")."\" />\n";
139 print_simple_box_end();
148 if (strcmp($quiz->password
, $_POST['quizpassword']) !== 0) {
149 error(get_string("passworderror", "quiz"), "view.php?id=$cm->id");
154 /// Load attempt or create a new attempt if there is no unfinished one
156 if ($isteacher and $forcenew) { // teacher wants a new preview
157 // so we set a finish time on the current attempt (if any).
158 // It will then automatically be deleted below
159 set_field('quiz_attempts', 'timefinish', $timestamp, 'quiz', $quiz->id
, 'userid', $USER->id
);
162 $attempt = get_record('quiz_attempts', 'quiz', $quiz->id
,
163 'userid', $USER->id
, 'timefinish', 0);
167 // Check if this is a preview request from a teacher
168 // in which case the previous previews should be deleted
170 if ($oldattempts = get_records_select('quiz_attempts', "quiz = '$quiz->id'
171 AND userid = '$USER->id'")) {
172 delete_records('quiz_attempts', 'quiz', $quiz->id
, 'userid', $USER->id
);
173 delete_records('quiz_grades', 'quiz', $quiz->id
, 'userid', $USER->id
);
174 foreach ($oldattempts as $oldattempt) {
175 // there should only be one but we loop just in case
176 delete_records('quiz_states', 'attempt', $oldattempt->id
);
177 delete_records('quiz_newest_states', 'attemptid', $oldattempt->id
);
182 // Start a new attempt and initialize the question sessions
183 $attempt = quiz_create_attempt($quiz, $attemptnumber);
184 // If this is an attempt by a teacher mark it as a preview
186 $attempt->preview
= 1;
189 if (!$attempt->id
= insert_record('quiz_attempts', $attempt)) {
190 error('Could not create new attempt');
194 add_to_log($course->id
, 'quiz', 'preview',
195 "attempt.php?id=$cm->id",
196 "$quiz->id", $cm->id
);
198 add_to_log($course->id
, 'quiz', 'attempt',
199 "review.php?attempt=$attempt->id",
200 "$quiz->id", $cm->id
);
203 // log continuation of attempt only if some time has lapsed
204 if (($timestamp - $attempt->timemodified
) > 600) { // 10 minutes have elapsed
205 add_to_log($course->id
, 'quiz', 'continue attempt',
206 "review.php?attempt=$attempt->id",
207 "$quiz->id", $cm->id
);
210 if (!$attempt->timestart
) { // shouldn't really happen, just for robustness
211 $attempt->timestart
= time();
214 /// Load all the questions and states needed by this script
216 // list of questions needed by page
217 $pagelist = quiz_questions_on_page($attempt->layout
, $page);
220 $questionlist = quiz_questions_in_quiz($attempt->layout
);
222 $questionlist = $pagelist;
225 // add all questions that are on the submitted form
227 $questionlist .= ','.$questionids;
230 if (!$questionlist) {
231 error(get_string('noquestionsfound', 'quiz'), 'view.php?q='.$quiz->id
);
234 $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
235 " FROM {$CFG->prefix}quiz_questions q,".
236 " {$CFG->prefix}quiz_question_instances i".
237 " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
238 " AND q.id IN ($questionlist)";
240 // Load the questions
241 if (!$questions = get_records_sql($sql)) {
242 error(get_string('noquestionsfound', 'quiz'), 'view.php?q='.$quiz->id
);
245 // Load the question type specific information
246 if (!quiz_get_question_options($questions)) {
247 error('Could not load question options');
250 // Restore the question sessions to their most recent states
251 // creating new sessions where required
252 if (!$states = quiz_restore_question_sessions($questions, $quiz, $attempt)) {
253 error('Could not restore question sessions');
256 // Save all the newly created states
258 foreach ($questions as $i => $question) {
259 quiz_save_question_session($questions[$i], $states[$i]);
264 /// Process form data /////////////////////////////////////////////////
266 if ($responses = data_submitted() and empty($_POST['quizpassword'])) {
268 // set the default event. This can be overruled by individual buttons.
269 $event = (array_key_exists('markall', $responses)) ? QUIZ_EVENTGRADE
:
270 ($finishattempt ? QUIZ_EVENTCLOSE
: QUIZ_EVENTSAVE
);
272 // Unset any variables we know are not responses
273 unset($responses->id
);
274 unset($responses->q
);
275 unset($responses->oldpage
);
276 unset($responses->newpage
);
277 unset($responses->review
);
278 unset($responses->questionids
);
279 unset($responses->saveattempt
); // responses get saved anway
280 unset($responses->finishattempt
); // same as $finishattempt
281 unset($responses->markall
);
282 unset($responses->forcenewattempt
);
285 // $actions is an array indexed by the questions ids
286 $actions = quiz_extract_responses($questions, $responses, $event);
288 // Process each question in turn
290 $questionidarray = explode(',', $questionids);
291 foreach($questionidarray as $i) {
292 if (!isset($actions[$i])) {
293 $actions[$i]->responses
= array('' => '');
295 $actions[$i]->timestamp
= $timestamp;
296 quiz_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt);
297 quiz_save_question_session($questions[$i], $states[$i]);
300 $attempt->timemodified
= $timestamp;
302 // We have now finished processing form data
306 /// Finish attempt if requested
307 if ($finishattempt) {
309 // Set the attempt to be finished
310 $attempt->timefinish
= $timestamp;
312 // Find all the questions for this attempt for which the newest
313 // state is not also the newest graded state
314 if ($closequestions = get_records_select('quiz_newest_states',
315 "attemptid = $attempt->id AND newest != newgraded", '', 'questionid, questionid')) {
317 // load all the questions
318 $closequestionlist = implode(',', array_keys($closequestions));
319 $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
320 " FROM {$CFG->prefix}quiz_questions q,".
321 " {$CFG->prefix}quiz_question_instances i".
322 " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
323 " AND q.id IN ($closequestionlist)";
324 if (!$closequestions = get_records_sql($sql)) {
325 error('Questions missing');
328 // Load the question type specific information
329 if (!quiz_get_question_options($closequestions)) {
330 error('Could not load question options');
333 // Restore the question sessions
334 if (!$closestates = quiz_restore_question_sessions($closequestions, $quiz, $attempt)) {
335 error('Could not restore question sessions');
338 foreach($closequestions as $key => $question) {
339 $action->event
= QUIZ_EVENTCLOSE
;
340 $action->responses
= $closestates[$key]->responses
;
341 $action->timestamp
= $closestates[$key]->timestamp
;
342 quiz_process_responses($question, $closestates[$key], $action, $quiz, $attempt);
343 quiz_save_question_session($question, $closestates[$key]);
346 add_to_log($course->id
, 'quiz', 'close attempt',
347 "review.php?attempt=$attempt->id",
348 "$quiz->id", $cm->id
);
351 /// Update the quiz attempt and the overall grade for the quiz
352 if ($responses ||
$finishattempt) {
353 if (!update_record('quiz_attempts', $attempt)) {
354 error('Failed to save the current quiz attempt!');
356 if (($attempt->attempt
> 1 ||
$attempt->timefinish
> 0) and !$attempt->preview
) {
357 quiz_save_best_grade($quiz);
361 /// Check access to quiz page
363 // check the quiz times
364 if (($timestamp < $quiz->timeopen ||
$timestamp > $quiz->timeclose
)) {
366 notify(get_string('notavailabletostudents', 'quiz'));
368 print_continue(get_string('notavailable', 'quiz'), "view.php?id={$cm->id}");
372 if ($finishattempt) {
373 redirect('review.php?attempt='.$attempt->id
);
376 /// Print the quiz page ////////////////////////////////////////////////////////
378 /// Print the attempt number or preview heading
380 print_heading(get_string('previewquiz', 'quiz', format_string($quiz->name
)));
381 unset($buttonoptions);
382 $buttonoptions['q'] = $quiz->id
;
383 $buttonoptions['forcenew'] = true;
385 print_single_button($CFG->wwwroot
.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz'));
388 notify(get_string('popupnotice', 'quiz'));
391 print_heading($strattemptnum);
395 if($quiz->timelimit
> 0) {
396 // Make sure javascript is enabled for time limited quizzes
398 <script language
="javascript" type
="text/javascript">
400 document
.write("<form name=\"responseform\" method=\"post\" action=\"attempt.php\">\n");
404 <center
><p
><strong
><?php
print_string('noscript', 'quiz'); ?
></strong
></p
></center
>
408 echo "<form name=\"responseform\" method=\"post\" action=\"attempt.php\">\n";
411 // Add a hidden field with the quiz id
412 echo '<input type="hidden" name="q" value="' . s($quiz->id
) . "\" />\n";
414 /// Print the navigation panel if required
415 $numpages = quiz_number_of_pages($attempt->layout
);
418 <script language
="javascript" type
="text/javascript">
419 function navigate(page
) {
420 document
.responseform
.page
.value
=page
;
421 document
.responseform
.submit();
425 echo '<input type="hidden" id="page" name="page" value="'.$page."\" />\n";
426 quiz_print_navigation_panel($page, $numpages);
430 /// Print all the questions
432 // Add a hidden field with questionids
433 echo '<input type="hidden" name="questionids" value="'.$pagelist."\" />\n";
435 $pagequestions = explode(',', $pagelist);
436 $number = quiz_first_questionnumber($attempt->layout
, $pagelist);
437 foreach ($pagequestions as $i) {
438 $options = quiz_get_renderoptions($quiz, $states[$i]);
439 // Print the question
443 quiz_print_quiz_question($questions[$i], $states[$i], $number, $quiz, $options);
444 quiz_save_question_session($questions[$i], $states[$i]);
445 $number +
= $questions[$i]->length
;
448 /// Print the submit buttons
450 $strconfirmattempt = addslashes(get_string("confirmclose", "quiz"));
451 $onclick = "return confirm('$strconfirmattempt')";
454 echo "<input type=\"submit\" name=\"saveattempt\" value=\"".get_string("savenosubmit", "quiz")."\" />\n";
455 if ($quiz->optionflags
& QUIZ_ADAPTIVE
) {
456 echo "<input type=\"submit\" name=\"markall\" value=\"".get_string("markall", "quiz")."\" />\n";
458 echo "<input type=\"submit\" name=\"finishattempt\" value=\"".get_string("finishattempt", "quiz")."\" onclick=\"$onclick\" />\n";
459 echo '<input type="hidden" name="timeup" value="0" />';
463 // Print the navigation panel if required
466 quiz_print_navigation_panel($page, $numpages);
474 $secondsleft = $quiz->timeclose
- time();
475 // If time limit is set include floating timer.
476 if ($quiz->timelimit
> 0) {
478 $timesincestart = time() - $attempt->timestart
;
479 $timerstartvalue = min($quiz->timelimit
*60 - $timesincestart, $secondsleft);
480 if ($timerstartvalue <= 0) {
481 $timerstartvalue = 1;
484 require('jstimer.php');
486 // Add the javascript timer in the title bar if the closing time appears close
487 if ($secondsleft > 0 and $secondsleft < 24*3600) { // less than a day remaining
488 include('jsclock.php');
494 print_footer($course);