From: tjhunt Date: Tue, 15 Jul 2008 16:46:24 +0000 (+0000) Subject: MDL-15542 - Refactor quiz response processing into a separate processresponses.php. X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=9f9eec1ef0051dd5f5addf3bec4b2b7898cd326f;p=moodle.git MDL-15542 - Refactor quiz response processing into a separate processresponses.php. --- diff --git a/lang/en_utf8/quiz.php b/lang/en_utf8/quiz.php index 84beb2b650..e0210c30ca 100644 --- a/lang/en_utf8/quiz.php +++ b/lang/en_utf8/quiz.php @@ -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'; diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php index c521d62a69..d0cb979d07 100644 --- a/mod/quiz/attempt.php +++ b/mod/quiz/attempt.php @@ -23,41 +23,12 @@ 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 . -/// Instead we have to do them as - -/// 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()); @@ -72,8 +43,22 @@ $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(), @@ -81,21 +66,7 @@ } /// 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)) { @@ -106,133 +77,6 @@ $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 '
', "\n"; if($attemptobj->get_quiz()->timelimit > 0) { @@ -312,16 +156,20 @@ if ($page == -1) { /// Print a link to the next page. echo '
'; if ($attemptobj->is_last_page($page)) { - $nextpage = 'gotosummary'; + $nextpage = -1; + $nextpageforie = 'gotosummary'; } else { - $nextpage = 'gotopage' . ($page + 1); + $nextpage = $page + 1; + $nextpageforie = 'gotopage' . $nextpage; } - echo ''; + echo ''; echo "
"; - // Finish the form - echo ''; + // Some hidden fields to trach what is going on. + echo ''; + echo ''; echo ''; + echo ''; // 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 '\n"; + // Finish the form + echo ''; echo "
\n"; // End middle column. diff --git a/mod/quiz/attemptlib.php b/mod/quiz/attemptlib.php index 31b03ff311..7d7166f6a8 100644 --- a/mod/quiz/attemptlib.php +++ b/mod/quiz/attemptlib.php @@ -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 index 0000000000..b63c011e09 --- /dev/null +++ b/mod/quiz/processattempt.php @@ -0,0 +1,214 @@ +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 . +/// Instead we have to do them as - +/// 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 diff --git a/mod/quiz/summary.php b/mod/quiz/summary.php index a6fefe3b2f..0e375a1242 100644 --- a/mod/quiz/summary.php +++ b/mod/quiz/summary.php @@ -100,12 +100,13 @@ print_table($table); /// Finish attempt button. echo "
\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 "
\n"; diff --git a/question/type/questiontype.php b/question/type/questiontype.php index 27c1b97c24..9efc545172 100644 --- a/question/type/questiontype.php +++ b/question/type/questiontype.php @@ -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 'id, "'; return true;", '" />'; + get_string('mark', 'quiz'), '" class="submit btn" />'; } }