Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace mod_assign;
18
 
19
use DateTime;
20
use core\output\html_writer;
21
 
22
defined('MOODLE_INTERNAL') || die();
23
 
24
require_once($CFG->dirroot . '/mod/assign/locallib.php');
25
 
26
/**
27
 * Helper for sending assignment related notifications.
28
 *
29
 * @package    mod_assign
30
 * @copyright  2024 David Woloszyn <david.woloszyn@moodle.com>
31
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32
 */
33
class notification_helper {
34
 
35
    /**
36
     * @var int Due soon time interval of 48 hours.
37
     */
38
    private const INTERVAL_DUE_SOON = (DAYSECS * 2);
39
 
40
    /**
41
     * @var int Overdue time interval of 2 hours.
42
     */
43
    private const INTERVAL_OVERDUE = (HOURSECS * 2);
44
 
45
    /**
46
     * @var int Due digest time interval of 7 days.
47
     */
48
    private const INTERVAL_DUE_DIGEST = WEEKSECS;
49
 
50
    /**
51
     * @var string Due soon notification type.
52
     */
53
    public const TYPE_DUE_SOON = 'assign_due_soon';
54
 
55
    /**
56
     * @var string Overdue notification type.
57
     */
58
    public const TYPE_OVERDUE = 'assign_overdue';
59
 
60
    /**
61
     * @var string Due digest notification type.
62
     */
63
    public const TYPE_DUE_DIGEST = 'assign_due_digest';
64
 
65
    /**
66
     * Get all assignments that have an approaching due date (includes users and groups with due date overrides).
67
     *
68
     * @return \moodle_recordset Returns the matching assignment records.
69
     */
70
    public static function get_due_soon_assignments(): \moodle_recordset {
71
        global $DB;
72
 
73
        $timenow = self::get_time_now();
74
        $futuretime = self::get_future_time(self::INTERVAL_DUE_SOON);
75
 
76
        $sql = "SELECT DISTINCT a.id
77
                  FROM {assign} a
78
                  JOIN {course_modules} cm ON a.id = cm.instance
79
                  JOIN {course} c ON a.course = c.id
80
                  JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
81
             LEFT JOIN {assign_overrides} ao ON a.id = ao.assignid
82
                 WHERE (a.duedate < :futuretime OR ao.duedate < :ao_futuretime)
83
                   AND (a.duedate > :timenow OR ao.duedate > :ao_timenow)
84
                   AND cm.visible = 1
85
                   AND c.visible = 1";
86
 
87
        $params = [
88
            'timenow' => $timenow,
89
            'futuretime' => $futuretime,
90
            'ao_timenow' => $timenow,
91
            'ao_futuretime' => $futuretime,
92
            'modulename' => 'assign',
93
        ];
94
 
95
        return $DB->get_recordset_sql($sql, $params);
96
    }
97
 
98
    /**
99
     * Get all assignments that are overdue, but not exceeding the cut-off date (includes users and groups with due date overrides).
100
     *
101
     * We don't want to get every single overdue assignment ever.
102
     * We just want the ones within the specified window.
103
     *
104
     * @return \moodle_recordset Returns the matching assignment records.
105
     */
106
    public static function get_overdue_assignments(): \moodle_recordset {
107
        global $DB;
108
 
109
        $timenow = self::get_time_now();
110
        $timewindow = self::get_time_now() - self::INTERVAL_OVERDUE;
111
 
112
        // Get all assignments that:
113
        // - Are overdue.
114
        // - Do not exceed the window of time in the past.
115
        // - Are still within the cut-off (if it is set).
116
        $sql = "SELECT DISTINCT a.id
117
                  FROM {assign} a
118
                  JOIN {course_modules} cm ON a.id = cm.instance
119
                  JOIN {course} c ON a.course = c.id
120
                  JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
121
             LEFT JOIN {assign_overrides} ao ON a.id = ao.assignid
122
                 WHERE (a.duedate < :dd_timenow OR ao.duedate < :dd_ao_timenow)
123
                   AND (a.duedate > :dd_timewindow OR ao.duedate > :dd_ao_timewindow)
124
                   AND ((a.cutoffdate > :co_timenow OR a.cutoffdate = 0) OR
125
                       (ao.cutoffdate > :co_ao_timenow OR ao.cutoffdate = 0))
126
                   AND cm.visible = 1
127
                   AND c.visible = 1";
128
 
129
        $params = [
130
            'dd_timenow' => $timenow,
131
            'dd_ao_timenow' => $timenow,
132
            'dd_timewindow' => $timewindow,
133
            'dd_ao_timewindow' => $timewindow,
134
            'co_timenow' => $timenow,
135
            'co_ao_timenow' => $timenow,
136
            'modulename' => 'assign',
137
        ];
138
 
139
        return $DB->get_recordset_sql($sql, $params);
140
    }
141
 
142
    /**
143
     * Get all assignments that are due in 7 days (includes users and groups with due date overrides).
144
     *
145
     * @return \moodle_recordset Returns the matching assignment records.
146
     */
147
    public static function get_due_digest_assignments(): \moodle_recordset {
148
        global $DB;
149
 
150
        $futuretime = self::get_future_time(self::INTERVAL_DUE_DIGEST);
151
        $day = self::get_day_start_and_end($futuretime);
152
 
153
        $sql = "SELECT DISTINCT a.id
154
                  FROM {assign} a
155
                  JOIN {course_modules} cm ON a.id = cm.instance
156
                  JOIN {course} c ON a.course = c.id
157
                  JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
158
             LEFT JOIN {assign_overrides} ao ON a.id = ao.assignid
159
                 WHERE (a.duedate <= :endofday OR ao.duedate <= :ao_endofday)
160
                   AND (a.duedate >= :startofday OR ao.duedate >= :ao_startofday)
161
                   AND cm.visible = 1
162
                   AND c.visible = 1";
163
 
164
        $params = [
165
            'startofday' => $day['start'],
166
            'endofday' => $day['end'],
167
            'ao_startofday' => $day['start'],
168
            'ao_endofday' => $day['end'],
169
            'modulename' => 'assign',
170
        ];
171
 
172
        return $DB->get_recordset_sql($sql, $params);
173
    }
174
 
175
    /**
176
     * Get all assignments for a user that are due in 7 days (includes users and groups with due date overrides).
177
     *
178
     * @param int $userid The user id.
179
     * @return \moodle_recordset Returns the matching assignment records.
180
     */
181
    public static function get_due_digest_assignments_for_user(int $userid): \moodle_recordset {
182
        global $DB;
183
 
184
        $futuretime = self::get_future_time(self::INTERVAL_DUE_DIGEST);
185
        $day = self::get_day_start_and_end($futuretime);
186
 
187
        $sql = "SELECT DISTINCT a.id,
188
                       a.duedate,
189
                       a.name AS assignmentname,
190
                       c.fullname AS coursename,
191
                       cm.id AS cmid
192
                  FROM {assign} a
193
                  JOIN {course} c ON a.course = c.id
194
                  JOIN {course_modules} cm ON a.id = cm.instance
195
                  JOIN {modules} m ON cm.module = m.id AND m.name = :modulename
196
                  JOIN {enrol} e ON c.id = e.courseid
197
                  JOIN {user_enrolments} ue ON e.id = ue.enrolid
198
             LEFT JOIN {assign_overrides} ao ON a.id = ao.assignid
199
                 WHERE (a.duedate <= :endofday OR ao.duedate <= :ao_endofday)
200
                   AND (a.duedate >= :startofday OR ao.duedate >= :ao_startofday)
201
                   AND ue.userid = :userid
202
                   AND cm.visible = 1
203
                   AND c.visible = 1
204
              ORDER BY a.duedate ASC";
205
 
206
        $params = [
207
            'startofday' => $day['start'],
208
            'endofday' => $day['end'],
209
            'ao_startofday' => $day['start'],
210
            'ao_endofday' => $day['end'],
211
            'modulename' => 'assign',
212
            'userid' => $userid,
213
        ];
214
 
215
        return $DB->get_recordset_sql($sql, $params);
216
    }
217
 
218
    /**
219
     * Get all assignment users that we should send the notification to.
220
     *
221
     * @param int $assignmentid The assignment id.
222
     * @param string $type The notification type.
223
     * @return array The users after all filtering has been applied.
224
     */
225
    public static function get_users_within_assignment(int $assignmentid, string $type): array {
226
        // Get assignment data.
227
        $assignmentobj = self::get_assignment_data($assignmentid);
228
 
229
        // Get our assignment users.
230
        $users = $assignmentobj->list_participants(0, true);
231
 
232
        foreach ($users as $key => $user) {
233
            // Check if the user has submitted already.
234
            $submission = $assignmentobj->get_user_submission($user->id, false);
235
            if ($submission && $submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
236
                unset($users[$key]);
237
                continue;
238
            }
239
 
240
            // Determine key dates with respect to any overrides.
241
            $duedate = $assignmentobj->override_exists($user->id)->duedate ?? $assignmentobj->get_instance()->duedate;
242
            $cutoffdate = $assignmentobj->override_exists($user->id)->cutoffdate ?? $assignmentobj->get_instance()->cutoffdate;
243
 
244
            // If the due date has no value, unset this user.
245
            if (empty($duedate)) {
246
                unset($users[$key]);
247
                continue;
248
            }
249
 
250
            // Perform some checks depending on the notification type.
251
            $match = [];
252
            $checksent = true;
253
            switch ($type) {
254
                case self::TYPE_DUE_SOON:
255
                    $range = [
256
                        'lower' => self::get_time_now(),
257
                        'upper' => self::get_future_time(self::INTERVAL_DUE_SOON),
258
                    ];
259
                    if (!self::is_time_within_range($duedate, $range)) {
260
                        unset($users[$key]);
261
                        break;
262
                    }
263
                    $match = [
264
                        'assignmentid' => $assignmentid,
265
                        'duedate' => $duedate,
266
                    ];
267
                    break;
268
 
269
                case self::TYPE_OVERDUE:
270
                    if ($duedate > self::get_time_now()) {
271
                        unset($users[$key]);
272
                        break;
273
                    }
274
                    // Check if the cut-off date is set and passed already.
275
                    if (!empty($cutoffdate) && self::get_time_now() > $cutoffdate) {
276
                        unset($users[$key]);
277
                        break;
278
                    }
279
                    $match = [
280
                        'assignmentid' => $assignmentid,
281
                        'duedate' => $duedate,
282
                        'cutoffdate' => $cutoffdate,
283
                    ];
284
                    break;
285
 
286
                case self::TYPE_DUE_DIGEST:
287
                    $checksent = false;
288
                    $futuretime = self::get_future_time(self::INTERVAL_DUE_DIGEST);
289
                    $day = self::get_day_start_and_end($futuretime);
290
                    $range = [
291
                        'lower' => $day['start'],
292
                        'upper' => $day['end'],
293
                    ];
294
                    if (!self::is_time_within_range($duedate, $range)) {
295
                        unset($users[$key]);
296
                        break;
297
                    }
298
                    break;
299
 
300
                default:
301
                    break;
302
            }
303
 
304
            // Check if the user has already received this notification.
305
            if ($checksent && self::has_user_been_sent_a_notification_already($user->id, json_encode($match), $type)) {
306
                unset($users[$key]);
307
            }
308
        }
309
 
310
        return $users;
311
    }
312
 
313
    /**
314
     * Send the due soon notification to the user.
315
     *
316
     * @param int $assignmentid The assignment id.
317
     * @param int $userid The user id.
318
     */
319
    public static function send_due_soon_notification_to_user(int $assignmentid, int $userid): void {
320
        try {
321
            // Get assignment data.
322
            $assignmentobj = self::get_assignment_data($assignmentid);
323
        } catch (\dml_missing_record_exception) {
324
            // The assignment has vanished, nothing to do.
325
            mtrace("No notification send as the assignment $assignmentid can no longer be found in the database.");
326
            return;
327
        }
328
 
329
        // Check if the due date still within range.
330
        $assignmentobj->update_effective_access($userid);
331
        $duedate = $assignmentobj->get_instance($userid)->duedate;
332
        $range = [
333
            'lower' => self::get_time_now(),
334
            'upper' => self::get_future_time(self::INTERVAL_DUE_SOON),
335
        ];
336
        if (!self::is_time_within_range($duedate, $range)) {
337
            return;
338
        }
339
 
340
        // Check if the user has submitted already.
341
        $submission = $assignmentobj->get_user_submission($userid, false);
342
        if ($submission && $submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
343
            return;
344
        }
345
 
346
        // Build the user's notification message.
347
        $user = $assignmentobj->get_participant($userid);
348
        $urlparams = [
349
            'id' => $assignmentobj->get_course_module()->id,
350
            'action' => 'view',
351
        ];
352
        $url = new \moodle_url('/mod/assign/view.php', $urlparams);
353
 
354
        $stringparams = [
355
            'firstname' => $user->firstname,
356
            'assignmentname' => $assignmentobj->get_instance()->name,
357
            'coursename' => $assignmentobj->get_course()->fullname,
358
            'duedate' => userdate($duedate),
359
            'url' => $url,
360
        ];
361
 
362
        $messagedata = [
363
            'user' => \core_user::get_user($user->id),
364
            'url' => $url->out(false),
365
            'subject' => get_string('assignmentduesoonsubject', 'mod_assign', $stringparams),
366
            'assignmentname' => $assignmentobj->get_instance()->name,
367
            'html' => get_string('assignmentduesoonhtml', 'mod_assign', $stringparams),
368
            'sms' => get_string('assignmentduesoonsms', 'mod_assign', $stringparams),
369
        ];
370
 
371
        $message = new \core\message\message();
372
        $message->component = 'mod_assign';
373
        $message->name = self::TYPE_DUE_SOON;
374
        $message->userfrom = \core_user::get_noreply_user();
375
        $message->userto = $messagedata['user'];
376
        $message->subject = $messagedata['subject'];
377
        $message->fullmessageformat = FORMAT_HTML;
378
        $message->fullmessage = html_to_text($messagedata['html']);
379
        $message->fullmessagehtml = $messagedata['html'];
380
        $message->fullmessagesms = $messagedata['sms'];
381
        $message->smallmessage = $messagedata['subject'];
382
        $message->notification = 1;
383
        $message->contexturl = $messagedata['url'];
384
        $message->contexturlname = $messagedata['assignmentname'];
385
        // Use custom data to avoid future notifications being sent again.
386
        $message->customdata = [
387
            'assignmentid' => $assignmentid,
388
            'duedate' => $duedate,
389
        ];
390
 
391
        message_send($message);
392
    }
393
 
394
    /**
395
     * Send the overdue notification to the user.
396
     *
397
     * @param int $assignmentid The assignment id.
398
     * @param int $userid The user id.
399
     */
400
    public static function send_overdue_notification_to_user(int $assignmentid, int $userid): void {
401
        try {
402
            // Get assignment data.
403
            $assignmentobj = self::get_assignment_data($assignmentid);
404
        } catch (\dml_missing_record_exception) {
405
            // The assignment has vanished, nothing to do.
406
            mtrace("No notification send as the assignment $assignmentid can no longer be found in the database.");
407
            return;
408
        }
409
 
410
        // Get the user and check they are a still a valid participant.
411
        $user = $assignmentobj->get_participant($userid);
412
        if (empty($user)) {
413
            return;
414
        }
415
 
416
        // Check if the due date still considered overdue.
417
        $assignmentobj->update_effective_access($userid);
418
        $duedate = $assignmentobj->get_instance($userid)->duedate;
419
        if ($duedate > self::get_time_now()) {
420
            return;
421
        }
422
 
423
        // Check if the cut-off date is set and passed already.
424
        $cutoffdate = $assignmentobj->get_instance($userid)->cutoffdate;
425
        if (!empty($cutoffdate) && self::get_time_now() > $cutoffdate) {
426
            return;
427
        }
428
 
429
        // Check if the user has submitted already.
430
        $submission = $assignmentobj->get_user_submission($userid, false);
431
        if ($submission && $submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
432
            return;
433
        }
434
 
435
        // Build the user's notification message.
436
        $urlparams = [
437
            'id' => $assignmentobj->get_course_module()->id,
438
            'action' => 'view',
439
        ];
440
        $url = new \moodle_url('/mod/assign/view.php', $urlparams);
441
 
442
        // Prepare the cut-off date html string.
443
        $snippet = '';
444
        if (!empty($cutoffdate)) {
445
            $snippet = get_string('assignmentoverduehtmlcutoffsnippet', 'mod_assign', ['cutoffdate' => userdate($cutoffdate)]);
446
        }
447
 
448
        $stringparams = [
449
            'firstname' => $user->firstname,
450
            'assignmentname' => $assignmentobj->get_instance()->name,
451
            'coursename' => $assignmentobj->get_course()->fullname,
452
            'duedate' => userdate($duedate),
453
            'url' => $url,
454
            'cutoffsnippet' => $snippet,
455
        ];
456
 
457
        $messagedata = [
458
            'user' => \core_user::get_user($user->id),
459
            'url' => $url->out(false),
460
            'subject' => get_string('assignmentoverduesubject', 'mod_assign', $stringparams),
461
            'assignmentname' => $assignmentobj->get_instance()->name,
462
            'html' => get_string('assignmentoverduehtml', 'mod_assign', $stringparams),
463
            'sms' => get_string('assignmentoverduesms', 'mod_assign', $stringparams),
464
        ];
465
 
466
        $message = new \core\message\message();
467
        $message->component = 'mod_assign';
468
        $message->name = self::TYPE_OVERDUE;
469
        $message->userfrom = \core_user::get_noreply_user();
470
        $message->userto = $messagedata['user'];
471
        $message->subject = $messagedata['subject'];
472
        $message->fullmessageformat = FORMAT_HTML;
473
        $message->fullmessage = html_to_text($messagedata['html']);
474
        $message->fullmessagehtml = $messagedata['html'];
475
        $message->fullmessagesms = $messagedata['sms'];
476
        $message->smallmessage = $messagedata['subject'];
477
        $message->notification = 1;
478
        $message->contexturl = $messagedata['url'];
479
        $message->contexturlname = $messagedata['assignmentname'];
480
        // Use custom data to avoid future notifications being sent again.
481
        $message->customdata = [
482
            'assignmentid' => $assignmentid,
483
            'duedate' => $duedate,
484
            'cutoffdate' => $cutoffdate,
485
        ];
486
 
487
        message_send($message);
488
    }
489
 
490
    /**
491
     * Get all the assignments and send the due digest notification to the user.
492
     *
493
     * @param int $userid The user id.
494
     */
495
    public static function send_due_digest_notification_to_user(int $userid): void {
496
        // Get all the user's assignments due in 7 days.
497
        $assignments = self::get_due_digest_assignments_for_user($userid);
498
        $assignmentsfordigest = [];
499
 
500
        foreach ($assignments as $assignment) {
501
            $assignmentobj = self::get_assignment_data($assignment->id);
502
 
503
            // Check if the user has submitted already.
504
            $submission = $assignmentobj->get_user_submission($userid, false);
505
            if ($submission && $submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
506
                continue;
507
            }
508
 
509
            // Check if the module is visible to the user.
510
            $cm = $assignmentobj->get_course_module();
511
            if (!\core_availability\info_module::is_user_visible($cm, $userid)) {
512
                continue;
513
            }
514
 
515
            // Check if the due date is still within range.
516
            $assignmentobj->update_effective_access($userid);
517
            $duedate = $assignmentobj->get_instance($userid)->duedate;
518
            $futuretime = self::get_future_time(self::INTERVAL_DUE_DIGEST);
519
            $day = self::get_day_start_and_end($futuretime);
520
            $range = [
521
                'lower' => $day['start'],
522
                'upper' => $day['end'],
523
            ];
524
            if (!self::is_time_within_range($duedate, $range)) {
525
                continue;
526
            }
527
 
528
            // Record the assignment data to help us build the digest.
529
            $urlparams = [
530
                'id' => $assignmentobj->get_course_module()->id,
531
                'action' => 'view',
532
            ];
533
            $assignmentsfordigest[$assignment->id] = [
534
                'assignmentname' => $assignmentobj->get_instance()->name,
535
                'coursename' => $assignmentobj->get_course()->fullname,
536
                'duetime' => userdate($duedate, get_string('strftimetime12', 'langconfig')),
537
                'url' => new \moodle_url('/mod/assign/view.php', $urlparams),
538
            ];
539
        }
540
        $assignments->close();
541
 
542
        // If there are no assignments in the digest, don't send anything.
543
        if (empty($assignmentsfordigest)) {
544
            return;
545
        }
546
 
547
        // Build the digest.
548
        $digestarray = [];
549
        foreach ($assignmentsfordigest as $digestitem) {
550
            $digestarray[] = get_string('assignmentduedigestitem', 'mod_assign', $digestitem);
551
        }
552
 
553
        // Put the digest into list.
554
        $digest = html_writer::alist($digestarray);
555
 
556
        // Get user's object.
557
        $userobject = \core_user::get_user($userid);
558
 
559
        $stringparams = [
560
            'firstname' => $userobject->firstname,
561
            'duedate' => userdate(self::get_future_time(self::INTERVAL_DUE_DIGEST), get_string('strftimedaydate', 'langconfig')),
562
            'digest' => $digest,
563
        ];
564
 
565
        $messagedata = [
566
            'user' => $userobject,
567
            'subject' => get_string('assignmentduedigestsubject', 'mod_assign'),
568
            'html' => get_string('assignmentduedigesthtml', 'mod_assign', $stringparams),
569
            'sms' => get_string('assignmentduedigestsms', 'mod_assign', $stringparams),
570
        ];
571
 
572
        $message = new \core\message\message();
573
        $message->component = 'mod_assign';
574
        $message->name = self::TYPE_DUE_DIGEST;
575
        $message->userfrom = \core_user::get_noreply_user();
576
        $message->userto = $messagedata['user'];
577
        $message->subject = $messagedata['subject'];
578
        $message->fullmessageformat = FORMAT_HTML;
579
        $message->fullmessage = html_to_text($messagedata['html']);
580
        $message->fullmessagehtml = $messagedata['html'];
581
        $message->fullmessagesms = $messagedata['sms'];
582
        $message->smallmessage = $messagedata['subject'];
583
        $message->notification = 1;
584
 
585
        message_send($message);
586
    }
587
 
588
    /**
589
     * Get the time now.
590
     *
591
     * @return int The time now as a timestamp.
592
     */
593
    protected static function get_time_now(): int {
594
        return \core\di::get(\core\clock::class)->time();
595
    }
596
 
597
    /**
598
     * Get a future time.
599
     *
600
     * @param int $interval Amount of seconds added to the now time.
601
     * @return int The time now value plus the interval.
602
     */
603
    protected static function get_future_time(int $interval): int {
604
        return self::get_time_now() + $interval;
605
    }
606
 
607
    /**
608
     * Get the timestamps for the start (00:00:00) and end (23:59:59) of the provided day.
609
     *
610
     * @param int $timestamp The timestamp to base the calculation on.
611
     * @return array Day start and end timestamps.
612
     */
613
    protected static function get_day_start_and_end(int $timestamp): array {
614
        $day = [];
615
 
616
        $date = new DateTime();
617
        $date->setTimestamp($timestamp);
618
        $date->setTime(0, 0, 0);
619
        $day['start'] = $date->getTimestamp();
620
        $date->setTime(23, 59, 59);
621
        $day['end'] = $date->getTimestamp();
622
 
623
        return $day;
624
    }
625
 
626
    /**
627
     * Check if a time is within the current time now and the future time values (inclusive).
628
     *
629
     * @param int $time The timestamp to check.
630
     * @param array $range Lower and upper times to check.
631
     * @return boolean
632
     */
633
    protected static function is_time_within_range(int $time, array $range): bool {
634
        return ($time >= $range['lower'] && $time <= $range['upper']);
635
    }
636
 
637
    /**
638
     * Check if a user has been sent a notification already.
639
     *
640
     * @param int $userid The user id.
641
     * @param string $match The custom data string to match on.
642
     * @param string $type The notification/event type to match.
643
     * @return bool Returns true if already sent.
644
     */
645
    protected static function has_user_been_sent_a_notification_already(int $userid, string $match, string $type): bool {
646
        global $DB;
647
 
648
        $sql = $DB->sql_compare_text('customdata', 255) . " = " . $DB->sql_compare_text(':match', 255) . "
649
            AND useridto = :userid
650
            AND component = :component
651
            AND eventtype = :eventtype";
652
 
653
        return $DB->record_exists_select('notifications', $sql, [
654
            'userid' => $userid,
655
            'match' => $match,
656
            'component' => 'mod_assign',
657
            'eventtype' => $type,
658
        ]);
659
    }
660
 
661
    /**
662
     * Get the assignment object, including the course and course module.
663
     *
664
     * @param int $assignmentid The assignment id.
665
     * @return \assign Returns the assign object.
666
     */
667
    protected static function get_assignment_data(int $assignmentid): \assign {
668
        [$course, $assigncm] = get_course_and_cm_from_instance($assignmentid, 'assign');
669
        $cmcontext = \context_module::instance($assigncm->id);
670
        return new \assign($cmcontext, $assigncm, $course);
671
    }
672
}