message MDL-25148 using method on $PAGE to allow or disallow popup notifications
[moodle.git] / mod / quiz / accessrules.php
1 <?php
2 /**
3  * This class keeps track of the various access rules that apply to a particular
4  * quiz, with convinient methods for seeing whether access is allowed.
5  */
6 class quiz_access_manager {
7     private $_quizobj;
8     private $_timenow;
9     private $_passwordrule = null;
10     private $_securewindowrule = null;
11     private $_safebrowserrule = null;
12     private $_rules = array();
14     /**
15      * Create an instance for a particular quiz.
16      * @param object $quizobj An instance of the class quiz from attemptlib.php.
17      *      The quiz we will be controlling access to.
18      * @param integer $timenow The time to use as 'now'.
19      * @param boolean $canignoretimelimits Whether this user is exempt from time
20      *      limits (has_capability('mod/quiz:ignoretimelimits', ...)).
21      */
22     public function __construct($quizobj, $timenow, $canignoretimelimits) {
23         $this->_quizobj = $quizobj;
24         $this->_timenow = $timenow;
25         $this->create_standard_rules($canignoretimelimits);
26     }
28     private function create_standard_rules($canignoretimelimits) {
29         $quiz = $this->_quizobj->get_quiz();
30         if ($quiz->attempts > 0) {
31             $this->_rules[] = new num_attempts_access_rule($this->_quizobj, $this->_timenow);
32         }
33         $this->_rules[] = new open_close_date_access_rule($this->_quizobj, $this->_timenow);
34         if (!empty($quiz->timelimit) && !$canignoretimelimits) {
35             $this->_rules[] = new time_limit_access_rule($this->_quizobj, $this->_timenow);
36         }
37         if (!empty($quiz->delay1) || !empty($quiz->delay2)) {
38             $this->_rules[] = new inter_attempt_delay_access_rule($this->_quizobj, $this->_timenow);
39         }
40         if (!empty($quiz->subnet)) {
41             $this->_rules[] = new ipaddress_access_rule($this->_quizobj, $this->_timenow);
42         }
43         if (!empty($quiz->password)) {
44             $this->_passwordrule = new password_access_rule($this->_quizobj, $this->_timenow);
45             $this->_rules[] = $this->_passwordrule;
46         }
47         if (!empty($quiz->popup)) {
48             if ($quiz->popup == 1) {
49                 $this->_securewindowrule = new securewindow_access_rule($this->_quizobj, $this->_timenow);
50                 $this->_rules[] = $this->_securewindowrule;
51             } elseif ($quiz->popup == 2) {
52                 $this->_safebrowserrule = new safebrowser_access_rule($this->_quizobj, $this->_timenow);
53                 $this->_rules[] = $this->_safebrowserrule;
54             }
55         }
56     }
58     private function accumulate_messages(&$messages, $new) {
59         if (is_array($new)) {
60             $messages = array_merge($messages, $new);
61         } else if (is_string($new) && $new) {
62             $messages[] = $new;
63         }
64     }
66     /**
67      * Print each message in an array, surrounded by &lt;p>, &lt;/p> tags.
68      *
69      * @param array $messages the array of message strings.
70      * @param boolean $return if true, return a string, instead of outputting.
71      *
72      * @return mixed, if $return is true, return the string that would have been output, otherwise
73      * return null.
74      */
75     public function print_messages($messages, $return=false) {
76         $output = '';
77         foreach ($messages as $message) {
78             $output .= '<p>' . $message . "</p>\n";
79         }
80         if ($return) {
81             return $output;
82         } else {
83             echo $output;
84         }
85     }
87     /**
88      * Provide a description of the rules that apply to this quiz, such
89      * as is shown at the top of the quiz view page. Note that not all
90      * rules consider themselves important enough to output a description.
91      *
92      * @return array an array of description messages which may be empty. It
93      *         would be sensible to output each one surrounded by &lt;p> tags.
94      */
95     public function describe_rules() {
96         $result = array();
97         foreach ($this->_rules as $rule) {
98             $this->accumulate_messages($result, $rule->description());
99         }
100         return $result;
101     }
103     /**
104      * Is it OK to let the current user start a new attempt now? If there are
105      * any restrictions in force now, return an array of reasons why access
106      * should be blocked. If access is OK, return false.
107      *
108      * @param integer $numattempts the number of previous attempts this user has made.
109      * @param object|false $lastattempt information about the user's last completed attempt.
110      *      if there is not a previous attempt, the false is passed.
111      * @return mixed An array of reason why access is not allowed, or an empty array
112      *         (== false) if access should be allowed.
113      */
114     public function prevent_new_attempt($numprevattempts, $lastattempt) {
115         $reasons = array();
116         foreach ($this->_rules as $rule) {
117             $this->accumulate_messages($reasons,
118                     $rule->prevent_new_attempt($numprevattempts, $lastattempt));
119         }
120         return $reasons;
121     }
123     /**
124      * Is it OK to let the current user start a new attempt now? If there are
125      * any restrictions in force now, return an array of reasons why access
126      * should be blocked. If access is OK, return false.
127      *
128      * @return mixed An array of reason why access is not allowed, or an empty array
129      *         (== false) if access should be allowed.
130      */
131     public function prevent_access() {
132         $reasons = array();
133         foreach ($this->_rules as $rule) {
134             $this->accumulate_messages($reasons, $rule->prevent_access());
135         }
136         return $reasons;
137     }
139     /**
140      * Do any of the rules mean that this student will no be allowed any further attempts at this
141      * quiz. Used, for example, to change the label by the  grade displayed on the view page from
142      * 'your current score is' to 'your final score is'.
143      *
144      * @param integer $numattempts the number of previous attempts this user has made.
145      * @param object $lastattempt information about the user's last completed attempt.
146      * @return boolean true if there is no way the user will ever be allowed to attempt this quiz again.
147      */
148     public function is_finished($numprevattempts, $lastattempt) {
149         foreach ($this->_rules as $rule) {
150             if ($rule->is_finished($numprevattempts, $lastattempt)) {
151                 return true;
152             }
153         }
154         return false;
155     }
157     /**
158      * Do the printheader call, etc. required for a secure page, including the necessary JS.
159      *
160      * @param string $title HTML title tag content, passed to printheader.
161      * @param string $headtags extra stuff to go in the HTML head tag, passed to printheader.
162      */
163     public function setup_secure_page($title, $headtags = null) {
164         $this->_securewindowrule->setup_secure_page($title, $headtags);
165     }
167     public function show_attempt_timer_if_needed($attempt, $timenow) {
168         global $PAGE;
169         $timeleft = false;
170         foreach ($this->_rules as $rule) {
171             $ruletimeleft = $rule->time_left($attempt, $timenow);
172             if ($ruletimeleft !== false && ($timeleft === false || $ruletimeleft < $timeleft)) {
173                 $timeleft = $ruletimeleft;
174             }
175         }
176         if ($timeleft !== false) {
177         /// Make sure the timer starts just above zero. If $timeleft was <= 0, then
178         /// this will just have the effect of causing the quiz to be submitted immediately.
179             $timerstartvalue = max($timeleft, 1);
180             $PAGE->requires->js_init_call('M.mod_quiz.timer.init',
181                     array($timerstartvalue), false, quiz_get_js_module());
182         }
183     }
185     /**
186      * @return bolean if this quiz should only be shown to students in a secure window.
187      */
188     public function securewindow_required($canpreview) {
189         return !$canpreview && !is_null($this->_securewindowrule);
190     }
192     /**
193      * @return bolean if this quiz should only be shown to students with safe browser.
194     */
195     public function safebrowser_required($canpreview) {
196         return !$canpreview && !is_null($this->_safebrowserrule);
197     }
199     /**
200      * Print a button to start a quiz attempt, with an appropriate javascript warning,
201      * depending on the access restrictions. The link will pop up a 'secure' window, if
202      * necessary.
203      *
204      * @param boolean $canpreview whether this user can preview. This affects whether they must
205      * use a secure window.
206      * @param string $buttontext the label to put on the button.
207      * @param boolean $unfinished whether the button is to continue an existing attempt,
208      * or start a new one. This affects whether a javascript alert is shown.
209      */
210     public function print_start_attempt_button($canpreview, $buttontext, $unfinished) { 
211         global $OUTPUT;
213         $url = $this->_quizobj->start_attempt_url();
214         $button = new single_button($url, $buttontext);
215         $button->class .= ' quizstartbuttondiv';
217         if (!$unfinished) {
218             $strconfirmstartattempt = $this->confirm_start_attempt_message();
219             if ($strconfirmstartattempt) {
220                 $button->add_confirm_action($strconfirmstartattempt);
221             }
222         }
224         $warning = '';
226         if ($this->securewindow_required($canpreview)) {
227             $button->class .= ' quizsecuremoderequired';
229             $button->add_action(new popup_action('click', $url, 'quizpopup',
230                     securewindow_access_rule::$popupoptions));
232             $warning = html_writer::tag('noscript',
233                     $OUTPUT->heading(get_string('noscript', 'quiz')));
234         }
236         echo $OUTPUT->render($button) . $warning;
237     }
239     /**
240      * Send the user back to the quiz view page. Normally this is just a redirect, but
241      * If we were in a secure window, we close this window, and reload the view window we came from.
242      *
243      * @param boolean $canpreview This affects whether we have to worry about secure window stuff.
244      */
245     public function back_to_view_page($canpreview, $message = '') {
246         global $CFG, $OUTPUT, $PAGE;
247         $url = $this->_quizobj->view_url();
248         if ($this->securewindow_required($canpreview)) {
249             $PAGE->set_pagelayout('popup');
250             echo $OUTPUT->header();
251             echo $OUTPUT->box_start();
252             if ($message) {
253                 echo '<p>' . $message . '</p><p>' . get_string('windowclosing', 'quiz') . '</p>';
254                 $delay = 5;
255             } else {
256                 echo '<p>' . get_string('pleaseclose', 'quiz') . '</p>';
257                 $delay = 0;
258             }
259             $PAGE->requires->js_function_call('M.mod_quiz.secure_window.close', array($url, $delay));
260             echo $OUTPUT->box_end();
261             echo $OUTPUT->footer();
262             die();
263         } else {
264             redirect($url, $message);
265         }
266     }
268     /**
269      * Print a control to finish the review. Normally this is just a link, but if we are
270      * in a secure window, it needs to be a button that does M.mod_quiz.secure_window.close.
271      *
272      * @param boolean $canpreview This affects whether we have to worry about secure window stuff.
273      */
274     public function print_finish_review_link($canpreview, $return = false) {
275         global $CFG;
276         $output = '';
277         $url = $this->_quizobj->view_url();
278         $output .= '<div class="finishreview">';
279         if ($this->securewindow_required($canpreview)) {
280             $url = addslashes_js(htmlspecialchars($url));
281             $output .= '<input type="button" value="' . get_string('finishreview', 'quiz') . '" ' .
282                     "onclick=\"M.mod_quiz.secure_window.close('$url', 0)\" />\n";
283         } else {
284             $output .= '<a href="' . $url . '">' . get_string('finishreview', 'quiz') . "</a>\n";
285         }
286         $output .= "</div>\n";
287         if ($return) {
288             return $output;
289         } else {
290             echo $output;
291         }
292     }
294     /**
295      * @return bolean if this quiz is password protected.
296      */
297     public function password_required() {
298         return !is_null($this->_passwordrule);
299     }
301     /**
302      * Clear the flag in the session that says that the current user is allowed to do this quiz.
303      */
304     public function clear_password_access() {
305         if (!is_null($this->_passwordrule)) {
306             $this->_passwordrule->clear_access_allowed();
307         }
308     }
310     /**
311      * Actually ask the user for the password, if they have not already given it this session.
312      * This function only returns is access is OK.
313      *
314      * @param boolean $canpreview used to enfore securewindow stuff.
315      */
316     public function do_password_check($canpreview) {
317         if (!is_null($this->_passwordrule)) {
318             $this->_passwordrule->do_password_check($canpreview, $this);
319         }
320     }
322     /**
323      * @return string if the quiz policies merit it, return a warning string to be displayed
324      * in a javascript alert on the start attempt button.
325      */
326     public function confirm_start_attempt_message() {
327         $quiz = $this->_quizobj->get_quiz();
328         if ($quiz->timelimit && $quiz->attempts) {
329             return get_string('confirmstartattempttimelimit','quiz', $quiz->attempts);
330         } else if ($quiz->timelimit) {
331             return get_string('confirmstarttimelimit','quiz');
332         } else if ($quiz->attempts) {
333             return get_string('confirmstartattemptlimit','quiz', $quiz->attempts);
334         }
335         return '';
336     }
338     /**
339      * Make some text into a link to review the quiz, if that is appropriate.
340      *
341      * @param string $linktext some text.
342      * @param object $attempt the attempt object
343      * @return string some HTML, the $linktext either unmodified or wrapped in a link to the review page.
344      */
345     public function make_review_link($attempt, $canpreview, $reviewoptions) {
346         global $CFG;
348     /// If review of responses is not allowed, or the attempt is still open, don't link.
349         if (!$attempt->timefinish) {
350             return '';
351         }
352         if (!$reviewoptions->responses) {
353             $message = $this->cannot_review_message($reviewoptions, true);
354             if ($message) {
355                 return '<span class="noreviewmessage">' . $message . '</span>';
356             } else {
357                 return '';
358             }
359         }
361         $linktext = get_string('review', 'quiz');
363     /// It is OK to link, does it need to be in a secure window?
364         if ($this->securewindow_required($canpreview)) {
365             return $this->_securewindowrule->make_review_link($linktext, $attempt->id);
366         } else {
367             return '<a href="' . $this->_quizobj->review_url($attempt->id) . '" title="' .
368                     get_string('reviewthisattempt', 'quiz') . '">' . $linktext . '</a>';
369         }
370     }
372     /**
373      * If $reviewoptions->responses is false, meaning that students can't review this
374      * attempt at the moment, return an appropriate string explaining why.
375      *
376      * @param object $reviewoptions as obtained from quiz_get_reviewoptions.
377      * @param boolean $short if true, return a shorter string.
378      * @return string an appropraite message.
379      */
380     public function cannot_review_message($reviewoptions, $short = false) {
381         $quiz = $this->_quizobj->get_quiz();
382         if ($short) {
383             $langstrsuffix = 'short';
384             $dateformat = get_string('strftimedatetimeshort', 'langconfig');
385         } else {
386             $langstrsuffix = '';
387             $dateformat = '';
388         }
389         if ($reviewoptions->quizstate == QUIZ_STATE_IMMEDIATELY) {
390             return '';
391         } else if ($reviewoptions->quizstate == QUIZ_STATE_OPEN && $quiz->timeclose &&
392                     ($quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_RESPONSES)) {
393             return get_string('noreviewuntil' . $langstrsuffix, 'quiz', userdate($quiz->timeclose, $dateformat));
394         } else {
395             return get_string('noreview' . $langstrsuffix, 'quiz');
396         }
397     }
400 /**
401  * A base class that defines the interface for the various quiz access rules.
402  * Most of the methods are defined in a slightly unnatural way because we either
403  * want to say that access is allowed, or explain the reason why it is block.
404  * Therefore instead of is_access_allowed(...) we have prevent_access(...) that
405  * return false if access is permitted, or a string explanation (which is treated
406  * as true) if access should be blocked. Slighly unnatural, but acutally the easist
407  * way to implement this.
408  */
409 abstract class quiz_access_rule_base {
410     protected $_quiz;
411     protected $_quizobj;
412     protected $_timenow;
413     /**
414      * Create an instance of this rule for a particular quiz.
415      * @param object $quiz the quiz we will be controlling access to.
416      */
417     public function __construct($quizobj, $timenow) {
418         $this->_quizobj = $quizobj;
419         $this->_quiz = $quizobj->get_quiz();
420         $this->_timenow = $timenow;
421     }
422     /**
423      * Whether or not a user should be allowed to start a new attempt at this quiz now.
424      * @param integer $numattempts the number of previous attempts this user has made.
425      * @param object $lastattempt information about the user's last completed attempt.
426      * @return string false if access should be allowed, a message explaining the reason if access should be prevented.
427      */
428     public function prevent_new_attempt($numprevattempts, $lastattempt) {
429         return false;
430     }
431     /**
432      * Whether or not a user should be allowed to start a new attempt at this quiz now.
433      * @return string false if access should be allowed, a message explaining the reason if access should be prevented.
434      */
435     public function prevent_access() {
436         return false;
437     }
438     /**
439      * Information, such as might be shown on the quiz view page, relating to this restriction.
440      * There is no obligation to return anything. If it is not appropriate to tell students
441      * about this rule, then just return ''.
442      * @return mixed a message, or array of messages, explaining the restriction
443      *         (may be '' if no message is appropriate).
444      */
445     public function description() {
446         return '';
447     }
448     /**
449      * If this rule can determine that this user will never be allowed another attempt at
450      * this quiz, then return true. This is used so we can know whether to display a
451      * final score on the view page. This will only be called if there is not a currently
452      * active attempt for this user.
453      * @param integer $numattempts the number of previous attempts this user has made.
454      * @param object $lastattempt information about the user's last completed attempt.
455      * @return boolean true if this rule means that this user will never be allowed another
456      * attempt at this quiz.
457      */
458     public function is_finished($numprevattempts, $lastattempt) {
459         return false;
460     }
462     /**
463      * If, becuase of this rule, the user has to finish their attempt by a certain time,
464      * you should override this method to return the amount of time left in seconds.
465      * @param object $attempt the current attempt
466      * @param integer $timenow the time now. We don't use $this->_timenow, so we can
467      * give the user a more accurate indication of how much time is left.
468      * @return mixed false if there is no deadline, of the time left in seconds if there is one.
469      */
470     public function time_left($attempt, $timenow) {
471         return false;
472     }
475 /**
476  * A rule controlling the number of attempts allowed.
477  */
478 class num_attempts_access_rule extends quiz_access_rule_base {
479     public function description() {
480         return get_string('attemptsallowedn', 'quiz', $this->_quiz->attempts);
481     }
482     public function prevent_new_attempt($numprevattempts, $lastattempt) {
483         if ($numprevattempts >= $this->_quiz->attempts) {
484             return get_string('nomoreattempts', 'quiz');
485         }
486         return false;
487     }
488     public function is_finished($numprevattempts, $lastattempt) {
489         return $numprevattempts >= $this->_quiz->attempts;
490     }
493 /**
494  * A rule enforcing open and close dates.
495  */
496 class open_close_date_access_rule extends quiz_access_rule_base {
497     public function description() {
498         $result = array();
499         if ($this->_timenow < $this->_quiz->timeopen) {
500             $result[] = get_string('quiznotavailable', 'quiz', userdate($this->_quiz->timeopen));
501         } else if ($this->_quiz->timeclose && $this->_timenow > $this->_quiz->timeclose) {
502             $result[] = get_string("quizclosed", "quiz", userdate($this->_quiz->timeclose));
503         } else {
504             if ($this->_quiz->timeopen) {
505                 $result[] = get_string('quizopenedon', 'quiz', userdate($this->_quiz->timeopen));
506             }
507             if ($this->_quiz->timeclose) {
508                 $result[] = get_string('quizcloseson', 'quiz', userdate($this->_quiz->timeclose));
509             }
510         }
511         return $result;
512     }
513     public function prevent_access() {
514         if ($this->_timenow < $this->_quiz->timeopen ||
515                     ($this->_quiz->timeclose && $this->_timenow > $this->_quiz->timeclose)) {
516             return get_string('notavailable', 'quiz');
517         }
518         return false;
519     }
520     public function is_finished($numprevattempts, $lastattempt) {
521         return $this->_quiz->timeclose && $this->_timenow > $this->_quiz->timeclose;
522     }
523     public function time_left($attempt, $timenow) {
524         // If this is a teacher preview after the close date, do not show
525         // the time.
526         if ($attempt->preview && $timenow > $this->_quiz->timeclose) {
527             return false;
528         }
530         // Otherwise, return to the time left until the close date, providing
531         // that is less than QUIZ_SHOW_TIME_BEFORE_DEADLINE
532         if ($this->_quiz->timeclose) {
533             $timeleft = $this->_quiz->timeclose - $timenow;
534             if ($timeleft < QUIZ_SHOW_TIME_BEFORE_DEADLINE) {
535                 return $timeleft;
536             }
537         }
538         return false;
539     }
542 /**
543  * A rule imposing the delay between attemtps settings.
544  */
545 class inter_attempt_delay_access_rule extends quiz_access_rule_base {
546     public function prevent_new_attempt($numprevattempts, $lastattempt) {
547         if ($this->_quiz->attempts > 0 && $numprevattempts >= $this->_quiz->attempts) {
548         /// No more attempts allowed anyway.
549             return false;
550         }
551         if ($this->_quiz->timeclose != 0 && $this->_timenow > $this->_quiz->timeclose) {
552         /// No more attempts allowed anyway.
553             return false;
554         }
555         $nextstarttime = $this->compute_next_start_time($numprevattempts, $lastattempt);
556         if ($this->_timenow < $nextstarttime) {
557             if ($this->_quiz->timeclose == 0 || $nextstarttime <= $this->_quiz->timeclose) {
558                 return get_string('youmustwait', 'quiz', userdate($nextstarttime));
559             } else {
560                 return get_string('youcannotwait', 'quiz');
561             }
562         }
563         return false;
564     }
566     /**
567      * Compute the next time a student would be allowed to start an attempt,
568      * according to this rule.
569      * @param integer $numprevattempts number of previous attempts.
570      * @param object $lastattempt information about the previous attempt.
571      * @return number the time.
572      */
573     protected function compute_next_start_time($numprevattempts, $lastattempt) {
574         if ($numprevattempts == 0) {
575             return 0;
576         }
578         $lastattemptfinish = $lastattempt->timefinish;
579         if ($this->_quiz->timelimit > 0){
580             $lastattemptfinish = min($lastattemptfinish,
581                     $lastattempt->timestart + $this->_quiz->timelimit);
582         }
584         if ($numprevattempts == 1 && $this->_quiz->delay1) {
585             return $lastattemptfinish + $this->_quiz->delay1;
586         } else if ($numprevattempts > 1 && $this->_quiz->delay2) {
587             return $lastattemptfinish + $this->_quiz->delay2;
588         }
589         return 0;
590     }
592     public function is_finished($numprevattempts, $lastattempt) {
593         $nextstarttime = $this->compute_next_start_time($numprevattempts, $lastattempt);
594         return $this->_timenow <= $nextstarttime &&
595                 $this->_quiz->timeclose != 0 && $nextstarttime >= $this->_quiz->timeclose;
596     }
599 /**
600  * A rule implementing the ipaddress check against the ->submet setting.
601  */
602 class ipaddress_access_rule extends quiz_access_rule_base {
603     public function prevent_access() {
604         if (address_in_subnet(getremoteaddr(), $this->_quiz->subnet)) {
605             return false;
606         } else {
607             return get_string('subnetwrong', 'quiz');
608         }
609     }
612 /**
613  * A rule representing the password check. It does not actually implement the check,
614  * that has to be done directly in attempt.php, but this facilitates telling users about it.
615  */
616 class password_access_rule extends quiz_access_rule_base {
617     public function description() {
618         return get_string('requirepasswordmessage', 'quiz');
619     }
620     /**
621      * Clear the flag in the session that says that the current user is allowed to do this quiz.
622      */
623     public function clear_access_allowed() {
624         global $SESSION;
625         if (!empty($SESSION->passwordcheckedquizzes[$this->_quiz->id])) {
626             unset($SESSION->passwordcheckedquizzes[$this->_quiz->id]);
627         }
628     }
629     /**
630      * Actually ask the user for the password, if they have not already given it this session.
631      * This function only returns is access is OK.
632      *
633      * @param boolean $canpreview used to enfore securewindow stuff.
634      * @param object $accessmanager the accessmanager calling us.
635      */
636     public function do_password_check($canpreview, $accessmanager) {
637         global $CFG, $SESSION, $OUTPUT, $PAGE;
639     /// We have already checked the password for this quiz this session, so don't ask again.
640         if (!empty($SESSION->passwordcheckedquizzes[$this->_quiz->id])) {
641             return;
642         }
644     /// If the user cancelled the password form, send them back to the view page.
645         if (optional_param('cancelpassword', false, PARAM_BOOL)) {
646             $accessmanager->back_to_view_page($canpreview);
647         }
649     /// If they entered the right password, let them in.
650         $enteredpassword = optional_param('quizpassword', '', PARAM_RAW);
651         $validpassword = false;
652         if (strcmp($this->_quiz->password, $enteredpassword) === 0) {
653             $validpassword = true;
654         } else if (isset($this->_quiz->extrapasswords)) {
655             // group overrides may have additional passwords
656             foreach ($this->_quiz->extrapasswords as $password) {
657                 if (strcmp($password, $enteredpassword) === 0) {
658                     $validpassword = true;
659                     break;
660                 }
661             }
662         }
663         if ($validpassword) {
664             $SESSION->passwordcheckedquizzes[$this->_quiz->id] = true;
665             return;
666         }
668     /// User entered the wrong password, or has not entered one yet, so display the form.
669         $output = '';
671     /// Start the page and print the quiz intro, if any.
672         if ($accessmanager->securewindow_required($canpreview)) {
673             $accessmanager->setup_secure_page($this->_quizobj->get_course()->shortname . ': ' .
674                     format_string($this->_quizobj->get_quiz_name()));
675         } else if ($accessmanager->safebrowser_required($canpreview)) {
676             $PAGE->set_title($this->_quizobj->get_course()->shortname . ': '.format_string($this->_quizobj->get_quiz_name()));
677             $PAGE->set_cacheable(false);
678             echo $OUTPUT->header();
679         } else {
680             $PAGE->set_title(format_string($this->_quizobj->get_quiz_name()));
681             echo $OUTPUT->header();
682         }
684         if (trim(strip_tags($this->_quiz->intro))) {
685             $output .= $OUTPUT->box(format_module_intro('quiz', $this->_quiz, $this->_quizobj->get_cmid()), 'generalbox', 'intro');
686         }
687         $output .= $OUTPUT->box_start('generalbox', 'passwordbox');
689     /// If they have previously tried and failed to enter a password, tell them it was wrong.
690         if (!empty($enteredpassword)) {
691             $output .= '<p class="notifyproblem">' . get_string('passworderror', 'quiz') . '</p>';
692         }
694     /// Print the password entry form.
695         $output .= '<p>' . get_string('requirepasswordmessage', 'quiz') . "</p>\n";
696         $output .= '<form id="passwordform" method="post" action="' . $CFG->wwwroot .
697                 '/mod/quiz/startattempt.php" onclick="this.autocomplete=\'off\'">' . "\n";
698         $output .= "<div>\n";
699         $output .= '<label for="quizpassword">' . get_string('password') . "</label>\n";
700         $output .= '<input name="quizpassword" id="quizpassword" type="password" value=""/>' . "\n";
701         $output .= '<input name="cmid" type="hidden" value="' .
702                 $this->_quizobj->get_cmid() . '"/>' . "\n";
703         $output .= '<input name="sesskey" type="hidden" value="' . sesskey() . '"/>' . "\n";
704         $output .= '<input type="submit" value="' . get_string('ok') . '" />';
705         $output .= '<input type="submit" name="cancelpassword" value="' .
706                 get_string('cancel') . '" />' . "\n";
707         $output .= "</div>\n";
708         $output .= "</form>\n";
710     /// Finish page.
711         $output .= $OUTPUT->box_end();
713     /// return or display form.
714         echo $output;
715         echo $OUTPUT->footer();
716         exit;
717     }
720 /**
721  * A rule representing the time limit. It does not actually restrict access, but we use this
722  * class to encapsulate some of the relevant code.
723  */
724 class time_limit_access_rule extends quiz_access_rule_base {
725     public function description() {
726         return get_string('quiztimelimit', 'quiz', format_time($this->_quiz->timelimit));
727     }
728     public function time_left($attempt, $timenow) {
729         return $attempt->timestart + $this->_quiz->timelimit - $timenow;
730     }
733 /**
734  * A rule implementing the ipaddress check against the ->submet setting.
735  */
736 class securewindow_access_rule extends quiz_access_rule_base {
737     /**
738      * @var array options that should be used for opening the secure popup.
739      */
740     public static $popupoptions = array(
741         'left' => 0,
742         'top' => 0,
743         'fullscreen' => true,
744         'scrollbars' => true,
745         'resizeable' => false,
746         'directories' => false,
747         'toolbar' => false,
748         'titlebar' => false,
749         'location' => false,
750         'status' => false,
751         'menubar' => false,
752     );
754     /**
755      * Make a link to the review page for an attempt.
756      *
757      * @param string $linktext the desired link text.
758      * @param integer $attemptid the attempt id.
759      * @return string HTML for the link.
760      */
761     public function make_review_link($linktext, $attemptid) {
762         global $OUTPUT;
763         $url = $this->_quizobj->review_url($attemptid);
764         $button = new single_button($url, $linktext);
765         $button->add_action(new popup_action('click', $url, 'quizpopup', self::$popupoptions));
766         return $OUTPUT->render($button);
767     }
769     /**
770      * Do the printheader call, etc. required for a secure page, including the necessary JS.
771      *
772      * @param string $title HTML title tag content, passed to printheader.
773      * @param string $headtags extra stuff to go in the HTML head tag, passed to printheader.
774      *                $headtags has been deprectaed since Moodle 2.0
775      */
776     public function setup_secure_page($title, $headtags=null) {
777         global $OUTPUT, $PAGE;
778         $PAGE->set_popup_notification_allowed(false);//prevent message notifications
779         $PAGE->set_title($title);
780         $PAGE->set_cacheable(false);
781         $PAGE->set_pagelayout('popup');
782         $PAGE->add_body_class('quiz-secure-window');
783         $PAGE->requires->js_init_call('M.mod_quiz.secure_window.init', null, false,
784                 quiz_get_js_module());
785         echo $OUTPUT->header();
786     }
789 /**
790  * A rule representing the safe browser check.
791 */
792 class safebrowser_access_rule extends quiz_access_rule_base {
793     public function prevent_access() {
794         if (!$this->_quizobj->is_preview_user() && !quiz_check_safe_browser()) {
795             return get_string('safebrowsererror', 'quiz');
796         } else {
797             return false;
798         }
799     }
801     public function description() {
802         return get_string("safebrowsernotice", "quiz");
803     }