file ongoing.html was added on branch MOODLE_15_STABLE on 2005-07-07 16:14:36 +0000
[moodle.git] / mod / quiz / attempt.php
blobd4c0765ac491ddba4433d38f4414fee28568fdaa
1 <?php // $Id$
2 /**
3 * This page prints a particular instance of quiz
5 * @version $Id$
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
11 * @package quiz
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)
27 $timestamp = time();
29 // We treat automatically closed attempts just like normally closed attempts
30 if ($timeup) {
31 $finishattempt = 1;
34 if ($id) {
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");
47 } else {
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")) {
66 $attemptnumber = 1;
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
74 if (!empty($popup)) {
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');
78 } else {
79 $strupdatemodule = isteacheredit($course->id)
80 ? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz'))
81 : "";
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
91 if ($isteacher) {
92 $currenttab = 'preview';
93 include('tabs.php');
94 } else {
95 print_heading(format_string($quiz->name));
98 /// Check availability
99 if (isguest()) {
100 print_heading(get_string('guestsno', 'quiz'));
101 if (empty($popup)) {
102 print_footer($course);
104 exit;
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)) {
113 if ($isteacher) {
114 notify(get_string('subnetnotice', 'quiz'));
115 } else {
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");
127 echo "<br />\n";
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";
137 echo "</div>\n";
139 print_simple_box_end();
140 echo "</form>\n";
142 if (empty($popup)) {
143 print_footer();
145 exit;
147 } else {
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);
165 $newattempt = false;
166 if (!$attempt) {
167 // Check if this is a preview request from a teacher
168 // in which case the previous previews should be deleted
169 if ($isteacher) {
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);
181 $newattempt = true;
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
185 if ($isteacher) {
186 $attempt->preview = 1;
188 // Save the attempt
189 if (!$attempt->id = insert_record('quiz_attempts', $attempt)) {
190 error('Could not create new attempt');
192 // make log entries
193 if ($isteacher) {
194 add_to_log($course->id, 'quiz', 'preview',
195 "attempt.php?id=$cm->id",
196 "$quiz->id", $cm->id);
197 } else {
198 add_to_log($course->id, 'quiz', 'attempt',
199 "review.php?attempt=$attempt->id",
200 "$quiz->id", $cm->id);
202 } else {
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);
219 if ($newattempt) {
220 $questionlist = quiz_questions_in_quiz($attempt->layout);
221 } else {
222 $questionlist = $pagelist;
225 // add all questions that are on the submitted form
226 if ($questionids) {
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
257 if ($newattempt) {
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);
284 // extract responses
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)) {
365 if ($isteacher) {
366 notify(get_string('notavailabletostudents', 'quiz'));
367 } else {
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
379 if ($isteacher) {
380 print_heading(get_string('previewquiz', 'quiz', format_string($quiz->name)));
381 unset($buttonoptions);
382 $buttonoptions['q'] = $quiz->id;
383 $buttonoptions['forcenew'] = true;
384 echo '<center>';
385 print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz'));
386 echo '</center>';
387 if ($quiz->popup) {
388 notify(get_string('popupnotice', 'quiz'));
390 } else {
391 print_heading($strattemptnum);
394 /// Start the form
395 if($quiz->timelimit > 0) {
396 // Make sure javascript is enabled for time limited quizzes
398 <script language="javascript" type="text/javascript">
399 <!--
400 document.write("<form name=\"responseform\" method=\"post\" action=\"attempt.php\">\n");
401 // -->
402 </script>
403 <noscript>
404 <center><p><strong><?php print_string('noscript', 'quiz'); ?></strong></p></center>
405 </noscript>
406 <?php
407 } else {
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);
416 if ($numpages > 1) {
418 <script language="javascript" type="text/javascript">
419 function navigate(page) {
420 document.responseform.page.value=page;
421 document.responseform.submit();
423 </script>
424 <?php
425 echo '<input type="hidden" id="page" name="page" value="'.$page."\" />\n";
426 quiz_print_navigation_panel($page, $numpages);
427 echo "<br />\n";
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
440 if ($i > 0) {
441 echo "<br />\n";
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')";
452 echo "<center>\n";
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" />';
461 echo "</center>";
463 // Print the navigation panel if required
464 if ($numpages > 1) {
465 echo "<br />\n";
466 quiz_print_navigation_panel($page, $numpages);
467 echo '<br />';
470 // Finish the form
471 echo "</form>\n";
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');
485 } else {
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');
492 // Finish the page
493 if (empty($popup)) {
494 print_footer($course);