bbb.lib.php 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748
  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 $enableGlobalConferencePerUser = false;
  28. public $isGlobalConference = false;
  29. public $groupSupport = false;
  30. public $userSupport = false;
  31. public $accessUrl = 1;
  32. public $userId = 0;
  33. public $plugin;
  34. private $courseCode;
  35. private $courseId;
  36. private $sessionId;
  37. private $groupId;
  38. private $maxUsersLimit;
  39. /**
  40. * Constructor (generates a connection to the API and the Chamilo settings
  41. * required for the connection to the video conference server)
  42. * @param string $host
  43. * @param string $salt
  44. * @param bool $isGlobalConference
  45. * @param int $isGlobalPerUser
  46. */
  47. public function __construct(
  48. $host = '',
  49. $salt = '',
  50. $isGlobalConference = false,
  51. $isGlobalPerUser = 0
  52. ) {
  53. $this->courseCode = api_get_course_id();
  54. $this->courseId = api_get_course_int_id();
  55. $this->sessionId = api_get_session_id();
  56. $this->groupId = api_get_group_id();
  57. // Initialize video server settings from global settings
  58. $this->plugin = BBBPlugin::create();
  59. $bbbPluginEnabled = $this->plugin->get('tool_enable');
  60. $bbb_host = !empty($host) ? $host : $this->plugin->get('host');
  61. $bbb_salt = !empty($salt) ? $salt : $this->plugin->get('salt');
  62. $this->table = Database::get_main_table('plugin_bbb_meeting');
  63. $this->enableGlobalConference = $this->plugin->get('enable_global_conference') === 'true' ? true : false;
  64. $this->isGlobalConference = (bool) $isGlobalConference;
  65. $columns = Database::listTableColumns($this->table);
  66. $this->groupSupport = isset($columns['group_id']) ? true : false;
  67. $this->userSupport = isset($columns['user_id']) ? true : false;
  68. $this->accessUrl = api_get_current_access_url_id();
  69. $this->enableGlobalConferencePerUser = false;
  70. if ($this->userSupport && !empty($isGlobalPerUser)) {
  71. $this->enableGlobalConferencePerUser = $this->plugin->get('enable_global_conference_per_user') === 'true' ? true : false;
  72. $this->userId = $isGlobalPerUser;
  73. }
  74. if ($this->groupSupport) {
  75. // Plugin check
  76. $this->groupSupport = $this->plugin->get('enable_conference_in_course_groups') === 'true' ? true : false;
  77. if ($this->groupSupport) {
  78. // Platform check
  79. $bbbSetting = api_get_setting('bbb_enable_conference_in_course_groups');
  80. $bbbSetting = isset($bbbSetting['bbb']) ? $bbbSetting['bbb'] === 'true' : false;
  81. if ($bbbSetting) {
  82. // Course check
  83. $courseInfo = api_get_course_info();
  84. if ($courseInfo) {
  85. $this->groupSupport = api_get_course_setting('bbb_enable_conference_in_groups', $courseInfo['code']) === '1';
  86. }
  87. }
  88. }
  89. }
  90. $this->maxUsersLimit = $this->plugin->get('max_users_limit');
  91. if ($bbbPluginEnabled === 'true') {
  92. $userInfo = api_get_user_info();
  93. if (empty($userInfo) && !empty($isGlobalPerUser)) {
  94. // If we are following a link to a global "per user" conference
  95. // then generate a random guest name to join the conference
  96. // because there is no part of the process where we give a name
  97. $this->userCompleteName = 'Guest'.rand(1000, 9999);
  98. } else {
  99. $this->userCompleteName = $userInfo['complete_name'];
  100. }
  101. $this->salt = $bbb_salt;
  102. $info = parse_url($bbb_host);
  103. $this->url = $bbb_host.'/bigbluebutton/';
  104. if (isset($info['scheme'])) {
  105. $this->protocol = $info['scheme'].'://';
  106. $this->url = str_replace($this->protocol, '', $this->url);
  107. $urlWithProtocol = $bbb_host;
  108. } else {
  109. // We asume it's an http, if user wants to use https host must include the protocol.
  110. $urlWithProtocol = 'http://'.$bbb_host;
  111. }
  112. // Setting BBB api
  113. define('CONFIG_SECURITY_SALT', $this->salt);
  114. define('CONFIG_SERVER_URL_WITH_PROTOCOL', $urlWithProtocol);
  115. define('CONFIG_SERVER_BASE_URL', $this->url);
  116. $this->api = new BigBlueButtonBN();
  117. $this->pluginEnabled = true;
  118. $this->logoutUrl = $this->getListingUrl();
  119. }
  120. }
  121. /**
  122. * Force the course, session and/or group IDs
  123. * @param string $courseCode
  124. * @param int $sessionId
  125. * @param int $groupId
  126. */
  127. public function forceCIdReq($courseCode, $sessionId = 0, $groupId = 0)
  128. {
  129. $this->courseCode = $courseCode;
  130. $this->sessionId = intval($sessionId);
  131. $this->groupId = intval($groupId);
  132. }
  133. /**
  134. * @return bool
  135. */
  136. public function isGlobalConferenceEnabled()
  137. {
  138. return $this->enableGlobalConference;
  139. }
  140. /**
  141. * @return bool
  142. */
  143. public function isGlobalConferencePerUserEnabled()
  144. {
  145. return $this->enableGlobalConferencePerUser;
  146. }
  147. /**
  148. * @return bool
  149. */
  150. public function isGlobalConference()
  151. {
  152. if ($this->isGlobalConferenceEnabled() === false) {
  153. return false;
  154. }
  155. return (bool) $this->isGlobalConference;
  156. }
  157. /**
  158. * @return bool
  159. */
  160. public function hasGroupSupport()
  161. {
  162. return $this->groupSupport;
  163. }
  164. /**
  165. * Checks whether a user is teacher in the current course
  166. * @return bool True if the user can be considered a teacher in this course, false otherwise
  167. */
  168. public function isConferenceManager()
  169. {
  170. if (api_is_coach() || api_is_platform_admin()) {
  171. return true;
  172. }
  173. if ($this->isGlobalConferencePerUserEnabled()) {
  174. $currentUserId = api_get_user_id();
  175. if ($this->userId === $currentUserId) {
  176. return true;
  177. } else {
  178. return false;
  179. }
  180. }
  181. $courseInfo = api_get_course_info();
  182. if (!empty($courseInfo)) {
  183. return api_is_course_admin();
  184. }
  185. return false;
  186. }
  187. /**
  188. * Gets the global limit of users in a video-conference room.
  189. * This value can be overridden by course-specific values
  190. * @return int Maximum number of users set globally
  191. */
  192. public function getMaxUsersLimit()
  193. {
  194. $limit = $this->maxUsersLimit;
  195. if ($limit <= 0) {
  196. $limit = 0;
  197. }
  198. $courseLimit = 0;
  199. $sessionLimit = 0;
  200. // Check the extra fields for this course and session
  201. // Session limit takes priority over course limit
  202. // Course limit takes priority over global limit
  203. if (!empty($this->courseId)) {
  204. $extraField = new ExtraField('course');
  205. $fieldId = $extraField->get_all(
  206. array('variable = ?' => 'plugin_bbb_course_users_limit')
  207. );
  208. $extraValue = new ExtraFieldValue('course');
  209. $value = $extraValue->get_values_by_handler_and_field_id($this->courseId, $fieldId[0]['id']);
  210. if (!empty($value['value'])) {
  211. $courseLimit = (int) $value['value'];
  212. }
  213. }
  214. if (!empty($this->sessionId)) {
  215. $extraField = new ExtraField('session');
  216. $fieldId = $extraField->get_all(
  217. array('variable = ?' => 'plugin_bbb_session_users_limit')
  218. );
  219. $extraValue = new ExtraFieldValue('session');
  220. $value = $extraValue->get_values_by_handler_and_field_id($this->sessionId, $fieldId[0]['id']);
  221. if (!empty($value['value'])) {
  222. $sessionLimit = (int) $value['value'];
  223. }
  224. }
  225. if (!empty($sessionLimit)) {
  226. return $sessionLimit;
  227. } elseif (!empty($courseLimit)) {
  228. return $courseLimit;
  229. }
  230. return (int) $limit;
  231. }
  232. /**
  233. * Sets the global limit of users in a video-conference room.
  234. * @param int Maximum number of users (globally)
  235. */
  236. public function setMaxUsersLimit($max)
  237. {
  238. if ($max < 0) {
  239. $max = 0;
  240. }
  241. $this->maxUsersLimit = intval($max);
  242. }
  243. /**
  244. * See this file in you BBB to set up default values
  245. * @param array $params Array of parameters that will be completed if not containing all expected variables
  246. /var/lib/tomcat6/webapps/bigbluebutton/WEB-INF/classes/bigbluebutton.properties
  247. *
  248. More record information:
  249. http://code.google.com/p/bigbluebutton/wiki/RecordPlaybackSpecification
  250. # Default maximum number of users a meeting can have.
  251. # Doesn't get enforced yet but is the default value when the create
  252. # API doesn't pass a value.
  253. defaultMaxUsers=20
  254. # Default duration of the meeting in minutes.
  255. # Current default is 0 (meeting doesn't end).
  256. defaultMeetingDuration=0
  257. # Remove the meeting from memory when the end API is called.
  258. # This allows 3rd-party apps to recycle the meeting right-away
  259. # instead of waiting for the meeting to expire (see below).
  260. removeMeetingWhenEnded=false
  261. # The number of minutes before the system removes the meeting from memory.
  262. defaultMeetingExpireDuration=1
  263. # The number of minutes the system waits when a meeting is created and when
  264. # a user joins. If after this period, a user hasn't joined, the meeting is
  265. # removed from memory.
  266. defaultMeetingCreateJoinDuration=5
  267. *
  268. * @return mixed
  269. */
  270. public function createMeeting($params)
  271. {
  272. $courseCode = api_get_course_id();
  273. $params['c_id'] = api_get_course_int_id();
  274. $params['session_id'] = api_get_session_id();
  275. if ($this->hasGroupSupport()) {
  276. $params['group_id'] = api_get_group_id();
  277. }
  278. if ($this->isGlobalConferencePerUserEnabled() && !empty($this->userId)) {
  279. $params['user_id'] = (int) $this->userId;
  280. }
  281. $params['attendee_pw'] = isset($params['attendee_pw']) ? $params['attendee_pw'] : $this->getUserMeetingPassword();
  282. $attendeePassword = $params['attendee_pw'];
  283. $params['moderator_pw'] = isset($params['moderator_pw']) ? $params['moderator_pw'] : $this->getModMeetingPassword();
  284. $moderatorPassword = $params['moderator_pw'];
  285. $params['record'] = api_get_course_setting('big_blue_button_record_and_store', $courseCode) == 1 ? true : false;
  286. $max = api_get_course_setting('big_blue_button_max_students_allowed', $courseCode);
  287. $max = isset($max) ? $max : -1;
  288. $params['status'] = 1;
  289. // Generate a pseudo-global-unique-id to avoid clash of conferences on
  290. // the same BBB server with several Chamilo portals
  291. $params['remote_id'] = uniqid(true, true);
  292. // Each simultaneous conference room needs to have a different
  293. // voice_bridge composed of a 5 digits number, so generating a random one
  294. $params['voice_bridge'] = rand(10000, 99999);
  295. if ($this->debug) {
  296. error_log("enter create_meeting ".print_r($params, 1));
  297. }
  298. $params['created_at'] = api_get_utc_datetime();
  299. $params['access_url'] = $this->accessUrl;
  300. // Check interface feature is installed
  301. $interfaceFeature = $this->plugin->get('interface');
  302. if ($interfaceFeature === false) {
  303. if (isset($params['interface'])) {
  304. unset($params['interface']);
  305. }
  306. }
  307. $id = Database::insert($this->table, $params);
  308. if ($id) {
  309. if ($this->debug) {
  310. error_log("create_meeting: $id ");
  311. }
  312. $meetingName = isset($params['meeting_name']) ? $params['meeting_name'] : $this->getCurrentVideoConferenceName();
  313. $welcomeMessage = isset($params['welcome_msg']) ? $params['welcome_msg'] : null;
  314. $record = isset($params['record']) && $params['record'] ? 'true' : 'false';
  315. $duration = isset($params['duration']) ? intval($params['duration']) : 0;
  316. // This setting currently limits the maximum conference duration,
  317. // to avoid lingering sessions on the video-conference server #6261
  318. $duration = 300;
  319. $bbbParams = array(
  320. 'meetingId' => $params['remote_id'], // REQUIRED
  321. 'meetingName' => $meetingName, // REQUIRED
  322. 'attendeePw' => $attendeePassword, // Match this value in getJoinMeetingURL() to join as attendee.
  323. 'moderatorPw' => $moderatorPassword, // Match this value in getJoinMeetingURL() to join as moderator.
  324. 'welcomeMsg' => $welcomeMessage, // ''= use default. Change to customize.
  325. 'dialNumber' => '', // The main number to call into. Optional.
  326. 'voiceBridge' => $params['voice_bridge'], // PIN to join voice. Required.
  327. 'webVoice' => '', // Alphanumeric to join voice. Optional.
  328. 'logoutUrl' => $this->logoutUrl,
  329. 'maxParticipants' => $max, // Optional. -1 = unlimitted. Not supported in BBB. [number]
  330. 'record' => $record, // New. 'true' will tell BBB to record the meeting.
  331. 'duration' => $duration, // Default = 0 which means no set duration in minutes. [number]
  332. //'meta_category' => '', // Use to pass additional info to BBB server. See API docs.
  333. );
  334. if ($this->debug) {
  335. error_log("create_meeting params: ".print_r($bbbParams, 1));
  336. }
  337. $status = false;
  338. $meeting = null;
  339. while ($status === false) {
  340. $result = $this->api->createMeetingWithXmlResponseArray(
  341. $bbbParams
  342. );
  343. if (isset($result) && strval($result['returncode']) == 'SUCCESS') {
  344. if ($this->debug) {
  345. error_log(
  346. "create_meeting result: ".print_r($result, 1)
  347. );
  348. }
  349. $meeting = $this->joinMeeting($meetingName, true);
  350. return $meeting;
  351. }
  352. }
  353. return false;
  354. }
  355. return false;
  356. }
  357. /**
  358. * Save a participant in a meeting room
  359. * @param int $meetingId
  360. * @param int $participantId
  361. * @param int $interface
  362. *
  363. * @return false|int The last inserted ID. Otherwise return false
  364. */
  365. public function saveParticipant($meetingId, $participantId, $interface = 0)
  366. {
  367. $params = [
  368. 'meeting_id' => $meetingId,
  369. 'participant_id' => $participantId,
  370. 'in_at' => api_get_utc_datetime(),
  371. 'out_at' => api_get_utc_datetime()
  372. ];
  373. if ($this->plugin->get('interface') !== false) {
  374. $params['interface'] = $interface;
  375. }
  376. return Database::insert(
  377. 'plugin_bbb_room',
  378. $params
  379. );
  380. }
  381. /**
  382. * @param string $meetingName
  383. *
  384. * @return array
  385. */
  386. public function getMeetingByName($meetingName)
  387. {
  388. if (empty($meetingName)) {
  389. return [];
  390. }
  391. $courseId = api_get_course_int_id();
  392. $sessionId = api_get_session_id();
  393. $conditions = array(
  394. 'where' => array(
  395. 'c_id = ? AND session_id = ? AND meeting_name = ? AND status = 1 AND access_url = ?' =>
  396. array($courseId, $sessionId, $meetingName, $this->accessUrl)
  397. )
  398. );
  399. if ($this->hasGroupSupport()) {
  400. $groupId = api_get_group_id();
  401. $conditions = array(
  402. 'where' => array(
  403. 'c_id = ? AND session_id = ? AND meeting_name = ? AND group_id = ? AND status = 1 AND access_url = ?' =>
  404. array(
  405. $courseId,
  406. $sessionId,
  407. $meetingName,
  408. $groupId,
  409. $this->accessUrl
  410. )
  411. )
  412. );
  413. }
  414. $meetingData = Database::select(
  415. '*',
  416. $this->table,
  417. $conditions,
  418. 'first'
  419. );
  420. if ($this->debug) {
  421. error_log('meeting_exists '.print_r($meetingData, 1));
  422. }
  423. return $meetingData;
  424. }
  425. /**
  426. * Tells whether the given meeting exists and is running
  427. * (using course code as name)
  428. * @param string $meetingName Meeting name (usually the course code)
  429. *
  430. * @return bool True if meeting exists, false otherwise
  431. * @assert ('') === false
  432. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  433. */
  434. public function meetingExists($meetingName)
  435. {
  436. $meetingData = $this->getMeetingByName($meetingName);
  437. return !empty($meetingData);
  438. }
  439. /**
  440. * Returns a meeting "join" URL
  441. * @param string The name of the meeting (usually the course code)
  442. * @return mixed The URL to join the meeting, or false on error
  443. * @todo implement moderator pass
  444. * @assert ('') === false
  445. * @assert ('abcdefghijklmnopqrstuvwxyzabcdefghijklmno') === false
  446. */
  447. public function joinMeeting($meetingName)
  448. {
  449. if ($this->debug) {
  450. error_log("joinMeeting: $meetingName");
  451. }
  452. if (empty($meetingName)) {
  453. return false;
  454. }
  455. $manager = $this->isConferenceManager();
  456. if ($manager) {
  457. $pass = $this->getModMeetingPassword();
  458. } else {
  459. $pass = $this->getUserMeetingPassword();
  460. }
  461. $meetingData = Database::select(
  462. '*',
  463. $this->table,
  464. array(
  465. 'where' => array(
  466. 'meeting_name = ? AND status = 1 AND access_url = ?' => array(
  467. $meetingName,
  468. $this->accessUrl
  469. )
  470. )
  471. ),
  472. 'first'
  473. );
  474. if (empty($meetingData) || !is_array($meetingData)) {
  475. if ($this->debug) {
  476. error_log("meeting does not exist: $meetingName");
  477. }
  478. return false;
  479. }
  480. $params = array(
  481. 'meetingId' => $meetingData['remote_id'],
  482. // -- REQUIRED - The unique id for the meeting
  483. 'password' => $this->getModMeetingPassword()
  484. // -- REQUIRED - The moderator password for the meeting
  485. );
  486. $meetingInfoExists = false;
  487. $meetingIsRunningInfo = $this->getMeetingInfo($params);
  488. if ($this->debug) {
  489. error_log('Searching meeting with params:');
  490. error_log(print_r($params, 1));
  491. error_log('Result:');
  492. error_log(print_r($meetingIsRunningInfo, 1));
  493. }
  494. if ($meetingIsRunningInfo === false) {
  495. // checking with the remote_id didn't work, so just in case and
  496. // to provide backwards support, check with the id
  497. $params = array(
  498. 'meetingId' => $meetingData['id'],
  499. // -- REQUIRED - The unique id for the meeting
  500. 'password' => $this->getModMeetingPassword()
  501. // -- REQUIRED - The moderator password for the meeting
  502. );
  503. $meetingIsRunningInfo = $this->getMeetingInfo($params);
  504. if ($this->debug) {
  505. error_log('Searching meetingId with params:');
  506. error_log(print_r($params, 1));
  507. error_log('Result:');
  508. error_log(print_r($meetingIsRunningInfo, 1));
  509. }
  510. }
  511. if (strval($meetingIsRunningInfo['returncode']) == 'SUCCESS' &&
  512. isset($meetingIsRunningInfo['meetingName']) &&
  513. !empty($meetingIsRunningInfo['meetingName'])
  514. ) {
  515. $meetingInfoExists = true;
  516. }
  517. if ($this->debug) {
  518. error_log(
  519. "meeting is running: ".intval($meetingInfoExists)
  520. );
  521. }
  522. $url = false;
  523. if ($meetingInfoExists) {
  524. $joinParams = [
  525. 'meetingId' => $meetingData['remote_id'], // -- REQUIRED - A unique id for the meeting
  526. 'username' => $this->userCompleteName, //-- REQUIRED - The name that will display for the user in the meeting
  527. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  528. //'createTime' => api_get_utc_datetime(), //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  529. 'userID' => api_get_user_id(), //-- OPTIONAL - string
  530. 'webVoiceConf' => '', // -- OPTIONAL - string
  531. 'interface' => $this->checkInterface($meetingData),
  532. ];
  533. $url = $this->api->getJoinMeetingURL($joinParams);
  534. $url = $this->protocol.$url;
  535. }
  536. if ($this->debug) {
  537. error_log("return url :".$url);
  538. }
  539. return $url;
  540. }
  541. /**
  542. * Get information about the given meeting
  543. * @param array ...?
  544. * @return mixed Array of information on success, false on error
  545. * @assert (array()) === false
  546. */
  547. public function getMeetingInfo($params)
  548. {
  549. try {
  550. $result = $this->api->getMeetingInfoWithXmlResponseArray($params);
  551. if ($result == null) {
  552. if ($this->debug) {
  553. error_log("Failed to get any response. Maybe we can't contact the BBB server.");
  554. }
  555. } else {
  556. return $result;
  557. }
  558. } catch (Exception $e) {
  559. if ($this->debug) {
  560. error_log('Caught exception: ', $e->getMessage(), "\n");
  561. }
  562. }
  563. return false;
  564. }
  565. /**
  566. * @param int $courseId
  567. * @param int $sessionId
  568. * @param int $status
  569. *
  570. * @return array
  571. */
  572. public function getAllMeetingsInCourse($courseId, $sessionId, $status)
  573. {
  574. $conditions = array(
  575. 'where' => array(
  576. 'status = ? AND c_id = ? AND session_id = ? ' => array(
  577. $status,
  578. $courseId,
  579. $sessionId,
  580. ),
  581. ),
  582. );
  583. $meetingList = Database::select(
  584. '*',
  585. $this->table,
  586. $conditions
  587. );
  588. return $meetingList;
  589. }
  590. /**
  591. * Gets all the course meetings saved in the plugin_bbb_meeting table
  592. * @param int $courseId
  593. * @param int $sessionId
  594. * @param int $groupId
  595. * @param bool $isAdminReport Optional. Set to true then the report is for admins
  596. * @param array $dateRange Optional
  597. * @return array Array of current open meeting rooms
  598. */
  599. public function getMeetings(
  600. $courseId = 0,
  601. $sessionId = 0,
  602. $groupId = 0,
  603. $isAdminReport = false,
  604. $dateRange = []
  605. ) {
  606. $em = Database::getManager();
  607. $manager = $this->isConferenceManager();
  608. $conditions = [];
  609. if ($courseId || $sessionId || $groupId) {
  610. $conditions = array(
  611. 'where' => array(
  612. 'c_id = ? AND session_id = ? ' => array($courseId, $sessionId),
  613. ),
  614. );
  615. if ($this->hasGroupSupport()) {
  616. $conditions = array(
  617. 'where' => array(
  618. 'c_id = ? AND session_id = ? AND group_id = ? ' => array(
  619. $courseId,
  620. $sessionId,
  621. $groupId
  622. )
  623. )
  624. );
  625. }
  626. if ($this->isGlobalConferencePerUserEnabled()) {
  627. $conditions = array(
  628. 'where' => array(
  629. 'c_id = ? AND session_id = ? AND user_id = ?' => array(
  630. $courseId,
  631. $sessionId,
  632. $this->userId
  633. ),
  634. ),
  635. );
  636. }
  637. }
  638. if (!empty($dateRange)) {
  639. $dateStart = date_create($dateRange['search_meeting_start']);
  640. $dateStart = date_format($dateStart, 'Y-m-d H:i:s');
  641. $dateEnd = date_create($dateRange['search_meeting_end']);
  642. $dateEnd = $dateEnd->add(new DateInterval('P1D'));
  643. $dateEnd = date_format($dateEnd, 'Y-m-d H:i:s');
  644. $conditions = array(
  645. 'where' => array(
  646. 'created_at BETWEEN ? AND ? ' => array($dateStart, $dateEnd),
  647. ),
  648. );
  649. }
  650. $conditions['order'] = 'created_at ASC';
  651. $meetingList = Database::select(
  652. '*',
  653. $this->table,
  654. $conditions
  655. );
  656. $isGlobal = $this->isGlobalConference();
  657. $newMeetingList = array();
  658. foreach ($meetingList as $meetingDB) {
  659. $item = array();
  660. $courseId = $meetingDB['c_id'];
  661. $courseInfo = api_get_course_info_by_id($courseId);
  662. $courseCode = '';
  663. if (!empty($courseInfo)) {
  664. $courseCode = $courseInfo['code'];
  665. }
  666. if ($manager) {
  667. $pass = $this->getUserMeetingPassword($courseCode);
  668. } else {
  669. $pass = $this->getModMeetingPassword($courseCode);
  670. }
  671. $meetingBBB = $this->getMeetingInfo(
  672. [
  673. 'meetingId' => $meetingDB['remote_id'],
  674. 'password' => $pass
  675. ]
  676. );
  677. if ($meetingBBB === false) {
  678. //checking with the remote_id didn't work, so just in case and
  679. // to provide backwards support, check with the id
  680. $params = array(
  681. 'meetingId' => $meetingDB['id'],
  682. // -- REQUIRED - The unique id for the meeting
  683. 'password' => $pass
  684. // -- REQUIRED - The moderator password for the meeting
  685. );
  686. $meetingBBB = $this->getMeetingInfo($params);
  687. }
  688. if ($meetingDB['visibility'] == 0 && $this->isConferenceManager() === false) {
  689. continue;
  690. }
  691. $meetingBBB['end_url'] = $this->endUrl($meetingDB);
  692. if (isset($meetingBBB['returncode']) && (string) $meetingBBB['returncode'] == 'FAILED') {
  693. if ($meetingDB['status'] == 1 && $this->isConferenceManager()) {
  694. $this->endMeeting($meetingDB['id'], $courseCode);
  695. }
  696. } else {
  697. $meetingBBB['add_to_calendar_url'] = $this->addToCalendarUrl($meetingDB);
  698. }
  699. if ($meetingDB['record'] == 1) {
  700. // backwards compatibility (when there was no remote ID)
  701. $mId = $meetingDB['remote_id'];
  702. if (empty($mId)) {
  703. $mId = $meetingDB['id'];
  704. }
  705. if (empty($mId)) {
  706. // if the id is still empty (should *never* occur as 'id' is
  707. // the table's primary key), skip this conference
  708. continue;
  709. }
  710. $record = [];
  711. //if (empty($meetingDB['video_url'])) {
  712. $recordingParams = ['meetingId' => $mId];
  713. $records = $this->api->getRecordingsWithXmlResponseArray($recordingParams);
  714. if (!empty($records)) {
  715. if (!isset($records['messageKey']) || $records['messageKey'] != 'noRecordings') {
  716. $record = end($records);
  717. if (!is_array($record) || !isset($record['recordId'])) {
  718. continue;
  719. }
  720. if (!empty($record['playbackFormatUrl'])) {
  721. $this->updateMeetingVideoUrl($meetingDB['id'], $record['playbackFormatUrl']);
  722. }
  723. if (!$this->isConferenceManager()) {
  724. $record = [];
  725. }
  726. }
  727. }
  728. //}
  729. /* else {
  730. $record['playbackFormatUrl'] = $meetingDB['video_url'];
  731. }*/
  732. $recordLink = isset($record['playbackFormatUrl']) && !empty($record['playbackFormatUrl'])
  733. ? Display::url(
  734. $this->plugin->get_lang('ViewRecord'),
  735. $record['playbackFormatUrl'],
  736. ['target' => '_blank']
  737. )
  738. : $this->plugin->get_lang('NoRecording');
  739. if ($isAdminReport) {
  740. $this->forceCIdReq(
  741. $courseInfo['code'],
  742. $meetingDB['session_id'],
  743. $meetingDB['group_id']
  744. );
  745. }
  746. $actionLinks = $this->getActionLinks(
  747. $meetingDB,
  748. $record,
  749. $isGlobal,
  750. $isAdminReport
  751. );
  752. $item['show_links'] = $recordLink;
  753. } else {
  754. $actionLinks = $this->getActionLinks(
  755. $meetingDB,
  756. [],
  757. $isGlobal,
  758. $isAdminReport
  759. );
  760. $item['show_links'] = $this->plugin->get_lang('NoRecording');
  761. }
  762. $item['action_links'] = implode(PHP_EOL, $actionLinks);
  763. $item['created_at'] = api_convert_and_format_date($meetingDB['created_at']);
  764. // created_at
  765. $meetingDB['created_at'] = $item['created_at']; //avoid overwrite in array_merge() below
  766. $item['publish_url'] = $this->publishUrl($meetingDB);
  767. $item['unpublish_url'] = $this->unPublishUrl($meetingBBB);
  768. if ($meetingDB['status'] == 1) {
  769. $joinParams = [
  770. 'meetingId' => $meetingDB['remote_id'], //-- REQUIRED - A unique id for the meeting
  771. 'username' => $this->userCompleteName, //-- REQUIRED - The name that will display for the user in the meeting
  772. 'password' => $pass, //-- REQUIRED - The attendee or moderator password, depending on what's passed here
  773. 'createTime' => '', //-- OPTIONAL - string. Leave blank ('') unless you set this correctly.
  774. 'userID' => '', // -- OPTIONAL - string
  775. 'webVoiceConf' => '', // -- OPTIONAL - string
  776. 'interface' => $this->checkInterface($meetingDB),
  777. ];
  778. $item['go_url'] = $this->protocol.$this->api->getJoinMeetingURL($joinParams);
  779. }
  780. $item = array_merge($item, $meetingDB, $meetingBBB);
  781. $item['course'] = $em->find('ChamiloCoreBundle:Course', $item['c_id']);
  782. $item['session'] = $em->find('ChamiloCoreBundle:Session', $item['session_id']);
  783. $newMeetingList[] = $item;
  784. }
  785. return $newMeetingList;
  786. }
  787. /**
  788. * @param $meetingInfo
  789. *
  790. * @return int
  791. */
  792. public function checkInterface($meetingInfo)
  793. {
  794. $interface = BBBPlugin::LAUNCH_TYPE_DEFAULT;
  795. $type = $this->plugin->get('launch_type');
  796. switch ($type) {
  797. case BBBPlugin::LAUNCH_TYPE_DEFAULT:
  798. $interface = $this->plugin->get('interface');
  799. break;
  800. case BBBPlugin::LAUNCH_TYPE_SET_BY_TEACHER:
  801. if (isset($meetingInfo['interface'])) {
  802. $interface = $meetingInfo['interface'];
  803. }
  804. break;
  805. case BBBPlugin::LAUNCH_TYPE_SET_BY_STUDENT:
  806. if (isset($meetingInfo['id'])) {
  807. $roomInfo = $this->getMeetingParticipantInfo($meetingInfo['id'], api_get_user_id());
  808. if (!empty($roomInfo) && isset($roomInfo['interface'])) {
  809. $interface = $roomInfo['interface'];
  810. } else {
  811. if (isset($_REQUEST['interface'])) {
  812. $interface = isset($_REQUEST['interface']) ? (int) $_REQUEST['interface'] : 0;
  813. }
  814. }
  815. }
  816. break;
  817. }
  818. return $interface;
  819. }
  820. /**
  821. * Function disabled
  822. */
  823. public function publishMeeting($id)
  824. {
  825. //return BigBlueButtonBN::setPublishRecordings($id, 'true', $this->url, $this->salt);
  826. if (empty($id)) {
  827. return false;
  828. }
  829. $id = intval($id);
  830. Database::update($this->table, array('visibility' => 1), array('id = ? ' => $id));
  831. return true;
  832. }
  833. /**
  834. * Function disabled
  835. */
  836. public function unpublishMeeting($id)
  837. {
  838. //return BigBlueButtonBN::setPublishRecordings($id, 'false', $this->url, $this->salt);
  839. if (empty($id)) {
  840. return false;
  841. }
  842. $id = intval($id);
  843. Database::update($this->table, array('visibility' => 0), array('id = ?' => $id));
  844. return true;
  845. }
  846. /**
  847. * Closes a meeting (usually when the user click on the close button from
  848. * the conferences listing.
  849. * @param string The internal ID of the meeting (id field for this meeting)
  850. * @param string $courseCode
  851. *
  852. * @return void
  853. * @assert (0) === false
  854. */
  855. public function endMeeting($id, $courseCode = null)
  856. {
  857. if (empty($id)) {
  858. return false;
  859. }
  860. $meetingData = Database::select(
  861. '*',
  862. $this->table,
  863. array('where' => array('id = ?' => array($id))),
  864. 'first'
  865. );
  866. $manager = $this->isConferenceManager();
  867. if ($manager) {
  868. $pass = $this->getUserMeetingPassword($courseCode);
  869. } else {
  870. $pass = $this->getModMeetingPassword($courseCode);
  871. }
  872. $endParams = array(
  873. 'meetingId' => $meetingData['remote_id'], // REQUIRED - We have to know which meeting to end.
  874. 'password' => $pass, // REQUIRED - Must match moderator pass for meeting.
  875. );
  876. $this->api->endMeetingWithXmlResponseArray($endParams);
  877. Database::update(
  878. $this->table,
  879. array('status' => 0, 'closed_at' => api_get_utc_datetime()),
  880. array('id = ? ' => $id)
  881. );
  882. }
  883. /**
  884. * Gets the password for a specific meeting for the current user
  885. * @param string $courseCode
  886. * @return string A moderator password if user is teacher, or the course code otherwise
  887. *
  888. */
  889. public function getUserMeetingPassword($courseCode = null)
  890. {
  891. if ($this->isGlobalConferencePerUserEnabled()) {
  892. return 'url_'.$this->userId.'_'.api_get_current_access_url_id();
  893. }
  894. if ($this->isGlobalConference()) {
  895. return 'url_'.api_get_current_access_url_id();
  896. }
  897. $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
  898. return $courseCode;
  899. }
  900. /**
  901. * Generated a moderator password for the meeting.
  902. *
  903. * @param string $courseCode
  904. *
  905. * @return string A password for the moderation of the videoconference
  906. */
  907. public function getModMeetingPassword($courseCode = null)
  908. {
  909. if ($this->isGlobalConferencePerUserEnabled()) {
  910. return 'url_'.$this->userId.'_'.api_get_current_access_url_id().'_mod';
  911. }
  912. if ($this->isGlobalConference()) {
  913. return 'url_'.api_get_current_access_url_id().'_mod';
  914. }
  915. $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
  916. return $courseCode.'mod';
  917. }
  918. /**
  919. * Get users online in the current course room.
  920. *
  921. * @return int The number of users currently connected to the videoconference
  922. * @assert () > -1
  923. */
  924. public function getUsersOnlineInCurrentRoom()
  925. {
  926. $courseId = api_get_course_int_id();
  927. $sessionId = api_get_session_id();
  928. $conditions = array(
  929. 'where' => array(
  930. 'c_id = ? AND session_id = ? AND status = 1 AND access_url = ?' => array(
  931. $courseId,
  932. $sessionId,
  933. $this->accessUrl
  934. ),
  935. ),
  936. );
  937. if ($this->hasGroupSupport()) {
  938. $groupId = api_get_group_id();
  939. $conditions = array(
  940. 'where' => array(
  941. 'c_id = ? AND session_id = ? AND group_id = ? AND status = 1 AND access_url = ?' => array(
  942. $courseId,
  943. $sessionId,
  944. $groupId,
  945. $this->accessUrl
  946. ),
  947. ),
  948. );
  949. }
  950. if ($this->isGlobalConferencePerUserEnabled()) {
  951. $conditions = array(
  952. 'where' => array(
  953. 'user_id = ? AND status = 1 AND access_url = ?' => array(
  954. $this->userId,
  955. $this->accessUrl
  956. ),
  957. ),
  958. );
  959. }
  960. $meetingData = Database::select(
  961. '*',
  962. $this->table,
  963. $conditions,
  964. 'first'
  965. );
  966. if (empty($meetingData)) {
  967. return 0;
  968. }
  969. $pass = $this->getModMeetingPassword();
  970. $info = $this->getMeetingInfo(array('meetingId' => $meetingData['remote_id'], 'password' => $pass));
  971. if ($info === false) {
  972. //checking with the remote_id didn't work, so just in case and
  973. // to provide backwards support, check with the id
  974. $params = array(
  975. 'meetingId' => $meetingData['id'],
  976. // -- REQUIRED - The unique id for the meeting
  977. 'password' => $pass
  978. // -- REQUIRED - The moderator password for the meeting
  979. );
  980. $info = $this->getMeetingInfo($params);
  981. }
  982. if (!empty($info) && isset($info['participantCount'])) {
  983. return $info['participantCount'];
  984. }
  985. return 0;
  986. }
  987. /**
  988. * @param int $id
  989. * @param int $recordId
  990. *
  991. * @return bool
  992. */
  993. public function regenerateRecording($id, $recordId)
  994. {
  995. if (empty($id)) {
  996. return false;
  997. }
  998. $meetingData = Database::select(
  999. '*',
  1000. $this->table,
  1001. array('where' => array('id = ?' => array($id))),
  1002. 'first'
  1003. );
  1004. // Check if there are recordings for this meeting
  1005. $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
  1006. if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] == 'noRecordings') {
  1007. return false;
  1008. } else {
  1009. if (!empty($recordings['records'])) {
  1010. $recordExists = false;
  1011. foreach ($recordings['records'] as $record) {
  1012. if ($recordId == $record['recordId']) {
  1013. $recordExists = true;
  1014. break;
  1015. }
  1016. }
  1017. if ($recordExists) {
  1018. return $this->api->generateRecording(['recordId' => $recordId]);
  1019. }
  1020. }
  1021. }
  1022. return false;
  1023. }
  1024. /**
  1025. * Deletes a recording of a meeting
  1026. *
  1027. * @param int $id ID of the recording
  1028. *
  1029. * @return bool
  1030. *
  1031. * @assert () === false
  1032. * @todo Also delete links and agenda items created from this recording
  1033. */
  1034. public function deleteRecording($id)
  1035. {
  1036. if (empty($id)) {
  1037. return false;
  1038. }
  1039. $meetingData = Database::select(
  1040. '*',
  1041. $this->table,
  1042. array('where' => array('id = ?' => array($id))),
  1043. 'first'
  1044. );
  1045. $delete = false;
  1046. // Check if there are recordings for this meeting
  1047. $recordings = $this->api->getRecordings(['meetingId' => $meetingData['remote_id']]);
  1048. if (!empty($recordings) && isset($recordings['messageKey']) && $recordings['messageKey'] == 'noRecordings') {
  1049. $delete = true;
  1050. } else {
  1051. $recordsToDelete = [];
  1052. if (!empty($recordings['records'])) {
  1053. foreach ($recordings['records'] as $record) {
  1054. $recordsToDelete[] = $record['recordId'];
  1055. }
  1056. $recordingParams = ['recordId' => implode(',', $recordsToDelete)];
  1057. $result = $this->api->deleteRecordingsWithXmlResponseArray($recordingParams);
  1058. if (!empty($result) && isset($result['deleted']) && $result['deleted'] === 'true') {
  1059. $delete = true;
  1060. }
  1061. }
  1062. }
  1063. if ($delete) {
  1064. Database::delete(
  1065. 'plugin_bbb_room',
  1066. array('meeting_id = ?' => array($id))
  1067. );
  1068. Database::delete(
  1069. $this->table,
  1070. array('id = ?' => array($id))
  1071. );
  1072. }
  1073. return $delete;
  1074. }
  1075. /**
  1076. * Creates a link in the links tool from the given videoconference recording
  1077. * @param int $id ID of the item in the plugin_bbb_meeting table
  1078. * @param string Hash identifying the recording, as provided by the API
  1079. * @return mixed ID of the newly created link, or false on error
  1080. * @assert (null, null) === false
  1081. * @assert (1, null) === false
  1082. * @assert (null, 'abcdefabcdefabcdefabcdef') === false
  1083. */
  1084. public function copyRecordingToLinkTool($id)
  1085. {
  1086. if (empty($id)) {
  1087. return false;
  1088. }
  1089. //$records = BigBlueButtonBN::getRecordingsUrl($id);
  1090. $meetingData = Database::select(
  1091. '*',
  1092. $this->table,
  1093. array('where' => array('id = ?' => array($id))),
  1094. 'first'
  1095. );
  1096. $records = $this->api->getRecordingsWithXmlResponseArray(
  1097. array('meetingId' => $meetingData['remote_id'])
  1098. );
  1099. if (!empty($records)) {
  1100. if (isset($records['message']) && !empty($records['message'])) {
  1101. if ($records['messageKey'] == 'noRecordings') {
  1102. $recordArray[] = $this->plugin->get_lang('NoRecording');
  1103. } else {
  1104. //$recordArray[] = $records['message'];
  1105. }
  1106. return false;
  1107. } else {
  1108. $record = $records[0];
  1109. if (is_array($record) && isset($record['recordId'])) {
  1110. $url = $record['playbackFormatUrl'];
  1111. $link = new Link();
  1112. $params['url'] = $url;
  1113. $params['title'] = $meetingData['meeting_name'];
  1114. $id = $link->save($params);
  1115. return $id;
  1116. }
  1117. }
  1118. }
  1119. return false;
  1120. }
  1121. /**
  1122. * Checks if the video conference server is running.
  1123. * Function currently disabled (always returns 1)
  1124. * @return bool True if server is running, false otherwise
  1125. * @assert () === false
  1126. */
  1127. public function isServerRunning()
  1128. {
  1129. return true;
  1130. //return BigBlueButtonBN::isServerRunning($this->protocol.$this->url);
  1131. }
  1132. /**
  1133. * Get active session in the all platform
  1134. */
  1135. public function getActiveSessionsCount()
  1136. {
  1137. $meetingList = Database::select(
  1138. 'count(id) as count',
  1139. $this->table,
  1140. array('where' => array('status = ? AND access_url = ?' => array(1, $this->accessUrl))),
  1141. 'first'
  1142. );
  1143. return $meetingList['count'];
  1144. }
  1145. /**
  1146. * @param string $url
  1147. */
  1148. public function redirectToBBB($url)
  1149. {
  1150. if (file_exists(__DIR__.'/../config.vm.php')) {
  1151. // Using VM
  1152. echo Display::url($this->plugin->get_lang('ClickToContinue'), $url);
  1153. exit;
  1154. } else {
  1155. // Classic
  1156. header("Location: $url");
  1157. exit;
  1158. }
  1159. }
  1160. /**
  1161. * @return string
  1162. */
  1163. public function getUrlParams()
  1164. {
  1165. if (empty($this->courseCode)) {
  1166. if ($this->isGlobalConferencePerUserEnabled()) {
  1167. return 'global=1&user_id='.$this->userId;
  1168. }
  1169. if ($this->isGlobalConference()) {
  1170. return 'global=1';
  1171. }
  1172. return '';
  1173. }
  1174. return http_build_query([
  1175. 'cidReq' => $this->courseCode,
  1176. 'id_session' => $this->sessionId,
  1177. 'gidReq' => $this->groupId
  1178. ]);
  1179. }
  1180. /**
  1181. * @return string
  1182. */
  1183. public function getCurrentVideoConferenceName()
  1184. {
  1185. if ($this->isGlobalConferencePerUserEnabled()) {
  1186. return 'url_'.$this->userId.'_'.api_get_current_access_url_id();
  1187. }
  1188. if ($this->isGlobalConference()) {
  1189. return 'url_'.api_get_current_access_url_id();
  1190. }
  1191. if ($this->hasGroupSupport()) {
  1192. return api_get_course_id().'-'.api_get_session_id().'-'.api_get_group_id();
  1193. }
  1194. return api_get_course_id().'-'.api_get_session_id();
  1195. }
  1196. /**
  1197. * @return string
  1198. */
  1199. public function getConferenceUrl()
  1200. {
  1201. return api_get_path(WEB_PLUGIN_PATH).'bbb/start.php?launch=1&'.$this->getUrlParams();
  1202. }
  1203. /**
  1204. * @return string
  1205. */
  1206. public function getListingUrl()
  1207. {
  1208. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams();
  1209. }
  1210. /**
  1211. * @param array $meeting
  1212. * @return string
  1213. */
  1214. public function endUrl($meeting)
  1215. {
  1216. if (!isset($meeting['id'])) {
  1217. return '';
  1218. }
  1219. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=end&id='.$meeting['id'];
  1220. }
  1221. /**
  1222. * @param array $meeting
  1223. * @param array $record
  1224. * @return string
  1225. */
  1226. public function addToCalendarUrl($meeting, $record = [])
  1227. {
  1228. $url = isset($record['playbackFormatUrl']) ? $record['playbackFormatUrl'] : '';
  1229. 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;
  1230. }
  1231. /**
  1232. * @param array $meeting
  1233. * @return string
  1234. */
  1235. public function publishUrl($meeting)
  1236. {
  1237. if (!isset($meeting['id'])) {
  1238. return '';
  1239. }
  1240. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=publish&id='.$meeting['id'];
  1241. }
  1242. /**
  1243. * @param array $meeting
  1244. * @return string
  1245. */
  1246. public function unPublishUrl($meeting)
  1247. {
  1248. if (!isset($meeting['id'])) {
  1249. return null;
  1250. }
  1251. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=unpublish&id='.$meeting['id'];
  1252. }
  1253. /**
  1254. * @param array $meeting
  1255. * @return string
  1256. */
  1257. public function deleteRecordUrl($meeting)
  1258. {
  1259. if (!isset($meeting['id'])) {
  1260. return '';
  1261. }
  1262. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().'&action=delete_record&id='.$meeting['id'];
  1263. }
  1264. /**
  1265. * @param array $meeting
  1266. * @param array $recordInfo
  1267. *
  1268. * @return string
  1269. */
  1270. public function regenerateRecordUrl($meeting, $recordInfo)
  1271. {
  1272. if ($this->plugin->get('allow_regenerate_recording') !== 'true') {
  1273. return '';
  1274. }
  1275. if (!isset($meeting['id'])) {
  1276. return '';
  1277. }
  1278. if (empty($recordInfo) || (!empty($recordInfo['recordId']) && !isset($recordInfo['recordId']))) {
  1279. return '';
  1280. }
  1281. return api_get_path(WEB_PLUGIN_PATH).'bbb/listing.php?'.$this->getUrlParams().
  1282. '&action=regenerate_record&id='.$meeting['id'].'&record_id='.$recordInfo['recordId'];
  1283. }
  1284. /**
  1285. * @param array $meeting
  1286. * @return string
  1287. */
  1288. public function copyToRecordToLinkTool($meeting)
  1289. {
  1290. if (!isset($meeting['id'])) {
  1291. return '';
  1292. }
  1293. return api_get_path(WEB_PLUGIN_PATH).
  1294. 'bbb/listing.php?'.$this->getUrlParams().'&action=copy_record_to_link_tool&id='.$meeting['id'];
  1295. }
  1296. /**
  1297. * Get the meeting info from DB by its name
  1298. * @param string $name
  1299. *
  1300. * @return array
  1301. */
  1302. public function findMeetingByName($name)
  1303. {
  1304. $meetingData = Database::select(
  1305. '*',
  1306. 'plugin_bbb_meeting',
  1307. array('where' => array('meeting_name = ? AND status = 1 ' => $name)),
  1308. 'first'
  1309. );
  1310. return $meetingData;
  1311. }
  1312. /**
  1313. * Get the meeting info from DB by its name
  1314. * @param int $id
  1315. *
  1316. * @return array
  1317. */
  1318. public function getMeeting($id)
  1319. {
  1320. $meetingData = Database::select(
  1321. '*',
  1322. 'plugin_bbb_meeting',
  1323. array('where' => array('id = ?' => $id)),
  1324. 'first'
  1325. );
  1326. return $meetingData;
  1327. }
  1328. /**
  1329. * @param int $meetingId
  1330. * @return array
  1331. */
  1332. public function findConnectedMeetingParticipants($meetingId)
  1333. {
  1334. $meetingData = Database::select(
  1335. '*',
  1336. 'plugin_bbb_room',
  1337. array('where' => array('meeting_id = ? AND in_at IS NOT NULL' => $meetingId))
  1338. );
  1339. $participantIds = [];
  1340. $return = [];
  1341. foreach ($meetingData as $participantInfo) {
  1342. if (in_array($participantInfo['participant_id'], $participantIds)) {
  1343. continue;
  1344. }
  1345. $participantIds[] = $participantInfo['participant_id'];
  1346. $return[] = [
  1347. 'id' => $participantInfo['id'],
  1348. 'meeting_id' => $participantInfo['meeting_id'],
  1349. 'participant' => api_get_user_entity($participantInfo['participant_id']),
  1350. 'in_at' => $participantInfo['in_at'],
  1351. 'out_at' => $participantInfo['out_at']
  1352. ];
  1353. }
  1354. return $return;
  1355. }
  1356. /**
  1357. * @param int $meetingId
  1358. * @param int $userId
  1359. *
  1360. * @return array
  1361. */
  1362. public function getMeetingParticipantInfo($meetingId, $userId)
  1363. {
  1364. $meetingData = Database::select(
  1365. '*',
  1366. 'plugin_bbb_room',
  1367. array('where' => array('meeting_id = ? AND participant_id = ?' => [$meetingId, $userId])),
  1368. 'first'
  1369. );
  1370. if ($meetingData) {
  1371. return $meetingData;
  1372. }
  1373. return [];
  1374. }
  1375. /**
  1376. * @param array $meetingInfo
  1377. * @param array $recordInfo
  1378. * @param bool $isGlobal
  1379. * @param bool $isAdminReport
  1380. * @return array
  1381. */
  1382. private function getActionLinks(
  1383. $meetingInfo,
  1384. $recordInfo,
  1385. $isGlobal = false,
  1386. $isAdminReport = false
  1387. ) {
  1388. $isVisible = $meetingInfo['visibility'] != 0;
  1389. $linkVisibility = $isVisible
  1390. ? Display::url(
  1391. Display::return_icon('visible.png', get_lang('MakeInvisible')),
  1392. $this->unPublishUrl($meetingInfo)
  1393. )
  1394. : Display::url(
  1395. Display::return_icon('invisible.png', get_lang('MakeVisible')),
  1396. $this->publishUrl($meetingInfo)
  1397. );
  1398. $links = [];
  1399. if (empty($recordInfo)) {
  1400. if (!$isAdminReport) {
  1401. $links[] = Display::url(
  1402. Display::return_icon('delete.png', get_lang('Delete')),
  1403. $this->deleteRecordUrl($meetingInfo)
  1404. );
  1405. $links[] = $linkVisibility;
  1406. return $links;
  1407. } else {
  1408. $links[] = Display::url(
  1409. Display::return_icon('course_home.png', get_lang('GoToCourse')),
  1410. $this->getListingUrl()
  1411. );
  1412. return $links;
  1413. }
  1414. }
  1415. if (!$isGlobal) {
  1416. $links[] = Display::url(
  1417. Display::return_icon('link.gif', get_lang('UrlMeetingToShare')),
  1418. $this->copyToRecordToLinkTool($meetingInfo)
  1419. );
  1420. $links[] = Display::url(
  1421. Display::return_icon('agenda.png', get_lang('AddToCalendar')),
  1422. $this->addToCalendarUrl($meetingInfo, $recordInfo)
  1423. );
  1424. }
  1425. $hide = $this->plugin->get('disable_download_conference_link') === 'true' ? true : false;
  1426. if ($hide == false) {
  1427. if ($meetingInfo['has_video_m4v']) {
  1428. $links[] = Display::url(
  1429. Display::return_icon('save.png', get_lang('DownloadFile')),
  1430. $recordInfo['playbackFormatUrl'].'/capture.m4v',
  1431. ['target' => '_blank']
  1432. );
  1433. } else {
  1434. $links[] = Display::url(
  1435. Display::return_icon('save.png', get_lang('DownloadFile')),
  1436. '#',
  1437. [
  1438. 'id' => "btn-check-meeting-video-{$meetingInfo['id']}",
  1439. 'class' => 'check-meeting-video',
  1440. 'data-id' => $meetingInfo['id']
  1441. ]
  1442. );
  1443. }
  1444. }
  1445. if (!empty($recordInfo)) {
  1446. $links[] = Display::url(
  1447. Display::return_icon('reload.png', get_lang('RegenerateRecord')),
  1448. $this->regenerateRecordUrl($meetingInfo, $recordInfo)
  1449. );
  1450. }
  1451. if (!$isAdminReport) {
  1452. $links[] = Display::url(
  1453. Display::return_icon('delete.png', get_lang('Delete')),
  1454. $this->deleteRecordUrl($meetingInfo)
  1455. );
  1456. $links[] = $linkVisibility;
  1457. } else {
  1458. $links[] = Display::url(
  1459. Display::return_icon('course_home.png', get_lang('GoToCourse')),
  1460. $this->getListingUrl()
  1461. );
  1462. }
  1463. return $links;
  1464. }
  1465. /**
  1466. * @param int $meetingId
  1467. * @param string $videoUrl
  1468. * @return bool|int
  1469. */
  1470. public function updateMeetingVideoUrl($meetingId, $videoUrl)
  1471. {
  1472. return Database::update(
  1473. 'plugin_bbb_meeting',
  1474. ['video_url' => $videoUrl],
  1475. ['id = ?' => intval($meetingId)]
  1476. );
  1477. }
  1478. /**
  1479. * Check if the meeting has a capture.m4v video file. If exists then the has_video_m4v field is updated
  1480. * @param int $meetingId
  1481. * @return bool
  1482. */
  1483. public function checkDirectMeetingVideoUrl($meetingId)
  1484. {
  1485. $meetingInfo = Database::select(
  1486. '*',
  1487. 'plugin_bbb_meeting',
  1488. [
  1489. 'where' => ['id = ?' => intval($meetingId)]
  1490. ],
  1491. 'first'
  1492. );
  1493. if (!isset($meetingInfo['video_url'])) {
  1494. return false;
  1495. }
  1496. $hasCapture = SocialManager::verifyUrl($meetingInfo['video_url'].'/capture.m4v');
  1497. if ($hasCapture) {
  1498. return Database::update(
  1499. 'plugin_bbb_meeting',
  1500. ['has_video_m4v' => true],
  1501. ['id = ?' => intval($meetingId)]
  1502. );
  1503. }
  1504. return $hasCapture;
  1505. }
  1506. /**
  1507. * @param array $userInfo
  1508. * @return bool
  1509. */
  1510. public static function showGlobalConferenceLink($userInfo)
  1511. {
  1512. if (empty($userInfo)) {
  1513. return false;
  1514. }
  1515. $setting = api_get_plugin_setting('bbb', 'enable_global_conference');
  1516. $settingLink = api_get_plugin_setting('bbb', 'enable_global_conference_link');
  1517. if ($setting === 'true' && $settingLink === 'true') {
  1518. //$content = Display::url(get_lang('LaunchVideoConferenceRoom'), $url);
  1519. $allowedRoles = api_get_plugin_setting(
  1520. 'bbb',
  1521. 'global_conference_allow_roles'
  1522. );
  1523. if (api_is_platform_admin()) {
  1524. $userInfo['status'] = PLATFORM_ADMIN;
  1525. }
  1526. $showGlobalLink = true;
  1527. if (!empty($allowedRoles)) {
  1528. if (!in_array($userInfo['status'], $allowedRoles)) {
  1529. $showGlobalLink = false;
  1530. }
  1531. }
  1532. return $showGlobalLink;
  1533. }
  1534. }
  1535. }