Proyectos de Subversion Moodle

Rev

Rev 1 | | 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;
20
use stdClass;
21
 
22
/**
23
 * Helper method for communication.
24
 *
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
28
 */
29
class helper {
30
 
31
    /**
32
     * Load the communication instance for group id.
33
     *
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.
37
     */
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,
44
        );
45
    }
46
 
47
    /**
48
     * Load the communication instance for course id.
49
     *
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
54
     */
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,
66
        );
67
    }
68
 
69
    /**
70
     * Communication api call to create room for a group if course has group mode enabled.
71
     *
72
     * @param int $courseid The course id.
73
     * @return stdClass
74
     */
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,
81
        );
82
    }
83
 
84
    /**
85
     * Is group mode enabled for the course.
86
     *
87
     * @param stdClass $course The course object
88
     */
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;
93
        }
94
 
95
        $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
96
        return (int)$groupmode !== NOGROUPS;
97
    }
98
 
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.
102
     *
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.
106
     */
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;
115
        }
116
 
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.');
121
        }
122
 
123
        $coursecontext = \context_course::instance(courseid: $course->id);
124
 
125
        $communication = self::load_by_course(
126
            courseid: $course->id,
127
            context: $coursecontext,
128
        );
129
 
130
        // Check we have communication correctly set up before proceeding.
131
        if ($communication->get_processor() === null) {
132
            return;
133
        }
134
 
135
        // Get the group mode for this course.
136
        $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
137
 
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);
144
 
145
            $usershandled = [];
146
 
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,
151
            );
152
 
153
            foreach ($coursegroups as $coursegroup) {
154
 
155
                // Get all group members.
156
                $groupmembers = groups_get_members(groupid: $coursegroup->id, fields: 'u.id');
157
                $groupmembers = array_column($groupmembers, 'id');
158
 
159
                // Find the common user ids between the group members and incoming userids.
160
                $groupuserstohandle = array_intersect(
161
                    $groupmembers,
162
                    $userids,
163
                );
164
 
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;
169
                    }
170
                }
171
 
172
                // Keep track of the users we have handled already.
173
                $usershandled = array_merge($usershandled, $groupuserstohandle);
174
 
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,
180
                );
181
                $instanceusers = $communication->get_processor()->get_all_userids_for_instance();
182
 
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
187
                );
188
 
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,
196
                        ),
197
                    );
198
                    // Remove users who no longer have the capability.
199
                    if (!empty($userslostcaps)) {
200
                        $communication->remove_members_from_room(userids: $userslostcaps);
201
                    }
202
                }
203
 
204
                // Check if we have to add any room members who have gained the capability.
205
                $usersgainedcaps = array_diff(
206
                    $allaccessgroupusers,
207
                    $instanceusers,
208
                );
209
 
210
                // If we have users, add them to the room.
211
                if (!empty($usersgainedcaps)) {
212
                    $communication->add_members_to_room(userids: $usersgainedcaps);
213
                }
214
 
215
                // Finally, trigger the update task for the users who need to be handled.
216
                $communication->$memberaction($groupuserstohandle);
217
            }
218
 
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);
222
 
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,
228
                );
229
                $communication->remove_members_from_room(userids: $usersnothandled);
230
            }
231
        }
232
    }
233
 
234
    /**
235
     * Get users with the capability to access all groups.
236
     *
237
     * @param array $userids user ids to check the permission
238
     * @param int $courseid course id
239
     * @return array of userids
240
     */
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);
247
 
248
        foreach ($userids as $userid) {
249
            if (
250
                has_capability(
251
                    capability: 'moodle/site:accessallgroups',
252
                    context: $context,
253
                    user: $userid,
254
                )
255
            ) {
256
                $allgroupsusers[] = $userid;
257
            }
258
        }
259
 
260
        return $allgroupsusers;
261
    }
262
 
263
    /**
264
     * Get the course communication url according to course setup.
265
     *
266
     * @param stdClass $course The course object.
267
     * @return string The communication room url.
268
     */
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 '';
273
        }
274
 
275
        // If the communication subsystem is not enabled then just ignore.
276
        if (!api::is_available()) {
277
            return '';
278
        }
279
 
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);
284
 
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,
290
            );
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);
296
 
297
            // If no groups available, nothing to show.
298
            if ($numberofgroups === 0) {
299
                return '';
300
            }
301
 
302
            $readygroups = [];
303
 
304
            foreach ($coursegroups as $coursegroup) {
305
                $communication = self::load_by_group(
306
                    groupid: $coursegroup->id,
307
                    context: $coursecontext,
308
                );
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();
312
                }
313
            }
314
            if (!empty($readygroups)) {
315
                $highestkey = max(array_keys($readygroups));
316
                $url = $readygroups[$highestkey];
317
            }
