From bcf4a7f0764764816ec11f85d283fbab2c75ba69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luca=20B=C3=B6sch?= Date: Tue, 28 Nov 2017 01:25:29 +0100 Subject: [PATCH] MDL-52832 quiz: timeclose in activities block looks after overrides. --- mod/quiz/index.php | 8 +- mod/quiz/locallib.php | 49 ++++++++++++ mod/quiz/tests/locallib_test.php | 124 +++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 1 deletion(-) diff --git a/mod/quiz/index.php b/mod/quiz/index.php index 93f22871d88..38828822a6b 100644 --- a/mod/quiz/index.php +++ b/mod/quiz/index.php @@ -117,6 +117,8 @@ $table->align = $align; // Populate the table with the list of instances. $currentsection = ''; +// Get all closing dates. +$timeclosedates = quiz_get_user_timeclose($course->id); foreach ($quizzes as $quiz) { $cm = get_coursemodule_from_instance('quiz', $quiz->id); $context = context_module::instance($cm->id); @@ -146,7 +148,11 @@ foreach ($quizzes as $quiz) { // Close date. if ($quiz->timeclose) { - $data[] = userdate($quiz->timeclose); + if (($timeclosedates[$quiz->id]->usertimeclose == 0) AND ($timeclosedates[$quiz->id]->usertimelimit == 0)) { + $data[] = get_string('noclose', 'quiz'); + } else { + $data[] = userdate($timeclosedates[$quiz->id]->usertimeclose); + } } else if ($showclosingheader) { $data[] = ''; } diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 36bca77dd7a..9aa8627a5ea 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -1090,6 +1090,8 @@ function quiz_update_open_attempts(array $conditions) { /** * Returns SQL to compute timeclose and timelimit for every attempt, taking into account user and group overrides. + * The query used herein is very similar to the one in function quiz_get_user_timeclose, so, in case you + * would change either one of them, make sure to apply your changes to both. * * @param string $redundantwhereclauses extra where clauses to add to the subquery * for performance. These can use the table alias iquiza for the quiz attempts table. @@ -1205,6 +1207,53 @@ function quiz_get_user_image_options() { ); } +/** + * Return an user's timeclose for all quizzes in a course, hereby taking into account group and user overrides. + * The query used herein is very similar to the one in function quiz_get_attempt_usertime_sql, so, in case you + * would change either one of them, make sure to apply your changes to both. + * + * @param int $courseid the course id. + * @return object An object with quizids and unixdates of the most lenient close overrides, if any. + */ +function quiz_get_user_timeclose($courseid) { + global $DB, $USER; + + // For teacher and manager/admins return timeclose. + if (has_capability('moodle/course:update', context_course::instance($courseid))) { + $sql = "SELECT quiz.id, quiz.timeclose AS usertimeclose, COALESCE(quiz.timelimit, 0) AS usertimelimit + FROM {quiz} quiz + WHERE quiz.course = :courseid + GROUP BY quiz.id"; + + $results = $DB->get_records_sql($sql, array('courseid' => $courseid)); + return $results; + } + + // The multiple qgo JOINS are necessary because we want timeclose/timelimit = 0 (unlimited) to supercede + // any other group override. + + $sql = "SELECT quiz.id, + COALESCE(MAX(quo.timeclose), MAX(qgo1.timeclose), MAX(qgo2.timeclose), quiz.timeclose, 0) AS usertimeclose, + COALESCE(MAX(quo.timelimit), MAX(qgo3.timelimit), MAX(qgo4.timelimit), quiz.timelimit, 0) AS usertimelimit + FROM {quiz} quiz + LEFT JOIN {quiz_overrides} quo ON quo.quiz = quiz.id + LEFT JOIN {groups_members} gm ON gm.userid = quo.userid + LEFT JOIN {quiz_overrides} qgo1 ON qgo1.timeclose = 0 AND qgo1.quiz = quiz.id + LEFT JOIN {quiz_overrides} qgo2 ON qgo2.timeclose > 0 AND qgo2.quiz = quiz.id + LEFT JOIN {quiz_overrides} qgo3 ON qgo3.timelimit = 0 AND qgo3.quiz = quiz.id + LEFT JOIN {quiz_overrides} qgo4 ON qgo4.timelimit > 0 AND qgo4.quiz = quiz.id + AND qgo1.groupid = gm.groupid + AND qgo2.groupid = gm.groupid + AND qgo3.groupid = gm.groupid + AND qgo4.groupid = gm.groupid + WHERE quiz.course = :courseid + AND ((quo.userid = :userid) OR ((gm.userid IS NULL) AND (quo.userid IS NULL))) + GROUP BY quiz.id"; + + $results = $DB->get_records_sql($sql, array('courseid' => $courseid, 'userid' => $USER->id)); + return $results; +} + /** * Get the choices to offer for the 'Questions per page' option. * @return array int => string. diff --git a/mod/quiz/tests/locallib_test.php b/mod/quiz/tests/locallib_test.php index 081c7577867..7da393e90e8 100644 --- a/mod/quiz/tests/locallib_test.php +++ b/mod/quiz/tests/locallib_test.php @@ -296,4 +296,128 @@ class mod_quiz_locallib_testcase extends advanced_testcase { $this->assertTrue(quiz_is_overriden_calendar_event($event)); } + + /** + * Test test_quiz_get_user_timeclose(). + */ + public function test_quiz_get_user_timeclose() { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + + $basetimestamp = time(); // The timestamp we will base the enddates on. + + // Create generator, course and quizzes. + $student1 = $this->getDataGenerator()->create_user(); + $student2 = $this->getDataGenerator()->create_user(); + $teacher = $this->getDataGenerator()->create_user(); + $course = $this->getDataGenerator()->create_course(); + $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); + + // Both quizzes close in two hours. + $quiz1 = $quizgenerator->create_instance(array('course' => $course->id, 'timeclose' => $basetimestamp + 7200)); + $quiz2 = $quizgenerator->create_instance(array('course' => $course->id, 'timeclose' => $basetimestamp + 7200)); + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + + $student1id = $student1->id; + $student2id = $student2->id; + $teacherid = $teacher->id; + + // Users enrolments. + $studentrole = $DB->get_record('role', array('shortname' => 'student')); + $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); + $this->getDataGenerator()->enrol_user($student1id, $course->id, $studentrole->id, 'manual'); + $this->getDataGenerator()->enrol_user($student2id, $course->id, $studentrole->id, 'manual'); + $this->getDataGenerator()->enrol_user($teacherid, $course->id, $teacherrole->id, 'manual'); + + // Create groups. + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + $group1id = $group1->id; + $group2id = $group2->id; + $this->getDataGenerator()->create_group_member(array('userid' => $student1id, 'groupid' => $group1id)); + $this->getDataGenerator()->create_group_member(array('userid' => $student2id, 'groupid' => $group2id)); + + // Group 1 gets an group override for quiz 1 to close in three hours. + $record1 = (object) [ + 'quiz' => $quiz1->id, + 'groupid' => $group1id, + 'timeclose' => $basetimestamp + 10800 // In three hours. + ]; + $DB->insert_record('quiz_overrides', $record1); + + // Let's test quiz 1 closes in three hours for user student 1 since member of group 1. + // Quiz 2 closes in two hours. + $this->setUser($student1id); + $params = new stdClass(); + + $comparearray = array(); + $object = new stdClass(); + $object->id = $quiz1->id; + $object->usertimeclose = $basetimestamp + 10800; // The overriden timeclose for quiz 1. + $object->usertimelimit = 0; + + $comparearray[$quiz1->id] = $object; + + $object = new stdClass(); + $object->id = $quiz2->id; + $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 2. + $object->usertimelimit = 0; + + $comparearray[$quiz2->id] = $object; + + $this->assertEquals($comparearray, quiz_get_user_timeclose($course->id)); + + // User 2 gets an user override for quiz 1 to close in four hours. + $record2 = (object) [ + 'quiz' => $quiz1->id, + 'userid' => $student2id, + 'timeclose' => $basetimestamp + 14400 // In four hours. + ]; + $DB->insert_record('quiz_overrides', $record2); + + // Let's test quiz 1 closes in four hours for user student 2 since personally overriden. + // Quiz 2 closes in two hours. + $this->setUser($student2id); + + $comparearray = array(); + $object = new stdClass(); + $object->id = $quiz1->id; + $object->usertimeclose = $basetimestamp + 14400; // The overriden timeclose for quiz 1. + $object->usertimelimit = 0; + + $comparearray[$quiz1->id] = $object; + + $object = new stdClass(); + $object->id = $quiz2->id; + $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 2. + $object->usertimelimit = 0; + + $comparearray[$quiz2->id] = $object; + + $this->assertEquals($comparearray, quiz_get_user_timeclose($course->id)); + + // Let's test a teacher sees the original times. + // Quiz 1 and quiz 2 close in two hours. + $this->setUser($teacherid); + + $comparearray = array(); + $object = new stdClass(); + $object->id = $quiz1->id; + $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 1. + $object->usertimelimit = 0; + + $comparearray[$quiz1->id] = $object; + + $object = new stdClass(); + $object->id = $quiz2->id; + $object->usertimeclose = $basetimestamp + 7200; // The unchanged timeclose for quiz 2. + $object->usertimelimit = 0; + + $comparearray[$quiz2->id] = $object; + + $this->assertEquals($comparearray, quiz_get_user_timeclose($course->id)); + } }