bbb.lib.php 22 KB

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