Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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.
327
     * @return array
328
     */
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',
335
        );
336
    }
337
 
338
    /**
339
     * Get the course communication status notification for course.
340
     *
341
     * @param \stdClass $course The course object.
342
     */
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;
347
        }
348
 
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);
352
 
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,
358
            );
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);
364
 
365
            // If no groups available, nothing to show.
366
            if ($numberofgroups === 0) {
367
                return;
368
            }
369
 
370
            $numberofreadygroups = 0;
371
 
372
            foreach ($coursegroups as $coursegroup) {
373
                $communication = self::load_by_group(
374
                    groupid: $coursegroup->id,
375
                    context: $coursecontext,
376
                );
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;
385
                }
386
            }
387
 
388
            if ($numberofgroups === $numberofreadygroups) {
389
                $communication->show_communication_room_status_notification();
390
            } else {
391
                $pendincommunicationobject->show_communication_room_status_notification();
392
            }
393
        }
394
    }
395
 
396
    /**
397
     * Update course communication according to course data.
398
     * Course can have course or group rooms. Group mode enabling will create rooms for groups.
399
     *
400
     * @param stdClass $course The course data
401
     * @param bool $changesincoursecat Whether the course moved to a different category
402
     */
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;
410
        }
411
 
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;
417
        }
418
 
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,
427
        );
428
 
429
        // Attempt to get the communication provider if it wasn't provided in the data.
430
        if (empty($provider)) {
431
            $provider = $coursecommunication->get_provider();
432
        }
433
 
434
        // Determine the communication room name if none was provided and add it to the course data.
435
        if (empty($course->communicationroomname)) {
436
            $course->communicationroomname = $course->fullname ?? get_course($course->id)->fullname;
437
        }
438
 
439
        // List of enrolled users for course communication.
440
        $enrolledusers = self::get_enrolled_users_for_course(course: $course);
441
 
442
        // Check for group mode, we will have to get the course data again as the group info is not always in the object.
443
        $groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
444
 
445
        // If group mode is disabled, get the communication information for creating room for a course.
446
        if ((int)$groupmode === NOGROUPS) {
447
            // Remove all the members from active group rooms if there is any.
448
            $coursegroups = groups_get_all_groups(courseid: $course->id);
449
            foreach ($coursegroups as $coursegroup) {
450
                $communication = self::load_by_group(
451
                    groupid: $coursegroup->id,
452
                    context: $coursecontext,
453
                );
454
                // Remove the members from the group room.
455
                $communication->remove_all_members_from_room();
456
                // Now delete the group room.
457
                $communication->update_room(active: processor::PROVIDER_INACTIVE);
458
            }
459
 
460
            // Now create/update the course room.
461
            $communication = self::load_by_course(
462
                courseid: $course->id,
463
                context: $coursecontext,
464
            );
465
            $communication->configure_room_and_membership_by_provider(
466
                provider: $provider,
467
                instance: $course,
468
                communicationroomname: $course->communicationroomname,
469
                users: $enrolledusers,
470
                instanceimage: $courseimage,
471
            );
472
        } else {
473
            // Update the group communication instances.
474
            self::update_group_communication_instances_for_course(
475
                course: $course,
476
                provider: $provider,
477
            );
478
 
479
            // Remove all the members for the course room if instance available.
480
            $communication = self::load_by_course(
481
                courseid: $course->id,
482
                context: $coursecontext,
483
            );
484
 
485
            // Now handle the course communication according to the provider.
486
            $communication->configure_room_and_membership_by_provider(
487
                provider: $provider,
488
                instance: $course,
489
                communicationroomname: $course->communicationroomname,
490
                users: $enrolledusers,
491
                instanceimage: $courseimage,
492
                queue: false,
493
            );
494
 
495
            // As the course is in group mode, make sure no users are in the course room.
496
            $communication->reload();
497
            $communication->remove_all_members_from_room();
498
        }
499
    }
500
 
501
    /**
502
     * Update the group communication instances.
503
     *
504
     * @param stdClass $course The course object.
505
     * @param string $provider The provider name.
506
     */
507
    public static function update_group_communication_instances_for_course(
508
        stdClass $course,
509
        string $provider,
510
    ): void {
511
        $coursegroups = groups_get_all_groups(courseid: $course->id);
512
        $coursecontext = \context_course::instance(courseid: $course->id);
513
        $allaccessgroupusers = self::get_users_has_access_to_all_groups(
514
            userids: self::get_enrolled_users_for_course(course: $course),
515
            courseid: $course->id,
516
        );
517
 
518
        foreach ($coursegroups as $coursegroup) {
519
            $groupuserstoadd = array_column(
520
                groups_get_members(groupid: $coursegroup->id),
521
                'id',
522
            );
523
 
524
            foreach ($allaccessgroupusers as $allaccessgroupuser) {
525
                if (!in_array($allaccessgroupuser, $groupuserstoadd, true)) {
526
                    $groupuserstoadd[] = $allaccessgroupuser;
527
                }
528
            }
529
 
530
            // Now create/update the group room.
531
            $communication = self::load_by_group(
532
                groupid: $coursegroup->id,
533
                context: $coursecontext,
534
            );
535
 
536
            $communicationroomname = self::format_group_room_name(
537
                baseroomname: $course->communicationroomname,
538
                groupname: $coursegroup->name,
539
            );
540
 
541
            $communication->configure_room_and_membership_by_provider(
542
                provider: $provider,
543
                instance: $course,
544
                communicationroomname: $communicationroomname,
545
                users: $groupuserstoadd,
546
            );
547
        }
548
    }
549
 
550
    /**
551
     * Format a group communication room name with the following syntax: 'Group A (Course 1)'.
552
     *
553
     * @param string $baseroomname The base room name.
554
     * @param string $groupname The group name.
555
     */
556
    public static function format_group_room_name(
557
        string $baseroomname,
558
        string $groupname
559
    ): string {
560
        return get_string('communicationgrouproomnameformat', 'core_communication', [
561
            'groupname' => $groupname,
562
            'baseroomname' => $baseroomname,
563
        ]);
564
    }
565
}