318
        }
319
 
320
        return empty($url) ? '' : $url;
321
    }
322
 
323
    /**
324
     * Get the enrolled users for course.
325
     *
326
     * @param stdClass $course The course object.
11 efrain 327
     * @param bool $onlyactive Only enrolments that are active (e.g. not suspended).
1 efrain 328
     * @return array
329
     */
11 efrain 330
    public static function get_enrolled_users_for_course(stdClass $course, bool $onlyactive = true): array {
1 efrain 331
        global $CFG;
332
        require_once($CFG->libdir . '/enrollib.php');
333
        return array_column(
11 efrain 334
            enrol_get_course_users(courseid: $course->id, onlyactive: $onlyactive),
1 efrain 335
            'id',
336
        );
337
    }
338
 
339
    /**
340
     * Get the course communication status notification for course.
341
     *
342
     * @param \stdClass $course The course object.
343
     */
344
    public static function get_course_communication_status_notification(\stdClass $course): void {
345
        // If the communication subsystem is not enabled then just ignore.
346
        if (!api::is_available()) {
347
            return;
348
        }
349
 
350
        // Get the group mode for this course.
351
        $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
352
        $coursecontext = \context_course::instance(courseid: $course->id);
353
 
354
        // If group mode is not set then just handle the course communication for these users.
355
        if ((int)$groupmode === NOGROUPS) {
356
            $communication = self::load_by_course(
357
                courseid: $course->id,
358
                context: $coursecontext,
359
            );
360
            $communication->show_communication_room_status_notification();
361
        } else {
362
            // If group mode is set then handle the group communication rooms for these users.
363
            $coursegroups = groups_get_all_groups(courseid: $course->id);
364
            $numberofgroups = count($coursegroups);
365
 
366
            // If no groups available, nothing to show.
367
            if ($numberofgroups === 0) {
368
                return;
369
            }
370
 
371
            $numberofreadygroups = 0;
372
 
373
            foreach ($coursegroups as $coursegroup) {
374
                $communication = self::load_by_group(
375
                    groupid: $coursegroup->id,
376
                    context: $coursecontext,
377
                );
378
                $roomstatus = $communication->get_communication_room_url() ? 'ready' : 'pending';
379
                switch ($roomstatus) {
380
                    case 'ready':
381
                        $numberofreadygroups ++;
382
                        break;
383
                    case 'pending':
384
                        $pendincommunicationobject = $communication;
385
                        break;
386
                }
387
            }
388
 
389
            if ($numberofgroups === $numberofreadygroups) {
390
                $communication->show_communication_room_status_notification();
391
            } else {
392
                $pendincommunicationobject->show_communication_room_status_notification();
393
            }
394
        }
395
    }
396
 
397
    /**
398
     * Update course communication according to course data.
399
     * Course can have course or group rooms. Group mode enabling will create rooms for groups.
400
     *
401
     * @param stdClass $course The course data
402
     * @param bool $changesincoursecat Whether the course moved to a different category
403
     */
