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
$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';
}
/**
- * 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';
$question->questiontext = '<p>' . get_string('warningmissingtype', 'quiz') . '</p>' . $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;
}
/**
* 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;
* @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;
}
}
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 "</div>\n";
*/
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();
*/
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 '<div class="finishreview">';
if ($this->securewindow_required($canpreview)) {
$url = addslashes_js(htmlspecialchars($url));
* 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 '';
}
if ($this->securewindow_required($canpreview)) {
return $this->_securewindowrule->make_review_link($linktext, $attempt->id);
} else {
- return '<a href="' . $CFG->wwwroot . '/mod/quiz/review.php?q=' . $this->_quiz->id .
- '&attempt=' . $attempt->id . '">' . $linktext . '</a>';
+ return '<a href="' . $this->_quizobj->review_url($attempt->id) . '">' . $linktext . '</a>';
}
}
/**
* @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');
}
*/
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;
}
/**
/// Print the password entry form.
$output .= '<p>' . get_string('requirepasswordmessage', 'quiz') . "</p>\n";
$output .= '<form id="passwordform" method="post" action="' . $CFG->wwwroot .
- '/mod/quiz/attempt.php?q=' . $this->_quiz->id .
+ '/mod/quiz/startattempt.php?q=' . $this->_quiz->id .
'" onclick="this.autocomplete=\'off\'">' . "\n";
$output .= "<div>\n";
$output .= '<label for="quizpassword">' . get_string('password') . "</label>\n";
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()])) {
* @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);
}
/**
* @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
$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) {
}
/// 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 /////////////////////////////////////////////////
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');
/// 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');
}
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 '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // 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) {
} 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 '<form id="responseform" method="post" action="attempt.php?q=', s($quiz->id), '&page=', s($page),
+ echo '<form id="responseform" method="post" action="', $attemptobj->attempt_url(0, $page),
'" enctype="multipart/form-data"' .
' onclick="this.autocomplete=\'off\'" onkeypress="return check_enter(event);">', "\n";
- if($quiz->timelimit > 0) {
+ if($attemptobj->get_quiz()->timelimit > 0) {
// Make sure javascript is enabled for time limited quizzes
?>
<script type="text/javascript">
echo '<div>';
/// Print the navigation panel if required
- $numpages = quiz_number_of_pages($attempt->layout);
- if ($numpages > 1) {
- quiz_print_navigation_panel($page, $numpages);
- }
+ // TODO!!!
+ quiz_print_navigation_panel($page, $attemptobj->get_num_pages());
/// Print all the questions
- $number = quiz_first_questionnumber($attempt->layout, $pagelist);
- foreach ($pagequestions as $i) {
- $options = quiz_get_renderoptions($quiz->review, $states[$i]);
- // Print the question
- print_question($questions[$i], $states[$i], $number, $quiz, $options);
- save_question_session($questions[$i], $states[$i]);
- $number += $questions[$i]->length;
+ foreach ($attemptobj->get_question_ids($page) as $id) {
+ $attemptobj->print_question($id);
}
-/// Print the submit buttons
- $strconfirmattempt = get_string("confirmclose", "quiz");
- $onclick = "return confirm('$strconfirmattempt')";
+/// Print a link to the next page.
echo "<div class=\"submitbtns mdl-align\">\n";
-
- echo "<input type=\"submit\" name=\"saveattempt\" value=\"".get_string("savenosubmit", "quiz")."\" />\n";
- if ($quiz->optionflags & QUESTION_ADAPTIVE) {
- echo "<input type=\"submit\" name=\"markall\" value=\"".get_string("markall", "quiz")."\" />\n";
+ if ($attemptobj->is_last_page($page)) {
+ $nextpage = -1;
+ } else {
+ $nextpage = $page + 1;
}
- echo "<input type=\"submit\" name=\"finishattempt\" value=\"".get_string("finishattempt", "quiz")."\" onclick=\"$onclick\" />\n";
-
+ echo link_arrow_right(get_string('next'), 'javascript:navigate(' . $nextpage . ')');
echo "</div>";
- // Print the navigation panel if required
- if ($numpages > 1) {
- quiz_print_navigation_panel($page, $numpages);
- }
-
// Finish the form
echo '</div>';
echo '<input type="hidden" name="timeup" id="timeup" value="0" />';
// 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
// the student's answers.
- echo '<input type="hidden" name="questionids" value="'.$pagelist."\" />\n";
+ echo '<input type="hidden" name="questionids" value="' .
+ implode(',', $attemptobj->get_question_ids($page)) . "\" />\n";
echo "</form>\n";
// Finish the page
- $accessmanager->show_attempt_timer_if_needed($attempt, time());
- if ($accessmanager->securewindow_required($canpreview)) {
+ $accessmanager->show_attempt_timer_if_needed($attemptobj->get_attempt(), time());
+ if ($accessmanager->securewindow_required($attemptobj->is_preview_user())) {
print_footer('empty');
} else {
- print_footer($course);
+ print_footer($attemptobj->get_course());
}
?>
protected $cm;
protected $quiz;
protected $context;
+ protected $questionids; // All question ids in order that they appear in the quiz.
+ protected $pagequestionids; // array page no => array of questionids on the page in order.
// Fields set later if that data is needed.
+ protected $questions = null;
protected $accessmanager = null;
protected $reviewoptions = null;
protected $ispreviewuser = null;
- protected $questions = array();
- protected $questionsnumbered = false;
// Constructor =========================================================================
/**
$this->cm = $cm;
$this->course = $course;
$this->context = get_context_instance(CONTEXT_MODULE, $cm->id);
+ $this->determine_layout();
}
// Functions for loading more data =====================================================
public function load_questions_on_page($page) {
- $this->load_questions(quiz_questions_on_page($this->quiz->layout, $page));
+ $this->load_questions($this->pagequestionids[$page]);
+ }
+
+ public function preload_questions() {
+ if (empty($this->questionids)) {
+ throw new moodle_quiz_exception($this, 'noquestions', $this->edit_url());
+ }
+ $this->questions = question_preload_questions($this->questionids,
+ 'qqi.grade AS maxgrade, qqi.id AS instance',
+ '{quiz_question_instances} qqi ON qqi.quiz = :quizid AND q.id = qqi.question',
+ array('quizid' => $this->quiz->id));
+ $this->number_questions();
}
/**
* Load some or all of the queestions for this quiz.
*
- * @param string $questionlist comma-separate list of question ids. Blank for all.
+ * @param array $questionids question ids of the questions to load. null for all.
*/
- public function load_questions($questionlist = '') {
- if (!$questionlist) {
- $questionlist = quiz_questions_in_quiz($this->quiz->layout);
+ public function load_questions($questionids = null) {
+ if (is_null($questionids)) {
+ $questionids = $this->questionids;
+ }
+ $questionstoprocess = array();
+ foreach ($questionids as $id) {
+ $questionstoprocess[$id] = $this->questions[$id];
}
- $newquestions = question_load_questions($questionlist, 'qqi.grade AS maxgrade, qqi.id AS instance',
- '{quiz_question_instances} qqi ON qqi.quiz = ' . $this->quiz->id . ' AND q.id = qqi.question');
- if (is_string($newquestions)) {
- throw new moodle_quiz_exception($this, 'loadingquestionsfailed', $newquestions);
+ if (!get_question_options($questionstoprocess)) {
+ throw new moodle_quiz_exception($this, 'loadingquestionsfailed', implode(', ', $questionids));
}
- $this->questions = $this->questions + $newquestions;
- $this->questionsnumbered = false;
}
// Simple getters ======================================================================
return $this->course->id;
}
+ /** @return object the row of the course table. */
+ public function get_course() {
+ return $this->course;
+ }
+
/** @return integer the quiz id. */
public function get_quizid() {
return $this->quiz->id;
return $this->ispreviewuser;
}
+ /**
+ * @return integer number fo pages in this quiz.
+ */
+ public function get_num_pages() {
+ return count($this->pagequestionids);
+ }
+
+
+ /**
+ * @param int $page page number
+ * @return boolean true if this is the last page of the quiz.
+ */
+ public function is_last_page($page) {
+ return $page == count($this->pagequestionids) - 1;
+ }
+
/**
* @param integer $id the question id.
* @return object the question object with that id.
return $this->questions[$id];
}
+ /**
+ * @param array $questionids question ids of the questions to load. null for all.
+ */
+ public function get_questions($questionids = null) {
+ if (is_null($questionids)) {
+ $questionids = $this->questionids;
+ }
+ $questions = array();
+ foreach ($questionids as $id) {
+ $questions[$id] = $this->questions[$id];
+ $this->ensure_question_loaded($id);
+ }
+ return $questions;
+ }
+
+ /**
+ * Return the list of question ids for either a given page of the quiz, or for the
+ * whole quiz.
+ *
+ * @param mixed $page string 'all' or integer page number.
+ * @return array the reqested list of question ids.
+ */
+ public function get_question_ids($page = 'all') {
+ if ($page == 'all') {
+ $list = $this->questionids;
+ } else {
+ $list = $this->pagequestionids[$page];
+ }
+ // Clone the array, so our private arrays cannot be modified.
+ $result = array();
+ foreach ($list as $id) {
+ $result[] = $id;
+ }
+ return $result;
+ }
+
/**
* @param integer $timenow the current time as a unix timestamp.
* @return object and instance of the quiz_access_manager class for this quiz at this time.
*/
public function get_access_manager($timenow) {
if (is_null($this->accessmanager)) {
- $this->accessmanager = new quiz_access_manager($this->quiz, $timenow,
+ $this->accessmanager = new quiz_access_manager($this, $timenow,
has_capability('mod/quiz:ignoretimelimits', $this->context, NULL, false));
}
return $this->accessmanager;
}
+ /**
+ * Wrapper round the has_capability funciton that automatically passes in the quiz context.
+ */
+ public function has_capability($capability, $userid = NULL, $doanything = true) {
+ return has_capability($capability, $this->context, $userid, $doanything);
+ }
+
+ /**
+ * Wrapper round the require_capability funciton that automatically passes in the quiz context.
+ */
+ public function require_capability($capability, $userid = NULL, $doanything = true) {
+ return require_capability($capability, $this->context, $userid, $doanything);
+ }
+
// URLs related to this attempt ========================================================
/**
* @return string the URL of this quiz's view page.
return $CFG->wwwroot . '/mod/quiz/view.php?id=' . $this->cm->id;
}
+ /**
+ * @return string the URL of this quiz's edit page.
+ */
+ public function edit_url() {
+ global $CFG;
+ return $CFG->wwwroot . '/mod/quiz/edit.php?cmid=' . $this->cm->id;
+ }
+
+ /**
+ * @param integer $attemptid the id of an attempt.
+ * @return string the URL of that attempt.
+ */
+ public function attempt_url($attemptid) {
+ global $CFG;
+ return $CFG->wwwroot . '/mod/quiz/attempt.php?attempt=' . $attemptid . '&page=0';
+ }
+
+ /**
+ * @return string the URL of this quiz's edit page. Needs to be POSTed to with a cmid parameter.
+ */
+ public function start_attempt_url() {
+ global $CFG;
+ return $CFG->wwwroot . '/mod/quiz/startattempt.php';
+ }
+
+ /**
+ * @param integer $attemptid the id of an attempt.
+ * @return string the URL of the review of that attempt.
+ */
+ public function review_url($attemptid) {
+ global $CFG;
+ return $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attemptid;
+ }
+
// Bits of content =====================================================================
/**
* @return string the HTML snipped that needs to be supplied to print_header_simple
// Private methods =====================================================================
// Check that the definition of a particular question is loaded, and if not throw an exception.
- private function ensure_question_loaded($id) {
- if (!array_key_exists($id, $this->questions)) {
+ protected function ensure_question_loaded($id) {
+ if (isset($this->questions[$id]->_partiallyloaded)) {
throw new moodle_quiz_exception($this, 'questionnotloaded', $id);
}
}
+
+ private function determine_layout() {
+ $this->questionids = array();
+ $this->pagequestionids = array();
+
+ // Get the appropriate layout string (from quiz or attempt).
+ $layout = $this->get_layout_string();
+ if (empty($layout)) {
+ // Nothing to do.
+ return;
+ }
+
+ // Break up the layout string into pages.
+ $pagelayouts = explode(',0', $layout);
+
+ // Strip off any empty last page (normally there is one).
+ if (end($pagelayouts) == '') {
+ array_pop($pagelayouts);
+ }
+
+ // File the ids into the arrays.
+ $this->questionids = array();
+ $this->pagequestionids = array();
+ foreach ($pagelayouts as $page => $pagelayout) {
+ $pagelayout = trim($pagelayout, ',');
+ if ($pagelayout == '') continue;
+ $this->pagequestionids[$page] = explode(',', $pagelayout);
+ foreach ($this->pagequestionids[$page] as $id) {
+ $this->questionids[] = $id;
+ }
+ }
+ }
+
+ // Number the questions.
+ private function number_questions() {
+ $number = 1;
+ foreach ($this->pagequestionids as $page => $questionids) {
+ foreach ($questionids as $id) {
+ if ($this->questions[$id]->length > 0) {
+ $this->questions[$id]->_number = $number;
+ $number += $this->questions[$id]->length;
+ } else {
+ $this->questions[$id]->_number = get_string('infoshort', 'quiz');
+ }
+ $this->questions[$id]->_page = $page;
+ }
+ }
+ }
+
+ /**
+ * @return string the layout of this quiz. Used by number_questions to
+ * work out which questions are on which pages.
+ */
+ protected function get_layout_string() {
+ return $this->quiz->questions;
+ }
}
/**
throw new moodle_exception('invalidcoursemodule');
}
parent::__construct($quiz, $cm, $course);
+ $this->preload_questions();
}
// Functions for loading more data =====================================================
- public function load_questions_on_page($page) {
- $this->load_questions(quiz_questions_on_page($this->attempt->layout, $page));
- }
-
/**
- * Load some or all of the queestions for this quiz.
+ * Load the state of a number of questions that have already been loaded.
*
- * @param string $questionlist comma-separate list of question ids. Blank for all.
+ * @param array $questionids question ids to process. Blank = all.
*/
- public function load_questions($questionlist = '') {
- if (!$questionlist) {
- $questionlist = quiz_questions_in_quiz($this->attempt->layout);
+ public function load_question_states($questionids = null) {
+ if (is_null($questionids)) {
+ $questionids = $this->questionids;
}
- parent::load_questions($questionlist);
- }
-
- public function load_question_states() {
- $questionstodo = array_diff_key($this->questions, $this->states);
- if (!$newstates = get_question_states($questionstodo, $this->quiz, $this->attempt)) {
+ $questionstoprocess = array();
+ foreach ($questionids as $id) {
+ $this->ensure_question_loaded($id);
+ $questionstoprocess[$id] = $this->questions[$id];
+ }
+ if (!$newstates = get_question_states($questionstoprocess, $this->quiz, $this->attempt)) {
throw new moodle_quiz_exception($this, 'cannotrestore');
}
$this->states = $this->states + $newstates;
}
- /**
- * Number the loaded questions.
- *
- * At the moment, this assumes for simplicity that the loaded questions are contiguous.
- */
- public function number_questions($page = 'all') {
- if ($this->questionsnumbered) {
- return;
- }
- if ($page != 'all') {
- $pagelist = quiz_questions_in_page($this->attempt->layout, $page);
- $number = quiz_first_questionnumber($this->attempt->layout, $pagelist);
- } else {
- $number = 1;
- }
- $questionids = $this->get_question_ids($page);
- foreach ($questionids as $id) {
- if ($this->questions[$id]->length > 0) {
- $this->questions[$id]->number = $number;
- $number += $this->questions[$id]->length;
- } else {
- $this->questions[$id]->number = get_string('infoshort', 'quiz');
- }
- }
- }
-
// Simple getters ======================================================================
/** @return integer the attempt id. */
public function get_attemptid() {
return $this->attempt;
}
+ /** @return integer the number of this attemp (is it this user's first, second, ... attempt). */
+ public function get_attempt_number() {
+ return $this->attempt->attempt;
+ }
+
/** @return integer the id of the user this attempt belongs to. */
public function get_userid() {
return $this->attempt->userid;
return $this->attempt->timefinish != 0;
}
+ public function get_question_state($questionid) {
+ $this->ensure_state_loaded($questionid);
+ return $this->states[$questionid];
+ }
+
/**
* Wrapper that calls quiz_get_reviewoptions with the appropriate arguments.
*
return $this->reviewoptions;
}
- /**
- * Return the list of question ids for either a given page of the quiz, or for the
- * whole quiz.
- *
- * @param mixed $page string 'all' or integer page number.
- * @return array the reqested list of question ids.
- */
- public function get_question_ids($page = 'all') {
- if ($page == 'all') {
- $questionlist = quiz_questions_in_quiz($this->attempt->layout);
- } else {
- $questionlist = quiz_questions_in_page($this->attempt->layout, $page);
- }
- return explode(',', $questionlist);
- }
-
/**
* Get a quiz_attempt_question_iterator for either a page of the quiz, or a whole quiz.
* You must have called load_questions with an appropriate argument first.
/**
* @param integer $page if specified, the URL of this particular page of the attempt, otherwise
* the URL will go to the first page.
- * @param integer $question a question id. If set, will add a fragment to the URL
+ * @param integer $questionid a question id. If set, will add a fragment to the URL
* to jump to a particuar question on the page.
* @return string the URL to continue this attempt.
*/
- public function attempt_url($page = 0, $question = false) {
+ public function attempt_url($questionid = 0, $page = -1) {
global $CFG;
- $fragment = '';
- if ($question) {
- $fragment = '#q' . $question;
- }
- return $CFG->wwwroot . '/mod/quiz/attempt.php?id=' .
- $this->cm->id . '$amp;page=' . $page . $fragment;
+ return $CFG->wwwroot . '/mod/quiz/attempt.php?attempt=' . $this->attempt->id . '&' .
+ $this->page_and_question_fragment($questionid, $page);
}
/**
/**
* @param integer $page if specified, the URL of this particular page of the attempt, otherwise
* the URL will go to the first page.
- * @param integer $question a question id. If set, will add a fragment to the URL
+ * @param integer $questionid a question id. If set, will add a fragment to the URL
* to jump to a particuar question on the page.
* @param boolean $showall if true, the URL will be to review the entire attempt on one page,
* and $page will be ignored.
* @return string the URL to review this attempt.
*/
- public function review_url($page = 0, $question = false, $showall = false) {
+ public function review_url($questionid = 0, $page = -1, $showall = false) {
global $CFG;
- $fragment = '';
- if ($question) {
- $fragment = '#q' . $question;
- }
- $param = '';
- if ($showall) {
- $param = '$amp;showall=1';
- } else if ($page) {
- $param = '$amp;page=' . $page;
- }
- return $CFG->wwwroot . '/mod/quiz/review.php?attempt=' .
- $this->attempt->id . $param . $fragment;
+ return $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $this->attempt->id . '&' .
+ $this->page_and_question_fragment($questionid, $page, $showall);
+ }
+
+ // Bits of content =====================================================================
+ public function get_html_head_contributions($page = 'all') {
+ return get_html_head_contributions($this->get_question_ids($page),
+ $this->questions, $this->states);
+ }
+
+ public function print_restart_preview_button() {
+ global $CFG;
+ echo '<div class="controls">';
+ print_single_button($this->start_attempt_url(), array('cmid' => $this->cm->id,
+ 'forcenew' => true, 'sesskey' => sesskey()), get_string('startagain', 'quiz'), 'post');
+ echo '</div>';
}
+ public function print_question($id) {
+ $options = quiz_get_renderoptions($this->quiz->review, $this->states[$id]);
+ print_question($this->questions[$id], $this->states[$id], $this->questions[$id]->_number,
+ $this->quiz, $options);
+ }
+
+ public function quiz_send_notification_emails() {
+ quiz_send_notification_emails($this->course, $this->quiz, $this->attempt,
+ $this->context, $this->cm);
+ }
+
// Private methods =====================================================================
// Check that the state of a particular question is loaded, and if not throw an exception.
throw new moodle_quiz_exception($this, 'statenotloaded', $id);
}
}
+
+ /**
+ * @return string the layout of this quiz. Used by number_questions to
+ * work out which questions are on which pages.
+ */
+ protected function get_layout_string() {
+ return $this->attempt->layout;
+ }
+
+ /**
+ * Enter description here...
+ *
+ * @param unknown_type $questionid the id of a particular question on the page to jump to.
+ * @param integer $page -1 to look up the page number from the questionid, otherwise the page number to use.
+ * @param boolean $showall
+ * @return string bit to add to the end of a URL.
+ */
+ private function page_and_question_fragment($questionid, $page, $showall = false) {
+ if ($page = -1) {
+ if ($questionid) {
+ $page = $this->questions[$questionid]->_page;
+ } else {
+ $page = 0;
+ }
+ }
+ if ($showall) {
+ $page = 0;
+ }
+ $fragment = '';
+ if ($questionid && $questionid != reset($this->pagequestionids[$page])) {
+ $fragment = '#q' . $questionid;
+ }
+ $param = '';
+ if ($showall) {
+ $param = 'showall=1';
+ } else if (/*$page > 1*/ true) {
+ // TODO currently needed by the navigate JS, but clean this up later.
+ $param = 'page=' . $page;
+ }
+ return $param . $fragment;
+ }
}
/**
*/
public function __construct(quiz_attempt $attemptobj, $page = 'all') {
$this->attemptobj = $attemptobj;
- $attemptobj->number_questions($page);
$this->questionids = $attemptobj->get_question_ids($page);
}
public function key() {
$id = current($this->questionids);
if ($id) {
- return $this->attemptobj->get_question($id)->number;
+ return $this->attemptobj->get_question($id)->_number;
} else {
return false;
}
- return $this->attemptobj->get_question(current($this->questionids))->number;
}
public function next() {
require_once($CFG->dirroot . '/mod/quiz/lib.php');
require_once($CFG->dirroot . '/mod/quiz/accessrules.php');
require_once($CFG->dirroot . '/question/editlib.php');
+require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
/// Constants ///////////////////////////////////////////////////////////////////
return array($someoptions, $alloptions);
}
-function print_restart_preview_button($quiz) {
- global $CFG;
- echo '<div class="controls">';
- print_single_button($CFG->wwwroot . '/mod/quiz/attempt.php',
- array('q' => $quiz->id, 'forcenew' => true), get_string('startagain', 'quiz'));
- echo '</div>';
-}
-
/// FUNCTIONS FOR SENDING NOTIFICATION EMAILS ///////////////////////////////
/**
/// Create an object to manage all the other (non-roles) access rules.
$timenow = time();
- $accessmanager = new quiz_access_manager($quiz, $timenow,
+ $accessmanager = new quiz_access_manager(new quiz($quiz, $cm, $course), $timenow,
has_capability('mod/quiz:ignoretimelimits', $context, NULL, false));
$options = quiz_get_reviewoptions($quiz, $attempt, $context);
/// Print heading.
print_heading(format_string($quiz->name));
if ($canpreview && $reviewofownattempt) {
- print_restart_preview_button($quiz);
+ $attemptobj = new quiz_attempt($attemptid);
+ $attemptobj->print_restart_preview_button();
}
print_heading($strreviewtitle);
--- /dev/null
+<?php // $Id$
+/**
+ * This page deals with starting a new attempt at a quiz.
+ *
+ * Normally, it will end up redirecting to attempt.php - unless a password form is displayed.
+ *
+ * @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');
+
+/// Get submitted parameters.
+$id = required_param('cmid', PARAM_INT); // Course Module ID
+$forcenew = optional_param('forcenew', false, PARAM_BOOL); // Used to force a new preview
+
+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');
+}
+
+$quizobj = new quiz($quiz, $cm, $course);
+
+/// Check login and get contexts.
+require_login($quizobj->get_courseid(), false, $quizobj->get_cm());
+
+/// if no questions have been set up yet redirect to edit.php
+if (!$quizobj->get_question_ids() && $quizobj->has_capability('mod/quiz:manage')) {
+ redirect($quizobj->edit_url());
+}
+
+/// Create an object to manage all the other (non-roles) access rules.
+$accessmanager = $quizobj->get_access_manager(time());
+if ($quizobj->is_preview_user() && $forcenew) {
+ $accessmanager->clear_password_access();
+}
+
+// This page should only respond to post requests, if not, redirect to the view page.
+// However, becuase 'secure' mode opens in a new window, we cannot do enforce this rule for them.
+if (!data_submitted() && !$accessmanager->securewindow_required($quizobj->is_preview_user())) {
+ redirect($quizobj->view_url());
+}
+if (!confirm_sesskey()) {
+ throw new moodle_exception('confirmsesskeybad', 'error', $quizobj->view_url());
+}
+
+/// Check capabilites.
+if (!$quizobj->is_preview_user()) {
+ $quizobj->require_capability('mod/quiz:attempt');
+}
+
+/// Check to see if a new preview was requested.
+if ($quizobj->is_preview_user() && $forcenew) {
+/// To force the creation of a new preview, we set a finish time on the
+/// current attempt (if any). It will then automatically be deleted below
+ $DB->set_field('quiz_attempts', 'timefinish', time(), array('quiz' => $quiz->id, 'userid' => $USER->id));
+}
+
+/// Look for an existing attempt.
+$lastattempt = quiz_get_latest_attempt_by_user($quiz->id, $USER->id);
+
+if ($lastattempt && !$lastattempt->timefinish) {
+/// Continuation of an attempt - check password then redirect.
+ $accessmanager->do_password_check($quizobj->is_preview_user());
+ redirect($quizobj->attempt_url($lastattempt->id));
+}
+
+/// Get number for the next or unfinished attempt
+if ($lastattempt && !$lastattempt->preview && !$quizobj->is_preview_user()) {
+ $lastattemptid = $lastattempt->id;
+ $attemptnumber = $lastattempt->attempt + 1;
+} else {
+ $lastattempt = false;
+ $lastattemptid = false;
+ $attemptnumber = 1;
+}
+
+/// Check access.
+$messages = $accessmanager->prevent_access() +
+ $accessmanager->prevent_new_attempt($attemptnumber - 1, $lastattempt);
+if (!$quizobj->is_preview_user() && $messages) {
+ print_error('attempterror', 'quiz', $quizobj->view_url(),
+ $accessmanager->print_messages($messages, true));
+}
+$accessmanager->do_password_check($quizobj->is_preview_user());
+
+/// 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, time(), $quizobj->is_preview_user());
+
+/// 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);
+}
+
+/// Fully load all the questions in this quiz.
+$quizobj->preload_questions();
+$quizobj->load_questions();
+
+/// Create initial states for all questions in this quiz.
+if (!$states = get_question_states($quizobj->get_questions(), $quizobj->get_quiz(), $attempt, $lastattemptid)) {
+ print_error('cannotrestore', 'quiz');
+}
+
+/// Save all the newly created states.
+foreach ($quizobj->get_questions() as $i => $question) {
+ save_question_session($question, $states[$i]);
+}
+
+/// Redirect to the attempt page.
+redirect($quizobj->attempt_url($attempt->id));
+?>
\ No newline at end of file
* @package quiz
*/
-require_once("../../config.php");
-require_once("locallib.php");
-require_once("attemptlib.php");
+require_once(dirname(__FILE__) . '/../../config.php');
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$attemptid = required_param('attempt', PARAM_INT); // The attempt to summarise.
$attemptobj = new quiz_attempt($attemptid);
-/// Check login and get contexts.
+/// Check login.
require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());
/// If this is not our own attempt, display an error.
print_error('notyourattempt', 'quiz', $attemptobj->view_url());
}
-/// If the attempt is already closed, redirect them to the review page.
+/// If the attempt is alreadyuj closed, redirect them to the review page.
if ($attemptobj->is_finished()) {
redirect($attemptobj->review_url());
}
require_js($CFG->wwwroot . '/mod/quiz/quiz.js');
$title = get_string('summaryofattempt', 'quiz');
if ($accessmanager->securewindow_required($attemptobj->is_preview_user())) {
- $accessmanager->setup_secure_page($course->shortname.': '.format_string($quiz->name), $headtags);
+ $accessmanager->setup_secure_page($attemptobj->get_course()->shortname . ': ' .
+ format_string($attemptobj->get_quiz_name()), '');
} else {
print_header_simple(format_string($attemptobj->get_quiz_name()), '',
$attemptobj->navigation($title), '', '', true, $attemptobj->update_module_button());
/// Print heading.
print_heading(format_string($attemptobj->get_quiz_name()));
if ($attemptobj->is_preview_user()) {
- print_restart_preview_button($quiz);
+ $attemptobj->print_restart_preview_button();
}
print_heading($title);
/// Get the summary info for each question.
$questionids = $attemptobj->get_question_ids();
foreach ($attemptobj->get_question_iterator() as $number => $question) {
- $row = array($number, $attemptobj->get_question_status($question->id));
+ $row = array('<a href="' . $attemptobj->attempt_url($question->id) . '">' . $number . '</a>',
+ get_string($attemptobj->get_question_status($question->id), 'quiz'));
if ($scorescolumn) {
$row[] = $attemptobj->get_question_score($question->id);
}
if ($accessmanager->securewindow_required($attemptobj->is_preview_user())) {
print_footer('empty');
} else {
- print_footer($course);
+ print_footer($attemptobj->get_course());
}
?>
/// Create an object to manage all the other (non-roles) access rules.
$timenow = time();
- $accessmanager = new quiz_access_manager($quiz, $timenow,
+ $accessmanager = new quiz_access_manager(new quiz($quiz, $cm, $course), $timenow,
has_capability('mod/quiz:ignoretimelimits', $context, NULL, false));
/// If no questions have been set up yet redirect to edit.php