weekly back-to-dev release 5.0dev
[moodle.git] / communication / classes / hook_listener.php
blob82a25d19bb99f433e90cf85c2005c1f9f2216690
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.
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/>.
17 namespace core_communication;
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;
38 /**
39 * Hook listener for communication api.
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
45 class hook_listener {
47 /**
48 * Get the course and group object for the group hook.
50 * @param mixed $hook The hook object.
51 * @return array
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,
59 return [
60 $group,
61 $course,
65 /**
66 * Communication api call to create room for a group if course has group mode enabled.
68 * @param after_group_created $hook The group created hook.
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,
77 // Check if group mode enabled before handling the communication.
78 if (!helper::is_group_mode_enabled_for_course(course: $course)) {
79 return;
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,
89 // Check we have communication correctly set up before proceeding.
90 if ($coursecommunication->get_processor() === null) {
91 return;
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(),
102 $communicationroomname = helper::format_group_room_name(
103 baseroomname: $coursecommunication->get_room_name(),
104 groupname: $group->name,
107 $communication->create_and_configure_room(
108 communicationroomname: $communicationroomname,
109 instance: $course,
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,
118 $communication->add_members_to_room(
119 userids: $userstoadd,
120 queue: false,
125 * Communication api call to update room for a group if course has group mode enabled.
127 * @param after_group_updated $hook The group updated hook.
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,
136 // Check if group mode enabled before handling the communication.
137 if (!helper::is_group_mode_enabled_for_course(course: $course)) {
138 return;
141 $coursecontext = \context_course::instance(courseid: $course->id);
142 $communication = helper::load_by_group(
143 groupid: $group->id,
144 context: $coursecontext,
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,
153 $communicationroomname = helper::format_group_room_name(
154 baseroomname: $coursecommunication->get_room_name(),
155 groupname: $group->name,
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;
163 $communication->update_room(
164 active: processor::PROVIDER_ACTIVE,
165 communicationroomname: $communicationroomname,
166 instance: $course,
171 * Delete the communication room for a group if course has group mode enabled.
173 * @param after_group_deleted $hook The group deleted hook.
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,
182 // Check if group mode enabled before handling the communication.
183 if (!helper::is_group_mode_enabled_for_course(course: $course)) {
184 return;
187 $context = context_course::instance($course->id);
188 $communication = helper::load_by_group(
189 groupid: $group->id,
190 context: $context,
192 $communication->delete_room();
196 * Add members to group room when a new member is added to the group.
198 * @param after_group_membership_added $hook The group membership added hook.
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,
207 // Check if group mode enabled before handling the communication.
208 if (!helper::is_group_mode_enabled_for_course(course: $course)) {
209 return;
212 $context = context_course::instance($course->id);
213 $communication = helper::load_by_group(
214 groupid: $group->id,
215 context: $context,
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);
222 $communication->add_members_to_room(
223 userids: $userids,
228 * Remove members from the room when a member is removed from group room.
230 * @param after_group_membership_removed $hook The group membership removed hook.
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,
239 // Check if group mode enabled before handling the communication.
240 if (!helper::is_group_mode_enabled_for_course(course: $course)) {
241 return;
244 $context = context_course::instance($course->id);
245 $communication = helper::load_by_group(
246 groupid: $group->id,
247 context: $context,
249 $communication->remove_members_from_room(
250 userids: $hook->userids,
255 * Create course communication instance.
257 * @param after_course_created $hook The course created hook.
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;
267 $course = $hook->course;
269 // Check for default provider config setting.
270 $defaultprovider = get_config(
271 plugin: 'moodlecourse',
272 name: 'coursecommunicationprovider',
274 $provider = $course->selectedcommunication ?? $defaultprovider;
276 if (empty($provider) || $provider === processor::PROVIDER_NONE) {
277 return;
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;
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,
302 $communication->create_and_configure_room(
303 communicationroomname: $communicationroomname,
304 avatar: $courseimage,
305 instance: $course,
306 queue: $createcourseroom,
309 // Communication api call for group communication.
310 if ($creategrouprooms) {
311 helper::update_group_communication_instances_for_course(
312 course: $course,
313 provider: $provider,
315 } else {
316 $enrolledusers = helper::get_enrolled_users_for_course(course: $course);
317 $communication->add_members_to_room(
318 userids: $enrolledusers,
319 queue: false,
325 * Update the course communication instance.
327 * @param after_course_updated $hook The course updated hook.
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;
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,
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.
353 * @param before_course_deleted $hook The course deleted hook.
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;
363 $course = $hook->course;
364 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
365 $coursecontext = \context_course::instance(courseid: $course->id);
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,
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,
382 $communication->delete_room();
388 * Update the room membership for the user updates.
390 * @param before_user_updated $hook The user updated hook.
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;
400 $user = $hook->user;
401 $currentuserrecord = $hook->currentuserdata;
403 // Get the user courses.
404 $usercourses = enrol_get_users_courses(userid: $user->id);
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,
422 * Delete all room memberships for a user.
424 * @param before_user_deleted $hook The user deleted hook.
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;
434 $user = $hook->user;
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);
440 if ((int)$groupmode === NOGROUPS) {
441 $communication = helper::load_by_course(
442 courseid: $course->id,
443 context: $coursecontext,
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]);
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,
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]);
468 * Update the room membership of the user for role assigned in a course.
470 * @param after_role_assigned|after_role_unassigned $hook
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;
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',
491 * Update the communication memberships for enrol status change.
493 * @param after_enrol_instance_status_updated $hook The enrol status updated hook.
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;
503 $enrolinstance = $hook->enrolinstance;
504 // No need to do anything for guest instances.
505 if ($enrolinstance->enrol === 'guest') {
506 return;
509 $newstatus = $hook->newstatus;
510 // Check if a valid status is given.
511 if (
512 $newstatus !== ENROL_INSTANCE_ENABLED ||
513 $newstatus !== ENROL_INSTANCE_DISABLED
515 return;
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;
530 global $DB;
531 $instanceusers = $DB->get_records(
532 table: 'user_enrolments',
533 conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
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,
544 * Remove the communication instance memberships when an enrolment instance is deleted.
546 * @param before_enrol_instance_deleted $hook The enrol instance deleted hook.
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;
556 $enrolinstance = $hook->enrolinstance;
557 // No need to do anything for guest instances.
558 if ($enrolinstance->enrol === 'guest') {
559 return;
562 global $DB;
563 $instanceusers = $DB->get_records(
564 table: 'user_enrolments',
565 conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
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',
576 * Add communication instance membership for an enrolled user.
578 * @param after_user_enrolled $hook The user enrolled hook.
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;
588 $enrolinstance = $hook->enrolinstance;
589 // No need to do anything for guest instances.
590 if ($enrolinstance->enrol === 'guest') {
591 return;
594 helper::update_course_communication_room_membership(
595 course: get_course($enrolinstance->courseid),
596 userids: [$hook->get_userid()],
597 memberaction: 'add_members_to_room',
602 * Update the communication instance membership for the user enrolment updates.
604 * @param before_user_enrolment_updated $hook The user enrolment updated hook.
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;
614 $enrolinstance = $hook->enrolinstance;
615 // No need to do anything for guest instances.
616 if ($enrolinstance->enrol === 'guest') {
617 return;
620 $userenrolmentinstance = $hook->userenrolmentinstance;
621 $statusmodified = $hook->statusmodified;
622 $timeendmodified = $hook->timeendmodified;
624 if (
625 ($statusmodified && ((int) $userenrolmentinstance->status === 1)) ||
626 ($timeendmodified && $userenrolmentinstance->timeend !== 0 && (time() > $userenrolmentinstance->timeend))
628 $action = 'remove_members_from_room';
629 } else {
630 $action = 'add_members_to_room';
633 helper::update_course_communication_room_membership(
634 course: get_course($enrolinstance->courseid),
635 userids: [$hook->get_userid()],
636 memberaction: $action,
641 * Remove communication instance membership for an enrolled user.
643 * @param before_user_enrolment_removed $hook The user unenrolled hook.
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;
653 $enrolinstance = $hook->enrolinstance;
654 // No need to do anything for guest instances.
655 if ($enrolinstance->enrol === 'guest') {
656 return;
659 helper::update_course_communication_room_membership(
660 course: get_course($enrolinstance->courseid),
661 userids: [$hook->get_userid()],
662 memberaction: 'remove_members_from_room',