From 488cf46b651a5001b8fef39bf5451b2aeacff19c Mon Sep 17 00:00:00 2001
From: gustav_delius
Date: Sun, 15 May 2005 11:45:12 +0000
Subject: [PATCH] Cleaned up issues to do with timing. So for example late
submissions are now detected correctly.
---
mod/quiz/attempt.php | 82 +++++++++++++++++-------------------
mod/quiz/doc/eventtypes.html | 38 +++++++++++++++++
mod/quiz/doc/penalties.html | 9 ++++
mod/quiz/doc/timelimit.html | 39 +++++++++++++++++
mod/quiz/locallib.php | 62 ++++++++++++++++++---------
mod/quiz/preview.php | 1 +
6 files changed, 169 insertions(+), 62 deletions(-)
create mode 100644 mod/quiz/doc/eventtypes.html
create mode 100644 mod/quiz/doc/timelimit.html
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 @@
+
+
+ Event types
+
+
+
+
+Event types
+
+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):
+
+
+ - EVENTOPEN: The attempt has just been opened and this is the initial
+ state for which no attempts have come in yet.
+ - EVENTSAVE: The responses are just being saved, either because the student
+ requested this explicitly or because the student navigated to another
+ quiz page.
+ - EVENTVALIDATE: The student requested a validation of the responses.
+
- EVENTGRADE: The responses are being graded but the question session
+ is not closed.
+ - EVENTCLOSE: The responses are being graded and the question session
+ is closed. Usually this happens because the whole attempt closes,
+ either because the student requests it or because the time is up
+ or we are beyond the due date.
+ - EVENTDUPLICATEGRADE: This is a strange one. It indicates that the
+ responses would have been graded had they not been found to be
+ identical to previous responses.
+
+
+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.
Where it is done in the code
+The function quiz_apply_penalty_and_timelimit() subtracts the penalty in
+$state->sumpenalty from the raw grade in $state->raw_grade to obtain
+$state->grade for the response. However it is ensured that the grade
+of a new attempt at the question never falls below the previously
+achieved grade. This function also increases $state->sumpenalty by
+the amount in $state->penalty. The assumption is that
+$state->penalty has just been set appropriately by the code calling
+this function, e.g., quiz_process_responses.
+
About wrapped questions
+
+Time limit
+
+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 @@
+
+