bbb.lib.php 58 KB

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