Merge branch 'MDL-78129-master' of https://github.com/andrewnicols/moodle

This commit is contained in:
Huong Nguyen 2023-09-22 10:29:54 +07:00
commit bd323aede7
No known key found for this signature in database
GPG Key ID: 40D88AB693A3E72A
58 changed files with 945 additions and 145 deletions

View File

@ -32,11 +32,14 @@ use moodle_url;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class manage_communication_providers_page extends admin_setting {
public function __construct() {
$this->nosave = true;
parent::__construct('managecommunications',
new \lang_string('managecommunicationproviders', 'core_communication'), '', '');
parent::__construct(
'managecommunications',
new \lang_string('managecommunicationproviders', 'core_communication'),
'',
''
);
}
public function get_setting(): bool {
@ -71,20 +74,26 @@ class manage_communication_providers_page extends admin_setting {
foreach ($plugins as $plugin) {
$class = '';
$actionurl = new moodle_url('/admin/communication.php', ['sesskey' => sesskey(), 'name' => $plugin->name]);
if ($pluginmanager->get_plugin_info('communication_' . $plugin->name)->get_status() ===
core_plugin_manager::PLUGIN_STATUS_MISSING) {
if (
$pluginmanager->get_plugin_info('communication_' . $plugin->name)->get_status() ===
core_plugin_manager::PLUGIN_STATUS_MISSING
) {
$strtypename = $plugin->displayname . ' (' . get_string('missingfromdisk') . ')';
} else {
$strtypename = $plugin->displayname;
}
if ($plugin->is_enabled()) {
$hideshow = html_writer::link($actionurl->out(false, ['action' => 'disable']),
$OUTPUT->pix_icon('t/hide', get_string('disable'), 'moodle', ['class' => 'iconsmall']));
$hideshow = html_writer::link(
$actionurl->out(false, ['action' => 'disable']),
$OUTPUT->pix_icon('t/hide', get_string('disable'), 'moodle', ['class' => 'iconsmall'])
);
} else {
$class = 'dimmed_text';
$hideshow = html_writer::link($actionurl->out(false, ['action' => 'enable']),
$OUTPUT->pix_icon('t/show', get_string('enable'), 'moodle', ['class' => 'iconsmall']));
$hideshow = html_writer::link(
$actionurl->out(false, ['action' => 'enable']),
$OUTPUT->pix_icon('t/show', get_string('enable'), 'moodle', ['class' => 'iconsmall'])
);
}
$settings = '';
@ -93,8 +102,12 @@ class manage_communication_providers_page extends admin_setting {
}
$uninstall = '';
if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url(
'communication_' . $plugin->name, 'manage')) {
if (
$uninstallurl = core_plugin_manager::instance()->get_uninstall_url(
'communication_' . $plugin->name,
'manage'
)
) {
$uninstall = html_writer::link($uninstallurl, get_string('uninstallplugin', 'core_admin'));
}
@ -114,8 +127,10 @@ class manage_communication_providers_page extends admin_setting {
}
$types = core_plugin_manager::instance()->get_plugins_of_type('communication');
foreach ($types as $type) {
if (strpos($type->component, $query) !== false ||
strpos(core_text::strtolower($type->displayname), $query) !== false) {
if (
strpos($type->component, $query) !== false ||
strpos(core_text::strtolower($type->displayname), $query) !== false
) {
return true;
}
}

View File

@ -21,6 +21,7 @@ use core_communication\task\create_and_configure_room_task;
use core_communication\task\delete_room_task;
use core_communication\task\remove_members_from_room;
use core_communication\task\update_room_task;
use core_communication\task\update_room_membership_task;
use stdClass;
/**
@ -39,7 +40,6 @@ use stdClass;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class api {
/**
* @var null|processor $communication The communication settings object
*/
@ -166,7 +166,7 @@ class api {
$selection[processor::PROVIDER_NONE] = get_string('nocommunicationselected', 'communication');
$communicationplugins = \core\plugininfo\communication::get_enabled_plugins();
foreach ($communicationplugins as $pluginname => $notusing) {
$selection['communication_' . $pluginname] = get_string('pluginname', 'communication_'. $pluginname);
$selection['communication_' . $pluginname] = get_string('pluginname', 'communication_' . $pluginname);
}
return $selection;
}
@ -199,8 +199,7 @@ class api {
): void {
global $PAGE;
list($communicationproviders, $defaulprovider) = self::
get_enabled_providers_and_default($selectdefaultcommunication);
[$communicationproviders, $defaulprovider] = self::get_enabled_providers_and_default($selectdefaultcommunication);
$PAGE->requires->js_call_amd('core_communication/providerchooser', 'init');
@ -216,10 +215,12 @@ class api {
$mform->setDefault('selectedcommunication', $defaulprovider);
$mform->registerNoSubmitButton('updatecommunicationprovider');
$mform->addElement('submit',
$mform->addElement(
'submit',
'updatecommunicationprovider',
'update communication',
['data-communicationchooser-field' => 'updateButton', 'class' => 'd-none',]);
['data-communicationchooser-field' => 'updateButton', 'class' => 'd-none']
);
// Just a placeholder for the communication options.
$mform->addElement('hidden', 'addcommunicationoptionshere');
@ -239,7 +240,9 @@ class api {
$mform->createElement(
'text',
'communicationroomname',
get_string('communicationroomname', 'communication'), 'maxlength="100" size="20"'),
get_string('communicationroomname', 'communication'),
'maxlength="100" size="20"'
),
'addcommunicationoptionshere'
);
$mform->addHelpButton('communicationroomname', 'communicationroomname', 'communication');
@ -247,7 +250,6 @@ class api {
processor::set_provider_specific_form_definition($provider, $mform);
}
}
/**
@ -529,6 +531,34 @@ class api {
}
}
/**
* Create a communication ad-hoc task for updating members operation and update the user mapping.
*
* This method will add a task to the queue to update the room users.
*
* @param array $userids The user ids to add to the room
* @param bool $queue Whether to queue the task or not
*/
public function update_room_membership(array $userids, bool $queue = true): void {
// No communication object? something not done right.
if (!$this->communication) {
return;
}
// No userids? don't bother doing anything.
if (empty($userids)) {
return;
}
$this->communication->reset_users_sync_flag($userids);
if ($queue) {
update_room_membership_task::queue(
$this->communication
);
}
}
/**
* Create a communication ad-hoc task for remove members operation or action immediately.
*
@ -582,7 +612,6 @@ class api {
switch ($roomstatus) {
case 'pending':
\core\notification::add($message, \core\notification::INFO);
break;

View File

@ -28,7 +28,6 @@ namespace core_communication;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface communication_provider {
/**
* A base communication provider.
*

View File

@ -26,13 +26,12 @@ namespace core_communication\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
require_once($CFG->libdir . '/formslib.php');
/**
* Defines the configure communication form.
*/
class configure_form extends \moodleform {
/**
* @var \core_communication\api $communication The communication api object.
*/

View File

@ -26,7 +26,6 @@ namespace core_communication;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface form_provider {
/**
* Set the form data to the instance if any data is available.
*

View File

@ -29,10 +29,10 @@ use core_privacy\local\request\userlist;
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\provider,
\core_privacy\local\request\core_userlist_provider {
class provider implements
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\metadata\provider,
\core_privacy\local\request\subsystem\provider {
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('communication_user', [

View File

@ -29,7 +29,6 @@ use stored_file;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class processor {
/** @var string The magic 'none' provider */
public const PROVIDER_NONE = 'none';
@ -171,6 +170,21 @@ class processor {
);
}
/**
* Get all the user ids flagged as deleted.
*
* @return array
*/
public function get_all_delete_flagged_userids(): array {
global $DB;
return $DB->get_fieldset_select(
'communication_user',
'userid',
'commid = ? AND deleted = ?',
[$this->instancedata->id, 1]
);
}
/**
* Create communication user record for mapping and sync.
*
@ -309,7 +323,7 @@ class processor {
$DB->delete_records_select(
'communication_user',
'commid = ? AND userid IN (' . implode(',', $userids) . ') AND synced = ?' ,
'commid = ? AND userid IN (' . implode(',', $userids) . ') AND synced = ?',
[$this->instancedata->id, 0]
);
}
@ -400,6 +414,15 @@ class processor {
/**
* Get communication instance id.
*
* @return int
*/
public function get_instance_id(): int {
return $this->instancedata->instanceid;
}
/**
* Get communication instance component.
*
* @return string
*/
public function get_component(): string {

View File

@ -26,7 +26,6 @@ namespace core_communication;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface room_chat_provider {
/**
* Create a provider room when a instance is created.
*/

View File

@ -31,6 +31,13 @@ interface room_user_provider {
*/
public function add_members_to_room(array $userids): void;
/**
* Update room membership for the communication room.
*
* @param array $userids The user ids to be updated
*/
public function update_room_membership(array $userids): void;
/**
* Remove members from room.
*

View File

@ -27,7 +27,6 @@ use core_communication\processor;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add_members_to_room_task extends adhoc_task {
public function execute() {
// Initialize the custom data operation to be used for the action.
$data = $this->get_custom_data();
@ -55,7 +54,7 @@ class add_members_to_room_task extends adhoc_task {
// Add ad-hoc task to update the provider room.
$task = new self();
$task->set_custom_data([
'id' => $communication->get_id()
'id' => $communication->get_id(),
]);
// Queue the task for the next run.

View File

@ -29,7 +29,6 @@ use core_communication\processor;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class create_and_configure_room_task extends adhoc_task {
public function execute() {
$data = $this->get_custom_data();
@ -66,7 +65,7 @@ class create_and_configure_room_task extends adhoc_task {
// Add ad-hoc task to update the provider room.
$task = new self();
$task->set_custom_data([
'id' => $communication->get_id()
'id' => $communication->get_id(),
]);
// Queue the task for the next run.

View File

@ -29,7 +29,6 @@ use core_communication\processor;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_room_task extends adhoc_task {
public function execute() {
$data = $this->get_custom_data();
@ -63,7 +62,7 @@ class delete_room_task extends adhoc_task {
// Add ad-hoc task to update the provider room.
$task = new self();
$task->set_custom_data([
'id' => $communication->get_id()
'id' => $communication->get_id(),
]);
// Queue the task for the next run.

View File

@ -27,7 +27,6 @@ use core_communication\processor;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class remove_members_from_room extends adhoc_task {
public function execute() {
// Initialize the custom data operation to be used for the action.
$data = $this->get_custom_data();
@ -40,7 +39,7 @@ class remove_members_from_room extends adhoc_task {
return;
}
$communication->get_room_user_provider()->remove_members_from_room($communication->get_instance_userids(true, true));
$communication->get_room_user_provider()->remove_members_from_room($communication->get_all_delete_flagged_userids());
// Now remove any mapping for users who are not in the room.
$communication->delete_instance_non_synced_user_mapping($communication->get_instance_userids(false, true));
@ -58,7 +57,7 @@ class remove_members_from_room extends adhoc_task {
// Add ad-hoc task to update the provider room.
$task = new self();
$task->set_custom_data([
'id' => $communication->get_id()
'id' => $communication->get_id(),
]);
// Queue the task for the next run.

View File

@ -0,0 +1,63 @@
<?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 core_communication\task;
use core\task\adhoc_task;
use core_communication\processor;
/**
* Class update_room_membership_task to add the task to update members for the room and execute the task to action the addition.
*
* @package core_communication
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class update_room_membership_task extends adhoc_task {
public function execute() {
// Initialize the custom data operation to be used for the action.
$data = $this->get_custom_data();
// Call the communication api to action the operation.
$communication = processor::load_by_id($data->id);
if ($communication === null) {
mtrace("Skipping room creation because the instance does not exist");
return;
}
$communication->get_room_user_provider()->update_room_membership($communication->get_instance_userids());
}
/**
* Queue the task for the next run.
*
* @param processor $communication The communication processor to perform the action on
*/
public static function queue(
processor $communication
): void {
// Add ad-hoc task to update the provider room.
$task = new self();
$task->set_custom_data([
'id' => $communication->get_id(),
]);
// Queue the task for the next run.
\core\task\manager::queue_adhoc_task($task);
}
}

View File

@ -29,7 +29,6 @@ use core_communication\processor;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class update_room_task extends adhoc_task {
public function execute() {
$data = $this->get_custom_data();
@ -56,7 +55,7 @@ class update_room_task extends adhoc_task {
// Add ad-hoc task to update the provider room.
$task = new self();
$task->set_custom_data([
'id' => $communication->get_id()
'id' => $communication->get_id(),
]);
// Queue the task for the next run.

View File

@ -24,7 +24,6 @@ namespace core_communication;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface user_provider {
/**
* Create members.
*

View File

@ -52,7 +52,7 @@ if (!$communication) {
}
// Set variables according to the component callback and use them on the page.
list($instance, $context, $heading, $returnurl) = component_callback(
[$instance, $context, $heading, $returnurl] = component_callback(
$component,
'get_communication_instance_data',
[$instanceid]
@ -79,11 +79,8 @@ $form = new \core_communication\form\configure_form(
if ($form->is_cancelled()) {
redirect($returnurl);
} else if ($data = $form->get_data()) {
component_callback($component, 'update_communication_instance_data', [$data]);
redirect($returnurl);
}

View File

@ -27,9 +27,8 @@ use core_communication\processor;
*/
class communication_feature implements
\core_communication\communication_provider,
\core_communication\room_chat_provider,
\core_communication\form_provider {
\core_communication\form_provider,
\core_communication\room_chat_provider {
/** @var string The database table storing custom link specific data */
protected const CUSTOMLINK_TABLE = 'communication_customlink';
@ -132,7 +131,6 @@ class communication_feature implements
// Create the record if it does not exist.
$newrecord->commid = $commid;
$DB->insert_record(self::CUSTOMLINK_TABLE, $newrecord);
} else if ($newrecord->url !== $existingrecord->url) {
// Update record if the URL has changed.
$newrecord->id = $existingrecord->id;
@ -154,15 +152,22 @@ class communication_feature implements
public static function set_form_definition(\MoodleQuickForm $mform): void {
// Custom link description for the communication provider.
$mform->insertElementBefore($mform->createElement('text', 'customlinkurl',
$mform->insertElementBefore($mform->createElement(
'text',
'customlinkurl',
get_string('customlinkurl', 'communication_customlink'),
'maxlength="255" size="40"'), 'addcommunicationoptionshere');
'maxlength="255" size="40"'
), 'addcommunicationoptionshere');
$mform->addHelpButton('customlinkurl', 'customlinkurl', 'communication_customlink');
$mform->setType('customlinkurl', PARAM_URL);
$mform->addRule('customlinkurl', get_string('required'), 'required', null, 'client');
$mform->addRule('customlinkurl', get_string('maximumchars', '', 255), 'maxlength', 255);
$mform->insertElementBefore($mform->createElement('static', 'customlinkurlinfo', '',
$mform->insertElementBefore($mform->createElement(
'static',
'customlinkurlinfo',
'',
get_string('customlinkurlinfo', 'communication_customlink'),
'addcommunicationoptionshere'), 'addcommunicationoptionshere');
'addcommunicationoptionshere'
), 'addcommunicationoptionshere');
}
}

View File

@ -26,7 +26,6 @@ use core_privacy\local\metadata\null_provider;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.

View File

@ -33,7 +33,6 @@ require_once(__DIR__ . '/../../../tests/communication_test_helper_trait.php');
* @coversDefaultClass \communication_customlink\communication_feature
*/
class communication_feature_test extends \advanced_testcase {
use communication_test_helper_trait;
public function setUp(): void {

View File

@ -45,12 +45,11 @@ use GuzzleHttp\Psr7\Response;
*/
class communication_feature implements
\core_communication\communication_provider,
\core_communication\user_provider,
\core_communication\form_provider,
\core_communication\room_chat_provider,
\core_communication\room_user_provider,
\core_communication\form_provider {
/** @var matrix_room $room The matrix room object to update room information */
\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 */
@ -127,10 +126,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());
}
return $this->room;
}
@ -166,10 +162,10 @@ class communication_feature implements
$response = $this->matrixapi->create_user(
userid: $qualifiedmuid,
displayname: $userfullname,
threepids: [(object)[
threepids: [(object) [
'medium' => 'email',
'address' => $user->email
]],
'address' => $user->email,
], ],
externalids: [],
);
$body = json_decode($response->getBody());
@ -184,10 +180,19 @@ 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();
}
/**
* Add members to a room.
*
@ -209,6 +214,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);
@ -229,8 +239,8 @@ class communication_feature implements
if (!$this->check_room_membership($matrixuserid)) {
$response = $this->matrixapi->invite_member_to_room(
$this->get_room_id(),
$matrixuserid,
roomid: $this->get_room_id(),
userid: $matrixuserid,
);
$body = self::get_body($response);
@ -256,25 +266,41 @@ 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 = [];
$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);
// Check if user is the room admin and halt removal of this user.
$response = $this->matrixapi->get_room_info($this->get_room_id());
$matrixroomdata = self::get_body($response);
$roomadmin = $matrixroomdata->creator;
$isadmin = $matrixuserid === $roomadmin;
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 (
!$isadmin && $matrixuserid && $this->check_user_exists($matrixuserid) &&
$this->check_user_exists($matrixuserid) &&
$this->check_room_membership($matrixuserid)
) {
$this->matrixapi->remove_member_from_room(
$this->get_room_id(),
$matrixuserid,
roomid: $this->get_room_id(),
userid: $matrixuserid,
);
$membersremoved[] = $userid;
@ -295,7 +321,9 @@ class communication_feature implements
// This API requires the get_user_info feature.
$this->matrixapi->require_feature(get_user_info_feature::class);
$response = $this->matrixapi->get_user_info($matrixuserid);
$response = $this->matrixapi->get_user_info(
userid: $matrixuserid,
);
$body = self::get_body($response);
return isset($body->name);
@ -312,7 +340,9 @@ class communication_feature implements
// This API requires the get_room_members feature.
$this->matrixapi->require_feature(get_room_members_feature::class);
$response = $this->matrixapi->get_room_members($this->get_room_id());
$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.
@ -376,12 +406,17 @@ class communication_feature implements
]);
// Get room data.
$response = $this->matrixapi->get_room_info($this->get_room_id());
$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($this->get_room_id(), $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.
@ -431,12 +466,18 @@ class communication_feature implements
$contenturi = self::get_body($response)->content_uri;
// Now update the room avatar.
$response = $this->matrixapi->update_room_avatar($this->get_room_id(), $contenturi);
$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($this->get_room_id(), null);
$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.
@ -449,7 +490,10 @@ class communication_feature implements
}
// Now update the room avatar.
$response = $this->matrixapi->update_room_avatar($this->get_room_id(), $contenturi);
$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.
@ -497,9 +541,12 @@ class communication_feature implements
public static function set_form_definition(\MoodleQuickForm $mform): void {
// Room description for the communication provider.
$mform->insertElementBefore($mform->createElement('text', 'matrixroomtopic',
$mform->insertElementBefore($mform->createElement(
'text',
'matrixroomtopic',
get_string('matrixroomtopic', 'communication_matrix'),
'maxlength="255" size="20"'), 'addcommunicationoptionshere');
'maxlength="255" size="20"'
), 'addcommunicationoptionshere');
$mform->addHelpButton('matrixroomtopic', 'matrixroomtopic', 'communication_matrix');
$mform->setType('matrixroomtopic', PARAM_TEXT);
}
@ -515,4 +562,193 @@ class communication_feature implements
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 {
$context = \core\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_MOODLE_MODERATOR;
}
// If site admin, override all caps.
if (is_siteadmin($userid)) {
$powerlevel = matrix_constants::POWER_LEVEL_MOODLE_SITE_ADMIN;
}
return $powerlevel;
}
}

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait create_room_v3 {
/**
* Create a new room.
*

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait get_room_members_v3 {
/**
* Get a list of room members.
*

View File

@ -0,0 +1,91 @@
<?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 GuzzleHttp\Psr7\Response;
/**
* Matrix API feature to fetch room power levels using the sync API.
*
* https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3sync
*
* @package communication_matrix
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
* @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 get_room_powerlevels_from_sync_v3 {
/**
* Get a list of room members.
*
* @param string $roomid The room ID
* @return Response
*/
public function get_room_power_levels(string $roomid): Response {
// Filter the event data according to the API:
// https://spec.matrix.org/v1.1/client-server-api/#filtering
// We have to filter out all of the object data that we do not want,
// and set a filter to only fetch the one room that we do want.
$filter = (object) [
"account_data" => (object) [
// We don't want any account info for this call.
"not_types" => ['*'],
],
"event_fields" => [
// We only care about type, and content. Not sender.
"type",
"content",
],
"event_format" => "client",
"presence" => (object) [
// We don't need any presence data.
"not_types" => ['*'],
],
"room" => (object) [
// We only want state information for power levels, not timeline and ephemeral data.
"rooms" => [
$roomid,
],
"state" => (object) [
"types" => [
"m.room.power_levels",
],
],
"ephemeral" => (object) [
"not_types" => ['*'],
],
"timeline" => (object) [
"not_types" => ['*'],
],
],
];
$query = [
'filter' => json_encode($filter),
];
return $this->execute(new command(
$this,
method: 'GET',
endpoint: '_matrix/client/v3/sync',
query: $query,
sendasjson: false,
));
}
}

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait media_create_v1 {
/**
* Create a media URI.
*

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait remove_member_from_room_v3 {
/**
* Remove a member from a room.
*

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait update_room_avatar_v3 {
/**
* Set the avatar for a room to the specified URL.
*
@ -56,5 +55,4 @@ trait update_room_avatar_v3 {
params: $params,
));
}
}

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait update_room_name_v3 {
/**
* Set the name for a room.
*

View File

@ -0,0 +1,77 @@
<?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,
));
}
}

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait update_room_topic_v3 {
/**
* Set the topic for a room.
*
@ -51,6 +50,5 @@ trait update_room_topic_v3 {
endpoint: '_matrix/client/v3/rooms/:roomid/state/m.room.topic',
params: $params,
));
}
}

View File

@ -32,7 +32,6 @@ use GuzzleHttp\Psr7\Utils;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait upload_content_v3 {
/**
* Upload the content in the matrix/synapse server.
*
@ -60,7 +59,7 @@ trait upload_content_v3 {
sendasjson: false,
query: $query,
params: [
'mediaid' => $mediaid,
':mediaid' => $mediaid,
],
);
} else {

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait create_user_v2 {
/**
* Create a new user.
*
@ -63,5 +62,4 @@ trait create_user_v2 {
params: $params,
));
}
}

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait get_room_info_v1 {
/**
* Get room info.
*

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait get_user_info_v2 {
/**
* Get user info.
*
@ -49,5 +48,4 @@ trait get_user_info_v2 {
],
));
}
}

View File

@ -31,7 +31,6 @@ use GuzzleHttp\Psr7\Response;
* This code does not warrant being tested. Testing offers no discernible benefit given its usage is tested.
*/
trait invite_member_to_room_v1 {
/**
* Join a user to a room.
*

View File

@ -34,6 +34,8 @@ 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;
use features\matrix\get_room_powerlevels_from_sync_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;

View File

@ -30,6 +30,5 @@ class v1p7 extends v1p6 {
// Note: A new Content Upload API was introduced.
// See details in the spec:
// https://github.com/matrix-org/matrix-spec-proposals/pull/2246.
use features\matrix\media_create_v1;
}

View File

@ -36,7 +36,6 @@ use GuzzleHttp\Psr7\Response;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class matrix_client {
/** @var string $serverurl The URL of the home server */
/** @var string $accesstoken The access token of the matrix server */

View File

@ -0,0 +1,51 @@
<?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 moderator power level for matrix.
*/
public const POWER_LEVEL_MOODLE_MODERATOR = 51;
/**
* 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;
}

View File

@ -26,7 +26,6 @@ use stdClass;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class matrix_room {
private const TABLE = 'matrix_room';
/** @var \stdClass|null $record The matrix room record from db */

View File

@ -24,7 +24,6 @@ namespace communication_matrix;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class matrix_user_manager {
/**
* Gets matrix user id from moodle.
*
@ -33,7 +32,7 @@ class matrix_user_manager {
*/
public static function get_matrixid_from_moodle(
int $userid,
) : ?string {
): ?string {
self::load_requirements();
$field = profile_user_record($userid);
$matrixprofilefield = get_config('communication_matrix', 'matrixuserid_field');
@ -141,7 +140,7 @@ class matrix_user_manager {
// Check if matrixuserid exists in user_info_field table.
$matrixuserid = $DB->count_records('user_info_field', [
'shortname' => 'matrixuserid',
'categoryid' => $categoryid
'categoryid' => $categoryid,
]);
if ($matrixuserid < 1) {
@ -158,7 +157,7 @@ class matrix_user_manager {
'visible' => 0,
'locked' => 1,
'param1' => 30,
'param2' => 2048
'param2' => 2048,
];
$profileclass->define_save($data);

View File

@ -27,7 +27,6 @@ use core_privacy\local\metadata\null_provider;
* @codeCoverageIgnore
*/
class provider implements null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.

View 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,
],
],
];

View File

@ -52,5 +52,4 @@ function xmldb_communication_matrix_upgrade($oldversion) {
}
return true;
}

View File

@ -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.';

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use communication_matrix\matrix_test_helper_trait;
use Moodle\BehatExtension\Exception\SkippedException;
@ -31,7 +33,6 @@ require_once(__DIR__ . '/../../../../tests/communication_test_helper_trait.php')
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_communication_matrix extends \behat_base {
use \core_communication\communication_test_helper_trait;
use matrix_test_helper_trait;

View File

@ -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();
@ -32,6 +33,7 @@ require_once(__DIR__ . '/../../../tests/communication_test_helper_trait.php');
* @category test
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \communication_matrix\communication_feature
* @coversDefaultClass \communication_matrix\communication_feature
*/
class communication_feature_test extends \advanced_testcase {
@ -66,6 +68,7 @@ class communication_feature_test extends \advanced_testcase {
],
);
// phpcs:ignore moodle.Commenting.InlineComment.DocBlock
/** @var communication_feature */
$provider = $communication->get_room_provider();
$this->assertInstanceOf(
@ -124,6 +127,7 @@ class communication_feature_test extends \advanced_testcase {
roomtopic: 'Our room topic',
);
// phpcs:ignore moodle.Commenting.InlineComment.DocBlock
/** @var communication_feature */
$provider = $communication->get_room_provider();
$this->assertInstanceOf(
@ -191,7 +195,6 @@ class communication_feature_test extends \advanced_testcase {
'Our updated room name',
$communication->get_room_name(),
);
}
/**
@ -360,8 +363,11 @@ 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 {
$this->markTestSkipped('Skipping while we update the Mock Server with the new route');
$user = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
@ -396,6 +402,98 @@ 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->markTestSkipped('Skipping while we update the Mock Server with the new route');
$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_MOODLE_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.
*

View File

@ -266,7 +266,7 @@ class command_test extends \advanced_testcase {
mock: $mock,
);
$mock->append(function(Request $request) use ($expected): Response {
$mock->append(function (Request $request) use ($expected): Response {
$this->assertSame(
$expected,
$request->getUri()->getQuery(),

View File

@ -301,7 +301,7 @@ class matrix_client_test extends \advanced_testcase {
*/
public function require_features_provider(): array {
// We'll just add to the standard testcases.
$testcases = array_map(static function(array $testcase): array {
$testcases = array_map(static function (array $testcase): array {
$testcase[1] = [$testcase[1]];
return $testcase;
}, $this->implements_feature_provider());

View File

@ -47,7 +47,7 @@ trait matrix_client_test_trait {
mocked_matrix_client::reset_client();
}
public function tearDown():void {
public function tearDown(): void {
parent::tearDown();
// Reset the test client.

View File

@ -26,7 +26,6 @@ namespace communication_matrix;
* @coversDefaultClass \communication_matrix\matrix_room
*/
class matrix_room_test extends \advanced_testcase {
/**
* Test for load_by_processor_id with no record.
*
@ -84,7 +83,6 @@ class matrix_room_test extends \advanced_testcase {
$this->assertEquals(54321, $reloadedroom->get_processor_id());
$this->assertEquals('The topic of this room is thusly', $reloadedroom->get_topic());
$this->assertEquals('This is a roomid', $reloadedroom->get_room_id());
}
/**

View File

@ -26,7 +26,6 @@ use GuzzleHttp\Psr7\Response;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait matrix_test_helper_trait {
/**
* @var string $accesstoken The token for matrix connection
*/
@ -46,7 +45,7 @@ trait matrix_test_helper_trait {
$this->matrixhomeserverurl = TEST_COMMUNICATION_MATRIX_MOCK_SERVER;
set_config('matrixhomeserverurl', $this->matrixhomeserverurl, 'communication_matrix');
$request = $this->request();
$response = $request->post($this->matrixhomeserverurl. '/backoffice/create-admin');
$response = $request->post($this->matrixhomeserverurl . '/backoffice/create-admin');
$admindata = json_decode($response->getBody());
$json = [
'identifier' => [
@ -57,7 +56,7 @@ trait matrix_test_helper_trait {
'password' => $admindata->password,
];
$request = $this->request($json);
$response = $request->post($this->matrixhomeserverurl. '/_matrix/client/r0/login');
$response = $request->post($this->matrixhomeserverurl . '/_matrix/client/r0/login');
$response = json_decode($response->getBody());
if (empty($response->access_token)) {
$this->markTestSkipped(
@ -161,10 +160,15 @@ trait matrix_test_helper_trait {
array $rooms = [],
): Response {
$client = new \core\http_client();
return $client->put($this->get_backoffice_uri('create'), ['json' => [
return $client->put(
$this->get_backoffice_uri('create'),
[
'json' => [
'users' => $users,
'rooms' => $rooms,
]]);
],
],
);
}
/**
@ -225,7 +229,7 @@ trait matrix_test_helper_trait {
public function reset_mock(): void {
if (defined('TEST_COMMUNICATION_MATRIX_MOCK_SERVER')) {
$request = $this->request();
$response = $request->post(TEST_COMMUNICATION_MATRIX_MOCK_SERVER. '/backoffice/reset');
$response = $request->post(TEST_COMMUNICATION_MATRIX_MOCK_SERVER . '/backoffice/reset');
$response = json_decode($response->getBody());
if (empty($response->reset)) {
$this->markTestSkipped(

View File

@ -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;

View File

@ -30,7 +30,6 @@ require_once(__DIR__ . '/communication_test_helper_trait.php');
* @covers \core_communication\api
*/
class api_test extends \advanced_testcase {
use communication_test_helper_trait;
public function setUp(): void {
@ -251,9 +250,12 @@ class api_test extends \advanced_testcase {
}
/**
* Test the update_room_membership for adding adn removing members.
* Test the adding and removing of members from room.
*
* @covers ::add_members_to_room
* @covers ::remove_members_from_room
*/
public function test_update_room_membership(): void {
public function test_adding_and_removing_of_room_membership(): void {
$course = $this->get_course();
$userid = $this->get_user()->id;
@ -281,11 +283,40 @@ class api_test extends \advanced_testcase {
* Test the enabled communication plugin list and default.
*/
public function test_get_enabled_providers_and_default(): void {
list($communicationproviders, $defaulprovider) = \core_communication\api::get_enabled_providers_and_default();
[$communicationproviders, $defaulprovider] = \core_communication\api::get_enabled_providers_and_default();
// Get the communication plugins.
$plugins = \core_component::get_plugin_list('communication');
// Check the number of plugins matches plus 1 as we have none in the selection.
$this->assertCount(count($plugins) + 1, $communicationproviders);
$this->assertEquals(processor::PROVIDER_NONE, $defaulprovider);
}
/**
* Test the update of room membership with the change user role.
*
* @covers ::update_room_membership
*/
public function test_update_room_membership_on_user_role_change(): void {
global $DB;
// Generate the data.
$user = $this->getDataGenerator()->create_user();
$course = $this->get_course();
$coursecontext = \context_course::instance($course->id);
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$adhoctask = \core\task\manager::get_adhoc_tasks('\\core_communication\\task\\add_members_to_room_task');
$this->assertCount(1, $adhoctask);
$adhoctask = reset($adhoctask);
$this->assertInstanceOf('\\core_communication\\task\\add_members_to_room_task', $adhoctask);
// Test the tasks added as the role is a teacher.
$adhoctask = \core\task\manager::get_adhoc_tasks('\\core_communication\\task\\update_room_membership_task');
$this->assertCount(1, $adhoctask);
$adhoctask = reset($adhoctask);
$this->assertInstanceOf('\\core_communication\\task\\update_room_membership_task', $adhoctask);
}
}

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../tests/communication_test_helper_trait.php');
@ -26,7 +28,6 @@ require_once(__DIR__ . '/../../tests/communication_test_helper_trait.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_communication extends \behat_base {
use \core_communication\communication_test_helper_trait;
/**

View File

@ -25,7 +25,6 @@ namespace core_communication;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait communication_test_helper_trait {
/**
* Setup necessary configs for communication subsystem.
*
@ -83,7 +82,7 @@ trait communication_test_helper_trait {
$records = [
'firstname' => $firstname,
'lastname' => $lastname,
'username' => $username
'username' => $username,
];
return $this->getDataGenerator()->create_user($records);

View File

@ -30,7 +30,6 @@ require_once(__DIR__ . '/communication_test_helper_trait.php');
* @coversDefaultClass \core_communication\processor
*/
class processor_test extends \advanced_testcase {
use communication_test_helper_trait;
/**
@ -61,8 +60,10 @@ class processor_test extends \advanced_testcase {
);
// Now test the record against the database.
$communicationrecord = $DB->get_record('communication',
['instanceid' => $instanceid, 'component' => $component, 'instancetype' => $instancetype]);
$communicationrecord = $DB->get_record(
'communication',
['instanceid' => $instanceid, 'component' => $component, 'instancetype' => $instancetype]
);
// Test against the set data.
$this->assertNotEmpty($communicationrecord);
@ -114,7 +115,7 @@ class processor_test extends \advanced_testcase {
$communicationrecord = $DB->get_record('communication', [
'instanceid' => $instanceid,
'component' => $component,
'instancetype' => $instancetype
'instancetype' => $instancetype,
]);
// Test against the set data.
@ -163,7 +164,7 @@ class processor_test extends \advanced_testcase {
$communicationrecord = $DB->get_record('communication', [
'instanceid' => $instanceid,
'component' => $component,
'instancetype' => $instancetype
'instancetype' => $instancetype,
]);
// Test against the set data.
@ -173,7 +174,8 @@ class processor_test extends \advanced_testcase {
$communicationprocessor = processor::load_by_instance(
$component,
$instancetype,
$instanceid);
$instanceid
);
$this->assertNull($communicationprocessor);
}
@ -428,4 +430,44 @@ class processor_test extends \advanced_testcase {
set_config('disabled', 1, $communicationprovider);
$this->assertFalse(processor::is_provider_enabled($communicationprovider));
}
/**
* Test delete flagged user id's return correct users.
*
* @covers ::get_all_delete_flagged_userids
*/
public function test_get_all_delete_flagged_userids(): void {
$this->resetAfterTest();
$course = $this->get_course('Sampleroom', 'none');
$user1 = $this->getDataGenerator()->create_user()->id;
$user2 = $this->getDataGenerator()->create_user()->id;
// Sample data.
$communicationroomname = 'Sampleroom';
$selectedcommunication = 'communication_matrix';
$component = 'core_course';
$instancetype = 'coursecommunication';
// Load the communication api.
$communication = \core_communication\api::load_by_instance(
'core_course',
'coursecommunication',
$course->id
);
$communication->create_and_configure_room($selectedcommunication, $communicationroomname);
$communication->add_members_to_room([$user1, $user2]);
// Now remove user1 from the room.
$communication->remove_members_from_room([$user1]);
// Test against the object.
$communicationprocessor = processor::load_by_instance(
$component,
$instancetype,
$course->id
);
$this->assertEquals([$user1], $communicationprocessor->get_all_delete_flagged_userids());
}
}

View File

@ -1645,6 +1645,17 @@ function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0,
core_course_category::role_assignment_changed($roleid, $context);
// Update the room membership and power levels when the user role changes.
if (\core_communication\api::is_available() && $coursecontext = $context->get_course_context(false)) {
$communication = \core_communication\api::load_by_instance(
'core_course',
'coursecommunication',
$coursecontext->instanceid,
);
$communication->update_room_membership([$userid]);
}
$event = \core\event\role_assigned::create(array(
'context' => $context,
'objectid' => $ra->roleid,
@ -1750,6 +1761,19 @@ function role_unassign_all(array $params, $subcontexts = false, $includemanual =
$event->add_record_snapshot('role_assignments', $ra);
$event->trigger();
core_course_category::role_assignment_changed($ra->roleid, $context);
// Update the room membership and power levels when the user role changes.
if (\core_communication\api::is_available() && $coursecontext = $context->get_course_context(false)) {
$communication = \core_communication\api::load_by_instance(
'core_course',
'coursecommunication',
$coursecontext->instanceid,
);
$communication->update_room_membership([$ra->userid]);
}
}
}
unset($ras);