MDL-78551 core_communication: Add hook listeners
This commit is contained in:
+167
-10
@@ -118,9 +118,9 @@ class api {
|
||||
/**
|
||||
* Return the underlying communication processor object.
|
||||
*
|
||||
* @return processor
|
||||
* @return ?processor
|
||||
*/
|
||||
public function get_processor(): processor {
|
||||
public function get_processor(): ?processor {
|
||||
return $this->communication;
|
||||
}
|
||||
|
||||
@@ -405,6 +405,114 @@ class api {
|
||||
return $this->communication->get_provider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the room and membership by provider selected for the communication instance.
|
||||
*
|
||||
* This method will add a task to the queue to configure the room and membership by comparing the change of provider.
|
||||
* There are some major cases to consider for this method to allow minimum duplication when this api is used.
|
||||
* Some of the major cases are:
|
||||
* 1. If the communication instance is not created at all, then create it and add members.
|
||||
* 2. If the current provider is none and the new provider is also none, then nothing to do.
|
||||
* 3. If the current and existing provider is the same, don't need to do anything.
|
||||
* 4. If provider set to none, remove all the members.
|
||||
* 5. If previous provider was not none and current provider is not none, but a different provider, remove members and add
|
||||
* for the new one.
|
||||
* 6. If previous provider was none and current provider is not none, don't need to remove, just
|
||||
* update the selected provider and add users to that provider. Do not queue the task to add members to room as the room
|
||||
* might not have created yet. The add room task adds the task to add members to room anyway.
|
||||
* 7. If it's a new provider, never used/created, now create the room after considering all these cases for a new provider.
|
||||
*
|
||||
* @param string $provider The provider name
|
||||
* @param \stdClass $instance The instance object
|
||||
* @param string $communicationroomname The communication room name
|
||||
* @param array $users The user ids to add to the room
|
||||
* @param null|\stored_file $instanceimage The stored file for the avatar
|
||||
*/
|
||||
public function configure_room_and_membership_by_provider(
|
||||
string $provider,
|
||||
stdClass $instance,
|
||||
string $communicationroomname,
|
||||
array $users,
|
||||
?\stored_file $instanceimage = null,
|
||||
): void {
|
||||
// If the current provider is inactive and the new provider is also none, then nothing to do.
|
||||
if (
|
||||
$this->communication !== null &&
|
||||
$this->communication->get_provider_status() === processor::PROVIDER_INACTIVE &&
|
||||
$provider === processor::PROVIDER_NONE
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If provider set to none, remove all the members.
|
||||
if (
|
||||
$this->communication !== null &&
|
||||
$this->communication->get_provider_status() === processor::PROVIDER_ACTIVE &&
|
||||
$provider === processor::PROVIDER_NONE
|
||||
) {
|
||||
$this->remove_all_members_from_room();
|
||||
$this->update_room(
|
||||
active: processor::PROVIDER_INACTIVE,
|
||||
communicationroomname: $communicationroomname,
|
||||
avatar: $instanceimage,
|
||||
instance: $instance,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
// If previous provider was active and not none and current provider is not none, but a different provider,
|
||||
// remove members and de-activate the previous provider.
|
||||
$this->communication !== null &&
|
||||
$this->communication->get_provider_status() === processor::PROVIDER_ACTIVE &&
|
||||
$provider !== $this->get_provider()
|
||||
) {
|
||||
$this->remove_all_members_from_room();
|
||||
// Now deactivate the previous provider.
|
||||
$this->update_room(
|
||||
active: processor::PROVIDER_INACTIVE,
|
||||
communicationroomname: $communicationroomname,
|
||||
avatar: $instanceimage,
|
||||
instance: $instance,
|
||||
);
|
||||
}
|
||||
|
||||
// Now re-init the constructor for the new provider.
|
||||
$this->__construct(
|
||||
context: $this->context,
|
||||
component: $this->component,
|
||||
instancetype: $this->instancetype,
|
||||
instanceid: $this->instanceid,
|
||||
provider: $provider,
|
||||
);
|
||||
|
||||
// If it's a new provider, never used/created, now create the room.
|
||||
if ($this->communication === null) {
|
||||
$this->create_and_configure_room(
|
||||
communicationroomname: $communicationroomname,
|
||||
avatar: $instanceimage,
|
||||
instance: $instance,
|
||||
);
|
||||
$queue = false;
|
||||
} else {
|
||||
// Otherwise update the room.
|
||||
$this->update_room(
|
||||
active: processor::PROVIDER_ACTIVE,
|
||||
communicationroomname: $communicationroomname,
|
||||
avatar: $instanceimage,
|
||||
instance: $instance,
|
||||
);
|
||||
$queue = true;
|
||||
}
|
||||
|
||||
// Now add the members.
|
||||
$this->add_members_to_room(
|
||||
userids: $users,
|
||||
queue: $queue,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a communication ad-hoc task for create operation.
|
||||
* This method will add a task to the queue to create the room.
|
||||
@@ -412,16 +520,17 @@ class api {
|
||||
* @param string $communicationroomname The communication room name
|
||||
* @param null|\stored_file $avatar The stored file for the avatar
|
||||
* @param \stdClass|null $instance The actual instance object
|
||||
* @param bool $queue Whether to queue the task or not
|
||||
*/
|
||||
public function create_and_configure_room(
|
||||
string $communicationroomname,
|
||||
?\stored_file $avatar = null,
|
||||
?\stdClass $instance = null,
|
||||
bool $queue = true,
|
||||
): void {
|
||||
if ($this->provider === processor::PROVIDER_NONE || $this->provider === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create communication record.
|
||||
$this->communication = processor::create_instance(
|
||||
context: $this->context,
|
||||
@@ -442,6 +551,11 @@ class api {
|
||||
$this->set_avatar($avatar);
|
||||
}
|
||||
|
||||
// Nothing else to do if the queue is false.
|
||||
if (!$queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add ad-hoc task to create the provider room.
|
||||
create_and_configure_room_task::queue(
|
||||
$this->communication,
|
||||
@@ -456,13 +570,19 @@ class api {
|
||||
* @param null|string $communicationroomname The communication room name
|
||||
* @param null|\stored_file $avatar The stored file for the avatar
|
||||
* @param \stdClass|null $instance The actual instance object
|
||||
* @param bool $queue Whether to queue the task or not
|
||||
*/
|
||||
public function update_room(
|
||||
?int $active = null,
|
||||
?string $communicationroomname = null,
|
||||
?\stored_file $avatar = null,
|
||||
?\stdClass $instance = null,
|
||||
bool $queue = true,
|
||||
): void {
|
||||
if (!$this->communication) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the provider is none, we don't need to do anything from room point of view.
|
||||
if ($this->communication->get_provider() === processor::PROVIDER_NONE) {
|
||||
return;
|
||||
@@ -503,6 +623,11 @@ class api {
|
||||
// If the value is `null`, then unset the avatar.
|
||||
$this->set_avatar($avatar);
|
||||
|
||||
// Nothing else to do if the queue is false.
|
||||
if (!$queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always queue a room update, even if none of the above standard fields have changed.
|
||||
// It is possible for providers to have custom fields that have been updated.
|
||||
update_room_task::queue(
|
||||
@@ -613,6 +738,35 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all users from the room.
|
||||
*
|
||||
* @param bool $queue Whether to queue the task or not
|
||||
*/
|
||||
public function remove_all_members_from_room(bool $queue = true): void {
|
||||
// No communication object? something not done right.
|
||||
if (!$this->communication) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->communication->get_provider() === processor::PROVIDER_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This provider does not manage users? No action required.
|
||||
if (!$this->communication->supports_user_features()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->communication->add_delete_user_flag($this->communication->get_all_userids_for_instance());
|
||||
|
||||
if ($queue) {
|
||||
remove_members_from_room::queue(
|
||||
$this->communication
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the communication room status notification.
|
||||
*/
|
||||
@@ -626,20 +780,23 @@ class api {
|
||||
return;
|
||||
}
|
||||
|
||||
$roomstatus = $this->get_communication_room_url() ? 'ready' : 'pending';
|
||||
$roomstatus = $this->get_communication_room_url()
|
||||
? constants::COMMUNICATION_STATUS_READY
|
||||
: constants::COMMUNICATION_STATUS_PENDING;
|
||||
$pluginname = get_string('pluginname', $this->get_provider());
|
||||
$message = get_string('communicationroom' . $roomstatus, 'communication', $pluginname);
|
||||
|
||||
// We only show the ready notification once per user.
|
||||
// We check this with a custom user preference.
|
||||
$roomreadypreference = "{$this->component}_{$this->instancetype}_{$this->instanceid}_room_ready";
|
||||
|
||||
switch ($roomstatus) {
|
||||
case 'pending':
|
||||
case constants::COMMUNICATION_STATUS_PENDING:
|
||||
\core\notification::add($message, \core\notification::INFO);
|
||||
unset_user_preference($roomreadypreference);
|
||||
break;
|
||||
|
||||
case 'ready':
|
||||
// We only show the ready notification once per user.
|
||||
// We check this with a custom user preference.
|
||||
$roomreadypreference = "{$this->component}_{$this->instancetype}_{$this->instanceid}_room_ready";
|
||||
|
||||
case constants::COMMUNICATION_STATUS_READY:
|
||||
if (empty(get_user_preferences($roomreadypreference))) {
|
||||
\core\notification::add($message, \core\notification::SUCCESS);
|
||||
set_user_preference($roomreadypreference, true);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Constants for communication api.
|
||||
*
|
||||
* @package core_communication
|
||||
* @copyright 2024 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class constants {
|
||||
|
||||
/** @var string GROUP_COMMUNICATION_INSTANCETYPE The group communication instance type. */
|
||||
public const GROUP_COMMUNICATION_INSTANCETYPE = 'groupcommunication';
|
||||
|
||||
/** @var string GROUP_COMMUNICATION_COMPONENT The group communication component. */
|
||||
public const GROUP_COMMUNICATION_COMPONENT = 'core_group';
|
||||
|
||||
/** @var string COURSE_COMMUNICATION_INSTANCETYPE The course communication instance type. */
|
||||
public const COURSE_COMMUNICATION_INSTANCETYPE = 'coursecommunication';
|
||||
|
||||
/** @var string COURSE_COMMUNICATION_COMPONENT The course communication component. */
|
||||
public const COURSE_COMMUNICATION_COMPONENT = 'core_course';
|
||||
|
||||
/** @var string COMMUNICATION_STATUS_PENDING The communication status pending. */
|
||||
public const COMMUNICATION_STATUS_PENDING = 'pending';
|
||||
|
||||
/** @var string COMMUNICATION_STATUS_READY The communication status sent. */
|
||||
public const COMMUNICATION_STATUS_READY = 'ready';
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
<?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;
|
||||
|
||||
use context;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Helper method for communication.
|
||||
*
|
||||
* @package core_communication
|
||||
* @copyright 2023 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class helper {
|
||||
|
||||
/**
|
||||
* Load the communication instance for group id.
|
||||
*
|
||||
* @param int $groupid The group id
|
||||
* @param context $context The context, to make sure any instance using group can load the communication instance
|
||||
* @return api The communication instance.
|
||||
*/
|
||||
public static function load_by_group(int $groupid, context $context): api {
|
||||
return \core_communication\api::load_by_instance(
|
||||
context: $context,
|
||||
component: constants::GROUP_COMMUNICATION_COMPONENT,
|
||||
instancetype: constants::GROUP_COMMUNICATION_INSTANCETYPE,
|
||||
instanceid: $groupid,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the communication instance for course id.
|
||||
*
|
||||
* @param int $courseid The course id
|
||||
* @param \context $context The context
|
||||
* @param string|null $provider The provider name
|
||||
* @return api The communication instance
|
||||
*/
|
||||
public static function load_by_course(
|
||||
int $courseid,
|
||||
\context $context,
|
||||
?string $provider = null,
|
||||
): api {
|
||||
return \core_communication\api::load_by_instance(
|
||||
context: $context,
|
||||
component: constants::COURSE_COMMUNICATION_COMPONENT,
|
||||
instancetype: constants::COURSE_COMMUNICATION_INSTANCETYPE,
|
||||
instanceid: $courseid,
|
||||
provider: $provider,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Communication api call to create room for a group if course has group mode enabled.
|
||||
*
|
||||
* @param int $courseid The course id.
|
||||
* @return stdClass
|
||||
*/
|
||||
public static function get_course(int $courseid): stdClass {
|
||||
global $DB;
|
||||
return $DB->get_record(
|
||||
table: 'course',
|
||||
conditions: ['id' => $courseid],
|
||||
strictness: MUST_EXIST,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is group mode enabled for the course.
|
||||
*
|
||||
* @param stdClass $course The course object
|
||||
*/
|
||||
public static function is_group_mode_enabled_for_course(stdClass $course): bool {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
|
||||
return (int)$groupmode !== NOGROUPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to update room membership according to action passed.
|
||||
* This method will help reduce a large amount of duplications of code in different places in core.
|
||||
*
|
||||
* @param \stdClass $course The course object.
|
||||
* @param array $userids The user ids to add to the communication room.
|
||||
* @param string $memberaction The action to perform on the communication room.
|
||||
*/
|
||||
public static function update_course_communication_room_membership(
|
||||
\stdClass $course,
|
||||
array $userids,
|
||||
string $memberaction,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate communication api action.
|
||||
$roomuserprovider = new \ReflectionClass(room_user_provider::class);
|
||||
if (!$roomuserprovider->hasMethod($memberaction)) {
|
||||
throw new \coding_exception('Invalid action provided.');
|
||||
}
|
||||
|
||||
// Get the group mode for this course.
|
||||
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
|
||||
// If group mode is not set, then just handle the update normally for these users.
|
||||
if ((int)$groupmode === NOGROUPS) {
|
||||
$communication = self::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->$memberaction($userids);
|
||||
} else {
|
||||
// If group mode is set, then handle the update for these users with repect to the group they are in.
|
||||
$coursegroups = groups_get_all_groups(courseid: $course->id);
|
||||
|
||||
$usershandled = [];
|
||||
|
||||
// Filter all the users that have the capability to access all groups.
|
||||
$allaccessgroupusers = self::get_users_has_access_to_all_groups(
|
||||
userids: $userids,
|
||||
courseid: $course->id,
|
||||
);
|
||||
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
|
||||
// Get all group members.
|
||||
$groupmembers = groups_get_members(groupid: $coursegroup->id, fields: 'u.id');
|
||||
$groupmembers = array_column($groupmembers, 'id');
|
||||
|
||||
// Find the common user ids between the group members and incoming userids.
|
||||
$groupuserstohandle = array_intersect(
|
||||
$groupmembers,
|
||||
$userids,
|
||||
);
|
||||
|
||||
// Add users who have the capability to access this group (and haven't been added already).
|
||||
foreach ($allaccessgroupusers as $allaccessgroupuser) {
|
||||
if (!in_array($allaccessgroupuser, $groupuserstohandle, true)) {
|
||||
$groupuserstohandle[] = $allaccessgroupuser;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of the users we have handled already.
|
||||
$usershandled = array_merge($usershandled, $groupuserstohandle);
|
||||
|
||||
// Let's check if we need to add/remove members from room because of a role change.
|
||||
// First, get all the instance users for this group.
|
||||
$communication = self::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$instanceusers = $communication->get_processor()->get_all_userids_for_instance();
|
||||
|
||||
// The difference between the instance users and the group members are the ones we want to check.
|
||||
$roomuserstocheck = array_diff(
|
||||
$instanceusers,
|
||||
$groupmembers
|
||||
);
|
||||
|
||||
if (!empty($roomuserstocheck)) {
|
||||
// Check if they still have the capability to keep their access in the room.
|
||||
$userslostcaps = array_diff(
|
||||
$roomuserstocheck,
|
||||
self::get_users_has_access_to_all_groups(
|
||||
userids: $roomuserstocheck,
|
||||
courseid: $course->id,
|
||||
),
|
||||
);
|
||||
// Remove users who no longer have the capability.
|
||||
if (!empty($userslostcaps)) {
|
||||
$communication->remove_members_from_room(userids: $userslostcaps);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have to add any room members who have gained the capability.
|
||||
$usersgainedcaps = array_diff(
|
||||
$allaccessgroupusers,
|
||||
$instanceusers,
|
||||
);
|
||||
|
||||
// If we have users, add them to the room.
|
||||
if (!empty($usersgainedcaps)) {
|
||||
$communication->add_members_to_room(userids: $usersgainedcaps);
|
||||
}
|
||||
|
||||
// Finally, trigger the update task for the users who need to be handled.
|
||||
$communication->$memberaction($groupuserstohandle);
|
||||
}
|
||||
|
||||
// If the user was not in any group, but an update/remove action was requested for the user,
|
||||
// then the user must have had a role with the capablity, but made a regular user.
|
||||
$usersnothandled = array_diff($userids, $usershandled);
|
||||
|
||||
// These users are not handled and not in any group, so logically these users lost their permission to stay in the room.
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
$communication = self::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->remove_members_from_room(userids: $usersnothandled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users with the capability to access all groups.
|
||||
*
|
||||
* @param array $userids user ids to check the permission
|
||||
* @param int $courseid course id
|
||||
* @return array of userids
|
||||
*/
|
||||
public static function get_users_has_access_to_all_groups(
|
||||
array $userids,
|
||||
int $courseid
|
||||
): array {
|
||||
$allgroupsusers = [];
|
||||
$context = \context_course::instance(courseid: $courseid);
|
||||
|
||||
foreach ($userids as $userid) {
|
||||
if (
|
||||
has_capability(
|
||||
capability: 'moodle/site:accessallgroups',
|
||||
context: $context,
|
||||
user: $userid,
|
||||
)
|
||||
) {
|
||||
$allgroupsusers[] = $userid;
|
||||
}
|
||||
}
|
||||
|
||||
return $allgroupsusers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course communication url according to course setup.
|
||||
*
|
||||
* @param stdClass $course The course object.
|
||||
* @return string The communication room url.
|
||||
*/
|
||||
public static function get_course_communication_url(stdClass $course): string {
|
||||
// If it's called from site context, then just return.
|
||||
if ($course->id === SITEID) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$url = '';
|
||||
// Get the group mode for this course.
|
||||
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
|
||||
// If group mode is not set then just handle the course communication for these users.
|
||||
if ((int)$groupmode === NOGROUPS) {
|
||||
$communication = self::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$url = $communication->get_communication_room_url();
|
||||
} else {
|
||||
// If group mode is set then handle the group communication rooms for these users.
|
||||
$coursegroups = groups_get_all_groups(courseid: $course->id);
|
||||
$numberofgroups = count($coursegroups);
|
||||
|
||||
// If no groups available, nothing to show.
|
||||
if ($numberofgroups === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$readygroups = [];
|
||||
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
$communication = self::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$roomstatus = $communication->get_communication_room_url() ? 'ready' : 'pending';
|
||||
if ($roomstatus === 'ready') {
|
||||
$readygroups[$communication->get_processor()->get_id()] = $communication->get_communication_room_url();
|
||||
}
|
||||
}
|
||||
if (!empty($readygroups)) {
|
||||
$highestkey = max(array_keys($readygroups));
|
||||
$url = $readygroups[$highestkey];
|
||||
}
|
||||
}
|
||||
|
||||
return empty($url) ? '' : $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enrolled users for course.
|
||||
*
|
||||
* @param stdClass $course The course object.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_enrolled_users_for_course(stdClass $course): array {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/enrollib.php');
|
||||
return array_column(
|
||||
enrol_get_course_users(courseid: $course->id),
|
||||
'id',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the course communication status notification for course.
|
||||
*
|
||||
* @param \stdClass $course The course object.
|
||||
*/
|
||||
public static function get_course_communication_status_notification(\stdClass $course): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the group mode for this course.
|
||||
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
|
||||
// If group mode is not set then just handle the course communication for these users.
|
||||
if ((int)$groupmode === NOGROUPS) {
|
||||
$communication = self::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->show_communication_room_status_notification();
|
||||
} else {
|
||||
// If group mode is set then handle the group communication rooms for these users.
|
||||
$coursegroups = groups_get_all_groups(courseid: $course->id);
|
||||
$numberofgroups = count($coursegroups);
|
||||
|
||||
// If no groups available, nothing to show.
|
||||
if ($numberofgroups === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$numberofreadygroups = 0;
|
||||
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
$communication = self::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$roomstatus = $communication->get_communication_room_url() ? 'ready' : 'pending';
|
||||
switch ($roomstatus) {
|
||||
case 'ready':
|
||||
$numberofreadygroups ++;
|
||||
break;
|
||||
case 'pending':
|
||||
$pendincommunicationobject = $communication;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($numberofgroups === $numberofreadygroups) {
|
||||
$communication->show_communication_room_status_notification();
|
||||
} else {
|
||||
$pendincommunicationobject->show_communication_room_status_notification();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update course communication according to course data.
|
||||
* Course can have course or group rooms. Group mode enabling will create rooms for groups.
|
||||
*
|
||||
* @param stdClass $course The course data
|
||||
* @param bool $changesincoursecat Whether the course moved to a different category
|
||||
*/
|
||||
public static function update_course_communication_instance(
|
||||
stdClass $course,
|
||||
bool $changesincoursecat
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if provider is selected.
|
||||
$provider = $course->selectedcommunication ?? null;
|
||||
// If the course moved to hidden category, set provider to none.
|
||||
if ($changesincoursecat && empty($course->visible)) {
|
||||
$provider = processor::PROVIDER_NONE;
|
||||
}
|
||||
|
||||
// Get the course context.
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
// Get the course image.
|
||||
$courseimage = course_get_courseimage(course: $course);
|
||||
// Get the course communication instance.
|
||||
$coursecommunication = self::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
|
||||
// Attempt to get the communication provider if it wasn't provided in the data.
|
||||
if (empty($provider)) {
|
||||
$provider = $coursecommunication->get_provider();
|
||||
}
|
||||
|
||||
// This nasty logic is here because of hide course doesn't pass anything in the data object.
|
||||
if (!empty($course->communicationroomname)) {
|
||||
$coursecommunicationroomname = $course->communicationroomname;
|
||||
} else {
|
||||
$coursecommunicationroomname = $course->fullname ?? get_course($course->id)->fullname;
|
||||
}
|
||||
|
||||
// List of enrolled users for course communication.
|
||||
$enrolledusers = self::get_enrolled_users_for_course(course: $course);
|
||||
|
||||
// Check for group mode, we will have to get the course data again as the group info is not always in the object.
|
||||
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
|
||||
|
||||
// If group mode is disabled, get the communication information for creating room for a course.
|
||||
if ((int)$groupmode === NOGROUPS) {
|
||||
// Remove all the members from active group rooms if there is any.
|
||||
$coursegroups = groups_get_all_groups(courseid: $course->id);
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
$communication = self::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
// Remove the members from the group room.
|
||||
$communication->remove_all_members_from_room();
|
||||
// Now delete the group room.
|
||||
$communication->update_room(active: processor::PROVIDER_INACTIVE);
|
||||
}
|
||||
|
||||
// Now create/update the course room.
|
||||
$communication = self::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: $provider,
|
||||
instance: $course,
|
||||
communicationroomname: $coursecommunicationroomname,
|
||||
users: $enrolledusers,
|
||||
instanceimage: $courseimage,
|
||||
);
|
||||
} else {
|
||||
// Update the group communication instances.
|
||||
self::update_group_communication_instances_for_course(
|
||||
course: $course,
|
||||
provider: $provider,
|
||||
);
|
||||
|
||||
// Remove all the members for the course room if instance available.
|
||||
$communication = self::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
provider: $provider === processor::PROVIDER_NONE ? null : $provider,
|
||||
);
|
||||
|
||||
if ($communication->get_processor() === null) {
|
||||
// If a course communication instance is not created, create one.
|
||||
$communication->create_and_configure_room(
|
||||
communicationroomname: $coursecommunicationroomname,
|
||||
avatar: $courseimage,
|
||||
instance: $course,
|
||||
queue: false,
|
||||
);
|
||||
} else {
|
||||
$communication->remove_all_members_from_room();
|
||||
// Now update the course communication instance with the latest changes.
|
||||
// We are not making room for this instance as it is a group mode enabled course.
|
||||
// If provider is none, then we will make the room inactive, otherwise always active in group mode.
|
||||
$communication->update_room(
|
||||
active: $provider === processor::PROVIDER_NONE ? processor::PROVIDER_INACTIVE : processor::PROVIDER_ACTIVE,
|
||||
communicationroomname: $coursecommunicationroomname,
|
||||
avatar: $courseimage,
|
||||
instance: $course,
|
||||
queue: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the group communication instances.
|
||||
*
|
||||
* @param stdClass $course The course object.
|
||||
* @param string $provider The provider name.
|
||||
*/
|
||||
public static function update_group_communication_instances_for_course(
|
||||
stdClass $course,
|
||||
string $provider,
|
||||
): void {
|
||||
$coursegroups = groups_get_all_groups(courseid: $course->id);
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$allaccessgroupusers = self::get_users_has_access_to_all_groups(
|
||||
userids: self::get_enrolled_users_for_course(course: $course),
|
||||
courseid: $course->id,
|
||||
);
|
||||
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
$groupuserstoadd = array_column(
|
||||
groups_get_members(groupid: $coursegroup->id),
|
||||
'id',
|
||||
);
|
||||
|
||||
foreach ($allaccessgroupusers as $allaccessgroupuser) {
|
||||
if (!in_array($allaccessgroupuser, $groupuserstoadd, true)) {
|
||||
$groupuserstoadd[] = $allaccessgroupuser;
|
||||
}
|
||||
}
|
||||
|
||||
// Now create/update the group room.
|
||||
$communication = self::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: $provider,
|
||||
instance: $course,
|
||||
communicationroomname: $coursegroup->name,
|
||||
users: $groupuserstoadd,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
<?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;
|
||||
|
||||
use context_course;
|
||||
use core\hook\access\after_role_assigned;
|
||||
use core\hook\access\after_role_unassigned;
|
||||
use core_enrol\hook\before_enrol_instance_delete;
|
||||
use core_enrol\hook\after_enrol_instance_status_updated;
|
||||
use core_enrol\hook\after_user_enrolled;
|
||||
use core_enrol\hook\before_user_enrolment_update;
|
||||
use core_enrol\hook\before_user_enrolment_remove;
|
||||
use core_course\hook\after_course_created;
|
||||
use core_course\hook\before_course_delete;
|
||||
use core_course\hook\after_course_updated;
|
||||
use core_group\hook\after_group_created;
|
||||
use core_group\hook\after_group_deleted;
|
||||
use core_group\hook\after_group_membership_added;
|
||||
use core_group\hook\after_group_membership_removed;
|
||||
use core_group\hook\after_group_updated;
|
||||
use core_user\hook\before_user_deleted;
|
||||
use core_user\hook\before_user_update;
|
||||
|
||||
/**
|
||||
* Hook listener for communication api.
|
||||
*
|
||||
* @package core_communication
|
||||
* @copyright 2023 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class hook_listener {
|
||||
|
||||
/**
|
||||
* Get the course and group object for the group hook.
|
||||
*
|
||||
* @param mixed $hook The hook object.
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_group_and_course_data_for_group_hook(mixed $hook): array {
|
||||
$group = $hook->groupinstance;
|
||||
$course = helper::get_course(
|
||||
courseid: $group->courseid,
|
||||
);
|
||||
|
||||
return [
|
||||
$group,
|
||||
$course,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Communication api call to create room for a group if course has group mode enabled.
|
||||
*
|
||||
* @param after_group_created $hook The group created hook.
|
||||
*/
|
||||
public static function create_group_communication(
|
||||
after_group_created $hook,
|
||||
): void {
|
||||
[$group, $course] = self::get_group_and_course_data_for_group_hook(
|
||||
hook: $hook,
|
||||
);
|
||||
|
||||
// Check if group mode enabled before handling the communication.
|
||||
if (!helper::is_group_mode_enabled_for_course(course: $course)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
// Get the course communication instance to set the provider.
|
||||
$coursecommunication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
|
||||
$communication = api::load_by_instance(
|
||||
context: $coursecontext,
|
||||
component: constants::GROUP_COMMUNICATION_COMPONENT,
|
||||
instancetype: constants::GROUP_COMMUNICATION_INSTANCETYPE,
|
||||
instanceid: $group->id,
|
||||
provider: $coursecommunication->get_provider(),
|
||||
);
|
||||
|
||||
$communication->create_and_configure_room(
|
||||
communicationroomname: $group->name,
|
||||
instance: $course,
|
||||
);
|
||||
|
||||
// As it's a new group, we need to add the users with all access group role to the room.
|
||||
$enrolledusers = helper::get_enrolled_users_for_course(course: $course);
|
||||
$userstoadd = helper::get_users_has_access_to_all_groups(
|
||||
userids: $enrolledusers,
|
||||
courseid: $course->id,
|
||||
);
|
||||
$communication->add_members_to_room(
|
||||
userids: $userstoadd,
|
||||
queue: false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Communication api call to update room for a group if course has group mode enabled.
|
||||
*
|
||||
* @param after_group_updated $hook The group updated hook.
|
||||
*/
|
||||
public static function update_group_communication(
|
||||
after_group_updated $hook,
|
||||
): void {
|
||||
[$group, $course] = self::get_group_and_course_data_for_group_hook(
|
||||
hook: $hook,
|
||||
);
|
||||
|
||||
// Check if group mode enabled before handling the communication.
|
||||
if (!helper::is_group_mode_enabled_for_course(course: $course)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$communication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
|
||||
// If the name didn't change, then we don't need to update the room.
|
||||
if ($group->name === $communication->get_room_name()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$communication->update_room(
|
||||
active: processor::PROVIDER_ACTIVE,
|
||||
communicationroomname: $group->name,
|
||||
instance: $course,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the communication room for a group if course has group mode enabled.
|
||||
*
|
||||
* @param after_group_deleted $hook The group deleted hook.
|
||||
*/
|
||||
public static function delete_group_communication(
|
||||
after_group_deleted $hook
|
||||
): void {
|
||||
[$group, $course] = self::get_group_and_course_data_for_group_hook(
|
||||
hook: $hook,
|
||||
);
|
||||
|
||||
// Check if group mode enabled before handling the communication.
|
||||
if (!helper::is_group_mode_enabled_for_course(course: $course)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
$communication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $context,
|
||||
);
|
||||
$communication->delete_room();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add members to group room when a new member is added to the group.
|
||||
*
|
||||
* @param after_group_membership_added $hook The group membership added hook.
|
||||
*/
|
||||
public static function add_members_to_group_room(
|
||||
after_group_membership_added $hook,
|
||||
): void {
|
||||
[$group, $course] = self::get_group_and_course_data_for_group_hook(
|
||||
hook: $hook,
|
||||
);
|
||||
|
||||
// Check if group mode enabled before handling the communication.
|
||||
if (!helper::is_group_mode_enabled_for_course(course: $course)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
$communication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $context,
|
||||
);
|
||||
$communication->add_members_to_room(
|
||||
userids: $hook->userids,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove members from the room when a member is removed from group room.
|
||||
*
|
||||
* @param after_group_membership_removed $hook The group membership removed hook.
|
||||
*/
|
||||
public static function remove_members_from_group_room(
|
||||
after_group_membership_removed $hook,
|
||||
): void {
|
||||
[$group, $course] = self::get_group_and_course_data_for_group_hook(
|
||||
hook: $hook,
|
||||
);
|
||||
|
||||
// Check if group mode enabled before handling the communication.
|
||||
if (!helper::is_group_mode_enabled_for_course(course: $course)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
$communication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $context,
|
||||
);
|
||||
$communication->remove_members_from_room(
|
||||
userids: $hook->userids,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create course communication instance.
|
||||
*
|
||||
* @param after_course_created $hook The course created hook.
|
||||
*/
|
||||
public static function create_course_communication(
|
||||
after_course_created $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$course = $hook->course;
|
||||
|
||||
// Check for default provider config setting.
|
||||
$defaultprovider = get_config(
|
||||
plugin: 'moodlecourse',
|
||||
name: 'coursecommunicationprovider',
|
||||
);
|
||||
$provider = $course->selectedcommunication ?? $defaultprovider;
|
||||
|
||||
if (empty($provider) && $provider === processor::PROVIDER_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for group mode, we will have to get the course data again as the group info is not always in the object.
|
||||
$createcourseroom = true;
|
||||
$creategrouprooms = false;
|
||||
$coursedata = get_course(courseid: $course->id);
|
||||
$groupmode = $course->groupmode ?? $coursedata->groupmode;
|
||||
if ((int)$groupmode !== NOGROUPS) {
|
||||
$createcourseroom = false;
|
||||
$creategrouprooms = true;
|
||||
}
|
||||
|
||||
// Prepare the communication api data.
|
||||
$courseimage = course_get_courseimage(course: $course);
|
||||
$communicationroomname = !empty($course->communicationroomname) ? $course->communicationroomname : $coursedata->fullname;
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
// Communication api call for course communication.
|
||||
$communication = \core_communication\api::load_by_instance(
|
||||
context: $coursecontext,
|
||||
component: constants::COURSE_COMMUNICATION_COMPONENT,
|
||||
instancetype: constants::COURSE_COMMUNICATION_INSTANCETYPE,
|
||||
instanceid: $course->id,
|
||||
provider: $provider,
|
||||
);
|
||||
$communication->create_and_configure_room(
|
||||
communicationroomname: $communicationroomname,
|
||||
avatar: $courseimage,
|
||||
instance: $course,
|
||||
queue: $createcourseroom,
|
||||
);
|
||||
|
||||
// Communication api call for group communication.
|
||||
if ($creategrouprooms) {
|
||||
helper::update_group_communication_instances_for_course(
|
||||
course: $course,
|
||||
provider: $provider,
|
||||
);
|
||||
} else {
|
||||
$enrolledusers = helper::get_enrolled_users_for_course(course: $course);
|
||||
$communication->add_members_to_room(
|
||||
userids: $enrolledusers,
|
||||
queue: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the course communication instance.
|
||||
*
|
||||
* @param after_course_updated $hook The course updated hook.
|
||||
*/
|
||||
public static function update_course_communication(
|
||||
after_course_updated $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
$course = $hook->course;
|
||||
$oldcourse = $hook->oldcourse;
|
||||
$changeincoursecat = $hook->changeincoursecat;
|
||||
$groupmode = $course->groupmode ?? get_course($course->id)->groupmode;
|
||||
if ($changeincoursecat || $groupmode !== $oldcourse->groupmode) {
|
||||
helper::update_course_communication_instance(
|
||||
course: $course,
|
||||
changesincoursecat: $changeincoursecat,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete course communication data and remove members.
|
||||
* Course can have communication data if it is a group or a course.
|
||||
* This action is important to perform even if the experimental feature is disabled.
|
||||
*
|
||||
* @param before_course_delete $hook The course deleted hook.
|
||||
*/
|
||||
public static function delete_course_communication(
|
||||
before_course_delete $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$course = $hook->course;
|
||||
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
|
||||
// If group mode is not set then just handle the course communication room.
|
||||
if ((int)$groupmode === NOGROUPS) {
|
||||
$communication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->delete_room();
|
||||
} else {
|
||||
// If group mode is set then handle the group communication rooms.
|
||||
$coursegroups = groups_get_all_groups(courseid: $course->id);
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
$communication = helper::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->delete_room();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the room membership for the user updates.
|
||||
*
|
||||
* @param before_user_update $hook The user updated hook.
|
||||
*/
|
||||
public static function update_user_room_memberships(
|
||||
before_user_update $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $hook->user;
|
||||
$currentuserrecord = $hook->currentuserdata;
|
||||
|
||||
// Get the user courses.
|
||||
$usercourses = enrol_get_users_courses(userid: $user->id);
|
||||
|
||||
// If the user is suspended then remove the user from all the rooms.
|
||||
// Otherwise add the user to all the rooms for the courses the user enrolled in.
|
||||
if (!empty($currentuserrecord) && isset($user->suspended) && $currentuserrecord->suspended !== $user->suspended) {
|
||||
// Decide the action for the communication api for the user.
|
||||
$memberaction = ($user->suspended === 0) ? 'add_members_to_room' : 'remove_members_from_room';
|
||||
foreach ($usercourses as $usercourse) {
|
||||
helper::update_course_communication_room_membership(
|
||||
course: $usercourse,
|
||||
userids: [$user->id],
|
||||
memberaction: $memberaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all room memberships for a user.
|
||||
*
|
||||
* @param before_user_deleted $hook The user deleted hook.
|
||||
*/
|
||||
public static function delete_user_room_memberships(
|
||||
before_user_deleted $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $hook->user;
|
||||
|
||||
foreach (enrol_get_users_courses(userid: $user->id) as $course) {
|
||||
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
|
||||
if ((int)$groupmode === NOGROUPS) {
|
||||
$communication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->get_room_user_provider()->remove_members_from_room(userids: [$user->id]);
|
||||
$communication->get_processor()->delete_instance_user_mapping(userids: [$user->id]);
|
||||
} else {
|
||||
// If group mode is set then handle the group communication rooms.
|
||||
$coursegroups = groups_get_all_groups(courseid: $course->id);
|
||||
foreach ($coursegroups as $coursegroup) {
|
||||
$communication = helper::load_by_group(
|
||||
groupid: $coursegroup->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$communication->get_room_user_provider()->remove_members_from_room(userids: [$user->id]);
|
||||
$communication->get_processor()->delete_instance_user_mapping(userids: [$user->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the room membership of the user for role assigned in a course.
|
||||
*
|
||||
* @param after_role_assigned|after_role_unassigned $hook
|
||||
*/
|
||||
public static function update_user_membership_for_role_changes(
|
||||
after_role_assigned|after_role_unassigned $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$context = $hook->context;
|
||||
if ($coursecontext = $context->get_course_context(strict: false)) {
|
||||
helper::update_course_communication_room_membership(
|
||||
course: get_course(courseid: $coursecontext->instanceid),
|
||||
userids: [$hook->userid],
|
||||
memberaction: 'update_room_membership',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the communication memberships for enrol status change.
|
||||
*
|
||||
* @param after_enrol_instance_status_updated $hook The enrol status updated hook.
|
||||
*/
|
||||
public static function update_communication_memberships_for_enrol_status_change(
|
||||
after_enrol_instance_status_updated $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enrolinstance = $hook->enrolinstance;
|
||||
// No need to do anything for guest instances.
|
||||
if ($enrolinstance->enrol === 'guest') {
|
||||
return;
|
||||
}
|
||||
|
||||
$newstatus = $hook->newstatus;
|
||||
// Check if a valid status is given.
|
||||
if (
|
||||
$newstatus !== ENROL_INSTANCE_ENABLED ||
|
||||
$newstatus !== ENROL_INSTANCE_DISABLED
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the status provided is valid.
|
||||
switch ($newstatus) {
|
||||
case ENROL_INSTANCE_ENABLED:
|
||||
$action = 'add_members_to_room';
|
||||
break;
|
||||
case ENROL_INSTANCE_DISABLED:
|
||||
$action = 'remove_members_from_room';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
global $DB;
|
||||
$instanceusers = $DB->get_records(
|
||||
table: 'user_enrolments',
|
||||
conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
|
||||
);
|
||||
$enrolledusers = array_column($instanceusers, 'userid');
|
||||
helper::update_course_communication_room_membership(
|
||||
course: get_course(courseid: $enrolinstance->courseid),
|
||||
userids: $enrolledusers,
|
||||
memberaction: $action,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the communication instance memberships when an enrolment instance is deleted.
|
||||
*
|
||||
* @param before_enrol_instance_delete $hook The enrol instance deleted hook.
|
||||
*/
|
||||
public static function remove_communication_memberships_for_enrol_instance_deletion(
|
||||
before_enrol_instance_delete $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enrolinstance = $hook->enrolinstance;
|
||||
// No need to do anything for guest instances.
|
||||
if ($enrolinstance->enrol === 'guest') {
|
||||
return;
|
||||
}
|
||||
|
||||
global $DB;
|
||||
$instanceusers = $DB->get_records(
|
||||
table: 'user_enrolments',
|
||||
conditions: ['enrolid' => $enrolinstance->id, 'status' => ENROL_USER_ACTIVE],
|
||||
);
|
||||
$enrolledusers = array_column($instanceusers, 'userid');
|
||||
helper::update_course_communication_room_membership(
|
||||
course: get_course(courseid: $enrolinstance->courseid),
|
||||
userids: $enrolledusers,
|
||||
memberaction: 'remove_members_from_room',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add communication instance membership for an enrolled user.
|
||||
*
|
||||
* @param after_user_enrolled $hook The user enrolled hook.
|
||||
*/
|
||||
public static function add_communication_membership_for_enrolled_user(
|
||||
after_user_enrolled $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enrolinstance = $hook->enrolinstance;
|
||||
// No need to do anything for guest instances.
|
||||
if ($enrolinstance->enrol === 'guest') {
|
||||
return;
|
||||
}
|
||||
|
||||
helper::update_course_communication_room_membership(
|
||||
course: get_course($enrolinstance->courseid),
|
||||
userids: [$hook->get_userid()],
|
||||
memberaction: 'add_members_to_room',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the communication instance membership for the user enrolment updates.
|
||||
*
|
||||
* @param before_user_enrolment_update $hook The user enrolment updated hook.
|
||||
*/
|
||||
public static function update_communication_membership_for_updated_user_enrolment(
|
||||
before_user_enrolment_update $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enrolinstance = $hook->enrolinstance;
|
||||
// No need to do anything for guest instances.
|
||||
if ($enrolinstance->enrol === 'guest') {
|
||||
return;
|
||||
}
|
||||
|
||||
$userenrolmentinstance = $hook->userenrolmentinstance;
|
||||
$statusmodified = $hook->statusmodified;
|
||||
$timeendmodified = $hook->timeendmodified;
|
||||
|
||||
if (
|
||||
($statusmodified && ((int) $userenrolmentinstance->status === 1)) ||
|
||||
($timeendmodified && $userenrolmentinstance->timeend !== 0 && (time() > $userenrolmentinstance->timeend))
|
||||
) {
|
||||
$action = 'remove_members_from_room';
|
||||
} else {
|
||||
$action = 'add_members_to_room';
|
||||
}
|
||||
|
||||
helper::update_course_communication_room_membership(
|
||||
course: get_course($enrolinstance->courseid),
|
||||
userids: [$hook->get_userid()],
|
||||
memberaction: $action,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove communication instance membership for an enrolled user.
|
||||
*
|
||||
* @param before_user_enrolment_remove $hook The user unenrolled hook.
|
||||
*/
|
||||
public static function remove_communication_membership_for_unenrolled_user(
|
||||
before_user_enrolment_remove $hook,
|
||||
): void {
|
||||
// If the communication subsystem is not enabled then just ignore.
|
||||
if (!api::is_available()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enrolinstance = $hook->enrolinstance;
|
||||
// No need to do anything for guest instances.
|
||||
if ($enrolinstance->enrol === 'guest') {
|
||||
return;
|
||||
}
|
||||
|
||||
helper::update_course_communication_room_membership(
|
||||
course: get_course($enrolinstance->courseid),
|
||||
userids: [$hook->get_userid()],
|
||||
memberaction: 'remove_members_from_room',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class processor {
|
||||
/**
|
||||
* Update the communication instance with any changes.
|
||||
*
|
||||
* @param null|int $active Active state of the instance (processor::PROVIDER_ACTIVE or processor::PROVIDER_INACTIVE)
|
||||
* @param null|string $active Active state of the instance (processor::PROVIDER_ACTIVE or processor::PROVIDER_INACTIVE)
|
||||
* @param null|string $roomname The room name
|
||||
*/
|
||||
public function update_instance(
|
||||
@@ -489,6 +489,15 @@ class processor {
|
||||
return $this->instancedata->roomname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get provider active status.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_provider_status(): int {
|
||||
return $this->instancedata->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get communication instance id.
|
||||
*
|
||||
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
namespace core_communication;
|
||||
|
||||
use core_communication\task\add_members_to_room_task;
|
||||
use core_communication\task\create_and_configure_room_task;
|
||||
use communication_matrix\matrix_test_helper_trait;
|
||||
use core_communication\task\synchronise_provider_task;
|
||||
use core_communication\task\synchronise_providers_task;
|
||||
use core_communication\task\remove_members_from_room;
|
||||
use core_communication\task\update_room_task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
@@ -351,4 +355,194 @@ class api_test extends \advanced_testcase {
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(synchronise_provider_task::class);
|
||||
$this->assertCount(2, $adhoctask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the removal of all members from the room.
|
||||
*
|
||||
* @covers ::remove_all_members_from_room
|
||||
*/
|
||||
public function test_remove_all_members_from_room(): void {
|
||||
$course = $this->get_course();
|
||||
$userid = $this->get_user()->id;
|
||||
$communication = \core_communication\api::load_by_instance(
|
||||
context: \core\context\course::instance($course->id),
|
||||
component: 'core_course',
|
||||
instancetype: 'coursecommunication',
|
||||
instanceid: $course->id,
|
||||
);
|
||||
$communication->add_members_to_room([$userid]);
|
||||
|
||||
// Now test the removing members from a room.
|
||||
$communication->remove_all_members_from_room();
|
||||
|
||||
// Test the remove members tasks added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(remove_members_from_room::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the configuration of room changes as well as the membership with the change of provider.
|
||||
*
|
||||
* @covers ::configure_room_and_membership_by_provider
|
||||
*/
|
||||
public function test_configure_room_and_membership_by_provider(): void {
|
||||
global $DB;
|
||||
|
||||
$course = $this->get_course('Sampleroom', 'none');
|
||||
$userid = $this->get_user()->id;
|
||||
$provider = 'communication_matrix';
|
||||
|
||||
$communication = \core_communication\api::load_by_instance(
|
||||
context: \core\context\course::instance($course->id),
|
||||
component: 'core_course',
|
||||
instancetype: 'coursecommunication',
|
||||
instanceid: $course->id,
|
||||
);
|
||||
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: $provider,
|
||||
instance: $course,
|
||||
communicationroomname: $course->fullname,
|
||||
users: [$userid],
|
||||
);
|
||||
$communication->reload();
|
||||
|
||||
// Test that the task to create a room is added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(create_and_configure_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// Test that no update tasks are added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
// Test that the task to add members to room is not added, as we are adding the user mapping not the task.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(add_members_to_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
// Now delete all the ad-hoc tasks.
|
||||
$DB->delete_records('task_adhoc');
|
||||
|
||||
// Now disable the provider by setting none.
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: processor::PROVIDER_NONE,
|
||||
instance: $course,
|
||||
communicationroomname: $course->fullname,
|
||||
users: [$userid],
|
||||
);
|
||||
$communication->reload();
|
||||
|
||||
// Test that the task to delete a room is added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// Test that the task to remove members from room is added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(remove_members_from_room::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// Now delete all the ad-hoc tasks.
|
||||
$DB->delete_records('task_adhoc');
|
||||
|
||||
// Now try to set the same none provider again.
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: processor::PROVIDER_NONE,
|
||||
instance: $course,
|
||||
communicationroomname: $course->fullname,
|
||||
users: [$userid],
|
||||
);
|
||||
|
||||
// Test that no communicaiton task is added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(create_and_configure_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(add_members_to_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(remove_members_from_room::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
// Now let's change it back to matrix and test the update task is added.
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: $provider,
|
||||
instance: $course,
|
||||
communicationroomname: $course->fullname,
|
||||
users: [$userid],
|
||||
);
|
||||
$communication->reload();
|
||||
|
||||
// Test create task is not added because communication has been created in the past.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(create_and_configure_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
// Test an update task added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// Test add membership task is added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(add_members_to_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// Now delete all the ad-hoc tasks.
|
||||
$DB->delete_records('task_adhoc');
|
||||
|
||||
// Now change the provider to another one.
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: 'communication_customlink',
|
||||
instance: $course,
|
||||
communicationroomname: $course->fullname,
|
||||
users: [$userid],
|
||||
);
|
||||
$communication->reload();
|
||||
|
||||
// Remove membership and update room task for the previous provider.
|
||||
// Create room task for new one.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(remove_members_from_room::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(create_and_configure_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// Now delete all the ad-hoc tasks.
|
||||
$DB->delete_records('task_adhoc');
|
||||
|
||||
// Now disable the provider.
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: processor::PROVIDER_NONE,
|
||||
instance: $course,
|
||||
communicationroomname: $course->fullname,
|
||||
users: [$userid],
|
||||
);
|
||||
$communication->reload();
|
||||
|
||||
// Should have one update and one remove task.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// This provider doesn't have any membership, so no remove task.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(remove_members_from_room::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
// Now delete all the ad-hoc tasks.
|
||||
$DB->delete_records('task_adhoc');
|
||||
|
||||
// Now enable the same provider again.
|
||||
$communication->configure_room_and_membership_by_provider(
|
||||
provider: $provider,
|
||||
instance: $course,
|
||||
communicationroomname: $course->fullname,
|
||||
users: [$userid],
|
||||
);
|
||||
|
||||
// Now it should have one update and one add task.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(add_members_to_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ trait communication_test_helper_trait {
|
||||
*/
|
||||
protected function get_course(
|
||||
string $roomname = 'Sampleroom',
|
||||
string $provider = 'communication_matrix'
|
||||
string $provider = 'communication_matrix',
|
||||
array $extrafields = [],
|
||||
): \stdClass {
|
||||
|
||||
$this->setup_communication_configs();
|
||||
@@ -61,7 +62,7 @@ trait communication_test_helper_trait {
|
||||
'communicationroomname' => $roomname,
|
||||
];
|
||||
|
||||
return $this->getDataGenerator()->create_course($records);
|
||||
return $this->getDataGenerator()->create_course(array_merge($records, $extrafields));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
<?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;
|
||||
|
||||
use communication_matrix\matrix_test_helper_trait;
|
||||
use core_communication\processor as communication_processor;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/../provider/matrix/tests/matrix_test_helper_trait.php');
|
||||
require_once(__DIR__ . '/communication_test_helper_trait.php');
|
||||
|
||||
/**
|
||||
* Test communication helper methods.
|
||||
*
|
||||
* @package core_communication
|
||||
* @copyright 2023 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_communication\helper
|
||||
*/
|
||||
class helper_test extends \advanced_testcase {
|
||||
use communication_test_helper_trait;
|
||||
use matrix_test_helper_trait;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->resetAfterTest();
|
||||
$this->setup_communication_configs();
|
||||
$this->initialise_mock_server();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test load_by_group.
|
||||
*/
|
||||
public function test_load_by_group(): void {
|
||||
|
||||
// As communication is created by default.
|
||||
$course = $this->get_course(
|
||||
extrafields: ['groupmode' => SEPARATEGROUPS],
|
||||
);
|
||||
$group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
|
||||
$context = \context_course::instance(courseid: $course->id);
|
||||
|
||||
$groupcommunication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $context,
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
expected: communication_processor::class,
|
||||
actual: $groupcommunication->get_processor(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test load_by_course.
|
||||
*/
|
||||
public function test_load_by_course(): void {
|
||||
// As communication is created by default.
|
||||
$course = $this->get_course();
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$coursecommunication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
expected: communication_processor::class,
|
||||
actual: $coursecommunication->get_processor(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_access_to_all_group_cap_users.
|
||||
*/
|
||||
public function test_get_users_has_access_to_all_groups(): void {
|
||||
global $DB;
|
||||
// Set up the data with course, group, user etc.
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$course = $this->get_course();
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
|
||||
// Enrol user1 as teacher.
|
||||
$teacherrole = $DB->get_record(
|
||||
table: 'role',
|
||||
conditions: ['shortname' => 'manager'],
|
||||
);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user1->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $teacherrole->id,
|
||||
userid: $user1->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
|
||||
// Enrol user2 as student.
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user2->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $studentrole->id,
|
||||
userid: $user2->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
|
||||
$allgroupaccessusers = helper::get_users_has_access_to_all_groups(
|
||||
userids: [$user1->id, $user2->id],
|
||||
courseid: $course->id,
|
||||
);
|
||||
$this->assertContains(
|
||||
needle: $user1->id,
|
||||
haystack: $allgroupaccessusers,
|
||||
);
|
||||
$this->assertNotContains(
|
||||
needle: $user2->id,
|
||||
haystack: $allgroupaccessusers,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update_communication_room_membership.
|
||||
*/
|
||||
public function test_update_communication_room_membership(): void {
|
||||
global $DB;
|
||||
|
||||
// Set up the data with course, group, user etc.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$course = $this->get_course();
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$teacherrole = $DB->get_record(
|
||||
table: 'role',
|
||||
conditions: ['shortname' => 'manager'],
|
||||
);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $teacherrole->id,
|
||||
userid:$user->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
|
||||
// Now remove members from room.
|
||||
helper::update_course_communication_room_membership(
|
||||
course: $course,
|
||||
userids: [$user->id],
|
||||
memberaction: 'remove_members_from_room',
|
||||
);
|
||||
|
||||
// Now test that there is communication instances for the course and the user removed from that instance.
|
||||
$coursecommunication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
|
||||
// Check the user is added for course communication instance.
|
||||
$courseusers = $coursecommunication->get_processor()->get_all_delete_flagged_userids();
|
||||
$courseusers = reset($courseusers);
|
||||
$this->assertEquals(
|
||||
expected: $user->id,
|
||||
actual: $courseusers,
|
||||
);
|
||||
|
||||
// Now add members to room.
|
||||
helper::update_course_communication_room_membership(
|
||||
course: $course,
|
||||
userids: [$user->id],
|
||||
memberaction: 'add_members_to_room',
|
||||
);
|
||||
|
||||
$coursecommunication->reload();
|
||||
// Check the user is added for course communication instance.
|
||||
$courseusers = $coursecommunication->get_processor()->get_instance_userids();
|
||||
$courseusers = reset($courseusers);
|
||||
$this->assertEquals(
|
||||
expected: $user->id,
|
||||
actual: $courseusers,
|
||||
);
|
||||
|
||||
// Now update membership.
|
||||
helper::update_course_communication_room_membership(
|
||||
course: $course,
|
||||
userids: [$user->id],
|
||||
memberaction: 'update_room_membership',
|
||||
);
|
||||
|
||||
$coursecommunication->reload();
|
||||
// Check the user is added for course communication instance.
|
||||
$courseusers = $coursecommunication->get_processor()->get_instance_userids();
|
||||
$courseusers = reset($courseusers);
|
||||
$this->assertEquals(
|
||||
expected: $user->id,
|
||||
actual: $courseusers,
|
||||
);
|
||||
|
||||
// Now try using invalid action.
|
||||
$this->expectException('coding_exception');
|
||||
$this->expectExceptionMessage('Invalid action provided.');
|
||||
helper::update_course_communication_room_membership(
|
||||
course: $course,
|
||||
userids: [$user->id],
|
||||
memberaction: 'a_funny_action',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
<?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;
|
||||
|
||||
use communication_matrix\matrix_test_helper_trait;
|
||||
use core_communication\task\add_members_to_room_task;
|
||||
use core_communication\task\create_and_configure_room_task;
|
||||
use core_communication\task\delete_room_task;
|
||||
use core_communication\task\update_room_membership_task;
|
||||
use core_communication\task\update_room_task;
|
||||
use core_communication\processor as communication_processor;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/../provider/matrix/tests/matrix_test_helper_trait.php');
|
||||
require_once(__DIR__ . '/communication_test_helper_trait.php');
|
||||
|
||||
/**
|
||||
* Test communication hook listeners.
|
||||
*
|
||||
* @package core_communication
|
||||
* @copyright 2023 Safat Shahin <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_communication\hook_listener
|
||||
*/
|
||||
class hook_listener_test extends \advanced_testcase {
|
||||
|
||||
use communication_test_helper_trait;
|
||||
use matrix_test_helper_trait;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->resetAfterTest();
|
||||
$this->setup_communication_configs();
|
||||
$this->initialise_mock_server();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_group_communication.
|
||||
*/
|
||||
public function test_create_update_delete_group_communication(): void {
|
||||
global $DB;
|
||||
|
||||
$course = $this->get_course(
|
||||
extrafields: ['groupmode' => SEPARATEGROUPS],
|
||||
);
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
|
||||
// Enrol user1 as teacher.
|
||||
$teacherrole = $DB->get_record(
|
||||
table: 'role',
|
||||
conditions: ['shortname' => 'manager'],
|
||||
);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user1->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $teacherrole->id,
|
||||
userid: $user1->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
|
||||
// Enrol user2 as student.
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user2->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $studentrole->id,
|
||||
userid: $user2->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
|
||||
$group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
|
||||
$context = \context_course::instance($course->id);
|
||||
|
||||
$groupcommunication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $context,
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
expected: communication_processor::class,
|
||||
actual: $groupcommunication->get_processor(),
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
expected: $group->id,
|
||||
actual: $groupcommunication->get_processor()->get_instance_id(),
|
||||
);
|
||||
|
||||
// Task to create room should be added.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(create_and_configure_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
// Task to add members to room should not be there as the room is yet to be created.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(add_members_to_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
// Only users with access to all groups should be added to the room at this point.
|
||||
$groupcommunicationusers = $groupcommunication->get_processor()->get_all_userids_for_instance();
|
||||
$this->assertEquals(
|
||||
expected: [$user1->id],
|
||||
actual: $groupcommunicationusers,
|
||||
);
|
||||
|
||||
// Now delete all the ad-hoc tasks.
|
||||
$DB->delete_records('task_adhoc');
|
||||
|
||||
// Now cann the update group but don't change the group name.
|
||||
groups_update_group($group);
|
||||
|
||||
// No task should be added as nothing changed.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(0, $adhoctask);
|
||||
|
||||
// Now change the group name.
|
||||
$changedgroupname = 'Changedgroupname';
|
||||
$group->name = $changedgroupname;
|
||||
groups_update_group($group);
|
||||
|
||||
// Now one task should be there to update the group room name.
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
$groupcommunication->reload();
|
||||
$this->assertEquals(
|
||||
expected: $changedgroupname,
|
||||
actual: $groupcommunication->get_processor()->get_room_name(),
|
||||
);
|
||||
|
||||
// Now delete the group.
|
||||
groups_delete_group($group->id);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(delete_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test add_members_to_group_room.
|
||||
*/
|
||||
public function test_add_members_to_group_room(): void {
|
||||
global $DB;
|
||||
|
||||
$course = $this->get_course(
|
||||
extrafields: ['groupmode' => SEPARATEGROUPS],
|
||||
);
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
|
||||
// Enrol user1 as teacher.
|
||||
$teacherrole = $DB->get_record(
|
||||
table: 'role',
|
||||
conditions: ['shortname' => 'manager'],
|
||||
);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user1->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $teacherrole->id,
|
||||
userid: $user1->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
|
||||
// Enrol user2 as student.
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user2->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $studentrole->id,
|
||||
userid: $user2->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
|
||||
$group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
|
||||
|
||||
// Now check if the teacher is added to the group room as the teacher has access to all groups.
|
||||
$context = \context_course::instance($course->id);
|
||||
$groupcommunication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $context,
|
||||
);
|
||||
|
||||
// Now the communication instance should not have the student added yet.
|
||||
$this->assertNotContains(
|
||||
needle: $user2->id,
|
||||
haystack: $groupcommunication->get_processor()->get_all_userids_for_instance(),
|
||||
);
|
||||
|
||||
groups_add_member(
|
||||
grouporid: $group,
|
||||
userorid: $user2,
|
||||
);
|
||||
|
||||
// Now it should have the student.
|
||||
$this->assertContains(
|
||||
needle: $user2->id,
|
||||
haystack: $groupcommunication->get_processor()->get_all_userids_for_instance(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the course instances are created properly for course default provider.
|
||||
*/
|
||||
public function test_course_default_provider(): void {
|
||||
$defaultprovider = 'communication_matrix';
|
||||
// Set the default communication for course.
|
||||
set_config(
|
||||
name: 'coursecommunicationprovider',
|
||||
value: $defaultprovider,
|
||||
plugin: 'moodlecourse',
|
||||
);
|
||||
|
||||
// Test that the default communication is created for course mode.
|
||||
$course = $this->get_course();
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$coursecommunication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$this->assertEquals(
|
||||
expected: $defaultprovider,
|
||||
actual: $coursecommunication->get_provider(),
|
||||
);
|
||||
$this->assertEquals(
|
||||
expected: 'core_course',
|
||||
actual: $coursecommunication->get_processor()->get_component(),
|
||||
);
|
||||
$this->assertEquals(
|
||||
expected: $course->id,
|
||||
actual: $coursecommunication->get_processor()->get_instance_id(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update_course_communication.
|
||||
*/
|
||||
public function test_update_course_communication(): void {
|
||||
global $DB;
|
||||
|
||||
// Set up the data with course, group, user etc.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$course = $this->get_course();
|
||||
$group = $this->getDataGenerator()->create_group(record: ['courseid' => $course->id]);
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$teacherrole = $DB->get_record(
|
||||
table: 'role',
|
||||
conditions: ['shortname' => 'teacher'],
|
||||
);
|
||||
$this->getDataGenerator()->enrol_user(
|
||||
userid: $user->id,
|
||||
courseid: $course->id,
|
||||
);
|
||||
role_assign(
|
||||
roleid: $teacherrole->id,
|
||||
userid: $user->id,
|
||||
contextid: $coursecontext->id,
|
||||
);
|
||||
groups_add_member(
|
||||
grouporid: $group->id,
|
||||
userorid: $user->id,
|
||||
);
|
||||
|
||||
// Now test that there is communication instances for the course and the user added for that instance.
|
||||
$coursecommunication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
expected: communication_processor::class,
|
||||
actual: $coursecommunication->get_processor(),
|
||||
);
|
||||
|
||||
// Check the user is added for course communication instance.
|
||||
$courseusers = $coursecommunication->get_processor()->get_all_userids_for_instance();
|
||||
$courseusers = reset($courseusers);
|
||||
$this->assertEquals(
|
||||
expected: $user->id,
|
||||
actual: $courseusers,
|
||||
);
|
||||
|
||||
// Group should not have any instance yet.
|
||||
$groupcommunication = helper::load_by_group(
|
||||
groupid: $group->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
$this->assertNull(actual: $groupcommunication->get_processor());
|
||||
|
||||
// Now update the course.
|
||||
$course->groupmode = SEPARATEGROUPS;
|
||||
$course->selectedcommunication = 'communication_matrix';
|
||||
update_course(data: $course);
|
||||
|
||||
// Now there should be a group communication instance.
|
||||
$groupcommunication->reload();
|
||||
$this->assertInstanceOf(
|
||||
expected: communication_processor::class,
|
||||
actual: $groupcommunication->get_processor(),
|
||||
);
|
||||
|
||||
// The course communication instance must be active.
|
||||
$coursecommunication->reload();
|
||||
$this->assertInstanceOf(
|
||||
expected: communication_processor::class,
|
||||
actual: $coursecommunication->get_processor(),
|
||||
);
|
||||
|
||||
// All the course instance users must be marked as deleted.
|
||||
$coursecommunication->reload();
|
||||
$courseusers = $coursecommunication->get_processor()->get_all_delete_flagged_userids();
|
||||
$courseusers = reset($courseusers);
|
||||
$this->assertEquals(
|
||||
expected: $user->id,
|
||||
actual: $courseusers,
|
||||
);
|
||||
|
||||
// Group instance should have the user.
|
||||
$groupusers = $groupcommunication->get_processor()->get_all_userids_for_instance();
|
||||
$groupusers = reset($groupusers);
|
||||
$this->assertEquals(
|
||||
expected: $user->id,
|
||||
actual: $groupusers,
|
||||
);
|
||||
|
||||
// Now disable the communication instance for the course.
|
||||
$course->selectedcommunication = communication_processor::PROVIDER_NONE;
|
||||
update_course(data: $course);
|
||||
|
||||
// Now both course and group instance should be disabled.
|
||||
$coursecommunication->reload();
|
||||
$this->assertNull(actual: $coursecommunication->get_processor());
|
||||
|
||||
$groupcommunication->reload();
|
||||
$this->assertNull(actual: $groupcommunication->get_processor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create_course_communication_instance.
|
||||
*/
|
||||
public function test_create_course_communication_instance(): void {
|
||||
$course = $this->get_course();
|
||||
$coursecontext = \context_course::instance(courseid: $course->id);
|
||||
$coursecommunication = helper::load_by_course(
|
||||
courseid: $course->id,
|
||||
context: $coursecontext,
|
||||
);
|
||||
|
||||
$processor = $coursecommunication->get_processor();
|
||||
$this->assertEquals(
|
||||
expected: 'communication_matrix',
|
||||
actual: $processor->get_provider(),
|
||||
);
|
||||
$this->assertEquals(
|
||||
expected: 'Sampleroom',
|
||||
actual: $processor->get_room_name(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_course_communication.
|
||||
*/
|
||||
public function test_delete_course_communication(): void {
|
||||
$course = $this->get_course();
|
||||
delete_course(
|
||||
courseorid: $course,
|
||||
showfeedback: false,
|
||||
);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(delete_room_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update of room membership when user changes occur.
|
||||
*/
|
||||
public function test_update_user_room_memberships(): void {
|
||||
global $DB;
|
||||
|
||||
$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);
|
||||
role_assign($teacherrole->id, $user->id, $coursecontext->id);
|
||||
|
||||
$coursecommunication = helper::load_by_course($course->id, $coursecontext);
|
||||
$courseusers = $coursecommunication->get_processor()->get_all_userids_for_instance();
|
||||
$courseusers = reset($courseusers);
|
||||
$this->assertEquals($user->id, $courseusers);
|
||||
|
||||
$user->suspended = 1;
|
||||
user_update_user($user, false);
|
||||
|
||||
$coursecommunication->reload();
|
||||
$courseusers = $coursecommunication->get_processor()->get_all_delete_flagged_userids();
|
||||
$courseusers = reset($courseusers);
|
||||
$this->assertEquals($user->id, $courseusers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deletion of user room memberships when a user is deleted.
|
||||
*/
|
||||
public function test_delete_user_room_memberships(): void {
|
||||
global $DB;
|
||||
|
||||
$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);
|
||||
role_assign($teacherrole->id, $user->id, $coursecontext->id);
|
||||
|
||||
delete_user($user);
|
||||
$coursecommunication = helper::load_by_course($course->id, $coursecontext);
|
||||
$courseusers = $coursecommunication->get_processor()->get_all_userids_for_instance();
|
||||
$this->assertEmpty($courseusers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test user room membership updates with role changes in a course.
|
||||
*/
|
||||
public function test_update_user_membership_for_role_changes(): void {
|
||||
global $DB;
|
||||
|
||||
$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(update_room_membership_task::class);
|
||||
$this->assertCount(1, $adhoctask);
|
||||
|
||||
role_assign($teacherrole->id, $user->id, $coursecontext->id);
|
||||
|
||||
$adhoctask = \core\task\manager::get_adhoc_tasks(update_room_membership_task::class);
|
||||
$this->assertCount(2, $adhoctask);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-12
@@ -4444,18 +4444,7 @@ EOD;
|
||||
*/
|
||||
public function communication_url(): string {
|
||||
global $COURSE;
|
||||
$url = '';
|
||||
if ($COURSE->id !== SITEID) {
|
||||
$comm = \core_communication\api::load_by_instance(
|
||||
context: \core\context\course::instance($COURSE->id),
|
||||
component: 'core_course',
|
||||
instancetype: 'coursecommunication',
|
||||
instanceid: $COURSE->id,
|
||||
);
|
||||
$url = $comm->get_communication_room_url();
|
||||
}
|
||||
|
||||
return !empty($url) ? $url : '';
|
||||
return \core_communication\helper::get_course_communication_url($COURSE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user