Merge branch 'MDL-79718-main' of https://github.com/davewoloszyn/moodle
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_quiz;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Helper for sending quiz related notifications.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 David Woloszyn <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class notification_helper {
|
||||
/**
|
||||
* @var int Default date range of 48 hours.
|
||||
*/
|
||||
private const DEFAULT_DATE_RANGE = (DAYSECS * 2);
|
||||
|
||||
/**
|
||||
* Get all quizzes that have an approaching open date (includes users and groups with open date overrides).
|
||||
*
|
||||
* @return \moodle_recordset Returns the matching quiz records.
|
||||
*/
|
||||
public static function get_quizzes_within_date_range(): \moodle_recordset {
|
||||
global $DB;
|
||||
|
||||
$timenow = self::get_time_now();
|
||||
$futuretime = self::get_future_time();
|
||||
|
||||
$sql = "SELECT DISTINCT q.id
|
||||
FROM {quiz} q
|
||||
JOIN {course_modules} cm ON q.id = cm.instance
|
||||
JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
|
||||
LEFT JOIN {quiz_overrides} qo ON q.id = qo.quiz
|
||||
WHERE (q.timeopen < :futuretime OR qo.timeopen < :qo_futuretime)
|
||||
AND (q.timeopen > :timenow OR qo.timeopen > :qo_timenow);";
|
||||
|
||||
$params = [
|
||||
'timenow' => $timenow,
|
||||
'futuretime' => $futuretime,
|
||||
'qo_timenow' => $timenow,
|
||||
'qo_futuretime' => $futuretime,
|
||||
'modulename' => 'quiz',
|
||||
];
|
||||
|
||||
return $DB->get_recordset_sql($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users that have an approaching open date within a quiz.
|
||||
*
|
||||
* @param int $quizid The quiz id.
|
||||
* @return array The users after all filtering has been applied.
|
||||
*/
|
||||
public static function get_users_within_quiz(int $quizid): array {
|
||||
// Get quiz data.
|
||||
$quizobj = quiz_settings::create($quizid);
|
||||
$quiz = $quizobj->get_quiz();
|
||||
|
||||
// Get our users.
|
||||
$users = get_enrolled_users(
|
||||
context: \context_module::instance($quizobj->get_cm()->id),
|
||||
withcapability: 'mod/quiz:attempt',
|
||||
userfields: 'u.id, u.firstname',
|
||||
);
|
||||
|
||||
// Check for any override dates.
|
||||
$overrides = $quizobj->get_override_manager()->get_all_overrides();
|
||||
|
||||
foreach ($users as $key => $user) {
|
||||
// Time open and time close dates can be user specific with an override.
|
||||
// We begin by assuming it is the same as recorded in the quiz.
|
||||
$user->timeopen = $quiz->timeopen;
|
||||
$user->timeclose = $quiz->timeclose;
|
||||
|
||||
// Set the override type to 'none' to begin with.
|
||||
$user->overridetype = 'none';
|
||||
|
||||
// Update this user with any applicable override dates.
|
||||
if (!empty($overrides)) {
|
||||
self::update_user_with_date_overrides($overrides, $user);
|
||||
}
|
||||
|
||||
// If the 'timeopen' date has no value, even after overriding, unset this user.
|
||||
if (empty($quiz->timeopen) && empty($user->timeopen)) {
|
||||
unset($users[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the date is within our range.
|
||||
// We have to check here because we don't know if this quiz was selected because it only had users with overrides.
|
||||
if (!self::is_time_within_range($user->timeopen)) {
|
||||
unset($users[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the user has already received this notification.
|
||||
$match = [
|
||||
'quizid' => strval($quizid),
|
||||
'timeopen' => $user->timeopen,
|
||||
'overridetype' => $user->overridetype,
|
||||
];
|
||||
|
||||
if (self::has_user_been_sent_a_notification_already($user->id, json_encode($match))) {
|
||||
unset($users[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the notification to the user.
|
||||
*
|
||||
* @param stdClass $user The user's custom data.
|
||||
*/
|
||||
public static function send_notification_to_user(stdClass $user): void {
|
||||
// Check if the user has submitted already.
|
||||
if (self::has_user_attempted($user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get quiz data.
|
||||
$quizobj = quiz_settings::create($user->quizid);
|
||||
$quiz = $quizobj->get_quiz();
|
||||
$url = $quizobj->view_url();
|
||||
|
||||
$stringparams = [
|
||||
'firstname' => $user->firstname,
|
||||
'quizname' => $quiz->name,
|
||||
'coursename' => $quizobj->get_course()->fullname,
|
||||
'timeopen' => userdate($user->timeopen),
|
||||
'timeclose' => !empty($user->timeclose) ? userdate($user->timeclose) : get_string('statusna'),
|
||||
'url' => $url,
|
||||
];
|
||||
|
||||
$messagedata = [
|
||||
'user' => \core_user::get_user($user->id),
|
||||
'url' => $url->out(false),
|
||||
'subject' => get_string('quizopendatesoonsubject', 'mod_quiz', $stringparams),
|
||||
'quizname' => $quiz->name,
|
||||
'html' => get_string('quizopendatesoonhtml', 'mod_quiz', $stringparams),
|
||||
];
|
||||
|
||||
// Prepare message object.
|
||||
$message = new \core\message\message();
|
||||
$message->component = 'mod_quiz';
|
||||
$message->name = 'quiz_open_soon';
|
||||
$message->userfrom = \core_user::get_noreply_user();
|
||||
$message->userto = $messagedata['user'];
|
||||
$message->subject = $messagedata['subject'];
|
||||
$message->fullmessageformat = FORMAT_HTML;
|
||||
$message->fullmessage = html_to_text($messagedata['html']);
|
||||
$message->fullmessagehtml = $messagedata['html'];
|
||||
$message->smallmessage = $messagedata['subject'];
|
||||
$message->notification = 1;
|
||||
$message->contexturl = $messagedata['url'];
|
||||
$message->contexturlname = $messagedata['quizname'];
|
||||
// Use custom data to avoid future notifications being sent again.
|
||||
$message->customdata = [
|
||||
'quizid' => $user->quizid,
|
||||
'timeopen' => $user->timeopen,
|
||||
'overridetype' => $user->overridetype,
|
||||
];
|
||||
|
||||
message_send($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time now.
|
||||
*
|
||||
* @return int The time now as a timestamp.
|
||||
*/
|
||||
protected static function get_time_now(): int {
|
||||
return \core\di::get(\core\clock::class)->time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a future time that serves as the cut-off for this notification.
|
||||
*
|
||||
* @param int|null $range Amount of seconds added to the now time (optional).
|
||||
* @return int The time now value plus the range.
|
||||
*/
|
||||
protected static function get_future_time(?int $range = null): int {
|
||||
$range = $range ?? self::DEFAULT_DATE_RANGE;
|
||||
return self::get_time_now() + $range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time is within the current time now and the future time values.
|
||||
*
|
||||
* @param int $time The timestamp to check.
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function is_time_within_range(int $time): bool {
|
||||
return ($time > self::get_time_now() && $time < self::get_future_time());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's recorded date based on the overrides.
|
||||
*
|
||||
* @param array $overrides The overrides to check.
|
||||
* @param stdClass $user The user records we will be updating.
|
||||
*/
|
||||
protected static function update_user_with_date_overrides(array $overrides, stdClass $user): void {
|
||||
|
||||
foreach ($overrides as $override) {
|
||||
// User override.
|
||||
if ($override->userid === $user->id) {
|
||||
$user->timeopen = !empty($override->timeopen) ? $override->timeopen : $user->timeopen;
|
||||
$user->timeclose = !empty($override->timeclose) ? $override->timeclose : $user->timeclose;
|
||||
$user->overridetype = 'user';
|
||||
// User override has precedence over group. Return here.
|
||||
return;
|
||||
}
|
||||
// Group override.
|
||||
if (!empty($override->groupid) && groups_is_member($override->groupid, $user->id)) {
|
||||
// If user is a member of multiple groups, and we have set this already, use the earliest date.
|
||||
if ($user->overridetype === 'group' && $user->timeopen < $override->timeopen) {
|
||||
continue;
|
||||
}
|
||||
$user->timeopen = !empty($override->timeopen) ? $override->timeopen : $user->timeopen;
|
||||
$user->timeclose = !empty($override->timeclose) ? $override->timeclose : $user->timeclose;
|
||||
$user->overridetype = 'group';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has attempted this quiz already.
|
||||
*
|
||||
* @param stdClass $user The user record we will be checking.
|
||||
* @return bool Return true if attempt found.
|
||||
*/
|
||||
protected static function has_user_attempted(stdClass $user): bool {
|
||||
global $DB;
|
||||
|
||||
return $DB->record_exists('quiz_attempts', [
|
||||
'quiz' => $user->quizid,
|
||||
'userid' => $user->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has been sent a notification already.
|
||||
*
|
||||
* @param int $userid The user id.
|
||||
* @param string $match The custom data string to match on.
|
||||
* @return bool Returns true if already sent.
|
||||
*/
|
||||
protected static function has_user_been_sent_a_notification_already(int $userid, string $match): bool {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT COUNT(n.id)
|
||||
FROM {notifications} n
|
||||
WHERE " . $DB->sql_compare_text('n.customdata', 255) . " = " . $DB->sql_compare_text(':match', 255) . "
|
||||
AND n.useridto = :userid";
|
||||
|
||||
$result = $DB->count_records_sql($sql, [
|
||||
'userid' => $userid,
|
||||
'match' => $match,
|
||||
]);
|
||||
|
||||
return ($result > 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_quiz\task;
|
||||
|
||||
use core\task\scheduled_task;
|
||||
use mod_quiz\notification_helper;
|
||||
|
||||
/**
|
||||
* Scheduled task to queue tasks for notifying about quizzes with an approaching open date.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 David Woloszyn <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class queue_all_quiz_open_notification_tasks extends scheduled_task {
|
||||
|
||||
public function get_name(): string {
|
||||
return get_string('sendnotificationopendatesoon', 'mod_quiz');
|
||||
}
|
||||
|
||||
public function execute(): void {
|
||||
$quizzes = notification_helper::get_quizzes_within_date_range();
|
||||
foreach ($quizzes as $quiz) {
|
||||
$task = new queue_quiz_open_notification_tasks_for_users();
|
||||
$task->set_custom_data($quiz);
|
||||
\core\task\manager::queue_adhoc_task($task, true);
|
||||
}
|
||||
$quizzes->close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_quiz\task;
|
||||
|
||||
use core\task\adhoc_task;
|
||||
use mod_quiz\notification_helper;
|
||||
|
||||
/**
|
||||
* Ad-hoc task to queue another task for notifying a user about an approaching open date.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 David Woloszyn <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class queue_quiz_open_notification_tasks_for_users extends adhoc_task {
|
||||
|
||||
public function execute(): void {
|
||||
$quizid = $this->get_custom_data()->id;
|
||||
$users = notification_helper::get_users_within_quiz($quizid);
|
||||
foreach ($users as $user) {
|
||||
$user->quizid = $quizid;
|
||||
$task = new send_quiz_open_soon_notification_to_user();
|
||||
$task->set_custom_data($user);
|
||||
$task->set_userid($user->id);
|
||||
\core\task\manager::queue_adhoc_task($task, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_quiz\task;
|
||||
|
||||
use core\task\adhoc_task;
|
||||
use mod_quiz\notification_helper;
|
||||
|
||||
/**
|
||||
* Ad-hoc task to send a notification to a user about an approaching open date.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @copyright 2024 David Woloszyn <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class send_quiz_open_soon_notification_to_user extends adhoc_task {
|
||||
|
||||
public function execute(): void {
|
||||
$user = $this->get_custom_data();
|
||||
notification_helper::send_notification_to_user($user);
|
||||
}
|
||||
}
|
||||
@@ -63,4 +63,13 @@ $messageproviders = [
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED,
|
||||
],
|
||||
],
|
||||
|
||||
// Notify of a quiz opening soon.
|
||||
'quiz_open_soon' => [
|
||||
'defaults' => [
|
||||
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED,
|
||||
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED,
|
||||
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_ENABLED,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
+10
-1
@@ -43,5 +43,14 @@ $tasks = [
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
]
|
||||
],
|
||||
[
|
||||
'classname' => 'mod_quiz\task\queue_all_quiz_open_notification_tasks',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => '*/2',
|
||||
'day' => '*',
|
||||
'month' => '*',
|
||||
'dayofweek' => '*',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -544,6 +544,7 @@ $string['matchanswerno'] = 'Matching answer {$a}';
|
||||
$string['messageprovider:attempt_overdue'] = 'Warning when your quiz attempt becomes overdue';
|
||||
$string['messageprovider:confirmation'] = 'Confirmation of your own quiz submissions';
|
||||
$string['messageprovider:attempt_grading_complete'] = 'Notification that your attempt has been graded';
|
||||
$string['messageprovider:quiz_open_soon'] = 'Quiz opens soon notification';
|
||||
$string['messageprovider:submission'] = 'Notification of your students\' quiz submissions';
|
||||
$string['max'] = 'Max';
|
||||
$string['maxmark'] = 'Maximum mark';
|
||||
@@ -818,6 +819,12 @@ $string['quizopenclose_link'] = 'mod/quiz/timing';
|
||||
$string['quizopened'] = 'This quiz is open.';
|
||||
$string['quizopenedon'] = 'This quiz opened on {$a}';
|
||||
$string['quizopens'] = 'Quiz opens';
|
||||
$string['quizopendatesoonhtml'] = '<p>Hi {$a->firstname},</p>
|
||||
<p>The quiz <strong>{$a->quizname}</strong> in course {$a->coursename} is opening soon.
|
||||
<p><strong>Opens: {$a->timeopen}</strong></p>
|
||||
<p><strong>Closes: {$a->timeclose}</strong></p>
|
||||
<p><a href="{$a->url}">Go to quiz</a></p>';
|
||||
$string['quizopendatesoonsubject'] = 'Opens on {$a->timeopen}: {$a->quizname}';
|
||||
$string['quizopenwillclose'] = 'This quiz is open, will close on {$a} at';
|
||||
$string['quizordernotrandom'] = 'Order of quiz not shuffled';
|
||||
$string['quizorderrandom'] = '* Order of quiz is shuffled';
|
||||
@@ -978,6 +985,7 @@ $string['selectmultipletoolbar'] = 'Select multiple toolbar';
|
||||
$string['selectnone'] = 'Deselect all';
|
||||
$string['selectquestionslot'] = 'Select question {$a}';
|
||||
$string['selectquestiontype'] = '-- Select question type --';
|
||||
$string['sendnotificationopendatesoon'] = 'Notify user of an approaching quiz open date';
|
||||
$string['serveradded'] = 'Server added';
|
||||
$string['serveridentifier'] = 'Identifier';
|
||||
$string['serverinfo'] = 'Server information';
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
<?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/>.
|
||||
|
||||
namespace mod_quiz;
|
||||
|
||||
/**
|
||||
* Test class for the quiz notification helper.
|
||||
*
|
||||
* @package mod_quiz
|
||||
* @category test
|
||||
* @copyright 2024 David Woloszyn <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \mod_quiz\notification_helper
|
||||
*/
|
||||
class notification_helper_test extends \advanced_testcase {
|
||||
/**
|
||||
* Run all the tasks related to the notifications.
|
||||
*/
|
||||
public function run_notification_helper_tasks(): void {
|
||||
$task = \core\task\manager::get_scheduled_task(\mod_quiz\task\queue_all_quiz_open_notification_tasks::class);
|
||||
$task->execute();
|
||||
$clock = $this->mock_clock_with_frozen();
|
||||
|
||||
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
|
||||
if ($adhoctask) {
|
||||
$this->assertInstanceOf(\mod_quiz\task\queue_quiz_open_notification_tasks_for_users::class, $adhoctask);
|
||||
$adhoctask->execute();
|
||||
\core\task\manager::adhoc_task_complete($adhoctask);
|
||||
}
|
||||
|
||||
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
|
||||
if ($adhoctask) {
|
||||
$this->assertInstanceOf(\mod_quiz\task\send_quiz_open_soon_notification_to_user::class, $adhoctask);
|
||||
$adhoctask->execute();
|
||||
\core\task\manager::adhoc_task_complete($adhoctask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting quizzes with a 'timeopen' date within the date range.
|
||||
*/
|
||||
public function test_get_quizzes_within_date_range(): void {
|
||||
$this->resetAfterTest();
|
||||
$generator = $this->getDataGenerator();
|
||||
$helper = \core\di::get(notification_helper::class);
|
||||
$clock = $this->mock_clock_with_frozen();
|
||||
|
||||
// Create a quiz with an open date < 48 hours.
|
||||
$course = $generator->create_course();
|
||||
$generator->create_module('quiz', ['course' => $course->id, 'timeopen' => $clock->time() + DAYSECS]);
|
||||
|
||||
// Check that we have a result returned.
|
||||
$result = $helper::get_quizzes_within_date_range();
|
||||
$this->assertTrue($result->valid());
|
||||
$result->close();
|
||||
|
||||
// Time travel 3 days into the future. We should have no quizzes in range.
|
||||
$clock->bump(DAYSECS * 3);
|
||||
$result = $helper::get_quizzes_within_date_range();
|
||||
$this->assertFalse($result->valid());
|
||||
$result->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting users within a quiz that are within our date range.
|
||||
*/
|
||||
public function test_get_users_within_quiz(): void {
|
||||
$this->resetAfterTest();
|
||||
$generator = $this->getDataGenerator();
|
||||
$helper = \core\di::get(notification_helper::class);
|
||||
$clock = $this->mock_clock_with_frozen();
|
||||
|
||||
// Create a course and enrol some users.
|
||||
$course = $generator->create_course();
|
||||
$user1 = $generator->create_user();
|
||||
$user2 = $generator->create_user();
|
||||
$user3 = $generator->create_user();
|
||||
$user4 = $generator->create_user();
|
||||
$user5 = $generator->create_user();
|
||||
$generator->enrol_user($user1->id, $course->id, 'student');
|
||||
$generator->enrol_user($user2->id, $course->id, 'student');
|
||||
$generator->enrol_user($user3->id, $course->id, 'student');
|
||||
$generator->enrol_user($user4->id, $course->id, 'student');
|
||||
$generator->enrol_user($user5->id, $course->id, 'teacher');
|
||||
|
||||
/** @var \mod_quiz_generator $quizgenerator */
|
||||
$quizgenerator = $generator->get_plugin_generator('mod_quiz');
|
||||
|
||||
// Create a quiz with an open date < 48 hours.
|
||||
$timeopen = $clock->time() + DAYSECS;
|
||||
$quiz = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'timeopen' => $timeopen,
|
||||
]);
|
||||
|
||||
// User1 will have a user specific override, giving them an extra 1 hour for 'timeopen'.
|
||||
$usertimeopen = $timeopen + HOURSECS;
|
||||
$quizgenerator->create_override([
|
||||
'quiz' => $quiz->id,
|
||||
'userid' => $user1->id,
|
||||
'timeopen' => $usertimeopen,
|
||||
]);
|
||||
|
||||
// User2 and user3 will have a group override, giving them an extra 2 hours for 'timeopen'.
|
||||
$grouptimeopen = $timeopen + (HOURSECS * 2);
|
||||
$group = $generator->create_group(['courseid' => $course->id]);
|
||||
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
|
||||
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
|
||||
$quizgenerator->create_override([
|
||||
'quiz' => $quiz->id,
|
||||
'groupid' => $group->id,
|
||||
'timeopen' => $grouptimeopen,
|
||||
]);
|
||||
|
||||
// Get the users within the date range.
|
||||
$quizzes = $helper::get_quizzes_within_date_range();
|
||||
foreach ($quizzes as $q) {
|
||||
$users = $helper::get_users_within_quiz($q->id);
|
||||
}
|
||||
$quizzes->close();
|
||||
|
||||
// User1 has the 'user' override and its 'timeopen' date has been updated.
|
||||
$this->assertEquals($usertimeopen, $users[$user1->id]->timeopen);
|
||||
$this->assertEquals('user', $users[$user1->id]->overridetype);
|
||||
|
||||
// User2 and user3 have the 'group' override and their 'timeopen' date has been updated.
|
||||
$this->assertEquals($grouptimeopen, $users[$user2->id]->timeopen);
|
||||
$this->assertEquals('group', $users[$user2->id]->overridetype);
|
||||
$this->assertEquals($grouptimeopen, $users[$user3->id]->timeopen);
|
||||
$this->assertEquals('group', $users[$user3->id]->overridetype);
|
||||
|
||||
// User4 is unchanged.
|
||||
$this->assertEquals($timeopen, $users[$user4->id]->timeopen);
|
||||
$this->assertEquals('none', $users[$user4->id]->overridetype);
|
||||
|
||||
// User5 should not be in the returned users because they are a teacher.
|
||||
$this->assertArrayNotHasKey($user5->id, $users);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sending the quiz open soon notification to a user.
|
||||
*/
|
||||
public function test_send_notification_to_user(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$generator = $this->getDataGenerator();
|
||||
$helper = \core\di::get(notification_helper::class);
|
||||
$clock = $this->mock_clock_with_frozen();
|
||||
|
||||
// Create a course and enrol a user.
|
||||
$course = $generator->create_course();
|
||||
$user1 = $generator->create_user();
|
||||
$generator->enrol_user($user1->id, $course->id, 'student');
|
||||
|
||||
/** @var \mod_quiz_generator $quizgenerator */
|
||||
$quizgenerator = $generator->get_plugin_generator('mod_quiz');
|
||||
|
||||
// Create a quiz with an open date < 48 hours.
|
||||
$timeopen = $clock->time() + DAYSECS;
|
||||
$quiz = $quizgenerator->create_instance([
|
||||
'course' => $course->id,
|
||||
'timeopen' => $timeopen,
|
||||
]);
|
||||
|
||||
// Get the users within the date range.
|
||||
$quizzes = $helper::get_quizzes_within_date_range();
|
||||
foreach ($quizzes as $q) {
|
||||
$users = $helper::get_users_within_quiz($q->id);
|
||||
}
|
||||
$quizzes->close();
|
||||
|
||||
// Run the tasks.
|
||||
$this->run_notification_helper_tasks();
|
||||
|
||||
// Get the notifications that should have been created during the adhoc task.
|
||||
$notifications = $DB->get_records('notifications', ['useridto' => $user1->id]);
|
||||
$this->assertCount(1, $notifications);
|
||||
|
||||
// Check the subject matches.
|
||||
$stringparams = ['timeopen' => userdate($users[$user1->id]->timeopen), 'quizname' => $quiz->name];
|
||||
$expectedsubject = get_string('quizopendatesoonsubject', 'mod_quiz', $stringparams);
|
||||
$this->assertEquals($expectedsubject, reset($notifications)->subject);
|
||||
|
||||
// Run the tasks again.
|
||||
$this->run_notification_helper_tasks();
|
||||
|
||||
// There should still only be one notification because nothing has changed.
|
||||
$notifications = $DB->get_records('notifications', ['useridto' => $user1->id]);
|
||||
$this->assertCount(1, $notifications);
|
||||
|
||||
// Let's modify the 'timeopen' for the quiz (it will still be within the 48 hour range).
|
||||
$updatedata = new \stdClass();
|
||||
$updatedata->id = $quiz->id;
|
||||
$updatedata->timeopen = $timeopen + HOURSECS;
|
||||
$DB->update_record('quiz', $updatedata);
|
||||
|
||||
// Run the tasks again.
|
||||
$this->run_notification_helper_tasks();
|
||||
|
||||
// There should now be two notifications.
|
||||
$notifications = $DB->get_records('notifications', ['useridto' => $user1->id]);
|
||||
$this->assertCount(2, $notifications);
|
||||
|
||||
// Let's modify the 'timeopen' one more time.
|
||||
$updatedata = new \stdClass();
|
||||
$updatedata->id = $quiz->id;
|
||||
$updatedata->timeopen = $timeopen + (HOURSECS * 2);
|
||||
$DB->update_record('quiz', $updatedata);
|
||||
|
||||
// This time, the user will submit an attempt.
|
||||
$DB->insert_record('quiz_attempts', [
|
||||
'quiz' => $quiz->id,
|
||||
'userid' => $user1->id,
|
||||
'state' => 'finished',
|
||||
'timestart' => $clock->time(),
|
||||
'timecheckstate' => 0,
|
||||
'layout' => '',
|
||||
'uniqueid' => 123,
|
||||
]);
|
||||
|
||||
// Run the tasks again.
|
||||
$this->run_notification_helper_tasks();
|
||||
|
||||
// No new notification should have been sent.
|
||||
$notifications = $DB->get_records('notifications', ['useridto' => $user1->id]);
|
||||
$this->assertCount(2, $notifications);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2024042200;
|
||||
$plugin->version = 2024051700;
|
||||
$plugin->requires = 2024041600;
|
||||
$plugin->component = 'mod_quiz';
|
||||
|
||||
Reference in New Issue
Block a user