bbb.lib.php 58 KB

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