Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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 core_communication;
18
 
19
use context_course;
20
use core\hook\access\after_role_assigned;
21
use core\hook\access\after_role_unassigned;
22
use core_enrol\hook\before_enrol_instance_deleted;
23
use core_enrol\hook\after_enrol_instance_status_updated;
24
use core_enrol\hook\after_user_enrolled;
25
use core_enrol\hook\before_user_enrolment_updated;
26
use core_enrol\hook\before_user_enrolment_removed;
27
use core_course\hook\after_course_created;
28
use core_course\hook\before_course_deleted;
29
use core_course\hook\after_course_updated;
30
use core_group\hook\after_group_created;
31
use core_group\hook\after_group_deleted;
32
use core_group\hook\after_group_membership_added;
33
use core_group\hook\after_group_membership_removed;
34
use core_group\hook\after_group_updated;
35
use core_user\hook\before_user_deleted;
36
use core_user\hook\before_user_updated;
37
 
38
/**
39
 * Hook listener for communication api.
40
 *
41
 * @package    core_communication
42
 * @copyright  2023 Safat Shahin <safat.shahin@moodle.com>
43
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44
 */
45
class hook_listener {
46
 
47
    /**
48
     * Get the course and group object for the group hook.
49
     *
50
     * @param mixed $hook The hook object.
51
     * @return array
52
     */
53
    protected static function get_group_and_course_data_for_group_hook(mixed $hook): array {
54
        $group = $hook->groupinstance;
55
        $course = helper::get_course(
56
            courseid: $group->courseid,
57
        );
58
 
59
        return [
60
            $group,
61
            $course,
62
        ];
63
    }
64
 
65
    /**
66
     * Communication api call to create room for a group if course has group mode enabled.
67
     *
68
     * @param after_group_created $hook The group created hook.
69
     */
70
    public static function create_group_communication(
71
        after_group_created $hook,
72
    ): void {
73
        [$group, $course] = self::get_group_and_course_data_for_group_hook(
74
            hook: $hook,
75
        );
76
 
77
        // Check if group mode enabled before handling the communication.
78
        if (!helper::is_group_mode_enabled_for_course(course: $course)) {
79
            return;
80
        }
81
 
82
        $coursecontext = \context_course::instance(courseid: $course->id);
83
        // Get the course communication instance to set the provider.
84
        $coursecommunication = helper::load_by_course(
85
            courseid: $course->id,
86
            context: $coursecontext,
87
        );
88
 
89
        // Check we have communication correctly set up before proceeding.
90
        if ($coursecommunication->get_processor() === null) {
91
            return;
92
        }
93
 
94
        $communication = api::load_by_instance(
95
            context: $coursecontext,
96
            component: constants::GROUP_COMMUNICATION_COMPONENT,
97
            instancetype: constants::GROUP_COMMUNICATION_INSTANCETYPE,
98
            instanceid: $group->id,
99
            provider: $coursecommunication->get_provider(),
100
        );
101
 
102
        $communicationroomname = helper::format_group_room_name(
103
            baseroomname: $coursecommunication->get_room_name(),
104
            groupname: $group->name,
105
        );
106
 
107
        $communication->create_and_configure_room(
108
            communicationroomname: $communicationroomname,
109
            instance: $course,
110
        );
111
 
112
        // As it's a new group, we need to add the users with all access group role to the room.
113
        $enrolledusers = helper::get_enrolled_users_for_course(course: $course);
114
        $userstoadd = helper::get_users_has_access_to_all_groups(
115
            userids: $enrolledusers,
116
            courseid: $course->id,
117
        );
118
        $communication->add_members_to_room(
119
            userids: $userstoadd,
120
            queue: false,
121
        );
122
    }
123
 
124
    /**
125
     * Communication api call to update room for a group if course has group mode enabled.
126
     *
127
     * @param after_group_updated $hook The group updated hook.
128
     */
129
    public static function update_group_communication(
130
        after_group_updated $hook,
131
    ): void {
132
        [$group, $course] = self::get_group_and_course_data_for_group_hook(
133
            hook: $hook,
134
        );
135
 
136
        // Check if group mode enabled before handling the communication.
137
        if (!helper::is_group_mode_enabled_for_course(course: $course)) {
138
            return;
139
        }
140
 
141
        $coursecontext = \context_course::instance(courseid: $course->id);
142
        $communication = helper::load_by_group(
143
            groupid: $group->id,
144
            context: $coursecontext,
145
        );
146
 
147
        // Get the course communication instance so we can extract the base room name.
148
        $coursecommunication = helper::load_by_course(
149
            courseid: $course->id,
150
            context: $coursecontext,
151
        );
152
 
153
        $communicationroomname = helper::format_group_room_name(
154
            baseroomname: $coursecommunication->get_room_name(),
155
            groupname: $group->name,
156
        );
157
 
158
        // If the name didn't change, then we don't need to update the room.
159
        if ($communicationroomname === $communication->get_room_name()) {
160
            return;
161
        }
162
 
163
        $communication->update_room(
164
            active: processor::PROVIDER_ACTIVE,
165
            communicationroomname: $communicationroomname,
166
            instance: $course,
167
        );
168
    }
169
 
170
    /**
171
     * Delete the communication room for a group if course has group mode enabled.
172
     *
173
     * @param after_group_deleted $hook The group deleted hook.
174
     */
175
    public static function delete_group_communication(
176
        after_group_deleted $hook
177
    ): void {
178
        [$group, $course] = self::get_group_and_course_data_for_group_hook(
179
            hook: $hook,
180
        );
181
 
182
        // Check if group mode enabled before handling the communication.
183
        if (!helper::is_group_mode_enabled_for_course(course: $course)) {
184
            return;
185
        }
186
 
187
        $context = context_course::instance($course->id);
188
        $communication = helper::load_by_group(
189
            groupid: $group->id,
190
            context: $context,
191
        );
192
        $communication->delete_room();
193
    }
194
 
195
    /**
196
     * Add members to group room when a new member is added to the group.
197
     *
198
     * @param after_group_membership_added $hook The group membership added hook.
199
     */
200
    public static function add_members_to_group_room(
201
        after_group_membership_added $hook,
202
    ): void {
203
        [$group, $course] = self::get_group_and_course_data_for_group_hook(
204
            hook: $hook,
205
        );
206
 
207
        // Check if group mode enabled before handling the communication.
208
        if (!helper::is_group_mode_enabled_for_course(course: $course)) {
209
            return;
210
        }
211
 
212
        $context = context_course::instance($course->id);
213
        $communication = helper::load_by_group(
214
            groupid: $group->id,
215
            context: $context,
216
        );
11 efrain 217
 
218
        // Filter out users who are not active in this course.
219
        $enrolledusers = helper::get_enrolled_users_for_course($course, true);
220
        $userids = array_intersect($hook->userids, $enrolledusers);
221
 
1 efrain 222
        $communication->add_members_to_room(
11 efrain 223
            userids: $userids,
1 efrain 224
        );
225
    }
226
 
227
    /**
228
     * Remove members from the room when a member is removed from group room.
229
     *
230
     * @param after_group_membership_removed $hook The group membership removed hook.
231
     */
232
    public static function remove_members_from_group_room(
233
        after_group_membership_removed $hook,
234
    ): void {
235
        [$group, $course] = self::get_group_and_course_data_for_group_hook(
236
            hook: $hook,
237
        );
238
 
239
        // Check if group mode enabled before handling the communication.
240
        if (!helper::is_group_mode_enabled_for_course(course: $course)) {
241
            return;
242
        }
243
 
244
        $context = context_course::instance($course->id);
245
        $communication = helper::load_by_group(
246
            groupid: $group->id,
247
            context: $context,
248
        );
249
        $communication->remove_members_from_room(
250
            userids: $hook->userids,
251
        );
252
    }
253
 
254
    /**
255
     * Create course communication instance.
256
     *
257
     * @param after_course_created $hook The course created hook.
258
     */
259
    public static function create_course_communication(
260
        after_course_created $hook,
261
    ): void {
262
        // If the communication subsystem is not enabled then just ignore.
263
        if (!api::is_available()) {
264
            return;
265
        }
266
 
267
        $course = $hook->course;
268
 
269
        // Check for default provider config setting.
270
        $defaultprovider = get_config(
271
            plugin: 'moodlecourse',
272
            name: 'coursecommunicationprovider',
273
        );
274
        $provider = $course->selectedcommunication ?? $defaultprovider;
275
 
276
        if (empty($provider) || $provider === processor::PROVIDER_NONE) {
277
            return;
278
        }
279
 
280
        // Check for group mode, we will have to get the course data again as the group info is not always in the object.
281
        $createcourseroom = true;
282
        $creategrouprooms = false;
283
        $coursedata = get_course(courseid: $course->id);
284
        $groupmode = $course->groupmode ?? $coursedata->groupmode;
285
        if ((int)$groupmode !== NOGROUPS) {
286
            $createcourseroom = false;
287
            $creategrouprooms = true;
288
        }
289
 
290
        // Prepare the communication api data.
291
        $courseimage = course_get_courseimage(course: $course);
292
        $communicationroomname = !empty($course->communicationroomname) ? $course->communicationroomname : $coursedata->fullname;
293
        $coursecontext = \context_course::instance(courseid: $course->id);
294
        // Communication api call for course communication.
295
        $communication = \core_communication\api::load_by_instance(
296
            context: $coursecontext,
297
            component: constants::COURSE_COMMUNICATION_COMPONENT,
298
            instancetype: constants::COURSE_COMMUNICATION_INSTANCETYPE,
299
            instanceid: $course->id,
300
            provider: $provider,
301
        );
302
        $communication->create_and_configure_room(
303
            communicationroomname: $communicationroomname,
304
            avatar: $courseimage,
305
            instance: $course,
306
            queue: $createcourseroom,
307
        );
308
 
309
        // Communication api call for group communication.
310
        if ($creategrouprooms) {
311
            helper::update_group_communication_instances_for_course(
312
                course: $course,
313
                provider: $provider,
314
            );
315
        } else {
316
            $enrolledusers = helper::get_enrolled_users_for_course(course: $course);
317
            $communication->add_members_to_room(
318
                userids: $enrolledusers,
319
                queue: false,
320
            );
321
        }
322
    }
323
 
324
    /**
325
     * Update the course communication instance.
326
     *
327
     * @param after_course_updated $hook The course updated hook.
328
     */
329
    public static function update_course_communication(
330
        after_course_updated $hook,
331
    ): void {
332
        // If the communication subsystem is not enabled then just ignore.
333
        if (!api::is_available()) {
334
            return;
335
        }
336
        $course = $hook->course;
337
        $oldcourse = $hook->oldcourse;
338
        $changeincoursecat = $hook->changeincoursecat;
339
        $groupmode = $course->groupmode ?? get_course($course->id)->groupmode;
340
        if ($changeincoursecat || $groupmode !== $oldcourse->groupmode) {
341
            helper::update_course_communication_instance(
342
                course: $course,
343
                changesincoursecat: $changeincoursecat,
344
            );
345
        }
346
    }
347
 
348
    /**
349
     * Delete course communication data and remove members.
350
     * Course can have communication data if it is a group or a course.
351
     * This action is important to perform even if the experimental feature is disabled.
352
     *
353
     * @param before_course_deleted $hook The course deleted hook.
354
     */
355
    public static function delete_course_communication(
356
        before_course_deleted $hook,
357
    ): void {
358
        // If the communication subsystem is not enabled then just ignore.
359
        if (!api::is_available()) {
360
            return;
361
        }
362
 
363
        $course = $hook->course;
364
        $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
365
        $coursecontext = \context_course::instance(courseid: $course->id);
366
 
367
        // If group mode is not set then just handle the course communication room.
368
        if ((int)$groupmode === NOGROUPS) {
369
            $communication = helper::load_by_course(
370
                courseid: $course->id,
371
                context: $coursecontext,
372
            );
373
            $communication->delete_room();
374
        } else {
375
            // If group mode is set then handle the group communication rooms.
376
            $coursegroups = groups_get_all_groups(courseid: $course->id);
377
            foreach ($coursegroups as $coursegroup) {
378
                $communication = helper::load_by_group(
379
                    groupid: $coursegroup->id,
380
                    context: $coursecontext,
381
                );
382
                $communication->delete_room();
383
            }
384
        }
385
    }
386
 
387
    /**
388
     * Update the room membership for the user updates.
389
     *
390
     * @param before_user_updated $hook The user updated hook.
391
     */
392
    public static function update_user_room_memberships(
393
        before_user_updated $hook,
394
    ): void {
395
        // If the communication subsystem is not enabled then just ignore.
396
        if (!api::is_available()) {
397
            return;
398
        }
399
 
400
        $user = $hook->user;
401
        $currentuserrecord = $hook->currentuserdata;
402
 
403
        // Get the user courses.
404
        $usercourses = enrol_get_users_courses(userid: $user->id);
405
 
406
        // If the user is suspended then remove the user from all the rooms.
407
        // Otherwise add the user to all the rooms for the courses the user enrolled in.
408
        if (!empty($currentuserrecord) && isset($user->suspended) && $currentuserrecord->suspended !== $user->suspended) {
409
            // Decide the action for the communication api for the user.
410
            $memberaction = ($user->suspended === 0) ? 'add_members_to_room' : 'remove_members_from_room';
411
            foreach ($usercourses as $usercourse) {
412
                helper::update_course_communication_room_membership(
413
                    course: $usercourse,
414
                    userids: [$user->id],
415
                    memberaction: $memberaction,
416
                );
417
            }
418
        }
419
    }
420
 
421
    /**
422
     * Delete all room memberships for a user.
423
     *
424
     * @param before_user_deleted $hook The user deleted hook.
425
     */
426
    public static function delete_user_room_memberships(
427
        before_user_deleted $hook,
428
    ): void {
429
        // If the communication subsystem is not enabled then just ignore.
430
        if (!api::is_available()) {
431
            return;
432
        }
433
 
434
        $user = $hook->user;
435
 
436
        foreach (enrol_get_users_courses(userid: $user->id) as $course) {
437
            $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
438
            $coursecontext = \context_course::instance(courseid: $course->id);
439
 
440
            if ((int)$groupmode === NOGROUPS) {
441
                $communication = helper::load_by_course(
442
                    courseid: $course->id,
443
                    context: $coursecontext,
444
                );
1441 ariadna 445
                if ($communication->get_processor() !== null) {
446
                    $communication->get_room_user_provider()->remove_members_from_room(userids: [$user->id]);
447
                    $communication->get_processor()->delete_instance_user_mapping(userids: [$user->id]);
448
                }
1 efrain 449
            } else {
450
                // If group mode is set then handle the group communication rooms.
451
                $coursegroups = groups_get_all_groups(courseid: $course->id);
452
                foreach ($coursegroups as $coursegroup) {
453
                    $communication = helper::load_by_group(
454
                        groupid: $coursegroup->id,
455
                        context: $coursecontext,
456
                    );
1441 ariadna 457
                    if ($communication->get_processor() !== null) {
458
                        $communication->get_room_user_provider()->remove_members_from_room(userids: [$user->id]);
459
                        $communication->get_processor()->delete_instance_user_mapping(userids: [$user->id]);
460
                    }
461
 
1 efrain 462
                }
463
            }
464
        }
465
    }
466
 
467
    /**
468
     * Update the room membership of the user for role assigned in a course.
469
     *
470
     * @param after_role_assigned|after_role_unassigned $hook
471
     */
472
    public static function update_user_membership_for_role_changes(
473
        after_role_assigned|after_role_unassigned $hook,
474
    ): void {
475
        // If the communication subsystem is not enabled then just ignore.
476
        if (!api::is_available()) {
477
            return;
478
        }
479
 
480
        $context = $hook->context;
481
        if ($coursecontext = $context->get_course_context(strict: false)) {
482
            helper::update_course_communication_room_membership(
483
                course: get_course(courseid: $coursecontext->instanceid),
484
                userids: [$hook->userid],
485
                memberaction: 'update_room_membership',
486
            );
487
        }
488
    }
489
 
490
    /**
491
     * Update the communication memberships for enrol status change.
492
     *
493
     * @param after_enrol_instance_status_updated $hook The enrol status updated hook.
494
     */
495
    public static function update_communication_memberships_for_enrol_status_change(
496
        after_enrol_instance_status_updated $hook,
497
    ): void {
498
        // If the communication subsystem is not enabled then just ignore.
499
        if (!api::is_available()) {
500
            return;
501
        }
502
 
503
        $enrolinstance = $hook->enrolinstance;
504
        // No need to do anything for guest instances.
505
        if ($enrolinstance->enrol === 'guest') {
506
            return;
507
        }
508
 
509
        $newstatus = $hook->newstatus;
510
        // Check if a valid status is given.
511
        if (
512
            $newstatus !== ENROL_INSTANCE_ENABLED ||
513
            $newstatus !== ENROL_INSTANCE_DISABLED
514
        ) {
515
            return;
516
        }
517
 
518
        // Check if the status provided is valid.
519
        switch ($newstatus) {
520
            case ENROL_INSTANCE_ENABLED:
521
                $action = 'add_members_to_room';
522
                break;
523
            case ENROL_INSTANCE_DISABLED:
524
                $action = 'remove_members_from_room';
525
                break;
526
            default:
527
                return;
528
        }
529
 
530
        global $DB;
531
        $instanceusers = $DB->get_records(
532
            table: 'user_enrolments',
533
            conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
534
        );
535
        $enrolledusers = array_column($instanceusers, 'userid');
536
        helper::update_course_communication_room_membership(
537
            course: get_course(courseid: $enrolinstance->courseid),
538
            userids: $enrolledusers,
539
            memberaction: $action,
540
        );
541
    }
542
 
543
    /**
544
     * Remove the communication instance memberships when an enrolment instance is deleted.
545
     *
546
     * @param before_enrol_instance_deleted $hook The enrol instance deleted hook.
547
     */
548
    public static function remove_communication_memberships_for_enrol_instance_deletion(
549
        before_enrol_instance_deleted $hook,
550
    ): void {
551
        // If the communication subsystem is not enabled then just ignore.
552
        if (!api::is_available()) {
553
            return;
554
        }
555
 
556
        $enrolinstance = $hook->enrolinstance;
557
        // No need to do anything for guest instances.
558
        if ($enrolinstance->enrol === 'guest') {
559
            return;
560
        }
561
 
562
        global $DB;
563
        $instanceusers = $DB->get_records(
564
            table: 'user_enrolments',
565
            conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
566
        );
567
        $enrolledusers = array_column($instanceusers, 'userid');
568
        helper::update_course_communication_room_membership(
569
            course: get_course(courseid: $enrolinstance->courseid),
570
            userids: $enrolledusers,
571
            memberaction: 'remove_members_from_room',
572
        );
573
    }
574
 
575
    /**
576
     * Add communication instance membership for an enrolled user.
577
     *
578
     * @param after_user_enrolled $hook The user enrolled hook.
579
     */
580
    public static function add_communication_membership_for_enrolled_user(
581
        after_user_enrolled $hook,
582
    ): void {
583
        // If the communication subsystem is not enabled then just ignore.
584
        if (!api::is_available()) {
585
            return;
586
        }
587
 
588
        $enrolinstance = $hook->enrolinstance;
589
        // No need to do anything for guest instances.
590
        if ($enrolinstance->enrol === 'guest') {
591
            return;
592
        }
593
 
594
        helper::update_course_communication_room_membership(
595
            course: get_course($enrolinstance->courseid),
596
            userids: [$hook->get_userid()],
597
            memberaction: 'add_members_to_room',
598
        );
599
    }
600
 
601
    /**
602
     * Update the communication instance membership for the user enrolment updates.
603
     *
604
     * @param before_user_enrolment_updated $hook The user enrolment updated hook.
605
     */
606
    public static function update_communication_membership_for_updated_user_enrolment(
607
        before_user_enrolment_updated $hook,
608
    ): void {
609
        // If the communication subsystem is not enabled then just ignore.
610
        if (!api::is_available()) {
611
            return;
612
        }
613
 
614
        $enrolinstance = $hook->enrolinstance;
615
        // No need to do anything for guest instances.
616
        if ($enrolinstance->enrol === 'guest') {
617
            return;
618
        }
619
 
620
        $userenrolmentinstance = $hook->userenrolmentinstance;
621
        $statusmodified = $hook->statusmodified;
622
        $timeendmodified = $hook->timeendmodified;
623
 
624
        if (
625
            ($statusmodified && ((int) $userenrolmentinstance->status === 1)) ||
626
            ($timeendmodified && $userenrolmentinstance->timeend !== 0 && (time() > $userenrolmentinstance->timeend))
627
        ) {
628
            $action = 'remove_members_from_room';
629
        } else {
630
            $action = 'add_members_to_room';
631
        }
632
 
633
        helper::update_course_communication_room_membership(
634
            course: get_course($enrolinstance->courseid),
635
            userids: [$hook->get_userid()],
636
            memberaction: $action,
637
        );
638
    }
639
 
640
    /**
641
     * Remove communication instance membership for an enrolled user.
642
     *
643
     * @param before_user_enrolment_removed $hook The user unenrolled hook.
644
     */
645
    public static function remove_communication_membership_for_unenrolled_user(
646
        before_user_enrolment_removed $hook,
647
    ): void {
648
        // If the communication subsystem is not enabled then just ignore.
649
        if (!api::is_available()) {
650
            return;
651
        }
652
 
653
        $enrolinstance = $hook->enrolinstance;
654
        // No need to do anything for guest instances.
655
        if ($enrolinstance->enrol === 'guest') {
656
            return;
657
        }
658
 
659
        helper::update_course_communication_room_membership(
660
            course: get_course($enrolinstance->courseid),
661
            userids: [$hook->get_userid()],
662
            memberaction: 'remove_members_from_room',
663
        );
664
    }
665
}