Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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