PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/bigbluebuttonlib.php

https://gitlab.com/ElvisAns/tiki
PHP | 474 lines | 315 code | 77 blank | 82 comment | 60 complexity | 3900e074eac727fe4e777ecdf86cc312 MD5 | raw file
  1. <?php
  2. // (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
  3. //
  4. // All Rights Reserved. See copyright.txt for details and a complete list of authors.
  5. // Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
  6. // $Id$
  7. /**
  8. *
  9. */
  10. class BigBlueButtonLib
  11. {
  12. private $version = false;
  13. /**
  14. * @return bool|string
  15. */
  16. private function getVersion()
  17. {
  18. if ($this->version !== false) {
  19. return $this->version;
  20. }
  21. if ($version = $this->performRequest('', [])) {
  22. $values = $this->grabValues($version->documentElement);
  23. $version = $values['version'];
  24. if (false !== $pos = strpos($version, '-')) {
  25. $version = substr($version, 0, $pos);
  26. }
  27. $this->version = $version;
  28. } else {
  29. $this->version = '0.6';
  30. }
  31. return $this->version;
  32. }
  33. /**
  34. * @return array|mixed
  35. */
  36. public function getMeetings()
  37. {
  38. $cachelib = TikiLib::lib('cache');
  39. if (! $meetings = $cachelib->getSerialized('bbb_meetinglist')) {
  40. $meetings = [];
  41. if ($dom = $this->performRequest('getMeetings', ['random' => 1])) {
  42. foreach ($dom->getElementsByTagName('meeting') as $node) {
  43. $meetings[] = $this->grabValues($node);
  44. }
  45. }
  46. $cachelib->cacheItem('bbb_meetinglist', serialize($meetings));
  47. }
  48. return $meetings;
  49. }
  50. /**
  51. * @param $room
  52. * @return array
  53. */
  54. public function getAttendees($room, $username = false)
  55. {
  56. if ($meeting = $this->getMeeting($room)) {
  57. if ($dom = $this->performRequest('getMeetingInfo', ['meetingID' => $room, 'password' => $meeting['moderatorPW']])) {
  58. $attendees = [];
  59. foreach ($dom->getElementsByTagName('attendee') as $node) {
  60. $attendees[] = $this->grabValues($node, $username);
  61. }
  62. return $attendees;
  63. }
  64. }
  65. }
  66. /**
  67. * @param $node
  68. * @return array
  69. */
  70. private function grabValues($node, $username = false)
  71. {
  72. $values = [];
  73. foreach ($node->childNodes as $n) {
  74. if ($n instanceof DOMElement) {
  75. $values[$n->tagName] = $n->textContent;
  76. }
  77. }
  78. if ($username && $values['fullName']) {
  79. preg_match('!\(([^\)]+)\)!', $values['fullName'], $match);
  80. $values['fullName'] = $match[1];
  81. } else {
  82. $values['fullName'] = trim(preg_replace('!\(([^\)]+)\)!', '', $values['fullName']));
  83. }
  84. return $values;
  85. }
  86. /**
  87. * @param $room
  88. * @return bool
  89. */
  90. public function roomExists($room)
  91. {
  92. foreach ($this->getMeetings() as $meeting) {
  93. if ($meeting['meetingID'] == $room) {
  94. return true;
  95. }
  96. }
  97. return false;
  98. }
  99. /**
  100. * @param $room
  101. * @param array $params
  102. */
  103. public function createRoom($room, array $params = [])
  104. {
  105. global $prefs;
  106. $cachelib = TikiLib::lib('cache');
  107. $tikilib = TikiLib::lib('tiki');
  108. $params = array_merge(
  109. ['logout' => $tikilib->tikiUrl(''),],
  110. $params
  111. );
  112. $request = [
  113. 'name' => $room,
  114. 'meetingID' => $room,
  115. 'logoutURL' => $params['logout'],
  116. ];
  117. if (isset($params['welcome'])) {
  118. $request['welcome'] = $params['welcome'];
  119. }
  120. if (isset($params['number'])) {
  121. $request['dialNumber'] = $params['number'];
  122. }
  123. if (isset($params['voicebridge'])) {
  124. $request['voiceBridge'] = $params['voicebridge'];
  125. } else {
  126. $request['voiceBridge'] = '7' . mt_rand(0, 9999);
  127. }
  128. if (isset($params['logout'])) {
  129. $request['logoutURL'] = $tikilib->tikiUrl($params['logout']);
  130. }
  131. if (isset($params['recording']) && $params['recording'] > 0 && $this->isRecordingSupported()) {
  132. $request['record'] = 'true';
  133. $request['duration'] = $prefs['bigbluebutton_recording_max_duration'];
  134. }
  135. $this->performRequest('create', $request);
  136. $cachelib->invalidate('bbb_meetinglist');
  137. }
  138. public function configureRoom($meetingName, $configuration)
  139. {
  140. global $prefs;
  141. if (empty($configuration) || ! $this->isDynamicConfigurationSupported()) {
  142. return null;
  143. }
  144. $content = $this->performRequest('getDefaultConfigXML', ['random' => '1'], false);
  145. if (! $content) {
  146. return null;
  147. }
  148. $config = new Tiki\BigBlueButton\Configuration($content);
  149. if (isset($configuration['presentation']['active']) && ! $configuration['presentation']['active']) {
  150. $config->removeModule('PresentModule');
  151. }
  152. $content = $config->getXml();
  153. $parameters = [
  154. 'meetingID' => $meetingName,
  155. 'configXML' => rawurlencode($content),
  156. ];
  157. $tikilib = TikiLib::lib('tiki');
  158. $checksum = $this->generateChecksum('setConfigXML', $parameters);
  159. $client = $tikilib->get_http_client($this->getBaseUrl('/api/setConfigXML.xml') . '?');
  160. $client->setParameterPost(
  161. [
  162. 'meetingID' => $meetingName,
  163. 'configXML' => rawurlencode($content),
  164. 'checksum' => $checksum,
  165. ]
  166. );
  167. $client->getRequest()->setMethod(Laminas\Http\Request::METHOD_POST);
  168. $response = $client->send();
  169. $document = $response->getBody();
  170. $dom = new DOMDocument();
  171. $dom->loadXML($document);
  172. $values = $this->grabValues($dom->documentElement);
  173. if ($values['returncode'] == 'SUCCESS') {
  174. return $values['configToken'];
  175. }
  176. }
  177. /**
  178. * @param $room
  179. */
  180. public function joinMeeting($room, $configToken = null)
  181. {
  182. $version = $this->getVersion();
  183. $name = $this->getAttendeeName();
  184. $password = $this->getAttendeePassword($room);
  185. if ($name && $password) {
  186. TikiLib::lib('logs')->add_action('Joined Room', $room, 'bigbluebutton');
  187. $this->joinRawMeeting($room, $name, $password, $configToken);
  188. }
  189. }
  190. /**
  191. * @param $recordingID
  192. */
  193. public function removeRecording($recordingID)
  194. {
  195. if ($this->isRecordingSupported()) {
  196. $this->performRequest(
  197. 'deleteRecordings',
  198. ['recordID' => $recordingID]
  199. );
  200. }
  201. }
  202. /**
  203. * @return bool|mixed|null|string
  204. */
  205. private function getAttendeeName()
  206. {
  207. global $user, $tikilib;
  208. if ($realName = $tikilib->get_user_preference($user, 'realName')) {
  209. $realName .= " (" . $user . ")";
  210. return $realName;
  211. } elseif ($user) {
  212. return $user;
  213. } elseif (! empty($_SESSION['bbb_name'])) {
  214. return $_SESSION['bbb_name'];
  215. } else {
  216. return tra('anonymous');
  217. }
  218. }
  219. /**
  220. * @param $room
  221. * @return mixed
  222. */
  223. private function getAttendeePassword($room)
  224. {
  225. if ($meeting = $this->getMeeting($room)) {
  226. $perms = Perms::get('bigbluebutton', $room);
  227. if ($perms->bigbluebutton_moderate) {
  228. return $meeting['moderatorPW'];
  229. } else {
  230. return $meeting['attendeePW'];
  231. }
  232. }
  233. }
  234. /**
  235. * @param $room
  236. * @return mixed
  237. */
  238. private function getMeeting($room)
  239. {
  240. $meetings = $this->getMeetings();
  241. foreach ($meetings as $meeting) {
  242. if ($meeting['meetingID'] == $room) {
  243. return $meeting;
  244. }
  245. }
  246. }
  247. /**
  248. * @param $room
  249. * @param $name
  250. * @param $password
  251. */
  252. public function joinRawMeeting($room, $name, $password, $configToken = null)
  253. {
  254. $parameters = [
  255. 'meetingID' => $room,
  256. 'fullName' => $name,
  257. 'password' => $password,
  258. ];
  259. if ($configToken) {
  260. $parameters['configToken'] = $configToken;
  261. }
  262. $url = $this->buildUrl('join', $parameters);
  263. header('Location: ' . $url);
  264. exit;
  265. }
  266. /**
  267. * @param $action
  268. * @param array $parameters
  269. * @return DOMDocument
  270. */
  271. private function performRequest($action, array $parameters, $checkSuccess = true)
  272. {
  273. global $tikilib;
  274. $url = $this->buildUrl($action, $parameters);
  275. if ($result = $tikilib->httprequest($url)) {
  276. $dom = new DOMDocument();
  277. if ($dom->loadXML($result)) {
  278. $nodes = $dom->getElementsByTagName('returncode');
  279. if (! $checkSuccess) {
  280. return $dom;
  281. }
  282. if ($nodes->length > 0 && ($returnCode = $nodes->item(0)) && $returnCode->textContent == 'SUCCESS') {
  283. return $dom;
  284. }
  285. }
  286. }
  287. }
  288. /**
  289. * @param $action
  290. * @param array $parameters
  291. * @return string
  292. */
  293. private function buildUrl($action, array $parameters)
  294. {
  295. if ($action) {
  296. if ($checksum = $this->generateChecksum($action, $parameters)) {
  297. $parameters['checksum'] = $checksum;
  298. }
  299. }
  300. $url = $this->getBaseUrl("/api/$action");
  301. $url .= "?" . http_build_query($parameters, '', '&');
  302. return $url;
  303. }
  304. private function getBaseUrl($path)
  305. {
  306. global $prefs;
  307. $base = rtrim($prefs['bigbluebutton_server_location'], '/');
  308. if (false === strpos($base, '/bigbluebutton')) {
  309. $base .= '/bigbluebutton';
  310. }
  311. $url = "$base$path";
  312. return $url;
  313. }
  314. /**
  315. * @param $action
  316. * @param array $parameters
  317. * @return string
  318. */
  319. private function generateChecksum($action, array $parameters)
  320. {
  321. global $prefs;
  322. if ($prefs['bigbluebutton_server_salt']) {
  323. $query = http_build_query($parameters, '', '&');
  324. $version = $this->getVersion();
  325. if (-1 === version_compare($version, '0.7')) {
  326. return sha1($query . $prefs['bigbluebutton_server_salt']);
  327. } else {
  328. return sha1($action . $query . $prefs['bigbluebutton_server_salt']);
  329. }
  330. }
  331. }
  332. /**
  333. * @return bool
  334. */
  335. private function isRecordingSupported()
  336. {
  337. $version = $this->getVersion();
  338. return version_compare($version, '0.8') >= 0;
  339. }
  340. /**
  341. * @return bool
  342. */
  343. private function isDynamicConfigurationSupported()
  344. {
  345. global $prefs;
  346. return $prefs['bigbluebutton_dynamic_configuration'] == 'y';
  347. }
  348. /**
  349. * @param $room
  350. * @return array
  351. */
  352. public function getRecordings($room)
  353. {
  354. if (! $this->isRecordingSupported()) {
  355. return [];
  356. }
  357. $result = $this->performRequest(
  358. 'getRecordings',
  359. ['meetingID' => $room,]
  360. );
  361. $data = [];
  362. $recordings = $result->getElementsByTagName('recording');
  363. foreach ($recordings as $recording) {
  364. $recording = simplexml_import_dom($recording);
  365. if ($recording->published == 'false') {
  366. $published = false;
  367. } else {
  368. $published = true;
  369. }
  370. $info = [
  371. 'recordID' => (string) $recording->recordID,
  372. 'startTime' => floor(((string) $recording->startTime) / 1000),
  373. 'endTime' => ceil(((string) $recording->endTime) / 1000),
  374. 'playback' => [],
  375. 'published' => $published,
  376. ];
  377. foreach ($recording->playback as $playback) {
  378. $info['playback'][ (string) $playback->format->type ] = (string) $playback->format->url;
  379. }
  380. $data[] = $info;
  381. }
  382. usort($data, ["BigBlueButtonLib", "cmpStartTime"]);
  383. return $data;
  384. }
  385. /**
  386. * @param $a
  387. * @param $b
  388. * @return int
  389. */
  390. private static function cmpStartTime($a, $b)
  391. {
  392. if ($a['startTime'] == $b['startTime']) {
  393. return 0;
  394. }
  395. return ($a['startTime'] > $b['startTime']) ? -1 : 1;
  396. }
  397. }