Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   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.
  13  //
  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/>.
  16  
  17  namespace core_communication;
  18  
  19  use core\context;
  20  use stdClass;
  21  use stored_file;
  22  
  23  /**
  24   * Class processor to manage the base operations of the providers.
  25   *
  26   * This class is responsible for creating, updating, deleting and loading the communication instance, associated actions.
  27   *
  28   * @package    core_communication
  29   * @copyright  2023 Safat Shahin <safat.shahin@moodle.com>
  30   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  class processor {
  33      /** @var string The magic 'none' provider */
  34      public const PROVIDER_NONE = 'none';
  35  
  36      /** @var int The provider active flag */
  37      public const PROVIDER_ACTIVE = 1;
  38  
  39      /** @var int The provider inactive flag */
  40      public const PROVIDER_INACTIVE = 0;
  41  
  42      /** @var null|communication_provider|user_provider|room_chat_provider|room_user_provider The provider class */
  43      private communication_provider|user_provider|room_chat_provider|room_user_provider|null $provider = null;
  44  
  45      /**
  46       * Communication processor constructor.
  47       *
  48       * @param stdClass $instancedata The instance data object
  49       */
  50      protected function __construct(
  51          private stdClass $instancedata,
  52      ) {
  53          $providercomponent = $this->instancedata->provider;
  54          $providerclass = $this->get_classname_for_provider($providercomponent);
  55          if (!class_exists($providerclass)) {
  56              throw new \moodle_exception('communicationproviderclassnotfound', 'core_communication', '', $providerclass);
  57          }
  58  
  59          if (!is_a($providerclass, communication_provider::class, true)) {
  60              // At the moment we only have one communication provider interface.
  61              // In the future, we may have others, at which point we will support the newest first and
  62              // emit a debugging notice for older ones.
  63              throw new \moodle_exception('communicationproviderclassinvalid', 'core_communication', '', $providerclass);
  64          }
  65  
  66          $this->provider = $providerclass::load_for_instance($this);
  67      }
  68  
  69      /**
  70       * Create communication instance.
  71       *
  72       * @param context $context The context of the item for the instance
  73       * @param string $provider The communication provider
  74       * @param int $instanceid The instance id
  75       * @param string $component The component name
  76       * @param string $instancetype The instance type
  77       * @param string $roomname The room name
  78       * @return processor|null
  79       */
  80      public static function create_instance(
  81          context $context,
  82          string $provider,
  83          int $instanceid,
  84          string $component,
  85          string $instancetype,
  86          string $roomname,
  87      ): ?self {
  88          global $DB;
  89  
  90          if ($provider === self::PROVIDER_NONE) {
  91              return null;
  92          }
  93          $record = (object) [
  94              'contextid' => $context->id,
  95              'provider' => $provider,
  96              'instanceid' => $instanceid,
  97              'component' => $component,
  98              'instancetype' => $instancetype,
  99              'roomname' => $roomname,
 100              'avatarfilename' => null,
 101              'active' => self::PROVIDER_ACTIVE,
 102              'avatarsynced' => 0,
 103          ];
 104          $record->id = $DB->insert_record('communication', $record);
 105  
 106          return new self($record);
 107      }
 108  
 109      /**
 110       * Update the communication instance with any changes.
 111       *
 112       * @param null|int $active Active state of the instance (processor::PROVIDER_ACTIVE or processor::PROVIDER_INACTIVE)
 113       * @param null|string $roomname The room name
 114       */
 115      public function update_instance(
 116          ?string $active = null,
 117          ?string $roomname = null,
 118      ): void {
 119          global $DB;
 120  
 121          if ($active !== null && in_array($active, [self::PROVIDER_ACTIVE, self::PROVIDER_INACTIVE])) {
 122              $this->instancedata->active = $active;
 123          }
 124  
 125          if ($roomname !== null) {
 126              $this->instancedata->roomname = $roomname;
 127          }
 128  
 129          $DB->update_record('communication', $this->instancedata);
 130      }
 131  
 132      /**
 133       * Delete communication data.
 134       */
 135      public function delete_instance(): void {
 136          global $DB;
 137          $DB->delete_records('communication', ['id' => $this->instancedata->id]);
 138      }
 139  
 140      /**
 141       * Get non synced instance user ids for the instance.
 142       *
 143       * @param bool $synced The synced status
 144       * @param bool $deleted The deleted status
 145       * @return array
 146       */
 147      public function get_instance_userids(bool $synced = false, bool $deleted = false): array {
 148          global $DB;
 149          return $DB->get_fieldset_select(
 150              'communication_user',
 151              'userid',
 152              'commid = ? AND synced = ? AND deleted = ?',
 153              [$this->instancedata->id, (int) $synced, (int) $deleted]
 154          );
 155      }
 156  
 157      /**
 158       * Get existing instance user ids.
 159       *
 160       * @return array
 161       */
 162      public function get_all_userids_for_instance(): array {
 163          global $DB;
 164          return $DB->get_fieldset_select(
 165              'communication_user',
 166              'userid',
 167              'commid = ?',
 168              [$this->instancedata->id]
 169          );
 170      }
 171  
 172      /**
 173       * Get all the user ids flagged as deleted.
 174       *
 175       * @return array
 176       */
 177      public function get_all_delete_flagged_userids(): array {
 178          global $DB;
 179          return $DB->get_fieldset_select(
 180              'communication_user',
 181              'userid',
 182              'commid = ? AND deleted = ?',
 183              [$this->instancedata->id, 1]
 184          );
 185      }
 186  
 187      /**
 188       * Create communication user record for mapping and sync.
 189       *
 190       * @param array $userids The user ids
 191       */
 192      public function create_instance_user_mapping(array $userids): void {
 193          global $DB;
 194  
 195          // Check if user ids exits in existing user ids.
 196          $useridstoadd = array_diff($userids, $this->get_all_userids_for_instance());
 197  
 198          foreach ($useridstoadd as $userid) {
 199              $record = (object) [
 200                  'commid' => $this->instancedata->id,
 201                  'userid' => $userid,
 202              ];
 203              $DB->insert_record('communication_user', $record);
 204          }
 205          $this->mark_users_as_not_deleted($userids);
 206      }
 207  
 208      /**
 209       * Mark users as not deleted for the instance.
 210       *
 211       * @param array $userids The user ids
 212       */
 213      public function mark_users_as_not_deleted(array $userids): void {
 214          global $DB;
 215  
 216          if (empty($userids)) {
 217              return;
 218          }
 219  
 220          $DB->set_field_select(
 221              'communication_user',
 222              'deleted',
 223              0,
 224              'commid = ? AND userid IN (' . implode(',', $userids) . ')',
 225              [$this->instancedata->id]
 226          );
 227      }
 228  
 229      /**
 230       * Mark users as synced for the instance.
 231       *
 232       * @param array $userids The user ids
 233       */
 234      public function mark_users_as_synced(array $userids): void {
 235          global $DB;
 236  
 237          if (empty($userids)) {
 238              return;
 239          }
 240  
 241          $DB->set_field_select(
 242              'communication_user',
 243              'synced',
 244              1,
 245              'commid = ? AND userid IN (' . implode(',', $userids) . ')',
 246              [$this->instancedata->id]
 247          );
 248      }
 249  
 250      /**
 251       * Reset users sync flag for the instance.
 252       *
 253       * @param array $userids The user ids
 254       */
 255      public function reset_users_sync_flag(array $userids): void {
 256          global $DB;
 257  
 258          if (empty($userids)) {
 259              return;
 260          }
 261  
 262          $DB->set_field_select(
 263              'communication_user',
 264              'synced',
 265              0,
 266              'commid = ? AND userid IN (' . implode(',', $userids) . ')',
 267              [$this->instancedata->id]
 268          );
 269      }
 270  
 271      /**
 272       * Delete users flag for the instance users.
 273       *
 274       * @param array $userids The user ids
 275       */
 276      public function add_delete_user_flag(array $userids): void {
 277          global $DB;
 278  
 279          if (empty($userids)) {
 280              return;
 281          }
 282  
 283          $DB->set_field_select(
 284              'communication_user',
 285              'deleted',
 286              1,
 287              'commid = ? AND userid IN (' . implode(',', $userids) . ')',
 288              [$this->instancedata->id]
 289          );
 290      }
 291  
 292      /**
 293       * Delete communication user record for userid.
 294       *
 295       * @param array $userids The user ids
 296       */
 297      public function delete_instance_user_mapping(array $userids): void {
 298          global $DB;
 299  
 300          if (empty($userids)) {
 301              return;
 302          }
 303  
 304          $DB->delete_records_select(
 305              'communication_user',
 306              'commid = ? AND userid IN (' . implode(',', $userids) . ')',
 307              [$this->instancedata->id]
 308          );
 309      }
 310  
 311      /**
 312       * Delete communication user record for userid who are not synced.
 313       *
 314       * @param array $userids The user ids
 315       */
 316      public function delete_instance_non_synced_user_mapping(array $userids): void {
 317          global $DB;
 318  
 319          if (empty($userids)) {
 320              return;
 321          }
 322  
 323          $DB->delete_records_select(
 324              'communication_user',
 325              'commid = ? AND userid IN (' . implode(',', $userids) . ') AND synced = ?',
 326              [$this->instancedata->id, 0]
 327          );
 328      }
 329  
 330      /**
 331       * Delete communication user record for instance.
 332       */
 333      public function delete_user_mappings_for_instance(): void {
 334          global $DB;
 335          $DB->delete_records('communication_user', [
 336              'commid' => $this->instancedata->id,
 337          ]);
 338      }
 339  
 340      /**
 341       * Load communication instance by id.
 342       *
 343       * @param int $id The communication instance id
 344       * @return processor|null
 345       */
 346      public static function load_by_id(int $id): ?self {
 347          global $DB;
 348          $record = $DB->get_record('communication', ['id' => $id]);
 349          if ($record && self::is_provider_available($record->provider)) {
 350              return new self($record);
 351          }
 352  
 353          return null;
 354      }
 355  
 356      /**
 357       * Load communication instance by instance id.
 358       *
 359       * @param context $context The context of the item for the instance
 360       * @param string $component The component name
 361       * @param string $instancetype The instance type
 362       * @param int $instanceid The instance id
 363       * @param string|null $provider The provider type - if null will load for this context's active provider.
 364       * @return processor|null
 365       */
 366      public static function load_by_instance(
 367          context $context,
 368          string $component,
 369          string $instancetype,
 370          int $instanceid,
 371          ?string $provider = null,
 372      ): ?self {
 373  
 374          global $DB;
 375  
 376          $params = [
 377              'contextid' => $context->id,
 378              'instanceid' => $instanceid,
 379              'component' => $component,
 380              'instancetype' => $instancetype,
 381          ];
 382  
 383          if ($provider === null) {
 384              // Fetch the active provider in this context.
 385              $params['active'] = 1;
 386          } else {
 387              // Fetch a specific provider in this context (which may be inactive).
 388              $params['provider'] = $provider;
 389          }
 390  
 391          $record = $DB->get_record('communication', $params);
 392          if ($record && self::is_provider_available($record->provider)) {
 393              return new self($record);
 394          }
 395  
 396          return null;
 397      }
 398  
 399      /**
 400       * Check if communication instance is active.
 401       *
 402       * @return bool
 403       */
 404      public function is_instance_active(): bool {
 405          return $this->instancedata->active;
 406      }
 407  
 408      /**
 409       * Get communication provider class name.
 410       *
 411       * @param string $component The component name.
 412       * @return string
 413       */
 414      private function get_classname_for_provider(string $component): string {
 415          return "{$component}\\communication_feature";
 416      }
 417  
 418      /**
 419       * Get communication instance id after creating the instance in communication table.
 420       *
 421       * @return int
 422       */
 423      public function get_id(): int {
 424          return $this->instancedata->id;
 425      }
 426  
 427      /**
 428       * Get the context of the communication instance.
 429       *
 430       * @return context
 431       */
 432      public function get_context(): context {
 433          return context::instance_by_id($this->get_context_id());
 434      }
 435  
 436      /**
 437       * Get the context id of the communication instance.
 438       *
 439       * @return int
 440       */
 441      public function get_context_id(): int {
 442          return $this->instancedata->contextid;
 443      }
 444  
 445      /**
 446       * Get communication instance type.
 447       *
 448       * @return string
 449       */
 450      public function get_instance_type(): string {
 451          return $this->instancedata->instancetype;
 452      }
 453  
 454      /**
 455       * Get communication instance id.
 456       *
 457       * @return int
 458       */
 459      public function get_instance_id(): int {
 460          return $this->instancedata->instanceid;
 461      }
 462  
 463      /**
 464       * Get communication instance component.
 465       *
 466       * @return string
 467       */
 468      public function get_component(): string {
 469          return $this->instancedata->component;
 470      }
 471  
 472      /**
 473       * Get communication provider type.
 474       *
 475       * @return string|null
 476       */
 477      public function get_provider(): ?string {
 478          return $this->instancedata->provider;
 479      }
 480  
 481      /**
 482       * Get room name.
 483       *
 484       * @return string|null
 485       */
 486      public function get_room_name(): ?string {
 487          return $this->instancedata->roomname;
 488      }
 489  
 490      /**
 491       * Get communication instance id.
 492       *
 493       * @return room_chat_provider
 494       */
 495      public function get_room_provider(): room_chat_provider {
 496          $this->require_api_enabled();
 497          $this->require_room_features();
 498          return $this->provider;
 499      }
 500  
 501      /**
 502       * Get communication instance id.
 503       *
 504       * @return user_provider
 505       */
 506      public function get_user_provider(): user_provider {
 507          $this->require_api_enabled();
 508          $this->require_user_features();
 509          return $this->provider;
 510      }
 511  
 512      /**
 513       * Get communication instance id.
 514       *
 515       * @return room_user_provider
 516       */
 517      public function get_room_user_provider(): room_user_provider {
 518          $this->require_api_enabled();
 519          $this->require_room_features();
 520          $this->require_room_user_features();
 521          return $this->provider;
 522      }
 523  
 524      /**
 525       * Set provider specific form definition.
 526       *
 527       * @param string $provider The provider name
 528       * @param \MoodleQuickForm $mform The moodle form
 529       */
 530      public static function set_provider_specific_form_definition(string $provider, \MoodleQuickForm $mform): void {
 531          $providerclass = "{$provider}\\communication_feature";
 532          $providerclass::set_form_definition($mform);
 533      }
 534  
 535      /**
 536       * Get communication instance for form feature.
 537       *
 538       * @return form_provider
 539       */
 540      public function get_form_provider(): form_provider {
 541          $this->requires_form_features();
 542          return $this->provider;
 543      }
 544  
 545      /**
 546       * Get communication instance id.
 547       *
 548       * @return bool
 549       */
 550      public function supports_user_features(): bool {
 551          return ($this->provider instanceof user_provider);
 552      }
 553  
 554      /**
 555       * Get communication instance id.
 556       *
 557       * @return bool
 558       */
 559      public function supports_room_user_features(): bool {
 560          if (!$this->supports_user_features()) {
 561              return false;
 562          }
 563  
 564          if (!$this->supports_room_features()) {
 565              return false;
 566          }
 567  
 568          return ($this->provider instanceof room_user_provider);
 569      }
 570  
 571      /**
 572       * Check form feature available.
 573       *
 574       * @return bool
 575       */
 576      public function requires_form_features(): void {
 577          if (!$this->supports_form_features()) {
 578              throw new \coding_exception('Form features are not supported by the provider');
 579          }
 580      }
 581  
 582      /**
 583       * Check support for form feature.
 584       *
 585       * @return bool
 586       */
 587      public function supports_form_features(): bool {
 588          return ($this->provider instanceof form_provider);
 589      }
 590  
 591      /**
 592       * Get communication instance id.
 593       */
 594      public function require_user_features(): void {
 595          if (!$this->supports_user_features()) {
 596              throw new \coding_exception('User features are not supported by the provider');
 597          }
 598      }
 599  
 600      /**
 601       * Get communication instance id.
 602       *
 603       * @return bool
 604       */
 605      public function supports_room_features(): bool {
 606          return ($this->provider instanceof room_chat_provider);
 607      }
 608  
 609      /**
 610       * Check if communication api is enabled.
 611       */
 612      public function require_api_enabled(): void {
 613          if (!api::is_available()) {
 614              throw new \coding_exception('Communication API is not enabled, please enable it from experimental features');
 615          }
 616      }
 617  
 618      /**
 619       * Get communication instance id.
 620       */
 621      public function require_room_features(): void {
 622          if (!$this->supports_room_features()) {
 623              throw new \coding_exception('room features are not supported by the provider');
 624          }
 625      }
 626  
 627      /**
 628       * Get communication instance id.
 629       */
 630      public function require_room_user_features(): void {
 631          if (!$this->supports_room_user_features()) {
 632              throw new \coding_exception('room features are not supported by the provider');
 633          }
 634      }
 635  
 636      /**
 637       * Get communication instance id.
 638       *
 639       * @return bool|\stored_file
 640       */
 641      public function get_avatar(): ?stored_file {
 642          $fs = get_file_storage();
 643          $file = $fs->get_file(
 644              (\context_system::instance())->id,
 645              'core_communication',
 646              'avatar',
 647              $this->instancedata->id,
 648              '/',
 649              $this->instancedata->avatarfilename,
 650          );
 651  
 652          return $file ?: null;
 653      }
 654  
 655  
 656      /**
 657       * Set the avatar file name.
 658       *
 659       * @param string|null $filename
 660       */
 661      public function set_avatar_filename(?string $filename): void {
 662          global $DB;
 663  
 664          $this->instancedata->avatarfilename = $filename;
 665          $DB->set_field('communication', 'avatarfilename', $filename, ['id' => $this->instancedata->id]);
 666      }
 667  
 668      /**
 669       * Get the avatar file name.
 670       *
 671       * @return string|null
 672       */
 673      public function get_avatar_filename(): ?string {
 674          return $this->instancedata->avatarfilename;
 675      }
 676  
 677      /**
 678       * Check if the avatar has been synced with the provider.
 679       *
 680       * @return bool
 681       */
 682      public function is_avatar_synced(): bool {
 683          return (bool) $this->instancedata->avatarsynced;
 684      }
 685  
 686      /**
 687       * Indicate if the avatar has been synced with the provider.
 688       *
 689       * @param boolean $synced True if avatar has been synced.
 690       */
 691      public function set_avatar_synced_flag(bool $synced): void {
 692          global $DB;
 693  
 694          $this->instancedata->avatarsynced = (int) $synced;
 695          $DB->set_field('communication', 'avatarsynced', (int) $synced, ['id' => $this->instancedata->id]);
 696      }
 697  
 698      /**
 699       * Get a room url.
 700       *
 701       * @return string|null
 702       */
 703      public function get_room_url(): ?string {
 704          if ($this->provider && $this->is_instance_active()) {
 705              return $this->get_room_provider()->get_chat_room_url();
 706          }
 707          return null;
 708      }
 709  
 710      /**
 711       * Is the communication provider enabled and configured, or disabled.
 712       *
 713       * @param string $provider provider component name
 714       * @return bool
 715       */
 716      public static function is_provider_available(string $provider): bool {
 717          if (\core\plugininfo\communication::is_plugin_enabled($provider)) {
 718              $providerclass = "{$provider}\\communication_feature";
 719              return $providerclass::is_configured();
 720          }
 721          return false;
 722      }
 723  }