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
This commit is contained in:
tjhunt
2008-07-08 16:33:47 +00:00
parent 1b8b535dda
commit 78e7a3dd5d
10 changed files with 626 additions and 401 deletions
+1
View File
@@ -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';
+46 -12
View File
@@ -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 = '<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;
}
/**
+42 -37
View File
@@ -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 "</div>\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 '<div class="finishreview">';
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 '<a href="' . $CFG->wwwroot . '/mod/quiz/review.php?q=' . $this->_quiz->id .
'&amp;attempt=' . $attempt->id . '">' . $linktext . '</a>';
return '<a href="' . $this->_quizobj->review_url($attempt->id) . '">' . $linktext . '</a>';
}
}
/**
@@ -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 .= '<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";
@@ -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 .
'&amp;attempt=' . $attemptid, 'quizpopup', $linktext, '', '', '', $this->windowoptions, true);
return link_to_popup_window($this->_quizobj->review_url($attemptid),
'quizpopup', $linktext, '', '', '', $this->windowoptions, true);
}
/**
+100 -234
View File
@@ -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));
/// 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());
}
/// 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;
}
/// 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 = '&amp;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 = '&amp;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 '<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) {
@@ -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 '<form id="responseform" method="post" action="attempt.php?q=', s($quiz->id), '&amp;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">
@@ -381,39 +261,24 @@
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" />';
@@ -421,15 +286,16 @@
// 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());
}
?>
+287 -98
View File
@@ -32,13 +32,14 @@ class quiz {
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 =========================================================================
/**
@@ -53,29 +54,41 @@ class quiz {
$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;
}
$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);
$questionstoprocess = array();
foreach ($questionids as $id) {
$questionstoprocess[$id] = $this->questions[$id];
}
if (!get_question_options($questionstoprocess)) {
throw new moodle_quiz_exception($this, 'loadingquestionsfailed', implode(', ', $questionids));
}
$this->questions = $this->questions + $newquestions;
$this->questionsnumbered = false;
}
// Simple getters ======================================================================
@@ -84,6 +97,11 @@ class quiz {
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;
@@ -120,6 +138,22 @@ class quiz {
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.
@@ -129,18 +163,68 @@ class quiz {
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.
@@ -150,6 +234,40 @@ class quiz {
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 . '&amp;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
@@ -175,11 +293,67 @@ class quiz {
// 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;
}
}
/**
@@ -215,59 +389,30 @@ class quiz_attempt extends quiz {
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() {
@@ -279,6 +424,11 @@ class quiz_attempt extends quiz {
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;
@@ -289,6 +439,11 @@ class quiz_attempt extends quiz {
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.
*
@@ -301,22 +456,6 @@ class quiz_attempt extends quiz {
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.
@@ -389,18 +528,14 @@ class quiz_attempt extends quiz {
/**
* @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);
}
/**
@@ -414,28 +549,43 @@ class quiz_attempt extends quiz {
/**
* @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.
@@ -444,6 +594,47 @@ class quiz_attempt extends quiz {
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;
}
}
/**
@@ -463,7 +654,6 @@ class quiz_attempt_question_iterator implements Iterator {
*/
public function __construct(quiz_attempt $attemptobj, $page = 'all') {
$this->attemptobj = $attemptobj;
$attemptobj->number_questions($page);
$this->questionids = $attemptobj->get_question_ids($page);
}
@@ -484,11 +674,10 @@ class quiz_attempt_question_iterator implements Iterator {
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() {
+1 -8
View File
@@ -22,6 +22,7 @@
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 ///////////////////////////////////////////////////////////////////
@@ -891,14 +892,6 @@ function quiz_get_combined_reviewoptions($quiz, $attempts, $context=null) {
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 ///////////////////////////////
/**
+3 -2
View File
@@ -38,7 +38,7 @@
/// 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);
@@ -119,7 +119,8 @@
/// 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);
+135
View File
@@ -0,0 +1,135 @@
<?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));
?>
+10 -9
View File
@@ -7,14 +7,13 @@
* @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.
@@ -22,7 +21,7 @@ if ($attemptobj->get_userid() != $USER->id) {
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());
}
@@ -48,7 +47,8 @@ $attemptobj->load_question_states();
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());
@@ -63,7 +63,7 @@ if ($attemptobj->is_preview_user()) {
/// 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);
@@ -83,7 +83,8 @@ $table->data = array();
/// 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);
}
@@ -110,7 +111,7 @@ $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());
}
?>
+1 -1
View File
@@ -44,7 +44,7 @@
/// 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