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 communication_matrix;
18
 
19
use communication_matrix\local\spec\features\matrix\{
20
    create_room_v3 as create_room_feature,
21
    get_room_members_v3 as get_room_members_feature,
22
    remove_member_from_room_v3 as remove_member_from_room_feature,
23
    update_room_avatar_v3 as update_room_avatar_feature,
24
    update_room_name_v3 as update_room_name_feature,
25
    update_room_topic_v3 as update_room_topic_feature,
26
    upload_content_v3 as upload_content_feature,
27
    media_create_v1 as media_create_feature,
28
};
29
use communication_matrix\local\spec\features\synapse\{
30
    create_user_v2 as create_user_feature,
31
    get_room_info_v1 as get_room_info_feature,
32
    get_user_info_v2 as get_user_info_feature,
33
    invite_member_to_room_v1 as invite_member_to_room_feature,
34
};
35
use core_communication\processor;
36
use stdClass;
37
use GuzzleHttp\Psr7\Response;
38
 
39
/**
40
 * class communication_feature to handle matrix specific actions.
41
 *
42
 * @package    communication_matrix
43
 * @copyright  2023 Safat Shahin <safat.shahin@moodle.com>
44
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45
 */
46
class communication_feature implements
47
    \core_communication\communication_provider,
48
    \core_communication\form_provider,
49
    \core_communication\room_chat_provider,
50
    \core_communication\room_user_provider,
51
    \core_communication\synchronise_provider,
