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 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\external;
  18  
  19  use external_api;
  20  use mod_bigbluebuttonbn\instance;
  21  use mod_bigbluebuttonbn\test\testcase_helper_trait;
  22  use require_login_exception;
  23  
  24  defined('MOODLE_INTERNAL') || die();
  25  
  26  global $CFG;
  27  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  28  
  29  /**
  30   * Tests for the update_course class.
  31   *
  32   * @package    mod_bigbluebuttonbn
  33   * @category   test
  34   * @copyright  2021 - present, Blindside Networks Inc
  35   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   * @author    Laurent David (laurent@call-learning.fr)
  37   * @covers \mod_bigbluebuttonbn\external\get_recordings
  38   */
  39  class get_recordings_test extends \externallib_advanced_testcase {
  40      use testcase_helper_trait;
  41  
  42      /**
  43       * Setup for test
  44       */
  45      public function setUp(): void {
  46          parent::setUp();
  47          $this->initialise_mock_server();
  48      }
  49  
  50      /**
  51       * Helper
  52       *
  53       * @param mixed ...$params
  54       * @return array|bool|mixed
  55       */
  56      protected function get_recordings(...$params) {
  57          $recordings = get_recordings::execute(...$params);
  58  
  59          return external_api::clean_returnvalue(get_recordings::execute_returns(), $recordings);
  60      }
  61  
  62      /**
  63       * Test execute API CALL with no instance
  64       */
  65      public function test_execute_wrong_instance() {
  66          $getrecordings = $this->get_recordings(1234);
  67  
  68          $this->assertIsArray($getrecordings);
  69          $this->assertArrayHasKey('status', $getrecordings);
  70          $this->assertEquals(false, $getrecordings['status']);
  71          $this->assertStringContainsString('nosuchinstance', $getrecordings['warnings'][0]['warningcode']);
  72      }
  73  
  74      /**
  75       * Test execute API CALL without login
  76       */
  77      public function test_execute_without_login() {
  78          $this->resetAfterTest();
  79  
  80          $course = $this->getDataGenerator()->create_course();
  81          $record = $this->getDataGenerator()->create_module('bigbluebuttonbn', ['course' => $course->id]);
  82          $instance = instance::get_from_instanceid($record->id);
  83  
  84          $this->expectException(require_login_exception::class);
  85          $this->get_recordings($instance->get_instance_id());
  86      }
  87  
  88      /**
  89       * Test execute API CALL with invalid login
  90       */
  91      public function test_execute_with_invalid_login() {
  92          $this->resetAfterTest();
  93  
  94          $generator = $this->getDataGenerator();
  95          $course = $generator->create_course();
  96          $record = $generator->create_module('bigbluebuttonbn', ['course' => $course->id]);
  97          $instance = instance::get_from_instanceid($record->id);
  98  
  99          $user = $generator->create_user();
 100          $this->setUser($user);
 101  
 102          $this->expectException(require_login_exception::class);
 103          $this->get_recordings($instance->get_instance_id());
 104      }
 105  
 106      /**
 107       * When login as a student
 108       */
 109      public function test_execute_with_valid_login() {
 110          $this->resetAfterTest();
 111  
 112          $generator = $this->getDataGenerator();
 113          $course = $generator->create_course();
 114          $record = $generator->create_module('bigbluebuttonbn', ['course' => $course->id]);
 115          $instance = instance::get_from_instanceid($record->id);
 116  
 117          $user = $generator->create_and_enrol($course, 'student');
 118          $this->setUser($user);
 119  
 120          $getrecordings = $this->get_recordings($instance->get_instance_id());
 121  
 122          $this->assertIsArray($getrecordings);
 123          $this->assertArrayHasKey('status', $getrecordings);
 124          $this->assertEquals(true, $getrecordings['status']);
 125          $this->assertNotEmpty($getrecordings['tabledata']);
 126          $this->assertEquals('[]', $getrecordings['tabledata']['data']);
 127      }
 128  
 129      /**
 130       * Check if tools are present for teacher/moderator
 131       */
 132      public function test_get_recordings_tools() {
 133          $this->resetAfterTest();
 134          $dataset = [
 135              'type' => instance::TYPE_ALL,
 136              'groups' => null,
 137              'users' => [['username' => 't1', 'role' => 'editingteacher'], ['username' => 's1', 'role' => 'student']],
 138              'recordingsdata' => [
 139                  [['name' => 'Recording1']],
 140                  [['name' => 'Recording2']]
 141              ],
 142          ];
 143          $activityid = $this->create_from_dataset($dataset);
 144          $instance = instance::get_from_instanceid($activityid);
 145  
 146          $context = \context_course::instance($instance->get_course_id());
 147          foreach ($dataset['users'] as $userdef) {
 148              $user = \core_user::get_user_by_username($userdef['username']);
 149              $this->setUser($user);
 150              $getrecordings = $this->get_recordings($instance->get_instance_id());
 151              // Check users see or do not see recording dependings on their groups.
 152              foreach ($dataset['recordingsdata'] as $recordingdata) {
 153                  foreach ($recordingdata as $recording) {
 154                      if (has_capability('moodle/course:update', $context)) {
 155                          $this->assertStringContainsString('data-action=\"delete\"', $getrecordings['tabledata']['data'],
 156                              "User $user->username, should be able to delete the recording {$recording['name']}");
 157                          $this->assertStringContainsString('data-action=\"publish\"', $getrecordings['tabledata']['data'],
 158                              "User $user->username, should be able to publish the recording {$recording['name']}");
 159                      } else {
 160                          $this->assertStringNotContainsString('data-action=\"delete\"', $getrecordings['tabledata']['data'],
 161                              "User $user->username, should not be able to delete the recording {$recording['name']}");
 162                      }
 163                  }
 164              }
 165          }
 166          // Now without delete.
 167          foreach ($dataset['users'] as $userdef) {
 168              $user = \core_user::get_user_by_username($userdef['username']);
 169              $this->setUser($user);
 170              $getrecordings = $this->get_recordings($instance->get_instance_id(), 'protect');
 171              // Check users see or do not see recording dependings on their groups.
 172              foreach ($dataset['recordingsdata'] as $recordingdata) {
 173                  foreach ($recordingdata as $recording) {
 174                      $this->assertStringNotContainsString('data-action=\"delete\"', $getrecordings['tabledata']['data'],
 175                          "User $user->username, should not be able to delete the recording {$recording['name']}");
 176                  }
 177              }
 178          }
 179      }
 180  
 181      /**
 182       * Check preview is present and displayed
 183       */
 184      public function test_get_recordings_preview() {
 185          $this->resetAfterTest();
 186          $dataset = [
 187              'type' => instance::TYPE_ALL,
 188              'additionalsettings' => [
 189                  'recordings_preview' => 1
 190              ],
 191              'groups' => null,
 192              'users' => [['username' => 't1', 'role' => 'editingteacher'], ['username' => 's1', 'role' => 'student']],
 193              'recordingsdata' => [
 194                  [['name' => 'Recording1']],
 195                  [['name' => 'Recording2']]
 196              ],
 197          ];
 198          $activityid = $this->create_from_dataset($dataset);
 199          $instance = instance::get_from_instanceid($activityid);
 200  
 201          $context = \context_course::instance($instance->get_course_id());
 202          foreach ($dataset['users'] as $userdef) {
 203              $user = \core_user::get_user_by_username($userdef['username']);
 204              $this->setUser($user);
 205              $getrecordings = $this->get_recordings($instance->get_instance_id());
 206              $this->assertNotEmpty($getrecordings['tabledata']['columns']['3']);
 207              $this->assertEquals('preview', $getrecordings['tabledata']['columns']['3']['key']);
 208          }
 209      }
 210  
 211      /**
 212       * Check we can see all recording from a course in a room only instance
 213       * @covers \mod_bigbluebuttonbn\external\get_recordings::execute
 214       */
 215      public function test_get_recordings_room_only() {
 216          $this->resetAfterTest();
 217          set_config('bigbluebuttonbn_importrecordings_enabled', 1);
 218          $dataset = [
 219              'type' => instance::TYPE_ALL,
 220              'groups' => null,
 221              'users' => [['username' => 't1', 'role' => 'editingteacher'], ['username' => 's1', 'role' => 'student']],
 222              'recordingsdata' => [
 223                  [['name' => 'Recording1']],
 224                  [['name' => 'Recording2']]
 225              ],
 226          ];
 227          $activityid = $this->create_from_dataset($dataset);
 228          $instance = instance::get_from_instanceid($activityid);
 229  
 230          // Now create a recording only activity.
 231          $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 232          // Now create a new activity and import the first record.
 233          $newactivity = $plugingenerator->create_instance([
 234              'course' => $instance->get_course_id(),
 235              'type' => instance::TYPE_RECORDING_ONLY,
 236              'name' => 'Example 2'
 237          ]);
 238          $plugingenerator->create_meeting([
 239              'instanceid' => $newactivity->id,
 240          ]); // We need to have a meeting created in order to import recordings.
 241          $newinstance = instance::get_from_instanceid($newactivity->id);
 242          $this->create_recordings_for_instance($newinstance, [['name' => 'Recording3']]);
 243  
 244          foreach ($dataset['users'] as $userdef) {
 245              $user = \core_user::get_user_by_username($userdef['username']);
 246              $this->setUser($user);
 247              $getrecordings = $this->get_recordings($newinstance->get_instance_id());
 248              // Check users see or do not see recording dependings on their groups.
 249              $data = json_decode($getrecordings['tabledata']['data']);
 250              $this->assertCount(3, $data);
 251          }
 252      }
 253  
 254      /**
 255       * Check if we can see the imported recording in a new instance
 256       * @covers \mod_bigbluebuttonbn\external\get_recordings::execute
 257       */
 258      public function test_get_recordings_imported() {
 259          $this->resetAfterTest();
 260          set_config('bigbluebuttonbn_importrecordings_enabled', 1);
 261          $dataset = [
 262              'type' => instance::TYPE_ALL,
 263              'groups' => null,
 264              'users' => [['username' => 't1', 'role' => 'editingteacher'], ['username' => 's1', 'role' => 'student']],
 265              'recordingsdata' => [
 266                  [['name' => 'Recording1']],
 267                  [['name' => 'Recording2']]
 268              ],
 269          ];
 270  
 271          $activityid = $this->create_from_dataset($dataset);
 272          $instance = instance::get_from_instanceid($activityid);
 273  
 274          $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 275          // Now create a new activity and import the first record.
 276          $newactivity = $plugingenerator->create_instance([
 277              'course' => $instance->get_course_id(),
 278              'type' => instance::TYPE_ALL,
 279              'name' => 'Example 2'
 280          ]);
 281          $plugingenerator->create_meeting([
 282              'instanceid' => $newactivity->id,
 283          ]); // We need to have a meeting created in order to import recordings.
 284          $newinstance = instance::get_from_instanceid($newactivity->id);
 285          $recordings = $instance->get_recordings();
 286          foreach ($recordings as $recording) {
 287              if ($recording->get('name') == 'Recording1') {
 288                  $recording->create_imported_recording($newinstance);
 289              }
 290          }
 291  
 292          foreach ($dataset['users'] as $userdef) {
 293              $user = \core_user::get_user_by_username($userdef['username']);
 294              $this->setUser($user);
 295              $getrecordings = $this->get_recordings($newinstance->get_instance_id());
 296              // Check users see or do not see recording dependings on their groups.
 297              foreach ($dataset['recordingsdata'] as $index => $recordingdata) {
 298                  foreach ($recordingdata as $recording) {
 299                      if ($instance->can_manage_recordings()) {
 300                          $this->assertStringContainsString('data-action=\"delete\"', $getrecordings['tabledata']['data'],
 301                              "User $user->username, should be able to delete the recording {$recording['name']}");
 302                      } else {
 303                          $this->assertStringNotContainsString('data-action=\"delete\"', $getrecordings['tabledata']['data'],
 304                              "User $user->username, should not be able to delete the recording {$recording['name']}");
 305                      }
 306                      if ($index === 0) {
 307                          $this->assertStringContainsString($recording['name'], $getrecordings['tabledata']['data']);
 308                      } else {
 309                          $this->assertStringNotContainsString($recording['name'], $getrecordings['tabledata']['data']);
 310                      }
 311                  }
 312              }
 313  
 314          }
 315      }
 316  
 317      /**
 318       * Check we can see only imported recordings in a recordings only instance when "Show only imported links" enabled.
 319       * @covers \mod_bigbluebuttonbn\external\get_recordings::execute
 320       */
 321      public function test_get_imported_recordings_only() {
 322          $this->resetAfterTest();
 323          set_config('bigbluebuttonbn_importrecordings_enabled', 1);
 324          $dataset = [
 325              'type' => instance::TYPE_ALL,
 326              'groups' => null,
 327              'users' => [['username' => 's1', 'role' => 'student']],
 328              'recordingsdata' => [
 329                  [['name' => 'Recording1']],
 330                  [['name' => 'Recording2']]
 331              ],
 332          ];
 333          $activityid = $this->create_from_dataset($dataset);
 334          $instance = instance::get_from_instanceid($activityid);
 335  
 336          // Now create a recording only activity.
 337          $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_bigbluebuttonbn');
 338          // Now create a new activity and import the first record.
 339          $newactivity = $plugingenerator->create_instance([
 340              'course' => $instance->get_course_id(),
 341              'type' => instance::TYPE_RECORDING_ONLY,
 342              'name' => 'Example 2'
 343          ]);
 344          $plugingenerator->create_meeting([
 345              'instanceid' => $newactivity->id,
 346          ]); // We need to have a meeting created in order to import recordings.
 347          $newinstance = instance::get_from_instanceid($newactivity->id);
 348          $recordings = $instance->get_recordings();
 349          foreach ($recordings as $recording) {
 350              if ($recording->get('name') == 'Recording1') {
 351                  $recording->create_imported_recording($newinstance);
 352              }
 353          }
 354          $user = \core_user::get_user_by_username('s1');
 355          $this->setUser($user);
 356          $getrecordings = $this->get_recordings($newinstance->get_instance_id());
 357          $data = json_decode($getrecordings['tabledata']['data']);
 358          // Check that all recordings including the imported recording appear.
 359          $this->assertCount(3, $data);
 360          // Set the flags to enable "Show only imported links".
 361          set_config('bigbluebuttonbn_recordings_imported_default', 1);
 362          set_config('bigbluebuttonbn_recordings_imported_editable', 0);
 363          $getrecordings = $this->get_recordings($newinstance->get_instance_id());
 364          $data = json_decode($getrecordings['tabledata']['data']);
 365          $this->assertCount(1, $data);
 366      }
 367  
 368      /**
 369       * Check if recording are visible/invisible depending on the group.
 370       *
 371       * @param string $type
 372       * @param array $groups
 373       * @param array $users
 374       * @param array $recordingsdata
 375       * @param array $test
 376       * @param int $coursemode
 377       *
 378       * @covers   \mod_bigbluebuttonbn\external\get_recordings::execute
 379       * @dataProvider recording_group_test_data
 380       */
 381      public function test_get_recordings_groups($type, $groups, $users, $recordingsdata, $test, $coursemode) {
 382          $this->resetAfterTest();
 383          $dataset = compact('type', 'groups', 'users', 'recordingsdata', 'test', 'coursemode');
 384          $activityid = $this->create_from_dataset($dataset);
 385          $instance = instance::get_from_instanceid($activityid);
 386  
 387          foreach ($dataset['users'] as $userdef) {
 388              $user = \core_user::get_user_by_username($userdef['username']);
 389              $this->setUser($user);
 390              $groups = array_values(groups_get_my_groups());
 391              $mygroup = !empty($groups) ? end($groups) : null;
 392  
 393              $getrecordings = $this->get_recordings(
 394                  $instance->get_instance_id(), null, !empty($mygroup) ? $mygroup->id : null);
 395              $allrecordingsnames = [];
 396              foreach ($recordingsdata as $groups => $rsinfo) {
 397                  $rnames = array_map(function($rdata) {
 398                      return $rdata['name'];
 399                  }, $rsinfo);
 400                  $allrecordingsnames = array_merge($allrecordingsnames, $rnames);
 401              }
 402              // Check users see or do not see recording dependings on their groups.
 403              foreach ($dataset['test'][$user->username] as $viewablerecordings) {
 404                  $viewablerecordings = $dataset['test'][$user->username];
 405                  $invisiblerecordings = array_diff($allrecordingsnames, $viewablerecordings);
 406                  foreach ($viewablerecordings as $viewablerecordingname) {
 407                      $this->assertStringContainsString($viewablerecordingname, $getrecordings['tabledata']['data'],
 408                          "User $user->username, should see recording {$viewablerecordingname}");
 409                  }
 410                  foreach ($invisiblerecordings as $invisiblerecordingname) {
 411                      $this->assertStringNotContainsString($invisiblerecordingname, $getrecordings['tabledata']['data'],
 412                          "User $user->username, should not see recording {$viewablerecordingname}");
 413                  }
 414              }
 415          }
 416      }
 417  
 418      /**
 419       * Recording group test
 420       *
 421       * @return array[]
 422       */
 423      public function recording_group_test_data() {
 424          return [
 425              'visiblegroups' => [
 426                  'type' => instance::TYPE_ALL,
 427                  'groups' => ['G1' => ['s1'], 'G2' => ['s2']],
 428                  'users' => [
 429                      ['username' => 't1', 'role' => 'editingteacher'],
 430                      ['username' => 's1', 'role' => 'student'],
 431                      ['username' => 's2', 'role' => 'student'],
 432                      ['username' => 's3', 'role' => 'student']
 433                  ],
 434                  'recordingsdata' => [
 435                      'G1' => [['name' => 'Recording1']],
 436                      'G2' => [['name' => 'Recording2']],
 437                      '' => [['name' => 'Recording3']]
 438                  ],
 439                  'test' => [
 440                      't1' => ['Recording1', 'Recording2', 'Recording3'], // A moderator should see all recordings.
 441                      's1' => ['Recording1'], // S1 can only see the recordings from his group.
 442                      's2' => ['Recording2'], // S2 can only see the recordings from his group.
 443                      's3' => ['Recording3', 'Recording2', 'Recording1']
 444                      // S3 should see recordings which have no groups and his groups's recording.
 445                  ],
 446                  'coursemode' => VISIBLEGROUPS
 447              ],
 448              'separategroups' => [
 449                  'type' => instance::TYPE_ALL,
 450                  'groups' => ['G1' => ['s1'], 'G2' => ['s2']],
 451                  'users' => [
 452                      ['username' => 't1', 'role' => 'editingteacher'],
 453                      ['username' => 's1', 'role' => 'student'],
 454                      ['username' => 's2', 'role' => 'student']
 455                  ],
 456                  'recordingsdata' => [
 457                      'G1' => [['name' => 'Recording1']],
 458                      'G2' => [['name' => 'Recording2']],
 459                      '' => [['name' => 'Recording3']]
 460                  ],
 461                  'test' => [
 462                      't1' => ['Recording1', 'Recording2', 'Recording3'], // A moderator should see all recordings.
 463                      's1' => ['Recording1'], // S1 can only see the recordings from his group.
 464                      's2' => ['Recording2'], // S2 can only see the recordings from his group.
 465                      's3' => ['Recording3'] // S3 should see recordings which have no groups.
 466                  ],
 467                  'coursemode' => SEPARATEGROUPS
 468              ]
 469          ];
 470      }
 471  }