mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 04:22:07 +02:00
MDL-35717 quiz: fix overdue attempt processing
This commit is contained in:
parent
6109f2112c
commit
8e771aed93
@ -392,17 +392,35 @@ class quiz_access_manager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute how much time is left before this attempt must be submitted.
|
||||
* Compute when the attempt must be submitted.
|
||||
*
|
||||
* @param object $attempt the data from the relevant quiz_attempts row.
|
||||
* @return int|false the attempt close time.
|
||||
* False if there is no limit.
|
||||
*/
|
||||
public function get_end_time($attempt) {
|
||||
$timeclose = false;
|
||||
foreach ($this->rules as $rule) {
|
||||
$ruletimeclose = $rule->end_time($attempt);
|
||||
if ($ruletimeclose !== false && ($timeclose === false || $ruletimeclose < $timeclose)) {
|
||||
$timeclose = $ruletimeclose;
|
||||
}
|
||||
}
|
||||
return $timeclose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute what should be displayed to the user for time remaining in this attempt.
|
||||
*
|
||||
* @param object $attempt the data from the relevant quiz_attempts row.
|
||||
* @param int $timenow the time to consider as 'now'.
|
||||
* @return int|false the number of seconds remaining for this attempt.
|
||||
* False if there is no limit.
|
||||
* False if no limit should be displayed.
|
||||
*/
|
||||
public function get_time_left($attempt, $timenow) {
|
||||
public function get_time_left_display($attempt, $timenow) {
|
||||
$timeleft = false;
|
||||
foreach ($this->rules as $rule) {
|
||||
$ruletimeleft = $rule->time_left($attempt, $timenow);
|
||||
$ruletimeleft = $rule->time_left_display($attempt, $timenow);
|
||||
if ($ruletimeleft !== false && ($timeleft === false || $ruletimeleft < $timeleft)) {
|
||||
$timeleft = $ruletimeleft;
|
||||
}
|
||||
|
@ -180,14 +180,28 @@ abstract class quiz_access_rule_base {
|
||||
|
||||
/**
|
||||
* If, because 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.
|
||||
* you should override this method to return the attempt end time.
|
||||
* @param object $attempt the current attempt
|
||||
* @return mixed the attempt close time, or false if there is no close time.
|
||||
*/
|
||||
public function end_time($attempt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user should be shown a different amount of time than $timenow - $this->end_time(), then
|
||||
* override this method. This is useful if the time remaining is large enough to be omitted.
|
||||
* @param object $attempt the current attempt
|
||||
* @param int $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.
|
||||
* @return mixed the time left in seconds (can be negative) or false if there is no limit.
|
||||
*/
|
||||
public function time_left($attempt, $timenow) {
|
||||
return false;
|
||||
public function time_left_display($attempt, $timenow) {
|
||||
$endtime = $this->end_time($attempt);
|
||||
if ($endtime === false) {
|
||||
return false;
|
||||
}
|
||||
return $endtime - $timenow;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,7 +55,8 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
|
||||
$this->assertEmpty($rule->description());
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 0));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->prevent_new_attempt(3, $attempt));
|
||||
@ -89,7 +90,8 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
|
||||
$this->assertEmpty($rule->description());
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 0));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->prevent_new_attempt(5, $attempt));
|
||||
@ -128,7 +130,8 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
|
||||
$this->assertEmpty($rule->description());
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 0));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->prevent_new_attempt(5, $attempt));
|
||||
@ -179,7 +182,8 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
|
||||
$this->assertEmpty($rule->description());
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 0));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
|
||||
$attempt->timefinish = 13000;
|
||||
$this->assertEquals($rule->prevent_new_attempt(1, $attempt),
|
||||
@ -236,7 +240,8 @@ class quizaccess_delaybetweenattempts_testcase extends basic_testcase {
|
||||
$this->assertEmpty($rule->description());
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 0));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->prevent_new_attempt(5, $attempt));
|
||||
|
@ -56,7 +56,8 @@ class quizaccess_ipaddress_testcase extends basic_testcase {
|
||||
$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));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
|
||||
$quiz->subnet = '0.0.0.0';
|
||||
@ -68,6 +69,7 @@ class quizaccess_ipaddress_testcase extends basic_testcase {
|
||||
$this->assertEmpty($rule->description());
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 1));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ class quizaccess_numattempts_testcase extends basic_testcase {
|
||||
$this->assertTrue($rule->is_finished(666, $attempt));
|
||||
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->time_left($attempt, 1));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
}
|
||||
|
@ -93,20 +93,24 @@ class quizaccess_openclosedate extends quiz_access_rule_base {
|
||||
return $this->quiz->timeclose && $this->timenow > $this->quiz->timeclose;
|
||||
}
|
||||
|
||||
public function time_left($attempt, $timenow) {
|
||||
public function end_time($attempt) {
|
||||
if ($this->quiz->timeclose) {
|
||||
return $this->quiz->timeclose;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function time_left_display($attempt, $timenow) {
|
||||
// If this is a teacher preview after the close date, do not show
|
||||
// the time.
|
||||
if ($attempt->preview && $timenow > $this->quiz->timeclose) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, return to the time left until the close date, providing
|
||||
// that is less than QUIZ_SHOW_TIME_BEFORE_DEADLINE.
|
||||
if ($this->quiz->timeclose) {
|
||||
$timeleft = $this->quiz->timeclose - $timenow;
|
||||
if ($timeleft < QUIZ_SHOW_TIME_BEFORE_DEADLINE) {
|
||||
return $timeleft;
|
||||
}
|
||||
// Otherwise, return to the time left until the close date, providing that is
|
||||
// less than QUIZ_SHOW_TIME_BEFORE_DEADLINE.
|
||||
$endtime = $this->end_time($attempt);
|
||||
if ($endtime !== false && $timenow > $endtime - QUIZ_SHOW_TIME_BEFORE_DEADLINE) {
|
||||
return $endtime - $timenow;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -55,15 +55,17 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
|
||||
$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));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 10000));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
|
||||
$rule = new quizaccess_openclosedate($quizobj, 0);
|
||||
$this->assertEmpty($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));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
|
||||
public function test_start_date() {
|
||||
@ -85,7 +87,8 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
|
||||
get_string('notavailable', 'quizaccess_openclosedate'));
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 0));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
|
||||
$rule = new quizaccess_openclosedate($quizobj, 10000);
|
||||
$this->assertEquals($rule->description(),
|
||||
@ -93,7 +96,8 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
|
||||
$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));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
|
||||
public function test_close_date() {
|
||||
@ -114,10 +118,12 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
|
||||
$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->assertEquals($rule->time_left($attempt, 19900), 100);
|
||||
$this->assertEquals($rule->time_left($attempt, 20000), 0);
|
||||
$this->assertEquals($rule->time_left($attempt, 20100), -100);
|
||||
|
||||
$this->assertEquals($rule->end_time($attempt), 20000);
|
||||
$this->assertFalse($rule->time_left_display($attempt, 20000 - QUIZ_SHOW_TIME_BEFORE_DEADLINE));
|
||||
$this->assertEquals($rule->time_left_display($attempt, 19900), 100);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 20000), 0);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 20100), -100);
|
||||
|
||||
$rule = new quizaccess_openclosedate($quizobj, 20001);
|
||||
$this->assertEquals($rule->description(),
|
||||
@ -126,10 +132,11 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
|
||||
get_string('notavailable', 'quizaccess_openclosedate'));
|
||||
$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->assertEquals($rule->time_left($attempt, 19900), 100);
|
||||
$this->assertEquals($rule->time_left($attempt, 20000), 0);
|
||||
$this->assertEquals($rule->time_left($attempt, 20100), -100);
|
||||
$this->assertEquals($rule->end_time($attempt), 20000);
|
||||
$this->assertFalse($rule->time_left_display($attempt, 20000 - QUIZ_SHOW_TIME_BEFORE_DEADLINE));
|
||||
$this->assertEquals($rule->time_left_display($attempt, 19900), 100);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 20000), 0);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 20100), -100);
|
||||
}
|
||||
|
||||
public function test_both_dates() {
|
||||
@ -176,10 +183,11 @@ class quizaccess_openclosedate_testcase extends basic_testcase {
|
||||
$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->assertEquals($rule->time_left($attempt, 19900), 100);
|
||||
$this->assertEquals($rule->time_left($attempt, 20000), 0);
|
||||
$this->assertEquals($rule->time_left($attempt, 20100), -100);
|
||||
$this->assertEquals($rule->end_time($attempt), 20000);
|
||||
$this->assertFalse($rule->time_left_display($attempt, 20000 - QUIZ_SHOW_TIME_BEFORE_DEADLINE));
|
||||
$this->assertEquals($rule->time_left_display($attempt, 19900), 100);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 20000), 0);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 20100), -100);
|
||||
}
|
||||
|
||||
public function test_close_date_with_overdue() {
|
||||
|
@ -53,6 +53,7 @@ class quizaccess_password_testcase extends basic_testcase {
|
||||
get_string('requirepasswordmessage', 'quizaccess_password'));
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 1));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ class quizaccess_safebrowser_testcase extends basic_testcase {
|
||||
$rule->description());
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 1));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ class quizaccess_securewindow_testcase extends basic_testcase {
|
||||
$this->assertEmpty($rule->description());
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
$this->assertFalse($rule->is_finished(0, $attempt));
|
||||
$this->assertFalse($rule->time_left($attempt, 1));
|
||||
$this->assertFalse($rule->end_time($attempt));
|
||||
$this->assertFalse($rule->time_left_display($attempt, 0));
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,17 @@ class quizaccess_timelimit extends quiz_access_rule_base {
|
||||
format_time($this->quiz->timelimit));
|
||||
}
|
||||
|
||||
public function time_left($attempt, $timenow) {
|
||||
return $attempt->timestart + $this->quiz->timelimit - $timenow;
|
||||
public function end_time($attempt) {
|
||||
return $attempt->timestart + $this->quiz->timelimit;
|
||||
}
|
||||
|
||||
public function time_left_display($attempt, $timenow) {
|
||||
// If this is a teacher preview after the time limit expires, don't show the time_left
|
||||
$endtime = $this->end_time($attempt);
|
||||
if ($attempt->preview && $timenow > $endtime) {
|
||||
return false;
|
||||
}
|
||||
return $endtime - $timenow;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -51,9 +51,11 @@ class quizaccess_timelimit_testcase extends basic_testcase {
|
||||
get_string('quiztimelimit', 'quizaccess_timelimit', format_time(3600)));
|
||||
|
||||
$attempt->timestart = 10000;
|
||||
$this->assertEquals($rule->time_left($attempt, 10000), 3600);
|
||||
$this->assertEquals($rule->time_left($attempt, 12000), 1600);
|
||||
$this->assertEquals($rule->time_left($attempt, 14000), -400);
|
||||
$attempt->preview = 0;
|
||||
$this->assertEquals($rule->end_time($attempt), 13600);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 10000), 3600);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 12000), 1600);
|
||||
$this->assertEquals($rule->time_left_display($attempt, 14000), -400);
|
||||
|
||||
$this->assertFalse($rule->prevent_access());
|
||||
$this->assertFalse($rule->prevent_new_attempt(0, $attempt));
|
||||
|
@ -13,3 +13,7 @@ Overview of this plugin type at http://docs.moodle.org/dev/Quiz_access_rules
|
||||
* This plugin type now supports cron in the standard way. If required, Create a
|
||||
lib.php file containing
|
||||
function quizaccess_mypluginname_cron() {};
|
||||
|
||||
=== 2.4 ===
|
||||
|
||||
* Replaced time_left() with new time_left_display() and end_time() functions.
|
@ -1002,13 +1002,14 @@ class quiz_attempt {
|
||||
* @return int|false the number of seconds remaining for this attempt.
|
||||
* False if there is no limit.
|
||||
*/
|
||||
public function get_time_left($timenow) {
|
||||
public function get_time_left_display($timenow) {
|
||||
if ($this->attempt->state != self::IN_PROGRESS) {
|
||||
return false;
|
||||
}
|
||||
return $this->get_access_manager($timenow)->get_time_left($this->attempt, $timenow);
|
||||
return $this->get_access_manager($timenow)->get_time_left_display($this->attempt, $timenow);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return int the time when this attempt was submitted. 0 if it has not been
|
||||
* submitted yet.
|
||||
@ -1269,30 +1270,39 @@ class quiz_attempt {
|
||||
|
||||
/**
|
||||
* Check this attempt, to see if there are any state transitions that should
|
||||
* happen automatically.
|
||||
* happen automatically. This function will update the attempt checkstatetime.
|
||||
* @param int $timestamp the timestamp that should be stored as the modifed
|
||||
* @param bool $studentisonline is the student currently interacting with Moodle?
|
||||
*/
|
||||
public function handle_if_time_expired($timestamp, $studentisonline) {
|
||||
global $DB;
|
||||
|
||||
$timeleft = $this->get_access_manager($timestamp)->get_time_left($this->attempt, $timestamp);
|
||||
$timeclose = $this->get_access_manager($timestamp)->get_end_time($this->attempt);
|
||||
|
||||
if ($timeleft === false || $timeleft > 0) {
|
||||
if ($timeclose === false || $this->is_preview()) {
|
||||
$this->update_timecheckstate(null);
|
||||
return; // No time limit
|
||||
}
|
||||
if ($timestamp < $timeclose) {
|
||||
$this->update_timecheckstate($timeclose);
|
||||
return; // Time has not yet expired.
|
||||
}
|
||||
|
||||
// If the attempt is already overdue, look to see if it should be abandoned ...
|
||||
if ($this->attempt->state == self::OVERDUE) {
|
||||
$timeoverdue = -$timeleft;
|
||||
if ($timeoverdue > $this->quizobj->get_quiz()->graceperiod) {
|
||||
$timeoverdue = $timestamp - $timeclose;
|
||||
$graceperiod = $this->quizobj->get_quiz()->graceperiod;
|
||||
if ($timeoverdue >= $graceperiod) {
|
||||
$this->process_abandon($timestamp, $studentisonline);
|
||||
} else {
|
||||
// Overdue time has not yet expired
|
||||
$this->update_timecheckstate($timeclose + $graceperiod);
|
||||
}
|
||||
|
||||
return; // ... and we are done.
|
||||
}
|
||||
|
||||
if ($this->attempt->state != self::IN_PROGRESS) {
|
||||
$this->update_timecheckstate(null);
|
||||
return; // Attempt is already in a final state.
|
||||
}
|
||||
|
||||
@ -1311,6 +1321,10 @@ class quiz_attempt {
|
||||
$this->process_abandon($timestamp, $studentisonline);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is an overdue attempt with no overdue handling defined, so just abandon.
|
||||
$this->process_abandon($timestamp, $studentisonline);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1373,6 +1387,7 @@ class quiz_attempt {
|
||||
$this->attempt->timefinish = $timestamp;
|
||||
$this->attempt->sumgrades = $this->quba->get_total_mark();
|
||||
$this->attempt->state = self::FINISHED;
|
||||
$this->attempt->timecheckstate = null;
|
||||
$DB->update_record('quiz_attempts', $this->attempt);
|
||||
|
||||
if (!$this->is_preview()) {
|
||||
@ -1388,6 +1403,18 @@ class quiz_attempt {
|
||||
$transaction->allow_commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this attempt timecheckstate if necessary.
|
||||
* @param int|null the timecheckstate
|
||||
*/
|
||||
public function update_timecheckstate($time) {
|
||||
global $DB;
|
||||
if ($this->attempt->timecheckstate !== $time) {
|
||||
$this->attempt->timecheckstate = $time;
|
||||
$DB->set_field('quiz_attempts', 'timecheckstate', $time, array('id'=>$this->attempt->id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this attempt as now overdue.
|
||||
* @param int $timestamp the time to deem as now.
|
||||
@ -1399,6 +1426,9 @@ class quiz_attempt {
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$this->attempt->timemodified = $timestamp;
|
||||
$this->attempt->state = self::OVERDUE;
|
||||
// If we knew the attempt close time, we could compute when the graceperiod ends.
|
||||
// Instead we'll just fix it up through cron.
|
||||
$this->attempt->timecheckstate = $timestamp;
|
||||
$DB->update_record('quiz_attempts', $this->attempt);
|
||||
|
||||
$this->fire_state_transition_event('quiz_attempt_overdue', $timestamp);
|
||||
@ -1417,6 +1447,7 @@ class quiz_attempt {
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$this->attempt->timemodified = $timestamp;
|
||||
$this->attempt->state = self::ABANDONED;
|
||||
$this->attempt->timecheckstate = null;
|
||||
$DB->update_record('quiz_attempts', $this->attempt);
|
||||
|
||||
$this->fire_state_transition_event('quiz_attempt_abandoned', $timestamp);
|
||||
|
@ -79,7 +79,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
|
||||
|
||||
$attempt = new backup_nested_element('attempt', array('id'), array(
|
||||
'userid', 'attemptnum', 'uniqueid', 'layout', 'currentpage', 'preview',
|
||||
'state', 'timestart', 'timefinish', 'timemodified', 'sumgrades'));
|
||||
'state', 'timestart', 'timefinish', 'timemodified', 'timecheckstate', 'sumgrades'));
|
||||
|
||||
// This module is using questions, so produce the related question states and sessions
|
||||
// attaching them to the $attempt element based in 'uniqueid' matching.
|
||||
|
@ -306,6 +306,7 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
|
||||
$data->timestart = $this->apply_date_offset($data->timestart);
|
||||
$data->timefinish = $this->apply_date_offset($data->timefinish);
|
||||
$data->timemodified = $this->apply_date_offset($data->timemodified);
|
||||
$data->timecheckstate = $this->apply_date_offset($data->timecheckstate);
|
||||
|
||||
// Deals with up-grading pre-2.3 back-ups to 2.3+.
|
||||
if (!isset($data->state)) {
|
||||
|
@ -40,15 +40,13 @@ class mod_quiz_overdue_attempt_updater {
|
||||
/**
|
||||
* Do the processing required.
|
||||
* @param int $timenow the time to consider as 'now' during the processing.
|
||||
* @param int $processfrom the value of $processupto the last time update_overdue_attempts was
|
||||
* called called and completed successfully.
|
||||
* @param int $processto only process attempt modifed longer ago than this.
|
||||
* @param int $processto only process attempt with timecheckstate longer ago than this.
|
||||
* @return array with two elements, the number of attempt considered, and how many different quizzes that was.
|
||||
*/
|
||||
public function update_overdue_attempts($timenow, $processfrom, $processto) {
|
||||
public function update_overdue_attempts($timenow, $processto) {
|
||||
global $DB;
|
||||
|
||||
$attemptstoprocess = $this->get_list_of_overdue_attempts($processfrom, $processto);
|
||||
$attemptstoprocess = $this->get_list_of_overdue_attempts($processto);
|
||||
|
||||
$course = null;
|
||||
$quiz = null;
|
||||
@ -97,61 +95,27 @@ class mod_quiz_overdue_attempt_updater {
|
||||
* @return moodle_recordset of quiz_attempts that need to be processed because time has
|
||||
* passed. The array is sorted by courseid then quizid.
|
||||
*/
|
||||
protected function get_list_of_overdue_attempts($processfrom, $processto) {
|
||||
public function get_list_of_overdue_attempts($processto) {
|
||||
global $DB;
|
||||
|
||||
|
||||
// SQL to compute timeclose and timelimit for each attempt:
|
||||
$quizausersql = quiz_get_attempt_usertime_sql();
|
||||
|
||||
// This query should have all the quiz_attempts columns.
|
||||
return $DB->get_recordset_sql("
|
||||
SELECT quiza.*,
|
||||
group_by_results.usertimeclose,
|
||||
group_by_results.usertimelimit
|
||||
quizauser.usertimeclose,
|
||||
quizauser.usertimelimit
|
||||
|
||||
FROM (
|
||||
FROM {quiz_attempts} quiza
|
||||
JOIN {quiz} quiz ON quiz.id = quiza.quiz
|
||||
JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
|
||||
|
||||
SELECT iquiza.id AS attemptid,
|
||||
quiz.course,
|
||||
quiz.graceperiod,
|
||||
COALESCE(quo.timeclose, MAX(qgo.timeclose), quiz.timeclose) AS usertimeclose,
|
||||
COALESCE(quo.timelimit, MAX(qgo.timelimit), quiz.timelimit) AS usertimelimit
|
||||
WHERE quiza.state IN ('inprogress', 'overdue')
|
||||
AND quiza.timecheckstate <= :processto
|
||||
ORDER BY quiz.course, quiza.quiz",
|
||||
|
||||
FROM {quiz_attempts} iquiza
|
||||
JOIN {quiz} quiz ON quiz.id = iquiza.quiz
|
||||
LEFT JOIN {quiz_overrides} quo ON quo.quiz = quiz.id AND quo.userid = iquiza.userid
|
||||
LEFT JOIN {groups_members} gm ON gm.userid = iquiza.userid
|
||||
LEFT JOIN {quiz_overrides} qgo ON qgo.quiz = quiz.id AND qgo.groupid = gm.groupid
|
||||
|
||||
WHERE iquiza.state IN ('inprogress', 'overdue')
|
||||
AND iquiza.timemodified >= :processfrom
|
||||
AND iquiza.timemodified < :processto
|
||||
|
||||
GROUP BY iquiza.id,
|
||||
quiz.course,
|
||||
quiz.timeclose,
|
||||
quiz.timelimit,
|
||||
quiz.graceperiod,
|
||||
quo.timeclose,
|
||||
quo.timelimit
|
||||
) group_by_results
|
||||
JOIN {quiz_attempts} quiza ON quiza.id = group_by_results.attemptid
|
||||
|
||||
WHERE (
|
||||
state = 'inprogress' AND (
|
||||
(usertimeclose > 0 AND :timenow1 > usertimeclose) OR
|
||||
(usertimelimit > 0 AND :timenow2 > quiza.timestart + usertimelimit)
|
||||
)
|
||||
)
|
||||
OR
|
||||
(
|
||||
state = 'overdue' AND (
|
||||
(usertimeclose > 0 AND :timenow3 > graceperiod + usertimeclose) OR
|
||||
(usertimelimit > 0 AND :timenow4 > graceperiod + quiza.timestart + usertimelimit)
|
||||
)
|
||||
)
|
||||
|
||||
ORDER BY course, quiz",
|
||||
|
||||
array('processfrom' => $processfrom, 'processto' => $processto,
|
||||
'timenow1' => $processto, 'timenow2' => $processto,
|
||||
'timenow3' => $processto, 'timenow4' => $processto));
|
||||
array('processto' => $processto));
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,29 @@ $handlers = array(
|
||||
'handlerfunction' => 'quiz_attempt_overdue_handler',
|
||||
'schedule' => 'cron',
|
||||
),
|
||||
|
||||
// Handle group events, so that open quiz attempts with group overrides get
|
||||
// updated check times.
|
||||
'groups_member_added' => array (
|
||||
'handlerfile' => '/mod/quiz/locallib.php',
|
||||
'handlerfunction' => 'quiz_groups_member_added_handler',
|
||||
'schedule' => 'instant',
|
||||
),
|
||||
'groups_member_removed' => array (
|
||||
'handlerfile' => '/mod/quiz/locallib.php',
|
||||
'handlerfunction' => 'quiz_groups_member_removed_handler',
|
||||
'schedule' => 'instant',
|
||||
),
|
||||
'groups_members_removed' => array (
|
||||
'handlerfile' => '/mod/quiz/locallib.php',
|
||||
'handlerfunction' => 'quiz_groups_members_removed_handler',
|
||||
'schedule' => 'instant',
|
||||
),
|
||||
'groups_group_deleted' => array (
|
||||
'handlerfile' => '/mod/quiz/locallib.php',
|
||||
'handlerfunction' => 'quiz_groups_group_deleted_handler',
|
||||
'schedule' => 'instant',
|
||||
),
|
||||
);
|
||||
|
||||
/* List of events generated by the quiz module, with the fields on the event object.
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="mod/quiz/db" VERSION="20120122" COMMENT="XMLDB file for Moodle mod/quiz"
|
||||
<XMLDB PATH="mod/quiz/db" VERSION="20121006" COMMENT="XMLDB file for Moodle mod/quiz"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@ -9,7 +9,7 @@
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" COMMENT="Standard Moodle primary key." NEXT="course"/>
|
||||
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key reference to the course this quiz is part of." PREVIOUS="id" NEXT="name"/>
|
||||
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Quiz name." PREVIOUS="course" NEXT="intro"/>
|
||||
<FIELD NAME="intro" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" COMMENT="Quiz introduction text." PREVIOUS="name" NEXT="introformat"/>
|
||||
<FIELD NAME="intro" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Quiz introduction text." PREVIOUS="name" NEXT="introformat"/>
|
||||
<FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Quiz intro text format." PREVIOUS="intro" NEXT="timeopen"/>
|
||||
<FIELD NAME="timeopen" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time when this quiz opens. (0 = no restriction.)" PREVIOUS="introformat" NEXT="timeclose"/>
|
||||
<FIELD NAME="timeclose" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time when this quiz closes. (0 = no restriction.)" PREVIOUS="timeopen" NEXT="timelimit"/>
|
||||
@ -33,7 +33,7 @@
|
||||
<FIELD NAME="navmethod" TYPE="char" LENGTH="16" NOTNULL="true" DEFAULT="free" SEQUENCE="false" COMMENT="Any constraints on how the user is allowed to navigate around the quiz. Currently recognised values are 'free' and 'seq'." PREVIOUS="questionsperpage" NEXT="shufflequestions"/>
|
||||
<FIELD NAME="shufflequestions" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether the question order should be shuffled for each attempt." PREVIOUS="navmethod" NEXT="shuffleanswers"/>
|
||||
<FIELD NAME="shuffleanswers" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether the parts of the question should be shuffled, in those question types that support it." PREVIOUS="shufflequestions" NEXT="questions"/>
|
||||
<FIELD NAME="questions" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" COMMENT="Comma-separated list of question ids, with 0s for page breaks. The quiz layout. See also the quiz_question_instances table." PREVIOUS="shuffleanswers" NEXT="sumgrades"/>
|
||||
<FIELD NAME="questions" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Comma-separated list of question ids, with 0s for page breaks. The quiz layout. See also the quiz_question_instances table." PREVIOUS="shuffleanswers" NEXT="sumgrades"/>
|
||||
<FIELD NAME="sumgrades" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The total of all the question instance maxmarks." PREVIOUS="questions" NEXT="grade"/>
|
||||
<FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="The total that the quiz overall grade is scaled to be out of." PREVIOUS="sumgrades" NEXT="timecreated"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time when the quiz was added to the course." PREVIOUS="grade" NEXT="timemodified"/>
|
||||
@ -60,14 +60,15 @@
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key reference to the user whose attempt this is." PREVIOUS="quiz" NEXT="attempt"/>
|
||||
<FIELD NAME="attempt" TYPE="int" LENGTH="6" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Sequentially numbers this student's attempts at this quiz." PREVIOUS="userid" NEXT="uniqueid"/>
|
||||
<FIELD NAME="uniqueid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Foreign key reference to the question_usage that holds the details of the the question_attempts that make up this quiz attempt." PREVIOUS="attempt" NEXT="layout"/>
|
||||
<FIELD NAME="layout" TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false" PREVIOUS="uniqueid" NEXT="currentpage"/>
|
||||
<FIELD NAME="layout" TYPE="text" NOTNULL="true" SEQUENCE="false" PREVIOUS="uniqueid" NEXT="currentpage"/>
|
||||
<FIELD NAME="currentpage" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="layout" NEXT="preview"/>
|
||||
<FIELD NAME="preview" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="currentpage" NEXT="state"/>
|
||||
<FIELD NAME="state" TYPE="char" LENGTH="16" NOTNULL="true" DEFAULT="inprogress" SEQUENCE="false" COMMENT="The current state of the attempts. 'inprogress', 'overdue', 'finished' or 'abandoned'." PREVIOUS="preview" NEXT="timestart"/>
|
||||
<FIELD NAME="timestart" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time when the attempt was started." PREVIOUS="state" NEXT="timefinish"/>
|
||||
<FIELD NAME="timefinish" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time when the attempt was submitted. 0 if the attempt has not been submitted yet." PREVIOUS="timestart" NEXT="timemodified"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Last modified time." PREVIOUS="timefinish" NEXT="sumgrades"/>
|
||||
<FIELD NAME="sumgrades" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Total marks for this attempt." PREVIOUS="timemodified" NEXT="needsupgradetonewqe"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Last modified time." PREVIOUS="timefinish" NEXT="timecheckstate"/>
|
||||
<FIELD NAME="timecheckstate" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Next time quiz cron should check attempt for state changes. NULL means never check." PREVIOUS="timemodified" NEXT="sumgrades"/>
|
||||
<FIELD NAME="sumgrades" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Total marks for this attempt." PREVIOUS="timecheckstate" NEXT="needsupgradetonewqe"/>
|
||||
<FIELD NAME="needsupgradetonewqe" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Used during the upgrade from Moodle 2.0 to 2.1. This will be removed in the future." PREVIOUS="sumgrades"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
@ -77,7 +78,8 @@
|
||||
<KEY NAME="uniqueid" TYPE="foreign-unique" FIELDS="uniqueid" REFTABLE="question_usages" REFFIELDS="id" PREVIOUS="userid"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="quiz-userid-attempt" UNIQUE="true" FIELDS="quiz, userid, attempt"/>
|
||||
<INDEX NAME="quiz-userid-attempt" UNIQUE="true" FIELDS="quiz, userid, attempt" NEXT="state-timecheckstate"/>
|
||||
<INDEX NAME="state-timecheckstate" UNIQUE="false" FIELDS="state, timecheckstate" PREVIOUS="quiz-userid-attempt"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="quiz_grades" COMMENT="Stores the overall grade for each user on the quiz, based on their various attempts and the quiz.grademethod setting." PREVIOUS="quiz_attempts" NEXT="quiz_question_instances">
|
||||
@ -157,4 +159,4 @@
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
@ -360,6 +360,38 @@ function xmldb_quiz_upgrade($oldversion) {
|
||||
upgrade_mod_savepoint(true, 2012061703, 'quiz');
|
||||
}
|
||||
|
||||
if ($oldversion < 2012100801) {
|
||||
|
||||
// Define field timecheckstate to be added to quiz_attempts
|
||||
$table = new xmldb_table('quiz_attempts');
|
||||
$field = new xmldb_field('timecheckstate', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'timemodified');
|
||||
|
||||
// Conditionally launch add field timecheckstate
|
||||
if (!$dbman->field_exists($table, $field)) {
|
||||
$dbman->add_field($table, $field);
|
||||
}
|
||||
|
||||
// Define index state-timecheckstate (not unique) to be added to quiz_attempts
|
||||
$table = new xmldb_table('quiz_attempts');
|
||||
$index = new xmldb_index('state-timecheckstate', XMLDB_INDEX_NOTUNIQUE, array('state', 'timecheckstate'));
|
||||
|
||||
// Conditionally launch add index state-timecheckstate
|
||||
if (!$dbman->index_exists($table, $index)) {
|
||||
$dbman->add_index($table, $index);
|
||||
}
|
||||
|
||||
// Overdue cron no longer needs these
|
||||
unset_config('overduelastrun', 'quiz');
|
||||
unset_config('overduedoneto', 'quiz');
|
||||
|
||||
// Update timecheckstate on all open attempts
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
quiz_update_open_attempts(array());
|
||||
|
||||
// quiz savepoint reached
|
||||
upgrade_mod_savepoint(true, 2012100801, 'quiz');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,14 @@ function quiz_update_instance($quiz, $mform) {
|
||||
quiz_update_grades($quiz);
|
||||
}
|
||||
|
||||
$updateattempts = $oldquiz->timelimit != $quiz->timelimit
|
||||
|| $oldquiz->timeclose != $quiz->timeclose
|
||||
|| $oldquiz->graceperiod != $quiz->graceperiod;
|
||||
if ($updateattempts) {
|
||||
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
|
||||
quiz_update_open_attempts(array('quizid'=>$quiz->id));
|
||||
}
|
||||
|
||||
// Delete any previous preview attempts.
|
||||
quiz_delete_previews($quiz);
|
||||
|
||||
@ -284,13 +292,25 @@ function quiz_update_effective_access($quiz, $userid) {
|
||||
$override->timeopen = min($opens);
|
||||
}
|
||||
if (is_null($override->timeclose) && count($closes)) {
|
||||
$override->timeclose = max($closes);
|
||||
if (in_array(0, $closes)) {
|
||||
$override->timeclose = 0;
|
||||
} else {
|
||||
$override->timeclose = max($closes);
|
||||
}
|
||||
}
|
||||
if (is_null($override->timelimit) && count($limits)) {
|
||||
$override->timelimit = max($limits);
|
||||
if (in_array(0, $limits)) {
|
||||
$override->timelimit = 0;
|
||||
} else {
|
||||
$override->timelimit = max($limits);
|
||||
}
|
||||
}
|
||||
if (is_null($override->attempts) && count($attempts)) {
|
||||
$override->attempts = max($attempts);
|
||||
if (in_array(0, $attempts)) {
|
||||
$override->attempts = 0;
|
||||
} else {
|
||||
$override->attempts = max($attempts);
|
||||
}
|
||||
}
|
||||
if (is_null($override->password) && count($passwords)) {
|
||||
$override->password = array_shift($passwords);
|
||||
@ -446,32 +466,20 @@ function quiz_user_complete($course, $user, $mod, $quiz) {
|
||||
*/
|
||||
function quiz_cron() {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/mod/quiz/cronlib.php');
|
||||
mtrace('');
|
||||
|
||||
// Since the quiz specifies $module->cron = 60, so that the subplugins can
|
||||
// have frequent cron if they need it, we now need to do our own scheduling.
|
||||
$quizconfig = get_config('quiz');
|
||||
if (!isset($quizconfig->overduelastrun)) {
|
||||
$quizconfig->overduelastrun = 0;
|
||||
$quizconfig->overduedoneto = 0;
|
||||
}
|
||||
|
||||
$timenow = time();
|
||||
if ($timenow > $quizconfig->overduelastrun + 3600) {
|
||||
require_once($CFG->dirroot . '/mod/quiz/cronlib.php');
|
||||
$overduehander = new mod_quiz_overdue_attempt_updater();
|
||||
$overduehander = new mod_quiz_overdue_attempt_updater();
|
||||
|
||||
$processto = $timenow - $quizconfig->graceperiodmin;
|
||||
$processto = $timenow - get_config('quiz', 'graceperiodmin');
|
||||
|
||||
mtrace(' Looking for quiz overdue quiz attempts between ' .
|
||||
userdate($quizconfig->overduedoneto) . ' and ' . userdate($processto) . '...');
|
||||
mtrace(' Looking for quiz overdue quiz attempts...');
|
||||
|
||||
list($count, $quizcount) = $overduehander->update_overdue_attempts($timenow, $quizconfig->overduedoneto, $processto);
|
||||
set_config('overduelastrun', $timenow, 'quiz');
|
||||
set_config('overduedoneto', $processto, 'quiz');
|
||||
list($count, $quizcount) = $overduehander->update_overdue_attempts($timenow, $processto);
|
||||
|
||||
mtrace(' Considered ' . $count . ' attempts in ' . $quizcount . ' quizzes.');
|
||||
}
|
||||
mtrace(' Considered ' . $count . ' attempts in ' . $quizcount . ' quizzes.');
|
||||
|
||||
// Run cron for our sub-plugin types.
|
||||
cron_execute_plugin_type('quiz', 'quiz reports');
|
||||
|
@ -65,7 +65,7 @@ define('QUIZ_MIN_TIME_TO_CONTINUE', '2');
|
||||
* user starting at the current time. The ->id field is not set. The object is
|
||||
* NOT written to the database.
|
||||
*
|
||||
* @param object $quiz the quiz to create an attempt for.
|
||||
* @param object $quizobj the quiz object to create an attempt for.
|
||||
* @param int $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.
|
||||
@ -74,9 +74,10 @@ define('QUIZ_MIN_TIME_TO_CONTINUE', '2');
|
||||
*
|
||||
* @return object the newly created attempt object.
|
||||
*/
|
||||
function quiz_create_attempt($quiz, $attemptnumber, $lastattempt, $timenow, $ispreview = false) {
|
||||
function quiz_create_attempt(quiz $quizobj, $attemptnumber, $lastattempt, $timenow, $ispreview = false) {
|
||||
global $USER;
|
||||
|
||||
$quiz = $quizobj->get_quiz();
|
||||
if ($quiz->sumgrades < 0.000005 && $quiz->grade > 0.000005) {
|
||||
throw new moodle_exception('cannotstartgradesmismatch', 'quiz',
|
||||
new moodle_url('/mod/quiz/view.php', array('q' => $quiz->id)),
|
||||
@ -112,6 +113,13 @@ function quiz_create_attempt($quiz, $attemptnumber, $lastattempt, $timenow, $isp
|
||||
$attempt->preview = 1;
|
||||
}
|
||||
|
||||
$timeclose = $quizobj->get_access_manager($timenow)->get_end_time($attempt);
|
||||
if ($timeclose === false || $ispreview) {
|
||||
$attempt->timecheckstate = null;
|
||||
} else {
|
||||
$attempt->timecheckstate = $timeclose;
|
||||
}
|
||||
|
||||
return $attempt;
|
||||
}
|
||||
|
||||
@ -754,6 +762,142 @@ function quiz_update_all_final_grades($quiz) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Efficiently update check state time on all open attempts
|
||||
*
|
||||
* @param array $conditions optional restrictions on which attempts to update
|
||||
* Allowed conditions:
|
||||
* courseid => (array|int) attempts in given course(s)
|
||||
* userid => (array|int) attempts for given user(s)
|
||||
* quizid => (array|int) attempts in given quiz(s)
|
||||
* groupid => (array|int) quizzes with some override for given group(s)
|
||||
*
|
||||
*/
|
||||
function quiz_update_open_attempts(array $conditions) {
|
||||
global $DB;
|
||||
|
||||
foreach ($conditions as &$value) {
|
||||
if (!is_array($value)) {
|
||||
$value = array($value);
|
||||
}
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$coursecond = '';
|
||||
$usercond = '';
|
||||
$quizcond = '';
|
||||
$groupcond = '';
|
||||
|
||||
if (isset($conditions['courseid'])) {
|
||||
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['courseid'], SQL_PARAMS_NAMED, 'cid');
|
||||
$params = array_merge($params, $inparams);
|
||||
$coursecond = "AND quiza.quiz IN (SELECT q.id FROM {quiz} q WHERE q.course $incond)";
|
||||
}
|
||||
if (isset($conditions['userid'])) {
|
||||
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['userid'], SQL_PARAMS_NAMED, 'uid');
|
||||
$params = array_merge($params, $inparams);
|
||||
$usercond = "AND quiza.userid $incond";
|
||||
}
|
||||
if (isset($conditions['quizid'])) {
|
||||
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['quizid'], SQL_PARAMS_NAMED, 'qid');
|
||||
$params = array_merge($params, $inparams);
|
||||
$quizcond = "AND quiza.quiz $incond";
|
||||
}
|
||||
if (isset($conditions['groupid'])) {
|
||||
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['groupid'], SQL_PARAMS_NAMED, 'gid');
|
||||
$params = array_merge($params, $inparams);
|
||||
$groupcond = "AND quiza.quiz IN (SELECT qo.quiz FROM {quiz_overrides} qo WHERE qo.groupid $incond)";
|
||||
}
|
||||
|
||||
// SQL to compute timeclose and timelimit for each attempt:
|
||||
$quizausersql = quiz_get_attempt_usertime_sql();
|
||||
|
||||
// SQL to compute the new timecheckstate
|
||||
$timecheckstatesql = "
|
||||
CASE WHEN quizauser.usertimelimit = 0 AND quizauser.usertimeclose = 0 THEN NULL
|
||||
WHEN quizauser.usertimelimit = 0 THEN quizauser.usertimeclose
|
||||
WHEN quizauser.usertimeclose = 0 THEN quiza.timestart + quizauser.usertimelimit
|
||||
WHEN quiza.timestart + quizauser.usertimelimit < quizauser.usertimeclose THEN quiza.timestart + quizauser.usertimelimit
|
||||
ELSE quizauser.usertimeclose END +
|
||||
CASE WHEN quiza.state = 'overdue' THEN quiz.graceperiod ELSE 0 END";
|
||||
|
||||
// SQL to select which attempts to process
|
||||
$attemptselect = " quiza.state IN ('inprogress', 'overdue')
|
||||
$coursecond
|
||||
$usercond
|
||||
$quizcond
|
||||
$groupcond";
|
||||
|
||||
/*
|
||||
* Each database handles updates with inner joins differently:
|
||||
* - mysql does not allow a FROM clause
|
||||
* - postgres and mssql allow FROM but handle table aliases differently
|
||||
* - oracle requires a subquery
|
||||
*
|
||||
* Different code for each database.
|
||||
*/
|
||||
|
||||
$dbfamily = $DB->get_dbfamily();
|
||||
if ($dbfamily == 'mysql') {
|
||||
$updatesql = "UPDATE {quiz_attempts} quiza
|
||||
JOIN {quiz} quiz ON quiz.id = quiza.quiz
|
||||
JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
|
||||
SET quiza.timecheckstate = $timecheckstatesql
|
||||
WHERE $attemptselect";
|
||||
} else if ($dbfamily == 'postgres') {
|
||||
$updatesql = "UPDATE {quiz_attempts} quiza
|
||||
SET timecheckstate = $timecheckstatesql
|
||||
FROM {quiz} quiz, ( $quizausersql ) quizauser
|
||||
WHERE quiz.id = quiza.quiz
|
||||
AND quizauser.id = quiza.id
|
||||
AND $attemptselect";
|
||||
} else if ($dbfamily == 'mssql') {
|
||||
$updatesql = "UPDATE quiza
|
||||
SET timecheckstate = $timecheckstatesql
|
||||
FROM {quiz_attempts} quiza
|
||||
JOIN {quiz} quiz ON quiz.id = quiza.quiz
|
||||
JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
|
||||
WHERE $attemptselect";
|
||||
} else {
|
||||
// oracle, sqlite and others
|
||||
$updatesql = "UPDATE {quiz_attempts} quiza
|
||||
SET timecheckstate = (
|
||||
SELECT $timecheckstatesql
|
||||
FROM {quiz} quiz, ( $quizausersql ) quizauser
|
||||
WHERE quiz.id = quiza.quiz
|
||||
AND quizauser.id = quiza.id
|
||||
)
|
||||
WHERE $attemptselect";
|
||||
}
|
||||
|
||||
$DB->execute($updatesql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SQL to compute timeclose and timelimit for every attempt, taking into account user and group overrides.
|
||||
*
|
||||
* @return string SQL select with columns attempt.id, usertimeclose, usertimelimit
|
||||
*/
|
||||
function quiz_get_attempt_usertime_sql() {
|
||||
// The multiple qgo JOINS are necessary because we want timeclose/timelimit = 0 (unlimited) to supercede
|
||||
// any other group override
|
||||
$quizausersql = "
|
||||
SELECT iquiza.id,
|
||||
COALESCE(MAX(quo.timeclose), MAX(qgo1.timeclose), MAX(qgo2.timeclose), iquiz.timeclose) AS usertimeclose,
|
||||
COALESCE(MAX(quo.timelimit), MAX(qgo3.timelimit), MAX(qgo4.timelimit), iquiz.timelimit) AS usertimelimit
|
||||
|
||||
FROM {quiz_attempts} iquiza
|
||||
JOIN {quiz} iquiz ON iquiz.id = iquiza.quiz
|
||||
LEFT JOIN {quiz_overrides} quo ON quo.quiz = iquiza.quiz AND quo.userid = iquiza.userid
|
||||
LEFT JOIN {groups_members} gm ON gm.userid = iquiza.userid
|
||||
LEFT JOIN {quiz_overrides} qgo1 ON qgo1.quiz = iquiza.quiz AND qgo1.groupid = gm.groupid AND qgo1.timeclose = 0
|
||||
LEFT JOIN {quiz_overrides} qgo2 ON qgo2.quiz = iquiza.quiz AND qgo2.groupid = gm.groupid AND qgo2.timeclose > 0
|
||||
LEFT JOIN {quiz_overrides} qgo3 ON qgo3.quiz = iquiza.quiz AND qgo3.groupid = gm.groupid AND qgo3.timelimit = 0
|
||||
LEFT JOIN {quiz_overrides} qgo4 ON qgo4.quiz = iquiza.quiz AND qgo4.groupid = gm.groupid AND qgo4.timelimit > 0
|
||||
GROUP BY iquiza.id, iquiz.id, iquiz.timeclose, iquiz.timelimit";
|
||||
return $quizausersql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attempt with the best grade for a quiz
|
||||
*
|
||||
@ -1445,6 +1589,58 @@ function quiz_attempt_overdue_handler($event) {
|
||||
context_module::instance($cm->id), $cm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle groups_member_added event
|
||||
*
|
||||
* @param object $event the event object.
|
||||
*/
|
||||
function quiz_groups_member_added_handler($event) {
|
||||
quiz_update_open_attempts(array('userid'=>$event->userid, 'groupid'=>$event->groupid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle groups_member_removed event
|
||||
*
|
||||
* @param object $event the event object.
|
||||
*/
|
||||
function quiz_groups_member_removed_handler($event) {
|
||||
quiz_update_open_attempts(array('userid'=>$event->userid, 'groupid'=>$event->groupid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle groups_group_deleted event
|
||||
*
|
||||
* @param object $event the event object.
|
||||
*/
|
||||
function quiz_groups_group_deleted_handler($event) {
|
||||
global $DB;
|
||||
|
||||
// It would be nice if we got the groupid that was deleted.
|
||||
// Instead, we just update all quizzes with orphaned group overrides
|
||||
$sql = "SELECT o.id, o.quiz
|
||||
FROM {quiz_overrides} o
|
||||
JOIN {quiz} quiz ON quiz.id = o.quiz
|
||||
LEFT JOIN {groups} grp ON grp.id = o.groupid
|
||||
WHERE quiz.course = :courseid AND grp.id IS NULL";
|
||||
$params = array('courseid'=>$event->courseid);
|
||||
$records = $DB->get_records_sql_menu($sql, $params);
|
||||
$DB->delete_records_list('quiz_overrides', 'id', array_keys($records));
|
||||
quiz_update_open_attempts(array('quizid'=>array_unique(array_values($records))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle groups_members_removed event
|
||||
*
|
||||
* @param object $event the event object.
|
||||
*/
|
||||
function quiz_groups_members_removed_handler($event) {
|
||||
if ($event->userid == 0) {
|
||||
quiz_update_open_attempts(array('courseid'=>$event->courseid));
|
||||
} else {
|
||||
quiz_update_open_attempts(array('courseid'=>$event->courseid, 'userid'=>$event->userid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the information about the standard quiz JavaScript module.
|
||||
* @return array a standard jsmodule structure.
|
||||
|
@ -50,6 +50,9 @@ M.mod_quiz.timer = {
|
||||
|
||||
// Timestamp at which time runs out, according to the student's computer's clock.
|
||||
endtime: 0,
|
||||
|
||||
// Is this a quiz preview?
|
||||
preview: 0,
|
||||
|
||||
// This records the id of the timeout that updates the clock periodically,
|
||||
// so we can cancel.
|
||||
@ -57,11 +60,13 @@ M.mod_quiz.timer = {
|
||||
|
||||
/**
|
||||
* @param Y the YUI object
|
||||
* @param timeleft, the time remaining, in seconds.
|
||||
* @param start, the timer starting time, in seconds.
|
||||
* @param preview, is this a quiz preview?
|
||||
*/
|
||||
init: function(Y, timeleft) {
|
||||
init: function(Y, start, preview) {
|
||||
M.mod_quiz.timer.Y = Y;
|
||||
M.mod_quiz.timer.endtime = new Date().getTime() + timeleft*1000;
|
||||
M.mod_quiz.timer.endtime = new Date().getTime() + start*1000;
|
||||
M.mod_quiz.timer.preview = preview;
|
||||
M.mod_quiz.timer.update();
|
||||
Y.one('#quiz-timer').setStyle('display', 'block');
|
||||
},
|
||||
@ -90,6 +95,12 @@ M.mod_quiz.timer = {
|
||||
update: function() {
|
||||
var Y = M.mod_quiz.timer.Y;
|
||||
var secondsleft = Math.floor((M.mod_quiz.timer.endtime - new Date().getTime())/1000);
|
||||
|
||||
// If this is a preview and time expired, display timeleft 0 and don't renew the timer.
|
||||
if (M.mod_quiz.timer.preview && secondsleft < 0) {
|
||||
Y.one('#quiz-time-left').setContent('0:00:00');
|
||||
return;
|
||||
}
|
||||
|
||||
// If time has expired, Set the hidden form field that says time has expired.
|
||||
if (secondsleft < 0) {
|
||||
|
@ -169,6 +169,7 @@ if ($mform->is_cancelled()) {
|
||||
$fromform->id = $DB->insert_record('quiz_overrides', $fromform);
|
||||
}
|
||||
|
||||
quiz_update_open_attempts(array('quizid'=>$quiz->id));
|
||||
quiz_update_events($quiz, $fromform);
|
||||
|
||||
add_to_log($cm->course, 'quiz', 'edit override',
|
||||
|
@ -67,12 +67,17 @@ if ($page == -1) {
|
||||
// to show the student another page of the quiz. Just finish now.
|
||||
$graceperiodmin = null;
|
||||
$accessmanager = $attemptobj->get_access_manager($timenow);
|
||||
$timeleft = $accessmanager->get_time_left($attemptobj->get_attempt(), $timenow);
|
||||
$timeclose = $accessmanager->get_end_time($attemptobj->get_attempt());
|
||||
|
||||
// Don't enforce timeclose for previews
|
||||
if ($attemptobj->is_preview()) {
|
||||
$timeclose = false;
|
||||
}
|
||||
$toolate = false;
|
||||
if ($timeleft !== false && $timeleft < QUIZ_MIN_TIME_TO_CONTINUE) {
|
||||
if ($timeclose !== false && $timenow > $timeclose - QUIZ_MIN_TIME_TO_CONTINUE) {
|
||||
$timeup = true;
|
||||
$graceperiodmin = get_config('quiz', 'graceperiodmin');
|
||||
if ($timeleft < -$graceperiodmin) {
|
||||
if ($timenow > $timeclose + $graceperiodmin) {
|
||||
$toolate = true;
|
||||
}
|
||||
}
|
||||
@ -105,7 +110,7 @@ if ($timeup) {
|
||||
if (is_null($graceperiodmin)) {
|
||||
$graceperiodmin = get_config('quiz', 'graceperiodmin');
|
||||
}
|
||||
if ($timeleft < -$attemptobj->get_quiz()->graceperiod - $graceperiodmin) {
|
||||
if ($timenow > $timeclose + $attemptobj->get_quiz()->graceperiod + $graceperiodmin) {
|
||||
// Grace period has run out.
|
||||
$finishattempt = true;
|
||||
$becomingabandoned = true;
|
||||
|
@ -265,12 +265,16 @@ class mod_quiz_renderer extends plugin_renderer_base {
|
||||
*/
|
||||
public function countdown_timer(quiz_attempt $attemptobj, $timenow) {
|
||||
|
||||
$timeleft = $attemptobj->get_time_left($timenow);
|
||||
$timeleft = $attemptobj->get_time_left_display($timenow);
|
||||
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);
|
||||
$this->initialise_timer($timerstartvalue);
|
||||
$ispreview = $attemptobj->is_preview();
|
||||
$timerstartvalue = $timeleft;
|
||||
if (!$ispreview) {
|
||||
// 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($timerstartvalue, 1);
|
||||
}
|
||||
$this->initialise_timer($timerstartvalue, $ispreview);
|
||||
}
|
||||
|
||||
return html_writer::tag('div', get_string('timeleft', 'quiz') . ' ' .
|
||||
@ -486,9 +490,9 @@ class mod_quiz_renderer extends plugin_renderer_base {
|
||||
* Output the JavaScript required to initialise the countdown timer.
|
||||
* @param int $timerstartvalue time remaining, in seconds.
|
||||
*/
|
||||
public function initialise_timer($timerstartvalue) {
|
||||
$this->page->requires->js_init_call('M.mod_quiz.timer.init',
|
||||
array($timerstartvalue), false, quiz_get_js_module());
|
||||
public function initialise_timer($timerstartvalue, $ispreview) {
|
||||
$options = array($timerstartvalue, (bool)$ispreview);
|
||||
$this->page->requires->js_init_call('M.mod_quiz.timer.init', $options, false, quiz_get_js_module());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,7 +165,7 @@ $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
|
||||
|
||||
// Create the new attempt and initialize the question sessions
|
||||
$timenow = time(); // Update time now, in case the server is running really slowly.
|
||||
$attempt = quiz_create_attempt($quizobj->get_quiz(), $attemptnumber, $lastattempt, $timenow,
|
||||
$attempt = quiz_create_attempt($quizobj, $attemptnumber, $lastattempt, $timenow,
|
||||
$quizobj->is_preview_user());
|
||||
|
||||
if (!($quizobj->get_quiz()->attemptonlast && $lastattempt)) {
|
||||
|
388
mod/quiz/tests/attempts_test.php
Normal file
388
mod/quiz/tests/attempts_test.php
Normal file
@ -0,0 +1,388 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Quiz attempt overdue handling tests
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category phpunit
|
||||
* @copyright 2012 Matt Petro
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot.'/group/lib.php');
|
||||
|
||||
/**
|
||||
* Unit tests for quiz attempt overdue handling
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category phpunit
|
||||
* @copyright 2012 Matt Petro
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Test the functions quiz_update_open_attempts() and get_list_of_overdue_attempts()
|
||||
*/
|
||||
public function test_bulk_update_functions() {
|
||||
global $DB,$CFG;
|
||||
|
||||
require_once($CFG->dirroot.'/mod/quiz/cronlib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Setup course, user and groups
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname'=>'student'));
|
||||
$this->assertNotEmpty($studentrole);
|
||||
$this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
|
||||
$group1 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
|
||||
$group2 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
|
||||
$group3 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
|
||||
$this->assertTrue(groups_add_member($group1, $user1));
|
||||
$this->assertTrue(groups_add_member($group2, $user1));
|
||||
|
||||
$uniqueid = 0;
|
||||
$usertimes = array();
|
||||
|
||||
$quiz_generator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
|
||||
// Basic quiz settings
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, 'message'=>'Test1A');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>1800));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>1800, 'message'=>'Test1B');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>0, 'message'=>'Test1C');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>600, 'message'=>'Test1D');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>0));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>0, 'message'=>'Test1E');
|
||||
|
||||
// Group overrides
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>null));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>0, 'message'=>'Test2A');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1100, 'timelimit'=>null));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1100, 'timelimit'=>0, 'message'=>'Test2B');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>700));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>700, 'message'=>'Test2C');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>500));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>500, 'message'=>'Test2D');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>0));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>0, '', 'message'=>'Test2E');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>500, '', 'message'=>'Test2F');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>500, '', 'message'=>'Test2G');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group3->id, 'timeclose'=>1300, 'timelimit'=>500)); // user not in group
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, '', 'message'=>'Test2H');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, '', 'message'=>'Test2I');
|
||||
|
||||
// Multiple group overrides
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>501));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1301, 'timelimit'=>500));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3A');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3B');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1301, 'timelimit'=>500));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1300, 'timelimit'=>501));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3C');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3D');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1301, 'timelimit'=>500));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1300, 'timelimit'=>501));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group3->id, 'timeclose'=>1500, 'timelimit'=>1000)); // user not in group
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3E');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3F');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>null, 'timelimit'=>501));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>501, '', 'message'=>'Test3G');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>501, '', 'message'=>'Test3H');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1301, 'timelimit'=>null));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>500, '', 'message'=>'Test3I');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>500, '', 'message'=>'Test3J');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1301, 'timelimit'=>0));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>0, '', 'message'=>'Test3K');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>0, '', 'message'=>'Test3L');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>0, 'timelimit'=>501));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>501, '', 'message'=>'Test3M');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>501, '', 'message'=>'Test3N');
|
||||
|
||||
// User overrides
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>601));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>601, '', 'message'=>'Test4A');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>601, '', 'message'=>'Test4B');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>0, 'timelimit'=>601));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>601, '', 'message'=>'Test4C');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>601, '', 'message'=>'Test4D');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>0));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>0, '', 'message'=>'Test4E');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>0, '', 'message'=>'Test4F');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>null, 'timelimit'=>601));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>601, '', 'message'=>'Test4G');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>601, '', 'message'=>'Test4H');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>700));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>null, 'timelimit'=>601));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>601, '', 'message'=>'Test4I');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>601, '', 'message'=>'Test4J');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>null));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>700, '', 'message'=>'Test4K');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>700, '', 'message'=>'Test4L');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>null));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>null));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>600, '', 'message'=>'Test4M');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>600, '', 'message'=>'Test4N');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>0, 'timeclose'=>1201, 'timelimit'=>601)); // not user
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>700, '', 'message'=>'Test4O');
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>700, '', 'message'=>'Test4P');
|
||||
|
||||
// Attempt state overdue
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600, 'overduehandling'=>'graceperiod', 'graceperiod'=>250));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'overdue', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, '', 'message'=>'Test5A');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600, 'overduehandling'=>'graceperiod', 'graceperiod'=>250));
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'overdue', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>600, '', 'message'=>'Test5B');
|
||||
|
||||
//
|
||||
// Test quiz_update_open_attempts()
|
||||
//
|
||||
|
||||
quiz_update_open_attempts(array('courseid'=>$course->id));
|
||||
foreach ($usertimes as $attemptid=>$times) {
|
||||
$attempt = $DB->get_record('quiz_attempts', array('id'=>$attemptid));
|
||||
$this->assertTrue(false !== $attempt, $times['message']);
|
||||
|
||||
if ($attempt->state == 'overdue') {
|
||||
$graceperiod = $DB->get_field('quiz', 'graceperiod', array('id'=>$attempt->quiz));
|
||||
} else {
|
||||
$graceperiod = 0;
|
||||
}
|
||||
if ($times['timeclose'] > 0 and $times['timelimit'] > 0) {
|
||||
$this->assertEquals(min($times['timeclose'], $attempt->timestart + $times['timelimit']) + $graceperiod, $attempt->timecheckstate, $times['message']);
|
||||
} else if ($times['timeclose'] > 0) {
|
||||
$this->assertEquals($times['timeclose'] + $graceperiod, $attempt->timecheckstate <= $times['timeclose'], $times['message']);
|
||||
} else if ($times['timelimit'] > 0) {
|
||||
$this->assertEquals($attempt->timestart + $times['timelimit'] + $graceperiod, $attempt->timecheckstate, $times['message']);
|
||||
} else {
|
||||
$this->assertNull($attempt->timecheckstate, $times['message']);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Test get_list_of_overdue_attempts()
|
||||
//
|
||||
|
||||
$overduehander = new mod_quiz_overdue_attempt_updater();
|
||||
|
||||
$attempts = $overduehander->get_list_of_overdue_attempts(100000); // way in the future
|
||||
$count = 0;
|
||||
foreach ($attempts as $attempt) {
|
||||
$this->assertTrue(isset($usertimes[$attempt->id]));
|
||||
$times = $usertimes[$attempt->id];
|
||||
$this->assertEquals($times['timeclose'], $attempt->usertimeclose, $times['message']);
|
||||
$this->assertEquals($times['timelimit'], $attempt->usertimelimit, $times['message']);
|
||||
$count++;
|
||||
|
||||
}
|
||||
$this->assertEquals($DB->count_records_select('quiz_attempts', 'timecheckstate IS NOT NULL'), $count);
|
||||
|
||||
$attempts = $overduehander->get_list_of_overdue_attempts(0); // before all attempts
|
||||
$count = 0;
|
||||
foreach ($attempts as $attempt) {
|
||||
$count++;
|
||||
}
|
||||
$this->assertEquals(0, $count);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the group event handlers
|
||||
*/
|
||||
public function test_group_event_handlers() {
|
||||
global $DB,$CFG;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Setup course, user and groups
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname'=>'student'));
|
||||
$this->assertNotEmpty($studentrole);
|
||||
$this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
|
||||
$group1 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
|
||||
$group2 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
|
||||
$this->assertTrue(groups_add_member($group1, $user1));
|
||||
$this->assertTrue(groups_add_member($group2, $user1));
|
||||
|
||||
$uniqueid = 0;
|
||||
|
||||
$quiz_generator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
|
||||
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
|
||||
|
||||
// add a group1 override
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>null));
|
||||
|
||||
// add an attempt
|
||||
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
|
||||
|
||||
// update timecheckstate
|
||||
quiz_update_open_attempts(array('quizid'=>$quiz->id));
|
||||
$this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
|
||||
// remove from group
|
||||
$this->assertTrue(groups_remove_member($group1, $user1));
|
||||
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
|
||||
// add back to group
|
||||
$this->assertTrue(groups_add_member($group1, $user1));
|
||||
$this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
|
||||
// delete group
|
||||
groups_delete_group($group1);
|
||||
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
$this->assertEquals(0, $DB->count_records('quiz_overrides', array('quiz'=>$quiz->id)));
|
||||
|
||||
// add a group2 override
|
||||
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1400, 'timelimit'=>null));
|
||||
quiz_update_open_attempts(array('quizid'=>$quiz->id));
|
||||
$this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
|
||||
// delete user1 from all groups
|
||||
groups_delete_group_members($course->id, $user1->id);
|
||||
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
|
||||
// add back to group2
|
||||
$this->assertTrue(groups_add_member($group2, $user1));
|
||||
$this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
|
||||
// delete everyone from all groups
|
||||
groups_delete_group_members($course->id);
|
||||
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
|
||||
}
|
||||
}
|
105
mod/quiz/tests/generator/lib.php
Normal file
105
mod/quiz/tests/generator/lib.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* mod_quiz data generator
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category phpunit
|
||||
* @copyright 2012 Matt Petro
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Quiz module PHPUnit data generator class
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category phpunit
|
||||
* @copyright 2012 Matt Petro
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_quiz_generator extends phpunit_module_generator {
|
||||
|
||||
/**
|
||||
* Create new quiz module instance
|
||||
* @param array|stdClass $record
|
||||
* @param array $options (mostly course_module properties)
|
||||
* @return stdClass activity record with extra cmid field
|
||||
*/
|
||||
public function create_instance($record = null, array $options = null) {
|
||||
global $CFG;
|
||||
require_once("$CFG->dirroot/mod/quiz/locallib.php");
|
||||
|
||||
$this->instancecount++;
|
||||
$i = $this->instancecount;
|
||||
|
||||
$record = (object)(array)$record;
|
||||
$options = (array)$options;
|
||||
|
||||
if (empty($record->course)) {
|
||||
throw new coding_exception('module generator requires $record->course');
|
||||
}
|
||||
if (!isset($record->name)) {
|
||||
$record->name = get_string('pluginname', 'quiz').' '.$i;
|
||||
}
|
||||
if (!isset($record->intro)) {
|
||||
$record->intro = 'Test quiz '.$i;
|
||||
}
|
||||
if (!isset($record->introformat)) {
|
||||
$record->introformat = FORMAT_MOODLE;
|
||||
}
|
||||
if (!isset($record->overduehandling)) {
|
||||
$record->overduehandling = 'autoabandon';
|
||||
}
|
||||
if (!isset($record->preferredbehavior)) {
|
||||
$record->preferredbehavior = 'deferredfeedback';
|
||||
}
|
||||
if (!isset($record->grade)) {
|
||||
$record->grade = 100;
|
||||
}
|
||||
if (!isset($record->quizpassword)) {
|
||||
$record->quizpassword = '';
|
||||
}
|
||||
if (!isset($record->feedbackboundarycount)) {
|
||||
$record->feedbackboundarycount = -1;
|
||||
}
|
||||
if (!isset($record->timeopen)) {
|
||||
$record->timeopen = 0;
|
||||
}
|
||||
if (!isset($record->timeclose)) {
|
||||
$record->timeclose = 0;
|
||||
}
|
||||
if (!isset($record->timelimit)) {
|
||||
$record->timelimit = 0;
|
||||
}
|
||||
if (!isset($record->questiondecimalpoints)) {
|
||||
$record->questiondecimalpoints = -2;
|
||||
}
|
||||
if (isset($options['idnumber'])) {
|
||||
$record->cmidnumber = $options['idnumber'];
|
||||
} else {
|
||||
$record->cmidnumber = '';
|
||||
}
|
||||
|
||||
$record->coursemodule = $this->precreate_course_module($record->course, $options);
|
||||
$id = quiz_add_instance($record);
|
||||
return $this->post_add_instance($id, $record->coursemodule);
|
||||
}
|
||||
|
||||
}
|
63
mod/quiz/tests/generator_test.php
Normal file
63
mod/quiz/tests/generator_test.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* PHPUnit data generator tests
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category phpunit
|
||||
* @copyright 2012 Matt Petro
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* PHPUnit data generator testcase
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category phpunit
|
||||
* @copyright 2012 Matt Petro
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_quiz_generator_testcase extends advanced_testcase {
|
||||
public function test_generator() {
|
||||
global $DB, $SITE;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$this->assertEquals(0, $DB->count_records('quiz'));
|
||||
|
||||
/** @var mod_quiz_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
|
||||
$this->assertInstanceOf('mod_quiz_generator', $generator);
|
||||
$this->assertEquals('quiz', $generator->get_modulename());
|
||||
|
||||
$generator->create_instance(array('course'=>$SITE->id));
|
||||
$generator->create_instance(array('course'=>$SITE->id));
|
||||
$quiz = $generator->create_instance(array('course'=>$SITE->id));
|
||||
$this->assertEquals(3, $DB->count_records('quiz'));
|
||||
|
||||
$cm = get_coursemodule_from_instance('quiz', $quiz->id);
|
||||
$this->assertEquals($quiz->id, $cm->instance);
|
||||
$this->assertEquals('quiz', $cm->modname);
|
||||
$this->assertEquals($SITE->id, $cm->course);
|
||||
|
||||
$context = context_module::instance($cm->id);
|
||||
$this->assertEquals($quiz->cmid, $context->instanceid);
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$module->version = 2012061703; // The current module version (Date: YYYYMMDDXX).
|
||||
$module->version = 2012100801; // The current module version (Date: YYYYMMDDXX).
|
||||
$module->requires = 2012061700; // Requires this Moodle version.
|
||||
$module->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics).
|
||||
$module->cron = 60;
|
||||
|
Loading…
x
Reference in New Issue
Block a user