diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php index 4517f058e0b..9a705aa6e81 100644 --- a/mod/quiz/attempt.php +++ b/mod/quiz/attempt.php @@ -22,6 +22,10 @@ $timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer. $forcenew = optional_param('forcenew', false, PARAM_BOOL); // Teacher has requested new preview + // remember the current time as the time any responses were submitted + // (so as to make sure students don't get penalized for slow processing on this page) + $timestamp = time(); + // We treat automatically closed attempts just like normally closed attempts if ($timeup) { $finishattempt = 1; @@ -104,15 +108,6 @@ error(get_string('nomoreattempts', 'quiz'), "view.php?id={$cm->id}"); } - $timenow = time(); - if (($timenow < $quiz->timeopen || $timenow > $quiz->timeclose)) { - if ($isteacher) { - notify(get_string('notavailabletostudents', 'quiz')); - } else { - error(get_string('notavailable', 'quiz'), "view.php?id={$cm->id}"); - } - } - /// Check subnet access if ($quiz->subnet and !address_in_subnet(getremoteaddr(), $quiz->subnet)) { if ($isteacher) { @@ -161,7 +156,7 @@ if ($isteacher and $forcenew) { // teacher wants a new preview // so we set a finish time on the current attempt (if any). // It will then automatically be deleted below - set_field('quiz_attempts', 'timefinish', time(), 'quiz', $quiz->id, 'userid', $USER->id); + set_field('quiz_attempts', 'timefinish', $timestamp, 'quiz', $quiz->id, 'userid', $USER->id); } $attempt = get_record('quiz_attempts', 'quiz', $quiz->id, @@ -204,13 +199,15 @@ } } else { // log continuation of attempt only if some time has lapsed - if ((time() - $attempt->timemodified) > 600) { // 10 minutes have elapsed + if (($timestamp - $attempt->timemodified) > 600) { // 10 minutes have elapsed add_to_log($course->id, 'quiz', 'continue attempt', "review.php?attempt=$attempt->id", "$quiz->id", $cm->id); } } - + if ($attempt->timestart) { // shouldn't really happen, just for robustness + $attempt->timestart = time(); + } /// Load all the questions and states needed by this script @@ -282,11 +279,12 @@ if (!isset($actions[$i])) { $actions[$i]->responses = array('' => ''); } + $actions[$i]->timestamp = $timestamp; quiz_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt); quiz_save_question_session($questions[$i], $states[$i]); } - $attempt->timemodified = time(); + $attempt->timemodified = $timestamp; // We have now finished processing form data } @@ -296,7 +294,7 @@ if ($finishattempt) { // Set the attempt to be finished - $attempt->timefinish = time(); + $attempt->timefinish = $timestamp; // Find all the questions for this attempt for which the newest // state is not also the newest graded state @@ -327,6 +325,7 @@ foreach($closequestions as $key => $question) { $action->event = QUIZ_EVENTCLOSE; $action->responses = $closestates[$key]->responses; + $action->timestamp = $colsestates[$key]->timestamp; quiz_process_responses($question, $closestates[$key], $action, $quiz, $attempt); quiz_save_question_session($question, $closestates[$key]); } @@ -336,7 +335,6 @@ "$quiz->id", $cm->id); } - /// Update the quiz attempt and the overall grade for the quiz if ($responses || $finishattempt) { if (!update_record('quiz_attempts', $attempt)) { @@ -347,31 +345,21 @@ } } +/// Check access to quiz page + + // check the quiz times + if (($timestamp < $quiz->timeopen || $timestamp > $quiz->timeclose)) { + if ($isteacher) { + notify(get_string('notavailabletostudents', 'quiz')); + } else { + print_continue(get_string('notavailable', 'quiz'), "view.php?id={$cm->id}"); + } + } + if ($finishattempt) { redirect('review.php?attempt='.$attempt->id); } -/// Get time limit if any. - $timelimit = $quiz->timelimit * 60; - - if ($timelimit > 0) { - $timestart = $attempt->timestart; - if ($timestart) { - $timesincestart = $timenow - $timestart; - $timerstartvalue = $timelimit - $timesincestart; - } else { - $timerstartvalue = $timelimit; - } - if ($timerstartvalue <= 0) { - $timerstartvalue = 1; - } - if(($timelimit + 60) <= $timesincestart) { - // To pass it on to quiz_grade_responses - $quiz->timesincestart = $timesincestart; - } - } - - /// Print the quiz page //////////////////////////////////////////////////////// /// Print the attempt number or preview heading @@ -387,12 +375,6 @@ print_heading($strattemptnum); } -/// Add the javascript timer in the title bar if the closing time appears close - $secondsleft = $quiz->timeclose - time(); - if ($secondsleft > 0 and $secondsleft < 24*3600) { // less than a day remaining - include('jsclock.php'); - } - /// Start the form if($quiz->timelimit > 0) { // Make sure javascript is enabled for time limited quizzes @@ -472,9 +454,23 @@ // Finish the form echo "\n"; + + $secondsleft = $quiz->timeclose - time(); // If time limit is set include floating timer. - if ($timelimit > 0) { + if ($quiz->timelimit > 0) { + + $timesincestart = time() - $attempt->timestart; + $timerstartvalue = min($quiz->timelimit*60 - $timesincestart, $secondsleft); + if ($timerstartvalue <= 0) { + $timerstartvalue = 1; + } + require('jstimer.php'); + } else { + // Add the javascript timer in the title bar if the closing time appears close + if ($secondsleft > 0 and $secondsleft < 24*3600) { // less than a day remaining + include('jsclock.php'); + } } if (!$isteacher) { diff --git a/mod/quiz/doc/eventtypes.html b/mod/quiz/doc/eventtypes.html new file mode 100644 index 00000000000..112db1078b7 --- /dev/null +++ b/mod/quiz/doc/eventtypes.html @@ -0,0 +1,38 @@ + +
+The $state object (and the quiz_states table) has a field $event +which indicates the event that led to the state's creation. The +field can take the value of any of the following constants (defined +in locallib.php):
+ +When new responses are being processed by +quiz_process_responses() then this function is being passed the +event type in $action->event while the responses are in +$action->responses.
+ + diff --git a/mod/quiz/doc/penalties.html b/mod/quiz/doc/penalties.html index 89100fa8c41..cbec720314c 100644 --- a/mod/quiz/doc/penalties.html +++ b/mod/quiz/doc/penalties.html @@ -60,6 +60,15 @@ per response.A quiz can have a time limit. This is stored in minutes in +$quiz->timelimit. So before using this in time calculations it +always has to be multiplied by 60 to turn it into seconds like all +other timestamps in moodle and php. If $quiz->timelimit is zero it +means there is no timelimit.
+ +If a student asks to start an attempt on view.php for a quiz with +a timelimit then he is shown a javascript message alerting him to +the timelimit and is asked to confirm.
+ +For quizzes with timelimit attempt.php shows a javascript timer +that counts down and automatically submits and closes the attempt +when the time is up.
+ +Confusingly there are two javascript timers in the quiz module. +jsclock.php provides a countdown in the title bar that counts down +to the quiz closing time if this is less than a day away. This has +nothing to do with the timelimit. jstimer.php provides the countdown +timer that implements the timelimit. It in turn uses timer.js.
+ +The time a response was submitted by the student is recorded by +attempt.php right at the top of the page and is then passed on to +quiz_process_responses in $action->timestamp. This puts it into +$state->timestamp. Finally, after the responses have been graded, +the function quiz_apply_penalty_and_timelimit() checks that the +responses are within the timelimit to within 5% and if not it sets +the grade to zero (or the previously obtained grade, if that is +higher).
+ +diff --git a/mod/quiz/doc/timelimit.html b/mod/quiz/doc/timelimit.html new file mode 100644 index 00000000000..a7642dff58e --- /dev/null +++ b/mod/quiz/doc/timelimit.html @@ -0,0 +1,39 @@ + +
+
+ + +