mirror of
https://github.com/moodle/moodle.git
synced 2025-08-08 10:26:40 +02:00
MDL-78129 communication_matrix: Add support for matrix power level
This commit is contained in:
parent
3e47253787
commit
e9743431a6
8 changed files with 363 additions and 7 deletions
|
@ -50,7 +50,7 @@ class communication_feature implements
|
|||
\core_communication\room_user_provider,
|
||||
\core_communication\form_provider {
|
||||
|
||||
/** @var matrix_room $room The matrix room object to update room information */
|
||||
/** @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 */
|
||||
|
@ -127,10 +127,7 @@ class communication_feature implements
|
|||
* @return null|matrix_room
|
||||
*/
|
||||
public function get_room_configuration(): ?matrix_room {
|
||||
if ($this->room === null) {
|
||||
$this->room = matrix_room::load_by_processor_id($this->processor->get_id());
|
||||
}
|
||||
|
||||
$this->room = matrix_room::load_by_processor_id($this->processor->get_id());
|
||||
return $this->room;
|
||||
}
|
||||
|
||||
|
@ -184,10 +181,21 @@ class communication_feature implements
|
|||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
$this->set_matrix_power_levels();
|
||||
// Mark then users as synced for the updated members.
|
||||
$this->processor->mark_users_as_synced($userids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add members to a room.
|
||||
*
|
||||
|
@ -209,6 +217,11 @@ class communication_feature implements
|
|||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
|
@ -256,6 +269,13 @@ class communication_feature implements
|
|||
// 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 = [];
|
||||
|
||||
foreach ($userids as $userid) {
|
||||
|
@ -515,4 +535,82 @@ class communication_feature implements
|
|||
|
||||
return json_decode($body, false, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the matrix power level with the room.
|
||||
*
|
||||
* @param array $resetusers The list of users to override and reset their power level to 0
|
||||
*/
|
||||
private function set_matrix_power_levels(array $resetusers = []): void {
|
||||
// Get all the current users for the room.
|
||||
$existingusers = $this->processor->get_all_userids_for_instance();
|
||||
|
||||
$userpowerlevels = [];
|
||||
foreach ($existingusers as $existinguser) {
|
||||
$matrixuserid = matrix_user_manager::get_matrixid_from_moodle($existinguser);
|
||||
|
||||
if (!$matrixuserid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($resetusers) && in_array($existinguser, $resetusers, true)) {
|
||||
$userpowerlevels[$matrixuserid] = matrix_constants::POWER_LEVEL_DEFAULT;
|
||||
} else {
|
||||
$existinguserpowerlevel = $this->get_user_allowed_power_level($existinguser);
|
||||
// We don't need to include the default power level users in request, as matrix will make then default anyways.
|
||||
if ($existinguserpowerlevel > matrix_constants::POWER_LEVEL_DEFAULT) {
|
||||
$userpowerlevels[$matrixuserid] = $existinguserpowerlevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now add the token user permission to retain the permission in the room.
|
||||
$response = $this->matrixapi->get_room_info($this->get_room_id());
|
||||
$matrixroomdata = self::get_body($response);
|
||||
$roomadmin = $matrixroomdata->creator;
|
||||
$userpowerlevels[$roomadmin] = matrix_constants::POWER_LEVEL_MAXIMUM;
|
||||
|
||||
$this->matrixapi->update_room_power_levels($this->get_room_id(), $userpowerlevels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
$context = \context_course::instance($this->processor->get_instance_id());
|
||||
$powerlevel = matrix_constants::POWER_LEVEL_DEFAULT;
|
||||
|
||||
if (has_capability('communication/matrix:moderator', $context, $userid)) {
|
||||
$powerlevel = matrix_constants::POWER_LEVEL_MODERATOR;
|
||||
}
|
||||
|
||||
// If site admin, override all caps.
|
||||
if (is_siteadmin($userid)) {
|
||||
$powerlevel = matrix_constants::POWER_LEVEL_MOODLE_SITE_ADMIN;
|
||||
}
|
||||
|
||||
return $powerlevel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?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\local\spec\features\matrix;
|
||||
|
||||
use communication_matrix\local\command;
|
||||
use communication_matrix\matrix_constants;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
|
||||
/**
|
||||
* Matrix API feature to update a room power levels.
|
||||
*
|
||||
* Matrix rooms have a concept of power levels, which are used to determine what actions a user can perform in a room.
|
||||
*
|
||||
* https://spec.matrix.org/v1.1/client-server-api/#mroompower_levels
|
||||
*
|
||||
* @package communication_matrix
|
||||
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @codeCoverageIgnore
|
||||
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
|
||||
*/
|
||||
trait update_room_power_levels_v3 {
|
||||
|
||||
/**
|
||||
* Set the avatar for a room to the specified URL.
|
||||
*
|
||||
* @param string $roomid The roomid to set for
|
||||
* @param array $users The users to set power levels for
|
||||
* @param int $ban The level required to ban a user
|
||||
* @param int $invite The level required to invite a user
|
||||
* @param int $kick The level required to kick a user
|
||||
* @param array $notifications The level required to send notifications
|
||||
* @param int $redact The level required to redact events
|
||||
* @return Response
|
||||
*/
|
||||
public function update_room_power_levels(
|
||||
string $roomid,
|
||||
array $users,
|
||||
int $ban = matrix_constants::POWER_LEVEL_MAXIMUM,
|
||||
int $invite = matrix_constants::POWER_LEVEL_MODERATOR,
|
||||
int $kick = matrix_constants::POWER_LEVEL_MODERATOR,
|
||||
array $notifications = [
|
||||
'room' => matrix_constants::POWER_LEVEL_MODERATOR,
|
||||
],
|
||||
int $redact = matrix_constants::POWER_LEVEL_MODERATOR,
|
||||
): Response {
|
||||
$params = [
|
||||
':roomid' => $roomid,
|
||||
'ban' => $ban,
|
||||
'invite' => $invite,
|
||||
'kick' => $kick,
|
||||
'notifications' => $notifications,
|
||||
'redact' => $redact,
|
||||
'users' => $users,
|
||||
];
|
||||
|
||||
return $this->execute(new command(
|
||||
$this,
|
||||
method: 'PUT',
|
||||
endpoint: '_matrix/client/v3/rooms/:roomid/state/m.room.power_levels',
|
||||
params: $params,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -34,6 +34,7 @@ class v1p1 extends \communication_matrix\matrix_client {
|
|||
use features\matrix\update_room_name_v3;
|
||||
use features\matrix\update_room_topic_v3;
|
||||
use features\matrix\upload_content_v3;
|
||||
use features\matrix\update_room_power_levels_v3;
|
||||
|
||||
// We use the Synapse API here because it can invite users to a room without requiring them to accept the invite.
|
||||
use features\synapse\invite_member_to_room_v1;
|
||||
|
|
47
communication/provider/matrix/classes/matrix_constants.php
Normal file
47
communication/provider/matrix/classes/matrix_constants.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* class matrix_constants to have one location to store all constants related to matrix.
|
||||
*
|
||||
* @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 matrix_constants {
|
||||
|
||||
/**
|
||||
* User default power level for matrix.
|
||||
*/
|
||||
public const POWER_LEVEL_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* User moderator power level for matrix.
|
||||
*/
|
||||
public const POWER_LEVEL_MODERATOR = 50;
|
||||
|
||||
/**
|
||||
* User power level for matrix associated to moodle site admins. It is a custom power level for site admins.
|
||||
*/
|
||||
public const POWER_LEVEL_MOODLE_SITE_ADMIN = 90;
|
||||
|
||||
/**
|
||||
* User maximum power level for matrix. This is only associated to the token user to allow god mode actions.
|
||||
*/
|
||||
public const POWER_LEVEL_MAXIMUM = 100;
|
||||
}
|
39
communication/provider/matrix/db/access.php
Normal file
39
communication/provider/matrix/db/access.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Capability definitions for matrix communication.
|
||||
*
|
||||
* @package communication_matrix
|
||||
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$capabilities = [
|
||||
|
||||
// Matrix moderator capability which aligns with the matrix moderator role or power level 50.
|
||||
'communication/matrix:moderator' => [
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => [
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW,
|
||||
'teacher' => CAP_ALLOW,
|
||||
]
|
||||
],
|
||||
];
|
|
@ -35,5 +35,6 @@ $string['matrixelementurl'] = 'Element web URL';
|
|||
$string['matrixelementurl_desc'] = 'The URL to Element Web instance.';
|
||||
$string['matrixroomtopic'] = 'Room topic';
|
||||
$string['matrixroomtopic_help'] = 'A short description of what this room is for.';
|
||||
$string['matrix:moderator'] = 'Matrix moderator';
|
||||
$string['pluginname'] = 'Matrix';
|
||||
$string['privacy:metadata'] = 'Matrix communication plugin does not store any personal data.';
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace communication_matrix;
|
|||
|
||||
use core_communication\api;
|
||||
use core_communication\communication_test_helper_trait;
|
||||
use core_communication\processor;
|
||||
use stored_file;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
@ -191,7 +192,6 @@ class communication_feature_test extends \advanced_testcase {
|
|||
'Our updated room name',
|
||||
$communication->get_room_name(),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -360,6 +360,7 @@ class communication_feature_test extends \advanced_testcase {
|
|||
* @covers ::add_members_to_room
|
||||
* @covers ::add_registered_matrix_user_to_room
|
||||
* @covers ::check_room_membership
|
||||
* @covers ::set_matrix_power_levels
|
||||
*/
|
||||
public function test_add_and_remove_members_from_room(): void {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
@ -396,6 +397,96 @@ class communication_feature_test extends \advanced_testcase {
|
|||
$this->assertContains("@{$user2->username}", $userids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update of room membership.
|
||||
*
|
||||
* @covers ::update_room_membership
|
||||
* @covers ::set_matrix_power_levels
|
||||
* @covers ::is_power_levels_update_required
|
||||
* @covers ::get_user_allowed_power_level
|
||||
*/
|
||||
public function test_update_room_membership(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
global $DB;
|
||||
|
||||
// Create a new room.
|
||||
$course = $this->get_course('Sampleroom', 'none');
|
||||
$user = $this->get_user();
|
||||
|
||||
$communication = $this->create_room(
|
||||
component: 'core_course',
|
||||
itemtype: 'coursecommunication',
|
||||
itemid: $course->id
|
||||
);
|
||||
$provider = $communication->get_room_user_provider();
|
||||
|
||||
// Add the members to the room.
|
||||
$provider->add_members_to_room([$user->id]);
|
||||
|
||||
// Assign teacher role to the user.
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
||||
role_assign($teacherrole->id, $user->id, $coursecontext->id);
|
||||
|
||||
// Test the tasks added as the role is a teacher.
|
||||
$provider->update_room_membership([$user->id]);
|
||||
|
||||
$processor = \core_communication\processor::load_by_instance(
|
||||
component: 'core_course',
|
||||
instancetype: 'coursecommunication',
|
||||
instanceid: $course->id,
|
||||
);
|
||||
$synceduser = $processor->get_instance_userids(
|
||||
synced: true,
|
||||
);
|
||||
$synceduser = reset($synceduser);
|
||||
|
||||
// Test if the communication user record is synced.
|
||||
$this->assertEquals($user->id, $synceduser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the user power level allocation according to context.
|
||||
*
|
||||
* @covers ::get_user_allowed_power_level
|
||||
*/
|
||||
public function test_get_user_allowed_power_level(): void {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
// Create users.
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
|
||||
$course = $this->get_course();
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
|
||||
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
|
||||
// Assign roles.
|
||||
role_assign($teacherrole->id, $user1->id, $coursecontext->id);
|
||||
role_assign($studentrole->id, $user2->id, $coursecontext->id);
|
||||
|
||||
$communicationprocessor = processor::load_by_instance(
|
||||
component: 'core_course',
|
||||
instancetype: 'coursecommunication',
|
||||
instanceid: $course->id
|
||||
);
|
||||
|
||||
// Test if the power level is set according to the context.
|
||||
$this->assertEquals(
|
||||
matrix_constants::POWER_LEVEL_MODERATOR,
|
||||
$communicationprocessor->get_room_provider()->get_user_allowed_power_level($user1->id)
|
||||
);
|
||||
$this->assertEquals(
|
||||
matrix_constants::POWER_LEVEL_DEFAULT,
|
||||
$communicationprocessor->get_room_provider()->get_user_allowed_power_level($user2->id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a room.
|
||||
*
|
||||
|
|
|
@ -25,6 +25,6 @@
|
|||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'communication_matrix';
|
||||
$plugin->version = 2023071900;
|
||||
$plugin->version = 2023090600;
|
||||
$plugin->requires = 2023011300;
|
||||
$plugin->maturity = MATURITY_ALPHA;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue