2 // This file is part of Moodle - http://moodle.org/
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.
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
;
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
;
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
48 * Get the course and group object for the group hook.
50 * @param mixed $hook The hook object.
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
,
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,
73 [$group, $course] = self
::get_group_and_course_data_for_group_hook(
77 // Check if group mode enabled before handling the communication.
78 if (!helper
::is_group_mode_enabled_for_course(course
: $course)) {
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) {
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,
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,
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,
132 [$group, $course] = self
::get_group_and_course_data_for_group_hook(
136 // Check if group mode enabled before handling the communication.
137 if (!helper
::is_group_mode_enabled_for_course(course
: $course)) {
141 $coursecontext = \context_course
::instance(courseid
: $course->id
);
142 $communication = helper
::load_by_group(
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()) {
163 $communication->update_room(
164 active
: processor
::PROVIDER_ACTIVE
,
165 communicationroomname
: $communicationroomname,
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
178 [$group, $course] = self
::get_group_and_course_data_for_group_hook(
182 // Check if group mode enabled before handling the communication.
183 if (!helper
::is_group_mode_enabled_for_course(course
: $course)) {
187 $context = context_course
::instance($course->id
);
188 $communication = helper
::load_by_group(
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,
203 [$group, $course] = self
::get_group_and_course_data_for_group_hook(
207 // Check if group mode enabled before handling the communication.
208 if (!helper
::is_group_mode_enabled_for_course(course
: $course)) {
212 $context = context_course
::instance($course->id
);
213 $communication = helper
::load_by_group(
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(
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,
235 [$group, $course] = self
::get_group_and_course_data_for_group_hook(
239 // Check if group mode enabled before handling the communication.
240 if (!helper
::is_group_mode_enabled_for_course(course
: $course)) {
244 $context = context_course
::instance($course->id
);
245 $communication = helper
::load_by_group(
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,
262 // If the communication subsystem is not enabled then just ignore.
263 if (!api
::is_available()) {
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
) {
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
,
302 $communication->create_and_configure_room(
303 communicationroomname
: $communicationroomname,
304 avatar
: $courseimage,
306 queue
: $createcourseroom,
309 // Communication api call for group communication.
310 if ($creategrouprooms) {
311 helper
::update_group_communication_instances_for_course(
316 $enrolledusers = helper
::get_enrolled_users_for_course(course
: $course);
317 $communication->add_members_to_room(
318 userids
: $enrolledusers,
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,
332 // If the communication subsystem is not enabled then just ignore.
333 if (!api
::is_available()) {
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(
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,
358 // If the communication subsystem is not enabled then just ignore.
359 if (!api
::is_available()) {
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();
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,
395 // If the communication subsystem is not enabled then just ignore.
396 if (!api
::is_available()) {
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(
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,
429 // If the communication subsystem is not enabled then just ignore.
430 if (!api
::is_available()) {
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
]);
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,
475 // If the communication subsystem is not enabled then just ignore.
476 if (!api
::is_available()) {
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,
498 // If the communication subsystem is not enabled then just ignore.
499 if (!api
::is_available()) {
503 $enrolinstance = $hook->enrolinstance
;
504 // No need to do anything for guest instances.
505 if ($enrolinstance->enrol
=== 'guest') {
509 $newstatus = $hook->newstatus
;
510 // Check if a valid status is given.
512 $newstatus !== ENROL_INSTANCE_ENABLED ||
513 $newstatus !== ENROL_INSTANCE_DISABLED
518 // Check if the status provided is valid.
519 switch ($newstatus) {
520 case ENROL_INSTANCE_ENABLED
:
521 $action = 'add_members_to_room';
523 case ENROL_INSTANCE_DISABLED
:
524 $action = 'remove_members_from_room';
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,
551 // If the communication subsystem is not enabled then just ignore.
552 if (!api
::is_available()) {
556 $enrolinstance = $hook->enrolinstance
;
557 // No need to do anything for guest instances.
558 if ($enrolinstance->enrol
=== 'guest') {
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,
583 // If the communication subsystem is not enabled then just ignore.
584 if (!api
::is_available()) {
588 $enrolinstance = $hook->enrolinstance
;
589 // No need to do anything for guest instances.
590 if ($enrolinstance->enrol
=== 'guest') {
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,
609 // If the communication subsystem is not enabled then just ignore.
610 if (!api
::is_available()) {
614 $enrolinstance = $hook->enrolinstance
;
615 // No need to do anything for guest instances.
616 if ($enrolinstance->enrol
=== 'guest') {
620 $userenrolmentinstance = $hook->userenrolmentinstance
;
621 $statusmodified = $hook->statusmodified
;
622 $timeendmodified = $hook->timeendmodified
;
625 ($statusmodified && ((int) $userenrolmentinstance->status
=== 1)) ||
626 ($timeendmodified && $userenrolmentinstance->timeend
!== 0 && (time() > $userenrolmentinstance->timeend
))
628 $action = 'remove_members_from_room';
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,
648 // If the communication subsystem is not enabled then just ignore.
649 if (!api
::is_available()) {
653 $enrolinstance = $hook->enrolinstance
;
654 // No need to do anything for guest instances.
655 if ($enrolinstance->enrol
=== 'guest') {
659 helper
::update_course_communication_room_membership(
660 course
: get_course($enrolinstance->courseid
),
661 userids
: [$hook->get_userid()],
662 memberaction
: 'remove_members_from_room',