server_http_bind; } public function __construct() { global $prefs; $this->server_host = $prefs['xmpp_server_host']; $this->server_http_bind = $prefs['xmpp_server_http_bind']; if (! class_exists('XmppPrebind')) { throw new Exception( "class 'XmppPrebind' does not exists." . " Install with `composer require candy-chat/xmpp-prebind-php:dev-master`.", 1 ); } } public function get_user_connection_info($user) { global $prefs; global $tikilib; $query = 'SELECT' . ' MAX(CASE WHEN `prefName`="xmpp_jid" THEN `value` END) AS `jid`,' . ' MAX(CASE WHEN `prefName`="xmpp_password" THEN `value` END) AS `password`,' . ' MAX(CASE WHEN `prefName`="xmpp_custom_server_http_bind" THEN `value` END) AS `http_bind`,' . ' MAX(CASE WHEN `prefName`="realName" THEN `value` END) AS `nickname`' . ' FROM `tiki_user_preferences` WHERE `user`=?' . ' AND `prefName` IN ("xmpp_jid", "xmpp_password", "xmpp_custom_server_http_bind", "realName")'; $query = $this->query($query, [$user]); $login = $query->fetchRow(); if (empty($login['jid']) && $user) { $login['jid'] = sprintf('%s@%s', $user, $prefs['xmpp_server_host']); } $info = array( 'domain' => $this->server_host, 'http_bind' => $this->server_http_bind, 'jid' => JID::buildJid($login['jid'], $prefs['xmpp_server_host']), 'password' => $login['password'] ?: '', 'username' => $login['jid'], 'nickname' => $login['nickname'] ?: $user, ); $jid_parts = JID::parseJid($login['jid']); if ($jid_parts) { $info['jid'] = $login['jid']; $info['username'] = $jid_parts['node']; $info['domain'] = $jid_parts['domain']; $info['http_bind'] = $login['http_bind'] ?: $this->server_http_bind; } return $info; } public function check_token($givenUser, $givenToken) { global $prefs; $tokenlib = AuthTokens::build($prefs); $token = $tokenlib->getToken($givenToken); if (! $token || $token['entry'] !== 'openfireauthtoken') { return false; } // TODO: figure out how to delete token after n usages $tokenlib->deleteToken($token['tokenId']); $param = json_decode($token['parameters'], true); return is_array($param) && ! empty($param['user']) && $param['user'] === $givenUser; } public function create_room_from_wikipage($args, $name, $priority) { global $prefs; global $user; $info = $this->get_user_connection_info($user); $userJid = $info['jid']; if (! is_array($args) || empty($args['data'])) { return; } preg_match('/\{xmpp\b[^}]*\}/i', $args['data'], $match) && preg_match_all('/(?:(\w+)=(?:"([^"]*)"|([^\s]+)))/', $match[0], $match); if (empty($match)) { return; } $params = array(); for ($i = 0; $i < count($match[1]); $i += 1) { $key = $match[ 1 ][ $i ]; $val = $match[ 2 ][ $i ] ?: $match[ 3 ][ $i ]; $params[ $key ] = $val; } if (empty($params['room'])) { return; } $room = $params['room']; $args = [ 'whois' => [ 'moderator', 'participant', 'visitor' ], ]; $args['roomname'] = $room; $atpos = strpos($room, '@'); if ($atpos) { $args['roomname'] = substr($room, 0, $atpos); } else { $room = $room . '@' . $prefs['xmpp_muc_component_domain']; } $args['roomdesc'] = $args['roomname']; if (! empty($params['roomdesc'])) { $args['roomdesc'] = $params['roomdesc']; } $args['maxusers'] = 30; if (! empty($params['maxUsers']) && is_numeric($params['maxUsers'])) { $params['maxUsers'] = (int)$params['maxUsers']; } if (! empty($params['can_anyone_discover_jid'])) { $args['whois'] = ($params['can_anyone_discover_jid'] === 'anyone'); } $args['persistentroom'] = isset($params['persistent']) && $params['persistent'] === 'y'; $args['moderatedroom'] = isset($params['moderated']) && $params['moderated'] === 'y'; $args['enablelogging'] = isset($params['archiving']) && $params['archiving'] === 'y'; $args['membersonly'] = ! empty($params['visibility']) && $params['visibility'] === 'members_only'; $args['publicroom'] = ! isset($params['secret']) || $params['secret'] !== 'y'; $args['roomadmins'] = [ $userJid ]; if ($this->create_room($room, $args)) { if (! empty($params['groups'])) { $groups = explode(',', $params['groups']); foreach ($groups as $group) { $this->add_group_to_room($args['roomname'], $group, 'members'); } } } } public function create_room($room, $args) { if (empty($args) || empty($args['roomname'])) { return; } $xmppapi = $this->getXmppApi(); $return = $xmppapi->createRoom( $xmppapi->getJid(), $room . '/' . $xmppapi->getUsername(), $args ); return $return; } public function sanitize_name($text) { global $tikilib; $result = $tikilib->take_away_accent($text); $result = preg_replace('*[ $&`:<>\[\]{}"+#%@/;=?^|~\',]+*', '-', $result); $result = strtolower($result); $result = trim($result); return $result; } public function prebind($user) { global $prefs; global $tikilib; $session_id = substr($tikilib->sessionId, 0, 5); $browser_title = $prefs['browsertitle']; $browser_title = $this->sanitize_name($browser_title); $resource_name = "{$browser_title}-{$session_id}"; $tokenlib = AuthTokens::build($prefs); if (empty($this->server_host) || empty($this->server_http_bind)) { header("HTTP/1.0 500 Internal Server Error"); header('Content-Type: application/json'); return ["msg" => "No XMPP server to bind."]; } if ($user === null && $prefs['xmpp_openfire_allow_anonymous'] === 'y') { $user = $user ?: 'anonymous_' . $session_id; } $xmpp = $this->get_user_connection_info($user); $xmpp_prebind_class = XmppPrebind; $use_tikitoken = $xmpp['username'] === $user; $use_tikitoken = $use_tikitoken && $xmpp['domain'] === $this->server_host; $use_tikitoken = $use_tikitoken && ! empty($prefs['xmpp_auth_method']); $use_tikitoken = $use_tikitoken && $prefs['xmpp_auth_method'] === 'tikitoken'; if ($use_tikitoken) { $token = $tokenlib->createToken( 'openfireauthtoken', ['user' => $user], // parameters [], // groups [ 'timeout' => 300, 'createUser' => 'n', ] ); $xmpp['password'] = "$token"; $xmpp_prebind_class = TikiXmppPrebind; } else { if (empty($xmpp['password'])) { return []; } } $xmppPrebind = new $xmpp_prebind_class( $xmpp['domain'], $xmpp['http_bind'], $resource_name, false, false ); $xmppPrebind->connect($xmpp['username'], $xmpp['password']); try { $xmppPrebind->auth(); $result = $xmppPrebind->getSessionInfo(); } catch (XmppPrebindException $e) { throw new Exception($e->getMessage(), 401); } return $result; } /** * Add css and js files and initializes xmpp client page * * @param array $params : * view__mode => overlayed | fullscreen | mobile | embedded * * @return string * @throws Exception */ public function render_xmpp_client($params = []) { global $user, $prefs; static $instance = 0; $instance++; if ($instance > 1) { return ''; } $xmpplib = TikiLib::lib('xmpp'); $xmpp = $xmpplib->get_user_connection_info($user); $params = array_merge([ 'view_mode' => 'overlayed', 'room' => '', 'show_controlbox_by_default' => 'y', 'show_occupants_by_default' => 'y', ], $params); $xmppclient = new ConverseJS(); $xmppclient->set_auth($params); $xmppclient->set_options( [ 'bosh_service_url' => $xmpp['http_bind'], 'jid' => $xmpp['jid'], 'nickname' => $xmpp['nickname'] ?: 'visitor-' . time(), 'view_mode' => $params['view_mode'], 'show_controlbox_by_default' => $params['show_controlbox_by_default'] === 'y', 'show_occupants_by_default' => $params['show_occupants_by_default'] === 'y', ] ); $xmppclient->set_auto_join_rooms($params['room']); $xmppclient->render(); } public function initializeRestApi() { global $prefs; $endpoint = $prefs['xmpp_openfire_rest_api']; $username = $prefs['xmpp_openfire_rest_api_username']; $password = $prefs['xmpp_openfire_rest_api_password']; $url = parse_url($endpoint); $ssl = $url['scheme'] === 'https'; $host = $url['host']; $port = $url['port'] ?: ($ssl ? 9091 : 9090); $path = rtrim($url['path'], '/'); $api = new Gidkom\OpenFireRestApi\OpenFireRestApi(); $api->useBasicAuth = true; $api->basicUser = $username; $api->basicPwd = $password; $api->host = $host; $api->port = $port; $api->useSSL = $ssl; $api->plugin = $path; $this->restapi = $api; return $api; } public function getRestApi() { if ($this->restapi == null) { return $this->initializeRestApi(); } return $this->restapi; } public function getXmppApi() { global $prefs; if (empty($this->xmppapi)) { $params = array( "scheme" => "tcp", "host" => $this->server_host, "port" => 5222, "user" => $prefs['xmpp_openfire_rest_api_username'], "pass" => $prefs['xmpp_openfire_rest_api_password'], ); $this->xmppapi = new TikiXmppChat($params); $this->xmppapi->connect(); } return $this->xmppapi; } public function addUserToRoom($room, $userJid, $role = 'members') { global $prefs; // first, allow myself to join the room $ownerJid = new JID($this->getXmppApi()->getJid()); $onwerName = $ownerJid->getNode(); $roomJid = new JID($room); $roomName = $roomJid->getNode(); $result = $this->getRestApi()->addUserRoleToChatRoom($roomName, $onwerName, 'owners'); $result = $this->getRestApi()->addUserRoleToChatRoom($roomName, $userJid, $role); $this->getXmppApi() ->sendPresence(1, (string) $roomJid, $onwerName) ->sendInvitation((string) $roomJid, $userJid) ; return $result; } public function addUsersToRoom($params = array(), $defaultRoom = '', $defaultRole = 'members') { $params = array_map(function ($item) use ($defaultRoom, $defaultRole) { $status = is_array($item); $item = $status ? $item : array(); $status = $status && ! empty($item['name']); $status = ! (empty($item['room']) && empty($defaultRoom)); return array_merge(array( 'role' => $defaultRole, 'room' => $defaultRoom, 'name' => '', 'status' => $status ), $item); }, $params); $self = $this; return array_map(function ($item) use ($self) { if (empty($item['status'])) { return $item; } $response = $self->addUserToRoom($item['room'], $item['jid'], $item['role']); return array_merge($item, $response); }, $params); } public function add_group_to_room($room, $name, $role = 'members') { $restapi = $this->getRestApi(); return $restapi->addGroupRoleToChatRoom($room, $name, $role); } public function add_groups_to_room($params = array(), $defaultRoom = '', $defaultRole = 'members') { $params = array_map(function ($item) use ($defaultRoom, $defaultRole) { $status = is_array($item); $item = $status ? $item : array(); $status = $status && ! empty($item['name']); $status = ! (empty($item['room']) && empty($defaultRoom)); return array_merge(array( 'role' => $defaultRole, 'room' => $defaultRoom, 'name' => '', 'status' => $status ), $item); }, $params); $self = $this; return array_map(function ($item) use ($self) { if (empty($item['status'])) { return $item; } $response = $self->add_group_to_room(item['room'], $item['name'], $item['role']); return array_merge($item, $response); }, $params); } public function get_groups() { $restapi = $this->getRestApi(); $response = $restapi->getGroups(); $items = []; if (! empty($response['data']) && ! empty($response['data']->groups)) { $items = $response['data']->groups; } // groups has attr `name` and `description` // users has attr `username` and `name` // let's make a common `name` and `fullname` return array_map(function ($item) { return array( 'name' => $item->name, 'fullname' => $item->description ); }, $items); } public function getUsers() { $cachelib = TikiLib::lib('cache'); $cache_key = 'xmppJidList'; if ($items = $cachelib->getSerialized($cache_key)) { return $items; } $query = 'SELECT' . ' user as username,' . ' MAX(CASE WHEN `prefName`="xmpp_jid" THEN `value` END) AS `jid`,' . ' MAX(CASE WHEN `prefName`="realName" THEN `value` END) AS `name`' . ' FROM `tiki_user_preferences` WHERE' . ' `prefName` IN ("xmpp_jid", "realName")' . ' GROUP BY user;'; $items = $this->query($query); $items = array_map(function ($item) { return array( 'name' => $item['username'], 'fullname' => $item['name'], 'jid' => $item['jid'] ?: "{$item['username']}@{$this->server_host}" ); }, $items->result); $cachelib->cacheItem($cache_key, serialize($items)); return $items; } }