]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-15542 - Refactor quiz response processing into a separate processresponses.php.
authortjhunt <tjhunt>
Tue, 15 Jul 2008 16:46:24 +0000 (16:46 +0000)
committertjhunt <tjhunt>
Tue, 15 Jul 2008 16:46:24 +0000 (16:46 +0000)
lang/en_utf8/quiz.php
mod/quiz/attempt.php
mod/quiz/attemptlib.php
mod/quiz/processattempt.php [new file with mode: 0644]
mod/quiz/summary.php
question/type/questiontype.php

index 84beb2b6500dc9020f39e0058aa85b8e784222b2..e0210c30cac10c140fb132952b1678d10be0c9db 100644 (file)
@@ -51,6 +51,7 @@ $string['answerswithacceptederrormarginmustbenumeric'] = 'Answers with accepted
 $string['answertoolong'] = 'Answer too long after line $a (255 char. max)';
 $string['aon'] = 'AON format';
 $string['attempt'] = 'Attempt $a';
+$string['attemptalreadyclosed'] = 'This attempt has already be finished.';
 $string['attemptclosed'] = 'Attempt has not closed yet';
 $string['attemptduration'] = 'Time taken';
 $string['attemptedon'] = 'Attempted on';
@@ -374,6 +375,7 @@ $string['nocategory'] = 'Incorrect or no category specified';
 $string['nocommentsyet'] = 'No comments yet.';
 $string['noconnection'] = 'There is currently no connection to a web service that can process this question. Please contact your administrator';
 $string['nodataset'] = 'nothing - it is not a wild card';
+$string['nodatasubmitted'] = 'No data was submitted.';
 $string['noessayquestionsfound'] = 'No manually graded questions found';
 $string['nomatchinganswer'] = 'You must specify an answer matching the question \'$a\'.';
 $string['nominal'] = 'Nominal';
index c521d62a697b26d4694561b04ed9cff9a71ec53e..d0cb979d07e5bafee1caa0dfb636a54a8b14cfbc 100644 (file)
         redirect($CFG->wwwroot . '/mod/quiz/startattempt.php?cmid=' . $cm->id . '&sesskey=' . sesskey());
     }
 
-/// 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)
-    $timenow = time();
-
 /// Get submitted parameters.
     $attemptid = required_param('attempt', PARAM_INT);
     $page = optional_param('page', 0, PARAM_INT);
-    $submittedquestionids = optional_param('questionids', '', PARAM_SEQUENCE);
-    $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
-    $timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer.
 
     $attemptobj = new quiz_attempt($attemptid);
 
-/// Because IE is buggy (see http://www.peterbe.com/plog/button-tag-in-IE) we cannot
-/// do the quiz navigation buttons as <button type="submit" name="page" value="N">Caption</button>.
-/// Instead we have to do them as <input type="submit" name="gotopageN" value="Caption"/> -
-/// at lest that seemed like the least horrible work-around to me. Therefore, we need to
-/// intercept gotopageN parameters here, and adjust $pgae accordingly.
-    if (optional_param('gotosummary', false, PARAM_BOOL)) {
-        $page = -1;
-    } else {
-        $numpagesinquiz = $attemptobj->get_num_pages();
-        for ($i = 0; $i < $numpagesinquiz; $i++) {
-            if (optional_param('gotopage' . $i, false, PARAM_BOOL)) {
-                $page = $i;
-                break;
-            }
-        }
-    }
-
-/// We treat automatically closed attempts just like normally closed attempts
-    if ($timeup) {
-        $finishattempt = 1;
-    }
-
 /// Check login.
     require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());
 
         $attemptobj->require_capability('mod/quiz:attempt');
     }
 
+/// If the attempt is already closed, send them to the review page.
+    if ($attemptobj->is_finished()) {
+        redirect($attemptobj->review_url(0, $page));
+    }
+
+/// Check the access rules.
+    $accessmanager = $attemptobj->get_access_manager(time());
+    $messages = $accessmanager->prevent_access();
+    if (!$attemptobj->is_preview_user() && $messages) {
+        print_error('attempterror', 'quiz', $quizobj->view_url(),
+                $accessmanager->print_messages($messages, true));
+    }
+    $accessmanager->do_password_check($attemptobj->is_preview_user());
+
 /// Log continuation of the attempt, but only if some time has passed.