404
    public static function update_course_communication_instance(
405
        stdClass $course,
406
        bool $changesincoursecat
407
    ): void {
408
        // If the communication subsystem is not enabled then just ignore.
409
        if (!api::is_available()) {
410
            return;
411
        }
412
 
413
        // Check if provider is selected.
414
        $provider = $course->selectedcommunication ?? null;
415
        // If the course moved to hidden category, set provider to none.
416
        if ($changesincoursecat && empty($course->visible)) {
417
            $provider = processor::PROVIDER_NONE;
418
        }
419
 
420
        // Get the course context.
421
        $coursecontext = \context_course::instance(courseid: $course->id);
422
        // Get the course image.
423
        $courseimage = course_get_courseimage(course: $course);
424
        // Get the course communication instance.
425
        $coursecommunication = self::load_by_course(
426
            courseid: $course->id,
427
            context: $coursecontext,
428
        );
429
 
430
        // Attempt to get the communication provider if it wasn't provided in the data.
431
        if (empty($provider)) {
432
            $provider = $coursecommunication->get_provider();
433
        }
11 efrain 434
        $roomnameidenfier = $provider . 'roomname';
1 efrain 435
 
436
        // Determine the communication room name if none was provided and add it to the course data.
11 efrain 437
        if (empty($course->$roomnameidenfier)) {
438
            $course->$roomnameidenfier = $coursecommunication->get_room_name();
439
            if (empty($course->$roomnameidenfier)) {
440
                $course->$roomnameidenfier = $course->fullname ?? get_course($course->id)->fullname;
441
            }
1 efrain 442
        }
443
 
444
        // List of enrolled users for course communication.
445
        $enrolledusers = self::get_enrolled_users_for_course(course: $course);
446
 
447
        // Check for group mode, we will have to get the course data again as the group info is not always in the object.
448
        $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
449
 
450
        // If group mode is disabled, get the communication information for creating room for a course.
451
        if ((int)$groupmode === NOGROUPS) {
452
            // Remove all the members from active group rooms if there is any.
453
            $coursegroups = groups_get_all_groups(courseid: $course->id);
454
            foreach ($coursegroups as $coursegroup) {
455
                $communication = self::load_by_group(
456
                    groupid: $coursegroup->id,
457
                    context: $coursecontext,
458
                );
459
                // Remove the members from the group room.
460
                $communication->remove_all_members_from_room();
461
                // Now delete the group room.
462
                $communication->update_room(active: processor::PROVIDER_INACTIVE);
463
            }
464
 
465
            // Now create/update the course room.
466
            $communication = self::load_by_course(
467
                courseid: $course->id,
468
                context: $coursecontext,
469
            );
470
            $communication->configure_room_and_membership_by_provider(
471
                provider: $provider,
472
                instance: $course,
11 efrain 473
                communicationroomname: $course->$roomnameidenfier,
1 efrain 474
                users: $enrolledusers,
475
                instanceimage: $courseimage,
476
            );
477
        } else {
478
            // Update the group communication instances.
479
            self::update_group_communication_instances_for_course(
480
                course: $course,
481
                provider: $provider,
482
            );
483
 
484
            // Remove all the members for the course room if instance available.
485
            $communication = self::load_by_course(
486
                courseid: $course->id,
487
                context: $coursecontext,
488
            );
489
 
490
            // Now handle the course communication according to the provider.
491
            $communication->configure_room_and_membership_by_provider(
492
                provider: $provider,
493
                instance: $course,
11 efrain 494
                communicationroomname: $course->$roomnameidenfier,
1 efrain 495
                users: $enrolledusers,
496
                instanceimage: $courseimage,
497
                queue: false,
498
            );
499
 
500
            // As the course is in group mode, make sure no users are in the course room.
501
            $communication->reload();
502
            $communication->remove_all_members_from_room();
503
        }
504
    }
505
 
506
    /**
507
     * Update the group communication instances.
508
     *
509
     * @param stdClass $course The course object.
510
     * @param string $provider The provider name.
511
     */
512
    public static function update_group_communication_instances_for_course(
513
        stdClass $course,
514
        string $provider,
515
    ): void {
516
        $coursegroups = groups_get_all_groups(courseid: $course->id);
517
        $coursecontext = \context_course::instance(courseid: $course->id);
518
        $allaccessgroupusers = self::get_users_has_access_to_all_groups(
519
            userids: self::get_enrolled_users_for_course(course: $course),
520
            courseid: $course->id,
521
        );
522
 
523
        foreach ($coursegroups as $coursegroup) {
11 efrain 524
            $groupusers = array_column(
1 efrain 525
                groups_get_members(groupid: $coursegroup->id),
526
                'id',
527
            );
528
 
11 efrain 529
            // Filter out users who are not active in this course.
530
            $enrolledusers = self::get_enrolled_users_for_course(course: $course);
531
            $groupuserstoadd = array_intersect($groupusers, $enrolledusers);
532
 
1 efrain 533
            foreach ($allaccessgroupusers as $allaccessgroupuser) {
534
                if (!in_array($allaccessgroupuser, $groupuserstoadd, true)) {
535
                    $groupuserstoadd[] = $allaccessgroupuser;
536
                }
537
            }
538
 
539
            // Now create/update the group room.
540
            $communication = self::load_by_group(
541
                groupid: $coursegroup->id,
542
                context: $coursecontext,
543
            );
544
 
11 efrain 545
            $roomnameidenfier = $provider . 'roomname';
1 efrain 546
            $communicationroomname = self::format_group_room_name(
11 efrain 547
                baseroomname: $course->$roomnameidenfier,
1 efrain 548
                groupname: $coursegroup->name,
549
            );
550
 
551
            $communication->configure_room_and_membership_by_provider(
552
                provider: $provider,
553
                instance: $course,
554
                communicationroomname: $communicationroomname,
555
                users: $groupuserstoadd,
556
            );
557
        }
558
    }
559
 
560
    /**
561
     * Format a group communication room name with the following syntax: 'Group A (Course 1)'.
562
     *
563
     * @param string $baseroomname The base room name.
564
     * @param string $groupname The group name.
565
     */
566
    public static function format_group_room_name(
567
        string $baseroomname,
568
        string $groupname
569
    ): string {
570
        return get_string('communicationgrouproomnameformat', 'core_communication', [
571
            'groupname' => $groupname,
572
            'baseroomname' => $baseroomname,
573
        ]);
574
    }
575
}