bbb.lib.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class bbb
  5. * This script initiates a video conference session, calling the BigBlueButton
  6. * API
  7. * @package chamilo.plugin.bigbluebutton
  8. *
  9. * BigBlueButton-Chamilo connector class
  10. */
  11. class bbb
  12. {
  13. public $url;
  14. public $salt;
  15. public $api;
  16. public $user_complete_name = null;
  17. public $protocol = 'http://';
  18. public $debug = false;
  19. public $logout_url = null;
  20. public $plugin_enabled = false;
  21. /**
  22. *
  23. * Constructor (generates a connection to the API and the Chamilo settings
  24. * required for the connection to the video conference server)
  25. * @param string $host
  26. * @param string $salt
  27. */
  28. public function __construct($host = null, $salt = null)
  29. {
  30. // Initialize video server settings from global settings
  31. $plugin = BBBPlugin::create();
  32. $bbb_plugin = $plugin->get('tool_enable');
  33. if (empty($host)) {
  34. $bbb_host = $plugin->get('host');
  35. } else {
  36. $bbb_host = $host;
  37. }
  38. if (empty($salt)) {
  39. $bbb_salt = $plugin->get('salt');
  40. } else {
  41. $bbb_salt = $salt;
  42. }
  43. $this->logout_url = api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.api_get_cidreq();
  44. $this->table = Database::get_main_table('plugin_bbb_meeting');
  45. if ($bbb_plugin == true) {
  46. $user_info = api_get_user_info();
  47. $this->user_complete_name = $user_info['complete_name'];
  48. $this->salt = $bbb_salt;
  49. $info = parse_url($bbb_host);
  50. $this->url = $bbb_host.'/bigbluebutton/';
  51. if (isset($info['scheme'])) {
  52. $this->protocol = $info['scheme'].'://';
  53. $this->url = str_replace($this->protocol, '', $this->url);
  54. }
  55. // Setting BBB api
  56. define('CONFIG_SECURITY_SALT', $this->salt);
  57. define('CONFIG_SERVER_BASE_URL', $this->url);
  58. $this->api = new BigBlueButtonBN();
  59. $this->plugin_enabled = true;
  60. }
  61. }
  62. /**
  63. * Checks whether a user is teacher in the current course
  64. * @return bool True if the user can be considered a teacher in this course, false otherwise
  65. */
  66. public function is_teacher()
  67. {
  68. return api_is_course_admin() || api_is_coach() || api_is_platform_admin();
  69. }
  70. /**
  71. * See this file in you BBB to set up default values
  72. /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
  73. *
  74. More record information:
  75. http://code.google.com/p/bigbluebutton/wiki/RecordPlaybackSpecification
  76. # Default maximum number of users a meeting can have.
  77. # Doesn't get enforced yet but is the default value when the create
  78. # API doesn't pass a value.
  79. defaultMaxUsers=20
  80. # Default duration of the meeting in minutes.
  81. # Current default is 0 (meeting doesn't end).
  82. defaultMeetingDuration=0
  83. # Remove the meeting from memory when the end API is called.
  84. # This allows 3rd-party apps to recycle the meeting right-away
  85. # instead of waiting for the meeting to expire (see below).
  86. removeMeetingWhenEnded=false
  87. # The number of minutes before the system removes the meeting from memory.
  88. defaultMeetingExpireDuration=1
  89. # The number of minutes the system waits when a meeting is created and when
  90. # a user joins. If after this period, a user hasn't joined, the meeting is
  91. # removed from memory.
  92. defaultMeetingCreateJoinDuration=5
  93. *
  94. */
  95. public function create_meeting($params)
  96. {
  97. $params['c_id'] = api_get_course_int_id();
  98. $course_code = api_get_course_id();
  99. $params['session_id'] = api_get_session_id();
  100. $attende_password = $params['attendee_pw'] = isset($params['moderator_pw']) ? $params['moderator_pw'] : api_get_course_id();
  101. $moderator_password = $params['moderator_pw'] = isset($params['moderator_pw']) ? $params['moderator_pw'] : $this->get_mod_meeting_password();
  102. $params['record'] = api_get_course_setting('big_blue_button_record_and_store', $course_code) == 1 ? true : false;
  103. $max = api_get_course_setting('big_blue_button_max_students_allowed', $course_code);
  104. $max = isset($max) ? $max : -1;
  105. $params['status'] = 1;
  106. if ($this->debug) error_log("enter create_meeting ".print_r($params, 1));
  107. $params['created_at'] = api_get_utc_datetime();
  108. $id = Database::insert($this->table, $params);
  109. if ($id) {
  110. if ($this->debug) error_log("create_meeting: $id ");
  111. $meeting_name = isset($params['meeting_name']) ? $params['meeting_name'] : api_get_course_id().'-'.api_get_session_id();
  112. $welcome_msg = isset($params['welcome_msg']) ? $params['welcome_msg'] : null;
  113. $record = isset($params['record']) && $params['record'] ? 'true' : 'false';
  114. $duration = isset($params['duration']) ? intval($params['duration']) : 0;
  115. // This setting currently limits the maximum conference duration,
  116. // to avoid lingering sessions on the videoconference server #6261
  117. $duration = 300;
  118. $bbb_params = array(
  119. 'meetingId' => $id, // REQUIRED
  120. 'meetingName' => $meeting_name, // REQUIRED
  121. 'attendeePw' => $attende_password, // Match this value in getJoinMeetingURL() to join as attendee.
  122. 'moderatorPw' => $moderator_password, // Match this value in getJoinMeetingURL() to join as moderator.
  123. 'welcomeMsg' => $welcome_msg, // ''= use default. Change to customize.
  124. 'dialNumber' => '', // The main number to call into. Optional.
  125. 'voiceBridge' => '12345', // PIN to join voice. Required.
  126. 'webVoice' => '', // Alphanumeric to join voice. Optional.
  127. 'logoutUrl' => $this->logout_url,
  128. 'maxParticipants' => $max, // Optional. -1 = unlimitted. Not supported in BBB. [number]
  129. 'record' => $record, // New. 'true' will tell BBB to record the meeting.
  130. 'duration' => $duration, // Default = 0 which means no set duration in minutes. [number]
  131. //'meta_category' => '', // Use to pass additional info to BBB server. See API docs.
  132. );
  133. if ($this->debug) error_log("create_meeting params: ".print_r($bbb_params,1));
  134. $status = false;
  135. $meeting = null;
  136. while ($status == false) {
  137. $result = $this->api->createMeetingWithXmlResponseArray(
  138. $bbb_params
  139. );
  140. if (isset($result) && strval(
  141. $result['returncode']
  142. ) == 'SUCCESS'
  143. ) {
  144. if ($this->debug) {
  145. error_log(
  146. "create_meeting result: " . print_r($result, 1)
  147. );
  148. }
  149. $meeting = $this->join_meeting($meeting_name, true);
  150. return $meeting;
  151. }
  152. }
  153. return $this->logout;
  154. }
  155. }
  156. /**
  157. * Tells whether the given meeting exists and is running
  158. * (using course code as name)
  159. * @param string $meeting_name Meeting name (usually the course code)
  160. *
  161. * @return bool True if meeting exists, false otherwise
  162. * @assert ('') === false
  163. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  164. */
  165. public function meeting_exists($meeting_name)
  166. {
  167. if (empty($meeting_name)) { return false; }
  168. $course_id = api_get_course_int_id();
  169. $session_id = api_get_session_id();
  170. $meeting_data = Database::select(
  171. '*',
  172. $this->table,
  173. array(
  174. 'where' => array(
  175. 'c_id = ? AND session_id = ? AND meeting_name = ? AND status = 1 ' =>
  176. array($course_id, $session_id, $meeting_name)
  177. )
  178. ),
  179. 'first'
  180. );
  181. if ($this->debug) error_log("meeting_exists ".print_r($meeting_data,1));
  182. if (empty($meeting_data)) {
  183. return false;
  184. } else {
  185. return true;
  186. }
  187. }
  188. /**
  189. * Returns a meeting "join" URL
  190. * @param string The name of the meeting (usually the course code)
  191. * @return mixed The URL to join the meeting, or false on error
  192. * @todo implement moderator pass
  193. * @assert ('') === false
  194. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  195. */
  196. public function join_meeting($meeting_name, $loop = false)
  197. {
  198. if (empty($meeting_name)) {
  199. return false;
  200. }
  201. $pass = $this->get_user_meeting_password();
  202. $meeting_data = Database::select(
  203. '*',
  204. $this->table,
  205. array('where' => array('meeting_name = ? AND status = 1 ' => $meeting_name)),
  206. 'first'
  207. );
  208. if (empty($meeting_data) || !is_array($meeting_data)) {
  209. if ($this->debug) error_log("meeting does not exist: $meeting_name ");
  210. return false;
  211. }
  212. $params = array(
  213. 'meetingId' => $meeting_data['id'],
  214. // -- REQUIRED - The unique id for the meeting
  215. 'password' => $this->get_mod_meeting_password()
  216. // -- REQUIRED - The moderator password for the meeting
  217. );
  218. $status = false;
  219. $meeting_info_exists = false;
  220. while ($status == false) {
  221. $meeting_is_running_info = $this->get_meeting_info($params);
  222. error_log(print_r($meeting_is_running_info, 1));
  223. if (strval($meeting_is_running_info['returncode']) == 'SUCCESS' &&
  224. isset($meeting_is_running_info['meetingName']) &&
  225. !empty($meeting_is_running_info['meetingName'])
  226. //strval($meeting_is_running_info['running']) == 'true'
  227. ) {
  228. $meeting_info_exists = true;
  229. }
  230. if ($this->debug) {
  231. error_log(
  232. "meeting is running: " . intval($meeting_info_exists)
  233. );
  234. }
  235. if ($meeting_info_exists) {
  236. $status = true;
  237. }
  238. if ($loop) {
  239. continue;
  240. } else {
  241. break;
  242. }
  243. }
  244. if ($meeting_info_exists) {
  245. $joinParams = array(
  246. 'meetingId' => $meeting_data['id'], // -- REQUIRED - A unique id for the meeting
  247. 'username' => $this->user_complete_name, //-- REQUIRED - The name that will display for the user in the meeting
  248. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  249. //'createTime' => api_get_utc_datetime(), //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  250. 'userID' => api_get_user_id(), //-- OPTIONAL - string
  251. 'webVoiceConf' => '' // -- OPTIONAL - string
  252. );
  253. $url = $this->api->getJoinMeetingURL($joinParams);
  254. $url = $this->protocol.$url;
  255. } else {
  256. $url = $this->logout_url;
  257. }
  258. if ($this->debug) error_log("return url :".$url);
  259. return $url;
  260. }
  261. /**
  262. * Get information about the given meeting
  263. * @param array ...?
  264. * @return mixed Array of information on success, false on error
  265. * @assert (array()) === false
  266. */
  267. public function get_meeting_info($params)
  268. {
  269. try {
  270. $result = $this->api->getMeetingInfoWithXmlResponseArray($params);
  271. if ($result == null) {
  272. if ($this->debug) error_log("Failed to get any response. Maybe we can't contact the BBB server.");
  273. } else {
  274. return $result;
  275. }
  276. } catch (Exception $e) {
  277. if ($this->debug) error_log('Caught exception: ', $e->getMessage(), "\n");
  278. }
  279. return false;
  280. }
  281. /**
  282. * Gets all the course meetings saved in the plugin_bbb_meeting table
  283. * @return array Array of current open meeting rooms
  284. */
  285. public function get_course_meetings()
  286. {
  287. $pass = $this->get_user_meeting_password();
  288. $meeting_list = Database::select('*', $this->table, array('where' => array('c_id = ? AND session_id = ? ' => array(api_get_course_int_id(), api_get_session_id()))));
  289. $new_meeting_list = array();
  290. $item = array();
  291. foreach ($meeting_list as $meeting_db) {
  292. $meeting_bbb = $this->get_meeting_info(array('meetingId' => $meeting_db['id'], 'password' => $pass));
  293. $meeting_bbb['end_url'] = api_get_self().'?'.api_get_cidreq().'&action=end&id='.$meeting_db['id'];
  294. if ((string)$meeting_bbb['returncode'] == 'FAILED') {
  295. if ($meeting_db['status'] == 1 && $this->is_teacher()) {
  296. $this->end_meeting($meeting_db['id']);
  297. }
  298. } else {
  299. $meeting_bbb['add_to_calendar_url'] = api_get_self().'?'.api_get_cidreq().'&action=add_to_calendar&id='.$meeting_db['id'].'&start='.api_strtotime($meeting_db['created_at']);
  300. }
  301. $record_array = array();
  302. if ($meeting_db['record'] == 1) {
  303. $recordingParams = array(
  304. 'meetingId' => $meeting_db['id'], //-- OPTIONAL - comma separate if multiple ids
  305. );
  306. //To see the recording list in your BBB server do: bbb-record --list
  307. $records = $this->api->getRecordingsWithXmlResponseArray($recordingParams);
  308. if (!empty($records)) {
  309. $count = 1;
  310. if (isset($records['message']) && !empty($records['message'])) {
  311. if ($records['messageKey'] == 'noRecordings') {
  312. $record_array[] = get_lang('NoRecording');
  313. } else {
  314. //$record_array[] = $records['message'];
  315. }
  316. } else {
  317. foreach ($records as $record) {
  318. if (is_array($record) && isset($record['recordId'])) {
  319. $url = Display::url(get_lang('ViewRecord'), $record['playbackFormatUrl'], array('target' => '_blank'));
  320. if ($this->is_teacher()) {
  321. $url .= Display::url(Display::return_icon('link.gif',get_lang('CopyToLinkTool')), api_get_self().'?'.api_get_cidreq().'&action=copy_record_to_link_tool&id='.$meeting_db['id'].'&record_id='.$record['recordId']);
  322. $url .= Display::url(Display::return_icon('agenda.png',get_lang('AddToCalendar')), api_get_self().'?'.api_get_cidreq().'&action=add_to_calendar&id='.$meeting_db['id'].'&start='.api_strtotime($meeting_db['created_at']).'&url='.$record['playbackFormatUrl']);
  323. $url .= Display::url(Display::return_icon('delete.png',get_lang('Delete')), api_get_self().'?'.api_get_cidreq().'&action=delete_record&id='.$record['recordId']);
  324. }
  325. //$url .= api_get_self().'?action=publish&id='.$record['recordID'];
  326. $count++;
  327. $record_array[] = $url;
  328. } else {
  329. /*if (is_array($record) && isset($record['recordID']) && isset($record['playbacks'])) {
  330. //Fix the bbb timestamp
  331. //$record['startTime'] = substr($record['startTime'], 0, strlen($record['startTime']) -3);
  332. //$record['endTime'] = substr($record['endTime'], 0, strlen($record['endTime']) -3);
  333. //.' - '.api_convert_and_format_date($record['startTime']).' - '.api_convert_and_format_date($record['endTime'])
  334. foreach($record['playbacks'] as $item) {
  335. $url = Display::url(get_lang('ViewRecord'), $item['url'], array('target' => '_blank'));
  336. //$url .= Display::url(get_lang('DeleteRecord'), api_get_self().'?action=delete_record&'.$record['recordID']);
  337. if ($this->is_teacher()) {
  338. $url .= Display::url(Display::return_icon('link.gif',get_lang('CopyToLinkTool')), api_get_self().'?action=copy_record_to_link_tool&id='.$meeting_db['id'].'&record_id='.$record['recordID']);
  339. $url .= Display::url(Display::return_icon('agenda.png',get_lang('AddToCalendar')), api_get_self().'?action=add_to_calendar&id='.$meeting_db['id'].'&start='.api_strtotime($meeting_db['created_at']).'&url='.$item['url']);
  340. $url .= Display::url(Display::return_icon('delete.png',get_lang('Delete')), api_get_self().'?action=delete_record&id='.$record['recordID']);
  341. }
  342. //$url .= api_get_self().'?action=publish&id='.$record['recordID'];
  343. $count++;
  344. $record_array[] = $url;
  345. }
  346. }*/
  347. }
  348. }
  349. }
  350. }
  351. //var_dump($record_array);
  352. $item['show_links'] = implode('<br />', $record_array);
  353. }
  354. $item['created_at'] = api_convert_and_format_date($meeting_db['created_at']);
  355. //created_at
  356. $meeting_db['created_at'] = $item['created_at']; //avoid overwrite in array_merge() below
  357. $item['publish_url'] = api_get_self().'?'.api_get_cidreq().'&action=publish&id='.$meeting_db['id'];
  358. $item['unpublish_url'] = api_get_self().'?'.api_get_cidreq().'&action=unpublish&id='.$meeting_db['id'];
  359. if ($meeting_db['status'] == 1) {
  360. $joinParams = array(
  361. 'meetingId' => $meeting_db['id'], //-- REQUIRED - A unique id for the meeting
  362. 'username' => $this->user_complete_name, //-- REQUIRED - The name that will display for the user in the meeting
  363. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  364. 'createTime' => '', //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  365. 'userID' => '', // -- OPTIONAL - string
  366. 'webVoiceConf' => '' // -- OPTIONAL - string
  367. );
  368. $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
  369. }
  370. $item = array_merge($item, $meeting_db, $meeting_bbb);
  371. $new_meeting_list[] = $item;
  372. }
  373. return $new_meeting_list;
  374. }
  375. /**
  376. * Function disabled
  377. */
  378. public function publish_meeting($id)
  379. {
  380. //return BigBlueButtonBN::setPublishRecordings($id, 'true', $this->url, $this->salt);
  381. }
  382. /**
  383. * Function disabled
  384. */
  385. public function unpublish_meeting($id)
  386. {
  387. //return BigBlueButtonBN::setPublishRecordings($id, 'false', $this->url, $this->salt);
  388. }
  389. /**
  390. * Closes a meeting (usually when the user click on the close button from
  391. * the conferences listing.
  392. * @param string The name of the meeting (usually the course code)
  393. * @return void
  394. * @assert (0) === false
  395. */
  396. public function end_meeting($id)
  397. {
  398. if (empty($id)) { return false; }
  399. $pass = $this->get_user_meeting_password();
  400. $endParams = array(
  401. 'meetingId' => $id, // REQUIRED - We have to know which meeting to end.
  402. 'password' => $pass, // REQUIRED - Must match moderator pass for meeting.
  403. );
  404. $this->api->endMeetingWithXmlResponseArray($endParams);
  405. Database::update($this->table, array('status' => 0, 'closed_at' => api_get_utc_datetime()), array('id = ? ' => $id));
  406. }
  407. /**
  408. * Gets the password for a specific meeting for the current user
  409. * @return string A moderator password if user is teacher, or the course code otherwise
  410. */
  411. public function get_user_meeting_password()
  412. {
  413. if ($this->is_teacher()) {
  414. return $this->get_mod_meeting_password();
  415. } else {
  416. return api_get_course_id();
  417. }
  418. }
  419. /**
  420. * Generated a moderator password for the meeting
  421. * @return string A password for the moderation of the videoconference
  422. */
  423. public function get_mod_meeting_password()
  424. {
  425. return api_get_course_id().'mod';
  426. }
  427. /**
  428. * Get users online in the current course room
  429. * @return int The number of users currently connected to the videoconference
  430. * @assert () > -1
  431. */
  432. public function get_users_online_in_current_room()
  433. {
  434. $course_id = api_get_course_int_id();
  435. $session_id = api_get_session_id();
  436. $meeting_data = Database::select('*', $this->table, array('where' => array('c_id = ? AND session_id = ? AND status = 1 ' => array($course_id, $session_id))), 'first');
  437. if (empty($meeting_data)) {
  438. return 0;
  439. }
  440. $pass = $this->get_mod_meeting_password();
  441. $info = $this->get_meeting_info(array('meetingId' => $meeting_data['id'], 'password' => $pass));
  442. if (!empty($info) && isset($info['participantCount'])) {
  443. return $info['participantCount'];
  444. }
  445. return 0;
  446. }
  447. /**
  448. * Deletes a previous recording of a meeting
  449. * @param int integral ID of the recording
  450. * @return array ?
  451. * @assert () === false
  452. * @todo Also delete links and agenda items created from this recording
  453. */
  454. public function delete_record($ids)
  455. {
  456. if (empty($ids) or (is_array($ids) && count($ids)==0)) { return false; }
  457. $recordingParams = array(
  458. /*
  459. * NOTE: Set the recordId below to a valid id after you have
  460. * created a recorded meeting, and received a real recordID
  461. * back from your BBB server using the
  462. * getRecordingsWithXmlResponseArray method.
  463. */
  464. // REQUIRED - We have to know which recording:
  465. 'recordId' => $ids,
  466. );
  467. return $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
  468. }
  469. /**
  470. * Creates a link in the links tool from the given videoconference recording
  471. * @param int ID of the item in the plugin_bbb_meeting table
  472. * @param string Hash identifying the recording, as provided by the API
  473. * @return mixed ID of the newly created link, or false on error
  474. * @assert (null, null) === false
  475. * @assert (1, null) === false
  476. * @assert (null, 'abcdefabcdefabcdefabcdef') === false
  477. */
  478. public function copy_record_to_link_tool($id, $record_id)
  479. {
  480. if (empty($id) or empty($record_id)) {
  481. return false;
  482. }
  483. require_once api_get_path(LIBRARY_PATH).'link.lib.php';
  484. $records = BigBlueButtonBN::getRecordingsArray($id, $this->url, $this->salt);
  485. if (!empty($records)) {
  486. foreach ($records as $record) {
  487. if ($record['recordID'] == $record_id) {
  488. if (is_array($record) && isset($record['recordID']) && isset($record['playbacks'])) {
  489. foreach ($record['playbacks'] as $item) {
  490. $link = new Link();
  491. $params['url'] = $item['url'];
  492. $params['title'] = 'bbb 1';
  493. $id = $link->save($params);
  494. return $id;
  495. }
  496. }
  497. }
  498. }
  499. }
  500. return false;
  501. }
  502. /**
  503. * Checks if the videoconference server is running.
  504. * Function currently disabled (always returns 1)
  505. * @return bool True if server is running, false otherwise
  506. * @assert () === false
  507. */
  508. public function is_server_running()
  509. {
  510. return true;
  511. //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
  512. }
  513. /**
  514. * Get active session in the all platform
  515. */
  516. public function getActiveSessionsCount()
  517. {
  518. $meetingList = Database::select(
  519. 'count(id) as count',
  520. $this->table,
  521. array('where' => array('status = ?' => array(1))),
  522. 'first'
  523. );
  524. return $meetingList['count'];
  525. }
  526. /**
  527. * @param string $url
  528. */
  529. public function redirectToBBB($url)
  530. {
  531. if (file_exists(__DIR__ . '/../config.vm.php')) {
  532. // Using VM
  533. echo Display::url(get_lang('ClickToContinue'), $url);
  534. exit;
  535. } else {
  536. // Classic
  537. header("Location: $url");
  538. exit;
  539. }
  540. // js
  541. /*echo '<script>';
  542. echo 'window.location = "'.$url.'"';
  543. echo '</script>';
  544. exit;*/
  545. }
  546. }