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