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