version !== false) { return $this->version; } if ($version = $this->performRequest('', [])) { $values = $this->grabValues($version->documentElement); $version = $values['version']; if (false !== $pos = strpos($version, '-')) { $version = substr($version, 0, $pos); } $this->version = $version; } else { $this->version = '0.6'; } return $this->version; } /** * @return array|mixed */ public function getMeetings() { $cachelib = TikiLib::lib('cache'); if (! $meetings = $cachelib->getSerialized('bbb_meetinglist')) { $meetings = []; if ($dom = $this->performRequest('getMeetings', ['random' => 1])) { foreach ($dom->getElementsByTagName('meeting') as $node) { $meetings[] = $this->grabValues($node); } } $cachelib->cacheItem('bbb_meetinglist', serialize($meetings)); } return $meetings; } /** * @param $room * @return array */ public function getAttendees($room, $username = false) { if ($meeting = $this->getMeeting($room)) { if ($dom = $this->performRequest('getMeetingInfo', ['meetingID' => $room, 'password' => $meeting['moderatorPW']])) { $attendees = []; foreach ($dom->getElementsByTagName('attendee') as $node) { $attendees[] = $this->grabValues($node, $username); } return $attendees; } } } /** * @param $node * @return array */ private function grabValues($node, $username = false) { $values = []; foreach ($node->childNodes as $n) { if ($n instanceof DOMElement) { $values[$n->tagName] = $n->textContent; } } if ($username && $values['fullName']) { preg_match('!\(([^\)]+)\)!', $values['fullName'], $match); $values['fullName'] = $match[1]; } else { $values['fullName'] = trim(preg_replace('!\(([^\)]+)\)!', '', $values['fullName'])); } return $values; } /** * @param $room * @return bool */ public function roomExists($room) { foreach ($this->getMeetings() as $meeting) { if ($meeting['meetingID'] == $room) { return true; } } return false; } /** * @param $room * @param array $params */ public function createRoom($room, array $params = []) { global $prefs; $cachelib = TikiLib::lib('cache'); $tikilib = TikiLib::lib('tiki'); $params = array_merge( ['logout' => $tikilib->tikiUrl(''),], $params ); $request = [ 'name' => $room, 'meetingID' => $room, 'logoutURL' => $params['logout'], ]; if (isset($params['welcome'])) { $request['welcome'] = $params['welcome']; } if (isset($params['number'])) { $request['dialNumber'] = $params['number']; } if (isset($params['voicebridge'])) { $request['voiceBridge'] = $params['voicebridge']; } else { $request['voiceBridge'] = '7' . mt_rand(0, 9999); } if (isset($params['logout'])) { $request['logoutURL'] = $tikilib->tikiUrl($params['logout']); } if (isset($params['recording']) && $params['recording'] > 0 && $this->isRecordingSupported()) { $request['record'] = 'true'; $request['duration'] = $prefs['bigbluebutton_recording_max_duration']; } $this->performRequest('create', $request); $cachelib->invalidate('bbb_meetinglist'); } public function configureRoom($meetingName, $configuration) { global $prefs; if (empty($configuration) || ! $this->isDynamicConfigurationSupported()) { return null; } $content = $this->performRequest('getDefaultConfigXML', ['random' => '1'], false); if (! $content) { return null; } $config = new Tiki\BigBlueButton\Configuration($content); if (isset($configuration['presentation']['active']) && ! $configuration['presentation']['active']) { $config->removeModule('PresentModule'); } $content = $config->getXml(); $parameters = [ 'meetingID' => $meetingName, 'configXML' => rawurlencode($content), ]; $tikilib = TikiLib::lib('tiki'); $checksum = $this->generateChecksum('setConfigXML', $parameters); $client = $tikilib->get_http_client($this->getBaseUrl('/api/setConfigXML.xml') . '?'); $client->setParameterPost( [ 'meetingID' => $meetingName, 'configXML' => rawurlencode($content), 'checksum' => $checksum, ] ); $client->getRequest()->setMethod(Laminas\Http\Request::METHOD_POST); $response = $client->send(); $document = $response->getBody(); $dom = new DOMDocument(); $dom->loadXML($document); $values = $this->grabValues($dom->documentElement); if ($values['returncode'] == 'SUCCESS') { return $values['configToken']; } } /** * @param $room */ public function joinMeeting($room, $configToken = null) { $version = $this->getVersion(); $name = $this->getAttendeeName(); $password = $this->getAttendeePassword($room); if ($name && $password) { TikiLib::lib('logs')->add_action('Joined Room', $room, 'bigbluebutton'); $this->joinRawMeeting($room, $name, $password, $configToken); } } /** * @param $recordingID */ public function removeRecording($recordingID) { if ($this->isRecordingSupported()) { $this->performRequest( 'deleteRecordings', ['recordID' => $recordingID] ); } } /** * @return bool|mixed|null|string */ private function getAttendeeName() { global $user, $tikilib; if ($realName = $tikilib->get_user_preference($user, 'realName')) { $realName .= " (" . $user . ")"; return $realName; } elseif ($user) { return $user; } elseif (! empty($_SESSION['bbb_name'])) { return $_SESSION['bbb_name']; } else { return tra('anonymous'); } } /** * @param $room * @return mixed */ private function getAttendeePassword($room) { if ($meeting = $this->getMeeting($room)) { $perms = Perms::get('bigbluebutton', $room); if ($perms->bigbluebutton_moderate) { return $meeting['moderatorPW']; } else { return $meeting['attendeePW']; } } } /** * @param $room * @return mixed */ private function getMeeting($room) { $meetings = $this->getMeetings(); foreach ($meetings as $meeting) { if ($meeting['meetingID'] == $room) { return $meeting; } } } /** * @param $room * @param $name * @param $password */ public function joinRawMeeting($room, $name, $password, $configToken = null) { $parameters = [ 'meetingID' => $room, 'fullName' => $name, 'password' => $password, ]; if ($configToken) { $parameters['configToken'] = $configToken; } $url = $this->buildUrl('join', $parameters); header('Location: ' . $url); exit; } /** * @param $action * @param array $parameters * @return DOMDocument */ private function performRequest($action, array $parameters, $checkSuccess = true) { global $tikilib; $url = $this->buildUrl($action, $parameters); if ($result = $tikilib->httprequest($url)) { $dom = new DOMDocument(); if ($dom->loadXML($result)) { $nodes = $dom->getElementsByTagName('returncode'); if (! $checkSuccess) { return $dom; } if ($nodes->length > 0 && ($returnCode = $nodes->item(0)) && $returnCode->textContent == 'SUCCESS') { return $dom; } } } } /** * @param $action * @param array $parameters * @return string */ private function buildUrl($action, array $parameters) { if ($action) { if ($checksum = $this->generateChecksum($action, $parameters)) { $parameters['checksum'] = $checksum; } } $url = $this->getBaseUrl("/api/$action"); $url .= "?" . http_build_query($parameters, '', '&'); return $url; } private function getBaseUrl($path) { global $prefs; $base = rtrim($prefs['bigbluebutton_server_location'], '/'); if (false === strpos($base, '/bigbluebutton')) { $base .= '/bigbluebutton'; } $url = "$base$path"; return $url; } /** * @param $action * @param array $parameters * @return string */ private function generateChecksum($action, array $parameters) { global $prefs; if ($prefs['bigbluebutton_server_salt']) { $query = http_build_query($parameters, '', '&'); $version = $this->getVersion(); if (-1 === version_compare($version, '0.7')) { return sha1($query . $prefs['bigbluebutton_server_salt']); } else { return sha1($action . $query . $prefs['bigbluebutton_server_salt']); } } } /** * @return bool */ private function isRecordingSupported() { $version = $this->getVersion(); return version_compare($version, '0.8') >= 0; } /** * @return bool */ private function isDynamicConfigurationSupported() { global $prefs; return $prefs['bigbluebutton_dynamic_configuration'] == 'y'; } /** * @param $room * @return array */ public function getRecordings($room) { if (! $this->isRecordingSupported()) { return []; } $result = $this->performRequest( 'getRecordings', ['meetingID' => $room,] ); $data = []; $recordings = $result->getElementsByTagName('recording'); foreach ($recordings as $recording) { $recording = simplexml_import_dom($recording); if ($recording->published == 'false') { $published = false; } else { $published = true; } $info = [ 'recordID' => (string) $recording->recordID, 'startTime' => floor(((string) $recording->startTime) / 1000), 'endTime' => ceil(((string) $recording->endTime) / 1000), 'playback' => [], 'published' => $published, ]; foreach ($recording->playback as $playback) { $info['playback'][ (string) $playback->format->type ] = (string) $playback->format->url; } $data[] = $info; } usort($data, ["BigBlueButtonLib", "cmpStartTime"]); return $data; } /** * @param $a * @param $b * @return int */ private static function cmpStartTime($a, $b) { if ($a['startTime'] == $b['startTime']) { return 0; } return ($a['startTime'] > $b['startTime']) ? -1 : 1; } }