-    if (($timenow - $attemptobj->get_attempt()->timemodified) > QUIZ_CONTINUE_ATTEMPT_LOG_INTERVAL) {
+    if ((time() - $attemptobj->get_attempt()->timemodified) > QUIZ_CONTINUE_ATTEMPT_LOG_INTERVAL) {
     /// This action used to be 'continue attempt' but the database field has only 15 characters.
         add_to_log($attemptobj->get_courseid(), 'quiz', 'continue attemp',
                 'review.php?attempt=' . $attemptobj->get_attemptid(),
     }
 
 /// Get the list of questions needed by this page.
-    if ($finishattempt) {
-        $questionids = $attemptobj->get_question_ids();
-    } else if ($page >= 0) {
-        $questionids = $attemptobj->get_question_ids($page);
-    } else {
-        $questionids = array();
-    }
-
-/// Add all questions that are on the submitted form
-    if ($submittedquestionids) {
-        $submittedquestionids = explode(',', $submittedquestionids);
-        $questionids = array_unique(array_merge($questionids, $submittedquestionids));
-    } else {
-        $submittedquestionids = array();
-    }
+    $questionids = $attemptobj->get_question_ids($page);
 
 /// Check.
     if (empty($questionids)) {
     $attemptobj->load_questions($questionids);
     $attemptobj->load_question_states($questionids);
 
-
-/// Process form data /////////////////////////////////////////////////
-
-    if ($responses = data_submitted() and empty($responses->quizpassword)) {
-
-    /// Set the default event. This can be overruled by individual buttons.
-        if (array_key_exists('markall', $responses)) {
-            $event = QUESTION_EVENTSUBMIT;
-        } else if ($finishattempt) {
-            $event = QUESTION_EVENTCLOSE;
-        } else {
-            $event = QUESTION_EVENTSAVE;
-        }
-
-    /// Unset any variables we know are not responses
-        unset($responses->id);
-        unset($responses->q);
-        unset($responses->oldpage);
-        unset($responses->newpage);
-        unset($responses->review);
-        unset($responses->questionids);
-        unset($responses->saveattempt); // responses get saved anway
-        unset($responses->finishattempt); // same as $finishattempt
-        unset($responses->markall);
-        unset($responses->forcenewattempt);
-
-    /// Extract the responses. $actions will be an array indexed by the questions ids.
-        $actions = question_extract_responses($attemptobj->get_questions($questionids),
-                $responses, $event);
-
-    /// Process each question in turn
-        $success = true;
-        $attempt = $attemptobj->get_attempt();
-        foreach($submittedquestionids as $id) {
-            if (!isset($actions[$id])) {
-                $actions[$id]->responses = array('' => '');
-                $actions[$id]->event = QUESTION_EVENTOPEN;
-            }
-            $actions[$id]->timestamp = $timenow;
-            $state = $attemptobj->get_question_state($id);
-            if (question_process_responses($attemptobj->get_question($id),
-                    $state, $actions[$id], $attemptobj->get_quiz(), $attempt)) {
-                save_question_session($attemptobj->get_question($id), $state);
-            } else {
-                $success = false;
-            }
-        }
-
-        if (!$success) {
-            print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page));
-        }
-
-        $attempt->timemodified = $timenow;
-        if (!$DB->update_record('quiz_attempts', $attempt)) {
-            quiz_error($quiz, 'saveattemptfailed');
-        }
-
-        // For now, reload the states to pick up the changes:
-        $attemptobj->load_question_states($questionids);
-    }
-
-/// Finish attempt if requested
-    if ($finishattempt) {
-
-    /// Move each question to the closed state.
-        $success = true;
-        $attempt = $attemptobj->get_attempt();
-        foreach ($attemptobj->get_questions() as $id => $question) {
-            $action = new stdClass;
-            $action->event = QUESTION_EVENTCLOSE;
-            $action->responses = $attemptobj->get_question_state($id)->responses;
-            $action->timestamp = $attemptobj->get_question_state($id)->timestamp;
-            $state = $attemptobj->get_question_state($id);
-            if (question_process_responses($attemptobj->get_question($id),
-                    $state, $action, $attemptobj->get_quiz(), $attempt)) {
-                save_question_session($attemptobj->get_question($id), $state);
-            } else {
-                $success = false;
-            }
-        }
-
-        if (!$success) {
-            print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page));
-        }
-
-    /// Log the end of this attempt.
-        add_to_log($attemptobj->get_courseid(), 'quiz', 'close attempt',
-                'review.php?attempt=' . $attemptobj->get_attemptid(),
-                $attemptobj->get_quizid(), $attemptobj->get_cmid());
-
-    /// Update the quiz attempt record.
-        $attempt->timemodified = $timenow;
-        $attempt->timefinish = $timenow;
-        if (!$DB->update_record('quiz_attempts', $attempt)) {
-            quiz_error($quiz, 'saveattemptfailed');
-        }
-
-        if (!$attempt->preview) {
-        /// Record this user's best grade (if this is not a preview).
-            quiz_save_best_grade($quiz);
-
-        /// Send any notification emails (if this is not a preview).
-            $attemptobj->quiz_send_notification_emails();
-        }
-
-    /// Clear the password check flag in the session.
-        $accessmanager = $attemptobj->get_access_manager($timenow);
-        $accessmanager->clear_password_access();
-
-    /// Send the user to the review page.
-        redirect($attemptobj->review_url());
-    }
-
-/// Now is the right time to check access.
-    $accessmanager = $attemptobj->get_access_manager($timenow);
-    $messages = $accessmanager->prevent_access();
-    if (!$attemptobj->is_preview_user() && $messages) {
-        print_error('attempterror', 'quiz', $quizobj->view_url(),
-                $accessmanager->print_messages($messages, true));
-    }
-    $accessmanager->do_password_check($attemptobj->is_preview_user());
-
-/// Having processed the responses, we want to go to the summary page.
-if ($page == -1) {
-    redirect($attemptobj->summary_url());
-}
-
 /// Print the quiz page ////////////////////////////////////////////////////////
 
     // Print the page header
