first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,139 @@
<?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\admin;
use admin_setting;
use core_plugin_manager;
use core_text;
use html_table;
use html_table_row;
use html_writer;
use moodle_url;
/**
* Communication providers manager. Allow enable/disable communication providers and jump to settings.
*
* @package core_communication
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @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'),
'',
''
);
}
public function get_setting(): bool {
return true;
}
public function write_setting($data): string {
// Do not write any setting.
return '';
}
public function output_html($data, $query = ''): string {
global $OUTPUT;
$pluginmanager = core_plugin_manager::instance();
$plugins = $pluginmanager->get_plugins_of_type('communication');
if (empty($plugins)) {
return get_string('nocommunicationprovider', 'core_communication');
}
$table = new html_table();
$table->head = [
get_string('name'),
get_string('enable'),
get_string('settings'),
get_string('uninstallplugin', 'core_admin'),
];
$table->align = ['left', 'center', 'center', 'center'];
$table->attributes['class'] = 'managecommunicationtable generaltable admintable';
$table->data = [];
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
) {
$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'])
);
} else {
$class = 'dimmed_text';
$hideshow = html_writer::link(
$actionurl->out(false, ['action' => 'enable']),
$OUTPUT->pix_icon('t/show', get_string('enable'), 'moodle', ['class' => 'iconsmall'])
);
}
$settings = '';
if ($plugin->get_settings_url()) {
$settings = html_writer::link($plugin->get_settings_url(), get_string('settings'));
}
$uninstall = '';
if (
$uninstallurl = core_plugin_manager::instance()->get_uninstall_url(
'communication_' . $plugin->name,
'manage'
)
) {
$uninstall = html_writer::link($uninstallurl, get_string('uninstallplugin', 'core_admin'));
}
$row = new html_table_row([$strtypename, $hideshow, $settings, $uninstall]);
if ($class) {
$row->attributes['class'] = $class;
}
$table->data[] = $row;
}
return highlight($query, html_writer::table($table));
}
public function is_related($query): bool {
if (parent::is_related($query)) {
return true;
}
$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
) {
return true;
}
}
return false;
}
}
+833
View File
@@ -0,0 +1,833 @@
<?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 core\context;
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\remove_members_from_room;
use core_communication\task\synchronise_provider_task;
use core_communication\task\update_room_task;
use core_communication\task\update_room_membership_task;
use stdClass;
/**
* Class api is the public endpoint of the communication api. This class is the point of contact for api usage.
*
* Communication api allows to add ad-hoc tasks to the queue to perform actions on the communication providers. This api will
* not allow any immediate actions to be performed on the communication providers. It will only add the tasks to the queue. The
* exception has been made for deletion of members in case of deleting the user. This is because the user will not be available.
* The member management api part allows run actions immediately if required.
*
* Communication api does allow to have form elements related to communication api in the required forms. This is done by using
* the form_definition method. This method will add the form elements to the form.
*
* @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 api {
/**
* @var null|processor $communication The communication settings object
*/
private ?processor $communication;
/**
* Communication handler constructor to manage and handle all communication related actions.
*
* This class is the entrypoint for all kinda usages.
* It will be used by the other api to manage the communication providers.
*
* @param context $context The context of the item for the instance
* @param string $component The component of the item for the instance
* @param string $instancetype The type of the item for the instance
* @param int $instanceid The id of the instance
* @param string|null $provider The provider type - if null will load for this context's active provider.
*
*/
private function __construct(
private context $context,
private string $component,
private string $instancetype,
private int $instanceid,
private ?string $provider = null,
) {
$this->communication = processor::load_by_instance(
context: $context,
component: $component,
instancetype: $instancetype,
instanceid: $instanceid,
provider: $provider,
);
}
/**
* Get the communication processor object.
*
* @param context $context The context of the item for the instance
* @param string $component The component of the item for the instance
* @param string $instancetype The type of the item for the instance
* @param int $instanceid The id of the instance
* @param string|null $provider The provider type - if null will load for this context's active provider.
* @return api
*/
public static function load_by_instance(
context $context,
string $component,
string $instancetype,
int $instanceid,
?string $provider = null,
): self {
return new self(
context: $context,
component: $component,
instancetype: $instancetype,
instanceid: $instanceid,
provider: $provider,
);
}
/**
* Reload in the internal instance data.
*/
public function reload(): void {
$this->communication = processor::load_by_instance(
context: $this->context,
component: $this->component,
instancetype: $this->instancetype,
instanceid: $this->instanceid,
provider: $this->provider,
);
}
/**
* Return the underlying communication processor object.
*
* @return ?processor
*/
public function get_processor(): ?processor {
return $this->communication;
}
/**
* Return the room provider.
*
* @return \core_communication\room_chat_provider
*/
public function get_room_provider(): \core_communication\room_chat_provider {
return $this->communication->get_room_provider();
}
/**
* Return the user provider.
*
* @return \core_communication\user_provider
*/
public function get_user_provider(): \core_communication\user_provider {
return $this->communication->get_user_provider();
}
/**
* Return the room user provider.
*
* @return \core_communication\room_user_provider
*/
public function get_room_user_provider(): \core_communication\room_user_provider {
return $this->communication->get_room_user_provider();
}
/**
* Return the form provider.
*
* @return \core_communication\form_provider
*/
public function get_form_provider(): \core_communication\form_provider {
return $this->communication->get_form_provider();
}
/**
* Check if the communication api is enabled.
*/
public static function is_available(): bool {
return (bool) get_config('core', 'enablecommunicationsubsystem');
}
/**
* Get the communication room url.
*
* @return string|null
*/
public function get_communication_room_url(): ?string {
return $this->communication?->get_room_url();
}
/**
* Get the list of plugins for form selection.
*
* @return array
*/
public static function get_communication_plugin_list_for_form(): array {
// Add the option to have communication disabled.
$selection[processor::PROVIDER_NONE] = get_string('nocommunicationselected', 'communication');
$communicationplugins = \core\plugininfo\communication::get_enabled_plugins();
foreach ($communicationplugins as $pluginname => $notusing) {
$provider = 'communication_' . $pluginname;
if (processor::is_provider_available($provider)) {
$selection[$provider] = get_string('pluginname', 'communication_' . $pluginname);
}
}
return $selection;
}
/**
* Get the enabled communication providers and default provider according to the selected provider.
*
* @param string|null $selecteddefaulprovider
* @return array
*/
public static function get_enabled_providers_and_default(string $selecteddefaulprovider = null): array {
$communicationproviders = self::get_communication_plugin_list_for_form();
$defaulprovider = processor::PROVIDER_NONE;
if (!empty($selecteddefaulprovider) && array_key_exists($selecteddefaulprovider, $communicationproviders)) {
$defaulprovider = $selecteddefaulprovider;
}
return [$communicationproviders, $defaulprovider];
}
/**
* Define the form elements for the communication api.
* This method will be called from the form definition method of the instance.
*
* @param \MoodleQuickForm $mform The form element
* @param string $selectdefaultcommunication The default selected communication provider in the form field
*/
public function form_definition(
\MoodleQuickForm $mform,
string $selectdefaultcommunication = processor::PROVIDER_NONE
): void {
global $PAGE;
[$communicationproviders, $defaulprovider] = self::get_enabled_providers_and_default($selectdefaultcommunication);
$PAGE->requires->js_call_amd('core_communication/providerchooser', 'init');
// List the communication providers.
$mform->addElement(
'select',
'selectedcommunication',
get_string('selectcommunicationprovider', 'communication'),
$communicationproviders,
['data-communicationchooser-field' => 'selector'],
);
$mform->addHelpButton('selectedcommunication', 'selectcommunicationprovider', 'communication');
$mform->setDefault('selectedcommunication', $defaulprovider);
$mform->registerNoSubmitButton('updatecommunicationprovider');
$mform->addElement(
'submit',
'updatecommunicationprovider',
'update communication',
['data-communicationchooser-field' => 'updateButton', 'class' => 'd-none']
);
// Just a placeholder for the communication options.
$mform->addElement('hidden', 'addcommunicationoptionshere');
$mform->setType('addcommunicationoptionshere', PARAM_BOOL);
}
/**
* Set the form definitions for the plugins.
*
* @param \MoodleQuickForm $mform The moodle form
* @param string $provider The provider name
*/
public function form_definition_for_provider(\MoodleQuickForm $mform, string $provider = processor::PROVIDER_NONE): void {
if ($provider === processor::PROVIDER_NONE) {
return;
}
// Room name for the communication provider.
$mform->insertElementBefore(
$mform->createElement(
'text',
$provider . 'roomname',
get_string('communicationroomname', 'communication'),
'maxlength="100" size="20"'
),
'addcommunicationoptionshere'
);
$mform->setType($provider . 'roomname', PARAM_TEXT);
$mform->insertElementBefore(
$mform->createElement(
'static',
'communicationroomnameinfo',
'',
get_string('communicationroomnameinfo', 'communication'),
),
'addcommunicationoptionshere',
);
processor::set_provider_specific_form_definition($provider, $mform);
}
/**
* Get the avatar file.
*
* @return null|\stored_file
*/
public function get_avatar(): ?\stored_file {
$filename = $this->communication->get_avatar_filename();
if ($filename === null) {
return null;
}
$fs = get_file_storage();
$args = (array) $this->get_avatar_filerecord($filename);
return $fs->get_file(...$args) ?: null;
}
/**
* Get the avatar file record for the avatar for filesystem.
*
* @param string $filename The filename of the avatar
* @return stdClass
*/
protected function get_avatar_filerecord(string $filename): stdClass {
return (object) [
'contextid' => \core\context\system::instance()->id,
'component' => 'core_communication',
'filearea' => 'avatar',
'itemid' => $this->communication->get_id(),
'filepath' => '/',
'filename' => $filename,
];
}
/**
* Get the avatar file.
*
* If null is set, then delete the old area file and set the avatarfilename to null.
* This will make sure the plugin api deletes the avatar from the room.
*
* @param null|\stored_file $avatar The stored file for the avatar
* @return bool
*/
public function set_avatar(?\stored_file $avatar): bool {
$currentfilename = $this->communication->get_avatar_filename();
if ($avatar === null && empty($currentfilename)) {
return false;
}
$currentfilerecord = $this->get_avatar();
if ($avatar && $currentfilerecord) {
$currentfilehash = $currentfilerecord->get_contenthash();
$updatedfilehash = $avatar->get_contenthash();
// No update required.
if ($currentfilehash === $updatedfilehash) {
return false;
}
}
$context = \core\context\system::instance();
$fs = get_file_storage();
$fs->delete_area_files(
$context->id,
'core_communication',
'avatar',
$this->communication->get_id()
);
if ($avatar) {
$fs->create_file_from_storedfile(
$this->get_avatar_filerecord($avatar->get_filename()),
$avatar,
);
$this->communication->set_avatar_filename($avatar->get_filename());
} else {
$this->communication->set_avatar_filename(null);
}
// Indicate that we need to sync the avatar when the update task is run.
$this->communication->set_avatar_synced_flag(false);
return true;
}
/**
* A helper to fetch the room name
*
* @return string
*/
public function get_room_name(): string {
if (!$this->communication) {
return '';
}
return $this->communication->get_room_name();
}
/**
* Set the form data if the data is already available.
*
* @param \stdClass $instance The instance object
*/
public function set_data(\stdClass $instance): void {
if (!empty($instance->id) && $this->communication) {
$instance->selectedcommunication = $this->communication->get_provider();
$roomnameidentifier = $this->get_provider() . 'roomname';
$instance->$roomnameidentifier = $this->communication->get_room_name();
$this->communication->get_form_provider()->set_form_data($instance);
}
}
/**
* Get the communication provider.
*
* @return string
*/
public function get_provider(): string {
if (!$this->communication) {
return '';
}
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
* @param bool $queue Queue the task for the provider room or not
*/
public function configure_room_and_membership_by_provider(
string $provider,
stdClass $instance,
string $communicationroomname,
array $users,
?\stored_file $instanceimage = null,
bool $queue = true,
): 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,
queue: $queue,
);
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,
instance: $instance,
queue: $queue,
);
}
// 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: $queue,
);
$queueusertask = false;
} else {
// Otherwise update the room.
$this->update_room(
active: processor::PROVIDER_ACTIVE,
communicationroomname: $communicationroomname,
avatar: $instanceimage,
instance: $instance,
queue: $queue,
);
$queueusertask = true;
}
// Now add the members.
$this->add_members_to_room(
userids: $users,
queue: $queueusertask,
);
}
/**
* Create a communication ad-hoc task for create operation.
* This method will add a task to the queue to create the room.
*
* @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,
provider: $this->provider,
instanceid: $this->instanceid,
component: $this->component,
instancetype: $this->instancetype,
roomname: $communicationroomname,
);
// Update provider record from form data.
if ($instance !== null) {
$this->communication->get_form_provider()->save_form_data($instance);
}
// Set the avatar.
if (!empty($avatar)) {
$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,
);
}
/**
* Create a communication ad-hoc task for update operation.
* This method will add a task to the queue to update the room.
*
* @param null|int $active The selected active state of the provider
* @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;
}
$roomnamechange = null;
$activestatuschange = null;
// Check if the room name is being changed.
if (
$communicationroomname !== null &&
$communicationroomname !== $this->communication->get_room_name()
) {
$roomnamechange = $communicationroomname;
}
// Check if the active status of the provider is being changed.
if (
$active !== null &&
$active !== $this->communication->is_instance_active()
) {
$activestatuschange = $active;
}
if ($roomnamechange !== null || $activestatuschange !== null) {
$this->communication->update_instance(
active: $active,
roomname: $communicationroomname,
);
}
// Update provider record from form data.
if ($instance !== null) {
$this->communication->get_form_provider()->save_form_data($instance);
}
// Update the avatar.
// 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(
$this->communication,
);
}
/**
* Create a communication ad-hoc task for delete operation.
* This method will add a task to the queue to delete the room.
*/
public function delete_room(): void {
if ($this->communication !== null) {
// Add the ad-hoc task to remove the room data from the communication table and associated provider actions.
delete_room_task::queue(
$this->communication,
);
}
}
/**
* Create a communication ad-hoc task for add members operation and add the user mapping.
*
* This method will add a task to the queue to add 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 add_members_to_room(array $userids, bool $queue = true): void {
// No communication object? something not done right.
if (!$this->communication) {
return;
}
// No user IDs or this provider does not manage users? No action required.
if (empty($userids) || !$this->communication->supports_user_features()) {
return;
}
$this->communication->create_instance_user_mapping($userids);
if ($queue) {
add_members_to_room_task::queue(
$this->communication
);
}
}
/**
* 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.
*
* This method will add a task to the queue to remove the room users.
*
* @param array $userids The user ids to remove from the room
* @param bool $queue Whether to queue the task or not
*/
public function remove_members_from_room(array $userids, bool $queue = true): void {
// No communication object? something not done right.
if (!$this->communication) {
return;
}
$provider = $this->communication->get_provider();
if ($provider === processor::PROVIDER_NONE) {
return;
}
// No user IDs or this provider does not manage users? No action required.
if (empty($userids) || !$this->communication->supports_user_features()) {
return;
}
$this->communication->add_delete_user_flag($userids);
if ($queue) {
remove_members_from_room::queue(
$this->communication
);
}
}
/**
* 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.
*/
public function show_communication_room_status_notification(): void {
// No communication, no room.
if (!$this->communication) {
return;
}
if ($this->communication->get_provider() === processor::PROVIDER_NONE) {
return;
}
$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 constants::COMMUNICATION_STATUS_PENDING:
\core\notification::add($message, \core\notification::INFO);
unset_user_preference($roomreadypreference);
break;
case constants::COMMUNICATION_STATUS_READY:
if (empty(get_user_preferences($roomreadypreference))) {
\core\notification::add($message, \core\notification::SUCCESS);
set_user_preference($roomreadypreference, true);
}
break;
}
}
/**
* Add the task to sync the provider data with local Moodle data.
*/
public function sync_provider(): void {
// No communication, return.
if (!$this->communication) {
return;
}
if ($this->communication->get_provider() === processor::PROVIDER_NONE) {
return;
}
synchronise_provider_task::queue(
$this->communication
);
}
}
@@ -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;
/**
* A base communication provider.
*
* This interface should be used to declare support for the instantiation method for communication providers.
*
* Every communication provider must, as a minimum, implement this provider.
*
* @package core_communication
* @copyright 2023 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface communication_provider {
/**
* A base communication provider.
*
* @param processor $communication The communication object
*/
public static function load_for_instance(processor $communication): self;
/**
* Check if the provider is configured or not.
*
* This method is intended to check if the plugin have got any settings and if all the settings are set properly.
* This checking helps to reduce errors in future when a communication instance is added for the provider and not configured.
*/
public static function is_configured(): bool;
}
+45
View File
@@ -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 <safat.shahin@moodle.com>
* @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,140 @@
<?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/>.
/**
* Configure communication for a given instance - the form definition.
*
* @package core_communication
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_communication\form;
use core\context;
use stdClass;
defined('MOODLE_INTERNAL') || die();
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.
*/
protected $communication;
/**
* Class constructor
*
* @param context $context Context object
* @param int|null $instanceid Instance ID
* @param string|null $instancetype Instance type
* @param string|null $component Component name
* @param string|null $selectedcommunication Selected communication service (provider)
* @param stdClass|null $instancedata Instance data
*/
public function __construct(
context $context,
?int $instanceid = null,
?string $instancetype = null,
?string $component = null,
?string $selectedcommunication = null,
?stdClass $instancedata = null,
) {
parent::__construct(
null,
[
'context' => $context,
'instanceid' => $instanceid,
'instancetype' => $instancetype,
'component' => $component,
'selectedcommunication' => $selectedcommunication,
'instancedata' => $instancedata,
],
);
}
/**
* Defines the form fields.
*/
public function definition() {
$mform = $this->_form;
$context = $this->_customdata['context'];
$instanceid = $this->_customdata['instanceid'];
$instancetype = $this->_customdata['instancetype'];
$component = $this->_customdata['component'];
$instancedata = $this->_customdata['instancedata'];
// Add communication plugins to the form.
$this->communication = \core_communication\api::load_by_instance(
context: $context,
component: $component,
instancetype: $instancetype,
instanceid: $instanceid,
provider: $this->_customdata['selectedcommunication'],
);
$this->communication->form_definition($mform);
$this->communication->set_data($instancedata);
$this->set_form_definition_for_provider();
// Form buttons.
$buttonarray = [];
$buttonarray[] = $mform->createElement('submit', 'saveandreturn', get_string('savechanges'));
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', [' '], false);
$mform->closeHeaderBefore('buttonar');
// Hidden elements.
$mform->addElement('hidden', 'contextid', $context->id);
$mform->setType('contextid', PARAM_INT);
$mform->addElement('hidden', 'instanceid', $instanceid);
$mform->setType('instanceid', PARAM_INT);
$mform->addElement('hidden', 'instancetype', $instancetype);
$mform->setType('instancetype', PARAM_TEXT);
$mform->addElement('hidden', 'component', $component);
$mform->setType('component', PARAM_TEXT);
// Finally set the current form data.
$this->set_data($instancedata);
}
/**
* Defines the requested/current provider
*
* Get the selected communication service (provider),
* and then use it to show the provider form fields.
*/
private function set_form_definition_for_provider(): void {
$instancedata = $this->_customdata['instancedata'];
if ($selectedcommunication = $this->_customdata['selectedcommunication']) {
// First is to check whether the selected communication was selected from the form.
$provider = $selectedcommunication;
} else if (isset($instancedata->selectedcommunication)) {
// If the form is not yet submitted, get the value from the DB.
$provider = $instancedata->selectedcommunication;
} else {
// Otherwise, set to PROVIDER_NONE.
$provider = \core_communication\processor::PROVIDER_NONE;
}
$this->communication->form_definition_for_provider($this->_form, $provider);
}
}
+49
View File
@@ -0,0 +1,49 @@
<?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;
/**
* Interface form_provider to manage communication provider form options from provider plugins.
*
* Every provider plugin should implement this class to return the implemented form elements for custom data.
*
* @package core_communication
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @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.
*
* @param \stdClass $instance The actual instance to set the data
*/
public function save_form_data(\stdClass $instance): void;
/**
* Set the form data to the instance if any data is available.
*
* @param \stdClass $instance The actual instance to set the data
*/
public function set_form_data(\stdClass $instance): void;
/**
* Set the form definitions.
*
* @param \MoodleQuickForm $mform The form object
*/
public static function set_form_definition(\MoodleQuickForm $mform): void;
}
+575
View File
@@ -0,0 +1,575 @@
<?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 <safat.shahin@moodle.com>
* @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.');
}
$coursecontext = \context_course::instance(courseid: $course->id);
$communication = self::load_by_course(
courseid: $course->id,
context: $coursecontext,
);
// Check we have communication correctly set up before proceeding.
if ($communication->get_processor() === null) {
return;
}
// Get the group mode for this course.
$groupmode = $course->groupmode ?? get_course(courseid: $course->id)->groupmode;
if ((int)$groupmode === NOGROUPS) {
// If group mode is not set, then just handle the update normally for these users.
$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.
* @param bool $onlyactive Only enrolments that are active (e.g. not suspended).
* @return array
*/
public static function get_enrolled_users_for_course(stdClass $course, bool $onlyactive = true): array {
global $CFG;
require_once($CFG->libdir . '/enrollib.php');
return array_column(
enrol_get_course_users(courseid: $course->id, onlyactive: $onlyactive),
'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();
}
$roomnameidenfier = $provider . 'roomname';
// Determine the communication room name if none was provided and add it to the course data.
if (empty($course->$roomnameidenfier)) {
$course->$roomnameidenfier = $coursecommunication->get_room_name();
if (empty($course->$roomnameidenfier)) {
$course->$roomnameidenfier = $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: $course->$roomnameidenfier,
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,
);
// Now handle the course communication according to the provider.
$communication->configure_room_and_membership_by_provider(
provider: $provider,
instance: $course,
communicationroomname: $course->$roomnameidenfier,
users: $enrolledusers,
instanceimage: $courseimage,
queue: false,
);
// As the course is in group mode, make sure no users are in the course room.
$communication->reload();
$communication->remove_all_members_from_room();
}
}
/**
* 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) {
$groupusers = array_column(
groups_get_members(groupid: $coursegroup->id),
'id',
);
// Filter out users who are not active in this course.
$enrolledusers = self::get_enrolled_users_for_course(course: $course);
$groupuserstoadd = array_intersect($groupusers, $enrolledusers);
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,
);
$roomnameidenfier = $provider . 'roomname';
$communicationroomname = self::format_group_room_name(
baseroomname: $course->$roomnameidenfier,
groupname: $coursegroup->name,
);
$communication->configure_room_and_membership_by_provider(
provider: $provider,
instance: $course,
communicationroomname: $communicationroomname,
users: $groupuserstoadd,
);
}
}
/**
* Format a group communication room name with the following syntax: 'Group A (Course 1)'.
*
* @param string $baseroomname The base room name.
* @param string $groupname The group name.
*/
public static function format_group_room_name(
string $baseroomname,
string $groupname
): string {
return get_string('communicationgrouproomnameformat', 'core_communication', [
'groupname' => $groupname,
'baseroomname' => $baseroomname,
]);
}
}
+665
View File
@@ -0,0 +1,665 @@
<?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_deleted;
use core_enrol\hook\after_enrol_instance_status_updated;
use core_enrol\hook\after_user_enrolled;
use core_enrol\hook\before_user_enrolment_updated;
use core_enrol\hook\before_user_enrolment_removed;
use core_course\hook\after_course_created;
use core_course\hook\before_course_deleted;
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_updated;
/**
* Hook listener for communication api.
*
* @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 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,
);
// Check we have communication correctly set up before proceeding.
if ($coursecommunication->get_processor() === null) {
return;
}
$communication = api::load_by_instance(
context: $coursecontext,
component: constants::GROUP_COMMUNICATION_COMPONENT,
instancetype: constants::GROUP_COMMUNICATION_INSTANCETYPE,
instanceid: $group->id,
provider: $coursecommunication->get_provider(),
);
$communicationroomname = helper::format_group_room_name(
baseroomname: $coursecommunication->get_room_name(),
groupname: $group->name,
);
$communication->create_and_configure_room(
communicationroomname: $communicationroomname,
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,
);
// Get the course communication instance so we can extract the base room name.
$coursecommunication = helper::load_by_course(
courseid: $course->id,
context: $coursecontext,
);
$communicationroomname = helper::format_group_room_name(
baseroomname: $coursecommunication->get_room_name(),
groupname: $group->name,
);
// If the name didn't change, then we don't need to update the room.
if ($communicationroomname === $communication->get_room_name()) {
return;
}
$communication->update_room(
active: processor::PROVIDER_ACTIVE,
communicationroomname: $communicationroomname,
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,
);
// Filter out users who are not active in this course.
$enrolledusers = helper::get_enrolled_users_for_course($course, true);
$userids = array_intersect($hook->userids, $enrolledusers);
$communication->add_members_to_room(
userids: $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_deleted $hook The course deleted hook.
*/
public static function delete_course_communication(
before_course_deleted $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_updated $hook The user updated hook.
*/
public static function update_user_room_memberships(
before_user_updated $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,
);
if ($communication->get_processor() !== null) {
$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,
);
if ($communication->get_processor() !== null) {
$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_deleted $hook The enrol instance deleted hook.
*/
public static function remove_communication_memberships_for_enrol_instance_deletion(
before_enrol_instance_deleted $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_updated $hook The user enrolment updated hook.
*/
public static function update_communication_membership_for_updated_user_enrolment(
before_user_enrolment_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;
}
$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_removed $hook The user unenrolled hook.
*/
public static function remove_communication_membership_for_unenrolled_user(
before_user_enrolment_removed $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',
);
}
}
@@ -0,0 +1,70 @@
<?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\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\userlist;
/**
* Privacy Subsystem for core_communication implementing null_provider.
*
* @package core_communication
* @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\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', [
'commid' => 'privacy:metadata:communication_user:commid',
'userid' => 'privacy:metadata:communication_user:userid',
'synced' => 'privacy:metadata:communication_user:synced',
], 'privacy:metadata:communication_user');
return $collection;
}
public static function get_contexts_for_userid(int $userid): contextlist {
return new contextlist();
}
public static function export_user_data(approved_contextlist $contextlist) {
// None of the core communication tables should be exported.
}
public static function delete_data_for_all_users_in_context(\context $context) {
// None of the data from these tables should be deleted.
}
public static function delete_data_for_user(approved_contextlist $contextlist) {
// None of the data from these tables should be deleted.
}
public static function get_users_in_context(userlist $userlist) {
// Don't add any users.
}
public static function delete_data_for_users(approved_userlist $userlist) {
// None of the data from these tables should be deleted.
}
}
+763
View File
@@ -0,0 +1,763 @@
<?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 core\context;
use stdClass;
use stored_file;
/**
* Class processor to manage the base operations of the providers.
*
* This class is responsible for creating, updating, deleting and loading the communication instance, associated actions.
*
* @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 processor {
/** @var string The magic 'none' provider */
public const PROVIDER_NONE = 'none';
/** @var int The provider active flag */
public const PROVIDER_ACTIVE = 1;
/** @var int The provider inactive flag */
public const PROVIDER_INACTIVE = 0;
/**
* @var communication_provider|room_chat_provider|room_user_provider|synchronise_provider|user_provider|null The provider class
*/
private communication_provider|user_provider|room_chat_provider|room_user_provider|synchronise_provider|null $provider = null;
/**
* Communication processor constructor.
*
* @param stdClass $instancedata The instance data object
*/
protected function __construct(
private stdClass $instancedata,
) {
$providercomponent = $this->instancedata->provider;
$providerclass = $this->get_classname_for_provider($providercomponent);
if (!class_exists($providerclass)) {
throw new \moodle_exception('communicationproviderclassnotfound', 'core_communication', '', $providerclass);
}
if (!is_a($providerclass, communication_provider::class, true)) {
// At the moment we only have one communication provider interface.
// In the future, we may have others, at which point we will support the newest first and
// emit a debugging notice for older ones.
throw new \moodle_exception('communicationproviderclassinvalid', 'core_communication', '', $providerclass);
}
$this->provider = $providerclass::load_for_instance($this);
}
/**
* Create communication instance.
*
* @param context $context The context of the item for the instance
* @param string $provider The communication provider
* @param int $instanceid The instance id
* @param string $component The component name
* @param string $instancetype The instance type
* @param string $roomname The room name
* @return processor|null
*/
public static function create_instance(
context $context,
string $provider,
int $instanceid,
string $component,
string $instancetype,
string $roomname,
): ?self {
global $DB;
if ($provider === self::PROVIDER_NONE) {
return null;
}
$record = (object) [
'contextid' => $context->id,
'provider' => $provider,
'instanceid' => $instanceid,
'component' => $component,
'instancetype' => $instancetype,
'roomname' => $roomname,
'avatarfilename' => null,
'active' => self::PROVIDER_ACTIVE,
'avatarsynced' => 0,
];
$record->id = $DB->insert_record('communication', $record);
return new self($record);
}
/**
* Update the communication instance with any changes.
*
* @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(
?string $active = null,
?string $roomname = null,
): void {
global $DB;
if ($active !== null && in_array($active, [self::PROVIDER_ACTIVE, self::PROVIDER_INACTIVE])) {
$this->instancedata->active = $active;
}
if ($roomname !== null) {
$this->instancedata->roomname = $roomname;
}
$DB->update_record('communication', $this->instancedata);
}
/**
* Delete communication data.
*/
public function delete_instance(): void {
global $DB;
$DB->delete_records('communication', ['id' => $this->instancedata->id]);
}
/**
* Get non synced instance user ids for the instance.
*
* @param bool $synced The synced status
* @param bool $deleted The deleted status
* @return array
*/
public function get_instance_userids(bool $synced = false, bool $deleted = false): array {
global $DB;
return $DB->get_fieldset_select(
'communication_user',
'userid',
'commid = ? AND synced = ? AND deleted = ?',
[$this->instancedata->id, (int) $synced, (int) $deleted]
);
}
/**
* Get existing instance user ids.
*
* @return array
*/
public function get_all_userids_for_instance(): array {
global $DB;
return $DB->get_fieldset_select(
'communication_user',
'userid',
'commid = ?',
[$this->instancedata->id]
);
}
/**
* 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.
*
* @param array $userids The user ids
*/
public function create_instance_user_mapping(array $userids): void {
global $DB;
// Check if user ids exits in existing user ids.
$useridstoadd = array_diff($userids, $this->get_all_userids_for_instance());
foreach ($useridstoadd as $userid) {
$record = (object) [
'commid' => $this->instancedata->id,
'userid' => $userid,
];
$DB->insert_record('communication_user', $record);
}
$this->mark_users_as_not_deleted($userids);
}
/**
* Mark users as not deleted for the instance.
*
* @param array $userids The user ids
*/
public function mark_users_as_not_deleted(array $userids): void {
global $DB;
if (empty($userids)) {
return;
}
$DB->set_field_select(
'communication_user',
'deleted',
0,
'commid = ? AND userid IN (' . implode(',', $userids) . ')',
[$this->instancedata->id]
);
}
/**
* Mark users as synced for the instance.
*
* @param array $userids The user ids
*/
public function mark_users_as_synced(array $userids): void {
global $DB;
if (empty($userids)) {
return;
}
$DB->set_field_select(
'communication_user',
'synced',
1,
'commid = ? AND userid IN (' . implode(',', $userids) . ')',
[$this->instancedata->id]
);
}
/**
* Reset users sync flag for the instance.
*
* @param array $userids The user ids
*/
public function reset_users_sync_flag(array $userids): void {
global $DB;
if (empty($userids)) {
return;
}
$DB->set_field_select(
'communication_user',
'synced',
0,
'commid = ? AND userid IN (' . implode(',', $userids) . ')',
[$this->instancedata->id]
);
}
/**
* Delete users flag for the instance users.
*
* @param array $userids The user ids
*/
public function add_delete_user_flag(array $userids): void {
global $DB;
if (empty($userids)) {
return;
}
$DB->set_field_select(
'communication_user',
'deleted',
1,
'commid = ? AND userid IN (' . implode(',', $userids) . ')',
[$this->instancedata->id]
);
}
/**
* Delete communication user record for userid.
*
* @param array $userids The user ids
*/
public function delete_instance_user_mapping(array $userids): void {
global $DB;
if (empty($userids)) {
return;
}
$DB->delete_records_select(
'communication_user',
'commid = ? AND userid IN (' . implode(',', $userids) . ')',
[$this->instancedata->id]
);
}
/**
* Delete communication user record for userid who are not synced.
*
* @param array $userids The user ids
*/
public function delete_instance_non_synced_user_mapping(array $userids): void {
global $DB;
if (empty($userids)) {
return;
}
$DB->delete_records_select(
'communication_user',
'commid = ? AND userid IN (' . implode(',', $userids) . ') AND synced = ?',
[$this->instancedata->id, 0]
);
}
/**
* Delete communication user record for instance.
*/
public function delete_user_mappings_for_instance(): void {
global $DB;
$DB->delete_records('communication_user', [
'commid' => $this->instancedata->id,
]);
}
/**
* Load communication instance by id.
*
* @param int $id The communication instance id
* @return processor|null
*/
public static function load_by_id(int $id): ?self {
global $DB;
$record = $DB->get_record('communication', ['id' => $id]);
if ($record && self::is_provider_available($record->provider)) {
return new self($record);
}
return null;
}
/**
* Load communication instance by instance id.
*
* @param context $context The context of the item for the instance
* @param string $component The component name
* @param string $instancetype The instance type
* @param int $instanceid The instance id
* @param string|null $provider The provider type - if null will load for this context's active provider.
* @return processor|null
*/
public static function load_by_instance(
context $context,
string $component,
string $instancetype,
int $instanceid,
?string $provider = null,
): ?self {
global $DB;
$params = [
'contextid' => $context->id,
'instanceid' => $instanceid,
'component' => $component,
'instancetype' => $instancetype,
];
if ($provider === null) {
// Fetch the active provider in this context.
$params['active'] = 1;
} else {
// Fetch a specific provider in this context (which may be inactive).
$params['provider'] = $provider;
}
$record = $DB->get_record('communication', $params);
if ($record && self::is_provider_available($record->provider)) {
return new self($record);
}
return null;
}
/**
* Check if communication instance is active.
*
* @return bool
*/
public function is_instance_active(): bool {
return $this->instancedata->active;
}
/**
* Get communication provider class name.
*
* @param string $component The component name.
* @return string
*/
private function get_classname_for_provider(string $component): string {
return "{$component}\\communication_feature";
}
/**
* Get communication instance id after creating the instance in communication table.
*
* @return int
*/
public function get_id(): int {
return $this->instancedata->id;
}
/**
* Get the context of the communication instance.
*
* @return context
*/
public function get_context(): context {
return context::instance_by_id($this->get_context_id());
}
/**
* Get the context id of the communication instance.
*
* @return int
*/
public function get_context_id(): int {
return $this->instancedata->contextid;
}
/**
* Get communication instance type.
*
* @return string
*/
public function get_instance_type(): string {
return $this->instancedata->instancetype;
}
/**
* 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 {
return $this->instancedata->component;
}
/**
* Get communication provider type.
*
* @return string|null
*/
public function get_provider(): ?string {
return $this->instancedata->provider;
}
/**
* Get room name.
*
* @return string|null
*/
public function get_room_name(): ?string {
return $this->instancedata->roomname;
}
/**
* Get provider active status.
*
* @return int
*/
public function get_provider_status(): int {
return $this->instancedata->active;
}
/**
* Get communication instance id.
*
* @return room_chat_provider
*/
public function get_room_provider(): room_chat_provider {
$this->require_api_enabled();
$this->require_room_features();
return $this->provider;
}
/**
* Get communication instance id.
*
* @return user_provider
*/
public function get_user_provider(): user_provider {
$this->require_api_enabled();
$this->require_user_features();
return $this->provider;
}
/**
* Get communication instance id.
*
* @return room_user_provider
*/
public function get_room_user_provider(): room_user_provider {
$this->require_api_enabled();
$this->require_room_features();
$this->require_room_user_features();
return $this->provider;
}
/**
* Get the provider after checking if it supports sync features.
*
* @return synchronise_provider
*/
public function get_sync_provider(): synchronise_provider {
$this->require_api_enabled();
$this->require_sync_provider_features();
return $this->provider;
}
/**
* Set provider specific form definition.
*
* @param string $provider The provider name
* @param \MoodleQuickForm $mform The moodle form
*/
public static function set_provider_specific_form_definition(string $provider, \MoodleQuickForm $mform): void {
$providerclass = "{$provider}\\communication_feature";
$providerclass::set_form_definition($mform);
}
/**
* Get communication instance for form feature.
*
* @return form_provider
*/
public function get_form_provider(): form_provider {
$this->requires_form_features();
return $this->provider;
}
/**
* Get communication instance id.
*
* @return bool
*/
public function supports_user_features(): bool {
return ($this->provider instanceof user_provider);
}
/**
* Get communication instance id.
*
* @return bool
*/
public function supports_room_user_features(): bool {
if (!$this->supports_user_features()) {
return false;
}
if (!$this->supports_room_features()) {
return false;
}
return ($this->provider instanceof room_user_provider);
}
/**
* Check form feature available.
*
* @return bool
*/
public function requires_form_features(): void {
if (!$this->supports_form_features()) {
throw new \coding_exception('Form features are not supported by the provider');
}
}
/**
* Check support for form feature.
*
* @return bool
*/
public function supports_form_features(): bool {
return ($this->provider instanceof form_provider);
}
/**
* Get communication instance id.
*/
public function require_user_features(): void {
if (!$this->supports_user_features()) {
throw new \coding_exception('User features are not supported by the provider');
}
}
/**
* Get communication instance id.
*
* @return bool
*/
public function supports_room_features(): bool {
return ($this->provider instanceof room_chat_provider);
}
/**
* Check if communication api is enabled.
*/
public function require_api_enabled(): void {
if (!api::is_available()) {
throw new \coding_exception('Communication API is not enabled, please enable it from experimental features');
}
}
/**
* Get communication instance id.
*/
public function require_room_features(): void {
if (!$this->supports_room_features()) {
throw new \coding_exception('room features are not supported by the provider');
}
}
/**
* Get communication instance id.
*/
public function require_room_user_features(): void {
if (!$this->supports_room_user_features()) {
throw new \coding_exception('room features are not supported by the provider');
}
}
/**
* Check if the provider supports sync features.
*
* @return bool whether the provider supports sync features or not
*/
public function supports_sync_provider_features(): bool {
return ($this->provider instanceof synchronise_provider);
}
/**
* Check if the provider supports sync features when required.
*/
public function require_sync_provider_features(): void {
if (!$this->supports_sync_provider_features()) {
throw new \coding_exception('sync features are not supported by the provider');
}
}
/**
* Get communication instance id.
*
* @return bool|\stored_file
*/
public function get_avatar(): ?stored_file {
$fs = get_file_storage();
$file = $fs->get_file(
(\context_system::instance())->id,
'core_communication',
'avatar',
$this->instancedata->id,
'/',
$this->instancedata->avatarfilename,
);
return $file ?: null;
}
/**
* Set the avatar file name.
*
* @param string|null $filename
*/
public function set_avatar_filename(?string $filename): void {
global $DB;
$this->instancedata->avatarfilename = $filename;
$DB->set_field('communication', 'avatarfilename', $filename, ['id' => $this->instancedata->id]);
}
/**
* Get the avatar file name.
*
* @return string|null
*/
public function get_avatar_filename(): ?string {
return $this->instancedata->avatarfilename;
}
/**
* Check if the avatar has been synced with the provider.
*
* @return bool
*/
public function is_avatar_synced(): bool {
return (bool) $this->instancedata->avatarsynced;
}
/**
* Indicate if the avatar has been synced with the provider.
*
* @param boolean $synced True if avatar has been synced.
*/
public function set_avatar_synced_flag(bool $synced): void {
global $DB;
$this->instancedata->avatarsynced = (int) $synced;
$DB->set_field('communication', 'avatarsynced', (int) $synced, ['id' => $this->instancedata->id]);
}
/**
* Get a room url.
*
* @return string|null
*/
public function get_room_url(): ?string {
if ($this->provider && $this->is_instance_active()) {
return $this->get_room_provider()->get_chat_room_url();
}
return null;
}
/**
* Is the communication provider enabled and configured, or disabled.
*
* @param string $provider provider component name
* @return bool
*/
public static function is_provider_available(string $provider): bool {
if (\core\plugininfo\communication::is_plugin_enabled($provider)) {
$providerclass = "{$provider}\\communication_feature";
return $providerclass::is_configured();
}
return false;
}
}
@@ -0,0 +1,50 @@
<?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;
/**
* Class communication_room_base to manage the room operations of communication providers.
*
* Every plugin that supports room operation must implement/extend this class in the plugin.
*
* @package core_communication
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @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.
*/
public function create_chat_room(): bool;
/**
* Update a provider room when a instance is updated.
*/
public function update_chat_room(): bool;
/**
* Delete a provider room when a instance is deleted.
*/
public function delete_chat_room(): bool;
/**
* Generate a room url if there is a room.
*
* @return string|null
*/
public function get_chat_room_url(): ?string;
}
@@ -0,0 +1,47 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_communication;
/**
* Class communication_user_base to manage communication provider users.
*
* @package core_communication
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface room_user_provider {
/**
* Add members to communication room.
*
* @param array $userids The user ids to be added
*/
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.
*
* @param array $userids The user ids to be removed
*/
public function remove_members_from_room(array $userids): void;
}
@@ -0,0 +1,32 @@
<?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;
/**
* Interface synchronise_provider to check if the users and room is synced properly for the communication provider.
*
* @package core_communication
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface synchronise_provider {
/**
* Ensure the users are in sync with the communication provider.
*/
public function synchronise_room_members(): void;
}
@@ -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 add_members_to_room_task to add the task to add members to the room and execute the task to action the addition.
*
* @package core_communication
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @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();
// 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()->add_members_to_room($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);
}
}
@@ -0,0 +1,74 @@
<?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 create_and_configure_room_task to add a task to create a room and execute the task to action the creation.
*
* this task will be queued by the communication api and will use the communication handler api to action the creation.
*
* @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 create_and_configure_room_task extends adhoc_task {
public function execute() {
$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;
}
if (!$communication->is_instance_active()) {
mtrace("Skipping room creation because the instance is not active");
return;
}
// If the room is created successfully, add members to the room if supported by the provider.
if ($communication->get_room_provider()->create_chat_room() && $communication->supports_user_features()) {
add_members_to_room_task::queue(
$communication
);
}
}
/**
* 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);
}
}
@@ -0,0 +1,71 @@
<?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 delete_room_task to add a task to delete a room and execute the task to action the deletion.
*
* this task will be queued by the communication api and will use the communication handler api to action the deletion.
*
* @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 delete_room_task extends adhoc_task {
public function execute() {
$data = $this->get_custom_data();
$communication = processor::load_by_id($data->id);
if ($communication === null) {
mtrace("Skipping room creation because the instance does not exist");
return;
}
// First remove the members from the room.
$communication->get_room_user_provider()->remove_members_from_room($communication->get_instance_userids(true, true));
// 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));
// Now delete the room.
if ($communication->get_room_provider()->delete_chat_room()) {
$communication->delete_instance();
}
}
/**
* 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);
}
}
@@ -0,0 +1,66 @@
<?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 remove_members_from_room to add the task to remove members to the room and execute the task to action the removal.
*
* @package core_communication
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @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();
// 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()->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));
}
/**
* 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);
}
}
@@ -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 synchronise_provider_task to add a task to synchronise the provider and execute the task to action the synchronisation.
*
* @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 synchronise_provider_task extends adhoc_task {
public function execute() {
$data = $this->get_custom_data();
$communication = processor::load_by_id($data->id);
if ($communication === null) {
mtrace("Skipping provider sync because the instance does not exist");
return;
}
// Sync room members for the instance.
$communication->get_sync_provider()->synchronise_room_members();
}
/**
* 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);
}
}
@@ -0,0 +1,61 @@
<?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\scheduled_task;
use core_communication\api;
use core_communication\processor;
/**
* Class synchronise_providers to add a task to synchronise the providers and execute the task to action the synchronisation.
*
* @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 synchronise_providers_task extends scheduled_task {
public function get_name() {
return get_string('synchroniseproviders', 'core_communication');
}
public function execute() {
// Communication is not enabled? nothing to do.
if (!api::is_available()) {
return;
}
global $DB;
$communicationinstances = $DB->get_records(
table: 'communication',
conditions: ['active' => processor::PROVIDER_ACTIVE],
);
foreach ($communicationinstances as $communicationinstance) {
$communication = \core_communication\api::load_by_instance(
context: \context::instance_by_id($communicationinstance->contextid),
component: $communicationinstance->component,
instancetype: $communicationinstance->instancetype,
instanceid: $communicationinstance->instanceid,
);
$processor = $communication->get_processor();
if ($processor->supports_sync_provider_features()) {
$communication->sync_provider();
}
}
}
}
@@ -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);
}
}
@@ -0,0 +1,64 @@
<?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_task to add a task to update a room and execute the task to action the update.
*
* this task will be queued by the communication api and will use the communication handler api to action the updates.
*
* @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_task extends adhoc_task {
public function execute() {
$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_provider()->update_chat_room();
}
/**
* 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);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?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;
/**
* Class communication_user_base to manage communication provider users.
*
* @package core_communication
* @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface user_provider {
/**
* Create members.
*
* @param array $userid The users ids to be created
*/
public function create_members(array $userid): void;
}