bbb.lib.php 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  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. //namespace Chamilo\Plugin\BBB;
  12. /**
  13. * Class bbb
  14. * @package Chamilo\Plugin\BBB
  15. */
  16. class bbb
  17. {
  18. public $url;
  19. public $salt;
  20. public $api;
  21. public $userCompleteName = '';
  22. public $protocol = 'http://';
  23. public $debug = false;
  24. public $logoutUrl = '';
  25. public $pluginEnabled = false;
  26. public $enableGlobalConference = false;
  27. public $isGlobalConference = false;
  28. public $groupSupport = false;
  29. private $courseCode;
  30. private $sessionId;
  31. private $groupId;
  32. private $plugin;
  33. /**
  34. * Constructor (generates a connection to the API and the Chamilo settings
  35. * required for the connection to the video conference server)
  36. * @param string $host
  37. * @param string $salt
  38. * @param bool $isGlobalConference
  39. */
  40. public function __construct($host = '', $salt = '', $isGlobalConference = false)
  41. {
  42. $this->courseCode = api_get_course_id();
  43. $this->sessionId = api_get_session_id();
  44. $this->groupId = api_get_group_id();
  45. // Initialize video server settings from global settings
  46. $plugin = BBBPlugin::create();
  47. $this->plugin = $plugin;
  48. $bbbPlugin = $plugin->get('tool_enable');
  49. $bbb_host = !empty($host) ? $host : $plugin->get('host');
  50. $bbb_salt = !empty($salt) ? $salt : $plugin->get('salt');
  51. $this->logoutUrl = $this->getListingUrl();
  52. $this->table = Database::get_main_table('plugin_bbb_meeting');
  53. $this->enableGlobalConference = $plugin->get('enable_global_conference');
  54. $this->isGlobalConference = (bool) $isGlobalConference;
  55. $columns = Database::listTableColumns($this->table);
  56. $this->groupSupport = isset($columns['group_id']) ? true : false;
  57. if ($this->groupSupport) {
  58. // Plugin check
  59. $this->groupSupport = (bool) $plugin->get('enable_conference_in_course_groups');
  60. if ($this->groupSupport) {
  61. // Platform check
  62. $bbbSetting = api_get_setting('bbb_enable_conference_in_course_groups');
  63. $bbbSetting = isset($bbbSetting['bbb']) ? $bbbSetting['bbb'] === 'true' : false;
  64. if ($bbbSetting) {
  65. // Course check
  66. $courseInfo = api_get_course_info();
  67. if ($courseInfo) {
  68. $this->groupSupport = api_get_course_setting('bbb_enable_conference_in_groups') === '1';
  69. }
  70. }
  71. }
  72. }
  73. if ($bbbPlugin == true) {
  74. $userInfo = api_get_user_info();
  75. $this->userCompleteName = $userInfo['complete_name'];
  76. $this->salt = $bbb_salt;
  77. $info = parse_url($bbb_host);
  78. $this->url = $bbb_host.'/bigbluebutton/';
  79. if (isset($info['scheme'])) {
  80. $this->protocol = $info['scheme'].'://';
  81. $this->url = str_replace($this->protocol, '', $this->url);
  82. }
  83. // Setting BBB api
  84. define('CONFIG_SECURITY_SALT', $this->salt);
  85. define('CONFIG_SERVER_BASE_URL', $this->url);
  86. $this->api = new BigBlueButtonBN();
  87. $this->pluginEnabled = true;
  88. }
  89. }
  90. /**
  91. * Set forced the course, session or group IDs
  92. * @param string $courseCode
  93. * @param int $sessionId
  94. * @param int $groupId
  95. */
  96. public function forceCIdReq($courseCode, $sessionId = 0, $groupId = 0)
  97. {
  98. $this->courseCode = $courseCode;
  99. $this->sessionId = intval($sessionId);
  100. $this->groupId = intval($groupId);
  101. }
  102. /**
  103. * @return bool
  104. */
  105. public function isGlobalConferenceEnabled()
  106. {
  107. return (bool) $this->enableGlobalConference;
  108. }
  109. /**
  110. * @return bool
  111. */
  112. public function isGlobalConference()
  113. {
  114. if ($this->isGlobalConferenceEnabled() === false) {
  115. return false;
  116. }
  117. return (bool) $this->isGlobalConference;
  118. }
  119. /**
  120. * @return bool
  121. */
  122. public function hasGroupSupport()
  123. {
  124. return $this->groupSupport;
  125. }
  126. /**
  127. * Checks whether a user is teacher in the current course
  128. * @return bool True if the user can be considered a teacher in this course, false otherwise
  129. */
  130. public function isConferenceManager()
  131. {
  132. return api_is_course_admin() || api_is_coach() || api_is_platform_admin();
  133. }
  134. /**
  135. * See this file in you BBB to set up default values
  136. * @param array $params Array of parameters that will be completed if not containing all expected variables
  137. /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
  138. *
  139. More record information:
  140. http://code.google.com/p/bigbluebutton/wiki/RecordPlaybackSpecification
  141. # Default maximum number of users a meeting can have.
  142. # Doesn't get enforced yet but is the default value when the create
  143. # API doesn't pass a value.
  144. defaultMaxUsers=20
  145. # Default duration of the meeting in minutes.
  146. # Current default is 0 (meeting doesn't end).
  147. defaultMeetingDuration=0
  148. # Remove the meeting from memory when the end API is called.
  149. # This allows 3rd-party apps to recycle the meeting right-away
  150. # instead of waiting for the meeting to expire (see below).
  151. removeMeetingWhenEnded=false
  152. # The number of minutes before the system removes the meeting from memory.
  153. defaultMeetingExpireDuration=1
  154. # The number of minutes the system waits when a meeting is created and when
  155. # a user joins. If after this period, a user hasn't joined, the meeting is
  156. # removed from memory.
  157. defaultMeetingCreateJoinDuration=5
  158. *
  159. * @return mixed
  160. */
  161. public function createMeeting($params)
  162. {
  163. $courseCode = api_get_course_id();
  164. $params['c_id'] = api_get_course_int_id();
  165. $params['session_id'] = api_get_session_id();
  166. if ($this->hasGroupSupport()) {
  167. $params['group_id'] = api_get_group_id();
  168. }
  169. $params['attendee_pw'] = isset($params['moderator_pw']) ? $params['moderator_pw'] : $courseCode;
  170. $attendeePassword = $params['attendee_pw'];
  171. $params['moderator_pw'] = isset($params['moderator_pw']) ? $params['moderator_pw'] : $this->getModMeetingPassword();
  172. $moderatorPassword = $params['moderator_pw'];
  173. $params['record'] = api_get_course_setting('big_blue_button_record_and_store', $courseCode) == 1 ? true : false;
  174. $max = api_get_course_setting('big_blue_button_max_students_allowed', $courseCode);
  175. $max = isset($max) ? $max : -1;
  176. $params['status'] = 1;
  177. // Generate a pseudo-global-unique-id to avoid clash of conferences on
  178. // the same BBB server with several Chamilo portals
  179. $params['remote_id'] = uniqid(true, true);
  180. // Each simultaneous conference room needs to have a different
  181. // voice_bridge composed of a 5 digits number, so generating a random one
  182. $params['voice_bridge'] = rand(10000, 99999);
  183. if ($this->debug) {
  184. error_log("enter create_meeting ".print_r($params, 1));
  185. }
  186. $params['created_at'] = api_get_utc_datetime();
  187. $id = Database::insert($this->table, $params);
  188. if ($id) {
  189. if ($this->debug) {
  190. error_log("create_meeting: $id ");
  191. }
  192. $meetingName = isset($params['meeting_name']) ? $params['meeting_name'] : $this->getCurrentVideoConferenceName();
  193. $welcomeMessage = isset($params['welcome_msg']) ? $params['welcome_msg'] : null;
  194. $record = isset($params['record']) && $params['record'] ? 'true' : 'false';
  195. $duration = isset($params['duration']) ? intval($params['duration']) : 0;
  196. // This setting currently limits the maximum conference duration,
  197. // to avoid lingering sessions on the video-conference server #6261
  198. $duration = 300;
  199. $bbbParams = array(
  200. 'meetingId' => $params['remote_id'], // REQUIRED
  201. 'meetingName' => $meetingName, // REQUIRED
  202. 'attendeePw' => $attendeePassword, // Match this value in getJoinMeetingURL() to join as attendee.
  203. 'moderatorPw' => $moderatorPassword, // Match this value in getJoinMeetingURL() to join as moderator.
  204. 'welcomeMsg' => $welcomeMessage, // ''= use default. Change to customize.
  205. 'dialNumber' => '', // The main number to call into. Optional.
  206. 'voiceBridge' => $params['voice_bridge'], // PIN to join voice. Required.
  207. 'webVoice' => '', // Alphanumeric to join voice. Optional.
  208. 'logoutUrl' => $this->logoutUrl,
  209. 'maxParticipants' => $max, // Optional. -1 = unlimitted. Not supported in BBB. [number]
  210. 'record' => $record, // New. 'true' will tell BBB to record the meeting.
  211. 'duration' => $duration, // Default = 0 which means no set duration in minutes. [number]
  212. //'meta_category' => '', // Use to pass additional info to BBB server. See API docs.
  213. );
  214. if ($this->debug) {
  215. error_log("create_meeting params: ".print_r($bbbParams,1));
  216. }
  217. $status = false;
  218. $meeting = null;
  219. while ($status === false) {
  220. $result = $this->api->createMeetingWithXmlResponseArray(
  221. $bbbParams
  222. );
  223. if (isset($result) && strval($result['returncode']) == 'SUCCESS') {
  224. if ($this->debug) {
  225. error_log(
  226. "create_meeting result: " . print_r($result, 1)
  227. );
  228. }
  229. $meeting = $this->joinMeeting($meetingName, true);
  230. return $meeting;
  231. }
  232. }
  233. return $this->logoutUrl;
  234. }
  235. }
  236. /**
  237. * Save a participant in a meeting room
  238. * @param int $meetingId
  239. * @param int $participantId
  240. * @return false|int The last inserted ID. Otherwise return false
  241. */
  242. public function saveParticipant($meetingId, $participantId)
  243. {
  244. return Database::insert(
  245. 'plugin_bbb_room',
  246. [
  247. 'meeting_id' => $meetingId,
  248. 'participant_id' => $participantId,
  249. 'in_at' => api_get_utc_datetime(),
  250. 'out_at' => api_get_utc_datetime()
  251. ]
  252. );
  253. }
  254. /**
  255. * Tells whether the given meeting exists and is running
  256. * (using course code as name)
  257. * @param string $meetingName Meeting name (usually the course code)
  258. *
  259. * @return bool True if meeting exists, false otherwise
  260. * @assert ('') === false
  261. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  262. */
  263. public function meetingExists($meetingName)
  264. {
  265. if (empty($meetingName)) {
  266. return false;
  267. }
  268. $courseId = api_get_course_int_id();
  269. $sessionId = api_get_session_id();
  270. $conditions = array(
  271. 'where' => array(
  272. 'c_id = ? AND session_id = ? AND meeting_name = ? AND status = 1 ' =>
  273. array($courseId, $sessionId, $meetingName)
  274. )
  275. );
  276. if ($this->hasGroupSupport()) {
  277. $groupId = api_get_group_id();
  278. $conditions = array(
  279. 'where' => array(
  280. 'c_id = ? AND session_id = ? AND meeting_name = ? AND group_id = ? AND status = 1 ' =>
  281. array($courseId, $sessionId, $meetingName, $groupId)
  282. )
  283. );
  284. }
  285. $meetingData = Database::select(
  286. '*',
  287. $this->table,
  288. $conditions,
  289. 'first'
  290. );
  291. if ($this->debug) {
  292. error_log("meeting_exists ".print_r($meetingData, 1));
  293. }
  294. if (empty($meetingData)) {
  295. return false;
  296. } else {
  297. return true;
  298. }
  299. }
  300. /**
  301. * Returns a meeting "join" URL
  302. * @param string The name of the meeting (usually the course code)
  303. * @return mixed The URL to join the meeting, or false on error
  304. * @todo implement moderator pass
  305. * @assert ('') === false
  306. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  307. */
  308. public function joinMeeting($meetingName, $loop = false)
  309. {
  310. if (empty($meetingName)) {
  311. return false;
  312. }
  313. $pass = $this->getUserMeetingPassword();
  314. $meetingData = Database::select(
  315. '*',
  316. $this->table,
  317. array('where' => array('meeting_name = ? AND status = 1 ' => $meetingName)),
  318. 'first'
  319. );
  320. if (empty($meetingData) || !is_array($meetingData)) {
  321. if ($this->debug) {
  322. error_log("meeting does not exist: $meetingName");
  323. }
  324. return false;
  325. }
  326. $params = array(
  327. 'meetingId' => $meetingData['remote_id'],
  328. // -- REQUIRED - The unique id for the meeting
  329. 'password' => $this->getModMeetingPassword()
  330. // -- REQUIRED - The moderator password for the meeting
  331. );
  332. $status = false;
  333. $meetingInfoExists = false;
  334. while ($status === false) {
  335. $meetingIsRunningInfo = $this->getMeetingInfo($params);
  336. if ($meetingIsRunningInfo === false) {
  337. //checking with the remote_id didn't work, so just in case and
  338. // to provide backwards support, check with the id
  339. $params = array(
  340. 'meetingId' => $meetingData['id'],
  341. // -- REQUIRED - The unique id for the meeting
  342. 'password' => $this->getModMeetingPassword()
  343. // -- REQUIRED - The moderator password for the meeting
  344. );
  345. $meetingIsRunningInfo = $this->getMeetingInfo($params);
  346. }
  347. if ($this->debug) {
  348. error_log(print_r($meetingIsRunningInfo, 1));
  349. }
  350. if (strval($meetingIsRunningInfo['returncode']) == 'SUCCESS' &&
  351. isset($meetingIsRunningInfo['meetingName']) &&
  352. !empty($meetingIsRunningInfo['meetingName'])
  353. //strval($meetingIsRunningInfo['running']) == 'true'
  354. ) {
  355. $meetingInfoExists = true;
  356. }
  357. if ($this->debug) {
  358. error_log(
  359. "meeting is running: " . intval($meetingInfoExists)
  360. );
  361. }
  362. if ($meetingInfoExists) {
  363. $status = true;
  364. }
  365. if ($loop) {
  366. continue;
  367. } else {
  368. break;
  369. }
  370. }
  371. if ($meetingInfoExists) {
  372. $joinParams = array(
  373. 'meetingId' => $meetingData['remote_id'], // -- REQUIRED - A unique id for the meeting
  374. 'username' => $this->userCompleteName, //-- REQUIRED - The name that will display for the user in the meeting
  375. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  376. //'createTime' => api_get_utc_datetime(), //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  377. 'userID' => api_get_user_id(), //-- OPTIONAL - string
  378. 'webVoiceConf' => '' // -- OPTIONAL - string
  379. );
  380. $url = $this->api->getJoinMeetingURL($joinParams);
  381. $url = $this->protocol.$url;
  382. } else {
  383. $url = $this->logoutUrl;
  384. }
  385. if ($this->debug) {
  386. error_log("return url :" . $url);
  387. }
  388. return $url;
  389. }
  390. /**
  391. * Get information about the given meeting
  392. * @param array ...?
  393. * @return mixed Array of information on success, false on error
  394. * @assert (array()) === false
  395. */
  396. public function getMeetingInfo($params)
  397. {
  398. try {
  399. $result = $this->api->getMeetingInfoWithXmlResponseArray($params);
  400. if ($result == null) {
  401. if ($this->debug) {
  402. error_log("Failed to get any response. Maybe we can't contact the BBB server.");
  403. }
  404. } else {
  405. return $result;
  406. }
  407. } catch (Exception $e) {
  408. if ($this->debug) {
  409. error_log('Caught exception: ', $e->getMessage(), "\n");
  410. }
  411. }
  412. return false;
  413. }
  414. /**
  415. * Gets all the course meetings saved in the plugin_bbb_meeting table
  416. * @return array Array of current open meeting rooms
  417. */
  418. public function getMeetings($courseId = 0, $sessionId = 0, $groupId = 0, $isAdminReport = false, $date = '')
  419. {
  420. $em = Database::getManager();
  421. $pass = $this->getUserMeetingPassword();
  422. $conditions = [];
  423. if ($courseId || $sessionId || $groupId) {
  424. $conditions = array(
  425. 'where' => array(
  426. 'c_id = ? AND session_id = ? ' => array($courseId, $sessionId),
  427. ),
  428. );
  429. if ($this->hasGroupSupport()) {
  430. $conditions = array(
  431. 'where' => array(
  432. 'c_id = ? AND session_id = ? AND group_id = ? ' => array($courseId, $sessionId, $groupId)
  433. )
  434. );
  435. }
  436. }
  437. if (!empty($date)) {
  438. $date = date_create($date);
  439. $dateStart = date_format($date, 'Y-m-d H:i:s');
  440. $date = $date->add(new DateInterval('P1D'));
  441. $dateEnd = date_format($date, 'Y-m-d H:i:s');
  442. $conditions = array(
  443. 'where' => array(
  444. 'created_at BETWEEN ? AND ? ' => array($dateStart, $dateEnd),
  445. ),
  446. );
  447. }
  448. $meetingList = Database::select(
  449. '*',
  450. $this->table,
  451. $conditions
  452. );
  453. $isGlobal = $this->isGlobalConference();
  454. $newMeetingList = array();
  455. $item = array();
  456. foreach ($meetingList as $meetingDB) {
  457. $meetingBBB = $this->getMeetingInfo(['meetingId' => $meetingDB['remote_id'], 'password' => $pass]);
  458. if ($meetingBBB === false) {
  459. //checking with the remote_id didn't work, so just in case and
  460. // to provide backwards support, check with the id
  461. $params = array(
  462. 'meetingId' => $meetingDB['id'],
  463. // -- REQUIRED - The unique id for the meeting
  464. 'password' => $pass
  465. // -- REQUIRED - The moderator password for the meeting
  466. );
  467. $meetingBBB = $this->getMeetingInfo($params);
  468. }
  469. if ($meetingDB['visibility'] == 0 && $this->isConferenceManager() === false) {
  470. continue;
  471. }
  472. $meetingBBB['end_url'] = $this->endUrl($meetingDB);
  473. if ((string)$meetingBBB['returncode'] == 'FAILED') {
  474. if ($meetingDB['status'] == 1 && $this->isConferenceManager()) {
  475. $this->endMeeting($meetingDB['id']);
  476. }
  477. } else {
  478. $meetingBBB['add_to_calendar_url'] = $this->addToCalendarUrl($meetingDB);
  479. }
  480. if ($meetingDB['record'] == 1) {
  481. // backwards compatibility (when there was no remote ID)
  482. $mId = $meetingDB['remote_id'];
  483. if (empty($mId)) {
  484. $mId = $meetingDB['id'];
  485. }
  486. if (empty($mId)) {
  487. // if the id is still empty (should *never* occur as 'id' is
  488. // the table's primary key), skip this conference
  489. continue;
  490. }
  491. $record = [];
  492. if (empty($meetingDB['video_url'])) {
  493. $recordingParams = ['meetingId' => $mId];
  494. $records = $this->api->getRecordingsWithXmlResponseArray($recordingParams);
  495. if (!empty($records)) {
  496. if (!isset($records['messageKey']) || $records['messageKey'] != 'noRecordings') {
  497. $record = end($records);
  498. if (!is_array($record) || !isset($record['recordId'])) {
  499. continue;
  500. }
  501. $this->updateMeetingVideoUrl($meetingDB['id'], $record['playbackFormatUrl']);
  502. if (!$this->isConferenceManager()) {
  503. $record = [];
  504. }
  505. }
  506. }
  507. } else {
  508. $record['playbackFormatUrl'] = $meetingDB['video_url'];
  509. }
  510. $recordLink = isset($record['playbackFormatUrl']) && !empty($record['playbackFormatUrl'])
  511. ? Display::url(
  512. $this->plugin->get_lang('ViewRecord'),
  513. $record['playbackFormatUrl'],
  514. ['target' => '_blank']
  515. )
  516. : get_lang('NoRecording');
  517. if ($isAdminReport) {
  518. $courseInfo = api_get_course_info_by_id($meetingDB['c_id']);
  519. $this->forceCIdReq($courseInfo['code'], $meetingDB['session_id'], $meetingDB['group_id']);
  520. }
  521. $actionLinks = $this->getActionLinks($meetingDB, $record, $isGlobal, $isAdminReport);
  522. $item['show_links'] = $recordLink;
  523. } else {
  524. $actionLinks = $this->getActionLinks($meetingDB, [], $isGlobal, $isAdminReport);
  525. $item['show_links'] = get_lang('NoRecording');
  526. }
  527. $item['action_links'] = implode(PHP_EOL, $actionLinks);
  528. $item['created_at'] = api_convert_and_format_date($meetingDB['created_at']);
  529. //created_at
  530. $meetingDB['created_at'] = $item['created_at']; //avoid overwrite in array_merge() below
  531. $item['publish_url'] = $this->publishUrl($meetingDB);
  532. $item['unpublish_url'] = $this->unPublishUrl($meetingBBB);
  533. if ($meetingDB['status'] == 1) {
  534. $joinParams = array(
  535. 'meetingId' => $meetingDB['remote_id'], //-- REQUIRED - A unique id for the meeting
  536. 'username' => $this->userCompleteName, //-- REQUIRED - The name that will display for the user in the meeting
  537. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  538. 'createTime' => '', //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  539. 'userID' => '', // -- OPTIONAL - string
  540. 'webVoiceConf' => '' // -- OPTIONAL - string
  541. );
  542. $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
  543. }
  544. $item = array_merge($item, $meetingDB, $meetingBBB);
  545. $item['course'] = $em->find('ChamiloCoreBundle:Course', $item['c_id']);
  546. $item['session'] = $em->find('ChamiloCoreBundle:Session', $item['session_id']);
  547. $newMeetingList[] = $item;
  548. }
  549. return $newMeetingList;
  550. }
  551. /**
  552. * Function disabled
  553. */
  554. public function publishMeeting($id)
  555. {
  556. //return BigBlueButtonBN::setPublishRecordings($id, 'true', $this->url, $this->salt);
  557. if (empty($id)) {
  558. return false;
  559. }
  560. $id = intval($id);
  561. Database::update($this->table, array('visibility' => 1), array('id = ? ' => $id));
  562. return true;
  563. }
  564. /**
  565. * Function disabled
  566. */
  567. public function unpublishMeeting($id)
  568. {
  569. //return BigBlueButtonBN::setPublishRecordings($id, 'false', $this->url, $this->salt);
  570. if (empty($id)) {
  571. return false;
  572. }
  573. $id = intval($id);
  574. Database::update($this->table, array('visibility' => 0), array('id = ?' => $id));
  575. return true;
  576. }
  577. /**
  578. * Closes a meeting (usually when the user click on the close button from
  579. * the conferences listing.
  580. * @param string The internal ID of the meeting (id field for this meeting)
  581. * @return void
  582. * @assert (0) === false
  583. */
  584. public function endMeeting($id)
  585. {
  586. if (empty($id)) {
  587. return false;
  588. }
  589. $meetingData = Database::select('*', $this->table, array('where' => array('id = ?' => array($id))), 'first');
  590. $pass = $this->getUserMeetingPassword();
  591. $endParams = array(
  592. 'meetingId' => $meetingData['remote_id'], // REQUIRED - We have to know which meeting to end.
  593. 'password' => $pass, // REQUIRED - Must match moderator pass for meeting.
  594. );
  595. $this->api->endMeetingWithXmlResponseArray($endParams);
  596. Database::update(
  597. $this->table,
  598. array('status' => 0, 'closed_at' => api_get_utc_datetime()),
  599. array('id = ? ' => $id)
  600. );
  601. }
  602. /**
  603. * Gets the password for a specific meeting for the current user
  604. * @return string A moderator password if user is teacher, or the course code otherwise
  605. */
  606. public function getUserMeetingPassword()
  607. {
  608. if ($this->isConferenceManager()) {
  609. return $this->getModMeetingPassword();
  610. } else {
  611. if ($this->isGlobalConference()) {
  612. return 'url_'.api_get_current_access_url_id();
  613. }
  614. return api_get_course_id();
  615. }
  616. }
  617. /**
  618. * Generated a moderator password for the meeting
  619. * @return string A password for the moderation of the videoconference
  620. */
  621. public function getModMeetingPassword()
  622. {
  623. if ($this->isGlobalConference()) {
  624. return 'url_'.api_get_current_access_url_id().'_mod';
  625. }
  626. return api_get_course_id().'mod';
  627. }
  628. /**
  629. * Get users online in the current course room
  630. * @return int The number of users currently connected to the videoconference
  631. * @assert () > -1
  632. */
  633. public function getUsersOnlineInCurrentRoom()
  634. {
  635. $courseId = api_get_course_int_id();
  636. $sessionId = api_get_session_id();
  637. $conditions = array(
  638. 'where' => array(
  639. 'c_id = ? AND session_id = ? AND status = 1 ' => array(
  640. $courseId,
  641. $sessionId,
  642. ),
  643. ),
  644. );
  645. if ($this->hasGroupSupport()) {
  646. $groupId = api_get_group_id();
  647. $conditions = array(
  648. 'where' => array(
  649. 'c_id = ? AND session_id = ? AND group_id = ? AND status = 1 ' => array(
  650. $courseId,
  651. $sessionId,
  652. $groupId
  653. ),
  654. ),
  655. );
  656. }
  657. $meetingData = Database::select(
  658. '*',
  659. $this->table,
  660. $conditions,
  661. 'first'
  662. );
  663. if (empty($meetingData)) {
  664. return 0;
  665. }
  666. $pass = $this->getModMeetingPassword();
  667. $info = $this->getMeetingInfo(array('meetingId' => $meetingData['remote_id'], 'password' => $pass));
  668. if ($info === false) {
  669. //checking with the remote_id didn't work, so just in case and
  670. // to provide backwards support, check with the id
  671. $params = array(
  672. 'meetingId' => $meetingData['id'],
  673. // -- REQUIRED - The unique id for the meeting
  674. 'password' => $pass
  675. // -- REQUIRED - The moderator password for the meeting
  676. );
  677. $info = $this->getMeetingInfo($params);
  678. }
  679. if (!empty($info) && isset($info['participantCount'])) {
  680. return $info['participantCount'];
  681. }
  682. return 0;
  683. }
  684. /**
  685. * Deletes a previous recording of a meeting
  686. * @param int integral ID of the recording
  687. * @return array ?
  688. * @assert () === false
  689. * @todo Also delete links and agenda items created from this recording
  690. */
  691. public function deleteRecord($id)
  692. {
  693. if (empty($id)) {
  694. return false;
  695. }
  696. $meetingData = Database::select(
  697. '*',
  698. $this->table,
  699. array('where' => array('id = ?' => array($id))),
  700. 'first'
  701. );
  702. $recordingParams = array(
  703. /*
  704. * NOTE: Set the recordId below to a valid id after you have
  705. * created a recorded meeting, and received a real recordID
  706. * back from your BBB server using the
  707. * getRecordingsWithXmlResponseArray method.
  708. */
  709. // REQUIRED - We have to know which recording:
  710. 'recordId' => $meetingData['remote_id'],
  711. );
  712. $result = $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
  713. if (!empty($result) && isset($result['deleted']) && $result['deleted'] == 'true') {
  714. Database::delete(
  715. 'plugin_bbb_room',
  716. array('meeting_id = ?' => array($id))
  717. );
  718. Database::delete(
  719. $this->table,
  720. array('id = ?' => array($id))
  721. );
  722. }
  723. return $result;
  724. }
  725. /**
  726. * Creates a link in the links tool from the given videoconference recording
  727. * @param int ID of the item in the plugin_bbb_meeting table
  728. * @param string Hash identifying the recording, as provided by the API
  729. * @return mixed ID of the newly created link, or false on error
  730. * @assert (null, null) === false
  731. * @assert (1, null) === false
  732. * @assert (null, 'abcdefabcdefabcdefabcdef') === false
  733. */
  734. public function copyRecordToLinkTool($id)
  735. {
  736. if (empty($id)) {
  737. return false;
  738. }
  739. //$records = BigBlueButtonBN::getRecordingsUrl($id);
  740. $meetingData = Database::select('*', $this->table, array('where' => array('id = ?' => array($id))), 'first');
  741. $records = $this->api->getRecordingsWithXmlResponseArray(array('meetingId' => $meetingData['remote_id']));
  742. if (!empty($records)) {
  743. if (isset($records['message']) && !empty($records['message'])) {
  744. if ($records['messageKey'] == 'noRecordings') {
  745. $recordArray[] = get_lang('NoRecording');
  746. } else {
  747. //$recordArray[] = $records['message'];
  748. }
  749. return false;
  750. } else {
  751. $record = $records[0];
  752. if (is_array($record) && isset($record['recordId'])) {
  753. $url = $record['playbackFormatUrl'];
  754. $link = new Link();
  755. $params['url'] = $url;
  756. $params['title'] = $meetingData['meeting_name'];
  757. $id = $link->save($params);
  758. return $id;
  759. }
  760. }
  761. }
  762. return false;
  763. }
  764. /**
  765. * Checks if the video conference server is running.
  766. * Function currently disabled (always returns 1)
  767. * @return bool True if server is running, false otherwise
  768. * @assert () === false
  769. */
  770. public function isServerRunning()
  771. {
  772. return true;
  773. //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
  774. }
  775. /**
  776. * Get active session in the all platform
  777. */
  778. public function getActiveSessionsCount()
  779. {
  780. $meetingList = Database::select(
  781. 'count(id) as count',
  782. $this->table,
  783. array('where' => array('status = ?' => array(1))),
  784. 'first'
  785. );
  786. return $meetingList['count'];
  787. }
  788. /**
  789. * @param string $url
  790. */
  791. public function redirectToBBB($url)
  792. {
  793. if (file_exists(__DIR__ . '/../config.vm.php')) {
  794. // Using VM
  795. echo Display::url(get_lang('ClickToContinue'), $url);
  796. exit;
  797. } else {
  798. // Classic
  799. header("Location: $url");
  800. exit;
  801. }
  802. }
  803. /**
  804. * @return string
  805. */
  806. public function getUrlParams()
  807. {
  808. $courseInfo = api_get_course_info();
  809. if (empty($this->courseCode)) {
  810. if ($this->isGlobalConference()) {
  811. return 'global=1';
  812. }
  813. return '';
  814. }
  815. return http_build_query([
  816. 'cidReq' => $this->courseCode,
  817. 'id_session' => $this->sessionId,
  818. 'gidReq' => $this->groupId
  819. ]);
  820. }
  821. /**
  822. * @return string
  823. */
  824. public function getCurrentVideoConferenceName()
  825. {
  826. if ($this->isGlobalConference()) {
  827. return 'url_'.api_get_current_access_url_id();
  828. }
  829. if ($this->hasGroupSupport()) {
  830. return api_get_course_id().'-'.api_get_session_id().'-'.api_get_group_id();
  831. }
  832. return api_get_course_id().'-'.api_get_session_id();
  833. }
  834. /**
  835. * @return string
  836. */
  837. public function getConferenceUrl()
  838. {
  839. return api_get_path(WEB_PLUGIN_PATH).'bbb/start.php?launch=1&'.$this->getUrlParams();
  840. }
  841. /**
  842. * @return string
  843. */
  844. public function getListingUrl()
  845. {
  846. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams();
  847. }
  848. /**
  849. * @param array $meeting
  850. * @return string
  851. */
  852. public function endUrl($meeting)
  853. {
  854. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=end&id='.$meeting['id'];
  855. }
  856. /**
  857. * @param array $meeting
  858. * @param array $record
  859. * @return string
  860. */
  861. public function addToCalendarUrl($meeting, $record = [])
  862. {
  863. $url = isset($record['playbackFormatUrl']) ? $record['playbackFormatUrl'] : '';
  864. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=add_to_calendar&id='.$meeting['id'].'&start='.api_strtotime($meeting['created_at']).'&url='.$url;
  865. }
  866. /**
  867. * @param array $meeting
  868. * @return string
  869. */
  870. public function publishUrl($meeting)
  871. {
  872. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=publish&id='.$meeting['id'];
  873. }
  874. /**
  875. * @param array $meeting
  876. * @return string
  877. */
  878. public function unPublishUrl($meeting)
  879. {
  880. if (!isset($meeting['id'])) {
  881. return null;
  882. }
  883. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=unpublish&id='.$meeting['id'];
  884. }
  885. /**
  886. * @param array $meeting
  887. * @return string
  888. */
  889. public function deleteRecordUrl($meeting)
  890. {
  891. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=delete_record&id='.$meeting['id'];
  892. }
  893. /**
  894. * @param array $meeting
  895. * @return string
  896. */
  897. public function copyToRecordToLinkTool($meeting)
  898. {
  899. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=copy_record_to_link_tool&id='.$meeting['id'];
  900. }
  901. /**
  902. * Get the meeting info from DB by its name
  903. * @param string $name
  904. * @return array
  905. */
  906. public function findMeetingByName($name)
  907. {
  908. $meetingData = Database::select(
  909. '*',
  910. 'plugin_bbb_meeting',
  911. array('where' => array('meeting_name = ? AND status = 1 ' => $name)),
  912. 'first'
  913. );
  914. return $meetingData;
  915. }
  916. /**
  917. * @param int $meetingId
  918. * @return array
  919. */
  920. public function findMeetingParticipants($meetingId)
  921. {
  922. $em = Database::getManager();
  923. $meetingData = Database::select(
  924. '*',
  925. 'plugin_bbb_room',
  926. array('where' => array('meeting_id = ?' => intval($meetingId)))
  927. );
  928. $return = [];
  929. foreach ($meetingData as $participantInfo) {
  930. $return[] = [
  931. 'id' => $participantInfo['id'],
  932. 'meeting_id' => $participantInfo['meeting_id'],
  933. 'participant' => $em->find('ChamiloUserBundle:User', $participantInfo['participant_id']),
  934. 'in_at' => $participantInfo['in_at'],
  935. 'out_at' => $participantInfo['out_at']
  936. ];
  937. }
  938. return $return;
  939. }
  940. /**
  941. * @param array $meetingInfo
  942. * @param array $recordInfo
  943. * @param bool $isGlobal
  944. * @param bool $isAdminReport
  945. * @return array
  946. */
  947. private function getActionLinks($meetingInfo, $recordInfo, $isGlobal = false, $isAdminReport = false)
  948. {
  949. $isVisible = $meetingInfo['visibility'] != 0;
  950. $linkVisibility = $isVisible
  951. ? Display::url(
  952. Display::return_icon('visible.png', get_lang('MakeInvisible')),
  953. $this->unPublishUrl($meetingInfo)
  954. )
  955. : Display::url(
  956. Display::return_icon('invisible.png', get_lang('MakeVisible')),
  957. $this->publishUrl($meetingInfo)
  958. );
  959. $links = [];
  960. if (empty($recordInfo)) {
  961. if (!$isAdminReport) {
  962. $links[] = Display::url(
  963. Display::return_icon('delete.png', get_lang('Delete')),
  964. $this->deleteRecordUrl($meetingInfo)
  965. );
  966. $links[] = $linkVisibility;
  967. return $links;
  968. } else {
  969. $links[] = Display::url(
  970. Display::return_icon('course_home.png', get_lang('GoToCourse')),
  971. $this->getListingUrl()
  972. );
  973. return $links;
  974. }
  975. }
  976. if (!$isGlobal) {
  977. $links[] = Display::url(
  978. Display::return_icon('link.gif', get_lang('CopyToLinkTool')),
  979. $this->copyToRecordToLinkTool($meetingInfo)
  980. );
  981. $links[] = Display::url(
  982. Display::return_icon('agenda.png', get_lang('AddToCalendar')),
  983. $this->addToCalendarUrl($meetingInfo, $recordInfo)
  984. );
  985. }
  986. if ($meetingInfo['has_video_m4v']) {
  987. $links[] = Display::url(
  988. Display::return_icon('save.png', get_lang('DownloadFile')),
  989. $recordInfo['playbackFormatUrl'] . '/capture.m4v',
  990. ['target' => '_blank']
  991. );
  992. } else {
  993. $links[] = Display::url(
  994. Display::return_icon('save.png', get_lang('DownloadFile')),
  995. '#',
  996. [
  997. 'id' => "btn-check-meeting-video-{$meetingInfo['id']}",
  998. 'class' => 'check-meeting-video',
  999. 'data-id' => $meetingInfo['id']
  1000. ]
  1001. );
  1002. }
  1003. if (!$isAdminReport) {
  1004. $links[] = Display::url(
  1005. Display::return_icon('delete.png', get_lang('Delete')),
  1006. $this->deleteRecordUrl($meetingInfo)
  1007. );
  1008. $links[] = $linkVisibility;
  1009. } else {
  1010. $links[] = Display::url(
  1011. Display::return_icon('course_home.png', get_lang('GoToCourse')),
  1012. $this->getListingUrl()
  1013. );
  1014. }
  1015. return $links;
  1016. }
  1017. /**
  1018. * @param int $meetingId
  1019. * @param string $videoUrl
  1020. * @return bool|int
  1021. */
  1022. public function updateMeetingVideoUrl($meetingId, $videoUrl)
  1023. {
  1024. return Database::update(
  1025. 'plugin_bbb_meeting',
  1026. ['video_url' => $videoUrl],
  1027. ['id = ?' => intval($meetingId)]
  1028. );
  1029. }
  1030. /**
  1031. * Check if the meeting has a capture.m4v video file. If exists then the has_video_m4v field is updated
  1032. * @param int $meetingId
  1033. * @return bool
  1034. */
  1035. public function checkDirectMeetingVideoUrl($meetingId)
  1036. {
  1037. $meetingInfo = Database::select(
  1038. '*',
  1039. 'plugin_bbb_meeting',
  1040. [
  1041. 'where' => ['id = ?' => intval($meetingId)]
  1042. ],
  1043. 'first'
  1044. );
  1045. if (!isset($meetingInfo['video_url'])) {
  1046. return false;
  1047. }
  1048. $hasCapture = SocialManager::verifyUrl($meetingInfo['video_url'] . '/capture.m4v');
  1049. if ($hasCapture) {
  1050. return Database::update(
  1051. 'plugin_bbb_meeting',
  1052. ['has_video_m4v' => true],
  1053. ['id = ?' => intval($meetingId)]
  1054. );
  1055. }
  1056. return $hasCapture;
  1057. }
  1058. }