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.

Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   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 mod_bigbluebuttonbn;
  18  
  19  use cm_info;
  20  use context;
  21  use context_course;
  22  use context_module;
  23  use core\dml\table;
  24  use mod_bigbluebuttonbn\local\config;
  25  use mod_bigbluebuttonbn\local\helpers\files;
  26  use mod_bigbluebuttonbn\local\helpers\roles;
  27  use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
  28  use moodle_url;
  29  use stdClass;
  30  
  31  /**
  32   * Instance record for mod_bigbluebuttonbn.
  33   *
  34   * @package   mod_bigbluebuttonbn
  35   * @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
  36   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class instance {
  39  
  40      /** @var int Defines an instance type that includes room and recordings */
  41      public const TYPE_ALL = 0;
  42  
  43      /** @var int Defines an instance type that includes only room */
  44      public const TYPE_ROOM_ONLY = 1;
  45  
  46      /** @var int Defines an instance type that includes only recordings */
  47      public const TYPE_RECORDING_ONLY = 2;
  48  
  49      /** @var cm_info The cm_info object relating to the instance */
  50      protected $cm;
  51  
  52      /** @var stdClass The course that the instance is in */
  53      protected $course;
  54  
  55      /** @var stdClass The instance data for the instance */
  56      protected $instancedata;
  57  
  58      /** @var context The current context */
  59      protected $context;
  60  
  61      /** @var array The list of participants */
  62      protected $participantlist;
  63  
  64      /** @var int The current groupid if set */
  65      protected $groupid;
  66  
  67      /** @var int The course module id. */
  68      protected $cmid;
  69  
  70      /**
  71       * Instance constructor.
  72       *
  73       * Never called directly. Use self::get_from_instanceid or self::get_from_cmid.
  74       *
  75       * @param int $cmid
  76       * @param stdClass $course
  77       * @param stdClass $instancedata
  78       * @param int|null $groupid
  79       */
  80      private function __construct(int $cmid, stdClass $course, stdClass $instancedata, ?int $groupid = null) {
  81          $this->cmid = $cmid;
  82          $this->cm = null; // This is not retrieved later, whenever we call ::get_cm() it will be retrieved.
  83          $this->course = $course;
  84          $this->instancedata = $instancedata;
  85          $this->groupid = $groupid;
  86      }
  87  
  88      /**
  89       * Get a group instance of the specified instance.
  90       *
  91       * @param self $originalinstance
  92       * @param int $groupid
  93       * @return null|self
  94       */
  95      public static function get_group_instance_from_instance(self $originalinstance, int $groupid): ?self {
  96          return new self(
  97              $originalinstance->get_cm_id(),
  98              $originalinstance->get_course(),
  99              $originalinstance->get_instance_data(),
 100              $groupid
 101          );
 102      }
 103  
 104      /**
 105       * Get the instance information from an instance id.
 106       *
 107       * @param int $instanceid The id from the bigbluebuttonbn table
 108       * @return null|self
 109       */
 110      public static function get_from_instanceid(int $instanceid): ?self {
 111          return self::get_instance_info_retriever($instanceid, self::IDTYPE_INSTANCEID);
 112      }
 113  
 114      /**
 115       * Get the instance information from a cmid.
 116       *
 117       * @param int $cmid
 118       * @return null|self
 119       */
 120      public static function get_from_cmid(int $cmid): ?self {
 121          return self::get_instance_info_retriever($cmid, self::IDTYPE_CMID);
 122      }
 123  
 124      /**
 125       * Get the instance information from a cmid.
 126       */
 127      const IDTYPE_CMID = 0;
 128      /**
 129       * Get the instance information from an id.
 130       */
 131      const IDTYPE_INSTANCEID = 1;
 132  
 133      /**
 134       * Helper to get the instance information from an id.
 135       *
 136       * Used by self::get_from_id and self::get_cmid.
 137       *
 138       * @param int $id The id to look for.
 139       * @param int $idtype self::IDTYPE_CMID or self::IDTYPE_INSTANCEID
 140       * @return null|self
 141       * @throws \moodle_exception
 142       */
 143      private static function get_instance_info_retriever(int $id, int $idtype = self::IDTYPE_INSTANCEID): ?self {
 144          global $DB;
 145  
 146          if (!in_array($idtype, [self::IDTYPE_CMID, self::IDTYPE_INSTANCEID])) {
 147              throw new \moodle_exception('Invalid idtype');
 148          }
 149  
 150          [
 151              'coursetable' => $coursetable,
 152              'courseselect' => $courseselect,
 153              'coursefrom' => $coursefrom,
 154              'cmfrom' => $cmfrom,
 155              'cmselect' => $cmselect,
 156              'bbbtable' => $bbbtable,
 157              'bbbselect' => $bbbselect,
 158              'bbbfrom' => $bbbfrom,
 159              'subplugintables' => $subplugintables,
 160              'subpluginselects' => $subpluginselects,
 161              'subpluginfroms' => $subpluginfroms
 162          ] = self::get_tables_info();
 163  
 164          $select = implode(', ', array_merge([$courseselect, $bbbselect, $cmselect], $subpluginselects));
 165          $subpluginsleftjoins = '';
 166          foreach ($subpluginfroms as $tablealias => $subpluginfrom) {
 167              $subpluginsleftjoins .= "LEFT JOIN {$subpluginfrom} ON bbb.id = {$tablealias}.bigbluebuttonbnid\n";
 168          }
 169          $params = [
 170              'modname' => 'bigbluebuttonbn',
 171              'bbbid' => $id,
 172          ];
 173          $where = 'bbb.id = :bbbid';
 174          $from = <<<EOF
 175                  {$bbbfrom}
 176                  INNER JOIN {$cmfrom} ON cm.instance = bbb.id
 177                  INNER JOIN {$coursefrom} ON c.id = cm.course
 178                  INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
 179  EOF;
 180          if ($idtype == self::IDTYPE_CMID) {
 181              $params['cmid'] = $id;
 182              $where = 'cm.id = :cmid';
 183              $from = <<<EOF
 184                  {$cmfrom}
 185                  INNER JOIN {$coursefrom} ON c.id = cm.course
 186                  INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
 187                  INNER JOIN {$bbbfrom} ON cm.instance = bbb.id
 188  EOF;
 189          }
 190  
 191          $sql = "SELECT {$select} FROM {$from} {$subpluginsleftjoins} WHERE {$where}";
 192  
 193          $result = $DB->get_record_sql($sql, $params);
 194  
 195          if (empty($result)) {
 196              return null;
 197          }
 198  
 199          $course = $coursetable->extract_from_result($result);
 200          $instancedata = $bbbtable->extract_from_result($result);
 201          self::extract_plugin_table_info($instancedata, $result, $subplugintables);
 202          if ($idtype == self::IDTYPE_INSTANCEID) {
 203              $cmid = $result->cmid;
 204          } else {
 205              $cmid = $id;
 206          }
 207          return new self($cmid, $course, $instancedata);
 208      }
 209  
 210      /**
 211       * Get the instance information from a meetingid.
 212       *
 213       * If a group is specified in the meetingid then this will also be set.
 214       *
 215       * @param string $meetingid
 216       * @return null|self
 217       */
 218      public static function get_from_meetingid(string $meetingid): ?self {
 219          $matches = self::parse_meetingid($meetingid);
 220  
 221          $instance = self::get_from_instanceid($matches['instanceid']);
 222  
 223          if ($instance && array_key_exists('groupid', $matches)) {
 224              $instance->set_group_id($matches['groupid']);
 225          }
 226  
 227          return $instance;
 228      }
 229  
 230      /**
 231       * Parse a meetingID for key data.
 232       *
 233       * @param string $meetingid
 234       * @return array
 235       * @throws \moodle_exception
 236       */
 237      public static function parse_meetingid(string $meetingid): array {
 238          $result = preg_match(
 239              '@(?P<meetingid>[^-]*)-(?P<courseid>[^-]*)-(?P<instanceid>\d+)(\[(?P<groupid>\d*)\])?@',
 240              $meetingid,
 241              $matches
 242          );
 243  
 244          if ($result !== 1) {
 245              throw new \moodle_exception("The supplied meeting id '{$meetingid}' is invalid found.");
 246          }
 247  
 248          return $matches;
 249      }
 250  
 251      /**
 252       * Get all instances in the specified course.
 253       *
 254       * @param int $courseid
 255       * @return self[]
 256       */
 257      public static function get_all_instances_in_course(int $courseid): array {
 258          global $DB;
 259          [
 260              'coursetable' => $coursetable,
 261              'courseselect' => $courseselect,
 262              'coursefrom' => $coursefrom,
 263              'cmfrom' => $cmfrom,
 264              'bbbtable' => $bbbtable,
 265              'bbbselect' => $bbbselect,
 266              'bbbfrom' => $bbbfrom,
 267              'subplugintables' => $subplugintables,
 268              'subpluginselects' => $subpluginselects,
 269              'subpluginfroms' => $subpluginfroms
 270          ] = self::get_tables_info();
 271  
 272          $selects = implode(', ', array_merge([$courseselect, $bbbselect], $subpluginselects));
 273          $subpluginsleftjoins = '';
 274          foreach ($subpluginfroms as $tablealias => $subpluginfrom) {
 275              $subpluginsleftjoins .= "LEFT JOIN {$subpluginfrom} ON bbb.id = {$tablealias}.bigbluebuttonbnid\n";
 276          }
 277          $sql = <<<EOF
 278      SELECT cm.id as cmid, {$selects}
 279        FROM {$cmfrom}
 280  INNER JOIN {$coursefrom} ON c.id = cm.course
 281  INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
 282  INNER JOIN {$bbbfrom} ON cm.instance = bbb.id
 283  {$subpluginsleftjoins}
 284       WHERE cm.course = :courseid
 285  EOF;
 286  
 287          $results = $DB->get_records_sql($sql, [
 288              'modname' => 'bigbluebuttonbn',
 289              'courseid' => $courseid,
 290          ]);
 291  
 292          $instances = [];
 293          foreach ($results as $result) {
 294              $course = $coursetable->extract_from_result($result);
 295              $instancedata = $bbbtable->extract_from_result($result);
 296              self::extract_plugin_table_info($instancedata, $result, $subplugintables);
 297              $instances[$result->cmid] = new self($result->cmid, $course, $instancedata);
 298          }
 299  
 300          return $instances;
 301      }
 302  
 303      /**
 304       * Helper method to extract result from subplugin tables.
 305       * @param object $instancedata instance data
 306       * @param object $result result from sql query
 307       * @param array $subplugintables array of subplugin tables
 308       */
 309      private static function extract_plugin_table_info(object &$instancedata, object $result, array $subplugintables) {
 310          foreach ($subplugintables as $subplugintable) {
 311              $subplugindata = (array) $subplugintable->extract_from_result($result);
 312              if (isset($subplugindata['id'])) {
 313                  unset($subplugindata['id']); // Make sure that from the subplugin we don't conflict with the bigbluebutton id.
 314              }
 315              $instancedata = (object) array_merge($subplugindata, (array) $instancedata);
 316          }
 317      }
 318  
 319      /**
 320       * Get the additional tables returned from the subplugin.
 321       *
 322       * @return array
 323       */
 324      private static function get_tables_info(): array {
 325          $coursetable = new table('course', 'c', 'c');
 326          $courseselect = $coursetable->get_field_select();
 327          $coursefrom = $coursetable->get_from_sql();
 328  
 329          $cmtable = new table('course_modules', 'cm', 'cm');
 330          $cmselect = $cmtable->get_field_select();
 331          $cmfrom = $cmtable->get_from_sql();
 332  
 333          $bbbtable = new table('bigbluebuttonbn', 'bbb', 'b');
 334          $bbbselect = $bbbtable->get_field_select();
 335          $bbbfrom = $bbbtable->get_from_sql();
 336  
 337          // Look now for additional tables returned from the subplugin.
 338          $subpluginselects = [];
 339          $subpluginfroms = [];
 340          $subplugintables = [];
 341          $subplugintablesnames = extension::get_join_tables();
 342          foreach ($subplugintablesnames as $index => $subplugintablename) {
 343              $tablealias = 'ext'.$index;
 344              $subplugintable = new table($subplugintablename, $tablealias, 'ext'.$index);
 345              $subpluginselects[$tablealias] = $subplugintable->get_field_select();
 346              $subpluginfroms[$tablealias] = $subplugintable->get_from_sql();
 347              $subplugintables[$tablealias] = $subplugintable;
 348          }
 349          return compact(
 350              'coursetable', 'courseselect', 'coursefrom',
 351              'cmtable', 'cmselect', 'cmfrom',
 352              'bbbtable', 'bbbselect', 'bbbfrom',
 353              'subplugintables', 'subpluginselects', 'subpluginfroms',
 354          );
 355      }
 356  
 357      /**
 358       * Set the current group id of the activity.
 359       *
 360       * @param int $groupid
 361       */
 362      public function set_group_id(int $groupid): void {
 363          $this->groupid = $groupid;
 364      }
 365  
 366      /**
 367       * Get the current groupid if set.
 368       *
 369       * @return int
 370       */
 371      public function get_group_id(): int {
 372          return empty($this->groupid) ? 0 : $this->groupid;
 373      }
 374  
 375      /**
 376       * Check whether this instance is configured to use a group.
 377       *
 378       * @return bool
 379       */
 380      public function uses_groups(): bool {
 381          $groupmode = groups_get_activity_groupmode($this->get_cm());
 382          return $groupmode != NOGROUPS;
 383      }
 384  
 385      /**
 386       * Get the group name for the current group, if a group has been set.
 387       *
 388       * @return null|string
 389       */
 390      public function get_group_name(): ?string {
 391          $groupid = $this->get_group_id();
 392  
 393          if (!$this->uses_groups()) {
 394              return null;
 395          }
 396  
 397          if ($groupid == 0) {
 398              return get_string('allparticipants');
 399          }
 400  
 401          return format_string(groups_get_group_name($groupid), true, ['context' => $this->get_context()]);
 402      }
 403  
 404      /**
 405       * Get the course object for the instance.
 406       *
 407       * @return stdClass
 408       */
 409      public function get_course(): stdClass {
 410          return $this->course;
 411      }
 412  
 413      /**
 414       * Get the course id of the course that the instance is in.
 415       *
 416       * @return int
 417       */
 418      public function get_course_id(): int {
 419          return $this->course->id;
 420      }
 421  
 422      /**
 423       * Get the cm_info object for the instance.
 424       *
 425       * @return cm_info
 426       */
 427      public function get_cm(): cm_info {
 428          if ($this->cm === null) {
 429              // We do a sort of late binding here as if we call get_cm on a disabled module or in a call stack where
 430              // get_cm was already called, we will get an exception or infinite loop.
 431              $modinfo = get_fast_modinfo($this->course);
 432              $this->cm = $modinfo->get_cm($this->cmid);
 433          }
 434          return $this->cm;
 435      }
 436  
 437      /**
 438       * Get the id of the course module.
 439       *
 440       * @return int
 441       */
 442      public function get_cm_id(): int {
 443          return $this->cmid;
 444      }
 445  
 446      /**
 447       * Get the context.
 448       *
 449       * @return context_module
 450       */
 451      public function get_context(): context_module {
 452          if ($this->context === null) {
 453              $this->context = context_module::instance($this->get_cm()->id);
 454          }
 455  
 456          return $this->context;
 457      }
 458  
 459      /**
 460       * Get the context ID of the module context.
 461       *
 462       * @return int
 463       */
 464      public function get_context_id(): int {
 465          return $this->get_context()->id;
 466      }
 467  
 468      /**
 469       * Get the course context.
 470       *
 471       * @return context_course
 472       */
 473      public function get_course_context(): context_course {
 474          return $this->get_context()->get_course_context();
 475      }
 476  
 477      /**
 478       * Get the big blue button instance data.
 479       *
 480       * @return stdClass
 481       */
 482      public function get_instance_data(): stdClass {
 483          return $this->instancedata;
 484      }
 485  
 486      /**
 487       * Get the instance id.
 488       *
 489       * @return int
 490       */
 491      public function get_instance_id(): int {
 492          return $this->instancedata->id;
 493      }
 494  
 495      /**
 496       * Helper to get an instance var.
 497       *
 498       * @param string $name
 499       * @return mixed|null
 500       */
 501      public function get_instance_var(string $name) {
 502          $instance = $this->get_instance_data();
 503          if (property_exists($instance, $name)) {
 504              return $instance->{$name};
 505          }
 506  
 507          return null;
 508      }
 509  
 510      /**
 511       * Get the meeting id for this meeting.
 512       *
 513       * @param null|int $groupid
 514       * @return string
 515       */
 516      public function get_meeting_id(?int $groupid = null): string {
 517          $baseid = sprintf(
 518              '%s-%s-%s',
 519              $this->get_instance_var('meetingid'),
 520              $this->get_course_id(),
 521              $this->get_instance_var('id')
 522          );
 523  
 524          if ($groupid === null) {
 525              $groupid = $this->get_group_id();
 526          }
 527  
 528          return sprintf('%s[%s]', $baseid, $groupid);
 529      }
 530  
 531      /**
 532       * Get the name of the meeting, considering any group if set.
 533       *
 534       * @return string
 535       */
 536      public function get_meeting_name(): string {
 537          $meetingname = $this->get_instance_var('name');
 538  
 539          $groupname = $this->get_group_name();
 540          if ($groupname !== null) {
 541              $meetingname .= " ({$groupname})";
 542          }
 543  
 544          return $meetingname;
 545      }
 546  
 547      /**
 548       * Get the meeting description with the pluginfile URLs optionally rewritten.
 549       *
 550       * @param bool $rewritepluginfileurls
 551       * @return string
 552       */
 553      public function get_meeting_description(bool $rewritepluginfileurls = false): string {
 554          $description = $this->get_instance_var('intro');
 555  
 556          if ($rewritepluginfileurls) {
 557              $description = file_rewrite_pluginfile_urls(
 558                  $description,
 559                  'pluginfile.php',
 560                  $this->get_context_id(),
 561                  'mod_bigbluebuttonbn',
 562                  'intro',
 563                  null
 564              );
 565          }
 566  
 567          return $description;
 568      }
 569  
 570      /**
 571       * Get the meeting type if set.
 572       *
 573       * @return null|string
 574       */
 575      public function get_type(): ?string {
 576          return $this->get_instance_var('type');
 577      }
 578  
 579      /**
 580       * Whether this instance is includes both a room, and recordings.
 581       *
 582       * @return bool
 583       */
 584      public function is_type_room_and_recordings(): bool {
 585          return $this->get_type() == self::TYPE_ALL;
 586      }
 587  
 588      /**
 589       * Whether this instance is one that only includes a room.
 590       *
 591       * @return bool
 592       */
 593      public function is_type_room_only(): bool {
 594          return $this->get_type() == self::TYPE_ROOM_ONLY;
 595      }
 596  
 597      /**
 598       * Whether this instance is one that only includes recordings.
 599       *
 600       * @return bool
 601       */
 602      public function is_type_recordings_only(): bool {
 603          return $this->get_type() == self::TYPE_RECORDING_ONLY;
 604      }
 605  
 606      /**
 607       * Get the participant list for the session.
 608       *
 609       * @return array
 610       */
 611      public function get_participant_list(): array {
 612          if ($this->participantlist === null) {
 613              $this->participantlist = roles::get_participant_list(
 614                  $this->get_instance_data(),
 615                  $this->get_context()
 616              );
 617          }
 618  
 619          return $this->participantlist;
 620      }
 621  
 622      /**
 623       * Get the user.
 624       *
 625       * @return stdClass
 626       */
 627      public function get_user(): stdClass {
 628          global $USER;
 629          return $USER;
 630      }
 631  
 632      /**
 633       * Get the id of the user.
 634       *
 635       * @return int
 636       */
 637      public function get_user_id(): int {
 638          $user = $this->get_user();
 639          return $user->id ?? 0;
 640      }
 641  
 642      /**
 643       * Get the fullname of the current user.
 644       *
 645       * @return string
 646       */
 647      public function get_user_fullname(): string {
 648          $user = $this->get_user();
 649          return fullname($user);
 650      }
 651  
 652      /**
 653       * Whether the current user is an administrator.
 654       *
 655       * @return bool
 656       */
 657      public function is_admin(): bool {
 658          global $USER;
 659  
 660          return is_siteadmin($USER->id);
 661      }
 662  
 663      /**
 664       * Whether the user is a session moderator.
 665       *
 666       * @return bool
 667       */
 668      public function is_moderator(): bool {
 669          return roles::is_moderator(
 670              $this->get_context(),
 671              $this->get_participant_list()
 672          );
 673      }
 674  
 675      /**
 676       * Whether this user can join the conference.
 677       *
 678       * This checks the user right for access against capabilities and group membership
 679       *
 680       * @return bool
 681       */
 682      public function can_join(): bool {
 683          $groupid = $this->get_group_id();
 684          $context = $this->get_context();
 685          $inrightgroup =
 686              groups_group_visible($groupid, $this->get_course(), $this->get_cm());
 687          $hascapability = has_capability('moodle/category:manage', $context)
 688              || (has_capability('mod/bigbluebuttonbn:join', $context) && $inrightgroup);
 689          $canjoin = $this->get_type() != self::TYPE_RECORDING_ONLY && $hascapability; // Recording only cannot be joined ever.
 690          return $canjoin;
 691      }
 692  
 693      /**
 694       * Whether this user can manage recordings.
 695       *
 696       * @return bool
 697       */
 698      public function can_manage_recordings(): bool {
 699          // Note: This will include site administrators.
 700          // The has_capability() function returns truthy for admins unless otherwise directed.
 701          return has_capability('mod/bigbluebuttonbn:managerecordings', $this->get_context());
 702      }
 703  
 704      /**
 705       * Whether this user can publish/unpublish/protect/unprotect/delete recordings.
 706       *
 707       * @param string $action
 708       * @return bool
 709       */
 710      public function can_perform_on_recordings($action): bool {
 711          // Note: This will include site administrators.
 712          // The has_capability() function returns truthy for admins unless otherwise directed.
 713          return has_capability("mod/bigbluebuttonbn:{$action}recordings", $this->get_context());
 714      }
 715  
 716      /**
 717       * Get the configured user limit.
 718       *
 719       * @return int
 720       */
 721      public function get_user_limit(): int {
 722          if ((boolean) config::get('userlimit_editable')) {
 723              return intval($this->get_instance_var('userlimit'));
 724          }
 725  
 726          return intval((int) config::get('userlimit_default'));
 727      }
 728  
 729      /**
 730       * Check whether the user limit has been reached.
 731       *
 732       * @param int $currentusercount The user count to check
 733       * @return bool
 734       */
 735      public function has_user_limit_been_reached(int $currentusercount): bool {
 736          $userlimit = $this->get_user_limit();
 737          if (empty($userlimit)) {
 738              return false;
 739          }
 740  
 741          return $currentusercount >= $userlimit;
 742      }
 743  
 744      /**
 745       * Check whether the current user counts towards the user limit.
 746       *
 747       * @return bool
 748       */
 749      public function does_current_user_count_towards_user_limit(): bool {
 750          if ($this->is_admin()) {
 751              return false;
 752          }
 753  
 754          if ($this->is_moderator()) {
 755              return false;
 756          }
 757  
 758          return true;
 759      }
 760  
 761      /**
 762       * Get the voice bridge details.
 763       *
 764       * @return null|int
 765       */
 766      public function get_voice_bridge(): ?int {
 767          $voicebridge = (int) $this->get_instance_var('voicebridge');
 768          if ($voicebridge > 0) {
 769              return 70000 + $voicebridge;
 770          }
 771  
 772          return null;
 773      }
 774  
 775      /**
 776       * Whether participants are muted on entry.
 777       *
 778       * @return bool
 779       */
 780      public function get_mute_on_start(): bool {
 781          return $this->get_instance_var('muteonstart');
 782      }
 783  
 784      /**
 785       * Get the moderator password.
 786       *
 787       * @return string
 788       */
 789      public function get_moderator_password(): string {
 790          return $this->get_instance_var('moderatorpass');
 791      }
 792  
 793      /**
 794       * Get the viewer password.
 795       *
 796       * @return string
 797       */
 798      public function get_viewer_password(): string {
 799          return $this->get_instance_var('viewerpass');
 800      }
 801  
 802      /**
 803       * Get the appropriate password for the current user.
 804       *
 805       * @return string
 806       */
 807      public function get_current_user_password(): string {
 808          if ($this->is_admin() || $this->is_moderator()) {
 809              return $this->get_moderator_password();
 810          }
 811  
 812          return $this->get_viewer_password();
 813      }
 814  
 815      /**
 816       * Get the appropriate designated role for the current user.
 817       *
 818       * @return string
 819       */
 820      public function get_current_user_role(): string {
 821          if ($this->is_admin() || $this->is_moderator()) {
 822              return 'MODERATOR';
 823          }
 824  
 825          return 'VIEWER';
 826      }
 827  
 828      /**
 829       * Whether to show the recording button
 830       *
 831       * @return bool
 832       */
 833      public function should_show_recording_button(): bool {
 834          global $CFG;
 835          if (!empty($CFG->bigbluebuttonbn_recording_hide_button_editable)) {
 836              $recordhidebutton = (bool) $this->get_instance_var('recordhidebutton');
 837              $recordallfromstart = (bool) $this->get_instance_var('recordallfromstart');
 838              return !($recordhidebutton || $recordallfromstart);
 839          }
 840  
 841          return !$CFG->bigbluebuttonbn_recording_hide_button_default;
 842      }
 843  
 844      /**
 845       * Whether this instance is recorded.
 846       *
 847       * @return bool
 848       */
 849      public function is_recorded(): bool {
 850          return (bool) $this->get_instance_var('record');
 851      }
 852  
 853      /**
 854       * Moderator approval required ?
 855       *
 856       * By default we leave it as false as "ALWAYS_ACCEPT" is the default value for
 857       * the guestPolicy create parameter (https://docs.bigbluebutton.org/dev/api.html)
 858       * @return bool
 859       */
 860      public function is_moderator_approval_required(): bool {
 861          return $this->get_instance_var('mustapproveuser') ?? false;
 862      }
 863      /**
 864       * Whether this instance can import recordings from another instance.
 865       *
 866       * @return bool
 867       */
 868      public function can_import_recordings(): bool {
 869          if (!config::get('importrecordings_enabled')) {
 870              return false;
 871          }
 872          if (!$this->can_manage_recordings()) {
 873              return false;
 874          }
 875  
 876          return $this->is_feature_enabled('importrecordings');
 877      }
 878  
 879      /**
 880       * Get recordings_imported from instancedata.
 881       *
 882       * @return bool
 883       */
 884      public function get_recordings_imported(): bool {
 885          if (config::get('recordings_imported_editable')) {
 886              return (bool) $this->get_instance_var('recordings_imported');
 887          }
 888          return config::get('recordings_imported_default');
 889      }
 890  
 891      /**
 892       * Whether this instance is recorded from the start.
 893       *
 894       * @return bool
 895       */
 896      public function should_record_from_start(): bool {
 897          if (!$this->is_recorded()) {
 898              // This meeting is not recorded.
 899              return false;
 900          }
 901  
 902          return (bool) $this->get_instance_var('recordallfromstart');
 903      }
 904  
 905      /**
 906       * Whether recording can be started and stopped.
 907       *
 908       * @return bool
 909       */
 910      public function allow_recording_start_stop(): bool {
 911          if (!$this->is_recorded()) {
 912              // If the meeting is not configured for recordings, do not allow it to be recorded.
 913              return false;
 914          }
 915  
 916          return $this->should_show_recording_button();
 917      }
 918  
 919      /**
 920       * Get the welcome message to display.
 921       *
 922       * @return string
 923       */
 924      public function get_welcome_message(): string {
 925          $welcomestring = $this->get_instance_var('welcome');
 926          if (!config::get('welcome_editable') || empty($welcomestring)) {
 927              $welcomestring = config::get('welcome_default');
 928          }
 929          if (empty($welcomestring)) {
 930              $welcomestring = get_string('mod_form_field_welcome_default', 'bigbluebuttonbn');
 931          }
 932  
 933          $welcome = [$welcomestring];
 934  
 935          if ($this->is_recorded()) {
 936              if ($this->should_record_from_start()) {
 937                  $welcome[] = get_string('bbbrecordallfromstartwarning', 'bigbluebuttonbn');
 938              } else {
 939                  $welcome[] = get_string('bbbrecordwarning', 'bigbluebuttonbn');
 940              }
 941          }
 942  
 943          return implode('<br><br>', $welcome);
 944      }
 945  
 946      /**
 947       * Get the presentation data for internal use.
 948       *
 949       * The URL returned for the presentation will be accessible through moodle with checks about user being logged in.
 950       *
 951       * @return array|null
 952       */
 953      public function get_presentation(): ?array {
 954          return $this->do_get_presentation_with_nonce(false);
 955      }
 956  
 957      /**
 958       * Get the presentation data for external API url.
 959       *
 960       * The URL returned for the presentation will be accessible publicly but once and with a specific URL.
 961       *
 962       * @return array|null
 963       */
 964      public function get_presentation_for_bigbluebutton_upload(): ?array {
 965          return $this->do_get_presentation_with_nonce(true);
 966      }
 967  
 968      /**
 969       * Generate Presentation URL.
 970       *
 971       * @param bool $withnonce The generated url will have a nonce included
 972       * @return array|null
 973       */
 974      protected function do_get_presentation_with_nonce(bool $withnonce): ?array {
 975          if ($this->has_ended()) {
 976              return files::get_presentation(
 977                  $this->get_context(),
 978                  $this->get_instance_var('presentation'),
 979                  null,
 980                  $withnonce
 981              );
 982          } else if ($this->is_currently_open()) {
 983              return files::get_presentation(
 984                  $this->get_context(),
 985                  $this->get_instance_var('presentation'),
 986                  $this->get_instance_id(),
 987                  $withnonce
 988              );
 989          } else {
 990              return [];
 991          }
 992      }
 993  
 994      /**
 995       * Whether the current time is before the scheduled start time.
 996       *
 997       * @return bool
 998       */
 999      public function before_start_time(): bool {
1000          $openingtime = $this->get_instance_var('openingtime');
1001          if (empty($openingtime)) {
1002              return false;
1003          }
1004  
1005          return $openingtime >= time();
1006      }
1007  
1008      /**
1009       * Whether the meeting time has passed.
1010       *
1011       * @return bool
1012       */
1013      public function has_ended(): bool {
1014          $closingtime = $this->get_instance_var('closingtime');
1015          if (empty($closingtime)) {
1016              return false;
1017          }
1018  
1019          return $closingtime < time();
1020      }
1021  
1022      /**
1023       * Whether this session is currently open.
1024       *
1025       * @return bool
1026       */
1027      public function is_currently_open(): bool {
1028          if ($this->before_start_time()) {
1029              return false;
1030          }
1031  
1032          if ($this->has_ended()) {
1033              return false;
1034          }
1035  
1036          return true;
1037      }
1038  
1039      /**
1040       * Whether the user must wait to join the session.
1041       *
1042       * @return bool
1043       */
1044      public function user_must_wait_to_join(): bool {
1045          if ($this->is_admin() || $this->is_moderator()) {
1046              return false;
1047          }
1048  
1049          return (bool) $this->get_instance_var('wait');
1050      }
1051  
1052      /**
1053       * Whether the user can force join in all cases
1054       *
1055       * @return bool
1056       */
1057      public function user_can_force_join(): bool {
1058          return $this->is_admin() || $this->is_moderator();
1059      }
1060  
1061      /**
1062       * Whether the user can end a meeting
1063       *
1064       * @return bool
1065       */
1066      public function user_can_end_meeting(): bool {
1067          return $this->is_admin() || $this->is_moderator();
1068      }
1069  
1070      /**
1071       * Get information about the origin.
1072       *
1073       * @return stdClass
1074       */
1075      public function get_origin_data(): stdClass {
1076          global $CFG;
1077  
1078          $parsedurl = parse_url($CFG->wwwroot);
1079          return (object) [
1080              'origin' => 'Moodle',
1081              'originVersion' => $CFG->release,
1082              'originServerName' => $parsedurl['host'],
1083              'originServerUrl' => $CFG->wwwroot,
1084              'originServerCommonName' => '',
1085              'originTag' => sprintf('moodle-mod_bigbluebuttonbn (%s)', get_config('mod_bigbluebuttonbn', 'version')),
1086          ];
1087      }
1088  
1089      /**
1090       * Whether this is a server belonging to blindside networks.
1091       *
1092       * @return bool
1093       */
1094      public function is_blindside_network_server(): bool {
1095          return bigbluebutton_proxy::is_bn_server();
1096      }
1097  
1098      /**
1099       * Get the URL used to access the course that the instance is in.
1100       *
1101       * @return moodle_url
1102       */
1103      public function get_course_url(): moodle_url {
1104          return new moodle_url('/course/view.php', ['id' => $this->get_course_id()]);
1105      }
1106  
1107      /**
1108       * Get the URL used to view the instance as a user.
1109       *
1110       * @return moodle_url
1111       */
1112      public function get_view_url(): moodle_url {
1113          return new moodle_url('/mod/bigbluebuttonbn/view.php', [
1114              'id' => $this->get_cm()->id,
1115          ]);
1116      }
1117  
1118      /**
1119       * Get the logout URL used to log out of the meeting.
1120       *
1121       * @return moodle_url
1122       */
1123      public function get_logout_url(): moodle_url {
1124          return new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [
1125              'action' => 'logout',
1126              'id' => $this->get_cm()->id,
1127              'courseid' => $this->get_cm()->course // Used to find the course if ever the activity is deleted
1128              // while the meeting is running.
1129          ]);
1130      }
1131  
1132      /**
1133       * Get the URL that the remote server will use to notify that the recording is ready.
1134       *
1135       * @return moodle_url
1136       */
1137      public function get_record_ready_url(): moodle_url {
1138          return new moodle_url('/mod/bigbluebuttonbn/bbb_broker.php', [
1139              'action' => 'recording_ready',
1140              'bigbluebuttonbn' => $this->instancedata->id,
1141          ]);
1142      }
1143  
1144      /**
1145       * Get the URL that the remote server will use to notify of meeting events.
1146       *
1147       * @return moodle_url
1148       */
1149      public function get_meeting_event_notification_url(): moodle_url {
1150          return new moodle_url('/mod/bigbluebuttonbn/bbb_broker.php', [
1151              'action' => 'meeting_events',
1152              'bigbluebuttonbn' => $this->instancedata->id,
1153          ]);
1154      }
1155  
1156      /**
1157       * Get the URL used to join a meeting.
1158       *
1159       * @return moodle_url
1160       */
1161      public function get_join_url(): moodle_url {
1162          return new moodle_url('/mod/bigbluebuttonbn/bbb_view.php', [
1163              'action' => 'join',
1164              'id' => $this->get_cm()->id,
1165              'bn' => $this->instancedata->id,
1166          ]);
1167      }
1168  
1169      /**
1170       * Get the URL used for the import page.
1171       *
1172       * @return moodle_url
1173       */
1174      public function get_import_url(): moodle_url {
1175          return new moodle_url('/mod/bigbluebuttonbn/import_view.php', [
1176              'destbn' => $this->instancedata->id,
1177          ]);
1178      }
1179  
1180      /**
1181       * Get the list of enabled features for this instance.
1182       *
1183       * @return array
1184       */
1185      public function get_enabled_features(): array {
1186          return config::get_enabled_features(
1187              bigbluebutton_proxy::get_instance_type_profiles(),
1188              $this->get_instance_var('type') ?? null
1189          );
1190      }
1191  
1192      /**
1193       * Check whetherthe named features is enabled.
1194       *
1195       * @param string $feature
1196       * @return bool
1197       */
1198      public function is_feature_enabled(string $feature): bool {
1199          $features = $this->get_enabled_features();
1200  
1201          return !empty($features[$feature]);
1202      }
1203  
1204      /**
1205       * Check if meeting is recorded.
1206       *
1207       * @return bool
1208       */
1209      public function should_record() {
1210          return (boolean) config::recordings_enabled() && $this->is_recorded();
1211      }
1212  
1213      /**
1214       * Get recordings for this instance
1215       *
1216       * @param string[] $excludedid
1217       * @param bool $viewdeleted view deleted recordings ?
1218       * @return recording[]
1219       */
1220      public function get_recordings(array $excludedid = [], bool $viewdeleted = false): array {
1221          // Fetch the list of recordings depending on the status of the instance.
1222          // show room is enabled for TYPE_ALL and TYPE_ROOM_ONLY.
1223          if ($this->is_feature_enabled('showroom')) {
1224              // Not in the import page.
1225              return recording::get_recordings_for_instance(
1226                  $this,
1227                  $this->is_feature_enabled('importrecordings'),
1228                  $this->get_instance_var('recordings_imported'),
1229              );
1230          }
1231          // We show all recording from this course as this is TYPE_RECORDING.
1232          return recording::get_recordings_for_course(
1233              $this->get_course_id(),
1234              $excludedid,
1235              $this->is_feature_enabled('importrecordings'),
1236              false,
1237              $viewdeleted
1238          );
1239      }
1240  
1241      /**
1242       * Check if this is a valid group for this user/instance,
1243       *
1244       *
1245       * @param stdClass $user
1246       * @param int $groupid
1247       * @return bool
1248       */
1249      public function user_has_group_access($user, $groupid) {
1250          $cm = $this->get_cm();
1251          $context = $this->get_context();
1252          // Then validate group.
1253          $groupmode = groups_get_activity_groupmode($cm);
1254          if ($groupmode && $groupid) {
1255              $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
1256              if ($accessallgroups || $groupmode == VISIBLEGROUPS) {
1257                  $allowedgroups = groups_get_all_groups($cm->course, 0, $cm->groupingid);
1258              } else {
1259                  $allowedgroups = groups_get_all_groups($cm->course, $user->id, $cm->groupingid);
1260              }
1261              if (!array_key_exists($groupid, $allowedgroups)) {
1262                  return false;
1263              }
1264              if (!groups_group_visible($groupid, $this->get_course(), $this->get_cm())) {
1265                  return false;
1266              }
1267          }
1268          return true;
1269      }
1270  
1271      /**
1272       * Get current guest link url
1273       *
1274       * @return moodle_url
1275       */
1276      public function get_guest_access_url(): moodle_url {
1277          $guestlinkuid = $this->get_instance_var('guestlinkuid');
1278          if (empty($guestlinkuid)) {
1279              $this->generate_guest_credentials();
1280              $guestlinkuid = $this->get_instance_var('guestlinkuid');
1281          }
1282          return new moodle_url('/mod/bigbluebuttonbn/guest.php', ['uid' => $guestlinkuid]);
1283      }
1284  
1285      /**
1286       * Is guest access allowed in this instance.
1287       *
1288       * @return bool
1289       */
1290      public function is_guest_allowed(): bool {
1291          return !$this->is_type_recordings_only() &&
1292                  config::get('guestaccess_enabled') && $this->get_instance_var('guestallowed');
1293      }
1294  
1295      /**
1296       * Get current meeting password
1297       *
1298       * @return string
1299       */
1300      public function get_guest_access_password() : string {
1301          $guestpassword = $this->get_instance_var('guestpassword');
1302          if (empty($guestpassword)) {
1303              $this->generate_guest_credentials();
1304              $guestpassword = $this->get_instance_var('guestpassword');
1305          }
1306          return $guestpassword;
1307      }
1308  
1309      /**
1310       * Generate credentials for this instance and persist the value in the database
1311       *
1312       * @return void
1313       */
1314      private function generate_guest_credentials():void {
1315          global $DB;
1316          [$this->instancedata->guestlinkuid, $this->instancedata->guestpassword] =
1317              \mod_bigbluebuttonbn\plugin::generate_guest_meeting_credentials();
1318          $DB->update_record('bigbluebuttonbn', $this->instancedata);
1319      }
1320  
1321      /**
1322       * Is this meeting configured to display avatars of the users ?
1323       *
1324       * Note: this is for now a global setting.
1325       *
1326       * @return bool
1327       */
1328      public function is_profile_picture_enabled(): bool {
1329          return (bool) config::get('profile_picture_enabled');
1330      }
1331  }