--- /dev/null
+<?php
+/**
+ * This class keeps track of the various access rules that apply to a particular
+ * quiz, with convinient methods for seeing whether access is allowed.
+ */
+class quiz_access_manager {
+ private $_quiz;
+ private $_timenow;
+ private $_passwordrule = null;
+ private $_securewindowrule = null;
+ private $_rules = array();
+
+ /**
+ * 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 $ignoretimelimits
+ */
+ public function __construct($quiz, $timenow, $canignoretimelimits) {
+ $this->_quiz = $quiz;
+ $this->_timenow = $timenow;
+ $this->create_standard_rules($canignoretimelimits);
+ }
+
+ private function create_standard_rules($canignoretimelimits) {
+ if ($this->_quiz->attempts > 0) {
+ $this->_rules[] = new num_attempts_access_rule($this->_quiz, $this->_timenow);
+ }
+ $this->_rules[] = new open_close_date_access_rule($this->_quiz, $this->_timenow);
+ if ($this->_quiz->timelimit && !$canignoretimelimits) {
+ $this->_rules[] = new time_limit_access_rule($this->_quiz, $this->_timenow);
+ }
+ if ($this->_quiz->delay1 || $this->_quiz->delay2) {
+ $this->_rules[] = new inter_attempt_delay_access_rule($this->_quiz, $this->_timenow);
+ }
+ if ($this->_quiz->subnet) {
+ $this->_rules[] = new ipaddress_access_rule($this->_quiz, $this->_timenow);
+ }
+ if ($this->_quiz->password) {
+ $this->_passwordrule = new password_access_rule($this->_quiz, $this->_timenow);
+ $this->_rules[] = $this->_passwordrule;
+ }
+ if ($this->_quiz->popup) {
+ $this->_securewindowrule = new securewindow_access_rule($this->_quiz, $this->_timenow);
+ $this->_rules[] = $this->_securewindowrule;
+ }
+ }
+
+ private function accumulate_messages(&$messages, $new) {
+ if (is_array($new)) {
+ $messages = array_merge($messages, $new);
+ } else if (is_string($new) && $new) {
+ $messages[] = $new;
+ }
+ }
+
+ /**
+ * Print each message in an array, each surrounded by <p>, </p> tags.
+ *
+ * @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.
+ */
+ public function print_messages($messages, $return=false) {
+ $output = '';
+ foreach ($messages as $message) {
+ $output .= '<p>' . $message . "</p>\n";
+ }
+ if ($return) {
+ return $output;
+ } else {
+ echo $output;
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @return array an array of description messages which may be empty. It
+ * would be sensible to output each one surrounded by <p> tags.
+ */
+ public function describe_rules() {
+ $result = array();
+ foreach ($this->_rules as $rule) {
+ $this->accumulate_messages($result, $rule->description());
+ }
+ return $result;
+ }
+
+ /**
+ * Is it OK to let the current user start a new attempt now? If there are
+ * any restrictions in force now, return an array of reasons why access
+ * should be blocked. If access is OK, return false.
+ *
+ * @param integer $numattempts the number of previous attempts this user has made.
+ * @param object $lastattempt information about the user's last completed attempt.
+ * @return mixed An array of reason why access is not allowed, or an empty array
+ * (== false) if access should be allowed.
+ */
+ public function prevent_new_attempt($numprevattempts, $lastattempt) {
+ $reasons = array();
+ foreach ($this->_rules as $rule) {
+ $this->accumulate_messages($reasons,
+ $rule->prevent_new_attempt($numprevattempts, $lastattempt));
+ }
+ return $reasons;
+ }
+
+ /**
+ * Is it OK to let the current user start a new attempt now? If there are
+ * any restrictions in force now, return an array of reasons why access
+ * should be blocked. If access is OK, return false.
+ *
+ * @return mixed An array of reason why access is not allowed, or an empty array
+ * (== false) if access should be allowed.
+ */
+ public function prevent_access() {
+ $reasons = array();
+ foreach ($this->_rules as $rule) {
+ $this->accumulate_messages($reasons, $rule->prevent_access());
+ }
+ return $reasons;
+ }
+
+ /**
+ * Do any of the rules mean that this student will no be allowed any further attempts at this
+ * quiz. Used, for example, to change the label by the grade displayed on the view page from
+ * 'your current score is' to 'your final score is'.
+ *
+ * @param integer $numattempts the number of previous attempts this user has made.
+ * @param object $lastattempt information about the user's last completed attempt.
+ * @return boolean true if there is no way the user will ever be allowed to attempt this quiz again.
+ */
+ public function is_finished($numprevattempts, $lastattempt) {
+ foreach ($this->_rules as $rule) {
+ if ($rule->is_finished($numprevattempts, $lastattempt)) {
+ return true;
+ }
+ }
+ 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";
+ }
+
+ public function show_attempt_timer_if_needed($attempt, $timenow) {
+ $timeleft = false;
+ foreach ($this->_rules as $rule) {
+ $ruletimeleft = $rule->time_left($attempt, $timenow);
+ if ($ruletimeleft !== false && ($timeleft === false || $ruletimeleft < $timeleft)) {
+ $timeleft = $ruletimeleft;
+ }
+ }
+ if ($timeleft !== false) {
+ /// 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');
+ print_heading(get_string('timeleft', 'quiz'), '', 3);
+ echo '<p id="quiz-timer-display"></p>';
+ print_box_end();
+ echo "\n\n", '<script type="text/javascript">';
+ echo "quiz_timer.initialise('", get_string('timesup','quiz'), "', ", $timerstartvalue, ");";
+ echo "</script>\n";
+ }
+ }
+
+ /**
+ * @return bolean if this quiz should only be shown to students in a secure window.
+ */
+ public function securewindow_required($canpreview) {
+ return !$canpreview && !is_null($this->_securewindowrule);
+ }
+
+ /**
+ * @return object the securewindow_access_rule instance for this quiz,
+ * or null if securewindow_required returns false.
+ */
+ public function get_securewindow_object() {
+ return $this->_securewindowrule;
+ }
+
+ /**
+ * @return bolean if this quiz is password protected.
+ */
+ public function password_required() {
+ return !is_null($this->_passwordrule);
+ }
+
+ /**
+ * Clear the flag in the session that says that the current user is allowed to do this quiz.
+ */
+ public function clear_password_access() {
+ if (!is_null($this->_passwordrule)) {
+ $this->_passwordrule->clear_access_allowed();
+ }
+ }
+
+ /**
+ * Actually ask the user for the password, if they have not already given it this session.
+ * This function only returns is access is OK.
+ */
+ public function do_password_check() {
+ if (!is_null($this->_passwordrule)) {
+ $this->_passwordrule->do_password_check();
+ }
+ }
+
+ /**
+ * @return string if the quiz policies merit it, return a warning string to be displayed
+ * in a javascript alert on the start attempt button.
+ */
+ public function confirm_start_attempt_message() {
+ if ($this->_quiz->timelimit && $this->_quiz->attempts) {
+ return get_string('confirmstartattempttimelimit','quiz', $this->_quiz->attempts);
+ } else if ($this->_quiz->timelimit) {
+ return get_string('confirmstarttimelimit','quiz');
+ } else if ($this->_quiz->attempts) {
+ return get_string('confirmstartattemptlimit','quiz', $this->_quiz->attempts);
+ }
+ return '';
+ }
+
+ /**
+ * Make some text into a link to review the quiz, if that is appropriate.
+ *
+ * @param string $linktext some text.
+ * @param object $attempt the attempt object
+ * @return string some HTML, the $linktext either unmodified or wrapped in
+ */
+ public function make_review_link($linktext, $attempt) {
+ 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.
+ 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);
+ } else {
+ return '<a href="' . $CFG->wwwroot . '/mod/quiz/review.php?q=' . $this->_quiz->id .
+ '&attempt=' . $attempt->id . '">' . $linktext . '</a>';
+ }
+ }
+}
+
+/**
+ * A base class that defines the interface for the various quiz access rules.
+ * Most of the methods are defined in a slightly unnatural way because we either
+ * want to say that access is allowed, or explain the reason why it is block.
+ * Therefore instead of is_access_allowed(...) we have prevent_access(...) that
+ * return false if access is permitted, or a string explanation (which is treated
+ * as true) if access should be blocked. Slighly unnatural, but acutally the easist
+ * way to implement this.
+ */
+abstract class quiz_access_rule_base {
+ protected $_quiz;
+ protected $_timenow;
+ /**
+ * Create an instance of this rule for a particular quiz.
+ * @param object $quiz the quiz we will be controlling access to.
+ */
+ public function __construct($quiz, $timenow) {
+ $this->_quiz = $quiz;
+ $this->_timenow = $timenow;
+ }
+ /**
+ * Whether or not a user should be allowed to start a new attempt at this quiz now.
+ * @param integer $numattempts the number of previous attempts this user has made.
+ * @param object $lastattempt information about the user's last completed attempt.
+ * @return string false if access should be allowed, a message explaining the reason if access should be prevented.
+ */
+ public function prevent_new_attempt($numprevattempts, $lastattempt) {
+ return false;
+ }
+ /**
+ * Whether or not a user should be allowed to start a new attempt at this quiz now.
+ * @return string false if access should be allowed, a message explaining the reason if access should be prevented.
+ */
+ public function prevent_access() {
+ return false;
+ }
+ /**
+ * Information, such as might be shown on the quiz view page, relating to this restriction.
+ * There is no obligation to return anything. If it is not appropriate to tell students
+ * about this rule, then just return ''.
+ * @return mixed a message, or array of messages, explaining the restriction
+ * (may be '' if no message is appropriate).
+ */
+ public function description() {
+ return '';
+ }
+ /**
+ * 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
+ * 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.
+ * @param object $lastattempt information about the user's last completed attempt.
+ * @return boolean true if this rule means that this user will never be allowed another
+ * attempt at this quiz.
+ */
+ public function is_finished($numprevattempts, $lastattempt) {
+ return false;
+ }
+
+ /**
+ * If, becuase of this rule, the user has to finish their attempt by a certain time,
+ * you should override this method to return the amount of time left in seconds.
+ * @param object $attempt the current attempt
+ * @param integer $timenow the time now. We don't use $this->_timenow, so we can
+ * give the user a more accurate indication of how much time is left.
+ * @return mixed false if there is no deadline, of the time left in seconds if there is one.
+ */
+ public function time_left($attempt, $timenow) {
+ return false;
+ }
+}
+
+/**
+ * A rule controlling the number of attempts allowed.
+ */
+class num_attempts_access_rule extends quiz_access_rule_base {
+ public function description() {
+ return get_string('attemptsallowedn', 'quiz', $this->_quiz->attempts);
+ }
+ public function prevent_new_attempt($numprevattempts, $lastattempt) {
+ if ($numprevattempts >= $this->_quiz->attempts) {
+ return get_string('nomoreattempts', 'quiz');
+ }
+ return false;
+ }
+ public function is_finished($numprevattempts, $lastattempt) {
+ return $numprevattempts >= $this->_quiz->attempts;
+ }
+}
+
+/**
+ * A rule enforcing open and close dates.
+ */
+class open_close_date_access_rule extends quiz_access_rule_base {
+ public function description() {
+ $result = array();
+ if ($this->_timenow < $this->_quiz->timeopen) {
+ $result[] = get_string('quiznotavailable', 'quiz', userdate($this->_quiz->timeopen));
+ } else if ($this->_quiz->timeclose && $this->_timenow > $this->_quiz->timeclose) {
+ $result[] = get_string("quizclosed", "quiz", userdate($this->_quiz->timeclose));
+ } else {
+ if ($this->_quiz->timeopen) {
+ $result[] = get_string('quizopenedon', 'quiz', userdate($this->_quiz->timeopen));
+ }
+ if ($this->_quiz->timeclose) {
+ $result[] = get_string('quizcloseson', 'quiz', userdate($this->_quiz->timeclose));
+ }
+ }
+ return $result;
+ }
+ public function prevent_access() {
+ if ($this->_timenow < $this->_quiz->timeopen ||
+ ($this->_quiz->timeclose && $this->_timenow > $this->_quiz->timeclose)) {
+ return get_string('notavailable', 'quiz');
+ }
+ return false;
+ }
+ public function is_finished($numprevattempts, $lastattempt) {
+ return $this->_quiz->timeclose && $this->_timenow > $this->_quiz->timeclose;
+ }
+ public function time_left($attempt, $timenow) {
+ if ($this->_quiz->timeclose) {
+ $timeleft = $this->_quiz->timeclose - $timenow;
+ if ($timeleft < QUIZ_SHOW_TIME_BEFORE_DEADLINE) {
+ return $timeleft;
+ }
+ }
+ return false;
+ }
+}
+
+/**
+ * A rule imposing the delay between attemtps settings.
+ */
+class inter_attempt_delay_access_rule extends quiz_access_rule_base {
+ public function prevent_new_attempt($numprevattempts, $lastattempt) {
+ if ($this->_quiz->attempts > 0 && $numprevattempts >= $this->_quiz->attempts) {
+ /// No more attempts allowed anyway.
+ return false;
+ }
+ if ($this->_quiz->timeclose != 0 && $this->_timenow > $this->_quiz->timeclose) {
+ /// No more attempts allowed anyway.
+ return false;
+ }
+ $nextstarttime = 0;
+ if ($numprevattempts == 1 && $this->_quiz->delay1) {
+ $nextstarttime = $lastattempt->timefinish + $this->_quiz->delay1;
+ } else if ($numprevattempts > 1 && $this->_quiz->delay2) {
+ $nextstarttime = $lastattempt->timefinish + $this->_quiz->delay2;
+ }
+ if ($this->_timenow < $nextstarttime) {
+ if ($this->_quiz->timeclose == 0 || $nextstarttime <= $this->_quiz->timeclose) {
+ return get_string('youmustwait', 'quiz', userdate($nextstarttime));
+ } else {
+ return get_string('youcannotwait', 'quiz');
+ }
+ }
+ return false;
+ }
+ public function is_finished($numprevattempts, $lastattempt) {
+ $nextstarttime = 0;
+ if ($numprevattempts == 1 && $this->_quiz->delay1) {
+ $nextstarttime = $lastattempt->timefinish + $this->_quiz->delay1;
+ } else if ($numprevattempts > 1 && $this->_quiz->delay2) {
+ $nextstarttime = $lastattempt->timefinish + $this->_quiz->delay2;
+ }
+ return $this->_timenow <= $nextstarttime &&
+ $this->_quiz->timeclose != 0 && $nextstarttime >= $this->_quiz->timeclose;
+ }
+}
+
+/**
+ * A rule implementing the ipaddress check against the ->submet setting.
+ */
+class ipaddress_access_rule extends quiz_access_rule_base {
+ public function prevent_access() {
+ if (address_in_subnet(getremoteaddr(), $this->_quiz->subnet)) {
+ return false;
+ } else {
+ return get_string('subnetwrong', 'quiz');
+ }
+ }
+}
+
+/**
+ * A rule representing the password check. It does not actually implement the check,
+ * that has to be done directly in attempt.php, but this facilitates telling users about it.
+ */
+class password_access_rule extends quiz_access_rule_base {
+ public function description() {
+ return get_string('requirepasswordmessage', 'quiz');
+ }
+ /**
+ * Clear the flag in the session that says that the current user is allowed to do this quiz.
+ */
+ public function clear_access_allowed() {
+ global $SESSION;
+ if (!empty($SESSION->passwordcheckedquizzes[$this->_quiz->id])) {
+ unset($SESSION->passwordcheckedquizzes[$this->_quiz->id]);
+ }
+ }
+ /**
+ * 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
+ * @return mixed return null, unless $return is true, and a form needs to be displayed.
+ */
+ public function do_password_check($return = false) {
+ global $CFG, $SESSION;
+
+ /// 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 (optional_param('cancelpassword', false, PARAM_BOOL)) {
+ redirect($CFG->wwwroot . '/mod/quiz/view.php?q=' . $this->_quiz->id);
+ }
+
+ /// If they entered the right password, let them in.
+ $enteredpassword = optional_param('quizpassword', '', PARAM_RAW);
+ if (strcmp($this->_quiz->password, $enteredpassword) === 0) {
+ $SESSION->passwordcheckedquizzes[$this->_quiz->id] = true;
+ return;
+ }
+
+ /// User entered the wrong password, or has not entered one yet, so display the form.
+ $output = '';
+
+ /// Start the page and print the quiz intro, if any.
+ if (!$return) {
+ print_header('', '', '', 'quizpassword');
+ }
+ if (trim(strip_tags($this->_quiz->intro))) {
+ $formatoptions->noclean = true;
+ $output .= print_box(format_text($this->_quiz->intro, FORMAT_MOODLE, $formatoptions),
+ 'generalbox', 'intro', true);
+ }
+ $output .= print_box_start('generalbox', 'passwordbox', true);
+
+ /// If they have previously tried and failed to enter a password, tell them it was wrong.
+ if (!empty($enteredpassword)) {
+ $output .= '<p class="notifyproblem">' . get_string('passworderror', 'quiz') . '</p>';
+ }
+
+ /// Print the password entry form.
+ $output .= '<p>' . get_string('requirepasswordmessage', 'quiz') . "</p>\n";
+ $output .= '<form id="passwordform" method="post" action="' . $CFG->wwwroot .
+ '/mod/quiz/attempt.php?q=' . $this->_quiz->id .
+ '" onclick="this.autocomplete=\'off\'">' . "\n";
+ $output .= "<div>\n";
+ $output .= '<label for="quizpassword">' . get_string('password') . "</label>\n";
+ $output .= '<input name="quizpassword" id="quizpassword" type="password" value=""/>' . "\n";
+ $output .= '<input type="submit" value="' . get_string('ok') . '" />';
+ $output .= '<input type="submit" name="cancelpassword" value="' .
+ get_string('cancel') . '" />' . "\n";
+ $output .= "</div>\n";
+ $output .= "</form>\n";
+
+ /// Finish page.
+ $output .= print_box_end(true);
+
+ /// return or display form.
+ if ($return) {
+ return $output;
+ } else {
+ echo $output;
+ print_footer('empty');
+ exit;
+ }
+ }
+}
+
+/**
+ * A rule representing the time limit. It does not actually restrict access, but we use this
+ * class to encapsulate some of the relevant code.
+ */
+class time_limit_access_rule extends quiz_access_rule_base {
+ public function description() {
+ return get_string('quiztimelimit', 'quiz', format_time($this->_quiz->timelimit * 60));
+ }
+ public function time_left($attempt, $timenow) {
+ return $attempt->timestart + $this->_quiz->timelimit*60 - $timenow;
+ }
+}
+
+/**
+ * A rule implementing the ipaddress check against the ->submet setting.
+ */
+class securewindow_access_rule extends quiz_access_rule_base {
+ private $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";
+
+ /**
+ * 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) {
+ global $CFG;
+ $attempturl = $CFG->wwwroot . '/mod/quiz/attempt.php?id=' . $cmid;
+ $window = 'quizpopup';
+
+ if (!empty($CFG->usesid) && !isset($_COOKIE[session_name()])) {
+ $attempturl = sid_process_url($attempturl);
+ }
+
+ echo '<input type="button" value="' . s($buttontext) . '" onclick="javascript:';
+ if ($strconfirmstartattempt) {
+ echo "if (confirm('" . addslashes_js($strconfirmstartattempt) . "')) ";
+ }
+ echo "window.open('$attempturl', '$window', '$this->windowoptions');", '" />';
+ }
+
+ /**
+ * Make a link to the review page for an attempt.
+ *
+ * @param string $linktext the desired link text.
+ * @param integer $attemptid the attempt id.
+ * @return string HTML for the link.
+ */
+ public function make_review_link($linktext, $attemptid) {
+ global $CFG;
+ return link_to_popup_window($CFG->wwwroot . '/mod/quiz/review.php?q=' . $this->_quiz->id .
+ '&attempt=' . $attemptid, 'quizpopup', $linktext, '', '', '', $windowoptions, true);
+ }
+}
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Unit tests for (some of) mod/quiz/accessrules.php.
+ *
+ * @copyright © 2008 The Open University
+ * @author T.J.Hunt@open.ac.uk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package quiz
+ */
+
+if (!defined('MOODLE_INTERNAL')) {
+ die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
+}
+
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+
+class simple_rules_test extends UnitTestCase {
+ function test_num_attempts_access_rule() {
+ $quiz = new stdClass;
+ $quiz->attempts = 3;
+ $rule = new num_attempts_access_rule($quiz, 0);
+ $attempt = new stdClass;
+
+ $this->assertEqual($rule->description(), get_string('attemptsallowedn', 'quiz', 3));
+
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $this->assertEqual($rule->prevent_new_attempt(3, $attempt), get_string('nomoreattempts', 'quiz'));
+ $this->assertEqual($rule->prevent_new_attempt(666, $attempt), get_string('nomoreattempts', 'quiz'));
+
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->is_finished(2, $attempt));
+ $this->assertTrue($rule->is_finished(3, $attempt));
+ $this->assertTrue($rule->is_finished(666, $attempt));
+
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->time_left($attempt, 1));
+ }
+
+ function test_ipaddress_access_rule() {
+ $quiz = new stdClass;
+ $attempt = new stdClass;
+
+ $quiz->subnet = getremoteaddr();
+ $rule = new ipaddress_access_rule($quiz, 0);
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 1));
+
+ $quiz->subnet = '0.0.0.0';
+ $rule = new ipaddress_access_rule($quiz, 0);
+ $this->assertTrue($rule->prevent_access());
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 1));
+ }
+
+ function test_time_limit_access_rule() {
+ $quiz = new stdClass;
+ $quiz->timelimit = 60;
+ $rule = new time_limit_access_rule($quiz, 10000);
+ $attempt = new stdClass;
+
+ $this->assertEqual($rule->description(), get_string('quiztimelimit', 'quiz', format_time(3600)));
+
+ $attempt->timestart = 10000;
+ $this->assertEqual($rule->time_left($attempt, 10000), 3600);
+ $this->assertEqual($rule->time_left($attempt, 12000), 1600);
+ $this->assertEqual($rule->time_left($attempt, 14000), -400);
+
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ }
+}
+
+class open_close_date_access_rule_test extends UnitTestCase {
+ function test_no_dates() {
+ $quiz = new stdClass;
+ $quiz->timeopen = 0;
+ $quiz->timeclose = 0;
+ $attempt = new stdClass;
+
+ $rule = new open_close_date_access_rule($quiz, 10000);
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 10000));
+ $this->assertFalse($rule->time_left($attempt, 0));
+
+ $rule = new open_close_date_access_rule($quiz, 0);
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 0));
+ }
+
+ function test_start_date() {
+ $quiz = new stdClass;
+ $quiz->timeopen = 10000;
+ $quiz->timeclose = 0;
+ $attempt = new stdClass;
+
+ $rule = new open_close_date_access_rule($quiz, 9999);
+ $this->assertEqual($rule->description(), array(get_string('quiznotavailable', 'quiz', userdate(10000))));
+ $this->assertEqual($rule->prevent_access(), get_string('notavailable', 'quiz'));
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 0));
+
+ $rule = new open_close_date_access_rule($quiz, 10000);
+ $this->assertEqual($rule->description(), array(get_string('quizopenedon', 'quiz', userdate(10000))));
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 0));
+ }
+
+ function test_close_date() {
+ $quiz = new stdClass;
+ $quiz->timeopen = 0;
+ $quiz->timeclose = 20000;
+ $attempt = new stdClass;
+
+ $rule = new open_close_date_access_rule($quiz, 20000);
+ $this->assertEqual($rule->description(), array(get_string('quizcloseson', 'quiz', userdate(20000))));
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 20000 - QUIZ_SHOW_TIME_BEFORE_DEADLINE));
+ $this->assertEqual($rule->time_left($attempt, 19900), 100);
+ $this->assertEqual($rule->time_left($attempt, 20000), 0);
+ $this->assertEqual($rule->time_left($attempt, 20100), -100);
+
+ $rule = new open_close_date_access_rule($quiz, 20001);
+ $this->assertEqual($rule->description(), array(get_string('quizclosed', 'quiz', userdate(20000))));
+ $this->assertEqual($rule->prevent_access(), get_string('notavailable', 'quiz'));
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertTrue($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 20000 - QUIZ_SHOW_TIME_BEFORE_DEADLINE));
+ $this->assertEqual($rule->time_left($attempt, 19900), 100);
+ $this->assertEqual($rule->time_left($attempt, 20000), 0);
+ $this->assertEqual($rule->time_left($attempt, 20100), -100);
+ }
+
+ function test_both_dates() {
+ $quiz = new stdClass;
+ $quiz->timeopen = 10000;
+ $quiz->timeclose = 20000;
+ $attempt = new stdClass;
+
+ $rule = new open_close_date_access_rule($quiz, 9999);
+ $this->assertEqual($rule->description(), array(get_string('quiznotavailable', 'quiz', userdate(10000))));
+ $this->assertEqual($rule->prevent_access(), get_string('notavailable', 'quiz'));
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+
+ $rule = new open_close_date_access_rule($quiz, 10000);
+ $this->assertEqual($rule->description(), array(get_string('quizopenedon', 'quiz', userdate(10000)),
+ get_string('quizcloseson', 'quiz', userdate(20000))));
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+
+ $rule = new open_close_date_access_rule($quiz, 20000);
+ $this->assertEqual($rule->description(), array(get_string('quizopenedon', 'quiz', userdate(10000)),
+ get_string('quizcloseson', 'quiz', userdate(20000))));
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+
+ $rule = new open_close_date_access_rule($quiz, 20001);
+ $this->assertEqual($rule->description(), array(get_string('quizclosed', 'quiz', userdate(20000))));
+ $this->assertEqual($rule->prevent_access(), get_string('notavailable', 'quiz'));
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertTrue($rule->is_finished(0, $attempt));
+
+ $this->assertFalse($rule->time_left($attempt, 20000 - QUIZ_SHOW_TIME_BEFORE_DEADLINE));
+ $this->assertEqual($rule->time_left($attempt, 19900), 100);
+ $this->assertEqual($rule->time_left($attempt, 20000), 0);
+ $this->assertEqual($rule->time_left($attempt, 20100), -100);
+ }
+}
+
+class inter_attempt_delay_access_rule_test extends UnitTestCase {
+ function test_just_first_delay() {
+ $quiz = new stdClass;
+ $quiz->attempts = 3;
+ $quiz->delay1 = 1000;
+ $quiz->delay2 = 0;
+ $quiz->timeclose = 0;
+ $attempt = new stdClass;
+ $attempt->timefinish = 10000;
+
+ $rule = new inter_attempt_delay_access_rule($quiz, 10000);
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 0));
+
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(3, $attempt));
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youmustwait', 'quiz', userdate(11000)));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $attempt->timefinish = 9000;
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $attempt->timefinish = 9001;
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youmustwait', 'quiz', userdate(10001)));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ }
+
+ function test_just_second_delay() {
+ $quiz = new stdClass;
+ $quiz->attempts = 5;
+ $quiz->delay1 = 0;
+ $quiz->delay2 = 1000;
+ $quiz->timeclose = 0;
+ $attempt = new stdClass;
+ $attempt->timefinish = 10000;
+
+ $rule = new inter_attempt_delay_access_rule($quiz, 10000);
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 0));
+
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(5, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $this->assertEqual($rule->prevent_new_attempt(2, $attempt), get_string('youmustwait', 'quiz', userdate(11000)));
+ $this->assertEqual($rule->prevent_new_attempt(3, $attempt), get_string('youmustwait', 'quiz', userdate(11000)));
+ $attempt->timefinish = 9000;
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(3, $attempt));
+ $attempt->timefinish = 9001;
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $this->assertEqual($rule->prevent_new_attempt(2, $attempt), get_string('youmustwait', 'quiz', userdate(10001)));
+ $this->assertEqual($rule->prevent_new_attempt(4, $attempt), get_string('youmustwait', 'quiz', userdate(10001)));
+ }
+
+ function test_just_both_delays() {
+ $quiz = new stdClass;
+ $quiz->attempts = 5;
+ $quiz->delay1 = 2000;
+ $quiz->delay2 = 1000;
+ $quiz->timeclose = 0;
+ $attempt = new stdClass;
+ $attempt->timefinish = 10000;
+
+ $rule = new inter_attempt_delay_access_rule($quiz, 10000);
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 0));
+
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(5, $attempt));
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youmustwait', 'quiz', userdate(12000)));
+ $this->assertEqual($rule->prevent_new_attempt(2, $attempt), get_string('youmustwait', 'quiz', userdate(11000)));
+ $this->assertEqual($rule->prevent_new_attempt(3, $attempt), get_string('youmustwait', 'quiz', userdate(11000)));
+ $attempt->timefinish = 8000;
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(3, $attempt));
+ $attempt->timefinish = 8001;
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youmustwait', 'quiz', userdate(10001)));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(4, $attempt));
+ $attempt->timefinish = 9000;
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youmustwait', 'quiz', userdate(11000)));
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $this->assertFalse($rule->prevent_new_attempt(3, $attempt));
+ $attempt->timefinish = 9001;
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youmustwait', 'quiz', userdate(11001)));
+ $this->assertEqual($rule->prevent_new_attempt(2, $attempt), get_string('youmustwait', 'quiz', userdate(10001)));
+ $this->assertEqual($rule->prevent_new_attempt(4, $attempt), get_string('youmustwait', 'quiz', userdate(10001)));
+ }
+
+ function test_with_close_date() {
+ $quiz = new stdClass;
+ $quiz->attempts = 5;
+ $quiz->delay1 = 2000;
+ $quiz->delay2 = 1000;
+ $quiz->timeclose = 15000;
+ $attempt = new stdClass;
+ $attempt->timefinish = 13000;
+
+ $rule = new inter_attempt_delay_access_rule($quiz, 10000);
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 0));
+
+ $attempt->timefinish = 13000;
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youmustwait', 'quiz', userdate(15000)));
+ $attempt->timefinish = 13001;
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youcannotwait', 'quiz'));
+ $attempt->timefinish = 14000;
+ $this->assertEqual($rule->prevent_new_attempt(2, $attempt), get_string('youmustwait', 'quiz', userdate(15000)));
+ $attempt->timefinish = 14001;
+ $this->assertEqual($rule->prevent_new_attempt(2, $attempt), get_string('youcannotwait', 'quiz'));
+
+ $rule = new inter_attempt_delay_access_rule($quiz, 15000);
+ $attempt->timefinish = 13000;
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $attempt->timefinish = 13001;
+ $this->assertEqual($rule->prevent_new_attempt(1, $attempt), get_string('youcannotwait', 'quiz'));
+ $attempt->timefinish = 14000;
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $attempt->timefinish = 14001;
+ $this->assertEqual($rule->prevent_new_attempt(2, $attempt), get_string('youcannotwait', 'quiz'));
+
+ $rule = new inter_attempt_delay_access_rule($quiz, 15001);
+ $attempt->timefinish = 13000;
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $attempt->timefinish = 13001;
+ $this->assertFalse($rule->prevent_new_attempt(1, $attempt));
+ $attempt->timefinish = 14000;
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ $attempt->timefinish = 14001;
+ $this->assertFalse($rule->prevent_new_attempt(2, $attempt));
+ }
+}
+
+class password_access_rule_test extends UnitTestCase {
+ function test_password_access_rule() {
+ $quiz = new stdClass;
+ $quiz->password = 'frog';
+ $rule = new password_access_rule($quiz, 0);
+ $attempt = new stdClass;
+
+ $this->assertFalse($rule->prevent_access());
+ $this->assertEqual($rule->description(), get_string('requirepasswordmessage', 'quiz'));
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 1));
+ }
+
+ function test_do_password_check() {
+ $reqpwregex = '/' . preg_quote(get_string('requirepasswordmessage', 'quiz')) . '/';
+ $pwerrregex = '/' . preg_quote(get_string('passworderror', 'quiz')) . '/';
+
+ $quiz = new stdClass;
+ $quiz->id = -1; // So as not to interfere with any real quizzes.
+ $quiz->intro = 'SOME INTRO TEXT';
+ $quiz->password = 'frog';
+ $rule = new password_access_rule($quiz, 0);
+
+ $rule->clear_access_allowed(-1);
+ $_POST['cancelpassword'] = false;
+ $_POST['quizpassword'] = '';
+ $html = $rule->do_password_check(true);
+ $this->assertPattern($reqpwregex, $html);
+ $this->assertPattern('/SOME INTRO TEXT/', $html);
+ $this->assertNoPattern($pwerrregex, $html);
+
+ $_POST['quizpassword'] = 'toad';
+ $html = $rule->do_password_check(true);
+ $this->assertPattern($reqpwregex, $html);
+ $this->assertPattern($pwerrregex, $html);
+
+ $_POST['quizpassword'] = 'frog';
+ $this->assertNull($rule->do_password_check(true));
+
+ // Check that once you are in, the password isn't checked again.
+ $_POST['quizpassword'] = 'newt';
+ $this->assertNull($rule->do_password_check(true));
+
+ $rule->clear_access_allowed(-1);
+ $html = $rule->do_password_check(true);
+ $this->assertPattern($reqpwregex, $html);
+ }
+}
+
+class securewindow_access_rule_test extends UnitTestCase {
+ // Nothing very testable in this class, just test that it obeys the general access rule contact.
+
+ function test_securewindow_access_rule() {
+ $quiz = new stdClass;
+ $quiz->popup = 1;
+ $rule = new securewindow_access_rule($quiz, 0);
+ $attempt = new stdClass;
+
+ $this->assertFalse($rule->prevent_access());
+ $this->assertFalse($rule->description());
+ $this->assertFalse($rule->prevent_new_attempt(0, $attempt));
+ $this->assertFalse($rule->is_finished(0, $attempt));
+ $this->assertFalse($rule->time_left($attempt, 1));
+ }
+}
+
+class quiz_access_manager_test extends UnitTestCase {
+ // TODO
+}
+?>