Differences Between: [Versions 400 and 402] [Versions 401 and 402]
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 if (!empty($data['playback'])) { 262 $mockdata['playback'] = json_encode($data['playback']); 263 } 264 if (!empty($data['isBreakout'])) { 265 // If it is a breakout meeting, we do not have any way to know the real Id of the meeting 266 // unless we query the list of submeetings. 267 // For now we will just send the parent ID and let the mock server deal with the sequence + parentID 268 // to find the meetingID. 269 $mockdata['parentMeetingID'] = $instance->get_meeting_id(); 270 } else { 271 $mockdata['meetingID'] = $instance->get_meeting_id(); 272 } 273 274 $result = $this->send_mock_request('backoffice/createRecording', [], $mockdata); 275 276 return (string) $result->recordID; 277 } 278 279 /** 280 * Utility to send a request to the mock server 281 * 282 * @param string $endpoint 283 * @param array $params 284 * @param array $mockdata 285 * @return SimpleXMLElement|bool 286 * @throws moodle_exception 287 */ 288 protected function send_mock_request(string $endpoint, array $params = [], array $mockdata = []): SimpleXMLElement { 289 $url = $this->get_mocked_server_url($endpoint, $params); 290 291 foreach ($mockdata as $key => $value) { 292 if (is_array($value)) { 293 foreach ($value as $subkey => $subvalue) { 294 $paramname = "{$key}_{$subkey}"; 295 $url->param($paramname, $subvalue); 296 } 297 } else { 298 $url->param($key, $value); 299 } 300 } 301 302 $curl = new \curl(); 303 $result = $curl->get($url->out_omit_querystring(), $url->params()); 304 305 $retvalue = @simplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOBLANKS); 306 if ($retvalue === false) { 307 throw new moodle_exception('mockserverconnfailed', 'mod_bigbluebutton'); 308 } 309 return $retvalue; 310 } 311 312 /** 313 * Get a URL for a mocked BBB server endpoint. 314 * 315 * @param string $endpoint 316 * @param array $params 317 * @return moodle_url 318 */ 319 protected function get_mocked_server_url(string $endpoint = '', array $params = []): moodle_url { 320 return new moodle_url(TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER . '/' . $endpoint, $params); 321 } 322 323 /** 324 * Mock an in-progress meeting on the remote server. 325 * 326 * @param array $data 327 * @return stdClass 328 */ 329 public function create_meeting(array $data): stdClass { 330 $instance = instance::get_from_instanceid($data['instanceid']); 331 332 if (array_key_exists('groupid', $data)) { 333 $instance = instance::get_group_instance_from_instance($instance, $data['groupid']); 334 } 335 336 $meetingid = $instance->get_meeting_id(); 337 338 // Default room configuration. 339 $roomconfig = array_merge($data, [ 340 'meetingName' => $instance->get_meeting_name(), 341 'attendeePW' => $instance->get_viewer_password(), 342 'moderatorPW' => $instance->get_moderator_password(), 343 'voiceBridge' => $instance->get_voice_bridge(), 344 'meta' => [ 345 'bbb-context' => $instance->get_course()->fullname, 346 'bbb-context-id' => $instance->get_course()->id, 347 'bbb-context-label' => $instance->get_course()->shortname, 348 'bbb-context-name' => $instance->get_course()->fullname, 349 'bbb-origin' => 'Moodle', 350 'bbb-origin-tag' => 'moodle-mod_bigbluebuttonbn (TODO version)', 351 'bbb-recording-description' => $instance->get_meeting_description(), 352 'bbb-recording-name' => $instance->get_meeting_name(), 353 ], 354 ]); 355 if ((boolean) config::get('recordingready_enabled')) { 356 $roomconfig['meta']['bn-recording-ready-url'] = $instance->get_record_ready_url()->out(false); 357 } 358 if ((boolean) config::get('meetingevents_enabled')) { 359 $roomconfig['meta']['analytics-callback-url'] = $instance->get_meeting_event_notification_url()->out(false); 360 } 361 if (!empty($roomconfig['isBreakout'])) { 362 // If it is a breakout meeting, we do not have any way to know the real Id of the meeting 363 // For now we will just send the parent ID and let the mock server deal with the sequence + parentID 364 // to find the meetingID. 365 $roomconfig['parentMeetingID'] = $instance->get_meeting_id(); 366 } else { 367 $roomconfig['meetingID'] = $meetingid; 368 } 369 $this->send_mock_request('backoffice/createMeeting', [], $roomconfig); 370 return (object) $roomconfig; 371 } 372 373 /** 374 * Create a log record 375 * 376 * @param mixed $record 377 * @param array|null $options 378 */ 379 public function create_log($record, array $options = null) { 380 $instance = instance::get_from_instanceid($record['bigbluebuttonbnid']); 381 382 $record = array_merge([ 383 'meetingid' => $instance->get_meeting_id(), 384 ], (array) $record); 385 386 $testlogclass = new class extends logger { 387 /** 388 * Log test event 389 * 390 * @param instance $instance 391 * @param array $record 392 */ 393 public static function log_test_event(instance $instance, array $record): void { 394 self::log( 395 $instance, 396 logger::EVENT_CREATE, 397 $record 398 ); 399 } 400 }; 401 402 $testlogclass::log_test_event($instance, $record); 403 } 404 405 /** 406 * Set a value in the Mock server 407 * 408 * @param string $name 409 * @param mixed $value 410 * @return void 411 * @throws moodle_exception 412 */ 413 public function set_value(string $name, $value): void { 414 if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) { 415 $this->send_mock_request('backoffice/set', [], ['name' => $name, 'value' => json_encode($value)]); 416 } 417 } 418 419 /** 420 * Trigger a meeting event on BBB side 421 * 422 * @param object $user 423 * @param instance $instance 424 * @param string $eventtype 425 * @param string|null $eventdata 426 * @return void 427 */ 428 public function add_meeting_event(object $user, instance $instance, string $eventtype, string $eventdata = ''): void { 429 $this->send_mock_request('backoffice/addMeetingEvent', [ 430 'secret' => \mod_bigbluebuttonbn\local\config::DEFAULT_SHARED_SECRET, 431 'meetingID' => $instance->get_meeting_id(), 432 'attendeeID' => $user->id, 433 'attendeeName' => fullname($user), 434 'eventType' => $eventtype, 435 'eventData' => $eventdata 436 ] 437 ); 438 } 439 440 /** 441 * Send all previously store events 442 * 443 * @param instance $instance 444 * @return object|null 445 */ 446 public function send_all_events(instance $instance): ?object { 447 if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) { 448 return $this->send_mock_request('backoffice/sendAllEvents', [ 449 'meetingID' => $instance->get_meeting_id(), 450 'sendQuery' => false, // We get the result directly here. 451 'secret' => \mod_bigbluebuttonbn\local\config::DEFAULT_SHARED_SECRET, 452 ]); 453 } 454 return null; 455 } 456 457 /** 458 * Reset the mock server 459 */ 460 public function reset_mock(): void { 461 if (defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) { 462 $this->send_mock_request('backoffice/reset'); 463 } 464 } 465 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body