From 78e7a3dd5da5d9880c2e1591064eb6d01e2ab6f4 Mon Sep 17 00:00:00 2001 From: tjhunt Date: Tue, 8 Jul 2008 16:33:47 +0000 Subject: [PATCH] MDL-15452 - Put the OU quiz navigation improvements into the Moodle codebase - quite a lot of progress, sorry I am committing this as a big lump, but it took me a while to get the code back to a working state. MDL-15537 - create oo attemptlib.php to hold shared code between attempt, summary and review.php MDL-15541 - Refactor starting a new attempt into a new file startattempt.php MDL-15538 - Rework attempt.php to use attemptlib.php --- lang/en_utf8/quiz.php | 1 + lib/questionlib.php | 58 ++++-- mod/quiz/accessrules.php | 79 ++++---- mod/quiz/attempt.php | 334 ++++++++++----------------------- mod/quiz/attemptlib.php | 385 ++++++++++++++++++++++++++++---------- mod/quiz/locallib.php | 9 +- mod/quiz/review.php | 5 +- mod/quiz/startattempt.php | 135 +++++++++++++ mod/quiz/summary.php | 19 +- mod/quiz/view.php | 2 +- 10 files changed, 626 insertions(+), 401 deletions(-) create mode 100644 mod/quiz/startattempt.php diff --git a/lang/en_utf8/quiz.php b/lang/en_utf8/quiz.php index 2953dfc302..3d681b5cba 100644 --- a/lang/en_utf8/quiz.php +++ b/lang/en_utf8/quiz.php @@ -398,6 +398,7 @@ $string['numberabbr'] = '#'; $string['numerical'] = 'Numerical'; $string['onlyteachersexport'] = 'Only teachers can export questions'; $string['onlyteachersimport'] = 'Only teachers with editing rights can import questions'; +$string['open'] = 'Started'; $string['openclosedatesupdated'] = 'Quiz open and close dates updated'; $string['optional'] = 'optional'; $string['outof'] = '$a->grade out of a maximum of $a->maxgrade'; diff --git a/lib/questionlib.php b/lib/questionlib.php index d0c1028bce..ebdf50bab2 100644 --- a/lib/questionlib.php +++ b/lib/questionlib.php @@ -756,32 +756,61 @@ function questionbank_navigation_tabs(&$row, $contexts, $querystring) { } /** - * Load a set of questions, given a list of ids. The $join and $extrafields arguments can be used - * together to pull in extra data. See, for example, the usage in mod/quiz/attempt.php, and - * read the code below to see how the SQL is assembled. + * Given a list of ids, load the basic information about a set of questions from the questions table. + * The $join and $extrafields arguments can be used together to pull in extra data. + * See, for example, the usage in mod/quiz/attemptlib.php, and + * read the code below to see how the SQL is assembled. Throws exceptions on error. * - * @param string $questionlist list of comma-separated question ids. - * @param string $extrafields - * @param string $join + * @param array $questionids array of question ids. + * @param string $extrafields extra SQL code to be added to the query. + * @param string $join extra SQL code to be added to the query. + * @param array $extraparams values for any placeholders in $join. + * You are strongly recommended to use named placeholder. * - * @return mixed array of question objects on success, a string error message on failure. + * @return array partially complete question objects. You need to call get_question_options + * on them before they can be properly used. */ -function question_load_questions($questionlist, $extrafields = '', $join = '') { +function question_preload_questions($questionids, $extrafields = '', $join = '', $extraparams = array()) { global $CFG, $DB; if ($join) { - $join = ' JOIN '.$join.''; + $join = ' JOIN '.$join; } if ($extrafields) { $extrafields = ', ' . $extrafields; } + list($questionidcondition, $params) = $DB->get_in_or_equal( + $questionids, SQL_PARAMS_NAMED, 'qid0000'); $sql = 'SELECT q.*' . $extrafields . ' FROM {question} q' . $join . - ' WHERE q.id IN (' . $questionlist . ')'; + ' WHERE q.id ' . $questionidcondition; // Load the questions - if (!$questions = $DB->get_records_sql($sql)) { + if (!$questions = $DB->get_records_sql($sql, $extraparams + $params)) { return 'Could not load questions.'; } + foreach ($questions as $question) { + $question->_partiallyloaded = true; + } + + return $questions; +} + +/** + * Load a set of questions, given a list of ids. The $join and $extrafields arguments can be used + * together to pull in extra data. See, for example, the usage in mod/quiz/attempt.php, and + * read the code below to see how the SQL is assembled. Throws exceptions on error. + * + * @param array $questionids array of question ids. + * @param string $extrafields extra SQL code to be added to the query. + * @param string $join extra SQL code to be added to the query. + * @param array $extraparams values for any placeholders in $join. + * You are strongly recommended to use named placeholder. + * + * @return array question objects. + */ +function question_load_questions($questionids, $extrafields = '', $join = '') { + $questions = question_preload_questions($questionids, $extrafields, $join); + // Load the question type specific information if (!get_question_options($questions)) { return 'Could not load the question options'; @@ -803,7 +832,12 @@ function _tidy_question(&$question) { $question->questiontext = '

' . get_string('warningmissingtype', 'quiz') . '

' . $question->questiontext; } $question->name_prefix = question_make_name_prefix($question->id); - return $QTYPES[$question->qtype]->get_question_options($question); + if ($success = $QTYPES[$question->qtype]->get_question_options($question)) { + if (isset($question->_partiallyloaded)) { + unset($question->_partiallyloaded); + } + } + return $success; } /** diff --git a/mod/quiz/accessrules.php b/mod/quiz/accessrules.php index 91e0896aad..fc9dbac6c1 100644 --- a/mod/quiz/accessrules.php +++ b/mod/quiz/accessrules.php @@ -4,7 +4,7 @@ * quiz, with convinient methods for seeing whether access is allowed. */ class quiz_access_manager { - private $_quiz; + private $_quizobj; private $_timenow; private $_passwordrule = null; private $_securewindowrule = null; @@ -17,32 +17,33 @@ class quiz_access_manager { * @param boolean $canpreview whether the current user has the * @param boolean $ignoretimelimits */ - public function __construct($quiz, $timenow, $canignoretimelimits) { - $this->_quiz = $quiz; + public function __construct($quizobj, $timenow, $canignoretimelimits) { + $this->_quizobj = $quizobj; $this->_timenow = $timenow; $this->create_standard_rules($canignoretimelimits); } private function create_standard_rules($canignoretimelimits) { - if ($this->_quiz->attempts > 0) { - $this->_rules[] = new num_attempts_access_rule($this->_quiz, $this->_timenow); + $quiz = $this->_quizobj->get_quiz(); + if ($quiz->attempts > 0) { + $this->_rules[] = new num_attempts_access_rule($this->_quizobj, $this->_timenow); } - $this->_rules[] = new open_close_date_access_rule($this->_quiz, $this->_timenow); - if ($this->_quiz->timelimit && !$canignoretimelimits) { - $this->_rules[] = new time_limit_access_rule($this->_quiz, $this->_timenow); + $this->_rules[] = new open_close_date_access_rule($this->_quizobj, $this->_timenow); + if ($quiz->timelimit && !$canignoretimelimits) { + $this->_rules[] = new time_limit_access_rule($this->_quizobj, $this->_timenow); } - if ($this->_quiz->delay1 || $this->_quiz->delay2) { - $this->_rules[] = new inter_attempt_delay_access_rule($this->_quiz, $this->_timenow); + if ($quiz->delay1 || $quiz->delay2) { + $this->_rules[] = new inter_attempt_delay_access_rule($this->_quizobj, $this->_timenow); } - if ($this->_quiz->subnet) { - $this->_rules[] = new ipaddress_access_rule($this->_quiz, $this->_timenow); + if ($quiz->subnet) { + $this->_rules[] = new ipaddress_access_rule($this->_quizobj, $this->_timenow); } - if ($this->_quiz->password) { - $this->_passwordrule = new password_access_rule($this->_quiz, $this->_timenow); + if ($quiz->password) { + $this->_passwordrule = new password_access_rule($this->_quizobj, $this->_timenow); $this->_rules[] = $this->_passwordrule; } - if ($this->_quiz->popup) { - $this->_securewindowrule = new securewindow_access_rule($this->_quiz, $this->_timenow); + if ($quiz->popup) { + $this->_securewindowrule = new securewindow_access_rule($this->_quizobj, $this->_timenow); $this->_rules[] = $this->_securewindowrule; } } @@ -211,8 +212,9 @@ class quiz_access_manager { if ($this->securewindow_required($canpreview)) { $this->_securewindowrule->print_start_attempt_button($buttontext, $strconfirmstartattempt); } else { - print_single_button("attempt.php", array('q' => $this->_quiz->id), $buttontext, - 'get', '', false, '', false, $strconfirmstartattempt); + print_single_button($this->_quizobj->start_attempt_url(), + array('cmid' => $this->_quizobj->get_cmid(), 'sesskey' => sesskey()), + $buttontext, 'post', '', false, '', false, $strconfirmstartattempt); } echo "\n"; @@ -238,7 +240,7 @@ class quiz_access_manager { */ public function back_to_view_page($canpreview, $message = '') { global $CFG; - $url = $CFG->wwwroot . '/mod/quiz/view.php?q=' . $this->_quiz->id; + $url = $this->_quizobj->view_url(); if (securewindow_required($canpreview)) { print_header(); print_box_start(); @@ -268,7 +270,7 @@ class quiz_access_manager { */ public function print_finish_review_link($canpreview) { global $CFG; - $url = $CFG->wwwroot . '/mod/quiz/view.php?q=' . $this->_quiz->id; + $url = $this->_quizobj->view_url(); echo '
'; if ($this->securewindow_required($canpreview)) { $url = addslashes_js(htmlspecialchars($url)); @@ -313,12 +315,13 @@ class quiz_access_manager { * in a javascript alert on the start attempt button. */ public function confirm_start_attempt_message() { - if ($this->_quiz->timelimit && $this->_quiz->attempts) { - return get_string('confirmstartattempttimelimit','quiz', $this->_quiz->attempts); - } else if ($this->_quiz->timelimit) { + $quiz = $this->_quizobj->get_quiz(); + if ($quiz->timelimit && $quiz->attempts) { + return get_string('confirmstartattempttimelimit','quiz', $quiz->attempts); + } else if ($quiz->timelimit) { return get_string('confirmstarttimelimit','quiz'); - } else if ($this->_quiz->attempts) { - return get_string('confirmstartattemptlimit','quiz', $this->_quiz->attempts); + } else if ($quiz->attempts) { + return get_string('confirmstartattemptlimit','quiz', $quiz->attempts); } return ''; } @@ -342,8 +345,7 @@ class quiz_access_manager { if ($this->securewindow_required($canpreview)) { return $this->_securewindowrule->make_review_link($linktext, $attempt->id); } else { - return '' . $linktext . ''; + return '' . $linktext . ''; } } /** @@ -354,11 +356,12 @@ class quiz_access_manager { * @return string an appropraite message. */ public function cannot_review_message($reviewoptions) { + $quiz = $this->_quizobj->get_quiz(); if ($reviewoptions->quizstate == QUIZ_STATE_IMMEDIATELY) { return ''; - } else if ($reviewoptions->quizstate == QUIZ_STATE_OPEN && $this->_quiz->timeclose && - ($this->_quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_RESPONSES)) { - return get_string('noreviewuntil', 'quiz', userdate($this->_quiz->timeclose)); + } else if ($reviewoptions->quizstate == QUIZ_STATE_OPEN && $quiz->timeclose && + ($quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_RESPONSES)) { + return get_string('noreviewuntil', 'quiz', userdate($quiz->timeclose)); } else { return get_string('noreview', 'quiz'); } @@ -376,13 +379,15 @@ class quiz_access_manager { */ abstract class quiz_access_rule_base { protected $_quiz; + protected $_quizobj; protected $_timenow; /** * Create an instance of this rule for a particular quiz. * @param object $quiz the quiz we will be controlling access to. */ - public function __construct($quiz, $timenow) { - $this->_quiz = $quiz; + public function __construct($quizobj, $timenow) { + $this->_quizobj = $quizobj; + $this->_quiz = $quizobj->get_quiz(); $this->_timenow = $timenow; } /** @@ -620,7 +625,7 @@ class password_access_rule extends quiz_access_rule_base { /// Print the password entry form. $output .= '

' . get_string('requirepasswordmessage', 'quiz') . "

\n"; $output .= '
' . "\n"; $output .= "
\n"; $output .= '\n"; @@ -674,7 +679,8 @@ class securewindow_access_rule extends quiz_access_rule_base { public function print_start_attempt_button($buttontext, $strconfirmstartattempt) { global $CFG, $SESSION; - $attempturl = $CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $this->_quiz->id; + $attempturl = $this->_quizobj->start_attempt_url() . '?cmid=' . $this->_quizobj->get_cmid() . + '&sesskey=' . sesskey(); $window = 'quizpopup'; if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) { @@ -696,9 +702,8 @@ class securewindow_access_rule extends quiz_access_rule_base { * @return string HTML for the link. */ public function make_review_link($linktext, $attemptid) { - global $CFG; - return link_to_popup_window($CFG->wwwroot . '/mod/quiz/review.php?q=' . $this->_quiz->id . - '&attempt=' . $attemptid, 'quizpopup', $linktext, '', '', '', $this->windowoptions, true); + return link_to_popup_window($this->_quizobj->review_url($attemptid), + 'quizpopup', $linktext, '', '', '', $this->windowoptions, true); } /** diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php index b99f4c1897..75e00ce25f 100644 --- a/mod/quiz/attempt.php +++ b/mod/quiz/attempt.php @@ -10,7 +10,7 @@ * @package quiz */ - require_once('../../config.php'); + 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 @@ -18,35 +18,13 @@ $timenow = time(); /// Get submitted parameters. - $id = optional_param('id', 0, PARAM_INT); // Course Module ID - $q = optional_param('q', 0, PARAM_INT); // or quiz ID + $attemptid = required_param('attempt', PARAM_INT); $page = optional_param('page', 0, PARAM_INT); - $questionids = optional_param('questionids', ''); + $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. - $forcenew = optional_param('forcenew', false, PARAM_BOOL); // Teacher has requested new preview - if ($id) { - if (! $cm = get_coursemodule_from_id('quiz', $id)) { - print_error('invalidcoursemodule'); - } - if (! $course = $DB->get_record('course', array('id' => $cm->course))) { - print_error("coursemisconf"); - } - if (! $quiz = $DB->get_record('quiz', array('id' => $cm->instance))) { - print_error('invalidcoursemodule'); - } - } else { - if (! $quiz = $DB->get_record('quiz', array('id' => $q))) { - print_error('invalidcoursemodule'); - } - if (! $course = $DB->get_record('course', array('id' => $quiz->course))) { - print_error('invalidcourseid'); - } - if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $course->id)) { - print_error('invalidcoursemodule'); - } - } + $attemptobj = new quiz_attempt($attemptid); /// We treat automatically closed attempts just like normally closed attempts if ($timeup) { @@ -54,148 +32,50 @@ } /// Check login and get contexts. - require_login($course->id, false, $cm); - $coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course); - $context = get_context_instance(CONTEXT_MODULE, $cm->id); - $canpreview = has_capability('mod/quiz:preview', $context); - -/// Create an object to manage all the other (non-roles) access rules. - $accessmanager = new quiz_access_manager($quiz, $timenow, - has_capability('mod/quiz:ignoretimelimits', $context, NULL, false)); - if ($canpreview && $forcenew) { - $accessmanager->clear_password_access(); - } - -/// if no questions have been set up yet redirect to edit.php - if (!$quiz->questions && has_capability('mod/quiz:manage', $context)) { - redirect($CFG->wwwroot . '/mod/quiz/edit.php?cmid=' . $cm->id); - } + require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); /// Check capabilites. - if (!$canpreview) { + if (!$attemptobj->is_preview_user()) { require_capability('mod/quiz:attempt', $context); } -/// We intentionally do not check otehr access rules until after we have processed -/// any submitted responses (which would be sesskey protected). This is so that when -/// someone submits close to the exact moment when the quiz closes, there responses are not lost. - -/// Load attempt or create a new attempt if there is no unfinished one - -/// Check to see if a new preview was requested. - if ($canpreview && $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 - $DB->set_field('quiz_attempts', 'timefinish', $timenow, array('quiz' => $quiz->id, 'userid' => $USER->id)); - } - -/// Look for an existing attempt. - $newattempt = false; - $lastattempt = quiz_get_latest_attempt_by_user($quiz->id, $USER->id); - - if ($lastattempt && !$lastattempt->timefinish) { - /// Continuation of an attempt. - $attempt = $lastattempt; - $lastattemptid = false; - - /// Log it, but only if some time has elapsed. - if (($timenow - $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($course->id, 'quiz', 'continue attemp', "review.php?attempt=$attempt->id", - "$quiz->id", $cm->id); - } - - } else { - /// Start a new attempt. - $newattempt = true; - - /// Get number for the next or unfinished attempt - if ($lastattempt && !$lastattempt->preview && !$canpreview) { - $attemptnumber = $lastattempt->attempt + 1; - $lastattemptid = $lastattempt->id; - } else { - $lastattempt = false; - $lastattemptid = false; - $attemptnumber = 1; - } - - /// Check access. - $messages = $accessmanager->prevent_access() + - $accessmanager->prevent_new_attempt($attemptnumber - 1, $lastattempt); - if (!$canpreview && $messages) { - //TODO: need more detailed error info - print_error('attempterror', 'quiz', $CFG->wwwroot . '/mod/quiz/view.php?q=' . $quiz->id); - } - $accessmanager->do_password_check($canpreview); - - /// Delete any previous preview attempts belonging to this user. - if ($oldattempts = $DB->get_records_select('quiz_attempts', "quiz = ? - AND userid = ? AND preview = 1", array($quiz->id, $USER->id))) { - foreach ($oldattempts as $oldattempt) { - quiz_delete_attempt($oldattempt, $quiz); - } - } - - /// Create the new attempt and initialize the question sessions - $attempt = quiz_create_attempt($quiz, $attemptnumber, $lastattempt, $timenow, $canpreview); - - /// Save the attempt in the database. - if (!$attempt->id = $DB->insert_record('quiz_attempts', $attempt)) { - quiz_error($quiz, 'newattemptfail'); - } - - /// Log the new attempt. - if ($attempt->preview) { - add_to_log($course->id, 'quiz', 'preview', "attempt.php?id=$cm->id", - "$quiz->id", $cm->id); - } else { - add_to_log($course->id, 'quiz', 'attempt', "review.php?attempt=$attempt->id", - "$quiz->id", $cm->id); - } - } -/// This shouldn't really happen, just for robustness - if (!$attempt->timestart) { - debugging('timestart was not set for this attempt. That should be impossible.', DEBUG_DEVELOPER); - $attempt->timestart = $timenow - 1; +/// Log continuation of the attempt, but only if some time has passed. + if (($timenow - $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(), + $attemptobj->get_quizid(), $attemptobj->get_cmid()); } -/// Load all the questions and states needed by this script +/// Work out which questions we need. + $attemptobj->preload_questions(); /// Get the list of questions needed by this page. - $pagelist = quiz_questions_on_page($attempt->layout, $page); - - if ($newattempt || $finishattempt) { - $questionlist = quiz_questions_in_quiz($attempt->layout); + if ($finishattempt) { + $questionids = $attemptobj->get_question_ids(); + } else if ($page >= 0) { + $questionids = $attemptobj->get_question_ids($page); } else { - $questionlist = $pagelist; + $questionids = array(); } /// Add all questions that are on the submitted form - if ($questionids) { - $questionlist .= ','.$questionids; + if ($submittedquestionids) { + $submittedquestionids = explode(',', $submittedquestionids); + $questionids = $questionids + $submittedquestionids; + } else { + $submittedquestionids = array(); } - if (!$questionlist) { +/// Check. + if (empty($questionids)) { quiz_error($quiz, 'noquestionsfound'); } - $questions = question_load_questions($questionlist, 'qqi.grade AS maxgrade, qqi.id AS instance', - '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question'); - if (is_string($questions)) { - quiz_error($quiz, 'loadingquestionsfailed', $questions); - } +/// Load those questions and the associated states. + $attemptobj->load_questions($questionids); + $attemptobj->load_question_states($questionids); -/// Restore the question sessions to their most recent states creating new sessions where required. - if (!$states = get_question_states($questions, $quiz, $attempt, $lastattemptid)) { - print_error('cannotrestore', 'quiz'); - } - -/// If we are starting a new attempt, save all the newly created states. - if ($newattempt) { - foreach ($questions as $i => $question) { - save_question_session($questions[$i], $states[$i]); - } - } /// Process form data ///////////////////////////////////////////////// @@ -223,33 +103,31 @@ unset($responses->forcenewattempt); /// Extract the responses. $actions will be an array indexed by the questions ids. - $actions = question_extract_responses($questions, $responses, $event); + $actions = question_extract_responses($attemptobj->get_questions(), $responses, $event); /// Process each question in turn - $questionidarray = explode(',', $questionids); $success = true; - foreach($questionidarray as $i) { - if (!isset($actions[$i])) { - $actions[$i]->responses = array('' => ''); - $actions[$i]->event = QUESTION_EVENTOPEN; + foreach($submittedquestionids as $id) { + if (!isset($actions[$id])) { + $actions[$id]->responses = array('' => ''); + $actions[$id]->event = QUESTION_EVENTOPEN; } - $actions[$i]->timestamp = $timenow; - if (question_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt)) { - save_question_session($questions[$i], $states[$i]); + $actions[$id]->timestamp = $timenow; + if (question_process_responses($attemptobj->get_question($id), + $attemptobj->get_question_state($id), $actions[$id], + $attemptobj->get_quiz(), $attemptobj->get_attempt())) { + save_question_session($attemptobj->get_question($id), + $attemptobj->get_question_state($id)); } else { $success = false; } } if (!$success) { - $pagebit = ''; - if ($page) { - $pagebit = '&page=' . $page; - } - print_error('errorprocessingresponses', 'question', - $CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $quiz->id . $pagebit); + print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page)); } + $attempt = $attemptobj->get_attempt(); $attempt->timemodified = $timenow; if (!$DB->update_record('quiz_attempts', $attempt)) { quiz_error($quiz, 'saveattemptfailed'); @@ -259,36 +137,36 @@ /// Finish attempt if requested if ($finishattempt) { - /// Set the attempt to be finished - $attempt->timefinish = $timenow; - /// Move each question to the closed state. $success = true; - foreach ($questions as $key => $question) { + foreach ($attemptobj->get_questions() as $id => $question) { + $action = new stdClass; $action->event = QUESTION_EVENTCLOSE; - $action->responses = $states[$key]->responses; - $action->timestamp = $states[$key]->timestamp; - if (question_process_responses($question, $closestates[$key], $action, $quiz, $attempt)) { - save_question_session($question, $closestates[$key]); + $action->responses = $attemptobj->get_question_state($id)->responses; + $action->timestamp = $attemptobj->get_question_state($id)->timestamp; + if (question_process_responses($attemptobj->get_question($id), + $attemptobj->get_question_state($id), $action, + $attemptobj->get_quiz(), $attemptobj->get_attempt())) { + save_question_session($attemptobj->get_question($id), + $attemptobj->get_question_state($id)); } else { $success = false; } } if (!$success) { - $pagebit = ''; - if ($page) { - $pagebit = '&page=' . $page; - } - print_error('errorprocessingresponses', 'question', - $CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $quiz->id . $pagebit); + print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page)); } /// Log the end of this attempt. - add_to_log($course->id, 'quiz', 'close attempt', "review.php?attempt=$attempt->id", - "$quiz->id", $cm->id); + 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 = $attemptobj->get_attempt(); + $attempt->timemodified = $timenow; + $attempt->timefinish = $timenow; if (!$DB->update_record('quiz_attempts', $attempt)) { quiz_error($quiz, 'saveattemptfailed'); } @@ -298,52 +176,54 @@ quiz_save_best_grade($quiz); /// Send any notification emails (if this is not a preview). - quiz_send_notification_emails($course, $quiz, $attempt, $context, $cm); + $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($CFG->wwwroot . '/mod/quiz/review.php?attempt='.$attempt->id, 0); + redirect($attemptobj->review_url()); } -/// Now is the right time to check access (unless we are starting a new attempt, and did it above). - if (!$newattempt) { - $messages = $accessmanager->prevent_access(); - if (!$canpreview && $messages) { - //TODO: need more detailed error info - print_error('attempterror', 'quiz', $CFG->wwwroot . '/mod/quiz/view.php?q=' . $quiz->id); - } - $accessmanager->do_password_check($canpreview); +/// 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 require_js($CFG->wwwroot . '/mod/quiz/quiz.js'); - $pagequestions = explode(',', $pagelist); - $strattemptnum = get_string('attempt', 'quiz', $attempt->attempt); - $headtags = get_html_head_contributions($pagequestions, $questions, $states); - if ($accessmanager->securewindow_required($canpreview)) { - $accessmanager->setup_secure_page($course->shortname.': '.format_string($quiz->name), $headtags); + $title = get_string('attempt', 'quiz', $attemptobj->get_attempt_number()); + $headtags = $attemptobj->get_html_head_contributions($page); + if ($accessmanager->securewindow_required($attemptobj->is_preview_user())) { + $accessmanager->setup_secure_page($attemptobj->get_course()->shortname . ': ' . + format_string($attemptobj->get_quiz_name()), $headtags); } else { - $strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext) - ? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz')) - : ""; - $navigation = build_navigation($strattemptnum, $cm); - print_header_simple(format_string($quiz->name), "", $navigation, "", $headtags, true, $strupdatemodule); + print_header_simple(format_string($attemptobj->get_quiz_name()), '', $attemptobj->navigation($title), + '', $headtags, true, $attemptobj->update_module_button()); } echo ''; // for overlib - if ($canpreview) { + if ($attemptobj->is_preview_user()) { /// Show the tab bar. $currenttab = 'preview'; include('tabs.php'); /// Heading and tab bar. print_heading(get_string('previewquiz', 'quiz', format_string($quiz->name))); - print_restart_preview_button($quiz); + $attemptobj->print_restart_preview_button(); /// Inform teachers of any restrictions that would apply to students at this point. if ($messages) { @@ -355,17 +235,17 @@ } else { /// Just a heading. if ($quiz->attempts != 1) { - print_heading(format_string($quiz->name).' - '.$strattemptnum); + print_heading(format_string($quiz->name).' - '.$title); } else { print_heading(format_string($quiz->name)); } } // Start the form - echo '', "\n"; - if($quiz->timelimit > 0) { + if($attemptobj->get_quiz()->timelimit > 0) { // Make sure javascript is enabled for time limited quizzes ?>