bbb.lib.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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. $duration = 30;
  100. $bbb_params = array(
  101. 'meetingId' => $id, // REQUIRED
  102. 'meetingName' => $meeting_name, // REQUIRED
  103. 'attendeePw' => $attende_password, // Match this value in getJoinMeetingURL() to join as attendee.
  104. 'moderatorPw' => $moderator_password, // Match this value in getJoinMeetingURL() to join as moderator.
  105. 'welcomeMsg' => $welcome_msg, // ''= use default. Change to customize.
  106. 'dialNumber' => '', // The main number to call into. Optional.
  107. 'voiceBridge' => '12345', // PIN to join voice. Required.
  108. 'webVoice' => '', // Alphanumeric to join voice. Optional.
  109. 'logoutUrl' => $this->logout_url,
  110. 'maxParticipants' => $max, // Optional. -1 = unlimitted. Not supported in BBB. [number]
  111. 'record' => $record, // New. 'true' will tell BBB to record the meeting.
  112. 'duration' => $duration, // Default = 0 which means no set duration in minutes. [number]
  113. //'meta_category' => '', // Use to pass additional info to BBB server. See API docs.
  114. );
  115. if ($this->debug) error_log("create_meeting params: ".print_r($bbb_params,1));
  116. $result = $this->api->createMeetingWithXmlResponseArray($bbb_params);
  117. if (isset($result) && (string)$result['returncode'] == 'SUCCESS') {
  118. if ($this->debug) error_log("create_meeting result: ".print_r($result,1));
  119. return $this->join_meeting($meeting_name);
  120. }
  121. return $this->logout;
  122. }
  123. }
  124. /**
  125. * Tells whether the given meeting exists and is running
  126. * (using course code as name)
  127. * @param string Meeting name (usually the course code)
  128. * @return bool True if meeting exists, false otherwise
  129. * @assert ('') === false
  130. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  131. */
  132. function meeting_exists($meeting_name) {
  133. if (empty($meeting_name)) { return false; }
  134. $course_id = api_get_course_int_id();
  135. $meeting_data = Database::select('*', $this->table, array('where' => array('c_id = ? AND meeting_name = ? AND status = 1 ' => array($course_id, $meeting_name))), 'first');
  136. if ($this->debug) error_log("meeting_exists ".print_r($meeting_data,1));
  137. if (empty($meeting_data)) {
  138. return false;
  139. } else {
  140. return true;
  141. }
  142. }
  143. /**
  144. * Returns a meeting "join" URL
  145. * @param string The name of the meeting (usually the course code)
  146. * @return mixed The URL to join the meeting, or false on error
  147. * @todo implement moderator pass
  148. * @assert ('') === false
  149. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  150. */
  151. function join_meeting($meeting_name) {
  152. if (empty($meeting_name)) { return false; }
  153. $pass = $this->get_user_meeting_password();
  154. $meeting_data = Database::select('*', $this->table, array('where' => array('meeting_name = ? AND status = 1 ' => $meeting_name)), 'first');
  155. if (empty($meeting_data)) {
  156. if ($this->debug) error_log("meeting does not exist: $meeting_name ");
  157. return false;
  158. }
  159. $meeting_is_running_info = $this->api->isMeetingRunningWithXmlResponseArray($meeting_data['id']);
  160. $meeting_is_running = $meeting_is_running_info['running'] == 'true' ? true : false;
  161. if ($this->debug) error_log("meeting is running: ".$meeting_is_running);
  162. $params = array(
  163. 'meetingId' => $meeting_data['id'], // -- REQUIRED - The unique id for the meeting
  164. 'password' => $this->get_mod_meeting_password() // -- REQUIRED - The moderator password for the meeting
  165. );
  166. $meeting_info_exists = $this->get_meeting_info($params);
  167. if (isset($meeting_is_running) && $meeting_info_exists) {
  168. $joinParams = array(
  169. 'meetingId' => $meeting_data['id'], // -- REQUIRED - A unique id for the meeting
  170. 'username' => $this->user_complete_name, //-- REQUIRED - The name that will display for the user in the meeting
  171. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  172. //'createTime' => api_get_utc_datetime(), //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  173. 'userID' => api_get_user_id(), //-- OPTIONAL - string
  174. 'webVoiceConf' => '' // -- OPTIONAL - string
  175. );
  176. $url = $this->api->getJoinMeetingURL($joinParams);
  177. $url = $this->protocol.$url;
  178. } else {
  179. $url = $this->logout_url;
  180. }
  181. if ($this->debug) error_log("return url :".$url);
  182. return $url;
  183. }
  184. /**
  185. * Get information about the given meeting
  186. * @param array ...?
  187. * @return mixed Array of information on success, false on error
  188. * @assert (array()) === false
  189. */
  190. function get_meeting_info($params) {
  191. try {
  192. $result = $this->api->getMeetingInfoWithXmlResponseArray($params);
  193. if ($result == null) {
  194. if ($this->debug) error_log("Failed to get any response. Maybe we can't contact the BBB server.");
  195. } else {
  196. return $result;
  197. }
  198. } catch (Exception $e) {
  199. if ($this->debug) error_log('Caught exception: ', $e->getMessage(), "\n");
  200. }
  201. return false;
  202. }
  203. /**
  204. * Gets all the course meetings saved in the plugin_bbb_meeting table
  205. * @return array Array of current open meeting rooms
  206. */
  207. function get_course_meetings() {
  208. $pass = $this->get_user_meeting_password();
  209. $meeting_list = Database::select('*', $this->table, array('where' => array('c_id = ? ' => api_get_course_int_id())));
  210. $new_meeting_list = array();
  211. $item = array();
  212. foreach ($meeting_list as $meeting_db) {
  213. $meeting_bbb = $this->get_meeting_info(array('meetingId' => $meeting_db['id'], 'password' => $pass));
  214. $meeting_bbb['end_url'] = api_get_self().'?action=end&id='.$meeting_db['id'];
  215. if ((string)$meeting_bbb['returncode'] == 'FAILED') {
  216. if ($meeting_db['status'] == 1 && $this->is_teacher()) {
  217. $this->end_meeting($meeting_db['id']);
  218. }
  219. } else {
  220. $meeting_bbb['add_to_calendar_url'] = api_get_self().'?action=add_to_calendar&id='.$meeting_db['id'].'&start='.api_strtotime($meeting_db['created_at']);
  221. }
  222. $record_array = array();
  223. if ($meeting_db['record'] == 1) {
  224. $recordingParams = array(
  225. 'meetingId' => $meeting_db['id'], //-- OPTIONAL - comma separate if multiple ids
  226. );
  227. //To see the recording list in your BBB server do: bbb-record --list
  228. $records = $this->api->getRecordingsWithXmlResponseArray($recordingParams);
  229. if (!empty($records)) {
  230. $count = 1;
  231. if (isset($records['message']) && !empty($records['message'])) {
  232. if ($records['messageKey'] == 'noRecordings') {
  233. $record_array[] = get_lang('NoRecording');
  234. } else {
  235. //$record_array[] = $records['message'];
  236. }
  237. } else {
  238. foreach ($records as $record) {
  239. if (is_array($record) && isset($record['recordId'])) {
  240. $url = Display::url(get_lang('ViewRecord'), $record['playbackFormatUrl'], array('target' => '_blank'));
  241. if ($this->is_teacher()) {
  242. $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']);
  243. $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']);
  244. $url .= Display::url(Display::return_icon('delete.png',get_lang('Delete')), api_get_self().'?action=delete_record&id='.$record['recordId']);
  245. }
  246. //$url .= api_get_self().'?action=publish&id='.$record['recordID'];
  247. $count++;
  248. $record_array[] = $url;
  249. } else {
  250. /*if (is_array($record) && isset($record['recordID']) && isset($record['playbacks'])) {
  251. //Fix the bbb timestamp
  252. //$record['startTime'] = substr($record['startTime'], 0, strlen($record['startTime']) -3);
  253. //$record['endTime'] = substr($record['endTime'], 0, strlen($record['endTime']) -3);
  254. //.' - '.api_convert_and_format_date($record['startTime']).' - '.api_convert_and_format_date($record['endTime'])
  255. foreach($record['playbacks'] as $item) {
  256. $url = Display::url(get_lang('ViewRecord'), $item['url'], array('target' => '_blank'));
  257. //$url .= Display::url(get_lang('DeleteRecord'), api_get_self().'?action=delete_record&'.$record['recordID']);
  258. if ($this->is_teacher()) {
  259. $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']);
  260. $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']);
  261. $url .= Display::url(Display::return_icon('delete.png',get_lang('Delete')), api_get_self().'?action=delete_record&id='.$record['recordID']);
  262. }
  263. //$url .= api_get_self().'?action=publish&id='.$record['recordID'];
  264. $count++;
  265. $record_array[] = $url;
  266. }
  267. }*/
  268. }
  269. }
  270. }
  271. }
  272. $item['show_links'] = implode('<br />', $record_array);
  273. }
  274. $item['created_at'] = api_convert_and_format_date($meeting_db['created_at']);
  275. //created_at
  276. $item['publish_url'] = api_get_self().'?action=publish&id='.$meeting_db['id'];
  277. $item['unpublish_url'] = api_get_self().'?action=unpublish&id='.$meeting_db['id'];
  278. if ($meeting_db['status'] == 1) {
  279. $joinParams = array(
  280. 'meetingId' => $meeting_db['id'], //-- REQUIRED - A unique id for the meeting
  281. 'username' => $this->user_complete_name, //-- REQUIRED - The name that will display for the user in the meeting
  282. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  283. 'createTime' => '', //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  284. 'userID' => '', // -- OPTIONAL - string
  285. 'webVoiceConf' => '' // -- OPTIONAL - string
  286. );
  287. $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
  288. }
  289. $item = array_merge($item, $meeting_db, $meeting_bbb);
  290. $new_meeting_list[] = $item;
  291. }
  292. return $new_meeting_list;
  293. }
  294. /**
  295. * Function disabled
  296. */
  297. function publish_meeting($id) {
  298. //return BigBlueButtonBN::setPublishRecordings($id, 'true', $this->url, $this->salt);
  299. }
  300. /**
  301. * Function disabled
  302. */
  303. function unpublish_meeting($id) {
  304. //return BigBlueButtonBN::setPublishRecordings($id, 'false', $this->url, $this->salt);
  305. }
  306. /**
  307. * Closes a meeting (usually when the user click on the close button from
  308. * the conferences listing.
  309. * @param string The name of the meeting (usually the course code)
  310. * @return void
  311. * @assert (0) === false
  312. */
  313. function end_meeting($id) {
  314. if (empty($id)) { return false; }
  315. $pass = $this->get_user_meeting_password();
  316. $endParams = array(
  317. 'meetingId' => $id, // REQUIRED - We have to know which meeting to end.
  318. 'password' => $pass, // REQUIRED - Must match moderator pass for meeting.
  319. );
  320. $this->api->endMeetingWithXmlResponseArray($endParams);
  321. Database::update($this->table, array('status' => 0, 'closed_at' => api_get_utc_datetime()), array('id = ? ' => $id));
  322. }
  323. /**
  324. * Gets the password for a specific meeting for the current user
  325. * @return string A moderator password if user is teacher, or the course code otherwise
  326. */
  327. function get_user_meeting_password() {
  328. if ($this->is_teacher()) {
  329. return $this->get_mod_meeting_password();
  330. } else {
  331. return api_get_course_id();
  332. }
  333. }
  334. /**
  335. * Generated a moderator password for the meeting
  336. * @return string A password for the moderation of the videoconference
  337. */
  338. function get_mod_meeting_password() {
  339. return api_get_course_id().'mod';
  340. }
  341. /**
  342. * Get users online in the current course room
  343. * @return int The number of users currently connected to the videoconference
  344. * @assert () > -1
  345. */
  346. function get_users_online_in_current_room() {
  347. $course_id = api_get_course_int_id();
  348. $meeting_data = Database::select('*', $this->table, array('where' => array('c_id = ? AND status = 1 ' => $course_id)), 'first');
  349. if (empty($meeting_data)) {
  350. return 0;
  351. }
  352. $pass = $this->get_mod_meeting_password();
  353. $info = $this->get_meeting_info(array('meetingId' => $meeting_data['id'], 'password' => $pass));
  354. if (!empty($info) && isset($info['participantCount'])) {
  355. return $info['participantCount'];
  356. }
  357. return 0;
  358. }
  359. /**
  360. * Deletes a previous recording of a meeting
  361. * @param int intergal ID of the recording
  362. * @return array ?
  363. * @assert () === false
  364. * @todo Also delete links and agenda items created from this recording
  365. */
  366. function delete_record($ids) {
  367. if (empty($ids) or (is_array($ids) && count($ids)==0)) { return false; }
  368. $recordingParams = array(
  369. /*
  370. * NOTE: Set the recordId below to a valid id after you have
  371. * created a recorded meeting, and received a real recordID
  372. * back from your BBB server using the
  373. * getRecordingsWithXmlResponseArray method.
  374. */
  375. // REQUIRED - We have to know which recording:
  376. 'recordId' => $ids,
  377. );
  378. return $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
  379. }
  380. /**
  381. * Creates a link in the links tool from the given videoconference recording
  382. * @param int ID of the item in the plugin_bbb_meeting table
  383. * @param string Hash identifying the recording, as provided by the API
  384. * @return mixed ID of the newly created link, or false on error
  385. * @assert (null, null) === false
  386. * @assert (1, null) === false
  387. * @assert (null, 'abcdefabcdefabcdefabcdef') === false
  388. */
  389. function copy_record_to_link_tool($id, $record_id) {
  390. if (empty($id) or empty($record_id)) {
  391. return false;
  392. }
  393. $records = BigBlueButtonBN::getRecordingsArray($id, $this->url, $this->salt);
  394. if (!empty($records)) {
  395. foreach ($records as $record) {
  396. //error_log($record['recordID']);
  397. if ($record['recordID'] == $record_id) {
  398. if (is_array($record) && isset($record['recordID']) && isset($record['playbacks'])) {
  399. foreach ($record['playbacks'] as $item) {
  400. $link = new Link();
  401. $params['url'] = $item['url'];
  402. $params['title'] = 'bbb 1';
  403. $id = $link->save($params);
  404. return $id;
  405. }
  406. }
  407. }
  408. }
  409. }
  410. return false;
  411. }
  412. /**
  413. * Checks if the videoconference server is running.
  414. * Function currently disabled (always returns 1)
  415. * @return bool True if server is running, false otherwise
  416. * @assert () === false
  417. */
  418. function is_server_running() {
  419. return true;
  420. //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
  421. }
  422. }