Resource page now done as well. That should be everything.
$string['30minutes'] = '30 Minutes';
$string['6hours'] = '6 Hours';
$string['acceptederror'] = 'Accepted error';
+$string['accessnoticesheader'] = 'You can preview this quiz, but if this were a real attempt, you would be blocked because:';
$string['action'] = 'Action';
$string['adaptive'] = 'Adaptive mode';
$string['addcategory'] = 'Add category';
$string['attemptquiznow'] = 'Attempt quiz now';
$string['attempts'] = 'Attempts';
$string['attemptsallowed'] = 'Attempts allowed';
+$string['attemptsallowedn'] = 'Attempts allowed: $a';
$string['attemptsdeleted'] = 'Quiz attempts deleted';
$string['attemptselection'] = 'Select which attempts to analyze per user:';
$string['attemptsexist'] = 'You can no longer add or remove questions.';
$string['calculatedquestion'] = 'Calculated Question not supported at line $a. The question will be ignored';
$string['cannotcreatepath'] = 'Path cannot be created ($a)';
$string['cannoteditafterattempts'] = 'You cannot add or remove questions because there are attempts.';
+$string['cannotfindprevattempt'] = 'Cannot find previous attempt to build on.';
$string['cannotinsert'] = 'Cannot insert question';
$string['cannotopen'] = 'Cannot open export file ($a)';
$string['cannotread'] = 'Cannot read import file (or file is empty)';
$string['gradeessays'] = 'Grade Essays';
$string['gradehighest'] = 'Highest grade';
$string['grademethod'] = 'Grading method';
+$string['gradingmethod'] = 'Grading method: $a';
$string['gradesdeleted'] = 'Quiz grades deleted';
$string['gradesofar'] = '$a->method: $a->mygrade / $a->quizgrade.';
$string['gradingdetails'] = 'Marks for this submission: $a->raw/$a->max.';
$string['link'] = 'Link';
$string['listitems'] = 'Listing of Items in Quiz';
$string['literal'] = 'Literal';
+$string['loadingquestionsfailed'] = 'Loading questions failed: $a';
$string['loguniform'] = 'digits, from a loguniform distribution';
$string['makecopy'] = 'Save as new question';
$string['managetypes'] = 'Manage question types and servers';
$string['noreview'] = 'You are not allowed to review this quiz';
$string['noreviewuntil'] = 'You are not allowed to review this quiz until $a';
$string['noscript'] = 'JavaScript must be enabled to continue!';
-$string['notavailable'] = 'Sorry, this quiz is not available';
+$string['notavailable'] = 'This quiz is not currently available';
$string['notavailabletostudents'] = 'Note: This quiz is not currently available to your students';
$string['notenoughanswers'] = 'This type of question requires at least $a answers';
$string['notenoughsubquestions'] = 'Not enough sub-questions have been defined!<br />Do you want to go back and fix this question?';
$string['quizclosed'] = 'This quiz closed on $a';
$string['quizcloses'] = 'Quiz closes';
$string['quizcloseson'] = 'This quiz will close at $a';
-$string['quiznotavailable'] = 'The quiz will not be available until: $a';
+$string['quiznotavailable'] = 'The quiz will not be available until $a';
$string['quizopen'] = 'Open the quiz';
$string['quizopens'] = 'Quiz opens';
+$string['quizopenedon'] = 'This quiz opened at $a';
$string['quizsettings'] = 'Quiz settings';
$string['quiztimelimit'] = 'Time limit: $a';
$string['quiztimer'] = 'Quiz Timer';
$string['reviewclosed'] = 'After the quiz is closed';
$string['reviewimmediately'] = 'Immediately after the attempt';
$string['reviewnever'] = 'Never allow review';
+$string['reviewnotallowed'] = 'You are not allowed to review other users\' attempts at this quiz.';
$string['reviewofattempt'] = 'Review of attempt $a';
$string['reviewofpreview'] = 'Review of preview';
$string['reviewopen'] = 'Later, while the quiz is still open';
$string['rqps'] = 'Remote Questions';
$string['save'] = 'Save';
$string['saveandedit'] = 'Save changes and edit questions';
+$string['saveattemptfailed'] = 'Failed to save the current quiz attempt.';
$string['savedfromdeletedcourse'] = 'Saved from deleted course \"$a\"';
$string['savegrades'] = 'Save grades';
$string['savemyanswers'] = 'Save my answers';
$string['stoponerror'] = 'Stop on error';
$string['subneterror'] = 'Sorry, this quiz has been locked so that it is only accessible from certain locations. Currently your computer is not one of those allowed to use this quiz.';
$string['subnetnotice'] = 'This quiz has been locked so that it is only accessible from certain locations. Your computer is not on an allowed subnet. As teacher you are allowed to preview anyway.';
+$string['subnetwrong'] = 'This quiz is only accessible from certain locations, and this computer is not on the allowed list.';
$string['substitutedby'] = 'will be substituted by';
$string['summaryofattempts'] = 'Summary of your previous attempts';
$string['temporaryblocked'] = 'You are temporarily not allowed to re-attempt the quiz.<br /> You will be able to take another attempt on:';
$string['warningsdetected'] = '$a warning(s) detected';
$string['webct'] = 'WebCT format';
$string['wheregrade'] = 'Where\'s my grade?';
+$string['windowclosing'] = 'This window will close shortly.';
$string['wildcard'] = 'Wild card';
$string['withselected'] = 'With selected';
$string['withsummary'] = 'with Summary Statistics';
$string['xmlimportnoname'] = 'Missing question name in xml file';
$string['xmlimportnoquestion'] = 'Missing question text in xml file';
$string['xmltypeunsupported'] = 'Question type $a is not supported by xml import';
+$string['youmustwait'] = 'You must wait before you may re-attempt this quiz. You will be allowed to start another attempt after $a.';
+$string['youcannotwait'] = 'This quiz closes before you will be allowed to start another attempt.';
$string['youneedtoenrol'] = 'You need to enrol in this course before you can attempt this quiz';
$string['yourfinalgradeis'] = 'Your final grade for this quiz is $a.';
$string['zerosignificantfiguresnotallowed'] = 'The correct answer cannot have zero significant figures!';
* Create an instance for a particular quiz.
* @param object $quiz the quiz we will be controlling access to.
* @param integer $timenow the
- * @param boolean $canpreview whether the current user has the
+ * @param boolean $canpreview whether the current user has the
* @param boolean $ignoretimelimits
*/
public function __construct($quiz, $timenow, $canignoretimelimits) {
*
* @param array $messages the array of message strings.
* @param boolean $return if true, return a string, instead of outputting.
- *
+ *
* @return mixed, if $return is true, return the string that would have been output, otherwise
* return null.
*/
/**
* Provide a description of the rules that apply to this quiz, such
* as is shown at the top of the quiz view page. Note that not all
- * rules consider themselves important enough to output a description.
+ * rules consider themselves important enough to output a description.
*
* @return array an array of description messages which may be empty. It
* would be sensible to output each one surrounded by <p> tags.
return false;
}
- public function setup_secure_page() {
- /// This prevents the message window coming up.
- define('MESSAGE_WINDOW', true);
- echo "\n\n", '<script type="text/javascript">';
- /// This used to be in protect_js.php. I really don't understand this bit.
- /// I have just moved it here for cleanliness reasons.
- echo "quiz_init_securewindow_protection('", get_string('functiondisabled','quiz'), "');\n";
- echo 'document.write(unescape("%3C%53%43%52%49%50%54%20%4C%41%4E%47%55%41%47%45%3D%22%4A%61%76%61%53%63%72%69%70%74%22%3E%3C%21%2D%2D%0D%0A%68%70%5F%6F%6B%3D%74%72%75%65%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%30%30%28%73%29%7B%69%66%28%21%68%70%5F%6F%6B%29%72%65%74%75%72%6E%3B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%73%29%7D%2F%2F%2D%2D%3E%3C%2F%53%43%52%49%50%54%3E"));';
- echo 'hp_d00(unescape("%3C%53%43%52%49%50%54%20%4C%41%4E%47%55%41%47%45%3D%22%4A%61%76%61%53%63%72%69%70%74%22%3E%3C%21%2D%2D%0D%0A%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6E%65%28%29%7B%72%65%74%75%72%6E%20%74%72%75%65%7D%6F%6E%65%72%72%6F%72%3D%68%70%5F%6E%65%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%6E%28%61%29%7B%72%65%74%75%72%6E%20%66%61%6C%73%65%7D%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%65%28%65%29%7B%72%65%74%75%72%6E%28%65%2E%74%61%72%67%65%74%2E%74%61%67%4E%61%6D%65%21%3D%6E%75%6C%6C%26%26%65%2E%74%61%72%67%65%74%2E%74%61%67%4E%61%6D%65%2E%73%65%61%72%63%68%28%27%5E%28%49%4E%50%55%54%7C%54%45%58%54%41%52%45%41%7C%42%55%54%54%4F%4E%7C%53%45%4C%45%43%54%29%24%27%29%21%3D%2D%31%29%7D%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6D%64%28%65%29%7B%69%66%28%65%2E%77%68%69%63%68%3D%3D%31%29%7B%77%69%6E%64%6F%77%2E%63%61%70%74%75%72%65%45%76%65%6E%74%73%28%45%76%65%6E%74%2E%4D%4F%55%53%45%4D%4F%56%45%29%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%6D%6F%76%65%3D%68%70%5F%64%6E%7D%7D%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6D%75%28%65%29%7B%69%66%28%65%2E%77%68%69%63%68%3D%3D%31%29%7B%77%69%6E%64%6F%77%2E%72%65%6C%65%61%73%65%45%76%65%6E%74%73%28%45%76%65%6E%74%2E%4D%4F%55%53%45%4D%4F%56%45%29%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%6D%6F%76%65%3D%6E%75%6C%6C%7D%7D%69%66%28%6E%61%76%69%67%61%74%6F%72%2E%61%70%70%4E%61%6D%65%2E%69%6E%64%65%78%4F%66%28%27%49%6E%74%65%72%6E%65%74%20%45%78%70%6C%6F%72%65%72%27%29%3D%3D%2D%31%7C%7C%28%6E%61%76%69%67%61%74%6F%72%2E%75%73%65%72%41%67%65%6E%74%2E%69%6E%64%65%78%4F%66%28%27%4D%53%49%45%27%29%21%3D%2D%31%26%26%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%21%3D%30%29%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%29%7B%64%6F%63%75%6D%65%6E%74%2E%6F%6E%73%65%6C%65%63%74%73%74%61%72%74%3D%68%70%5F%64%6E%7D%65%6C%73%65%20%69%66%28%64%6F%63%75%6D%65%6E%74%2E%6C%61%79%65%72%73%29%7B%77%69%6E%64%6F%77%2E%63%61%70%74%75%72%65%45%76%65%6E%74%73%28%45%76%65%6E%74%2E%4D%4F%55%53%45%55%50%7C%45%76%65%6E%74%2E%4D%4F%55%53%45%44%4F%57%4E%29%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%64%6F%77%6E%3D%68%70%5F%6D%64%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%75%70%3D%68%70%5F%6D%75%7D%65%6C%73%65%20%69%66%28%64%6F%63%75%6D%65%6E%74%2E%67%65%74%45%6C%65%6D%65%6E%74%42%79%49%64%26%26%21%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%29%7B%64%6F%63%75%6D%65%6E%74%2E%6F%6E%6D%6F%75%73%65%64%6F%77%6E%3D%68%70%5F%64%65%7D%7D%69%66%28%77%69%6E%64%6F%77%2E%6C%6F%63%61%74%69%6F%6E%2E%68%72%65%66%2E%73%75%62%73%74%72%69%6E%67%28%30%2C%34%29%3D%3D%22%66%69%6C%65%22%29%77%69%6E%64%6F%77%2E%6C%6F%63%61%74%69%6F%6E%3D%22%61%62%6F%75%74%3A%62%6C%61%6E%6B%22%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6E%6C%73%28%29%7B%77%69%6E%64%6F%77%2E%73%74%61%74%75%73%3D%22%22%3B%73%65%74%54%69%6D%65%6F%75%74%28%22%68%70%5F%6E%6C%73%28%29%22%2C%31%30%29%7D%68%70%5F%6E%6C%73%28%29%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%70%31%28%29%7B%66%6F%72%28%69%3D%30%3B%69%3C%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%3B%69%2B%2B%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%73%74%79%6C%65%2E%76%69%73%69%62%69%6C%69%74%79%21%3D%22%68%69%64%64%65%6E%22%29%7B%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%73%74%79%6C%65%2E%76%69%73%69%62%69%6C%69%74%79%3D%22%68%69%64%64%65%6E%22%3B%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%69%64%3D%22%68%70%5F%69%64%22%7D%7D%7D%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%70%32%28%29%7B%66%6F%72%28%69%3D%30%3B%69%3C%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%3B%69%2B%2B%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%69%64%3D%3D%22%68%70%5F%69%64%22%29%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%73%74%79%6C%65%2E%76%69%73%69%62%69%6C%69%74%79%3D%22%22%7D%7D%3B%77%69%6E%64%6F%77%2E%6F%6E%62%65%66%6F%72%65%70%72%69%6E%74%3D%68%70%5F%64%70%31%3B%77%69%6E%64%6F%77%2E%6F%6E%61%66%74%65%72%70%72%69%6E%74%3D%68%70%5F%64%70%32%3B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%27%3C%73%74%79%6C%65%20%74%79%70%65%3D%22%74%65%78%74%2F%63%73%73%22%20%6D%65%64%69%61%3D%22%70%72%69%6E%74%22%3E%3C%21%2D%2D%62%6F%64%79%7B%64%69%73%70%6C%61%79%3A%6E%6F%6E%65%7D%2D%2D%3E%3C%2F%73%74%79%6C%65%3E%27%29%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%63%28%29%7B%68%70%5F%74%61%2E%63%72%65%61%74%65%54%65%78%74%52%61%6E%67%65%28%29%2E%65%78%65%63%43%6F%6D%6D%61%6E%64%28%22%43%6F%70%79%22%29%3B%73%65%74%54%69%6D%65%6F%75%74%28%22%68%70%5F%64%63%28%29%22%2C%33%30%30%29%7D%69%66%28%6E%61%76%69%67%61%74%6F%72%2E%61%70%70%4E%61%6D%65%2E%69%6E%64%65%78%4F%66%28%27%49%6E%74%65%72%6E%65%74%20%45%78%70%6C%6F%72%65%72%27%29%3D%3D%2D%31%7C%7C%28%6E%61%76%69%67%61%74%6F%72%2E%75%73%65%72%41%67%65%6E%74%2E%69%6E%64%65%78%4F%66%28%27%4D%53%49%45%27%29%21%3D%2D%31%26%26%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%21%3D%30%29%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%26%26%6E%61%76%69%67%61%74%6F%72%2E%75%73%65%72%41%67%65%6E%74%2E%69%6E%64%65%78%4F%66%28%27%4F%70%65%72%61%27%29%3D%3D%2D%31%29%7B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%27%3C%64%69%76%20%73%74%79%6C%65%3D%22%70%6F%73%69%74%69%6F%6E%3A%61%62%73%6F%6C%75%74%65%3B%6C%65%66%74%3A%2D%31%30%30%30%70%78%3B%74%6F%70%3A%2D%31%30%30%30%70%78%22%3E%3C%69%6E%70%75%74%20%74%79%70%65%3D%22%74%65%78%74%61%72%65%61%22%20%6E%61%6D%65%3D%22%68%70%5F%74%61%22%20%76%61%6C%75%65%3D%22%20%22%20%73%74%79%6C%65%3D%22%76%69%73%69%62%69%6C%69%74%79%3A%68%69%64%64%65%6E%22%3E%3C%2F%64%69%76%3E%27%29%3B%68%70%5F%64%63%28%29%7D%7D%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6E%64%64%28%29%7B%72%65%74%75%72%6E%20%66%61%6C%73%65%7D%64%6F%63%75%6D%65%6E%74%2E%6F%6E%64%72%61%67%73%74%61%72%74%3D%68%70%5F%6E%64%64%3B%2F%2F%2D%2D%3E%3C%2F%53%43%52%49%50%54%3E"));';
- echo "</script>\n";
+ /**
+ * Do the printheader call, etc. required for a secure page, including the necessary JS.
+ *
+ * @param string $title HTML title tag content, passed to printheader.
+ * @param string $headtags extra stuff to go in the HTML head tag, passed to printheader.
+ */
+ public function setup_secure_page($title, $headtags) {
+ $this->_securewindowrule->setup_secure_page($title, $headtags);
}
public function show_attempt_timer_if_needed($attempt, $timenow) {
}
}
if ($timeleft !== false) {
- /// Make sure the timer starts just above zero. If $timeleft was <= 0, then
+ /// Make sure the timer starts just above zero. If $timeleft was <= 0, then
/// this will just have the effect of causing the quiz to be submitted immediately.
$timerstartvalue = max($timeleft, 1);
print_box_start('', 'quiz-timer-outer');
}
/**
- * @return object the securewindow_access_rule instance for this quiz,
- * or null if securewindow_required returns false.
+ * Print a button to start a quiz attempt, with an appropriate javascript warning,
+ * depending on the access restrictions. The link will pop up a 'secure' window, if
+ * necessary. The button will initially be hidden, with JavaScript to reveal it, and
+ * a noscript tag saying that the quiz requires JavaScript.
+ *
+ * @param boolean $canpreview whether this user can preview. This affects whether they must
+ * use a secure window.
+ * @param string $buttontext the label to put on the button.
+ * @param boolean $unfinished whether the button is to continue an existing attempt,
+ * or start a new one. This affects whether a javascript alert is shown.
*/
- public function get_securewindow_object() {
- return $this->_securewindowrule;
+ public function print_start_attempt_button($canpreview, $buttontext, $unfinished) {
+ /// Do we need a confirm javascript alert?
+ if ($unfinished) {
+ $strconfirmstartattempt = '';
+ } else {
+ $strconfirmstartattempt = $this->confirm_start_attempt_message();
+ }
+
+ /// Show the start button, in a div that is initially hidden.
+ require_js('yui_yahoo');
+ require_js('yui_event');
+ echo '<div id="quizstartbuttondiv" style="display: none;">';
+ 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);
+ }
+ echo "</div>\n";
+
+ /// JavaScript to reveal the button.
+ echo '<script type="text/javascript">' . "\n";
+ echo "YAHOO.util.Event.onContentReady(\n";
+ echo "'quizstartbuttondiv', function() {\n";
+ echo "document.getElementById('quizstartbuttondiv').style.cssText = '';\n";
+ echo "});\n";
+ echo "</script>\n";
+
+ /// A noscript tag to explains that the quiz only works with JavaScript enabled.
+ echo '<noscript>';
+ print_heading(get_string('noscript', 'quiz'));
+ echo "</noscript>\n";
+ }
+
+ /**
+ * Send the user back to the quiz view page. Normally this is just a redirect, but
+ * If we were in a secure window, we close this window, and reload the view window we came from.
+ *
+ * @param boolean $canpreview This affects whether we have to worry about secure window stuff.
+ */
+ public function back_to_view_page($canpreview, $message = '') {
+ global $CFG;
+ $url = $CFG->wwwroot . '/mod/quiz/view.php?q=' . $this->_quiz->id;
+ if (securewindow_required($canpreview)) {
+ print_header();
+ print_box_start();
+ if ($message) {
+ echo '<p>' . $message . '</p><p>' . get_string('windowclosing', 'quiz') . '</p>';
+ $delay = 5;
+ } else {
+ echo '<p>' . get_string('pleaseclose', 'quiz') . '</p>';
+ $delay = 0;
+ }
+ print_box_end();
+ echo '<script type="text/javascript">';
+ echo 'quiz_secure_window.close(', addslashes_js(htmlspecialchars($url)), ', ', $delay, ')';
+ echo '</script>';
+ print_footer('empty');
+ die();
+ } else {
+ redirect($url, $message);
+ }
+ }
+
+ /**
+ * Print a control to finish the review. Normally this is just a link, but if we are
+ * in a secure window, it needs to be a button that does quiz_secure_window.close.
+ *
+ * @param boolean $canpreview This affects whether we have to worry about secure window stuff.
+ */
+ public function print_finish_review_link($canpreview) {
+ global $CFG;
+ $url = $CFG->wwwroot . '/mod/quiz/view.php?q=' . $this->_quiz->id;
+ echo '<div class="finishreview">';
+ if ($this->securewindow_required($canpreview)) {
+ $url = addslashes_js(htmlspecialchars($url));
+ echo '<input type="button" value="' . get_string('finishreview', 'quiz') . '" ' .
+ "onclick=\"quiz_secure_window.close('$url', 0)\" />\n";
+ } else {
+ echo '<a href="' . $url . '">' . get_string('finishreview', 'quiz') . "</a>\n";
+ }
+ echo "</div>\n";
}
/**
/**
* Actually ask the user for the password, if they have not already given it this session.
* This function only returns is access is OK.
+ *
+ * @param boolean $canpreview used to enfore securewindow stuff.
*/
- public function do_password_check() {
+ public function do_password_check($canpreview) {
if (!is_null($this->_passwordrule)) {
- $this->_passwordrule->do_password_check();
+ $this->_passwordrule->do_password_check($canpreview, $this);
}
}
*
* @param string $linktext some text.
* @param object $attempt the attempt object
- * @return string some HTML, the $linktext either unmodified or wrapped in
+ * @return string some HTML, the $linktext either unmodified or wrapped in a link to the review page.
*/
- public function make_review_link($linktext, $attempt) {
+ public function make_review_link($linktext, $attempt, $canpreview, $reviewoptions) {
global $CFG;
- /// If not even responses are to be shown in review then we don't allow any review
- if (!($this->_quiz->review & QUIZ_REVIEW_RESPONSES)) {
- return $linktext;
- }
-
- /// If the quiz is still open, are reviews allowed?
- if ((!$this->_quiz->timeclose || time() < $this->_quiz->timeclose) &&
- !($this->_quiz->review & QUIZ_REVIEW_OPEN)) {
- /// If not, don't link.
+ /// If review of responses is not allowed, or the attempt is still open, don't link.
+ if (!$reviewoptions->responses || !$attempt->timefinish) {
return $linktext;
}
- /// If the quiz is closed, are reviews allowed?
- if (($this->_quiz->timeclose && time() > $this->_quiz->timeclose) &&
- !($this->_quiz->review & QUIZ_REVIEW_CLOSED)) {
- /// If not, don't link.
- return $linktext;
- }
-
- /// If the attempt is still open, don't link.
- if (!$attempt->timefinish) {
- return $linktext;
- }
-
- /// It is OK to link.
- // TODO replace this with logic that matches review.php.
- if ($this->securewindow_required(false)) {
- return $this->get_securewindow_object()->make_review_link($linktext, $attempt->id);
+ /// It is OK to link, does it need to be in a secure window?
+ 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>';
}
}
+ /**
+ * If $reviewoptions->responses is false, meaning that students can't review this
+ * attempt at the moment, return an appropriate string explaining why.
+ *
+ * @param object $reviewoptions as obtained from quiz_get_reviewoptions.
+ * @return string an appropraite message.
+ */
+ public function cannot_review_message($reviewoptions) {
+ 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 {
+ return get_string('noreview', 'quiz');
+ }
+ }
}
/**
}
/**
* If this rule can determine that this user will never be allowed another attempt at
- * this quiz, then return true. This is used so we can know whether to display a
+ * this quiz, then return true. This is used so we can know whether to display a
* final score on the view page. This will only be called if there is not a currently
* active attempt for this user.
* @param integer $numattempts the number of previous attempts this user has made.
* Actually ask the user for the password, if they have not already given it this session.
* This function only returns is access is OK.
*
- * @param $return if true, return the HTML for the form (if required), instead of outputting
- * it at stopping
+ * @param boolean $canpreview used to enfore securewindow stuff.
+ * @param object $accessmanager the accessmanager calling us.
+ * @param boolean $return if true, return the HTML for the form (if required), instead of
+ * outputting it at stopping
* @return mixed return null, unless $return is true, and a form needs to be displayed.
*/
- public function do_password_check($return = false) {
+ public function do_password_check($canpreview, $accessmanager, $return = false) {
global $CFG, $SESSION;
- /// We have already checked the password for this quiz this session, so don't ask again.
+ /// We have already checked the password for this quiz this session, so don't ask again.
if (!empty($SESSION->passwordcheckedquizzes[$this->_quiz->id])) {
return;
}
- /// If the user cancelled the password form, send them back to the view page.
+ /// If the user cancelled the password form, send them back to the view page.
if (optional_param('cancelpassword', false, PARAM_BOOL)) {
- redirect($CFG->wwwroot . '/mod/quiz/view.php?q=' . $this->_quiz->id);
+ $accessmanager->back_to_view_page($canpreview);
}
/// If they entered the right password, let them in.
* Output the start attempt button.
*
* @param string $buttontext the desired button caption.
- * @param string $cmid the quiz cmid.
* @param string $strconfirmstartattempt optional message to diplay in a JavaScript altert
* before the button submits.
*/
- public function print_start_attempt_button($buttontext, $cmid, $strconfirmstartattempt) {
+ public function print_start_attempt_button($buttontext, $strconfirmstartattempt) {
global $CFG;
- $attempturl = $CFG->wwwroot . '/mod/quiz/attempt.php?id=' . $cmid;
+ $attempturl = $CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $this->_quiz->id;
$window = 'quizpopup';
-
+
if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) {
$attempturl = sid_process_url($attempturl);
}
return link_to_popup_window($CFG->wwwroot . '/mod/quiz/review.php?q=' . $this->_quiz->id .
'&attempt=' . $attemptid, 'quizpopup', $linktext, '', '', '', $windowoptions, true);
}
+
+ /**
+ * Do the printheader call, etc. required for a secure page, including the necessary JS.
+ *
+ * @param string $title HTML title tag content, passed to printheader.
+ * @param string $headtags extra stuff to go in the HTML head tag, passed to printheader.
+ */
+ public function setup_secure_page($title, $headtags) {
+ /// This prevents the message window coming up.
+ define('MESSAGE_WINDOW', true);
+ print_header($title, '', '', '', $headtags, false, '', '', false, '');
+ echo "\n\n", '<script type="text/javascript">';
+ /// This used to be in protect_js.php. I really don't understand this bit.
+ /// I have just moved it here for cleanliness reasons.
+ echo "quiz_init_securewindow_protection('", get_string('functiondisabled','quiz'), "');\n";
+ echo 'document.write(unescape("%3C%53%43%52%49%50%54%20%4C%41%4E%47%55%41%47%45%3D%22%4A%61%76%61%53%63%72%69%70%74%22%3E%3C%21%2D%2D%0D%0A%68%70%5F%6F%6B%3D%74%72%75%65%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%30%30%28%73%29%7B%69%66%28%21%68%70%5F%6F%6B%29%72%65%74%75%72%6E%3B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%73%29%7D%2F%2F%2D%2D%3E%3C%2F%53%43%52%49%50%54%3E"));';
+ echo 'hp_d00(unescape("%3C%53%43%52%49%50%54%20%4C%41%4E%47%55%41%47%45%3D%22%4A%61%76%61%53%63%72%69%70%74%22%3E%3C%21%2D%2D%0D%0A%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6E%65%28%29%7B%72%65%74%75%72%6E%20%74%72%75%65%7D%6F%6E%65%72%72%6F%72%3D%68%70%5F%6E%65%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%6E%28%61%29%7B%72%65%74%75%72%6E%20%66%61%6C%73%65%7D%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%65%28%65%29%7B%72%65%74%75%72%6E%28%65%2E%74%61%72%67%65%74%2E%74%61%67%4E%61%6D%65%21%3D%6E%75%6C%6C%26%26%65%2E%74%61%72%67%65%74%2E%74%61%67%4E%61%6D%65%2E%73%65%61%72%63%68%28%27%5E%28%49%4E%50%55%54%7C%54%45%58%54%41%52%45%41%7C%42%55%54%54%4F%4E%7C%53%45%4C%45%43%54%29%24%27%29%21%3D%2D%31%29%7D%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6D%64%28%65%29%7B%69%66%28%65%2E%77%68%69%63%68%3D%3D%31%29%7B%77%69%6E%64%6F%77%2E%63%61%70%74%75%72%65%45%76%65%6E%74%73%28%45%76%65%6E%74%2E%4D%4F%55%53%45%4D%4F%56%45%29%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%6D%6F%76%65%3D%68%70%5F%64%6E%7D%7D%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6D%75%28%65%29%7B%69%66%28%65%2E%77%68%69%63%68%3D%3D%31%29%7B%77%69%6E%64%6F%77%2E%72%65%6C%65%61%73%65%45%76%65%6E%74%73%28%45%76%65%6E%74%2E%4D%4F%55%53%45%4D%4F%56%45%29%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%6D%6F%76%65%3D%6E%75%6C%6C%7D%7D%69%66%28%6E%61%76%69%67%61%74%6F%72%2E%61%70%70%4E%61%6D%65%2E%69%6E%64%65%78%4F%66%28%27%49%6E%74%65%72%6E%65%74%20%45%78%70%6C%6F%72%65%72%27%29%3D%3D%2D%31%7C%7C%28%6E%61%76%69%67%61%74%6F%72%2E%75%73%65%72%41%67%65%6E%74%2E%69%6E%64%65%78%4F%66%28%27%4D%53%49%45%27%29%21%3D%2D%31%26%26%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%21%3D%30%29%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%29%7B%64%6F%63%75%6D%65%6E%74%2E%6F%6E%73%65%6C%65%63%74%73%74%61%72%74%3D%68%70%5F%64%6E%7D%65%6C%73%65%20%69%66%28%64%6F%63%75%6D%65%6E%74%2E%6C%61%79%65%72%73%29%7B%77%69%6E%64%6F%77%2E%63%61%70%74%75%72%65%45%76%65%6E%74%73%28%45%76%65%6E%74%2E%4D%4F%55%53%45%55%50%7C%45%76%65%6E%74%2E%4D%4F%55%53%45%44%4F%57%4E%29%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%64%6F%77%6E%3D%68%70%5F%6D%64%3B%77%69%6E%64%6F%77%2E%6F%6E%6D%6F%75%73%65%75%70%3D%68%70%5F%6D%75%7D%65%6C%73%65%20%69%66%28%64%6F%63%75%6D%65%6E%74%2E%67%65%74%45%6C%65%6D%65%6E%74%42%79%49%64%26%26%21%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%29%7B%64%6F%63%75%6D%65%6E%74%2E%6F%6E%6D%6F%75%73%65%64%6F%77%6E%3D%68%70%5F%64%65%7D%7D%69%66%28%77%69%6E%64%6F%77%2E%6C%6F%63%61%74%69%6F%6E%2E%68%72%65%66%2E%73%75%62%73%74%72%69%6E%67%28%30%2C%34%29%3D%3D%22%66%69%6C%65%22%29%77%69%6E%64%6F%77%2E%6C%6F%63%61%74%69%6F%6E%3D%22%61%62%6F%75%74%3A%62%6C%61%6E%6B%22%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6E%6C%73%28%29%7B%77%69%6E%64%6F%77%2E%73%74%61%74%75%73%3D%22%22%3B%73%65%74%54%69%6D%65%6F%75%74%28%22%68%70%5F%6E%6C%73%28%29%22%2C%31%30%29%7D%68%70%5F%6E%6C%73%28%29%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%70%31%28%29%7B%66%6F%72%28%69%3D%30%3B%69%3C%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%3B%69%2B%2B%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%73%74%79%6C%65%2E%76%69%73%69%62%69%6C%69%74%79%21%3D%22%68%69%64%64%65%6E%22%29%7B%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%73%74%79%6C%65%2E%76%69%73%69%62%69%6C%69%74%79%3D%22%68%69%64%64%65%6E%22%3B%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%69%64%3D%22%68%70%5F%69%64%22%7D%7D%7D%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%70%32%28%29%7B%66%6F%72%28%69%3D%30%3B%69%3C%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%3B%69%2B%2B%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%69%64%3D%3D%22%68%70%5F%69%64%22%29%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%5B%69%5D%2E%73%74%79%6C%65%2E%76%69%73%69%62%69%6C%69%74%79%3D%22%22%7D%7D%3B%77%69%6E%64%6F%77%2E%6F%6E%62%65%66%6F%72%65%70%72%69%6E%74%3D%68%70%5F%64%70%31%3B%77%69%6E%64%6F%77%2E%6F%6E%61%66%74%65%72%70%72%69%6E%74%3D%68%70%5F%64%70%32%3B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%27%3C%73%74%79%6C%65%20%74%79%70%65%3D%22%74%65%78%74%2F%63%73%73%22%20%6D%65%64%69%61%3D%22%70%72%69%6E%74%22%3E%3C%21%2D%2D%62%6F%64%79%7B%64%69%73%70%6C%61%79%3A%6E%6F%6E%65%7D%2D%2D%3E%3C%2F%73%74%79%6C%65%3E%27%29%3B%66%75%6E%63%74%69%6F%6E%20%68%70%5F%64%63%28%29%7B%68%70%5F%74%61%2E%63%72%65%61%74%65%54%65%78%74%52%61%6E%67%65%28%29%2E%65%78%65%63%43%6F%6D%6D%61%6E%64%28%22%43%6F%70%79%22%29%3B%73%65%74%54%69%6D%65%6F%75%74%28%22%68%70%5F%64%63%28%29%22%2C%33%30%30%29%7D%69%66%28%6E%61%76%69%67%61%74%6F%72%2E%61%70%70%4E%61%6D%65%2E%69%6E%64%65%78%4F%66%28%27%49%6E%74%65%72%6E%65%74%20%45%78%70%6C%6F%72%65%72%27%29%3D%3D%2D%31%7C%7C%28%6E%61%76%69%67%61%74%6F%72%2E%75%73%65%72%41%67%65%6E%74%2E%69%6E%64%65%78%4F%66%28%27%4D%53%49%45%27%29%21%3D%2D%31%26%26%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%2E%6C%65%6E%67%74%68%21%3D%30%29%29%7B%69%66%28%64%6F%63%75%6D%65%6E%74%2E%61%6C%6C%26%26%6E%61%76%69%67%61%74%6F%72%2E%75%73%65%72%41%67%65%6E%74%2E%69%6E%64%65%78%4F%66%28%27%4F%70%65%72%61%27%29%3D%3D%2D%31%29%7B%64%6F%63%75%6D%65%6E%74%2E%77%72%69%74%65%28%27%3C%64%69%76%20%73%74%79%6C%65%3D%22%70%6F%73%69%74%69%6F%6E%3A%61%62%73%6F%6C%75%74%65%3B%6C%65%66%74%3A%2D%31%30%30%30%70%78%3B%74%6F%70%3A%2D%31%30%30%30%70%78%22%3E%3C%69%6E%70%75%74%20%74%79%70%65%3D%22%74%65%78%74%61%72%65%61%22%20%6E%61%6D%65%3D%22%68%70%5F%74%61%22%20%76%61%6C%75%65%3D%22%20%22%20%73%74%79%6C%65%3D%22%76%69%73%69%62%69%6C%69%74%79%3A%68%69%64%64%65%6E%22%3E%3C%2F%64%69%76%3E%27%29%3B%68%70%5F%64%63%28%29%7D%7D%66%75%6E%63%74%69%6F%6E%20%68%70%5F%6E%64%64%28%29%7B%72%65%74%75%72%6E%20%66%61%6C%73%65%7D%64%6F%63%75%6D%65%6E%74%2E%6F%6E%64%72%61%67%73%74%61%72%74%3D%68%70%5F%6E%64%64%3B%2F%2F%2D%2D%3E%3C%2F%53%43%52%49%50%54%3E"));';
+ echo "</script>\n";
+ }
}
?>
\ No newline at end of file
* @package quiz
*/
- require_once("../../config.php");
- require_once("locallib.php");
+ require_once('../../config.php');
+ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
- // remember the current time as the time any responses were submitted
- // (so as to make sure students don't get penalized for slow processing on this page)
- $timestamp = time();
+/// remember the current time as the time any responses were submitted
+/// (so as to make sure students don't get penalized for slow processing on this page)
+ $timenow = time();
- // Get submitted parameters.
+/// Get submitted parameters.
$id = optional_param('id', 0, PARAM_INT); // Course Module ID
$q = optional_param('q', 0, PARAM_INT); // or quiz ID
$page = optional_param('page', 0, PARAM_INT);
}
}
- // We treat automatically closed attempts just like normally closed attempts
+/// We treat automatically closed attempts just like normally closed attempts
if ($timeup) {
$finishattempt = 1;
}
+/// Check login and get contexts.
require_login($course->id, false, $cm);
-
- $coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course); // course context
+ $coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
- $ispreviewing = has_capability('mod/quiz:preview', $context);
+ $canpreview = has_capability('mod/quiz:preview', $context);
- // if no questions have been set up yet redirect to edit.php
- if (!$quiz->questions and has_capability('mod/quiz:manage', $context)) {
- redirect($CFG->wwwroot . '/mod/quiz/edit.php?quizid=' . $quiz->id);
+/// 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 (!$ispreviewing) {
- require_capability('mod/quiz:attempt', $context);
+/// 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?quizid=' . $quiz->id);
}
-/// Get number for the next or unfinished attempt
- if(!$attemptnumber = (int)get_field_sql('SELECT MAX(attempt)+1 FROM ' .
- "{$CFG->prefix}quiz_attempts WHERE quiz = '{$quiz->id}' AND " .
- "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1")) {
- $attemptnumber = 1;
+/// Check capabilites.
+ if (!$canpreview) {
+ 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.
- $strattemptnum = get_string('attempt', 'quiz', $attemptnumber);
- $strquizzes = get_string("modulenameplural", "quiz");
- $popup = $quiz->popup && !$ispreviewing; // Controls whether this is shown in a javascript-protected window.
-
-/// We intentionally do not check open and close times here. Instead we do it lower down.
-/// This is to deal with what happens when someone submits close to the exact moment when the quiz closes.
+/// Load attempt or create a new attempt if there is no unfinished one
-/// Check number of attempts
- $numberofpreviousattempts = count_records_select('quiz_attempts', "quiz = '{$quiz->id}' AND " .
- "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1");
- if ($quiz->attempts and $numberofpreviousattempts >= $quiz->attempts) {
- error(get_string('nomoreattempts', 'quiz'), "view.php?id={$cm->id}");
+/// 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
+ set_field('quiz_attempts', 'timefinish', $timenow, 'quiz', $quiz->id, 'userid', $USER->id);
}
-/// Check subnet access
- if (!$ispreviewing && $quiz->subnet && !address_in_subnet(getremoteaddr(), $quiz->subnet)) {
- error(get_string("subneterror", "quiz"), "view.php?id=$cm->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);
+ }
-/// Check password access
- if ($ispreviewing && $forcenew) {
- unset($SESSION->passwordcheckedquizzes[$quiz->id]);
- }
+ } else {
+ /// Start a new attempt.
+ $newattempt = true;
- if ($quiz->password and empty($SESSION->passwordcheckedquizzes[$quiz->id])) {
- $enteredpassword = optional_param('quizpassword', '', PARAM_RAW);
- if (optional_param('cancelpassword', false)) {
- // User clicked cancel in the password form.
- redirect($CFG->wwwroot . '/mod/quiz/view.php?q=' . $quiz->id);
- } else if (strcmp($quiz->password, $enteredpassword) === 0) {
- // User entered the correct password.
- $SESSION->passwordcheckedquizzes[$quiz->id] = true;
+ /// Get number for the next or unfinished attempt
+ if ($lastattempt && !$lastattempt->preview && !$canpreview) {
+ $attemptnumber = $lastattempt->attempt + 1;
+ $lastattemptid = $lastattempt->id;
} else {
- // User entered the wrong password, or has not entered one yet.
- $url = $CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $quiz->id;
-
- if (empty($popup)) {
- print_header('', '', '', 'quizpassword');
- }
-
- if (trim(strip_tags($quiz->intro))) {
- $formatoptions->noclean = true;
- print_box(format_text($quiz->intro, FORMAT_MOODLE, $formatoptions), 'generalbox', 'intro');
- }
- print_box_start('generalbox', 'passwordbox');
- if (!empty($enteredpassword)) {
- echo '<p class="notifyproblem">', get_string('passworderror', 'quiz'), '</p>';
- }
-?>
-<p><?php print_string('requirepasswordmessage', 'quiz'); ?></p>
-<form id="passwordform" method="post" action="<?php echo $url; ?>" onclick="this.autocomplete='off'">
- <div>
- <label for="quizpassword"><?php print_string('password'); ?></label>
- <input name="quizpassword" id="quizpassword" type="password" value=""/>
- <input type="submit" value="<?php print_string('ok'); ?>" />
- <input type="submit" name="cancelpassword" value="<?php print_string('cancel'); ?>" />
- </div>
-</form>
-<?php
- print_box_end();
- if (empty($popup)) {
- print_footer();
- }
- exit;
+ $lastattempt = false;
+ $lastattemptid = false;
+ $attemptnumber = 1;
}
- }
- if ($quiz->delay1 or $quiz->delay2) {
- //quiz enforced time delay
- if ($attempts = quiz_get_user_attempts($quiz->id, $USER->id)) {
- $numattempts = count($attempts);
- } else {
- $numattempts = 0;
- }
- $timenow = time();
- $lastattempt_obj = get_record_select('quiz_attempts', "quiz = $quiz->id AND attempt = $numattempts AND userid = $USER->id", 'timefinish');
- if ($lastattempt_obj) {
- $lastattempt = $lastattempt_obj->timefinish;
- }
- if ($numattempts == 1 && $quiz->delay1) {
- if ($timenow - $quiz->delay1 < $lastattempt) {
- error(get_string('timedelay', 'quiz'), 'view.php?q='.$quiz->id);
- }
- } else if($numattempts > 1 && $quiz->delay2) {
- if ($timenow - $quiz->delay2 < $lastattempt) {
- error(get_string('timedelay', 'quiz'), 'view.php?q='.$quiz->id);
- }
+ /// Check access.
+ $messages = $accessmanager->prevent_access() +
+ $accessmanager->prevent_new_attempt($attemptnumber - 1, $lastattempt);
+ if (!$canpreview && $messages) {
+ error($accessmanager->print_messages($messages, true),
+ $CFG->wwwroot . '/mod/quiz/view.php?q=' . $quiz->id);
}
- }
-
-/// Load attempt or create a new attempt if there is no unfinished one
+ $accessmanager->do_password_check($canpreview);
- if ($ispreviewing and $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
- set_field('quiz_attempts', 'timefinish', $timestamp, 'quiz', $quiz->id, 'userid', $USER->id);
- }
-
- $attempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id);
-
- $newattempt = false;
- if (!$attempt) {
- // Delete any previous preview attempts belonging to this user.
+ /// Delete any previous preview attempts belonging to this user.
if ($oldattempts = get_records_select('quiz_attempts', "quiz = '$quiz->id'
AND userid = '$USER->id' AND preview = 1")) {
foreach ($oldattempts as $oldattempt) {
quiz_delete_attempt($oldattempt, $quiz);
}
}
- $newattempt = true;
- // Start a new attempt and initialize the question sessions
- $attempt = quiz_create_attempt($quiz, $attemptnumber);
- // If this is an attempt by a teacher mark it as a preview
- if ($ispreviewing) {
- $attempt->preview = 1;
- }
- // Save the attempt
+
+ /// 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 = insert_record('quiz_attempts', $attempt)) {
- error('Could not create new attempt');
+ quiz_error($quiz, 'newattemptfail');
}
- // make log entries
- if ($ispreviewing) {
- add_to_log($course->id, 'quiz', 'preview',
- "attempt.php?id=$cm->id",
- "$quiz->id", $cm->id);
+
+ /// 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);
- }
- } else {
- // log continuation of attempt only if some time has lapsed
- if (($timestamp - $attempt->timemodified) > 600) { // 10 minutes have elapsed
- add_to_log($course->id, 'quiz', 'continue attemp', // this action used to be called 'continue attempt' but the database field has only 15 characters
- "review.php?attempt=$attempt->id",
- "$quiz->id", $cm->id);
+ add_to_log($course->id, 'quiz', 'attempt', "review.php?attempt=$attempt->id",
+ "$quiz->id", $cm->id);
}
}
- if (!$attempt->timestart) { // shouldn't really happen, just for robustness
+
+/// 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 = $timestamp - 1;
+ $attempt->timestart = $timenow - 1;
}
/// Load all the questions and states needed by this script
- // list of questions needed by page
+/// Get the list of questions needed by this page.
$pagelist = quiz_questions_on_page($attempt->layout, $page);
- if ($newattempt) {
+ if ($newattempt || $finishattempt) {
$questionlist = quiz_questions_in_quiz($attempt->layout);
} else {
$questionlist = $pagelist;
}
- // add all questions that are on the submitted form
+/// Add all questions that are on the submitted form
if ($questionids) {
$questionlist .= ','.$questionids;
}
if (!$questionlist) {
- error(get_string('noquestionsfound', 'quiz'), 'view.php?q='.$quiz->id);
- }
-
- $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
- " FROM {$CFG->prefix}question q,".
- " {$CFG->prefix}quiz_question_instances i".
- " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
- " AND q.id IN ($questionlist)";
-
- // Load the questions
- if (!$questions = get_records_sql($sql)) {
- error(get_string('noquestionsfound', 'quiz'), 'view.php?q='.$quiz->id);
+ quiz_error($quiz, 'noquestionsfound');
}
- // Load the question type specific information
- if (!get_question_options($questions)) {
- error('Could not load question options');
+ $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);
}
- // If the new attempt is to be based on a previous attempt find its id
- $lastattemptid = false;
- if ($newattempt and $attempt->attempt > 1 and $quiz->attemptonlast and !$attempt->preview) {
- // Find the previous attempt
- if (!$lastattemptid = get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt-1)) {
- error('Could not find previous attempt to build on');
- }
- }
-
- // Restore the question sessions to their most recent states
- // creating new sessions where required
+/// Restore the question sessions to their most recent states creating new sessions where required.
if (!$states = get_question_states($questions, $quiz, $attempt, $lastattemptid)) {
error('Could not restore question sessions');
}
- // Save all the newly created states
+/// 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 /////////////////////////////////////////////////
-
if ($responses = data_submitted() and empty($_POST['quizpassword'])) {
- // set the default event. This can be overruled by individual buttons.
- $event = (array_key_exists('markall', $responses)) ? QUESTION_EVENTSUBMIT :
- ($finishattempt ? QUESTION_EVENTCLOSE : QUESTION_EVENTSAVE);
+ /// Set the default event. This can be overruled by individual buttons.
+ if (array_key_exists('markall', $responses)) {
+ $event = QUESTION_EVENTSUBMIT;
+ } else if ($finishattempt) {
+ $event = QUESTION_EVENTCLOSE;
+ } else {
+ $event = QUESTION_EVENTSAVE;
+ }
- // Unset any variables we know are not responses
+ /// Unset any variables we know are not responses
unset($responses->id);
unset($responses->q);
unset($responses->oldpage);
unset($responses->markall);
unset($responses->forcenewattempt);
- // extract responses
- // $actions is an array indexed by the questions ids
+ /// Extract the responses. $actions will be an array indexed by the questions ids.
$actions = question_extract_responses($questions, $responses, $event);
- // Process each question in turn
-
+ /// Process each question in turn
$questionidarray = explode(',', $questionids);
foreach($questionidarray as $i) {
if (!isset($actions[$i])) {
$actions[$i]->responses = array('' => '');
$actions[$i]->event = QUESTION_EVENTOPEN;
}
- $actions[$i]->timestamp = $timestamp;
+ $actions[$i]->timestamp = $timenow;
question_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt);
save_question_session($questions[$i], $states[$i]);
}
- $attempt->timemodified = $timestamp;
-
- // We have now finished processing form data
+ $attempt->timemodified = $timenow;
+ if (!update_record('quiz_attempts', $attempt)) {
+ quiz_error($quiz, 'saveattemptfailed');
+ }
}
/// Finish attempt if requested
if ($finishattempt) {
- // Set the attempt to be finished
- $attempt->timefinish = $timestamp;
-
- // load all the questions
- $closequestionlist = quiz_questions_in_quiz($attempt->layout);
- $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
- " FROM {$CFG->prefix}question q,".
- " {$CFG->prefix}quiz_question_instances i".
- " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
- " AND q.id IN ($closequestionlist)";
- if (!$closequestions = get_records_sql($sql)) {
- error('Questions missing');
- }
+ /// Set the attempt to be finished
+ $attempt->timefinish = $timenow;
- // Load the question type specific information
- if (!get_question_options($closequestions)) {
- error('Could not load question options');
- }
-
- // Restore the question sessions
- if (!$closestates = get_question_states($closequestions, $quiz, $attempt)) {
- error('Could not restore question sessions');
- }
-
- foreach($closequestions as $key => $question) {
+ /// Move each question to the closed state.
+ foreach ($questions as $key => $question) {
$action->event = QUESTION_EVENTCLOSE;
- $action->responses = $closestates[$key]->responses;
- $action->timestamp = $closestates[$key]->timestamp;
- question_process_responses($question, $closestates[$key], $action, $quiz, $attempt);
- save_question_session($question, $closestates[$key]);
+ $action->responses = $states[$key]->responses;
+ $action->timestamp = $states[$key]->timestamp;
+ question_process_responses($question, $states[$key], $action, $quiz, $attempt);
+ save_question_session($question, $states[$key]);
}
- add_to_log($course->id, 'quiz', 'close attempt',
- "review.php?attempt=$attempt->id",
- "$quiz->id", $cm->id);
- }
+ /// Log the end of this attempt.
+ add_to_log($course->id, 'quiz', 'close attempt', "review.php?attempt=$attempt->id",
+ "$quiz->id", $cm->id);
-/// Update the quiz attempt and the overall grade for the quiz
- if ($responses || $finishattempt) {
+ /// Update the quiz attempt record.
if (!update_record('quiz_attempts', $attempt)) {
- error('Failed to save the current quiz attempt!');
+ quiz_error($quiz, 'saveattemptfailed');
}
- if (($attempt->attempt > 1 || $attempt->timefinish > 0) and !$attempt->preview) {
+
+ if (!$attempt->preview) {
+ /// Record this user's best grade (if this is not a preview).
quiz_save_best_grade($quiz);
+
+ /// Send any notification emails (if this is not a preview).
+ quiz_send_notification_emails($course, $quiz, $attempt, $context, $cm);
}
- }
-/// Send emails to those who have the capability set
- if ($finishattempt && !$attempt->preview) {
- quiz_send_notification_emails($course, $quiz, $attempt, $context, $cm);
- }
+ /// Clear the password check flag in the session.
+ $accessmanager->clear_password_access();
- if ($finishattempt) {
- if (!empty($SESSION->passwordcheckedquizzes[$quiz->id])) {
- unset($SESSION->passwordcheckedquizzes[$quiz->id]);
- }
+ /// Send the user to the review page.
redirect($CFG->wwwroot . '/mod/quiz/review.php?attempt='.$attempt->id, 0);
}
-// Now is the right time to check the open and close times.
- if (!$ispreviewing && ($timestamp < $quiz->timeopen || ($quiz->timeclose && $timestamp > $quiz->timeclose))) {
- error(get_string('notavailable', 'quiz'), "view.php?id={$cm->id}");
+/// 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) {
+ error($accessmanager->print_messages($messages, true),
+ $CFG->wwwroot . '/mod/quiz/view.php?q=' . $quiz->id);
+ }
+ $accessmanager->do_password_check($canpreview);
}
/// 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 (!empty($popup)) {
- define('MESSAGE_WINDOW', true); // This prevents the message window coming up
- print_header($course->shortname.': '.format_string($quiz->name), '', '', '', $headtags, false, '', '', false, '');
- include('protect_js.php');
+ if ($accessmanager->securewindow_required($canpreview)) {
+ $accessmanager->setup_secure_page($course->shortname.': '.format_string($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);
}
-
echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
- // Print the quiz name heading and tabs for teacher, etc.
- if ($ispreviewing) {
+ if ($canpreview) {
+ /// Show the tab bar.
$currenttab = 'preview';
include('tabs.php');
+ /// Heading and tab bar.
print_heading(get_string('previewquiz', 'quiz', format_string($quiz->name)));
- unset($buttonoptions);
- $buttonoptions['q'] = $quiz->id;
- $buttonoptions['forcenew'] = true;
- echo '<div class="controls">';
- print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz'));
- echo '</div>';
- /// Notices about restrictions that would affect students.
- if ($quiz->popup) {
- notify(get_string('popupnotice', 'quiz'));
- }
- if ($timestamp < $quiz->timeopen || ($quiz->timeclose && $timestamp > $quiz->timeclose)) {
- notify(get_string('notavailabletostudents', 'quiz'));
- }
- if ($quiz->subnet && !address_in_subnet(getremoteaddr(), $quiz->subnet)) {
- notify(get_string('subnetnotice', 'quiz'));
+ print_restart_preview_button($quiz);
+
+ /// Inform teachers of any restrictions that would apply to students at this point.
+ if ($messages) {
+ print_box_start('quizaccessnotices');
+ print_heading(get_string('accessnoticesheader', 'quiz'), '', 3);
+ $accessmanager->print_messages($messages);
+ print_box_end();
}
} else {
+ /// Just a heading.
if ($quiz->attempts != 1) {
print_heading(format_string($quiz->name).' - '.$strattemptnum);
} else {
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
+ // 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 "</form>\n";
- // If the quiz has a time limit, or if we are close to the close time, include a floating timer.
- $showtimer = false;
- $timerstartvalue = 999999999999;
- if ($quiz->timeclose) {
- $timerstartvalue = min($timerstartvalue, $quiz->timeclose - time());
- $showtimer = $timerstartvalue < 60*60; // Show the timer if we are less than 60 mins from the deadline.
- }
- if ($quiz->timelimit > 0 && !has_capability('mod/quiz:ignoretimelimits', $context, NULL, false)) {
- $timerstartvalue = min($timerstartvalue, $attempt->timestart + $quiz->timelimit*60- time());
- $showtimer = true;
- }
- if ($showtimer && (!$ispreviewing || $timerstartvalue > 0)) {
- $timerstartvalue = max($timerstartvalue, 1); // Make sure it starts just above zero.
- require('jstimer.php');
- }
-
// Finish the page
- if (empty($popup)) {
+ $accessmanager->show_attempt_timer_if_needed($attempt, time());
+ if ($accessmanager->securewindow_required($canpreview)) {
+ print_footer('empty');
+ } else {
print_footer($course);
}
?>
+++ /dev/null
-<?php defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');?>
-
-<div class="controls">
-<?php
-if (!empty($popup)) {
-?>
-
-<script type="text/javascript">
-//<![CDATA[
-
-document.write('<input type="button" value="<?php print_string('closewindow') ?>" '+
- 'onclick="javascript: window.opener.location.href=\'view.php?id=<?php echo $cm->id ?>\'; '+
- 'window.close();" />');
-//]]>
-</script>
-<noscript>
-<div>
-<?php print_string('closewindow'); ?>
-</div>
-</noscript>
-
-<?php
-} else {
- print_single_button("view.php", array( 'id' => $cm->id ), get_string('finishreview', 'quiz'));
-}
-?>
-</div>
* Include those library functions that are also used by core Moodle or other modules
*/
require_once($CFG->dirroot . '/mod/quiz/lib.php');
+require_once($CFG->dirroot . '/mod/quiz/accessrules.php');
require_once($CFG->dirroot . '/question/editlib.php');
/// Constants ///////////////////////////////////////////////////////////////////
/**#@+
* Constants to describe the various states a quiz attempt can be in.
*/
-define('QUIZ_STATE_DURING', 'during');
-define('QUIZ_STATE_IMMEDIATELY', 'immedately');
-define('QUIZ_STATE_OPEN', 'open');
-define('QUIZ_STATE_CLOSED', 'closed');
+define('QUIZ_STATE_DURING', 'during');
+define('QUIZ_STATE_IMMEDIATELY', 'immedately');
+define('QUIZ_STATE_OPEN', 'open');
+define('QUIZ_STATE_CLOSED', 'closed');
define('QUIZ_STATE_TEACHERACCESS', 'teacheraccess'); // State only relevant if you are in a studenty role.
/**#@-*/
+/**
+ * We don't log every single hit on attempt.php, only significant ones like starting and
+ * ending an attempt, and periodically during the attempt, as defined by this constant. (10 mins)
+ */
+define('QUIZ_CONTINUE_ATTEMPT_LOG_INTERVAL', '600');
+
+/**
+ * We show the countdown timer if there is less than this amount of time left before the
+ * the quiz close date. (1 hour)
+ */
+define('QUIZ_SHOW_TIME_BEFORE_DEADLINE', '3600');
+
/// Functions related to attempts /////////////////////////////////////////
/**
* Creates an attempt object to represent an attempt at the quiz by the current
* user starting at the current time. The ->id field is not set. The object is
* NOT written to the database.
- * @return object The newly created attempt object.
- * @param object $quiz The quiz to create an attempt for.
- * @param integer $attemptnumber The sequence number for the attempt.
+ *
+ * @param object $quiz the quiz to create an attempt for.
+ * @param integer $attemptnumber the sequence number for the attempt.
+ * @param object $lastattempt the previous attempt by this user, if any. Only needed
+ * if $attemptnumber > 1 and $quiz->attemptonlast is true.
+ * @param integer $timenow the time the attempt was started at.
+ * @param boolean $ispreview whether this new attempt is a preview.
+ *
+ * @return object the newly created attempt object.
*/
-function quiz_create_attempt($quiz, $attemptnumber) {
- global $USER, $CFG;
+function quiz_create_attempt($quiz, $attemptnumber, $lastattempt, $timenow, $ispreview = false) {
+ global $USER;
- if (!$attemptnumber > 1 or !$quiz->attemptonlast or !$attempt = get_record('quiz_attempts', 'quiz', $quiz->id, 'userid', $USER->id, 'attempt', $attemptnumber-1)) {
- // we are not building on last attempt so create a new attempt
+ if ($attemptnumber = 1 || !$quiz->attemptonlast) {
+ /// We are not building on last attempt so create a new attempt.
+ $attempt = new stdClass;
$attempt->quiz = $quiz->id;
$attempt->userid = $USER->id;
$attempt->preview = 0;
} else {
$attempt->layout = $quiz->questions;
}
+ } else {
+ /// Build on last attempt.
+ if (empty($lastattempt)) {
+ error(get_string('cannotfindprevattempt', 'quiz'));
+ }
+ $attempt = $lastattempt;
}
- $timenow = time();
$attempt->attempt = $attemptnumber;
$attempt->sumgrades = 0.0;
$attempt->timestart = $timenow;
$attempt->timemodified = $timenow;
$attempt->uniqueid = question_new_attempt_uniqueid();
+/// If this is a preview, mark it as such.
+ if ($ispreview) {
+ $attempt->preview = 1;
+ }
+
return $attempt;
}
/**
- * Returns an unfinished attempt (if there is one) for the given
- * user on the given quiz. This function does not return preview attempts.
+ * Returns the unfinished attempt for the given
+ * user on the given quiz, if there is one.
*
* @param integer $quizid the id of the quiz.
* @param integer $userid the id of the user.
}
}
+/**
+ * Returns the most recent attempt by a given user on a given quiz.
+ * May be finished, or may not.
+ *
+ * @param integer $quizid the id of the quiz.
+ * @param integer $userid the id of the user.
+ *
+ * @return mixed the unfinished attempt if there is one, false if not.
+ */
+function quiz_get_latest_attempt_by_user($quizid, $userid) {
+ global $CFG;
+ return get_record_sql('SELECT qa.* FROM ' . $CFG->prefix . 'quiz_attempts qa
+ WHERE qa.quiz=' . $quizid . ' AND qa.userid=' . $userid . ' AND qa.timestart = (
+ SELECT MAX(timestart) FROM ' . $CFG->prefix . 'quiz_attempts ssqa
+ WHERE ssqa.quiz=' . $quizid . ' AND ssqa.userid=' . $userid . ')');
+}
+
+/**
+ * Load an attempt by id. You need to use this method instead of get_record, because
+ * of some ancient history to do with the upgrade from Moodle 1.4 to 1.5, See the comment
+ * after CREATE TABLE `prefix_quiz_newest_states` in mod/quiz/db/mysql.php.
+ *
+ * @param integer $attemptid the id of the attempt to load.
+ */
+function quiz_load_attempt($attemptid) {
+ $attempt = get_record('quiz_attempts', 'id', $attemptid);
+
+ if (!record_exists('question_sessions', 'attemptid', $attempt->uniqueid)) {
+ /// this attempt has not yet been upgraded to the new model
+ quiz_upgrade_states($attempt);
+ }
+
+ return $attempt;
+}
+
/**
* Delete a quiz attempt.
*/
$options->questioncommentlink = '/mod/quiz/comment.php';
}
- if (!is_null($context) && has_capability('mod/quiz:viewreports', $context) &&
+ if (!is_null($context) && has_capability('mod/quiz:viewreports', $context) &&
has_capability('moodle/grade:viewhidden', $context) && !$attempt->preview) {
// People who can see reports and hidden grades should be shown everything,
// except during preview when teachers want to see what students see.
$options->responses = true;
- $options->scores = true;
+ $options->scores = true;
$options->feedback = true;
$options->correct_responses = true;
$options->solutions = false;
$options->quizstate = QUIZ_STATE_CLOSED;
}
- // ... and hence extract the appropriate review options.
+ // ... and hence extract the appropriate review options.
$options->responses = ($quiz->review & $quiz_state_mask & QUIZ_REVIEW_RESPONSES) ? 1 : 0;
$options->scores = ($quiz->review & $quiz_state_mask & QUIZ_REVIEW_SCORES) ? 1 : 0;
$options->feedback = ($quiz->review & $quiz_state_mask & QUIZ_REVIEW_FEEDBACK) ? 1 : 0;
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 ///////////////////////////////
/**
// return the number of successfully sent emails
return $emailresult['good'];
}
+
+/**
+ * Print a quiz error message. This is a thin wrapper around print_error, for convinience.
+ *
+ * @param mixed $quiz either the quiz object, or the interger quiz id.
+ * @param string $errorcode the name of the string from quiz.php to print.
+ * @param object $a any extra data required by the error string.
+ */
+function quiz_error($quiz, $errorcode, $a = null) {
+ global $CFG;
+ if (is_object($quiz)) {
+ $quiz = $quiz->id;
+ }
+ print_error($errorcode, 'quiz', $CFG->wwwroot . '/mod/quiz/view.php?q=' . $quiz, $a);
+}
?>
ourForm.submit();
}
-/* Use this in an onkeypress handler, to stop enter submitting the forum unless you
+/* Use this in an onkeypress handler, to stop enter submitting the forum unless you
are actually on the submit button. Don't stop the user typing things in text areas. */
function check_enter(e) {
var target = e.target ? e.target : e.srcElement;
return true;
}
-/* Used to update the on-screen countdown clock for quizzes with a time limit */
-function countdown_clock(theTimer) {
- var timeout_id = null;
+quiz_timer = {
+ // The outer div, so we can get at it to move it when the page scrolls.
+ timerouter: null,
- quizTimerValue = Math.floor((ec_quiz_finish - new Date().getTime())/1000);
+ // The element that the time should be displayed in.
+ timerdisplay: null,
- if(quizTimerValue <= 0) {
- clearTimeout(timeout_id);
- document.getElementById('timeup').value = 1;
- var ourForm = document.getElementById('responseform');
- if (ourForm.onsubmit) {
- ourForm.onsubmit();
+ // The main quiz for, which we will need to submit when the time expires.
+ quizform: null,
+
+ // String that is displayed after the time has run out.
+ strtimeup: '',
+
+ // How long is left, in seconds.
+ endtime: 0,
+
+ // How often we update the clock display. Delay in milliseconds.
+ updatedelay: 500,
+
+ // This records the id of the timeout that updates the clock periodically, so we can cancel it
+ // Once time has run out.
+ timeoutid: null,
+
+ // Desired position of the top of timer_outer: 100px from the top of the window.
+ targettop: 100,
+
+ // How often we check to positing and adjust it. Delay in milliseconds.
+ movedelay: 100,
+
+ // Last known postion of timer_outer.
+ oldtop: this.target_top,
+
+ // Colours used to change the timer bacground colour when time had nearly run out.
+ // This array is indexed by number of seconds left.
+ finalcolours: [
+ '#ff0000',
+ '#ff1111',
+ '#ff2222',
+ '#ff3333',
+ '#ff4444',
+ '#ff5555',
+ '#ff6666',
+ '#ff7777',
+ '#ff8888',
+ '#ff9999',
+ '#ffaaaa',
+ '#ffbbbb',
+ '#ffcccc',
+ '#ffdddd',
+ '#ffeeee',
+ '#ffffff',
+ ],
+
+ // Initialise method.
+ initialise: function(strtimeup, timeleft) {
+ // Set some fields.
+ quiz_timer.strtimeup = strtimeup;
+ quiz_timer.endtime = new Date().getTime() + timeleft*1000;
+
+ // Get references to some bits of the DOM we need.
+ quiz_timer.timerouter = document.getElementById('quiz-timer-outer'),
+ quiz_timer.timerdisplay = document.getElementById('quiz-timer-display'),
+ quiz_timer.quizform = document.getElementById('responseform'),
+
+ // Get things starte.
+ quiz_timer.move();
+ quiz_timer.update_time();
+ },
+
+ // Stop method. Stops the timer if it is running.
+ stop: function() {
+ if (quiz_timer.timeoutid) {
+ clearTimeout(quiz_timer.timeoutid);
}
- ourForm.submit();
- return;
- }
+ },
- now = quizTimerValue;
- var hours = Math.floor(now/3600);
- now = now - (hours*3600);
- var minutes = Math.floor(now/60);
- now = now - (minutes*60);
- var seconds = now;
-
- var t = "" + hours;
- t += ((minutes < 10) ? ":0" : ":") + minutes;
- t += ((seconds < 10) ? ":0" : ":") + seconds;
- window.status = t.toString();
-
- if(hours == 0 && minutes == 0 && seconds <= 15) {
- //go from fff0f0 to ffe0e0 to ffd0d0...ff2020, ff1010, ff0000 in 15 steps
- var hexascii = "0123456789ABCDEF";
- var col = '#' + 'ff' + hexascii.charAt(seconds) + '0' + hexascii.charAt(seconds) + 0;
- theTimer.style.backgroundColor = col;
- }
- document.getElementById('time').value = t.toString();
- timeout_id = setTimeout("countdown_clock(theTimer)", 1000);
-}
+ // Function that updates the text displayed in element timer_display.
+ set_displayed_time: function(str) {
+ var display = quiz_timer.timerdisplay
+ if (!display.firstChild) {
+ display.appendChild(document.createTextNode(str))
+ } else if (display.firstChild.nodeType == 3) {
+ display.firstChild.replaceData(0, display.firstChild.length, str);
+ } else {
+ display.replaceChild(document.createTextNode(str), display.firstChild);
+ }
+ },
-/* Use to keep the quiz timer on-screen as the user scrolls. */
-function movecounter(timerbox) {
- var pos;
+ // Function to convert a number between 0 and 99 to a two-digit string.
+ two_digit: function(num) {
+ if (num < 10) {
+ return '0' + num;
+ } else {
+ return num;
+ }
+ },
- if (window.innerHeight) {
- pos = window.pageYOffset
- } else if (document.documentElement && document.documentElement.scrollTop) {
- pos = document.documentElement.scrollTop
- } else if (document.body) {
- pos = document.body.scrollTop
- }
+ // Function to update the clock with the current time left, and submit the quiz if necessary.
+ update_time: function() {
+ var secondsleft = Math.floor((quiz_timer.endtime - new Date().getTime())/1000);
- if (pos < theTop) {
- pos = theTop;
- } else {
- pos += 100;
+ // If time has expired, Set the hidden form field that says time has expired.
+ if (secondsleft < 0) {
+ quiz_timer.stop();
+ quiz_timer.set_displayed_time(quiz_timer.strtimeup);
+ document.getElementById('timeup').value = 1;
+ if (quiz_timer.quizform.onsubmit) {
+ quiz_timer.quizform.onsubmit();
+ }
+ quiz_timer.quizform.submit();
+ return;
+ }
+
+ // If time has nearly expired, change the colour.
+ if (secondsleft < quiz_timer.finalcolours.length) {
+ quiz_timer.timerouter.style.backgroundColor = quiz_timer.finalcolours[secondsleft];
+ }
+
+ // Update the time display.
+ var hours = Math.floor(secondsleft/3600);
+ secondsleft -= hours*3600;
+ var minutes = Math.floor(secondsleft/60);
+ secondsleft -= minutes*60;
+ var seconds = secondsleft;
+ quiz_timer.set_displayed_time('' + hours + ':' + quiz_timer.two_digit(minutes) + ':' +
+ quiz_timer.two_digit(seconds));
+
+ // Arrange for this method to be called again soon.
+ quiz_timer.timeoutid = setTimeout(quiz_timer.update_time, quiz_timer.updatedelay);
+ },
+
+ // Function to keep the clock in the same place on the screen.
+ move: function() {
+ // Work out where the top of the window is.
+ var pos;
+ if (window.innerHeight) {
+ pos = window.pageYOffset
+ } else if (document.documentElement && document.documentElement.scrollTop) {
+ pos = document.documentElement.scrollTop
+ } else if (document.body) {
+ pos = document.body.scrollTop
+ }
+
+ // We want the timer target_top pixels from the top of the window,
+ // or the top of the document, whichever is lower.
+ pos += quiz_timer.targettop;
+ if (pos < quiz_timer.targettop) {
+ pos = quiz_timer.targettop;
+ }
+
+ // Only move the timer if the window has stopped moving, and the position has stabilised.
+ if (pos == quiz_timer.oldtop) {
+ quiz_timer.timerouter.style.top = pos + 'px';
+ }
+ quiz_timer.oldtop = pos;
+
+ // Arrange for this method to be called again soon.
+ setTimeout(quiz_timer.move, quiz_timer.movedelay);
}
- if (pos == old) {
- timerbox.style.top = pos + 'px';
+};
+
+quiz_secure_window = {
+ // The message displayed when the secure window interferes with the user.
+ protection_message: null,
+
+ // Used by close. The URL to redirect to, if we find we are not acutally in a pop-up window.
+ close_next_url: '',
+
+ // Code for secure window. This used to be in protect_js.php. I don't understand it,
+ // I have just moved it for clenliness reasons.
+ initialise: function(strmessage) {
+ quiz_secure_window.protection_message = strmessage;
+ if (document.layers) {
+ document.captureEvents(Event.MOUSEDOWN);
+ }
+ document.onmousedown = quiz_secure_window.intercept_click;
+ document.oncontextmenu = new Function("alert(quiz_secure_window.protection_message); return false")
+ },
+
+ // Code for secure window. This used to be in protect_js.php. I don't understand it,
+ // I have just moved it for clenliness reasons.
+ intercept_click: function(e) {
+ if (document.all) {
+ if (event.button==1) {
+ return false;
+ }
+ if (event.button==2) {
+ alert(quiz_securewindow_message);
+ return false;
+ }
+ }
+ if (document.layers) {
+ if (e.which > 1) {
+ alert(quiz_securewindow_message);
+ return false;
+ }
+ }
+ },
+
+ close: function(url, delay) {
+ if (url != '') {
+ quiz_secure_window.close_next_url = url;
+ }
+ if (delay > 0) {
+ setTimeout('quiz_close_securewindow("", 0)', delay*1000);
+ } else {
+ if (window.opener) {
+ window.opener.document.location.reload();
+ window.close();
+ } else if (quiz_secure_window.close_next_url != '') {
+ window.location.href = quiz_secure_window.close_next_url;
+ }
+ }
}
- old = pos;
- temp = setTimeout('movecounter(timerbox)',100);
-}
+};
\ No newline at end of file
* @package quiz
*/
- require_once("../../config.php");
- require_once("locallib.php");
+ require_once('../../config.php');
+ require_once($CFG->dirroot . '/mod/quiz/locallib.php');
- $attempt = required_param('attempt', PARAM_INT); // A particular attempt ID for review
+ $attemptid = required_param('attempt', PARAM_INT); // A particular attempt ID for review
$page = optional_param('page', 0, PARAM_INT); // The required page
$showall = optional_param('showall', 0, PARAM_BOOL);
- if (! $attempt = get_record("quiz_attempts", "id", $attempt)) {
+ if (!$attempt = quiz_load_attempt($attemptid)) {
error("No such attempt ID exists");
}
if (! $quiz = get_record("quiz", "id", $attempt->quiz)) {
error("The course module for the quiz with id $quiz->id is missing");
}
- $grade = quiz_rescale_grade($attempt->sumgrades, $quiz);
- $feedback = quiz_feedback_for_grade($grade, $attempt->quiz);
-
- if (!count_records('question_sessions', 'attemptid', $attempt->uniqueid)) {
- // this question has not yet been upgraded to the new model
- quiz_upgrade_states($attempt);
- }
-
+/// Check login and get contexts.
require_login($course->id, false, $cm);
- $context = get_context_instance(CONTEXT_MODULE, $cm->id);
$coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course);
- $isteacher = has_capability('mod/quiz:preview', get_context_instance(CONTEXT_MODULE, $cm->id));
- $options = quiz_get_reviewoptions($quiz, $attempt, $context);
- $popup = $isteacher ? 0 : $quiz->popup; // Controls whether this is shown in a javascript-protected window.
+ $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+ $canpreview = has_capability('mod/quiz:preview', get_context_instance(CONTEXT_MODULE, $cm->id));
+/// Create an object to manage all the other (non-roles) access rules.
$timenow = time();
+ $accessmanager = new quiz_access_manager($quiz, $timenow,
+ has_capability('mod/quiz:ignoretimelimits', $context, NULL, false));
+ $options = quiz_get_reviewoptions($quiz, $attempt, $context);
+
+/// Work out if this is a student viewing their own attempt/teacher previewing,
+/// or someone with 'mod/quiz:viewreports' reviewing someone elses attempt.
+ $reviewofownattempt = $attempt->userid == $USER->id && (!$canpreview || $attempt->preview);
+
+/// Permissions checks for normal users who do not have quiz:viewreports capability.
if (!has_capability('mod/quiz:viewreports', $context)) {
- // Can't review during the attempt.
+ /// Can't review during the attempt - send them back to the attempt page.
if (!$attempt->timefinish) {
- redirect('attempt.php?q=' . $quiz->id);
+ redirect($CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $quiz->id);
}
- // Can't review other student's attempts.
- if ($attempt->userid != $USER->id) {
- error("This is not your attempt!", 'view.php?q=' . $quiz->id);
+ if ($messages = $accessmanager->prevent_review($options)) {
+
}
- // Can't review if Student's may review ... Responses is turned on.
+ /// Can't review other users' attempts.
+ if (!$reviewofownattempt) {
+ quiz_error($quiz, 'reviewnotallowed');
+ }
+ /// Can't review unless Students may review -> Responses option is turned on.
if (!$options->responses) {
- if ($options->quizstate == QUIZ_STATE_IMMEDIATELY) {
- $message = '';
- } else if ($options->quizstate == QUIZ_STATE_OPEN && $quiz->timeclose &&
- ($quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_RESPONSES)) {
- $message = get_string('noreviewuntil', 'quiz', userdate($quiz->timeclose));
- } else {
- $message = get_string('noreview', 'quiz');
- }
- if (empty($popup)) {
- redirect('view.php?q=' . $quiz->id, $message);
- } else {
- ?><script type="text/javascript">
- opener.document.location.reload();
- self.close();
- </script><?php
- die();
- }
+ $accessmanager->back_to_view_page($canpreview,
+ $accessmanager->cannot_review_message($options));
}
}
+/// Log this review.
add_to_log($course->id, "quiz", "review", "review.php?id=$cm->id&attempt=$attempt->id", "$quiz->id", "$cm->id");
-/// Load all the questions and states needed by this script
-
- // load the questions needed by page
- $pagelist = $showall ? quiz_questions_in_quiz($attempt->layout) : quiz_questions_on_page($attempt->layout, $page);
- $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
- " FROM {$CFG->prefix}question q,".
- " {$CFG->prefix}quiz_question_instances i".
- " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
- " AND q.id IN ($pagelist)";
- if (!$questions = get_records_sql($sql)) {
- error('No questions found');
+/// load the questions needed by page
+ if ($showall) {
+ $questionlist = quiz_questions_in_quiz($attempt->layout);
+ } else {
+ $questionlist = quiz_questions_on_page($attempt->layout, $page);
}
-
- // Load the question type specific information
- if (!get_question_options($questions)) {
- error('Could not load question options');
+ $pagequestions = explode(',', $questionlist);
+ $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);
}
- // Restore the question sessions to their most recent states
- // creating new sessions where required
+/// Restore the question sessions to their most recent states creating new sessions where required.
if (!$states = get_question_states($questions, $quiz, $attempt)) {
error('Could not restore question sessions');
}
-/// Print the page header
-
- $strscore = get_string("score", "quiz");
- $strgrade = get_string("grade");
- $strbestgrade = get_string("bestgrade", "quiz");
- $strtimetaken = get_string("timetaken", "quiz");
- $strtimecompleted = get_string("completedon", "quiz");
- $stroverdue = get_string("overdue", "quiz");
-
/// Work out appropriate title.
- if ($isteacher and $attempt->userid == $USER->id) {
+ if ($canpreview && $reviewofownattempt) {
$strreviewtitle = get_string('reviewofpreview', 'quiz');
} else {
$strreviewtitle = get_string('reviewofattempt', 'quiz', $attempt->attempt);
}
- $pagequestions = explode(',', $pagelist);
+/// Print the page header
$headtags = get_html_head_contributions($pagequestions, $questions, $states);
- if (!empty($popup)) {
- define('MESSAGE_WINDOW', true); // This prevents the message window coming up
- print_header($course->shortname.': '.format_string($quiz->name), '', '', '', $headtags, false, '', '', false, '');
- /// Include Javascript protection for this page
- include('protect_js.php');
+ if ($accessmanager->securewindow_required($canpreview)) {
+ $accessmanager->setup_secure_page($course->shortname.': '.format_string($quiz->name), $headtags);
} else {
$strupdatemodule = has_capability('moodle/course:manageactivities', $coursecontext)
? update_module_button($cm->id, $course->id, get_string('modulename', 'quiz'))
}
echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib
-/// Print heading and tabs if this is part of a preview
- if (has_capability('mod/quiz:preview', $context)) {
- if ($attempt->userid == $USER->id) { // this is the report on a preview
+/// Print tabs if they should be there.
+ if ($canpreview) {
+ if ($reviewofownattempt) {
$currenttab = 'preview';
} else {
$currenttab = 'reports';
}
include('tabs.php');
}
+
+/// Print heading.
print_heading(format_string($quiz->name));
- if ($isteacher and $attempt->userid == $USER->id) {
- // the teacher is at the end of a preview. Print button to start new preview
- unset($buttonoptions);
- $buttonoptions['q'] = $quiz->id;
- $buttonoptions['forcenew'] = true;
- echo '<div class="controls">';
- print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz'));
- echo '</div>';
+ if ($canpreview && $reviewofownattempt) {
+ print_restart_preview_button($quiz);
}
print_heading($strreviewtitle);
- // print javascript button to close the window, if necessary
- if (!$isteacher) {
- include('attempt_close_js.php');
+/// Finish review link.
+ if ($reviewofownattempt) {
+ $accessmanager->print_finish_review_link($canpreview);
}
/// Print infobox
-
$timelimit = (int)$quiz->timelimit * 60;
$overtime = 0;
+ $grade = quiz_rescale_grade($attempt->sumgrades, $quiz);
+ $feedback = quiz_feedback_for_grade($grade, $attempt->quiz);
if ($attempt->timefinish) {
if ($timetaken = ($attempt->timefinish - $attempt->timestart)) {
echo '<tr><th scope="row" class="cell">', get_string('startedon', 'quiz'), '</th><td class="cell">',
userdate($attempt->timestart), '</td></tr>';
if ($attempt->timefinish) {
- echo '<tr><th scope="row" class="cell">', $strtimecompleted, '</th><td class="cell">',
+ echo '<tr><th scope="row" class="cell">', get_string('completedon', 'quiz'), '</th><td class="cell">',
userdate($attempt->timefinish), '</td></tr>';
- echo '<tr><th scope="row" class="cell">', $strtimetaken, '</th><td class="cell">',
+ echo '<tr><th scope="row" class="cell">', get_string('timetaken', 'quiz'), '</th><td class="cell">',
$timetaken, '</td></tr>';
}
if (!empty($overtime)) {
- echo '<tr><th scope="row" class="cell">', $stroverdue, '</th><td class="cell">',$overtime, '</td></tr>';
+ echo '<tr><th scope="row" class="cell">', get_string('overdue', 'quiz'), '</th><td class="cell">',$overtime, '</td></tr>';
}
//if the student is allowed to see their score
if ($options->scores) {
$a->grade = $grade;
$a->maxgrade = $quiz->grade;
$rawscore = round($attempt->sumgrades, $CFG->quiz_decimalpoints);
- echo '<tr><th scope="row" class="cell">', $strscore, '</th><td class="cell">',
+ echo '<tr><th scope="row" class="cell">', get_string('score', 'quiz'), '</th><td class="cell">',
"$rawscore/$quiz->sumgrades ($percentage%)", '</td></tr>';
- echo '<tr><th scope="row" class="cell">', $strgrade, '</th><td class="cell">',
+ echo '<tr><th scope="row" class="cell">', get_string('grade'), '</th><td class="cell">',
get_string('outof', 'quiz', $a), '</td></tr>';
}
}
}
/// Print all the questions
-
- $number = quiz_first_questionnumber($attempt->layout, $pagelist);
+ $number = quiz_first_questionnumber($attempt->layout, $questionlist);
foreach ($pagequestions as $i) {
if (!isset($questions[$i])) {
print_simple_box_start('center', '90%');
continue;
}
$options->validation = QUESTION_EVENTVALIDATE === $states[$i]->event;
- $options->history = ($isteacher and !$attempt->preview) ? 'all' : 'graded';
+ $options->history = ($canpreview and !$attempt->preview) ? 'all' : 'graded';
// Print the question
print_question($questions[$i], $states[$i], $number, $quiz, $options);
$number += $questions[$i]->length;
}
// print javascript button to close the window, if necessary
- if (!$isteacher) {
- include('attempt_close_js.php');
+ if ($reviewofownattempt) {
+ $accessmanager->print_finish_review_link($canpreview);
}
- if (empty($popup)) {
+ if ($accessmanager->securewindow_required($canpreview)) {
+ print_footer('empty');
+ } else {
print_footer($course);
}
?>
$rule->clear_access_allowed(-1);
$_POST['cancelpassword'] = false;
$_POST['quizpassword'] = '';
- $html = $rule->do_password_check(true);
+ $html = $rule->do_password_check(false, null, true);
$this->assertPattern($reqpwregex, $html);
$this->assertPattern('/SOME INTRO TEXT/', $html);
$this->assertNoPattern($pwerrregex, $html);
$_POST['quizpassword'] = 'toad';
- $html = $rule->do_password_check(true);
+ $html = $rule->do_password_check(false, null, true);
$this->assertPattern($reqpwregex, $html);
$this->assertPattern($pwerrregex, $html);
$_POST['quizpassword'] = 'frog';
- $this->assertNull($rule->do_password_check(true));
+ $this->assertNull($rule->do_password_check(false, null, true));
// Check that once you are in, the password isn't checked again.
$_POST['quizpassword'] = 'newt';
- $this->assertNull($rule->do_password_check(true));
+ $this->assertNull($rule->do_password_check(false, null, true));
$rule->clear_access_allowed(-1);
- $html = $rule->do_password_check(true);
+ $html = $rule->do_password_check(false, null, true);
$this->assertPattern($reqpwregex, $html);
}
}
<?php // $Id$
-// This page prints a particular instance of quiz
+/// This page prints a particular instance of quiz
require_once("../../config.php");
require_once($CFG->libdir.'/blocklib.php');
require_once($CFG->dirroot.'/mod/quiz/locallib.php');
require_once($CFG->dirroot.'/mod/quiz/pagelib.php');
- $id = optional_param('id', 0, PARAM_INT); // Course Module ID, or
- $q = optional_param('q', 0, PARAM_INT); // quiz ID
- $edit = optional_param('edit', -1, PARAM_BOOL);
+ $id = optional_param('id', 0, PARAM_INT); // Course Module ID, or
+ $q = optional_param('q', 0, PARAM_INT); // quiz ID
if ($id) {
if (! $cm = get_coursemodule_from_id('quiz', $id)) {
}
}
- // Check login and get context.
+/// Check login and get context.
require_login($course->id, false, $cm);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
+ require_capability('mod/quiz:view', $context);
+
+/// Cache some other capabilites we use several times.
+ $canattempt = has_capability('mod/quiz:attempt', $context);
+ $canpreview = has_capability('mod/quiz:preview', $context);
- // if no questions have been set up yet redirect to edit.php
- if (!$quiz->questions and has_capability('mod/quiz:manage', $context)) {
+/// Create an object to manage all the other (non-roles) access rules.
+ $timenow = time();
+ $accessmanager = new quiz_access_manager($quiz, $timenow,
+ has_capability('mod/quiz:ignoretimelimits', $context, NULL, false));
+
+/// If no questions have been set up yet redirect to edit.php
+ if (!$quiz->questions && has_capability('mod/quiz:manage', $context)) {
redirect('edit.php?cmid='.$cm->id);
}
+/// Log this request.
add_to_log($course->id, "quiz", "view", "view.php?id=$cm->id", $quiz->id, $cm->id);
- // Initialize $PAGE, compute blocks
+/// Initialize $PAGE, compute blocks
$PAGE = page_create_instance($quiz->id);
$pageblocks = blocks_setup($PAGE);
$blocks_preferred_width = bounded_number(180, blocks_preferred_width($pageblocks[BLOCK_POS_LEFT]), 210);
- // Print the page header
- if ($edit != -1 and $PAGE->user_allowed_editing()) {
+ $edit = optional_param('edit', -1, PARAM_BOOL);
+ if ($edit != -1 && $PAGE->user_allowed_editing()) {
$USER->editing = $edit;
}
- //only check pop ups if the user is not a teacher, and popup is set
-
- $bodytags = (has_capability('mod/quiz:attempt', $context) && $quiz->popup)?'onload="popupchecker(\'' . get_string('popupblockerwarning', 'quiz') . '\');"':'';
+/// Print the page header
+ $bodytags = '';
+ if ($accessmanager->securewindow_required($canpreview)) {
+ $bodytags = 'onload="popupchecker(\'' . get_string('popupblockerwarning', 'quiz') . '\');"';
+ }
+ require_js('yui_yahoo');
+ require_js('yui_event');
$PAGE->print_header($course->shortname.': %fullname%','',$bodytags);
+/// Print any blocks on the left of the page.
echo '<table id="layout-table"><tr>';
-
if(!empty($CFG->showblocksonmodpages) && (blocks_have_content($pageblocks, BLOCK_POS_LEFT) || $PAGE->user_is_editing())) {
echo '<td style="width: '.$blocks_preferred_width.'px;" id="left-column">';
print_container_start();
blocks_print_group($PAGE, $pageblocks, BLOCK_POS_LEFT);
print_container_end();
- echo '</td>';
+ echo "</td>\n";
}
+/// Start the main part of the page
echo '<td id="middle-column">';
print_container_start();
- // Print the main part of the page
-
- // Print heading and tabs (if there is more than one).
+/// Print heading and tabs (if there is more than one).
$currenttab = 'info';
include('tabs.php');
- // Print quiz name
-
+/// Print quiz name and description
print_heading(format_string($quiz->name));
+ if (trim(strip_tags($quiz->intro))) {
+ $formatoptions->noclean = true;
+ print_box(format_text($quiz->intro, FORMAT_MOODLE, $formatoptions), 'generalbox', 'intro');
+ }
- if (has_capability('mod/quiz:view', $context)) {
-
- // Print quiz description
- if (trim(strip_tags($quiz->intro))) {
- $formatoptions->noclean = true;
- print_box(format_text($quiz->intro, FORMAT_MOODLE, $formatoptions), 'generalbox', 'intro');
- }
-
- echo '<div class="quizinfo">';
-
- // Print information about number of attempts and grading method.
- if ($quiz->attempts > 1) {
- echo "<p>".get_string("attemptsallowed", "quiz").": $quiz->attempts</p>";
- }
- if ($quiz->attempts != 1) {
- echo "<p>".get_string("grademethod", "quiz").": ".quiz_get_grading_option_name($quiz->grademethod)."</p>";
- }
-
- // Print information about timings.
- $timenow = time();
- $available = ($quiz->timeopen < $timenow and ($timenow < $quiz->timeclose or !$quiz->timeclose));
- if ($available) {
- if ($quiz->timelimit) {
- echo "<p>".get_string("quiztimelimit","quiz", format_time($quiz->timelimit * 60))."</p>";
- }
- if ($quiz->timeopen) {
- echo '<p>', get_string('quizopens', 'quiz'), ': ', userdate($quiz->timeopen), '</p>';
- }
- if ($quiz->timeclose) {
- echo '<p>', get_string('quizcloses', 'quiz'), ': ', userdate($quiz->timeclose), '</p>';
- }
- } else if ($timenow < $quiz->timeopen) {
- echo "<p>".get_string("quiznotavailable", "quiz", userdate($quiz->timeopen))."</p>";
- } else {
- echo "<p>".get_string("quizclosed", "quiz", userdate($quiz->timeclose))."</p>";
- }
- echo '</div>';
- } else {
- $available = false;
+/// Display information about this quiz.
+ $messages = $accessmanager->describe_rules();
+ if ($quiz->attempts != 1) {
+ $messages[] = get_string('gradingmethod', 'quiz', quiz_get_grading_option_name($quiz->grademethod));
}
+ print_box_start('quizinfo');
+ $accessmanager->print_messages($messages);
+ print_box_end();
- // Show number of attempts summary to those who can view reports.
+/// Show number of attempts summary to those who can view reports.
if (has_capability('mod/quiz:viewreports', $context)) {
if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm)) {
echo '<div class="quizattemptcounts"><a href="report.php?mode=overview&id=' .
- $cm->id . '">' . $strattemptnum . '</a></div>';
+ $cm->id . '">' . $strattemptnum . "</a></div>\n";
}
}
- // Guests can't do a quiz, so offer them a choice of logging in or going back.
+/// Guests can't do a quiz, so offer them a choice of logging in or going back.
if (isguestuser()) {
$loginurl = $CFG->wwwroot.'/login/index.php';
if (!empty($CFG->loginhttps)) {
$loginurl = str_replace('http:','https:', $loginurl);
}
- notice_yesno('<p>' . get_string('guestsno', 'quiz') . "</p>\n\n</p>" .
- get_string('liketologin') . '</p>', $loginurl, get_referer(false));
+ notice_yesno('<p>' . get_string('guestsno', 'quiz') . "</p>\n\n<p>" .
+ get_string('liketologin') . "</p>\n", $loginurl, get_referer(false));
finish_page($course);
}
- if (!(has_capability('mod/quiz:attempt', $context) || has_capability('mod/quiz:preview', $context))) {
- print_box('<p>' . get_string('youneedtoenrol', 'quiz') . '</p><p>' .
+/// If they are not using guest access, and they can't do the quiz, tell them that.
+ if (!($canattempt || $canpreview)) {
+ print_box('<p>' . get_string('youneedtoenrol', 'quiz') . "</p>\n\n<p>" .
print_continue($CFG->wwwroot . '/course/view.php?id=' . $course->id, true) .
- '</p>', 'generalbox', 'notice');
+ "</p>\n", 'generalbox', 'notice');
finish_page($course);
}
- // Get this user's attempts.
+/// Get this user's attempts.
$attempts = quiz_get_user_attempts($quiz->id, $USER->id);
+ $lastfinishedattempt = end($attempts);
$unfinished = false;
if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
$attempts[] = $unfinishedattempt;
}
$numattempts = count($attempts);
- // Work out the final grade, checking whether it was overridden in the gradebook.
+/// Work out the final grade, checking whether it was overridden in the gradebook.
$mygrade = quiz_get_best_grade($quiz, $USER->id);
$mygradeoverridden = false;
$gradebookfeedback = '';
}
}
- // Print table with existing attempts
+/// Print table with existing attempts
if ($attempts) {
print_heading(get_string('summaryofattempts', 'quiz'));
$feedbackcolumn = quiz_has_feedback($quiz->id);
$overallfeedback = $feedbackcolumn && $alloptions->overallfeedback;
- // prepare table header
+ // Prepare table header
$table->class = 'generaltable quizattemptsummary';
$table->head = array($strattempt, $strtimecompleted);
$table->align = array("center", "left");
// Add the attempt number, making it a link, if appropriate.
if ($attempt->preview) {
- $row[] = make_review_link(get_string('preview', 'quiz'), $quiz, $attempt);
+ $row[] = $accessmanager->make_review_link(get_string('preview', 'quiz'), $attempt, $canpreview, $attemptoptions);
} else {
- $row[] = make_review_link($attempt->attempt, $quiz, $attempt);
+ $row[] = $accessmanager->make_review_link($attempt->attempt, $attempt, $canpreview, $attemptoptions);
}
// prepare strings for time taken and date completed
// attempt has finished
$timetaken = format_time($attempt->timefinish - $attempt->timestart);
$datecompleted = userdate($attempt->timefinish);
- } else if ($available) {
+ } else if (!$quiz->timeclose || $timenow < $quiz->timeclose) {
// The attempt is still in progress.
- $timetaken = format_time(time() - $attempt->timestart);
+ $timetaken = format_time($timenow - $attempt->timestart);
$datecompleted = '';
- } else if ($quiz->timeclose) {
- // The attempt was not completed but is also not available any more becuase the quiz is closed.
+ } else {
$timetaken = format_time($quiz->timeclose - $attempt->timestart);
$datecompleted = userdate($quiz->timeclose);
- } else {
- // Something weird happened.
- $timetaken = '';
- $datecompleted = '';
}
$row[] = $datecompleted;
if ($markcolumn && $attempt->timefinish > 0) {
if ($attemptoptions->scores) {
- $row[] = make_review_link(round($attempt->sumgrades, $quiz->decimalpoints), $quiz, $attempt);
+ $row[] = $accessmanager->make_review_link(round($attempt->sumgrades, $quiz->decimalpoints),
+ $attempt, $canpreview, $attemptoptions);
} else {
$row[] = '';
}
if ($attemptoptions->scores && $attempt->timefinish > 0) {
$formattedgrade = $attemptgrade;
// highlight the highest grade if appropriate
- if ($overallstats && $numattempts > 1 && !is_null($mygrade) && $attemptgrade == $mygrade && $quiz->grademethod == QUIZ_GRADEHIGHEST) {
+ if ($overallstats && !$attempt->preview && $numattempts > 1 && !is_null($mygrade) &&
+ $attemptgrade == $mygrade && $quiz->grademethod == QUIZ_GRADEHIGHEST) {
$table->rowclass[$attempt->attempt] = 'bestrow';
}
- $row[] = make_review_link($formattedgrade, $quiz, $attempt);
+ $row[] = $accessmanager->make_review_link($formattedgrade, $attempt, $canpreview, $attemptoptions);
} else {
$row[] = '';
}
$row[] = $timetaken;
}
- $table->data[$attempt->attempt] = $row;
+ if ($attempt->preview) {
+ $table->data['preview'] = $row;
+ } else {
+ $table->data[$attempt->attempt] = $row;
+ }
} // End of loop over attempts.
print_table($table);
}
- // Print information about the student's best score for this quiz if possible.
- $moreattempts = $unfinished || $numattempts < $quiz->attempts || $quiz->attempts == 0;
+/// Print information about the student's best score for this quiz if possible.
+ $moreattempts = $unfinished || !$accessmanager->is_finished($numattempts, $lastfinishedattempt);
if (!$moreattempts) {
print_heading(get_string("nomoreattempts", "quiz"));
}
$resultinfo = '';
if ($overallstats) {
- if ($available && $moreattempts) {
+ if ($moreattempts) {
$a = new stdClass;
$a->method = quiz_get_grading_option_name($quiz->grademethod);
$a->mygrade = $mygrade;
} else {
$resultinfo .= print_heading(get_string('yourfinalgradeis', 'quiz', "$mygrade / $quiz->grade"), '', 2, 'main', true);
if ($mygradeoverridden) {
- $resultinfo .= '<p class="overriddennotice">'.get_string('overriddennotice', 'grades').'</p>';
+ $resultinfo .= '<p class="overriddennotice">'.get_string('overriddennotice', 'grades')."</p>\n";
}
}
}
if ($gradebookfeedback) {
$resultinfo .= print_heading(get_string('comment', 'quiz'), '', 3, 'main', true);
- $resultinfo .= '<p class="quizteacherfeedback">'.$gradebookfeedback.'</p>';
+ $resultinfo .= '<p class="quizteacherfeedback">'.$gradebookfeedback."</p>\n";
}
if ($overallfeedback) {
$resultinfo .= print_heading(get_string('overallfeedback', 'quiz'), '', 3, 'main', true);
- $resultinfo .= '<p class="quizgradefeedback">'.quiz_feedback_for_grade($mygrade, $quiz->id).'</p>';
+ $resultinfo .= '<p class="quizgradefeedback">'.quiz_feedback_for_grade($mygrade, $quiz->id)."</p>\n";
}
if ($resultinfo) {
}
}
- // Print a button to start/continue an attempt, if appropriate.
+/// Determine if we should be showing a start/continue attempt button,
+/// or a button to go back to the course page.
+ print_box_start('quizattempt');
+ $buttontext = ''; // This will be set something if as start/continue attempt button should appear.
if (!$quiz->questions) {
print_heading(get_string("noquestions", "quiz"));
-
- } else if ($available && $moreattempts) {
- echo "<br />";
- echo "<div class=\"quizattempt\">";
-
+ } else {
if ($unfinished) {
- if (has_capability('mod/quiz:preview', $context)) {
+ if ($canpreview) {
$buttontext = get_string('continuepreview', 'quiz');
} else {
$buttontext = get_string('continueattemptquiz', 'quiz');
}
} else {
-
- // Work out the appropriate button caption.
- if (has_capability('mod/quiz:preview', $context)) {
- $buttontext = get_string('previewquiznow', 'quiz');
- } else if ($numattempts == 0) {
- $buttontext = get_string('attemptquiznow', 'quiz');
+ $messages = $accessmanager->prevent_new_attempt($numattempts, $lastfinishedattempt);
+ if (!$canpreview && $messages) {
+ $accessmanager->print_messages($messages);
} else {
- $buttontext = get_string('reattemptquiz', 'quiz');
- }
-
- // Work out if the quiz is temporarily unavailable because of the delay option.
- if (!empty($attempts)) {
- $tempunavailable = '';
- $lastattempt = end($attempts);
- $lastattempttime = $lastattempt->timefinish;
- if ($numattempts == 1 && $quiz->delay1 && $timenow <= $lastattempttime + $quiz->delay1) {
- $tempunavailable = get_string('temporaryblocked', 'quiz') .
- ' <strong>'. userdate($lastattempttime + $quiz->delay1). '</strong>';
- } else if ($numattempts > 1 && $quiz->delay2 && $timenow <= $lastattempttime + $quiz->delay2) {
- $tempunavailable = get_string('temporaryblocked', 'quiz') .
- ' <strong>'. userdate($lastattempttime + $quiz->delay2). '</strong>';
- }
-
- // If so, display a message and prevent the start button from appearing.
- if ($tempunavailable) {
- print_simple_box($tempunavailable, "center");
- print_continue($CFG->wwwroot . '/course/view.php?id=' . $course->id);
- $buttontext = '';
+ if ($canpreview) {
+ $buttontext = get_string('previewquiznow', 'quiz');
+ } else if ($numattempts == 0) {
+ $buttontext = get_string('attemptquiznow', 'quiz');
+ } else {
+ $buttontext = get_string('reattemptquiz', 'quiz');
}
}
}
- // Actually print the start button.
+ // If, so far, we think a button should be printed, so check if they will be allowed to access it.
if ($buttontext) {
- $buttontext = htmlspecialchars($buttontext, ENT_QUOTES);
-
- // Do we need a confirm javascript alert?
- if ($unfinished) {
- $strconfirmstartattempt = '';
- } else if ($quiz->timelimit && $quiz->attempts) {
- $strconfirmstartattempt = get_string('confirmstartattempttimelimit','quiz', $quiz->attempts);
- } else if ($quiz->timelimit) {
- $strconfirmstartattempt = get_string('confirmstarttimelimit','quiz');
- } else if ($quiz->attempts) {
- $strconfirmstartattempt = get_string('confirmstartattemptlimit','quiz', $quiz->attempts);
- } else {
- $strconfirmstartattempt = '';
+ if (!$moreattempts) {
+ $buttontext = '';
+ } else if (!$canpreview && $messages = $accessmanager->prevent_access()) {
+ $accessmanager->print_messages($messages);
+ $buttontext = '';
}
- // Determine the URL to use.
- $attempturl = "attempt.php?id=$cm->id";
-
- // Prepare options depending on whether the quiz should be a popup.
- if (!empty($quiz->popup)) {
- $window = 'quizpopup';
- $windowoptions = "left=0, top=0, height='+window.screen.height+', " .
- "width='+window.screen.width+', channelmode=yes, fullscreen=yes, " .
- "scrollbars=yes, resizeable=no, directories=no, toolbar=no, " .
- "titlebar=no, location=no, status=no, menubar=no";
- if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) {
- $attempturl = sid_process_url($attempturl);
- }
-
- echo '<input type="button" value="'.$buttontext.'" onclick="javascript:';
- if ($strconfirmstartattempt) {
- $strconfirmstartattempt = addslashes($strconfirmstartattempt);
- echo "if (confirm('".addslashes_js($strconfirmstartattempt)."')) ";
- }
- echo "window.open('$attempturl','$window','$windowoptions');", '" />';
- } else {
- print_single_button("attempt.php", array('id'=>$cm->id), $buttontext, 'get', '', false, '', false, $strconfirmstartattempt);
- }
-
-
-?>
-<noscript>
-<div>
- <?php print_heading(get_string('noscript', 'quiz')); ?>
-</div>
-</noscript>
-<?php
}
+ }
- echo "</div>\n";
+/// Now actually print the appropriate button.
+ if ($buttontext) {
+ $accessmanager->print_start_attempt_button($canpreview, $buttontext, $unfinished);
} else {
print_continue($CFG->wwwroot . '/course/view.php?id=' . $course->id);
}
+ print_box_end();
// Should we not be seeing if we need to print right-hand-side blocks?
print_footer($course);
exit;
}
-
-/** Make some text into a link to review the quiz, if that is appropriate. */
-function make_review_link($linktext, $quiz, $attempt) {
- // If not even responses are to be shown in review then we don't allow any review
- if (!($quiz->review & QUIZ_REVIEW_RESPONSES)) {
- return $linktext;
- }
-
- // If the quiz is still open, are reviews allowed?
- if ((!$quiz->timeclose or time() < $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_OPEN)) {
- // If not, don't link.
- return $linktext;
- }
-
- // If the quiz is closed, are reviews allowed?
- if (($quiz->timeclose and time() > $quiz->timeclose) and !($quiz->review & QUIZ_REVIEW_CLOSED)) {
- // If not, don't link.
- return $linktext;
- }
-
- // If the attempt is still open, don't link.
- if (!$attempt->timefinish) {
- return $linktext;
- }
-
- $url = "review.php?q=$quiz->id&attempt=$attempt->id";
- if ($quiz->popup) {
- $windowoptions = "left=0, top=0, channelmode=yes, fullscreen=yes, scrollbars=yes, resizeable=no, directories=no, toolbar=no, titlebar=no, location=no, status=no, menubar=no";
- return link_to_popup_window('/mod/quiz/' . $url, 'quizpopup', $linktext, '+window.screen.height+', '+window.screen.width+', '', $windowoptions, true);
- } else {
- return "<a href='$url'>$linktext</a>";
- }
-}
?>
#mod-quiz-view .generalbox#feedback h2 {
margin: 0 0;
}
-body#mod-quiz-view .generalbox#feedback .overriddennotice {
+#mod-quiz-view .generalbox#feedback .overriddennotice {
text-align: center;
font-size: 0.7em;
}
text-align: center;
}
-#mod-quiz-attempt #timer .generalbox {
- width:150px
+#mod-quiz-attempt #quiz-timer-outer {
+ position: absolute;
+ width: 150px;
+ top: 100px;
+ left: 10px;
+ padding: 0.25em 0;
+ border-width: 1px;
+ border-style: solid;
}
-
-#mod-quiz-attempt #timer {
- position:absolute;
- /*top:100px; is set by js*/
- left:10px
+#mod-quiz-attempt #quiz-timer-outer h3,
+#mod-quiz-attempt #quiz-timer-outer p {
+ margin: 0;
}
body#question-preview .quemodname,
}
#mod-quiz-attempt #page .controls,
-#mod-quiz-review #page .controls {
+#mod-quiz-review #page .controls,
+#mod-quiz-review #page .finishreview {
text-align: center;
margin: 8px auto;
}