@@ -274,7 +118,7 @@ if ($page == -1) {
     }
 
     // Start the form
-    echo '<form id="responseform" method="post" action="', $attemptobj->attempt_url(0, $page),
+    echo '<form id="responseform" method="post" action="', $attemptobj->processattempt_url(),
             '" enctype="multipart/form-data"' .
             ' onclick="this.autocomplete=\'off\'" onkeypress="return check_enter(event);">', "\n";
     if($attemptobj->get_quiz()->timelimit > 0) {
@@ -312,16 +156,20 @@ if ($page == -1) {
 /// Print a link to the next page.
     echo '<div class="submitbtns">';
     if ($attemptobj->is_last_page($page)) {
-        $nextpage = 'gotosummary';
+        $nextpage = -1;
+        $nextpageforie = 'gotosummary';
     } else {
-        $nextpage = 'gotopage' . ($page + 1);
+        $nextpage = $page + 1;
+        $nextpageforie = 'gotopage' . $nextpage;
     }
-    echo '<input type="submit" name="' . $nextpage . '" value="' . get_string('next') . '" />';
+    echo '<input type="submit" name="' . $nextpageforie . '" value="' . get_string('next') . '" />';
     echo "</div>";
 
-    // Finish the form
-    echo '</div>';
+    // Some hidden fields to trach what is going on.
+    echo '<input type="hidden" name="attempt" value="' . $attemptobj->get_attemptid() . '" />';
+    echo '<input type="hidden" name="nextpage" value="' . $nextpage . '" />';
     echo '<input type="hidden" name="timeup" id="timeup" value="0" />';
+    echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
 
     // Add a hidden field with questionids. Do this at the end of the form, so
     // if you navigate before the form has finished loading, it does not wipe all
@@ -329,6 +177,8 @@ if ($page == -1) {
     echo '<input type="hidden" name="questionids" value="' .
             implode(',', $attemptobj->get_question_ids($page)) . "\" />\n";
 
+    // Finish the form
+    echo '</div>';
     echo "</form>\n";
 
     // End middle column.
index 31b03ff3119d3275aefd333ae04853e58df8b61c..7d7166f6a890a117fe0e1677bf2afd20cf1d88b6 100644 (file)
@@ -590,6 +590,14 @@ class quiz_attempt extends quiz {
         return $CFG->wwwroot . '/mod/quiz/summary.php?attempt=' . $this->attempt->id;
     }
 
+    /**
+     * @return string the URL of this quiz's summary page.
+     */
+    public function processattempt_url() {
+        global $CFG;
+        return $CFG->wwwroot . '/mod/quiz/processattempt.php';
+    }
+
     /**
      * @param integer $page if specified, the URL of this particular page of the attempt, otherwise
      * the URL will go to the first page.
diff --git a/mod/quiz/processattempt.php b/mod/quiz/processattempt.php
new file mode 100644 (file)
index 0000000..b63c011
--- /dev/null
@@ -0,0 +1,214 @@
+<?php  // $Id$
+/**
+ * This page deals with processing responses during an attempt at a quiz.
+ *
+ * People will normally arrive here from a form submission on attempt.php or
+ * summary.php, and once the responses are processed, they will be redirected to
+ * attempt.php or summary.php.
+ *
+ * This code used to be near the top of attempt.php, if you are looking for CVS history.
+ *
+ * @author Tim Hunt.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package quiz
+ */
+
+require_once(dirname(__FILE__) . '/../../config.php');
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+
+/// 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)
+$timenow = time();
+
+/// Get submitted parameters.
+$attemptid = required_param('attempt', PARAM_INT);
+$nextpage = optional_param('nextpage', 0, PARAM_INT);
+$submittedquestionids = required_param('questionids', PARAM_SEQUENCE);
+$finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
+$timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer.
+
+$attemptobj = new quiz_attempt($attemptid);
+
+/// Because IE is buggy (see http://www.peterbe.com/plog/button-tag-in-IE) we cannot
+/// do the quiz navigation buttons as <button type="submit" name="page" value="N">Caption</button>.
+/// Instead we have to do them as <input type="submit" name="gotopageN" value="Caption"/> -
+/// at lest that seemed like the least horrible work-around to me. Therefore, we need to
+/// intercept gotopageN parameters here, and adjust $pgae accordingly.
+if (optional_param('gotosummary', false, PARAM_BOOL)) {
+    $nextpage = -1;
+} else {
+    $numpagesinquiz = $attemptobj->get_num_pages();
+    for ($i = 0; $i < $numpagesinquiz; $i++) {
+        if (optional_param('gotopage' . $i, false, PARAM_BOOL)) {
+            $nextpage = $i;
+            break;
+        }
+    }
+}
+
+/// Set $nexturl now. It will be updated if a particular question was sumbitted in
+/// adaptive mode.
+if ($nextpage == -1) {
+    $nexturl = $attemptobj->summary_url();
+} else {
+    $nexturl = $attemptobj->attempt_url($nextpage);
+}
+
+/// We treat automatically closed attempts just like normally closed attempts
+if ($timeup) {
+    $finishattempt = 1;
+}
+
+/// Check login.
+require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());
+confirm_sesskey();
+
+/// Check that this attempt belongs to this user.
+if ($attemptobj->get_userid() != $USER->id) {
+    quiz_error($attemptobj->get_quiz(), 'notyourattempt');
+}
+
+/// Check capabilites.
+if (!$attemptobj->is_preview_user()) {
+    $attemptobj->require_capability('mod/quiz:attempt');
+}
+
+/// If the attempt is already closed, send them to the review page.
+if ($attemptobj->is_finished()) {
+    quiz_error($attemptobj->get_quiz(), 'attemptalreadyclosed');
+}
+
+/// Don't log - we will end with a redirect to a page that is logged.
+
+/// Get the list of questions needed by this page.
+$submittedquestionids = explode(',', $submittedquestionids);
+if ($finishattempt) {
+    $questionids = $attemptobj->get_question_ids();
+} else {
+    $questionids = $submittedquestionids;
+}
+
+/// Load those questions we need, and just the submitted states for now.
+$attemptobj->load_questions($questionids);
+$attemptobj->load_question_states($submittedquestionids);
+
+/// Process the responses /////////////////////////////////////////////////
+if (!$responses = data_submitted()) {
+    quiz_error($attemptobj->get_quiz(), 'nodatasubmitted');
+}
+
+/// Set the default event. This can be overruled by individual buttons.
+if ($finishattempt) {
+    $event = QUESTION_EVENTCLOSE;
+} else {
+    $event = QUESTION_EVENTSAVE;
+}
+
+/// Unset any variables we know are not responses
+unset($responses->id);
+unset($responses->q);
+unset($responses->oldpage);
+unset($responses->newpage);
+unset($responses->review);
+unset($responses->questionids);
+unset($responses->finishattempt); // same as $finishattempt
+unset($responses->forcenewattempt);
+
+/// Extract the responses. $actions will be an array indexed by the questions ids.
+$actions = question_extract_responses($attemptobj->get_questions($submittedquestionids),
+        $responses, $event);
+
+/// Process each question in turn
+$success = true;
+$attempt = $attemptobj->get_attempt();
+foreach($submittedquestionids as $id) {
+    if (!isset($actions[$id])) {
+        $actions[$id]->responses = array('' => '');
+        $actions[$id]->event = QUESTION_EVENTOPEN;
+    }
+    $actions[$id]->timestamp = $timenow;
+
+/// If a particular question was submitted, update the nexturl to go back to that question.
+    if ($actions[$id]->event == QUESTION_EVENTSUBMIT) {
+        $nexturl = $attemptobj->attempt_url($id);
+    }
+    
+
+    $state = $attemptobj->get_question_state($id);
+    if (question_process_responses($attemptobj->get_question($id),
+            $state, $actions[$id], $attemptobj->get_quiz(), $attempt)) {
+        save_question_session($attemptobj->get_question($id), $state);
+    } else {
+        $success = false;
+    }
+}
+
+if (!$success) {
+    print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page));
+}
+
+/// If we do not have to finish the attempts (if we are only processing responses)
+/// save the attempt and redirect to the next page.
+if (!$finishattempt) {
+    $attempt->timemodified = $timenow;
+    if (!$DB->update_record('quiz_attempts', $attempt)) {
+        quiz_error($quiz, 'saveattemptfailed');
+    }
+
+    redirect($nexturl);
+}
+
+/// We have been asked to finish attempt, so do that //////////////////////
+
+/// Load the states of questions we have not done anything with, and reload the 
+/// ones we changed above.
+$attemptobj->load_question_states();
+
+/// Move each question to the closed state.
+$success = true;
+$attempt = $attemptobj->get_attempt();
+foreach ($attemptobj->get_questions() as $id => $question) {
+    $action = new stdClass;
+    $action->event = QUESTION_EVENTCLOSE;
+    $action->responses = $attemptobj->get_question_state($id)->responses;
+    $action->timestamp = $attemptobj->get_question_state($id)->timestamp;
+    $state = $attemptobj->get_question_state($id);
+    if (question_process_responses($attemptobj->get_question($id),
+            $state, $action, $attemptobj->get_quiz(), $attempt)) {
+        save_question_session($attemptobj->get_question($id), $state);
+    } else {
+        $success = false;
+    }
+}
+
+if (!$success) {
+    print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page));
+}
+
+/// Log the end of this attempt.
+add_to_log($attemptobj->get_courseid(), 'quiz', 'close attempt',
+        'review.php?attempt=' . $attemptobj->get_attemptid(),
+        $attemptobj->get_quizid(), $attemptobj->get_cmid());
+
+/// Update the quiz attempt record.
+$attempt->timemodified = $timenow;
+$attempt->timefinish = $timenow;
+if (!$DB->update_record('quiz_attempts', $attempt)) {
+    quiz_error($quiz, 'saveattemptfailed');
+}
+
+if (!$attempt->preview) {
+/// Record this user's best grade (if this is not a preview).
+    quiz_save_best_grade($quiz);
+
+/// Send any notification emails (if this is not a preview).
+    $attemptobj->quiz_send_notification_emails();
+}
+
+/// Clear the password check flag in the session.
+$accessmanager = $attemptobj->get_access_manager($timenow);
+$accessmanager->clear_password_access();
+
+/// Send the user to the review page.
+redirect($attemptobj->review_url());
+?>
\ No newline at end of file
index a6fefe3b2fc7cbde746c7a48e49b5d684af2e4d0..0e375a124237a325b8153afd4fc9cee61bd72c56 100644 (file)
@@ -100,12 +100,13 @@ print_table($table);
 /// Finish attempt button.
 echo "<div class=\"submitbtns mdl-align\">\n";
 $options = array(
+    'attempt' => $attemptobj->get_attemptid(),
     'finishattempt' => 1,
     'timeup' => 0,
     'questionids' => '',
-    'sesskey' => sesskey()
+    'sesskey' => sesskey(),
 );
