]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-13806 - Refactor all the code that implements the rules for whether students...
authortjhunt <tjhunt>
Fri, 7 Mar 2008 12:33:07 +0000 (12:33 +0000)
committertjhunt <tjhunt>
Fri, 7 Mar 2008 12:33:07 +0000 (12:33 +0000)
Resource page now done as well. That should be everything.

lang/en_utf8/quiz.php
mod/quiz/accessrules.php
mod/quiz/attempt.php
mod/quiz/attempt_close_js.php [deleted file]
mod/quiz/locallib.php
mod/quiz/quiz.js
mod/quiz/review.php
mod/quiz/simpletest/testaccessrules.php
mod/quiz/view.php
theme/standard/styles_layout.css

index 3f75f4f15cda91aabf76436ca0aa2b8f5e44f30c..45ed7b689b67beda872070155b5d2f102086ff29 100644 (file)
@@ -6,6 +6,7 @@ $string['2hours'] = '2 Hours';
 $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';
@@ -57,6 +58,7 @@ $string['attemptlast'] = 'Last attempt';
 $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.';
@@ -77,6 +79,7 @@ $string['calculated'] = 'Calculated';
 $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)';
@@ -267,6 +270,7 @@ $string['gradeboundary'] = 'Grade boundary';
 $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.';
@@ -310,6 +314,7 @@ $string['learnwise'] = 'Learnwise format';
 $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';
@@ -365,7 +370,7 @@ $string['noresponse'] = 'No Response';
 $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?';
@@ -434,9 +439,10 @@ $string['quizclose'] = 'Close the quiz';
 $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';
@@ -491,6 +497,7 @@ $string['reviewbefore'] = 'Allow review while quiz is open';
 $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';
@@ -501,6 +508,7 @@ $string['rqp'] = 'Remote Question';
 $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';
@@ -547,6 +555,7 @@ $string['startedon'] = 'Started on';
 $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:';
@@ -587,6 +596,7 @@ $string['warningmissingtype'] = '<b>This question is of a type that has not been
 $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';
@@ -597,6 +607,8 @@ $string['xml'] = 'Moodle XML format';
 $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!';
