See Release Notes
Long Term Support Release
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 /** 18 * mod_bigbluebuttonbn data generator 19 * 20 * @package mod_bigbluebuttonbn 21 * @category test 22 * @copyright 2018 - present, Blindside Networks Inc 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com) 25 */ 26 27 use core\plugininfo\mod; 28 use mod_bigbluebuttonbn\instance; 29 use mod_bigbluebuttonbn\local\config; 30 use mod_bigbluebuttonbn\logger; 31 use mod_bigbluebuttonbn\recording; 32 33 /** 34 * bigbluebuttonbn module data generator 35 * 36 * @package mod_bigbluebuttonbn 37 * @category test 38 * @copyright 2018 - present, Blindside Networks Inc 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com) 41 */ 42 class mod_bigbluebuttonbn_generator extends \testing_module_generator { 43 44 /** 45 * Creates an instance of bigbluebuttonbn for testing purposes. 46 * 47 * @param array|stdClass $record data for module being generated. 48 * @param null|array $options general options for course module. 49 * @return stdClass record from module-defined table with additional field cmid 50 */ 51 public function create_instance($record = null, array $options = null) { 52 // Prior to creating the instance, make sure that the BigBlueButton module is enabled. 53 set_config('bigbluebuttonbn_default_dpa_accepted', true); 54 $modules = \core_plugin_manager::instance()->get_plugins_of_type('mod'); 55 if (!$modules['bigbluebuttonbn']->is_enabled()) { 56 mod::enable_plugin('bigbluebuttonbn', true); 57 } 58 59 $now = time(); 60 $defaults = [ 61 "type" => 0, 62 "meetingid" => sha1(rand()), 63 "record" => true, 64 "moderatorpass" => "mp", 65 "viewerpass" => "ap", 66 "participants" => "{}", 67 "timecreated" => $now, 68 "timemodified" => $now, 69 "presentation" => null, 70 "recordings_preview" => 0 71 ]; 72 73 $record = (array) $record; 74 75 $record['participants'] = json_encode($this->get_participants_from_record($record)); 76 77 foreach ($defaults as $key => $value) { 78 if (!isset($record[$key])) { 79 $record[$key] = $value; 80 } 81 } 82 if ($record['presentation']) { 83 global $USER; 84 // Here we replace the original presentation file with a draft area in which we store this file. 85 $draftareaid = file_get_unused_draft_itemid(); 86 $bbbfilerecord['contextid'] = context_user::instance($USER->id)->id; 87 $bbbfilerecord['component'] = 'user'; 88 $bbbfilerecord['filearea'] = 'draft'; 89 $bbbfilerecord['itemid'] = $draftareaid; 90 $bbbfilerecord['filepath'] = '/'; 91 $bbbfilerecord['filename'] = basename($record['presentation']); 92 $fs = get_file_storage(); 93 94 $fs->create_file_from_pathname($bbbfilerecord, $record['presentation']); 95 // Now the $record['presentation'] must contain the draftareaid. 96 $record['presentation'] = $draftareaid; 97 } 98 return parent::create_instance((object) $record, (array) $options); 99 } 100 101 /** 102 * Create the participants field data from create_instance data. 103 * 104 * @param array $record 105 * @return array 106 */ 107 protected function get_participants_from_record(array $record): array { 108 $roles = []; 109 if (array_key_exists('moderators', $record) && !empty($record['moderators'])) { 110 $roles = array_merge( 111 $roles, 112 $this->get_participant_configuration($record['moderators'], 'moderator') 113 ); 114 unset($record['moderators']); 115 } 116 117 if (array_key_exists('viewers', $record) && !empty($record['viewers'])) { 118 $roles = array_merge( 119 $roles, 120 $this->get_participant_configuration($record['viewers'], 'viewer') 121 ); 122 unset($record['viewers']); 123 } 124 125 if (!empty($roles)) { 126 array_unshift($roles, (object) [ 127 'selectiontype' => 'all', 128 'selectionid' => 'all', 129 'role' => 'viewer', 130 ]); 131 } 132 133 return $roles; 134 } 135 136 /** 137 * Get the participant configuration for a field and role for use in get_participants_from_record. 138 * 139 * @param string $field 140 * @param string $role 141 * @return array 142 */ 143 protected function get_participant_configuration(string $field, string $role): array { 144 global $DB; 145 146 $values = explode(',', $field); 147 148 $roles = $DB->get_records_menu('role', [], '', 'shortname, id'); 149 150 $configuration = []; 151 foreach ($values as $value) { 152 if (empty($value)) { 153 // Empty value. 154 continue; 155 } 156 [$type, $name] = explode(':', $value); 157 158 $participant = (object) [ 159 'selectiontype' => $type, 160 'role' => $role, 161 ]; 162 switch ($type) { 163 case 'role': 164 if (!array_key_exists($name, $roles)) { 165 throw new \coding_exception("Unknown role '{$name}'"); 166 } 167 $participant->selectionid = $roles[$name]; 168 169 break; 170 case 'user': 171 $participant->selectionid = $DB->get_field('user', 'id', ['username' => $name], MUST_EXIST); 172 break; 173 default: 174 throw new \coding_exception("Unknown participant type: '{$type}'"); 175 } 176 177 $configuration[] = $participant; 178 } 179 180 return $configuration; 181 } 182 183 /** 184 * Create a recording for the given bbb activity. 185 * 186 * The recording is created both locally, and a recording record is created on the mocked BBB server. 187 * 188 * @param array $data 189 * @param bool $serveronly create it only on the server, not in the database. 190 * @return stdClass the recording object 191 */ 192 public function create_recording(array $data, $serveronly = false): stdClass { 193 $instance = instance::get_from_instanceid($data['bigbluebuttonbnid']); 194 195 if (isset($data['imported']) && filter_var($data['imported'], FILTER_VALIDATE_BOOLEAN)) { 196 if (empty($data['importedid'])) { 197 throw new moodle_exception('error'); 198 } 199 $recording = recording::get_record(['recordingid' => $data['importedid']]); 200 $recording->imported = true; 201 } else { 202 $recording = (object) [ 203 'headless' => false, 204 'imported' => false, 205 'status' => $data['status'] ?? recording::RECORDING_STATUS_NOTIFIED, 206 ]; 207 } 208 209 if (!empty($data['groupid'])) { 210 $instance->set_group_id($data['groupid']); 211 $recording->groupid = $data['groupid']; 212 } 213 214 $recording->bigbluebuttonbnid = $instance->get_instance_id(); 215 $recording->courseid = $instance->get_course_id(); 216 if (isset($options['imported']) && $options['imported']) { 217 $precording = $recording->create_imported_recording($instance); 218 } else { 219 if ($recording->status == recording::RECORDING_STATUS_DISMISSED) { 220 $recording->recordingid = sprintf( 221 "%s-%s", 222 md5($instance->get_meeting_id()), 223 time() + rand(1, 100000) 224 ); 225 } else { 226 $recording->recordingid = $this->create_mockserver_recording($instance, $recording, $data); 227 } 228 $precording = new recording(0, $recording); 229 if (!$serveronly) { 230 $precording->create(); 231 } 232 } 233 return $precording->to_record(); 234 } 235 236 /** 237 * Add a recording in the mock server 238 * 239 * @param instance $instance 240 * @param stdClass $recordingdata 241 * @param array $data 242 * @return string 243 */ 244 protected function create_mockserver_recording(instance $instance, stdClass $recordingdata, array $data): string { 245 $now = time(); 246 $mockdata = array_merge((array) $recordingdata, [ 247 'sequence' => 1, 248 'meta' => [ 249 'bn-presenter-name' => $data['presentername'] ?? 'Fake presenter', 250 'bn-recording-ready-url' => new moodle_url('/mod/bigbluebuttonbn/bbb_broker.php', [ 251 'action' => 'recording_ready', 252 'bigbluebuttonbn' => $instance->get_instance_id() 253 ]), 254 'bbb-recording-description' => $data['description'] ?? '', 255 'bbb-recording-name' => $data['name'] ?? '', 256 'bbb-recording-tags' => $data['tags'] ?? '', 257 ], 258 ]); 259 $mockdata['startTime'] = $data['starttime'] ?? $now; 260 $mockdata['endTime'] = $data['endtime'] ?? $mockdata['startTime'] + HOURSECS; 261 262 if (!empty($data['isBreakout'])) { 263 // If it is a breakout meeting, we do not have any way to know the real Id of the meeting 264 // unless we query the list of submeetings. 265 // For now we will just send the parent ID and let the mock server deal with the sequence + parentID 266 // to find the meetingID. 267 $mockdata['parentMeetingID'] = $instance->get_meeting_id(); 268 } else { 269 $mockdata['meetingID'] = $instance->get_meeting_id(); 270 } 271 272 $result = $this->send_mock_request('backoffice/createRecording', [], $mockdata); 273 274 return (string) $result->recordID; 275 } 276 277 /** 278 * Mock an in-progress meeting on the remote server. 279 * 280 * @param array $data 281 * @return stdClass 282 */ 283 public function create_meeting(array $data): stdClass { 284 $instance = instance::get_from_instanceid($data['instanceid']); 285 286 if (array_key_exists('groupid', $data)) { 287 $instance = instance::get_group_instance_from_instance($instance, $data['groupid']); 288 } 289 290 $meetingid = $instance->get_meeting_id(); 291 292 // Default room configuration. 293 $roomconfig = array_merge($data, [ 294 'meetingName' => $instance->get_meeting_name(), 295 'attendeePW' => $instance->get_viewer_password(), 296 'moderatorPW' => $instance->get_moderator_password(), 297 'voiceBridge' => $instance->get_voice_bridge(), 298 'meta' => [ 299 'bbb-context' => $instance->get_course()->fullname, 300 'bbb-context-id' => $instance->get_course()->id, 301 'bbb-context-label' => $instance->get_course()->shortname, 302 'bbb-context-name' => $instance->get_course()->fullname, 303 'bbb-origin' => 'Moodle', 304 'bbb-origin-tag' => 'moodle-mod_bigbluebuttonbn (TODO version)', 305 'bbb-recording-description' => $instance->get_meeting_description(), 306 'bbb-recording-name' => $instance->get_meeting_name(), 307 ], 308 ]); 309 if ((boolean) config::get('recordingready_enabled')) { 310 $roomconfig['meta']['bn-recording-ready-url'] = $instance->get_record_ready_url()->out(false); 311 } 312 if ((boolean) config::get('meetingevents_enabled')) { 313 $roomconfig['meta']['analytics-callback-url'] = $instance->get_meeting_event_notification_url()->out(false); 314 } 315 if (!empty($roomconfig['isBreakout'])) { 316 // If it is a breakout meeting, we do not have any way to know the real Id of the meeting 317 // For now we will just send the parent ID and let the mock server deal with the sequence + parentID 318 // to find the meetingID. 319 $roomconfig['parentMeetingID'] = $instance->get_meeting_id(); 320 } else { 321 $roomconfig['meetingID'] = $meetingid; 322 } 323 $this->send_mock_request('backoffice/createMeeting', [], $roomconfig); 324 return (object) $roomconfig; 325 } 326 327 /** 328 * Create a log record 329 * 330 * @param mixed $record 331 * @param array|null $options 332 */ 333 public function create_log($record, array $options = null) { 334 $instance = instance::get_from_instanceid($record['bigbluebuttonbnid']); 335 336 $record = array_merge([ 337 'meetingid' => $instance->get_meeting_id(), 338 ], (array) $record); 339 340 $testlogclass = new class extends logger { 341 /** 342 * Log test event 343 * 344 * @param instance $instance 345 * @param array $record 346 */ 347 public static function log_test_event(instance $instance, array $record): void { 348 self::log( 349 $instance, 350 logger::EVENT_CREATE, 351 $record 352 ); 353 } 354 }; 355 356 $testlogclass::log_test_event($instance, $record); 357 } 358 359 /** 360 * Get a URL for a mocked BBB server endpoint. 361 * 362 * @param string $endpoint 363 * @param array $params 364 * @return moodle_url 365 */ 366 protected function get_mocked_server_url(string $endpoint = '', array $params = []): moodle_url { 367 return new moodle_url(TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER . '/' . $endpoint, $params); 368 } 369 370 /** 371 * Utility to send a request to the mock server 372 * 373 * @param string $endpoint 374 * @param array $params 375 * @param array $mockdata 376 * @return SimpleXMLElement|bool 377 * @throws moodle_exception 378 */ 379 protected function send_mock_request(string $endpoint, array $params = [], array $mockdata = []): SimpleXMLElement { 380 $url = $this->get_mocked_server_url($endpoint, $params); 381 382 foreach ($mockdata as $key => $value) { 383 if (is_array($value)) { 384 foreach ($value as $subkey => $subvalue) { 385 $paramname = "{$key}_{$subkey}"; 386 $url->param($paramname, $subvalue); 387 } 388 } else { 389 $url->param($key, $value); 390 } 391 } 392 393 $curl = new \curl(); 394 $result = $curl->get($url->out_omit_querystring(), $url->params()); 395 396 $retvalue = @simplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOBLANKS); 397 if ($retvalue === false) { 398 throw new moodle_exception('mockserverconnfailed', 'mod_bigbluebutton'); 399 } 400 return $retvalue; 401 } 402 403 /** 404 * Trigger a meeting event on BBB side 405 * 406 * @param object $user 407 * @param instance $instance 408 * @param string $eventtype 409 * @param string|null $eventdata 410 * @return void 411 */ 412 public function add_meeting_event(object $user, instance $instance, string $eventtype, string $eventdata = ''): void { 413 $this->send_mock_request('backoffice/addMeetingEvent', [ 414 'secret' => \mod_bigbluebuttonbn\local\config::DEFAULT_SHARED_SECRET, 415 'meetingID' => $instance->get_meeting_id(), 416 'attendeeID' => $user->id, 417 'attendeeName' => fullname($user), 418 'eventType' => $eventtype, 419 'eventData' => $eventdata 420 ] 421 ); 422 } 423 424 /** 425 * Send all previously store events 426 * 427 * @param instance $instance 428 * @return object|null 429 */ 430 public function send_all_events(instance $instance): ?object { 431 if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) { 432 return $this->send_mock_request('backoffice/sendAllEvents', [ 433 'meetingID' => $instance->get_meeting_id(), 434 'sendQuery' => false, // We get the result directly here. 435 'secret' => \mod_bigbluebuttonbn\local\config::DEFAULT_SHARED_SECRET, 436 ]); 437 } 438 return null; 439 } 440 441 /** 442 * Reset the mock server 443 */ 444 public function reset_mock(): void { 445 if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) { 446 $this->send_mock_request('backoffice/reset'); 447 } 448 } 449 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body