52
    \core_communication\user_provider {
53
    /** @var ?matrix_room $room The matrix room object to update room information */
54
    private ?matrix_room $room = null;
55
 
56
    /** @var string|null The URI of the home server */
57
    protected ?string $homeserverurl = null;
58
 
59
    /** @var string The URI of the Matrix web client */
60
    protected string $webclienturl;
61
 
62
    /** @var \communication_matrix\local\spec\v1p1|null The Matrix API processor */
63
    protected ?matrix_client $matrixapi;
64
 
65
    /**
66
     * Load the communication provider for the communication api.
67
     *
68
     * @param processor $communication The communication processor object
69
     * @return communication_feature The communication provider object
70
     */
71
    public static function load_for_instance(processor $communication): self {
72
        return new self($communication);
73
    }
74
 
75
    /**
76
     * Reload the room information.
77
     * This may be necessary after a room has been created or updated via the adhoc task.
78
     * This is primarily intended for use in unit testing, but may have real world cases too.
79
     */
80
    public function reload(): void {
81
        $this->room = null;
82
        $this->processor = processor::load_by_id($this->processor->get_id());
83
    }
84
 
85
    /**
86
     * Constructor for communication provider to initialize necessary objects for api cals etc..
87
     *
88
     * @param processor $processor The communication processor object
89
     */
90
    private function __construct(
91
        private \core_communication\processor $processor,
92
    ) {
93
        $this->homeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
94
        $this->webclienturl = get_config('communication_matrix', 'matrixelementurl');
95
 
96
        if ($processor::is_provider_available('communication_matrix')) {
97
            // Generate the API instance.
98
            $this->matrixapi = matrix_client::instance(
99
                serverurl: $this->homeserverurl,
100
                accesstoken: get_config('communication_matrix', 'matrixaccesstoken'),
101
            );
102
        }
103
    }
104
 
105
    /**
106
     * Check whether the room configuration has been created yet.
107
     *
108
     * @return bool
109
     */
110
    protected function room_exists(): bool {
111
        return (bool) $this->get_room_configuration();
112
    }
113
 
114
    /**
115
     * Whether the room exists on the remote server.
116
     * This does not involve a remote call, but checks whether Moodle is aware of the room id.
117
     * @return bool
118
     */
119
    protected function remote_room_exists(): bool {
120
        $room = $this->get_room_configuration();
121
 
122
        return $room && ($room->get_room_id() !== null);
123
    }
124
 
125
    /**
126
     * Get the stored room configuration.
127
     * @return null|matrix_room
128
     */
129
    public function get_room_configuration(): ?matrix_room {
130
        $this->room = matrix_room::load_by_processor_id($this->processor->get_id());
131
        return $this->room;
132
    }
133
 
134
    /**
135
     * Return the current room id.
136
     *
137
     * @return string|null
138
     */
139
    public function get_room_id(): ?string {
140
        return $this->get_room_configuration()?->get_room_id();
141
    }
142
 
143
    /**
144
     * Create members.
145
     *
146
     * @param array $userids The Moodle user ids to create
147
     */
148
    public function create_members(array $userids): void {
149
        $addedmembers = [];
150
 
151
        // This API requiures the create_user feature.
152
        $this->matrixapi->require_feature(create_user_feature::class);
153
 
154
        foreach ($userids as $userid) {
155
            $user = \core_user::get_user($userid);
156
            $userfullname = fullname($user);
157
 
158
            // Proceed if we have a user's full name and email to work with.
159
            if (!empty($user->email) && !empty($userfullname)) {
160
                $qualifiedmuid = matrix_user_manager::get_formatted_matrix_userid($user->username);
161
 
162
                // First create user in matrix.
163
                $response = $this->matrixapi->create_user(
164
                    userid: $qualifiedmuid,
165
                    displayname: $userfullname,
166
                    threepids: [(object) [
167
                        'medium' => 'email',
168
                        'address' => $user->email,
169
                    ], ],
170
                    externalids: [],
171
                );
172
                $body = json_decode($response->getBody());
173
 
174
                if (!empty($matrixuserid = $body->name)) {
175
                    // Then create matrix user id in moodle.
176
                    matrix_user_manager::set_matrix_userid_in_moodle($userid, $qualifiedmuid);
177
                    if ($this->add_registered_matrix_user_to_room($matrixuserid)) {
178
                        $addedmembers[] = $userid;
179
                    }
180
                }
181
            }
182
        }
183
 
184
        // Set the power level of the users.
185
        if (!empty($addedmembers) && $this->is_power_levels_update_required($addedmembers)) {
186
            $this->set_matrix_power_levels();
187
        }
188
 
189
        // Mark then users as synced for the added members.
190
        $this->processor->mark_users_as_synced($addedmembers);
191
    }
192
 
193
    public function update_room_membership(array $userids): void {
194
 
195
        // Filter out any users that are not room members yet.
196
        $response = $this->matrixapi->get_room_members(
197
            roomid: $this->get_room_id(),
198
        );
199
        $body = self::get_body($response);
200
 
201
        if (isset($body->joined)) {
202
            foreach ($userids as $key => $userid) {
203
                $matrixuserid = matrix_user_manager::get_matrixid_from_moodle(
204
                    userid: $userid,
205
                );
206
                if (!array_key_exists($matrixuserid, (array) $body->joined)) {
207
                    unset($userids[$key]);
208
                }
209
            }
210
        }
211
 
212
        $this->set_matrix_power_levels();
213
        // Mark the users as synced for the updated members.
214
        $this->processor->mark_users_as_synced($userids);
215
    }
216
 
217
    /**
218
     * Add members to a room.
219
     *
220
     * @param array $userids The user ids to add
221
     */
222
    public function add_members_to_room(array $userids): void {
223
        $unregisteredmembers = [];
224
        $addedmembers = [];
225
 
226
        foreach ($userids as $userid) {
227
            $matrixuserid = matrix_user_manager::get_matrixid_from_moodle($userid);
228
 
229
            if ($matrixuserid && $this->check_user_exists($matrixuserid)) {
230
                if ($this->add_registered_matrix_user_to_room($matrixuserid)) {
231
                    $addedmembers[] = $userid;
232
                }
233
            } else {
234
                $unregisteredmembers[] = $userid;
235
            }
236
        }
237
 
238
        // Set the power level of the users.
239
        if (!empty($addedmembers) && $this->is_power_levels_update_required($addedmembers)) {
240
            $this->set_matrix_power_levels();
241
        }
242
 
243
        // Mark then users as synced for the added members.
244
        $this->processor->mark_users_as_synced($addedmembers);
245
 
246
        // Create Matrix users.
247
        if (count($unregisteredmembers) > 0) {
248
            $this->create_members($unregisteredmembers);
249
        }
250
    }
251
 
252
    /**
253
     * Adds the registered matrix user id to room.
254
     *
255
     * @param string $matrixuserid Registered matrix user id
256
     */
257
    private function add_registered_matrix_user_to_room(string $matrixuserid): bool {
258
        // Require the invite_member_to_room API feature.
259
        $this->matrixapi->require_feature(invite_member_to_room_feature::class);
260
 
261
        if (!$this->check_room_membership($matrixuserid)) {
262
            $response = $this->matrixapi->invite_member_to_room(
263
                roomid: $this->get_room_id(),
264
                userid: $matrixuserid,
265
            );
266
 
267
            $body = self::get_body($response);
268
            if (empty($body->room_id)) {
269
                return false;
270
            }
271
 
272
            if ($body->room_id !== $this->get_room_id()) {
273
                return false;
274
            }
275
 
276
            return true;
277
        }
278
        return false;
279
    }
280
 
281
    /**
282
     * Remove members from a room.
283
     *
284
     * @param array $userids The Moodle user ids to remove
285
     */
286
    public function remove_members_from_room(array $userids): void {
287
        // This API requiures the remove_members_from_room feature.
288
        $this->matrixapi->require_feature(remove_member_from_room_feature::class);
289
 
290
        if ($this->get_room_id() === null) {
291
            return;
292
        }
293
 
294
        // Remove the power level for the user first.
295
        $this->set_matrix_power_levels($userids);
296
 
297
        $membersremoved = [];
298
 
299
        $currentpowerlevels = $this->get_current_powerlevel_data();
300
        $currentuserpowerlevels = (array) $currentpowerlevels->users ?? [];
301
 
302
        foreach ($userids as $userid) {
303
            // Check user is member of room first.
304
            $matrixuserid = matrix_user_manager::get_matrixid_from_moodle($userid);
305
 
306
            if (!$matrixuserid) {
307
                // Unable to find a matrix userid for this user.
308
                continue;
309
            }
310
 
311
            if (array_key_exists($matrixuserid, $currentuserpowerlevels)) {
312
                if ($currentuserpowerlevels[$matrixuserid] >= matrix_constants::POWER_LEVEL_MAXIMUM) {
313
                    // Skip removing the user if they are an admin.
314
                    continue;
315
                }
316
            }
317
 
318
            if (
319
                $this->check_user_exists($matrixuserid) &&
320
                $this->check_room_membership($matrixuserid)
321
            ) {
322
                $this->matrixapi->remove_member_from_room(
323
                    roomid: $this->get_room_id(),
324
                    userid: $matrixuserid,
325
                );
326
 
327
                $membersremoved[] = $userid;
328
            }
329
        }
330
 
331
        $this->processor->delete_instance_user_mapping($membersremoved);
332
    }
333
 
334
    /**
335
     * Check if a user exists in Matrix.
336
     * Use if user existence is needed before doing something else.
337
     *
338
     * @param string $matrixuserid The Matrix user id to check
339
     * @return bool
340
     */
341
    public function check_user_exists(string $matrixuserid): bool {
342
        // This API requires the get_user_info feature.
343
        $this->matrixapi->require_feature(get_user_info_feature::class);
344
 
345
        $response = $this->matrixapi->get_user_info(
346
            userid: $matrixuserid,
347
        );
348
        $body = self::get_body($response);
349
 
350
        return isset($body->name);
351
    }
352
 
353
    /**
354
     * Check if a user is a member of a room.
355
     * Use if membership confirmation is needed before doing something else.
356
     *
357
     * @param string $matrixuserid The Matrix user id to check
358
     * @return bool
359
     */
360
    public function check_room_membership(string $matrixuserid): bool {
361
        // This API requires the get_room_members feature.
362
        $this->matrixapi->require_feature(get_room_members_feature::class);
363
 
364
        $response = $this->matrixapi->get_room_members(
365
            roomid: $this->get_room_id(),
366
        );
367
        $body = self::get_body($response);
368
 
369
        // Check user id is in the returned room member ids.
370
        return isset($body->joined) && array_key_exists($matrixuserid, (array) $body->joined);
371
    }
372
 
373
    /**
374
     * Create a room based on the data in the communication instance.
375
     *
376
     * @return bool
377
     */
378
    public function create_chat_room(): bool {
379
        if ($this->remote_room_exists()) {
380
            // A room already exists. Update it instead.
381
            return $this->update_chat_room();
382
        }
383
 
384
        // This method requires the create_room API feature.
385
        $this->matrixapi->require_feature(create_room_feature::class);
386
 
387
        $room = $this->get_room_configuration();
388
 
389
        $response = $this->matrixapi->create_room(
390
            name: $this->processor->get_room_name(),
391
            visibility: 'private',
392
            preset: 'private_chat',
393
            initialstate: [],
394
            options: [
395
                'topic' => $room->get_topic(),
396
            ],
397
        );
398
 
399
        $response = self::get_body($response);
400
 
401
        if (empty($response->room_id)) {
402
            throw new \moodle_exception(
403
                'Unable to determine ID of matrix room',
404
            );
405
        }
406
 
407
        // Update our record of the matrix room_id.
408
        $room->update_room_record(
409
            roomid: $response->room_id,
410
        );
411
 
412
        // Update the room avatar.
413
        $this->update_room_avatar();
414
        return true;
415
    }
416
 
417
    public function update_chat_room(): bool {
418
        if (!$this->remote_room_exists()) {
419
            // No room exists. Create it instead.
420
            return $this->create_chat_room();
421
        }
422
 
423
        $this->matrixapi->require_features([
424
            get_room_info_feature::class,
425
            update_room_name_feature::class,
426
            update_room_topic_feature::class,
427
        ]);
428
 
429
        // Get room data.
430
        $response = $this->matrixapi->get_room_info(
431
            roomid: $this->get_room_id(),
432
        );
433
        $remoteroomdata = self::get_body($response);
434
 
435
        // Update the room name when it's updated from the form.
436
        if ($remoteroomdata->name !== $this->processor->get_room_name()) {
437
            $this->matrixapi->update_room_name(
438
                roomid: $this->get_room_id(),
439
                name: $this->processor->get_room_name(),
440
            );
441
        }
442
 
443
        // Update the room topic if set.
444
        $localroomdata = $this->get_room_configuration();
445
        if ($remoteroomdata->topic !== $localroomdata->get_topic()) {
446
            $this->matrixapi->update_room_topic(
447
                roomid: $localroomdata->get_room_id(),
448
                topic: $localroomdata->get_topic(),
449
            );
450
        }
451
 
452
        // Update room avatar.
453
        $this->update_room_avatar();
454
 
455
        return true;
456
    }
457
 
458
    public function delete_chat_room(): bool {
459
        $this->get_room_configuration()->delete_room_record();
460
        $this->room = null;
461
 
462
        return true;
463
    }
464
 
465
    /**
466
     * Update the room avatar when an instance image is added or updated.
467
     */
468
    public function update_room_avatar(): void {
469
        // Both of the following features of the remote API are required.
470
        $this->matrixapi->require_features([
471
            upload_content_feature::class,
472
            update_room_avatar_feature::class,
473
        ]);
474
 
475
        // Check if we have an avatar that needs to be synced.
476
        if ($this->processor->is_avatar_synced()) {
477
            return;
478
        }
479
 
480
        $instanceimage = $this->processor->get_avatar();
481
        $contenturi = null;
482
 
483
        if ($this->matrixapi->implements_feature(media_create_feature::class)) {
484
            // From version 1.7 we can fetch a mxc URI and use it before uploading the content.
485
            if ($instanceimage) {
486
                $response = $this->matrixapi->media_create();
487
                $contenturi = self::get_body($response)->content_uri;
488
 
489
                // Now update the room avatar.
490
                $response = $this->matrixapi->update_room_avatar(
491
                    roomid: $this->get_room_id(),
492
                    avatarurl: $contenturi,
493
                );
494
 
495
                // And finally upload the content.
496
                $this->matrixapi->upload_content($instanceimage);
497
            } else {
498
                $response = $this->matrixapi->update_room_avatar(
499
                    roomid: $this->get_room_id(),
500
                    avatarurl: null,
501
                );
502
            }
503
        } else {
504
            // Prior to v1.7 the only way to upload content was to upload the content, which returns a mxc URI to use.
505
 
506
            if ($instanceimage) {
507
                // First upload the content.
508
                $response = $this->matrixapi->upload_content($instanceimage);
509
                $body = self::get_body($response);
510
                $contenturi = $body->content_uri;
511
            }
512
 
513
            // Now update the room avatar.
514
            $response = $this->matrixapi->update_room_avatar(
515
                roomid: $this->get_room_id(),
516
                avatarurl: $contenturi,
517
            );
518
        }
519
 
520
        // Indicate the avatar has been synced if it was successfully set with Matrix.
521
        if ($response->getReasonPhrase() === 'OK') {
522
            $this->processor->set_avatar_synced_flag(true);
523
        }
524
    }
525
 
526
    public function get_chat_room_url(): ?string {
527
        if (!$this->get_room_id()) {
528
            // We don't have a room id for this record.
529
            return null;
530
        }
531
 
532
        return sprintf(
533
            "%s#/room/%s",
534
            $this->webclienturl,
535
            $this->get_room_id(),
536
        );
537
    }
538
 
539
    public function save_form_data(\stdClass $instance): void {
540
        $matrixroomtopic = $instance->matrixroomtopic ?? null;
541
        $room = $this->get_room_configuration();
542
 
543
        if ($room) {
544
            $room->update_room_record(
545
                topic: $matrixroomtopic,
546
            );
547
        } else {
548
            $this->room = matrix_room::create_room_record(
549
                processorid: $this->processor->get_id(),
550
                topic: $matrixroomtopic,
551
            );
552
        }
553
    }
554
 
555
    public function set_form_data(\stdClass $instance): void {
556
        if (!empty($instance->id) && !empty($this->processor->get_id())) {
557
            if ($this->room_exists()) {
558
                $instance->matrixroomtopic = $this->get_room_configuration()->get_topic();
559
            }
560
        }
561
    }
562
 
563
    public static function set_form_definition(\MoodleQuickForm $mform): void {
564
        // Room description for the communication provider.
565
        $mform->insertElementBefore($mform->createElement(
566
            'text',
567
            'matrixroomtopic',
568
            get_string('matrixroomtopic', 'communication_matrix'),
569
            'maxlength="255" size="20"'
570
        ), 'addcommunicationoptionshere');
571
        $mform->addHelpButton('matrixroomtopic', 'matrixroomtopic', 'communication_matrix');
572
        $mform->setType('matrixroomtopic', PARAM_TEXT);
573
    }
574
 
575
    /**
576
     * Get the body of a response as a stdClass.
577
     *
578
     * @param Response $response
579
     * @return stdClass
580
     */
581
    public static function get_body(Response $response): stdClass {
582
        $body = $response->getBody();
583
 
584
        return json_decode($body, false, 512, JSON_THROW_ON_ERROR);
585
    }
586
 
587
    /**
588
     * Set the matrix power level with the room.
589
     *
590
     * Users with a non-moodle power level are not typically removed unless specified in the $forceremoval param.
591
     * Matrix Admin users are never removed.
592
     *
593
     * @param array $forceremoval The users to force removal from the room, even if they have a custom power level
594
     */
595
    private function set_matrix_power_levels(
596
        array $forceremoval = [],
597
    ): void {
598
        // Get the current power levels.
599
        $currentpowerlevels = $this->get_current_powerlevel_data();
600
        $currentuserpowerlevels = (array) $currentpowerlevels->users ?? [];
601
 
602
        // Get all the current users who need to be in the room.
603
        $userlist = $this->processor->get_all_userids_for_instance();
604
 
605
        // Translate the user ids to matrix user ids.
606
        $userlist = array_combine(
607
            array_map(
608
                fn ($userid) => matrix_user_manager::get_matrixid_from_moodle($userid),
609
                $userlist,
610
            ),
611
            $userlist,
612
        );
613
 
614
        // Determine the power levels, and filter out anyone with the default level.
615
        $newuserpowerlevels = array_filter(
616
            array_map(
617
                fn($userid) => $this->get_user_allowed_power_level($userid),
618
                $userlist,
619
            ),
620
            fn($level) => $level !== matrix_constants::POWER_LEVEL_DEFAULT,
621
        );
622
 
623
        // Keep current room admins, and users which don't use our MODERATOR power level without changing them.
624
        $staticusers = $this->get_users_with_custom_power_level($currentuserpowerlevels);
625
        foreach ($staticusers as $userid => $level) {
626
            $newuserpowerlevels[$userid] = $level;
627
        }
628
 
629
        if (!empty($forceremoval)) {
630
            // Remove the users from the power levels if they are not admins.
631
            foreach ($forceremoval as $userid) {
11 efrain 632
                $muid = matrix_user_manager::get_matrixid_from_moodle($userid);
633
                if (isset($newuserpowerlevels[$muid]) && $newuserpowerlevels[$muid] < matrix_constants::POWER_LEVEL_MAXIMUM) {
634
                    unset($newuserpowerlevels[$muid]);
1 efrain 635
                }
636
            }
637
        }
638
 
639
        if (!$this->power_levels_changed($currentuserpowerlevels, $newuserpowerlevels)) {
640
            // No changes to make.
641
            return;
642
        }
643
 
644
        // Update the power levels for the room.
645
        $this->matrixapi->update_room_power_levels(
646
            roomid: $this->get_room_id(),
647
            users: $newuserpowerlevels,
648
        );
649
    }
650
 
651
    /**
652
     * Filter the list of users provided to remove those with a moodle-related power level.
653
     *
654
     * @param array $users
655
     * @return array
656
     */
657
    private function get_users_with_custom_power_level(array $users): array {
658
        return array_filter(
659
            $users,
660
            function ($level): bool {
661
                switch ($level) {
662
                    case matrix_constants::POWER_LEVEL_DEFAULT:
663
                    case matrix_constants::POWER_LEVEL_MOODLE_SITE_ADMIN:
664
                    case matrix_constants::POWER_LEVEL_MOODLE_MODERATOR:
665
                        return false;
666
                    default:
667
                        return true;
668
                }
669
            },
670
        );
671
    }
672
 
673
    /**
674
     * Check whether power levels have changed compared with the proposed power levels.
675
     *
676
     * @param array $currentuserpowerlevels The current power levels
677
     * @param array $newuserpowerlevels The new power levels proposed
678
     * @return bool Whether there is any change to be made
679
     */
680
    private function power_levels_changed(
681
        array $currentuserpowerlevels,
682
        array $newuserpowerlevels,
683
    ): bool {
684
        if (count($newuserpowerlevels) !== count($currentuserpowerlevels)) {
685
            // Different number of keys - there must be a difference then.
686
            return true;
687
        }
688
 
689
        // Sort the power levels.
690
        ksort($newuserpowerlevels, SORT_NUMERIC);
691
 
692
        // Get the current power levels.
693
        ksort($currentuserpowerlevels);
694
 
695
        $diff = array_merge(
696
            array_diff_assoc(
697
                $newuserpowerlevels,
698
                $currentuserpowerlevels,
699
            ),
700
            array_diff_assoc(
701
                $currentuserpowerlevels,
702
                $newuserpowerlevels,
703
            ),
704
        );
705
 
706
        return count($diff) > 0;
707
    }
708
 
709
    /**
710
     * Get the current power level for the room.
711
     *
712
     * @return stdClass
713
     */
714
    private function get_current_powerlevel_data(): \stdClass {
715
        $roomid = $this->get_room_id();
716
        $response = $this->matrixapi->get_room_power_levels(
717
            roomid: $roomid,
718
        );
719
        if ($response->getStatusCode() !== 200) {
720
            throw new \moodle_exception(
721
                'Unable to get power levels for room',
722
            );
723
        }
724
 
11 efrain 725
        return $this->get_body($response);
1 efrain 726
    }
727
 
728
    /**
729
     * Determine if a power level update is required.
730
     * Matrix will always set a user to the default power level of 0 when a power level update is made.
731
     * That is, unless we specify another level. As long as one person's level is greater than the default,
732
     * we will need to set the power levels of all users greater than the default.
733
     *
734
     * @param array $userids The users to evaluate
735
     * @return boolean Returns true if an update is required
736
     */
737
    private function is_power_levels_update_required(array $userids): bool {
738
        // Is the user's power level greater than the default?
739
        foreach ($userids as $userid) {
740
            if ($this->get_user_allowed_power_level($userid) > matrix_constants::POWER_LEVEL_DEFAULT) {
741
                return true;
742
            }
743
        }
744
        return false;
745
    }
746
 
747
    /**
748
     * Get the allowed power level for the user id according to perms/site admin or default.
749
     *
750
     * @param int $userid
751
     * @return int
752
     */
753
    public function get_user_allowed_power_level(int $userid): int {
754
        $powerlevel = matrix_constants::POWER_LEVEL_DEFAULT;
755
 
756
        if (has_capability('communication/matrix:moderator', $this->processor->get_context(), $userid)) {
757
            $powerlevel = matrix_constants::POWER_LEVEL_MOODLE_MODERATOR;
758
        }
759
 
760
        // If site admin, override all caps.
761
        if (is_siteadmin($userid)) {
762
            $powerlevel = matrix_constants::POWER_LEVEL_MOODLE_SITE_ADMIN;
763
        }
764
 
765
        return $powerlevel;
766
    }
767
 
768
    /*
769
     * Check if matrix settings are configured
770
     *
771
     * @return boolean
772
     */
773
    public static function is_configured(): bool {
774
        // Matrix communication settings.
775
        $matrixhomeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
776
        $matrixaccesstoken = get_config('communication_matrix', 'matrixaccesstoken');
777
        $matrixelementurl = get_config('communication_matrix', 'matrixelementurl');
778
 
779
        if (
780
            !empty($matrixhomeserverurl) &&
781
            !empty($matrixaccesstoken) &&
782
            (PHPUNIT_TEST || defined('BEHAT_SITE_RUNNING') || !empty($matrixelementurl))
783
        ) {
784
            return true;
785
        }
786
        return false;
787
    }
788
 
789
    public function synchronise_room_members(): void {
790
        $this->set_matrix_power_levels();
791
    }
792
}