795 lines
27 KiB
PHP
795 lines
27 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
namespace communication_matrix;
|
|
|
|
use communication_matrix\local\spec\features\matrix\{
|
|
create_room_v3 as create_room_feature,
|
|
get_room_members_v3 as get_room_members_feature,
|
|
remove_member_from_room_v3 as remove_member_from_room_feature,
|
|
update_room_avatar_v3 as update_room_avatar_feature,
|
|
update_room_name_v3 as update_room_name_feature,
|
|
update_room_topic_v3 as update_room_topic_feature,
|
|
upload_content_v3 as upload_content_feature,
|
|
media_create_v1 as media_create_feature,
|
|
};
|
|
use communication_matrix\local\spec\features\synapse\{
|
|
create_user_v2 as create_user_feature,
|
|
get_room_info_v1 as get_room_info_feature,
|
|
get_user_info_v2 as get_user_info_feature,
|
|
invite_member_to_room_v1 as invite_member_to_room_feature,
|
|
};
|
|
use core_communication\processor;
|
|
use stdClass;
|
|
use GuzzleHttp\Psr7\Response;
|
|
|
|
/**
|
|
* class communication_feature to handle matrix specific actions.
|
|
*
|
|
* @package communication_matrix
|
|
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class communication_feature implements
|
|
\core_communication\communication_provider,
|
|
\core_communication\form_provider,
|
|
\core_communication\room_chat_provider,
|
|
\core_communication\room_user_provider,
|
|
\core_communication\user_provider {
|
|
/** @var ?matrix_room $room The matrix room object to update room information */
|
|
private ?matrix_room $room = null;
|
|
|
|
/** @var string|null The URI of the home server */
|
|
protected ?string $homeserverurl = null;
|
|
|
|
/** @var string The URI of the Matrix web client */
|
|
protected string $webclienturl;
|
|
|
|
/** @var \communication_matrix\local\spec\v1p1|null The Matrix API processor */
|
|
protected ?matrix_client $matrixapi;
|
|
|
|
/**
|
|
* Load the communication provider for the communication api.
|
|
*
|
|
* @param processor $communication The communication processor object
|
|
* @return communication_feature The communication provider object
|
|
*/
|
|
public static function load_for_instance(processor $communication): self {
|
|
return new self($communication);
|
|
}
|
|
|
|
/**
|
|
* Reload the room information.
|
|
* This may be necessary after a room has been created or updated via the adhoc task.
|
|
* This is primarily intended for use in unit testing, but may have real world cases too.
|
|
*/
|
|
public function reload(): void {
|
|
$this->room = null;
|
|
$this->processor = processor::load_by_id($this->processor->get_id());
|
|
}
|
|
|
|
/**
|
|
* Constructor for communication provider to initialize necessary objects for api cals etc..
|
|
*
|
|
* @param processor $processor The communication processor object
|
|
*/
|
|
private function __construct(
|
|
private \core_communication\processor $processor,
|
|
) {
|
|
$this->homeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
|
|
$this->webclienturl = get_config('communication_matrix', 'matrixelementurl');
|
|
|
|
if ($processor::is_provider_available('communication_matrix')) {
|
|
// Generate the API instance.
|
|
$this->matrixapi = matrix_client::instance(
|
|
serverurl: $this->homeserverurl,
|
|
accesstoken: get_config('communication_matrix', 'matrixaccesstoken'),
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether the room configuration has been created yet.
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function room_exists(): bool {
|
|
return (bool) $this->get_room_configuration();
|
|
}
|
|
|
|
/**
|
|
* Whether the room exists on the remote server.
|
|
* This does not involve a remote call, but checks whether Moodle is aware of the room id.
|
|
* @return bool
|
|
*/
|
|
protected function remote_room_exists(): bool {
|
|
$room = $this->get_room_configuration();
|
|
|
|
return $room && ($room->get_room_id() !== null);
|
|
}
|
|
|
|
/**
|
|
* Get the stored room configuration.
|
|
* @return null|matrix_room
|
|
*/
|
|
public function get_room_configuration(): ?matrix_room {
|
|
$this->room = matrix_room::load_by_processor_id($this->processor->get_id());
|
|
return $this->room;
|
|
}
|
|
|
|
/**
|
|
* Return the current room id.
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function get_room_id(): ?string {
|
|
return $this->get_room_configuration()?->get_room_id();
|
|
}
|
|
|
|
/**
|
|
* Create members.
|
|
*
|
|
* @param array $userids The Moodle user ids to create
|
|
*/
|
|
public function create_members(array $userids): void {
|
|
$addedmembers = [];
|
|
|
|
// This API requiures the create_user feature.
|
|
$this->matrixapi->require_feature(create_user_feature::class);
|
|
|
|
foreach ($userids as $userid) {
|
|
$user = \core_user::get_user($userid);
|
|
$userfullname = fullname($user);
|
|
|
|
// Proceed if we have a user's full name and email to work with.
|
|
if (!empty($user->email) && !empty($userfullname)) {
|
|
$qualifiedmuid = matrix_user_manager::get_formatted_matrix_userid($user->username);
|
|
|
|
// First create user in matrix.
|
|
$response = $this->matrixapi->create_user(
|
|
userid: $qualifiedmuid,
|
|
displayname: $userfullname,
|
|
threepids: [(object) [
|
|
'medium' => 'email',
|
|
'address' => $user->email,
|
|
], ],
|
|
externalids: [],
|
|
);
|
|
$body = json_decode($response->getBody());
|
|
|
|
if (!empty($matrixuserid = $body->name)) {
|
|
// Then create matrix user id in moodle.
|
|
matrix_user_manager::set_matrix_userid_in_moodle($userid, $qualifiedmuid);
|
|
if ($this->add_registered_matrix_user_to_room($matrixuserid)) {
|
|
$addedmembers[] = $userid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the power level of the users.
|
|
if (!empty($addedmembers) && $this->is_power_levels_update_required($addedmembers)) {
|
|
$this->set_matrix_power_levels();
|
|
}
|
|
|
|
// Mark then users as synced for the added members.
|
|
$this->processor->mark_users_as_synced($addedmembers);
|
|
}
|
|
|
|
public function update_room_membership(array $userids): void {
|
|
|
|
// Filter out any users that are not room members yet.
|
|
$response = $this->matrixapi->get_room_members(
|
|
roomid: $this->get_room_id(),
|
|
);
|
|
$body = self::get_body($response);
|
|
|
|
if (isset($body->joined)) {
|
|
foreach ($userids as $key => $userid) {
|
|
$matrixuserid = matrix_user_manager::get_matrixid_from_moodle(
|
|
userid: $userid,
|
|
);
|
|
if (!array_key_exists($matrixuserid, (array) $body->joined)) {
|
|
unset($userids[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->set_matrix_power_levels();
|
|
// Mark the users as synced for the updated members.
|
|
$this->processor->mark_users_as_synced($userids);
|
|
}
|
|
|
|
/**
|
|
* Add members to a room.
|
|
*
|
|
* @param array $userids The user ids to add
|
|
*/
|
|
public function add_members_to_room(array $userids): void {
|
|
$unregisteredmembers = [];
|
|
$addedmembers = [];
|
|
|
|
foreach ($userids as $userid) {
|
|
$matrixuserid = matrix_user_manager::get_matrixid_from_moodle($userid);
|
|
|
|
if ($matrixuserid && $this->check_user_exists($matrixuserid)) {
|
|
if ($this->add_registered_matrix_user_to_room($matrixuserid)) {
|
|
$addedmembers[] = $userid;
|
|
}
|
|
} else {
|
|
$unregisteredmembers[] = $userid;
|
|
}
|
|
}
|
|
|
|
// Set the power level of the users.
|
|
if (!empty($addedmembers) && $this->is_power_levels_update_required($addedmembers)) {
|
|
$this->set_matrix_power_levels();
|
|
}
|
|
|
|
// Mark then users as synced for the added members.
|
|
$this->processor->mark_users_as_synced($addedmembers);
|
|
|
|
// Create Matrix users.
|
|
if (count($unregisteredmembers) > 0) {
|
|
$this->create_members($unregisteredmembers);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the registered matrix user id to room.
|
|
*
|
|
* @param string $matrixuserid Registered matrix user id
|
|
*/
|
|
private function add_registered_matrix_user_to_room(string $matrixuserid): bool {
|
|
// Require the invite_member_to_room API feature.
|
|
$this->matrixapi->require_feature(invite_member_to_room_feature::class);
|
|
|
|
if (!$this->check_room_membership($matrixuserid)) {
|
|
$response = $this->matrixapi->invite_member_to_room(
|
|
roomid: $this->get_room_id(),
|
|
userid: $matrixuserid,
|
|
);
|
|
|
|
$body = self::get_body($response);
|
|
if (empty($body->room_id)) {
|
|
return false;
|
|
}
|
|
|
|
if ($body->room_id !== $this->get_room_id()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Remove members from a room.
|
|
*
|
|
* @param array $userids The Moodle user ids to remove
|
|
*/
|
|
public function remove_members_from_room(array $userids): void {
|
|
// This API requiures the remove_members_from_room feature.
|
|
$this->matrixapi->require_feature(remove_member_from_room_feature::class);
|
|
|
|
if ($this->get_room_id() === null) {
|
|
return;
|
|
}
|
|
|
|
// Remove the power level for the user first.
|
|
$this->set_matrix_power_levels($userids);
|
|
|
|
$membersremoved = [];
|
|
|
|
$currentpowerlevels = $this->get_current_powerlevel_data();
|
|
$currentuserpowerlevels = (array) $currentpowerlevels->users ?? [];
|
|
|
|
foreach ($userids as $userid) {
|
|
// Check user is member of room first.
|
|
$matrixuserid = matrix_user_manager::get_matrixid_from_moodle($userid);
|
|
|
|
if (!$matrixuserid) {
|
|
// Unable to find a matrix userid for this user.
|
|
continue;
|
|
}
|
|
|
|
if (array_key_exists($matrixuserid, $currentuserpowerlevels)) {
|
|
if ($currentuserpowerlevels[$matrixuserid] >= matrix_constants::POWER_LEVEL_MAXIMUM) {
|
|
// Skip removing the user if they are an admin.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (
|
|
$this->check_user_exists($matrixuserid) &&
|
|
$this->check_room_membership($matrixuserid)
|
|
) {
|
|
$this->matrixapi->remove_member_from_room(
|
|
roomid: $this->get_room_id(),
|
|
userid: $matrixuserid,
|
|
);
|
|
|
|
$membersremoved[] = $userid;
|
|
}
|
|
}
|
|
|
|
$this->processor->delete_instance_user_mapping($membersremoved);
|
|
}
|
|
|
|
/**
|
|
* Check if a user exists in Matrix.
|
|
* Use if user existence is needed before doing something else.
|
|
*
|
|
* @param string $matrixuserid The Matrix user id to check
|
|
* @return bool
|
|
*/
|
|
public function check_user_exists(string $matrixuserid): bool {
|
|
// This API requires the get_user_info feature.
|
|
$this->matrixapi->require_feature(get_user_info_feature::class);
|
|
|
|
$response = $this->matrixapi->get_user_info(
|
|
userid: $matrixuserid,
|
|
);
|
|
$body = self::get_body($response);
|
|
|
|
return isset($body->name);
|
|
}
|
|
|
|
/**
|
|
* Check if a user is a member of a room.
|
|
* Use if membership confirmation is needed before doing something else.
|
|
*
|
|
* @param string $matrixuserid The Matrix user id to check
|
|
* @return bool
|
|
*/
|
|
public function check_room_membership(string $matrixuserid): bool {
|
|
// This API requires the get_room_members feature.
|
|
$this->matrixapi->require_feature(get_room_members_feature::class);
|
|
|
|
$response = $this->matrixapi->get_room_members(
|
|
roomid: $this->get_room_id(),
|
|
);
|
|
$body = self::get_body($response);
|
|
|
|
// Check user id is in the returned room member ids.
|
|
return isset($body->joined) && array_key_exists($matrixuserid, (array) $body->joined);
|
|
}
|
|
|
|
/**
|
|
* Create a room based on the data in the communication instance.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function create_chat_room(): bool {
|
|
if ($this->remote_room_exists()) {
|
|
// A room already exists. Update it instead.
|
|
return $this->update_chat_room();
|
|
}
|
|
|
|
// This method requires the create_room API feature.
|
|
$this->matrixapi->require_feature(create_room_feature::class);
|
|
|
|
$room = $this->get_room_configuration();
|
|
|
|
$response = $this->matrixapi->create_room(
|
|
name: $this->processor->get_room_name(),
|
|
visibility: 'private',
|
|
preset: 'private_chat',
|
|
initialstate: [],
|
|
options: [
|
|
'topic' => $room->get_topic(),
|
|
],
|
|
);
|
|
|
|
$response = self::get_body($response);
|
|
|
|
if (empty($response->room_id)) {
|
|
throw new \moodle_exception(
|
|
'Unable to determine ID of matrix room',
|
|
);
|
|
}
|
|
|
|
// Update our record of the matrix room_id.
|
|
$room->update_room_record(
|
|
roomid: $response->room_id,
|
|
);
|
|
|
|
// Update the room avatar.
|
|
$this->update_room_avatar();
|
|
return true;
|
|
}
|
|
|
|
public function update_chat_room(): bool {
|
|
if (!$this->remote_room_exists()) {
|
|
// No room exists. Create it instead.
|
|
return $this->create_chat_room();
|
|
}
|
|
|
|
$this->matrixapi->require_features([
|
|
get_room_info_feature::class,
|
|
update_room_name_feature::class,
|
|
update_room_topic_feature::class,
|
|
]);
|
|
|
|
// Get room data.
|
|
$response = $this->matrixapi->get_room_info(
|
|
roomid: $this->get_room_id(),
|
|
);
|
|
$remoteroomdata = self::get_body($response);
|
|
|
|
// Update the room name when it's updated from the form.
|
|
if ($remoteroomdata->name !== $this->processor->get_room_name()) {
|
|
$this->matrixapi->update_room_name(
|
|
roomid: $this->get_room_id(),
|
|
name: $this->processor->get_room_name(),
|
|
);
|
|
}
|
|
|
|
// Update the room topic if set.
|
|
$localroomdata = $this->get_room_configuration();
|
|
if ($remoteroomdata->topic !== $localroomdata->get_topic()) {
|
|
$this->matrixapi->update_room_topic(
|
|
roomid: $localroomdata->get_room_id(),
|
|
topic: $localroomdata->get_topic(),
|
|
);
|
|
}
|
|
|
|
// Update room avatar.
|
|
$this->update_room_avatar();
|
|
|
|
return true;
|
|
}
|
|
|
|
public function delete_chat_room(): bool {
|
|
$this->get_room_configuration()->delete_room_record();
|
|
$this->room = null;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Update the room avatar when an instance image is added or updated.
|
|
*/
|
|
public function update_room_avatar(): void {
|
|
// Both of the following features of the remote API are required.
|
|
$this->matrixapi->require_features([
|
|
upload_content_feature::class,
|
|
update_room_avatar_feature::class,
|
|
]);
|
|
|
|
// Check if we have an avatar that needs to be synced.
|
|
if ($this->processor->is_avatar_synced()) {
|
|
return;
|
|
}
|
|
|
|
$instanceimage = $this->processor->get_avatar();
|
|
$contenturi = null;
|
|
|
|
if ($this->matrixapi->implements_feature(media_create_feature::class)) {
|
|
// From version 1.7 we can fetch a mxc URI and use it before uploading the content.
|
|
if ($instanceimage) {
|
|
$response = $this->matrixapi->media_create();
|
|
$contenturi = self::get_body($response)->content_uri;
|
|
|
|
// Now update the room avatar.
|
|
$response = $this->matrixapi->update_room_avatar(
|
|
roomid: $this->get_room_id(),
|
|
avatarurl: $contenturi,
|
|
);
|
|
|
|
// And finally upload the content.
|
|
$this->matrixapi->upload_content($instanceimage);
|
|
} else {
|
|
$response = $this->matrixapi->update_room_avatar(
|
|
roomid: $this->get_room_id(),
|
|
avatarurl: null,
|
|
);
|
|
}
|
|
} else {
|
|
// Prior to v1.7 the only way to upload content was to upload the content, which returns a mxc URI to use.
|
|
|
|
if ($instanceimage) {
|
|
// First upload the content.
|
|
$response = $this->matrixapi->upload_content($instanceimage);
|
|
$body = self::get_body($response);
|
|
$contenturi = $body->content_uri;
|
|
}
|
|
|
|
// Now update the room avatar.
|
|
$response = $this->matrixapi->update_room_avatar(
|
|
roomid: $this->get_room_id(),
|
|
avatarurl: $contenturi,
|
|
);
|
|
}
|
|
|
|
// Indicate the avatar has been synced if it was successfully set with Matrix.
|
|
if ($response->getReasonPhrase() === 'OK') {
|
|
$this->processor->set_avatar_synced_flag(true);
|
|
}
|
|
}
|
|
|
|
public function get_chat_room_url(): ?string {
|
|
if (!$this->get_room_id()) {
|
|
// We don't have a room id for this record.
|
|
return null;
|
|
}
|
|
|
|
return sprintf(
|
|
"%s#/room/%s",
|
|
$this->webclienturl,
|
|
$this->get_room_id(),
|
|
);
|
|
}
|
|
|
|
public function save_form_data(\stdClass $instance): void {
|
|
$matrixroomtopic = $instance->matrixroomtopic ?? null;
|
|
$room = $this->get_room_configuration();
|
|
|
|
if ($room) {
|
|
$room->update_room_record(
|
|
topic: $matrixroomtopic,
|
|
);
|
|
} else {
|
|
$this->room = matrix_room::create_room_record(
|
|
processorid: $this->processor->get_id(),
|
|
topic: $matrixroomtopic,
|
|
);
|
|
}
|
|
}
|
|
|
|
public function set_form_data(\stdClass $instance): void {
|
|
if (!empty($instance->id) && !empty($this->processor->get_id())) {
|
|
if ($this->room_exists()) {
|
|
$instance->matrixroomtopic = $this->get_room_configuration()->get_topic();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function set_form_definition(\MoodleQuickForm $mform): void {
|
|
// Room description for the communication provider.
|
|
$mform->insertElementBefore($mform->createElement(
|
|
'text',
|
|
'matrixroomtopic',
|
|
get_string('matrixroomtopic', 'communication_matrix'),
|
|
'maxlength="255" size="20"'
|
|
), 'addcommunicationoptionshere');
|
|
$mform->addHelpButton('matrixroomtopic', 'matrixroomtopic', 'communication_matrix');
|
|
$mform->setType('matrixroomtopic', PARAM_TEXT);
|
|
}
|
|
|
|
/**
|
|
* Get the body of a response as a stdClass.
|
|
*
|
|
* @param Response $response
|
|
* @return stdClass
|
|
*/
|
|
public static function get_body(Response $response): stdClass {
|
|
$body = $response->getBody();
|
|
|
|
return json_decode($body, false, 512, JSON_THROW_ON_ERROR);
|
|
}
|
|
|
|
/**
|
|
* Set the matrix power level with the room.
|
|
*
|
|
* Users with a non-moodle power level are not typically removed unless specified in the $forceremoval param.
|
|
* Matrix Admin users are never removed.
|
|
*
|
|
* @param array $forceremoval The users to force removal from the room, even if they have a custom power level
|
|
*/
|
|
private function set_matrix_power_levels(
|
|
array $forceremoval = [],
|
|
): void {
|
|
// Get the current power levels.
|
|
$currentpowerlevels = $this->get_current_powerlevel_data();
|
|
$currentuserpowerlevels = (array) $currentpowerlevels->users ?? [];
|
|
|
|
// Get all the current users who need to be in the room.
|
|
$userlist = $this->processor->get_all_userids_for_instance();
|
|
|
|
// Translate the user ids to matrix user ids.
|
|
$userlist = array_combine(
|
|
array_map(
|
|
fn ($userid) => matrix_user_manager::get_matrixid_from_moodle($userid),
|
|
$userlist,
|
|
),
|
|
$userlist,
|
|
);
|
|
|
|
// Determine the power levels, and filter out anyone with the default level.
|
|
$newuserpowerlevels = array_filter(
|
|
array_map(
|
|
fn($userid) => $this->get_user_allowed_power_level($userid),
|
|
$userlist,
|
|
),
|
|
fn($level) => $level !== matrix_constants::POWER_LEVEL_DEFAULT,
|
|
);
|
|
|
|
// Keep current room admins, and users which don't use our MODERATOR power level without changing them.
|
|
$staticusers = $this->get_users_with_custom_power_level($currentuserpowerlevels);
|
|
foreach ($staticusers as $userid => $level) {
|
|
$newuserpowerlevels[$userid] = $level;
|
|
}
|
|
|
|
if (!empty($forceremoval)) {
|
|
// Remove the users from the power levels if they are not admins.
|
|
foreach ($forceremoval as $userid) {
|
|
if ($newuserpowerlevels < matrix_constants::POWER_LEVEL_MAXIMUM) {
|
|
unset($newuserpowerlevels[$userid]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$this->power_levels_changed($currentuserpowerlevels, $newuserpowerlevels)) {
|
|
// No changes to make.
|
|
return;
|
|
}
|
|
|
|
|
|
// Update the power levels for the room.
|
|
$this->matrixapi->update_room_power_levels(
|
|
roomid: $this->get_room_id(),
|
|
users: $newuserpowerlevels,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Filter the list of users provided to remove those with a moodle-related power level.
|
|
*
|
|
* @param array $users
|
|
* @return array
|
|
*/
|
|
private function get_users_with_custom_power_level(array $users): array {
|
|
return array_filter(
|
|
$users,
|
|
function ($level): bool {
|
|
switch ($level) {
|
|
case matrix_constants::POWER_LEVEL_DEFAULT:
|
|
case matrix_constants::POWER_LEVEL_MOODLE_SITE_ADMIN:
|
|
case matrix_constants::POWER_LEVEL_MOODLE_MODERATOR:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check whether power levels have changed compared with the proposed power levels.
|
|
*
|
|
* @param array $currentuserpowerlevels The current power levels
|
|
* @param array $newuserpowerlevels The new power levels proposed
|
|
* @return bool Whether there is any change to be made
|
|
*/
|
|
private function power_levels_changed(
|
|
array $currentuserpowerlevels,
|
|
array $newuserpowerlevels,
|
|
): bool {
|
|
if (count($newuserpowerlevels) !== count($currentuserpowerlevels)) {
|
|
// Different number of keys - there must be a difference then.
|
|
return true;
|
|
}
|
|
|
|
// Sort the power levels.
|
|
ksort($newuserpowerlevels, SORT_NUMERIC);
|
|
|
|
// Get the current power levels.
|
|
ksort($currentuserpowerlevels);
|
|
|
|
$diff = array_merge(
|
|
array_diff_assoc(
|
|
$newuserpowerlevels,
|
|
$currentuserpowerlevels,
|
|
),
|
|
array_diff_assoc(
|
|
$currentuserpowerlevels,
|
|
$newuserpowerlevels,
|
|
),
|
|
);
|
|
|
|
return count($diff) > 0;
|
|
}
|
|
|
|
/**
|
|
* Get the current power level for the room.
|
|
*
|
|
* @return stdClass
|
|
*/
|
|
private function get_current_powerlevel_data(): \stdClass {
|
|
$roomid = $this->get_room_id();
|
|
$response = $this->matrixapi->get_room_power_levels(
|
|
roomid: $roomid,
|
|
);
|
|
if ($response->getStatusCode() !== 200) {
|
|
throw new \moodle_exception(
|
|
'Unable to get power levels for room',
|
|
);
|
|
}
|
|
|
|
$powerdata = $this->get_body($response);
|
|
$powerdata = array_filter(
|
|
$powerdata->rooms->join->{$roomid}->state->events,
|
|
fn($value) => $value->type === 'm.room.power_levels'
|
|
);
|
|
$powerdata = reset($powerdata);
|
|
|
|
return $powerdata->content;
|
|
}
|
|
|
|
/**
|
|
* Determine if a power level update is required.
|
|
* Matrix will always set a user to the default power level of 0 when a power level update is made.
|
|
* That is, unless we specify another level. As long as one person's level is greater than the default,
|
|
* we will need to set the power levels of all users greater than the default.
|
|
*
|
|
* @param array $userids The users to evaluate
|
|
* @return boolean Returns true if an update is required
|
|
*/
|
|
private function is_power_levels_update_required(array $userids): bool {
|
|
// Is the user's power level greater than the default?
|
|
foreach ($userids as $userid) {
|
|
if ($this->get_user_allowed_power_level($userid) > matrix_constants::POWER_LEVEL_DEFAULT) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the allowed power level for the user id according to perms/site admin or default.
|
|
*
|
|
* @param int $userid
|
|
* @return int
|
|
*/
|
|
public function get_user_allowed_power_level(int $userid): int {
|
|
$powerlevel = matrix_constants::POWER_LEVEL_DEFAULT;
|
|
|
|
if (has_capability('communication/matrix:moderator', $this->processor->get_context(), $userid)) {
|
|
$powerlevel = matrix_constants::POWER_LEVEL_MOODLE_MODERATOR;
|
|
}
|
|
|
|
// If site admin, override all caps.
|
|
if (is_siteadmin($userid)) {
|
|
$powerlevel = matrix_constants::POWER_LEVEL_MOODLE_SITE_ADMIN;
|
|
}
|
|
|
|
return $powerlevel;
|
|
}
|
|
|
|
/*
|
|
* Check if matrix settings are configured
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function is_configured(): bool {
|
|
// Matrix communication settings.
|
|
$matrixhomeserverurl = get_config('communication_matrix', 'matrixhomeserverurl');
|
|
$matrixaccesstoken = get_config('communication_matrix', 'matrixaccesstoken');
|
|
$matrixelementurl = get_config('communication_matrix', 'matrixelementurl');
|
|
|
|
if (
|
|
!empty($matrixhomeserverurl) &&
|
|
!empty($matrixaccesstoken) &&
|
|
(PHPUNIT_TEST || defined('BEHAT_SITE_RUNNING') || !empty($matrixelementurl))
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|