Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace mod_bigbluebuttonbn\local\proxy; 18 19 use cache; 20 use completion_info; 21 use Exception; 22 use mod_bigbluebuttonbn\completion\custom_completion; 23 use mod_bigbluebuttonbn\instance; 24 use mod_bigbluebuttonbn\local\config; 25 use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception; 26 use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception; 27 use moodle_url; 28 use stdClass; 29 use user_picture; 30 31 /** 32 * The bigbluebutton proxy class. 33 * 34 * This class acts as a proxy between Moodle and the BigBlueButton API server, 35 * and handles all requests relating to the server and meetings. 36 * 37 * @package mod_bigbluebuttonbn 38 * @copyright 2010 onwards, 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 bigbluebutton_proxy extends proxy_base { 43 44 /** 45 * Minimum poll interval for remote bigbluebutton server in seconds. 46 */ 47 const MIN_POLL_INTERVAL = 2; 48 49 /** 50 * Default poll interval for remote bigbluebutton server in seconds. 51 */ 52 const DEFAULT_POLL_INTERVAL = 5; 53 54 /** 55 * Builds and returns a url for joining a BigBlueButton meeting. 56 * 57 * @param instance $instance 58 * @param string|null $createtime 59 * 60 * @return string 61 */ 62 public static function get_join_url( 63 instance $instance, 64 ?string $createtime 65 ): string { 66 return self::internal_get_join_url($instance, $createtime); 67 } 68 69 /** 70 * Builds and returns a url for joining a BigBlueButton meeting. 71 * 72 * @param instance $instance 73 * @param string|null $createtime 74 * @param string $username 75 * @return string 76 */ 77 public static function get_guest_join_url( 78 instance $instance, 79 ?string $createtime, 80 string $username 81 ): string { 82 return self::internal_get_join_url($instance, $createtime, $username, true); 83 } 84 85 /** 86 * Internal helper method to builds and returns a url for joining a BigBlueButton meeting. 87 * 88 * @param instance $instance 89 * @param string|null $jointime = null 90 * @param string|null $userfullname 91 * @param bool $isguestjoin 92 * @return string 93 */ 94 private static function internal_get_join_url( 95 instance $instance, 96 ?string $jointime, 97 string $userfullname = null, 98 bool $isguestjoin = false 99 ): string { 100 $data = [ 101 'meetingID' => $instance->get_meeting_id(), 102 'fullName' => $userfullname ?? $instance->get_user_fullname(), 103 'password' => $instance->get_current_user_password(), 104 'logoutURL' => $isguestjoin ? $instance->get_guest_access_url()->out(false) : $instance->get_logout_url()->out(false), 105 'role' => $instance->get_current_user_role() 106 ]; 107 108 if (!$isguestjoin) { 109 $data['userID'] = $instance->get_user_id(); 110 $data['guest'] = "false"; 111 } else { 112 $data['guest'] = "true"; 113 } 114 115 if (!is_null($jointime)) { 116 $data['createTime'] = $jointime; 117 } 118 $currentlang = current_language(); 119 if (!empty(trim($currentlang))) { 120 $data['userdata-bbb_override_default_locale'] = $currentlang; 121 } 122 if ($instance->is_profile_picture_enabled()) { 123 $user = $instance->get_user(); 124 if (!empty($user->picture)) { 125 $data['avatarURL'] = self::get_avatar_url($user)->out(false); 126 } 127 } 128 return self::action_url('join', $data, [], $instance->get_instance_id()); 129 } 130 131 /** 132 * Get user avatar URL 133 * 134 * @param stdClass $user 135 * @return moodle_url 136 */ 137 private static function get_avatar_url(stdClass $user): moodle_url { 138 global $PAGE; 139 $userpicture = new user_picture($user); 140 $userpicture->includetoken = true; 141 $userpicture->size = 3; // Size f3. 142 return $userpicture->get_url($PAGE); 143 } 144 145 /** 146 * Perform api request on BBB. 147 * 148 * @return null|string 149 */ 150 public static function get_server_version(): ?string { 151 $cache = cache::make('mod_bigbluebuttonbn', 'serverinfo'); 152 $serverversion = $cache->get('serverversion'); 153 154 if (!$serverversion) { 155 $xml = self::fetch_endpoint_xml(''); 156 if (!$xml || $xml->returncode != 'SUCCESS') { 157 return null; 158 } 159 160 if (!isset($xml->version)) { 161 return null; 162 } 163 164 $serverversion = (string) $xml->version; 165 $cache->set('serverversion', $serverversion); 166 } 167 168 return (double) $serverversion; 169 } 170 171 /** 172 * Helper for getting the owner userid of a bigbluebuttonbn instance. 173 * 174 * @param stdClass $bigbluebuttonbn BigBlueButtonBN instance 175 * @return int ownerid (a valid user id or null if not registered/found) 176 */ 177 public static function get_instance_ownerid(stdClass $bigbluebuttonbn): int { 178 global $DB; 179 180 $filters = [ 181 'bigbluebuttonbnid' => $bigbluebuttonbn->id, 182 'log' => 'Add', 183 ]; 184 185 return (int) $DB->get_field('bigbluebuttonbn_logs', 'userid', $filters); 186 } 187 188 /** 189 * Helper evaluates if a voicebridge number is unique. 190 * 191 * @param int $instance 192 * @param int $voicebridge 193 * @return bool 194 */ 195 public static function is_voicebridge_number_unique(int $instance, int $voicebridge): bool { 196 global $DB; 197 if ($voicebridge == 0) { 198 return true; 199 } 200 $select = 'voicebridge = ' . $voicebridge; 201 if ($instance != 0) { 202 $select .= ' AND id <>' . $instance; 203 } 204 if (!$DB->get_records_select('bigbluebuttonbn', $select)) { 205 return true; 206 } 207 return false; 208 } 209 210 /** 211 * Helper function validates a remote resource. 212 * 213 * @param string $url 214 * @return bool 215 */ 216 public static function is_remote_resource_valid(string $url): bool { 217 $urlhost = parse_url($url, PHP_URL_HOST); 218 $serverurlhost = parse_url(\mod_bigbluebuttonbn\local\config::get('server_url'), PHP_URL_HOST); 219 220 if ($urlhost == $serverurlhost) { 221 // Skip validation when the recording URL host is the same as the configured BBB server. 222 return true; 223 } 224 225 $cache = cache::make('mod_bigbluebuttonbn', 'validatedurls'); 226 227 if ($cachevalue = $cache->get($urlhost)) { 228 // Skip validation when the recording URL was already validated. 229 return $cachevalue == 1; 230 } 231 232 $curl = new curl(); 233 $curl->head($url); 234 235 $isvalid = false; 236 if ($info = $curl->get_info()) { 237 if ($info['http_code'] == 200) { 238 $isvalid = true; 239 } else { 240 debugging( 241 "Resources hosted by {$urlhost} are unreachable. Server responded with {$info['http_code']}", 242 DEBUG_DEVELOPER 243 ); 244 $isvalid = false; 245 } 246 247 // Note: When a cache key is not found, it returns false. 248 // We need to distinguish between a result not found, and an invalid result. 249 $cache->set($urlhost, $isvalid ? 1 : 0); 250 } 251 252 return $isvalid; 253 } 254 255 /** 256 * Helper function enqueues one user for being validated as for completion. 257 * 258 * @param stdClass $bigbluebuttonbn 259 * @param int $userid 260 * @return void 261 */ 262 public static function enqueue_completion_event(stdClass $bigbluebuttonbn, int $userid): void { 263 try { 264 // Create the instance of completion_update_state task. 265 $task = new \mod_bigbluebuttonbn\task\completion_update_state(); 266 // Add custom data. 267 $data = [ 268 'bigbluebuttonbn' => $bigbluebuttonbn, 269 'userid' => $userid, 270 ]; 271 $task->set_custom_data($data); 272 // CONTRIB-7457: Task should be executed by a user, maybe Teacher as Student won't have rights for overriding. 273 // $ task -> set_userid ( $ user -> id );. 274 // Enqueue it. 275 \core\task\manager::queue_adhoc_task($task); 276 } catch (Exception $e) { 277 mtrace("Error while enqueuing completion_update_state task. " . (string) $e); 278 } 279 } 280 281 /** 282 * Helper function enqueues completion trigger. 283 * 284 * @param stdClass $bigbluebuttonbn 285 * @param int $userid 286 * @return void 287 */ 288 public static function update_completion_state(stdClass $bigbluebuttonbn, int $userid) { 289 global $CFG; 290 require_once($CFG->libdir . '/completionlib.php'); 291 list($course, $cm) = get_course_and_cm_from_instance($bigbluebuttonbn, 'bigbluebuttonbn'); 292 $completion = new completion_info($course); 293 if (!$completion->is_enabled($cm)) { 294 mtrace("Completion not enabled"); 295 return; 296 } 297 298 $bbbcompletion = new custom_completion($cm, $userid); 299 if ($bbbcompletion->get_overall_completion_state()) { 300 mtrace("Completion for userid $userid and bigbluebuttonid {$bigbluebuttonbn->id} updated."); 301 $completion->update_state($cm, COMPLETION_COMPLETE, $userid, true); 302 } else { 303 // Still update state to current value (prevent unwanted caching). 304 $completion->update_state($cm, COMPLETION_UNKNOWN, $userid); 305 mtrace("Activity not completed for userid $userid and bigbluebuttonid {$bigbluebuttonbn->id}."); 306 } 307 } 308 309 /** 310 * Helper function returns an array with the profiles (with features per profile) for the different types 311 * of bigbluebuttonbn instances. 312 * 313 * @return array 314 */ 315 public static function get_instance_type_profiles(): array { 316 $instanceprofiles = [ 317 instance::TYPE_ALL => [ 318 'id' => instance::TYPE_ALL, 319 'name' => get_string('instance_type_default', 'bigbluebuttonbn'), 320 'features' => ['all'] 321 ], 322 instance::TYPE_ROOM_ONLY => [ 323 'id' => instance::TYPE_ROOM_ONLY, 324 'name' => get_string('instance_type_room_only', 'bigbluebuttonbn'), 325 'features' => ['showroom', 'welcomemessage', 'voicebridge', 'waitformoderator', 'userlimit', 326 'recording', 'sendnotifications', 'lock', 'preuploadpresentation', 'permissions', 'schedule', 'groups', 327 'modstandardelshdr', 'availabilityconditionsheader', 'tagshdr', 'competenciessection', 328 'completionattendance', 'completionengagement', 'availabilityconditionsheader'] 329 ], 330 instance::TYPE_RECORDING_ONLY => [ 331 'id' => instance::TYPE_RECORDING_ONLY, 332 'name' => get_string('instance_type_recording_only', 'bigbluebuttonbn'), 333 'features' => ['showrecordings', 'importrecordings', 'availabilityconditionsheader'] 334 ], 335 ]; 336 return $instanceprofiles; 337 } 338 339 /** 340 * Helper function returns an array with the profiles (with features per profile) for the different types 341 * of bigbluebuttonbn instances that the user is allowed to create. 342 * 343 * @param bool $room 344 * @param bool $recording 345 * 346 * @return array 347 */ 348 public static function get_instance_type_profiles_create_allowed(bool $room, bool $recording): array { 349 $profiles = self::get_instance_type_profiles(); 350 if (!$room) { 351 unset($profiles[instance::TYPE_ROOM_ONLY]); 352 unset($profiles[instance::TYPE_ALL]); 353 } 354 if (!$recording) { 355 unset($profiles[instance::TYPE_RECORDING_ONLY]); 356 unset($profiles[instance::TYPE_ALL]); 357 } 358 return $profiles; 359 } 360 361 /** 362 * Helper function returns an array with the profiles (with features per profile) for the different types 363 * of bigbluebuttonbn instances. 364 * 365 * @param array $profiles 366 * 367 * @return array 368 */ 369 public static function get_instance_profiles_array(array $profiles = []): array { 370 $profilesarray = []; 371 foreach ($profiles as $key => $profile) { 372 $profilesarray[$profile['id']] = $profile['name']; 373 } 374 return $profilesarray; 375 } 376 377 /** 378 * Return the status of an activity [open|not_started|ended]. 379 * 380 * @param instance $instance 381 * @return string 382 */ 383 public static function view_get_activity_status(instance $instance): string { 384 $now = time(); 385 if (!empty($instance->get_instance_var('openingtime')) && $now < $instance->get_instance_var('openingtime')) { 386 // The activity has not been opened. 387 return 'not_started'; 388 } 389 if (!empty($instance->get_instance_var('closingtime')) && $now > $instance->get_instance_var('closingtime')) { 390 // The activity has been closed. 391 return 'ended'; 392 } 393 // The activity is open. 394 return 'open'; 395 } 396 397 /** 398 * Ensure that the remote server was contactable. 399 * 400 * @param instance $instance 401 */ 402 public static function require_working_server(instance $instance): void { 403 $version = null; 404 try { 405 $version = self::get_server_version(); 406 } catch (server_not_available_exception $e) { 407 self::handle_server_not_available($instance); 408 } 409 410 if (empty($version)) { 411 self::handle_server_not_available($instance); 412 } 413 } 414 415 /** 416 * Handle the server not being available. 417 * 418 * @param instance $instance 419 */ 420 public static function handle_server_not_available(instance $instance): void { 421 \core\notification::add( 422 self::get_server_not_available_message($instance), 423 \core\notification::ERROR 424 ); 425 redirect(self::get_server_not_available_url($instance)); 426 } 427 428 /** 429 * Get message when server not available 430 * 431 * @param instance $instance 432 * @return string 433 */ 434 public static function get_server_not_available_message(instance $instance): string { 435 if ($instance->is_admin()) { 436 return get_string('view_error_unable_join', 'mod_bigbluebuttonbn'); 437 } else if ($instance->is_moderator()) { 438 return get_string('view_error_unable_join_teacher', 'mod_bigbluebuttonbn'); 439 } else { 440 return get_string('view_error_unable_join_student', 'mod_bigbluebuttonbn'); 441 } 442 } 443 444 /** 445 * Get URL to the page displaying that the server is not available 446 * 447 * @param instance $instance 448 * @return string 449 */ 450 public static function get_server_not_available_url(instance $instance): string { 451 if ($instance->is_admin()) { 452 return new moodle_url('/admin/settings.php', ['section' => 'modsettingbigbluebuttonbn']); 453 } else if ($instance->is_moderator()) { 454 return new moodle_url('/course/view.php', ['id' => $instance->get_course_id()]); 455 } else { 456 return new moodle_url('/course/view.php', ['id' => $instance->get_course_id()]); 457 } 458 } 459 460 /** 461 * Create a Meeting 462 * 463 * @param array $data 464 * @param array $metadata 465 * @param string|null $presentationname 466 * @param string|null $presentationurl 467 * @param int|null $instanceid 468 * @return array 469 * @throws bigbluebutton_exception 470 */ 471 public static function create_meeting( 472 array $data, 473 array $metadata, 474 ?string $presentationname = null, 475 ?string $presentationurl = null, 476 ?int $instanceid = null 477 ): array { 478 $createmeetingurl = self::action_url('create', $data, $metadata, $instanceid); 479 480 $curl = new curl(); 481 if (!is_null($presentationname) && !is_null($presentationurl)) { 482 $payload = "<?xml version='1.0' encoding='UTF-8'?><modules><module name='presentation'><document url='" . 483 $presentationurl . "' /></module></modules>"; 484 485 $xml = $curl->post($createmeetingurl, $payload); 486 } else { 487 $xml = $curl->get($createmeetingurl); 488 } 489 490 self::assert_returned_xml($xml); 491 492 if (empty($xml->meetingID)) { 493 throw new bigbluebutton_exception('general_error_cannot_create_meeting'); 494 } 495 496 if ($xml->hasBeenForciblyEnded === 'true') { 497 throw new bigbluebutton_exception('index_error_forciblyended'); 498 } 499 500 return [ 501 'meetingID' => (string) $xml->meetingID, 502 'internalMeetingID' => (string) $xml->internalMeetingID, 503 'attendeePW' => (string) $xml->attendeePW, 504 'moderatorPW' => (string) $xml->moderatorPW 505 ]; 506 } 507 508 /** 509 * Get meeting info for a given meeting id 510 * 511 * @param string $meetingid 512 * @param int|null $instanceid 513 * @return array 514 */ 515 public static function get_meeting_info(string $meetingid, ?int $instanceid = null): array { 516 $xmlinfo = self::fetch_endpoint_xml('getMeetingInfo', ['meetingID' => $meetingid], [], $instanceid); 517 self::assert_returned_xml($xmlinfo, ['meetingid' => $meetingid]); 518 return (array) $xmlinfo; 519 } 520 521 /** 522 * Perform end meeting on BBB. 523 * 524 * @param string $meetingid 525 * @param string $modpw 526 * @param int|null $instanceid 527 */ 528 public static function end_meeting(string $meetingid, string $modpw, ?int $instanceid = null): void { 529 $xml = self::fetch_endpoint_xml('end', ['meetingID' => $meetingid, 'password' => $modpw], [], $instanceid); 530 self::assert_returned_xml($xml, ['meetingid' => $meetingid]); 531 } 532 533 /** 534 * Helper evaluates if the bigbluebutton server used belongs to blindsidenetworks domain. 535 * 536 * @return bool 537 */ 538 public static function is_bn_server() { 539 if (config::get('bn_server')) { 540 return true; 541 } 542 $parsedurl = parse_url(config::get('server_url')); 543 if (!isset($parsedurl['host'])) { 544 return false; 545 } 546 $h = $parsedurl['host']; 547 $hends = explode('.', $h); 548 $hendslength = count($hends); 549 return ($hends[$hendslength - 1] == 'com' && $hends[$hendslength - 2] == 'blindsidenetworks'); 550 } 551 552 /** 553 * Get the poll interval as it is set in the configuration 554 * 555 * If configuration value is under the threshold of {@see self::MIN_POLL_INTERVAL}, 556 * then return the {@see self::MIN_POLL_INTERVAL} value. 557 * 558 * @return int the poll interval in seconds 559 */ 560 public static function get_poll_interval(): int { 561 $pollinterval = intval(config::get('poll_interval')); 562 if ($pollinterval < self::MIN_POLL_INTERVAL) { 563 $pollinterval = self::MIN_POLL_INTERVAL; 564 } 565 return $pollinterval; 566 } 567 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body