weekly release 4.5dev
[moodle.git] / communication / classes / hook_listener.php
blobcacb35b8c89c60648fd913ed61f929eaac0347d6
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,
217 $communication->add_members_to_room(
218 userids: $hook->userids,
223 * Remove members from the room when a member is removed from group room.
225 * @param after_group_membership_removed $hook The group membership removed hook.
227 public static function remove_members_from_group_room(
228 after_group_membership_removed $hook,
229 ): void {
230 [$group, $course] = self::get_group_and_course_data_for_group_hook(
231 hook: $hook,
234 // Check if group mode enabled before handling the communication.
235 if (!helper::is_group_mode_enabled_for_course(course: $course)) {
236 return;
239 $context = context_course::instance($course->id);
240 $communication = helper::load_by_group(
241 groupid: $group->id,
242 context: $context,
244 $communication->remove_members_from_room(
245 userids: $hook->userids,
250 * Create course communication instance.
252 * @param after_course_created $hook The course created hook.
254 public static function create_course_communication(
255 after_course_created $hook,
256 ): void {
257 // If the communication subsystem is not enabled then just ignore.
258 if (!api::is_available()) {
259 return;
262 $course = $hook->course;
264 // Check for default provider config setting.
265 $defaultprovider = get_config(
266 plugin: 'moodlecourse',
267 name: 'coursecommunicationprovider',
269 $provider = $course->selectedcommunication ?? $defaultprovider;
271 if (empty($provider) || $provider === processor::PROVIDER_NONE) {
272 return;
275 // Check for group mode, we will have to get the course data again as the group info is not always in the object.
276 $createcourseroom = true;
277 $creategrouprooms = false;
278 $coursedata = get_course(courseid: $course->id);
279 $groupmode = $course->groupmode ?? $coursedata->groupmode;
280 if ((int)$groupmode !== NOGROUPS) {
281 $createcourseroom = false;
282 $creategrouprooms = true;
285 // Prepare the communication api data.
286 $courseimage = course_get_courseimage(course: $course);
287 $communicationroomname = !empty($course->communicationroomname) ? $course->communicationroomname : $coursedata->fullname;
288 $coursecontext = \context_course::instance(courseid: $course->id);
289 // Communication api call for course communication.
290 $communication = \core_communication\api::load_by_instance(
291 context: $coursecontext,
292 component: constants::COURSE_COMMUNICATION_COMPONENT,
293 instancetype: constants::COURSE_COMMUNICATION_INSTANCETYPE,
294 instanceid: $course->id,
295 provider: $provider,
297 $communication->create_and_configure_room(
298 communicationroomname: $communicationroomname,
299 avatar: $courseimage,
300 instance: $course,
301 queue: $createcourseroom,
304 // Communication api call for group communication.
305 if ($creategrouprooms) {
306 helper::update_group_communication_instances_for_course(
307 course: $course,
308 provider: $provider,
310 } else {
311 $enrolledusers = helper::get_enrolled_users_for_course(course: $course);
312 $communication->add_members_to_room(
313 userids: $enrolledusers,
314 queue: false,
320 * Update the course communication instance.
322 * @param after_course_updated $hook The course updated hook.
324 public static function update_course_communication(
325 after_course_updated $hook,
326 ): void {
327 // If the communication subsystem is not enabled then just ignore.
328 if (!api::is_available()) {
329 return;
331 $course = $hook->course;
332 $oldcourse = $hook->oldcourse;
333 $changeincoursecat = $hook->changeincoursecat;
334 $groupmode = $course->groupmode ?? get_course($course->id)->groupmode;
335 if ($changeincoursecat || $groupmode !== $oldcourse->groupmode) {
336 helper::update_course_communication_instance(
337 course: $course,
338 changesincoursecat: $changeincoursecat,
344 * Delete course communication data and remove members.
345 * Course can have communication data if it is a group or a course.
346 * This action is important to perform even if the experimental feature is disabled.
348 * @param before_course_deleted $hook The course deleted hook.
350 public static function delete_course_communication(
351 before_course_deleted $hook,
352 ): void {
353 // If the communication subsystem is not enabled then just ignore.
354 if (!api::is_available()) {
355 return;
358 $course = $hook->course;
359 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
360 $coursecontext = \context_course::instance(courseid: $course->id);
362 // If group mode is not set then just handle the course communication room.
363 if ((int)$groupmode === NOGROUPS) {
364 $communication = helper::load_by_course(
365 courseid: $course->id,
366 context: $coursecontext,
368 $communication->delete_room();
369 } else {
370 // If group mode is set then handle the group communication rooms.
371 $coursegroups = groups_get_all_groups(courseid: $course->id);
372 foreach ($coursegroups as $coursegroup) {
373 $communication = helper::load_by_group(
374 groupid: $coursegroup->id,
375 context: $coursecontext,
377 $communication->delete_room();
383 * Update the room membership for the user updates.
385 * @param before_user_updated $hook The user updated hook.
387 public static function update_user_room_memberships(
388 before_user_updated $hook,
389 ): void {
390 // If the communication subsystem is not enabled then just ignore.
391 if (!api::is_available()) {
392 return;
395 $user = $hook->user;
396 $currentuserrecord = $hook->currentuserdata;
398 // Get the user courses.
399 $usercourses = enrol_get_users_courses(userid: $user->id);
401 // If the user is suspended then remove the user from all the rooms.
402 // Otherwise add the user to all the rooms for the courses the user enrolled in.
403 if (!empty($currentuserrecord) && isset($user->suspended) && $currentuserrecord->suspended !== $user->suspended) {
404 // Decide the action for the communication api for the user.
405 $memberaction = ($user->suspended === 0) ? 'add_members_to_room' : 'remove_members_from_room';
406 foreach ($usercourses as $usercourse) {
407 helper::update_course_communication_room_membership(
408 course: $usercourse,
409 userids: [$user->id],
410 memberaction: $memberaction,
417 * Delete all room memberships for a user.
419 * @param before_user_deleted $hook The user deleted hook.
421 public static function delete_user_room_memberships(
422 before_user_deleted $hook,
423 ): void {
424 // If the communication subsystem is not enabled then just ignore.
425 if (!api::is_available()) {
426 return;
429 $user = $hook->user;
431 foreach (enrol_get_users_courses(userid: $user->id) as $course) {
432 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
433 $coursecontext = \context_course::instance(courseid: $course->id);
435 if ((int)$groupmode === NOGROUPS) {
436 $communication = helper::load_by_course(
437 courseid: $course->id,
438 context: $coursecontext,
440 $communication->get_room_user_provider()->remove_members_from_room(userids: [$user->id]);
441 $communication->get_processor()->delete_instance_user_mapping(userids: [$user->id]);
442 } else {
443 // If group mode is set then handle the group communication rooms.
444 $coursegroups = groups_get_all_groups(courseid: $course->id);
445 foreach ($coursegroups as $coursegroup) {
446 $communication = helper::load_by_group(
447 groupid: $coursegroup->id,
448 context: $coursecontext,
450 $communication->get_room_user_provider()->remove_members_from_room(userids: [$user->id]);
451 $communication->get_processor()->delete_instance_user_mapping(userids: [$user->id]);
458 * Update the room membership of the user for role assigned in a course.
460 * @param after_role_assigned|after_role_unassigned $hook
462 public static function update_user_membership_for_role_changes(
463 after_role_assigned|after_role_unassigned $hook,
464 ): void {
465 // If the communication subsystem is not enabled then just ignore.
466 if (!api::is_available()) {
467 return;
470 $context = $hook->context;
471 if ($coursecontext = $context->get_course_context(strict: false)) {
472 helper::update_course_communication_room_membership(
473 course: get_course(courseid: $coursecontext->instanceid),
474 userids: [$hook->userid],
475 memberaction: 'update_room_membership',
481 * Update the communication memberships for enrol status change.
483 * @param after_enrol_instance_status_updated $hook The enrol status updated hook.
485 public static function update_communication_memberships_for_enrol_status_change(
486 after_enrol_instance_status_updated $hook,
487 ): void {
488 // If the communication subsystem is not enabled then just ignore.
489 if (!api::is_available()) {
490 return;
493 $enrolinstance = $hook->enrolinstance;
494 // No need to do anything for guest instances.
495 if ($enrolinstance->enrol === 'guest') {
496 return;
499 $newstatus = $hook->newstatus;
500 // Check if a valid status is given.
501 if (
502 $newstatus !== ENROL_INSTANCE_ENABLED ||
503 $newstatus !== ENROL_INSTANCE_DISABLED
505 return;
508 // Check if the status provided is valid.
509 switch ($newstatus) {
510 case ENROL_INSTANCE_ENABLED:
511 $action = 'add_members_to_room';
512 break;
513 case ENROL_INSTANCE_DISABLED:
514 $action = 'remove_members_from_room';
515 break;
516 default:
517 return;
520 global $DB;
521 $instanceusers = $DB->get_records(
522 table: 'user_enrolments',
523 conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
525 $enrolledusers = array_column($instanceusers, 'userid');
526 helper::update_course_communication_room_membership(
527 course: get_course(courseid: $enrolinstance->courseid),
528 userids: $enrolledusers,
529 memberaction: $action,
534 * Remove the communication instance memberships when an enrolment instance is deleted.
536 * @param before_enrol_instance_deleted $hook The enrol instance deleted hook.
538 public static function remove_communication_memberships_for_enrol_instance_deletion(
539 before_enrol_instance_deleted $hook,
540 ): void {
541 // If the communication subsystem is not enabled then just ignore.
542 if (!api::is_available()) {
543 return;
546 $enrolinstance = $hook->enrolinstance;
547 // No need to do anything for guest instances.
548 if ($enrolinstance->enrol === 'guest') {
549 return;
552 global $DB;
553 $instanceusers = $DB->get_records(
554 table: 'user_enrolments',
555 conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
557 $enrolledusers = array_column($instanceusers, 'userid');
558 helper::update_course_communication_room_membership(
559 course: get_course(courseid: $enrolinstance->courseid),
560 userids: $enrolledusers,
561 memberaction: 'remove_members_from_room',
566 * Add communication instance membership for an enrolled user.
568 * @param after_user_enrolled $hook The user enrolled hook.
570 public static function add_communication_membership_for_enrolled_user(
571 after_user_enrolled $hook,
572 ): void {
573 // If the communication subsystem is not enabled then just ignore.
574 if (!api::is_available()) {
575 return;
578 $enrolinstance = $hook->enrolinstance;
579 // No need to do anything for guest instances.
580 if ($enrolinstance->enrol === 'guest') {
581 return;
584 helper::update_course_communication_room_membership(
585 course: get_course($enrolinstance->courseid),
586 userids: [$hook->get_userid()],
587 memberaction: 'add_members_to_room',
592 * Update the communication instance membership for the user enrolment updates.
594 * @param before_user_enrolment_updated $hook The user enrolment updated hook.
596 public static function update_communication_membership_for_updated_user_enrolment(
597 before_user_enrolment_updated $hook,
598 ): void {
599 // If the communication subsystem is not enabled then just ignore.
600 if (!api::is_available()) {
601 return;
604 $enrolinstance = $hook->enrolinstance;
605 // No need to do anything for guest instances.
606 if ($enrolinstance->enrol === 'guest') {
607 return;
610 $userenrolmentinstance = $hook->userenrolmentinstance;
611 $statusmodified = $hook->statusmodified;
612 $timeendmodified = $hook->timeendmodified;
614 if (
615 ($statusmodified && ((int) $userenrolmentinstance->status === 1)) ||
616 ($timeendmodified && $userenrolmentinstance->timeend !== 0 && (time() > $userenrolmentinstance->timeend))
618 $action = 'remove_members_from_room';
619 } else {
620 $action = 'add_members_to_room';
623 helper::update_course_communication_room_membership(
624 course: get_course($enrolinstance->courseid),
625 userids: [$hook->get_userid()],
626 memberaction: $action,
631 * Remove communication instance membership for an enrolled user.
633 * @param before_user_enrolment_removed $hook The user unenrolled hook.
635 public static function remove_communication_membership_for_unenrolled_user(
636 before_user_enrolment_removed $hook,
637 ): void {
638 // If the communication subsystem is not enabled then just ignore.
639 if (!api::is_available()) {
640 return;
643 $enrolinstance = $hook->enrolinstance;
644 // No need to do anything for guest instances.
645 if ($enrolinstance->enrol === 'guest') {
646 return;
649 helper::update_course_communication_room_membership(
650 course: get_course($enrolinstance->courseid),
651 userids: [$hook->get_userid()],
652 memberaction: 'remove_members_from_room',