-print_single_button($attemptobj->attempt_url(), $options, get_string('finishattempt', 'quiz'),
+print_single_button($attemptobj->processattempt_url(), $options, get_string('finishattempt', 'quiz'),
         'post', '', false, '', false, get_string('confirmclose', 'quiz'));
 echo "</div>\n";
 
index 27c1b97c24a88df12e98ddc60240768323ec60fc..9efc545172e5df237cfcdb473f8700a908d3cfdb 100644 (file)
@@ -1087,14 +1087,11 @@ class default_questiontype {
     * @param object $options  An object describing the rendering options.
     */
     function print_question_submit_buttons(&$question, &$state, $cmoptions, $options) {
-        /* The default implementation should be suitable for most question
-        types. It prints a mark button in the case where individual marking is
-        allowed. */
-
+        // The default implementation should be suitable for most question types.
+        // It prints a mark button in the case where individual marking is allowed.
         if (($cmoptions->optionflags & QUESTION_ADAPTIVE) and !$options->readonly) {
             echo '<input type="submit" name="', $question->name_prefix, 'submit" value="',
-                    get_string('mark', 'quiz'), '" class="submit btn" onclick="',
-                    "form.action = form.action + '#q", $question->id, "'; return true;", '" />';
+                    get_string('mark', 'quiz'), '" class="submit btn" />';
         }
     }