index 5069d0c6945845d38b82835fb6871f6c009dc2c8..2a2ea4e21ffbbcbc8e3e1a4f607d6ad7c5f69b23 100644 (file)
@@ -14,7 +14,7 @@ class quiz_access_manager {
      * 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) {
@@ -60,7 +60,7 @@ class quiz_access_manager {
      *
      * @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.
      */
@@ -79,7 +79,7 @@ class quiz_access_manager {
     /**
      * 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 &lt;p> tags.
@@ -145,16 +145,14 @@ class quiz_access_manager {
         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) {
@@ -166,7 +164,7 @@ class quiz_access_manager {
             }
         }
         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');
@@ -187,11 +185,99 @@ class quiz_access_manager {
     }
 
     /**
-     * @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";
     }
 
     /**
@@ -213,10 +299,12 @@ class quiz_access_manager {
     /**
      * 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);
         }
     }
 
@@ -240,44 +328,41 @@ class quiz_access_manager {
      *
      * @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 .
                     '&amp;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');
+        }
+    }
 }
 
 /**
@@ -328,7 +413,7 @@ abstract class quiz_access_rule_base {
     }
     /**
      * 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.
@@ -486,21 +571,23 @@ class password_access_rule extends quiz_access_rule_base {
      * 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.
@@ -580,15 +667,14 @@ class securewindow_access_rule extends quiz_access_rule_base {
      * 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);
         }
@@ -612,5 +698,24 @@ class securewindow_access_rule extends quiz_access_rule_base {
         return link_to_popup_window($CFG->wwwroot . '/mod/quiz/review.php?q=' . $this->_quiz->id .
                '&amp;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
index 56c16f01ec7e0921f08ff4dd69bff322cd48ef33..bbd185e1accbe0e93eb10478241acb6692dbc9c9 100644 (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);
     }
 ?>
diff --git a/mod/quiz/attempt_close_js.php b/mod/quiz/attempt_close_js.php
deleted file mode 100644 (file)
index 3ba2d5b..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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>
index aaf50d8906f0c3b1a56f74a5e1bd53a8ed250dfe..a48c4fcf8b5c554e784c3745937a64bd7783d020 100644 (file)
@@ -20,6 +20,7 @@
  * 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 ///////////////////////////////////////////////////////////////////
@@ -37,13 +38,25 @@ define("QUIZ_ATTEMPTLAST",  "4");
 /**#@+
  * 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 /////////////////////////////////////////
 
 /**
@@ -52,15 +65,22 @@ define('QUIZ_STATE_TEACHERACCESS', 'teacheraccess'); // State only relevant if y
  * 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;
@@ -69,9 +89,14 @@ function quiz_create_attempt($quiz, $attemptnumber) {
         } 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;
@@ -79,12 +104,17 @@ function quiz_create_attempt($quiz, $attemptnumber) {
     $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.
@@ -100,6 +130,41 @@ function quiz_get_user_attempt_unfinished($quizid, $userid) {
     }
 }
 
+/**
+ * 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.
  */
@@ -698,12 +763,12 @@ function quiz_get_reviewoptions($quiz, $attempt, $context=null) {
         $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;
@@ -723,7 +788,7 @@ function quiz_get_reviewoptions($quiz, $attempt, $context=null) {
             $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;
@@ -769,6 +834,14 @@ function quiz_get_combined_reviewoptions($quiz, $attempts, $context=null) {
     return array($someoptions, $alloptions);
 }
 
+function print_restart_preview_button($quiz) {
+    global $CFG;
+    echo '<div class="controls">';
+    print_single_button($CFG->wwwroot . '/mod/quiz/attempt.php',
+            array('q' => $quiz->id, 'forcenew' => true), get_string('startagain', 'quiz'));
+    echo '</div>';
+}
+
 /// FUNCTIONS FOR SENDING NOTIFICATION EMAILS ///////////////////////////////
 
 /**
@@ -938,4 +1011,19 @@ function quiz_send_notification_emails($course, $quiz, $attempt, $context, $cm)
     // 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);
+}
 ?>
index a39c1e935fcecafaf396ded91c9be4d8a0e6365a..37ce19afa4a15499c52df81f45244666c27b39de 100644 (file)
@@ -16,7 +16,7 @@ function navigate(page) {
     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;
@@ -28,65 +28,218 @@ function check_enter(e) {
         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
index 0ca3c71539c1a88e03975ccfc77ded4c5da0fa0a..15a9f15f95e7fdd094cdb53763f42ec30bedf428 100644 (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&amp;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);
     }
 ?>
index 018a6f21863f9b7ba3607c1cb514251b0c35bd54..efe3cf75c307196cd49dc5c714b1616f18942527 100644 (file)
@@ -356,25 +356,25 @@ class password_access_rule_test extends UnitTestCase {
         $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);
     }
 }
index 27e7b65407810751b1cacd0c43564ed9faf1f4a8..13a33fb879757687088b7744fc9fb3640d3aa0f1 100644 (file)
@@ -1,6 +1,6 @@
 <?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');
@@ -8,9 +8,8 @@
     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&amp;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?
 
@@ -459,37 +391,4 @@ function finish_page($course) {
     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&amp;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>";
-    }
-}
 ?>
index d3eb49e1e5a63c578af5d5c1e557b292a9bc519d..28479819c736a1def3c54ecce3f20297c17496ee 100644 (file)
@@ -3769,7 +3769,7 @@ body#mod-forum-search .introcontent {
 #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;
 }
@@ -3795,14 +3795,18 @@ body#mod-quiz-view .generalbox#feedback .overriddennotice {
     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,
@@ -3815,7 +3819,8 @@ body#question-preview .quemodname, body#question-preview .controls {
 }
 
 #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;
 }