2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 namespace core_communication
;
19 use core_communication\task\add_members_to_room_task
;
20 use core_communication\task\create_and_configure_room_task
;
21 use core_communication\task\delete_room_task
;
22 use core_communication\task\remove_members_from_room
;
23 use core_communication\task\update_room_task
;
27 * Class api is the public endpoint of the communication api. This class is the point of contact for api usage.
29 * Communication api allows to add ad-hoc tasks to the queue to perform actions on the communication providers. This api will
30 * not allow any immediate actions to be performed on the communication providers. It will only add the tasks to the queue. The
31 * exception has been made for deletion of members in case of deleting the user. This is because the user will not be available.
32 * The member management api part allows run actions immediately if required.
34 * Communication api does allow to have form elements related to communication api in the required forms. This is done by using
35 * the form_definition method. This method will add the form elements to the form.
37 * @package core_communication
38 * @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 * @var null|processor $communication The communication settings object
46 private ?processor
$communication;
49 * Communication handler constructor to manage and handle all communication related actions.
51 * This class is the entrypoint for all kinda usages.
52 * It will be used by the other api to manage the communication providers.
54 * @param string $component The component of the item for the instance
55 * @param string $instancetype The type of the item for the instance
56 * @param int $instanceid The id of the instance
59 private function __construct(
60 private string $component,
61 private string $instancetype,
62 private int $instanceid
64 $this->communication
= processor
::load_by_instance(
72 * Get the communication processor object.
74 * @param string $component The component of the item for the instance
75 * @param string $instancetype The type of the item for the instance
76 * @param int $instanceid The id of the instance
79 public static function load_by_instance(
84 return new self($component, $instancetype, $instanceid);
88 * Check if the communication api is enabled.
90 public static function is_available(): bool {
91 return (bool) get_config('core', 'enablecommunicationsubsystem');
95 * Get the communication room url.
99 public function get_communication_room_url(): ?
string {
100 return $this->communication?
->get_room_url();
104 * Get the list of plugins for form selection.
108 public static function get_communication_plugin_list_for_form(): array {
109 // Add the option to have communication disabled.
110 $selection[processor
::PROVIDER_NONE
] = get_string('nocommunicationselected', 'communication');
111 $communicationplugins = \core\plugininfo\communication
::get_enabled_plugins();
112 foreach ($communicationplugins as $pluginname => $notusing) {
113 $selection['communication_' . $pluginname] = get_string('pluginname', 'communication_'. $pluginname);
119 * Get the enabled communication providers and default provider according to the selected provider.
121 * @param string|null $selecteddefaulprovider
124 public static function get_enabled_providers_and_default(string $selecteddefaulprovider = null): array {
125 $communicationproviders = self
::get_communication_plugin_list_for_form();
126 $defaulprovider = processor
::PROVIDER_NONE
;
127 if (!empty($selecteddefaulprovider) && array_key_exists($selecteddefaulprovider, $communicationproviders)) {
128 $defaulprovider = $selecteddefaulprovider;
130 return [$communicationproviders, $defaulprovider];
134 * Define the form elements for the communication api.
135 * This method will be called from the form definition method of the instance.
137 * @param \MoodleQuickForm $mform The form element
138 * @param string $selectdefaultcommunication The default selected communication provider in the form field
140 public function form_definition(
141 \MoodleQuickForm
$mform,
142 string $selectdefaultcommunication = processor
::PROVIDER_NONE
146 list($communicationproviders, $defaulprovider) = self
::
147 get_enabled_providers_and_default($selectdefaultcommunication);
149 $PAGE->requires
->js_call_amd('core_communication/providerchooser', 'init');
151 $mform->addElement('header', 'communication', get_string('communication', 'communication'));
153 // List the communication providers.
156 'selectedcommunication',
157 get_string('seleccommunicationprovider', 'communication'),
158 $communicationproviders,
159 ['data-communicationchooser-field' => 'selector'],
161 $mform->addHelpButton('selectedcommunication', 'seleccommunicationprovider', 'communication');
162 $mform->setDefault('selectedcommunication', $defaulprovider);
164 $mform->registerNoSubmitButton('updatecommunicationprovider');
165 $mform->addElement('submit',
166 'updatecommunicationprovider',
167 'update communication',
168 ['data-communicationchooser-field' => 'updateButton', 'class' => 'd-none',]);
170 // Just a placeholder for the communication options.
171 $mform->addElement('hidden', 'addcommunicationoptionshere');
172 $mform->setType('addcommunicationoptionshere', PARAM_BOOL
);
176 * Set the form definitions for the plugins.
178 * @param \MoodleQuickForm $mform
181 public function form_definition_for_provider(\MoodleQuickForm
$mform): void
{
182 $provider = $mform->getElementValue('selectedcommunication');
184 if ($provider[0] !== processor
::PROVIDER_NONE
) {
185 // Room name for the communication provider.
186 $mform->insertElementBefore(
187 $mform->createElement(
189 'communicationroomname',
190 get_string('communicationroomname', 'communication'), 'maxlength="100" size="20"'),
191 'addcommunicationoptionshere'
193 $mform->addHelpButton('communicationroomname', 'communicationroomname', 'communication');
194 $mform->setType('communicationroomname', PARAM_TEXT
);
196 processor
::set_proider_form_definition($provider[0], $mform);
202 * Get the avatar file record for the avatar for filesystem.
204 * @param string $filename The filename of the avatar
207 public function get_avatar_filerecord(string $filename): stdClass
{
209 'contextid' => \context_system
::instance()->id
,
210 'component' => 'core_communication',
211 'filearea' => 'avatar',
212 'filename' => $filename,
214 'itemid' => $this->communication
->get_id(),
220 * Get the avatar file.
222 * If null is set, then delete the old area file and set the avatarfilename to null.
223 * This will make sure the plugin api deletes the avatar from the room.
225 * @param null|\stored_file $avatar The stored file for the avatar
228 public function set_avatar(?\stored_file
$avatar): bool {
229 $currentfilename = $this->communication
->get_avatar_filename();
230 if ($avatar === null && empty($currentfilename)) {
234 $currentfilerecord = $this->communication
->get_avatar();
235 if ($avatar && !empty($currentfilerecord)) {
236 $currentfilehash = $currentfilerecord->get_contenthash();
237 $updatedfilehash = $avatar->get_contenthash();
239 // No update required.
240 if ($currentfilehash === $updatedfilehash) {
245 $context = \context_system
::instance();
247 $fs = get_file_storage();
248 $fs->delete_area_files(
250 'core_communication',
252 $this->communication
->get_id()
256 $fs->create_file_from_storedfile(
257 $this->get_avatar_filerecord($avatar->get_filename()),
260 $this->communication
->set_avatar_filename($avatar->get_filename());
262 $this->communication
->set_avatar_filename(null);
265 // Indicate that we need to sync the avatar when the update task is run.
266 $this->communication
->set_avatar_synced_flag(false);
272 * Set the form data if the data is already available.
274 * @param \stdClass $instance The instance object
276 public function set_data(\stdClass
$instance): void
{
277 if (!empty($instance->id
) && $this->communication
) {
278 $instance->selectedcommunication
= $this->communication
->get_provider();
279 $instance->communicationroomname
= $this->communication
->get_room_name();
281 $this->communication
->get_form_provider()->set_form_data($instance);
286 * Get the communication provider.
290 public function get_provider(): string {
291 if (!$this->communication
) {
294 return $this->communication
->get_provider();
298 * Create a communication ad-hoc task for create operation.
299 * This method will add a task to the queue to create the room.
301 * @param string $selectedcommunication The selected communication provider
302 * @param string $communicationroomname The communication room name
303 * @param null|\stored_file $avatar The stored file for the avatar
304 * @param \stdClass|null $instance The actual instance object
306 public function create_and_configure_room(
307 string $selectedcommunication,
308 string $communicationroomname,
309 ?\stored_file
$avatar = null,
310 ?\stdClass
$instance = null,
312 if ($selectedcommunication !== processor
::PROVIDER_NONE
&& $selectedcommunication !== '') {
313 // Create communication record.
314 $this->communication
= processor
::create_instance(
315 $selectedcommunication,
319 $communicationroomname,
322 // Update provider record from form data.
323 if ($instance !== null) {
324 $this->communication
->get_form_provider()->save_form_data($instance);
328 if (!empty($avatar)) {
329 $this->set_avatar($avatar);
332 // Add ad-hoc task to create the provider room.
333 create_and_configure_room_task
::queue(
334 $this->communication
,
340 * Create a communication ad-hoc task for update operation.
341 * This method will add a task to the queue to update the room.
343 * @param string $selectedprovider The selected communication provider
344 * @param string $communicationroomname The communication room name
345 * @param null|\stored_file $avatar The stored file for the avatar
346 * @param \stdClass|null $instance The actual instance object
348 public function update_room(
349 string $selectedprovider,
350 string $communicationroomname,
351 ?\stored_file
$avatar = null,
352 ?\stdClass
$instance = null,
355 // Existing object found, let's update the communication record and associated actions.
356 if ($this->communication
!== null) {
357 // Get the previous data to compare for update.
358 $previousroomname = $this->communication
->get_room_name();
359 $previousprovider = $this->communication
->get_provider();
361 // Update communication record.
362 $this->communication
->update_instance($selectedprovider, $communicationroomname);
364 // Update provider record from form data.
365 if ($instance !== null) {
366 $this->communication
->get_form_provider()->save_form_data($instance);
369 // Update the avatar.
370 $imageupdaterequired = $this->set_avatar($avatar);
372 // If the provider is none, we don't need to do anything from room point of view.
373 if ($this->communication
->get_provider() === processor
::PROVIDER_NONE
) {
377 // Add ad-hoc task to update the provider room if the room name changed.
378 // TODO add efficiency considering dynamic fields.
380 $previousprovider === $selectedprovider
382 update_room_task
::queue(
383 $this->communication
,
386 $previousprovider !== $selectedprovider
388 // Add ad-hoc task to create the provider room.
389 create_and_configure_room_task
::queue(
390 $this->communication
,
394 // The instance didn't have any communication record, so create one.
395 $this->create_and_configure_room($selectedprovider, $communicationroomname, $avatar, $instance);
400 * Create a communication ad-hoc task for delete operation.
401 * This method will add a task to the queue to delete the room.
403 public function delete_room(): void
{
404 if ($this->communication
!== null) {
405 // Add the ad-hoc task to remove the room data from the communication table and associated provider actions.
406 delete_room_task
::queue(
407 $this->communication
,
413 * Create a communication ad-hoc task for add members operation and add the user mapping.
415 * This method will add a task to the queue to add the room users.
417 * @param array $userids The user ids to add to the room
418 * @param bool $queue Whether to queue the task or not
420 public function add_members_to_room(array $userids, bool $queue = true): void
{
421 // No communication object? something not done right.
422 if (!$this->communication
) {
426 // No userids? don't bother doing anything.
427 if (empty($userids)) {
431 $this->communication
->create_instance_user_mapping($userids);
434 add_members_to_room_task
::queue(
441 * Create a communication ad-hoc task for remove members operation or action immediately.
443 * This method will add a task to the queue to remove the room users.
445 * @param array $userids The user ids to remove from the room
446 * @param bool $queue Whether to queue the task or not
448 public function remove_members_from_room(array $userids, bool $queue = true): void
{
449 // No communication object? something not done right.
450 if (!$this->communication
) {
454 if ($this->communication
->get_provider() === processor
::PROVIDER_NONE
) {
458 // No user ids? don't bother doing anything.
459 if (empty($userids)) {
463 $this->communication
->add_delete_user_flag($userids);
466 remove_members_from_room
::queue(
473 * Display the communication room status notification.
475 public function show_communication_room_status_notification(): void
{
476 // No communication, no room.
477 if (!$this->communication
) {
481 if ($this->communication
->get_provider() === processor
::PROVIDER_NONE
) {
485 $roomstatus = $this->get_communication_room_url() ?
'ready' : 'pending';
486 $pluginname = get_string('pluginname', $this->get_provider());
487 $message = get_string('communicationroom' . $roomstatus, 'communication', $pluginname);
489 switch ($roomstatus) {
492 \core\notification
::add($message, \core\notification
::INFO
);
496 // We only show the ready notification once per user.
497 // We check this with a custom user preference.
498 $roomreadypreference = "{$this->component}_{$this->instancetype}_{$this->instanceid}_room_ready";
500 if (empty(get_user_preferences($roomreadypreference))) {
501 \core\notification
::add($message, \core\notification
::SUCCESS
);
502 set_user_preference($roomreadypreference, true);