weekly release 4.5dev
[moodle.git] / communication / classes / helper.php
blob1d7bf47e4fe04ad0ab7e93eca831e8853bd89abf
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;
20 use stdClass;
22 /**
23 * Helper method for communication.
25 * @package core_communication
26 * @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 class helper {
31 /**
32 * Load the communication instance for group id.
34 * @param int $groupid The group id
35 * @param context $context The context, to make sure any instance using group can load the communication instance
36 * @return api The communication instance.
38 public static function load_by_group(int $groupid, context $context): api {
39 return \core_communication\api::load_by_instance(
40 context: $context,
41 component: constants::GROUP_COMMUNICATION_COMPONENT,
42 instancetype: constants::GROUP_COMMUNICATION_INSTANCETYPE,
43 instanceid: $groupid,
47 /**
48 * Load the communication instance for course id.
50 * @param int $courseid The course id
51 * @param \context $context The context
52 * @param string|null $provider The provider name
53 * @return api The communication instance
55 public static function load_by_course(
56 int $courseid,
57 \context $context,
58 ?string $provider = null,
59 ): api {
60 return \core_communication\api::load_by_instance(
61 context: $context,
62 component: constants::COURSE_COMMUNICATION_COMPONENT,
63 instancetype: constants::COURSE_COMMUNICATION_INSTANCETYPE,
64 instanceid: $courseid,
65 provider: $provider,
69 /**
70 * Communication api call to create room for a group if course has group mode enabled.
72 * @param int $courseid The course id.
73 * @return stdClass
75 public static function get_course(int $courseid): stdClass {
76 global $DB;
77 return $DB->get_record(
78 table: 'course',
79 conditions: ['id' => $courseid],
80 strictness: MUST_EXIST,
84 /**
85 * Is group mode enabled for the course.
87 * @param stdClass $course The course object
89 public static function is_group_mode_enabled_for_course(stdClass $course): bool {
90 // If the communication subsystem is not enabled then just ignore.
91 if (!api::is_available()) {
92 return false;
95 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
96 return (int)$groupmode !== NOGROUPS;
99 /**
100 * Helper to update room membership according to action passed.
101 * This method will help reduce a large amount of duplications of code in different places in core.
103 * @param \stdClass $course The course object.
104 * @param array $userids The user ids to add to the communication room.
105 * @param string $memberaction The action to perform on the communication room.
107 public static function update_course_communication_room_membership(
108 \stdClass $course,
109 array $userids,
110 string $memberaction,
111 ): void {
112 // If the communication subsystem is not enabled then just ignore.
113 if (!api::is_available()) {
114 return;
117 // Validate communication api action.
118 $roomuserprovider = new \ReflectionClass(room_user_provider::class);
119 if (!$roomuserprovider->hasMethod($memberaction)) {
120 throw new \coding_exception('Invalid action provided.');
123 $coursecontext = \context_course::instance(courseid: $course->id);
125 $communication = self::load_by_course(
126 courseid: $course->id,
127 context: $coursecontext,
130 // Check we have communication correctly set up before proceeding.
131 if ($communication->get_processor() === null) {
132 return;
135 // Get the group mode for this course.
136 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
138 if ((int)$groupmode === NOGROUPS) {
139 // If group mode is not set, then just handle the update normally for these users.
140 $communication->$memberaction($userids);
141 } else {
142 // If group mode is set, then handle the update for these users with repect to the group they are in.
143 $coursegroups = groups_get_all_groups(courseid: $course->id);
145 $usershandled = [];
147 // Filter all the users that have the capability to access all groups.
148 $allaccessgroupusers = self::get_users_has_access_to_all_groups(
149 userids: $userids,
150 courseid: $course->id,
153 foreach ($coursegroups as $coursegroup) {
155 // Get all group members.
156 $groupmembers = groups_get_members(groupid: $coursegroup->id, fields: 'u.id');
157 $groupmembers = array_column($groupmembers, 'id');
159 // Find the common user ids between the group members and incoming userids.
160 $groupuserstohandle = array_intersect(
161 $groupmembers,
162 $userids,
165 // Add users who have the capability to access this group (and haven't been added already).
166 foreach ($allaccessgroupusers as $allaccessgroupuser) {
167 if (!in_array($allaccessgroupuser, $groupuserstohandle, true)) {
168 $groupuserstohandle[] = $allaccessgroupuser;
172 // Keep track of the users we have handled already.
173 $usershandled = array_merge($usershandled, $groupuserstohandle);
175 // Let's check if we need to add/remove members from room because of a role change.
176 // First, get all the instance users for this group.
177 $communication = self::load_by_group(
178 groupid: $coursegroup->id,
179 context: $coursecontext,
181 $instanceusers = $communication->get_processor()->get_all_userids_for_instance();
183 // The difference between the instance users and the group members are the ones we want to check.
184 $roomuserstocheck = array_diff(
185 $instanceusers,
186 $groupmembers
189 if (!empty($roomuserstocheck)) {
190 // Check if they still have the capability to keep their access in the room.
191 $userslostcaps = array_diff(
192 $roomuserstocheck,
193 self::get_users_has_access_to_all_groups(
194 userids: $roomuserstocheck,
195 courseid: $course->id,
198 // Remove users who no longer have the capability.
199 if (!empty($userslostcaps)) {
200 $communication->remove_members_from_room(userids: $userslostcaps);
204 // Check if we have to add any room members who have gained the capability.
205 $usersgainedcaps = array_diff(
206 $allaccessgroupusers,
207 $instanceusers,
210 // If we have users, add them to the room.
211 if (!empty($usersgainedcaps)) {
212 $communication->add_members_to_room(userids: $usersgainedcaps);
215 // Finally, trigger the update task for the users who need to be handled.
216 $communication->$memberaction($groupuserstohandle);
219 // If the user was not in any group, but an update/remove action was requested for the user,
220 // then the user must have had a role with the capablity, but made a regular user.
221 $usersnothandled = array_diff($userids, $usershandled);
223 // These users are not handled and not in any group, so logically these users lost their permission to stay in the room.
224 foreach ($coursegroups as $coursegroup) {
225 $communication = self::load_by_group(
226 groupid: $coursegroup->id,
227 context: $coursecontext,
229 $communication->remove_members_from_room(userids: $usersnothandled);
235 * Get users with the capability to access all groups.
237 * @param array $userids user ids to check the permission
238 * @param int $courseid course id
239 * @return array of userids
241 public static function get_users_has_access_to_all_groups(
242 array $userids,
243 int $courseid
244 ): array {
245 $allgroupsusers = [];
246 $context = \context_course::instance(courseid: $courseid);
248 foreach ($userids as $userid) {
249 if (
250 has_capability(
251 capability: 'moodle/site:accessallgroups',
252 context: $context,
253 user: $userid,
256 $allgroupsusers[] = $userid;
260 return $allgroupsusers;
264 * Get the course communication url according to course setup.
266 * @param stdClass $course The course object.
267 * @return string The communication room url.
269 public static function get_course_communication_url(stdClass $course): string {
270 // If it's called from site context, then just return.
271 if ($course->id === SITEID) {
272 return '';
275 // If the communication subsystem is not enabled then just ignore.
276 if (!api::is_available()) {
277 return '';
280 $url = '';
281 // Get the group mode for this course.
282 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
283 $coursecontext = \context_course::instance(courseid: $course->id);
285 // If group mode is not set then just handle the course communication for these users.
286 if ((int)$groupmode === NOGROUPS) {
287 $communication = self::load_by_course(
288 courseid: $course->id,
289 context: $coursecontext,
291 $url = $communication->get_communication_room_url();
292 } else {
293 // If group mode is set then handle the group communication rooms for these users.
294 $coursegroups = groups_get_all_groups(courseid: $course->id);
295 $numberofgroups = count($coursegroups);
297 // If no groups available, nothing to show.
298 if ($numberofgroups === 0) {
299 return '';
302 $readygroups = [];
304 foreach ($coursegroups as $coursegroup) {
305 $communication = self::load_by_group(
306 groupid: $coursegroup->id,
307 context: $coursecontext,
309 $roomstatus = $communication->get_communication_room_url() ? 'ready' : 'pending';
310 if ($roomstatus === 'ready') {
311 $readygroups[$communication->get_processor()->get_id()] = $communication->get_communication_room_url();
314 if (!empty($readygroups)) {
315 $highestkey = max(array_keys($readygroups));
316 $url = $readygroups[$highestkey];
320 return empty($url) ? '' : $url;
324 * Get the enrolled users for course.
326 * @param stdClass $course The course object.
327 * @return array
329 public static function get_enrolled_users_for_course(stdClass $course): array {
330 global $CFG;
331 require_once($CFG->libdir . '/enrollib.php');
332 return array_column(
333 enrol_get_course_users(courseid: $course->id),
334 'id',
339 * Get the course communication status notification for course.
341 * @param \stdClass $course The course object.
343 public static function get_course_communication_status_notification(\stdClass $course): void {
344 // If the communication subsystem is not enabled then just ignore.
345 if (!api::is_available()) {
346 return;
349 // Get the group mode for this course.
350 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
351 $coursecontext = \context_course::instance(courseid: $course->id);
353 // If group mode is not set then just handle the course communication for these users.
354 if ((int)$groupmode === NOGROUPS) {
355 $communication = self::load_by_course(
356 courseid: $course->id,
357 context: $coursecontext,
359 $communication->show_communication_room_status_notification();
360 } else {
361 // If group mode is set then handle the group communication rooms for these users.
362 $coursegroups = groups_get_all_groups(courseid: $course->id);
363 $numberofgroups = count($coursegroups);
365 // If no groups available, nothing to show.
366 if ($numberofgroups === 0) {
367 return;
370 $numberofreadygroups = 0;
372 foreach ($coursegroups as $coursegroup) {
373 $communication = self::load_by_group(
374 groupid: $coursegroup->id,
375 context: $coursecontext,
377 $roomstatus = $communication->get_communication_room_url() ? 'ready' : 'pending';
378 switch ($roomstatus) {
379 case 'ready':
380 $numberofreadygroups ++;
381 break;
382 case 'pending':
383 $pendincommunicationobject = $communication;
384 break;
388 if ($numberofgroups === $numberofreadygroups) {
389 $communication->show_communication_room_status_notification();
390 } else {
391 $pendincommunicationobject->show_communication_room_status_notification();
397 * Update course communication according to course data.
398 * Course can have course or group rooms. Group mode enabling will create rooms for groups.
400 * @param stdClass $course The course data
401 * @param bool $changesincoursecat Whether the course moved to a different category
403 public static function update_course_communication_instance(
404 stdClass $course,
405 bool $changesincoursecat
406 ): void {
407 // If the communication subsystem is not enabled then just ignore.
408 if (!api::is_available()) {
409 return;
412 // Check if provider is selected.
413 $provider = $course->selectedcommunication ?? null;
414 // If the course moved to hidden category, set provider to none.
415 if ($changesincoursecat && empty($course->visible)) {
416 $provider = processor::PROVIDER_NONE;
419 // Get the course context.
420 $coursecontext = \context_course::instance(courseid: $course->id);
421 // Get the course image.
422 $courseimage = course_get_courseimage(course: $course);
423 // Get the course communication instance.
424 $coursecommunication = self::load_by_course(
425 courseid: $course->id,
426 context: $coursecontext,
429 // Attempt to get the communication provider if it wasn't provided in the data.
430 if (empty($provider)) {
431 $provider = $coursecommunication->get_provider();
433 $roomnameidenfier = $provider . 'roomname';
435 // Determine the communication room name if none was provided and add it to the course data.
436 if (empty($course->$roomnameidenfier)) {
437 $course->$roomnameidenfier = $coursecommunication->get_room_name();
438 if (empty($course->$roomnameidenfier)) {
439 $course->$roomnameidenfier = $course->fullname ?? get_course($course->id)->fullname;
443 // List of enrolled users for course communication.
444 $enrolledusers = self::get_enrolled_users_for_course(course: $course);
446 // Check for group mode, we will have to get the course data again as the group info is not always in the object.
447 $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
449 // If group mode is disabled, get the communication information for creating room for a course.
450 if ((int)$groupmode === NOGROUPS) {
451 // Remove all the members from active group rooms if there is any.
452 $coursegroups = groups_get_all_groups(courseid: $course->id);
453 foreach ($coursegroups as $coursegroup) {
454 $communication = self::load_by_group(
455 groupid: $coursegroup->id,
456 context: $coursecontext,
458 // Remove the members from the group room.
459 $communication->remove_all_members_from_room();
460 // Now delete the group room.
461 $communication->update_room(active: processor::PROVIDER_INACTIVE);
464 // Now create/update the course room.
465 $communication = self::load_by_course(
466 courseid: $course->id,
467 context: $coursecontext,
469 $communication->configure_room_and_membership_by_provider(
470 provider: $provider,
471 instance: $course,
472 communicationroomname: $course->$roomnameidenfier,
473 users: $enrolledusers,
474 instanceimage: $courseimage,
476 } else {
477 // Update the group communication instances.
478 self::update_group_communication_instances_for_course(
479 course: $course,
480 provider: $provider,
483 // Remove all the members for the course room if instance available.
484 $communication = self::load_by_course(
485 courseid: $course->id,
486 context: $coursecontext,
489 // Now handle the course communication according to the provider.
490 $communication->configure_room_and_membership_by_provider(
491 provider: $provider,
492 instance: $course,
493 communicationroomname: $course->$roomnameidenfier,
494 users: $enrolledusers,
495 instanceimage: $courseimage,
496 queue: false,
499 // As the course is in group mode, make sure no users are in the course room.
500 $communication->reload();
501 $communication->remove_all_members_from_room();
506 * Update the group communication instances.
508 * @param stdClass $course The course object.
509 * @param string $provider The provider name.
511 public static function update_group_communication_instances_for_course(
512 stdClass $course,
513 string $provider,
514 ): void {
515 $coursegroups = groups_get_all_groups(courseid: $course->id);
516 $coursecontext = \context_course::instance(courseid: $course->id);
517 $allaccessgroupusers = self::get_users_has_access_to_all_groups(
518 userids: self::get_enrolled_users_for_course(course: $course),
519 courseid: $course->id,
522 foreach ($coursegroups as $coursegroup) {
523 $groupuserstoadd = array_column(
524 groups_get_members(groupid: $coursegroup->id),
525 'id',
528 foreach ($allaccessgroupusers as $allaccessgroupuser) {
529 if (!in_array($allaccessgroupuser, $groupuserstoadd, true)) {
530 $groupuserstoadd[] = $allaccessgroupuser;
534 // Now create/update the group room.
535 $communication = self::load_by_group(
536 groupid: $coursegroup->id,
537 context: $coursecontext,
540 $roomnameidenfier = $provider . 'roomname';
541 $communicationroomname = self::format_group_room_name(
542 baseroomname: $course->$roomnameidenfier,
543 groupname: $coursegroup->name,
546 $communication->configure_room_and_membership_by_provider(
547 provider: $provider,
548 instance: $course,
549 communicationroomname: $communicationroomname,
550 users: $groupuserstoadd,
556 * Format a group communication room name with the following syntax: 'Group A (Course 1)'.
558 * @param string $baseroomname The base room name.
559 * @param string $groupname The group name.
561 public static function format_group_room_name(
562 string $baseroomname,
563 string $groupname
564 ): string {
565 return get_string('communicationgrouproomnameformat', 'core_communication', [
566 'groupname' => $groupname,
567 'baseroomname' => $baseroomname,