MDL-78738 core_communication: Set avatar from a stored_file
[moodle.git] / communication / classes / api.php
blobcb29f86ec969c157b1caa51c0fbf6df78bfd6fc5
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
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.
8 //
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;
24 use stdClass;
26 /**
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
41 class api {
43 /**
44 * @var null|processor $communication The communication settings object
46 private ?processor $communication;
48 /**
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
63 ) {
64 $this->communication = processor::load_by_instance(
65 $this->component,
66 $this->instancetype,
67 $this->instanceid,
71 /**
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
77 * @return api
79 public static function load_by_instance(
80 string $component,
81 string $instancetype,
82 int $instanceid
83 ): self {
84 return new self($component, $instancetype, $instanceid);
87 /**
88 * Check if the communication api is enabled.
90 public static function is_available(): bool {
91 return (bool) get_config('core', 'enablecommunicationsubsystem');
94 /**
95 * Get the communication room url.
97 * @return string|null
99 public function get_communication_room_url(): ?string {
100 return $this->communication?->get_room_url();
104 * Get the list of plugins for form selection.
106 * @return array
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);
115 return $selection;
119 * Get the enabled communication providers and default provider according to the selected provider.
121 * @param string|null $selecteddefaulprovider
122 * @return array
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
143 ): void {
144 global $PAGE;
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.
154 $mform->addElement(
155 'select',
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
179 * @return void
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(
188 'text',
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
205 * @return stdClass
207 public function get_avatar_filerecord(string $filename): stdClass {
208 return (object) [
209 'contextid' => \context_system::instance()->id,
210 'component' => 'core_communication',
211 'filearea' => 'avatar',
212 'filename' => $filename,
213 'filepath' => '/',
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
226 * @return bool
228 public function set_avatar(?\stored_file $avatar): bool {
229 $currentfilename = $this->communication->get_avatar_filename();
230 if ($avatar === null && empty($currentfilename)) {
231 return false;
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) {
241 return false;
245 $context = \context_system::instance();
247 $fs = get_file_storage();
248 $fs->delete_area_files(
249 $context->id,
250 'core_communication',
251 'avatar',
252 $this->communication->get_id()
255 if ($avatar) {
256 $fs->create_file_from_storedfile(
257 $this->get_avatar_filerecord($avatar->get_filename()),
258 $avatar,
260 $this->communication->set_avatar_filename($avatar->get_filename());
261 } else {
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);
268 return true;
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.
288 * @return string
290 public function get_provider(): string {
291 if (!$this->communication) {
292 return '';
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,
311 ): void {
312 if ($selectedcommunication !== processor::PROVIDER_NONE && $selectedcommunication !== '') {
313 // Create communication record.
314 $this->communication = processor::create_instance(
315 $selectedcommunication,
316 $this->instanceid,
317 $this->component,
318 $this->instancetype,
319 $communicationroomname,
322 // Update provider record from form data.
323 if ($instance !== null) {
324 $this->communication->get_form_provider()->save_form_data($instance);
327 // Set the avatar.
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,
353 ): void {
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) {
374 return;
377 // Add ad-hoc task to update the provider room if the room name changed.
378 // TODO add efficiency considering dynamic fields.
379 if (
380 $previousprovider === $selectedprovider
382 update_room_task::queue(
383 $this->communication,
385 } else if (
386 $previousprovider !== $selectedprovider
388 // Add ad-hoc task to create the provider room.
389 create_and_configure_room_task::queue(
390 $this->communication,
393 } else {
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) {
423 return;
426 // No userids? don't bother doing anything.
427 if (empty($userids)) {
428 return;
431 $this->communication->create_instance_user_mapping($userids);
433 if ($queue) {
434 add_members_to_room_task::queue(
435 $this->communication
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) {
451 return;
454 if ($this->communication->get_provider() === processor::PROVIDER_NONE) {
455 return;
458 // No user ids? don't bother doing anything.
459 if (empty($userids)) {
460 return;
463 $this->communication->add_delete_user_flag($userids);
465 if ($queue) {
466 remove_members_from_room::queue(
467 $this->communication
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) {
478 return;
481 if ($this->communication->get_provider() === processor::PROVIDER_NONE) {
482 return;
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) {
490 case 'pending':
492 \core\notification::add($message, \core\notification::INFO);
493 break;
495 case 'ready':
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);
504 break;