diff --git a/plugins/kolab_chat/config.inc.php.dist b/plugins/kolab_chat/config.inc.php.dist index cc5fe534..d355d83b 100644 --- a/plugins/kolab_chat/config.inc.php.dist +++ b/plugins/kolab_chat/config.inc.php.dist @@ -1,33 +1,38 @@ * * Copyright (C) 2014-2018, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ class kolab_chat_mattermost { private $rc; private $plugin; private $token_valid = false; /** * Object constructor * * @param rcube_plugin $plugin Kolab_chat plugin object */ public function __construct($plugin) { $this->rc = rcube::get_instance(); $this->plugin = $plugin; } /** * Returns location of the chat app * * @param bool $websocket Return websocket URL * * @return string The chat app location */ public function url($websocket = false) { $url = rtrim($this->rc->config->get('kolab_chat_url'), '/'); if ($websocket) { $url = str_replace(array('http://', 'https://'), array('ws://', 'wss://'), $url); $url .= '/api/v4/websocket'; } else if ($this->rc->action == 'index' && $this->rc->task == 'kolab-chat') { - if ($channel = rcube_utils::get_input_value('_channel', rcube_utils::INPUT_GET)) { - $team = rcube_utils::get_input_value('_team', rcube_utils::INPUT_GET); + $channel = rcube_utils::get_input_value('_channel', rcube_utils::INPUT_GET); + if (empty($channel)) { + $channel = $this->rc->config->get('kolab_chat_channel'); + } + if ($channel) { + $team = rcube_utils::get_input_value('_team', rcube_utils::INPUT_GET); if (empty($team) && ($_channel = $this->get_channel($channel, true))) { $team = $_channel['team_name']; } if ($channel && $team) { $url .= '/' . urlencode($team) . '/channels/' . urlencode($channel); } } } return $url; } /** * Add/register UI elements */ public function ui() { if ($this->rc->task != 'kolab-chat') { $this->plugin->include_script("js/mattermost.js"); $this->plugin->add_label('openchat', 'directmessage', 'mentionmessage'); } else if ($this->get_token()) { rcube_utils::setcookie('MMUSERID', $_SESSION['mattermost'][0], 0, false); rcube_utils::setcookie('MMAUTHTOKEN', $_SESSION['mattermost'][1], 0, false); } } /** * Driver specific actions handler */ public function action() { $result = array( 'url' => $this->url(true), 'token' => $this->get_token(), 'user_id' => $this->get_user_id(), ); echo rcube_output::json_serialize($result); exit; } + /** + * Handler for preferences_list hook. + * Adds options blocks into Chat settings sections in Preferences. + * + * @param array &$p Hook parameters + */ + public function preferences_list(&$p) + { + $no_override = array_flip((array) $this->rc->config->get('dont_override')); + + if (!isset($no_override['kolab_chat_channel'])) { + $field_id = 'rcmfd_kolab_chat_channel'; + $select = new html_select(array('name' => '_kolab_chat_channel', 'id' => $field_id)); + + $select->add('', ''); + if ($channels = $this->get_channels_list()) { + foreach ($channels as $channel) { + if ($channel['name'] && $channel['display_name']) { + $select->add($channel['display_name'], $channel['id']); + } + } + } + + $p['blocks']['main']['options']['kolab_chat_channel'] = array( + 'title' => html::label($field_id, rcube::Q($this->plugin->gettext('defaultchannel'))), + 'content' => $select->show($this->rc->config->get('kolab_chat_channel')), + ); + } + } + + /** + * Handler for preferences_save hook. + * Executed on Chat settings form submit. + * + * @param array &$p Hook parameters + */ + public function preferences_save(&$p) + { + if (isset($_POST['_kolab_chat_channel'])) { + $p['prefs']['kolab_chat_channel'] = rcube_utils::get_input_value('_kolab_chat_channel', rcube_utils::INPUT_POST); + } + } + /** * Returns the Mattermost session token * Note: This works only if the user/pass is the same in Kolab and Mattermost * * @return string Session token */ protected function get_token() { $user = $_SESSION['username']; $pass = $this->rc->decrypt($_SESSION['password']); // Use existing token if still valid if (!empty($_SESSION['mattermost'])) { $user_id = $_SESSION['mattermost'][0]; $token = $_SESSION['mattermost'][1]; if ($this->token_valid) { return $token; } try { - $request = $this->get_api_request('GET', '/api/v4/users/me'); + $request = $this->get_api_request('GET', '/users/me'); $request->setHeader('Authorization', "Bearer $token"); $response = $request->send(); $status = $response->getStatus(); if ($status != 200) { $token = null; } } catch (Exception $e) { rcube::raise_error($e, true, false); $token = null; } } // Request a new session token if (empty($token)) { - $request = $this->get_api_request('POST', '/api/v4/users/login'); + $request = $this->get_api_request('POST', '/users/login'); $request->setBody(json_encode(array( 'login_id' => $user, 'password' => $pass, ))); // send request to the API, get token and user ID try { $response = $request->send(); $status = $response->getStatus(); $token = $response->getHeader('Token'); $body = json_decode($response->getBody(), true); if ($status == 200) { $user_id = $body['id']; } else if (is_array($body) && $body['message']) { throw new Exception($body['message']); } else { throw new Exception("Failed to authenticate the chat user ($user). Status: $status"); } } catch (Exception $e) { rcube::raise_error($e, true, false); } } if ($user_id && $token) { $this->token_valid = true; $_SESSION['mattermost'] = array($user_id, $token); return $token; } } /** * Returns the Mattermost user ID * Note: This works only if the user/pass is the same in Kolab and Mattermost * * @return string User ID */ protected function get_user_id() { if ($token = $this->get_token()) { return $_SESSION['mattermost'][0]; } } /** * Returns the Mattermost channel info * * @param string $channel_id Channel ID * @param bool $extended Return extended information (i.e. team information) * * @return array Channel information */ protected function get_channel($channel_id, $extended = false) { - $channel = $this->api_get('/api/v4/channels/' . urlencode($channel_id)); + $channel = $this->api_get('/channels/' . urlencode($channel_id)); if ($extended && is_array($channel)) { if (!empty($channel['team_id'])) { - $team = $this->api_get('/api/v4/teams/' . urlencode($channel['team_id'])); + $team = $this->api_get('/teams/' . urlencode($channel['team_id'])); } else if ($teams = $this->get_user_teams()) { // if there's no team assigned to the channel, we'll get the user's team // so we can build proper channel URL, there's no other way in the API $team = $teams[0]; } if (!empty($team)) { $channel['team_id'] = $team['id']; $channel['team_name'] = $team['name']; } } return $channel; } + /** + * Returns the Mattermost channels list for the user + * + * @return array Channels list (id, name, display_name, etc.) + */ + protected function get_channels_list() + { + // FIXME: Should we list only public channels here? + // FIXME: SHould we check all user teams? + + $user_id = $this->get_user_id(); + $teams = $this->get_user_teams(); + + if (!empty($teams) && ($team = $teams[0])) { + // FIXME: Why it does return channels not assigned to the specified team? + return $this->api_get(sprintf('/users/%s/teams/%s/channels', urlencode($user_id), urlencode($team['id']))); + } + + return false; + } + /** * Returns the Mattermost teams of the user * * @return array List of teams */ protected function get_user_teams() { - return $this->api_get('/api/v4/users/' . urlencode($this->get_user_id()) . '/teams'); + return $this->api_get('/users/' . urlencode($this->get_user_id()) . '/teams'); } /** * Return HTTP/Request2 instance for Mattermost API connection */ protected function get_api_request($type, $path) { $url = rtrim($this->rc->config->get('kolab_chat_url'), '/'); $defaults = array( 'store_body' => true, 'follow_redirects' => true, ); $config = array_merge($defaults, (array) $this->rc->config->get('kolab_chat_http_request')); - return libkolab::http_request($url . $path, $type, $config); + return libkolab::http_request($url . '/api/v4' . $path, $type, $config); } /** * Call API GET command */ protected function api_get($path) { if ($token = $this->get_token()) { try { $request = $this->get_api_request('GET', $path); $request->setHeader('Authorization', "Bearer $token"); $response = $request->send(); $status = $response->getStatus(); $body = $response->getBody(); if ($status == 200) { return json_decode($body, true); } } catch (Exception $e) { rcube::raise_error($e, true, false); } } } } diff --git a/plugins/kolab_chat/kolab_chat.php b/plugins/kolab_chat/kolab_chat.php index 03f5e35d..830eaa4e 100644 --- a/plugins/kolab_chat/kolab_chat.php +++ b/plugins/kolab_chat/kolab_chat.php @@ -1,219 +1,222 @@ * * Copyright (C) 2014-2018, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ class kolab_chat extends rcube_plugin { public $task = '^(?!login|logout).*$'; private $rc; private $driver; public function init() { $this->rc = rcube::get_instance(); $this->add_hook('startup', array($this, 'startup')); } /** * Startup hook handler, initializes/enables Chat */ public function startup($args) { // the files module can be enabled/disabled by the kolab_auth plugin if ($this->rc->config->get('kolab_chat_disabled') || !$this->rc->config->get('kolab_chat_enabled', true)) { return; } - $this->add_texts('localization/'); $this->load_config(); $extwin = $this->rc->config->get('kolab_chat_extwin'); $driver = $this->rc->config->get('kolab_chat_driver', 'mattermost'); if (!$driver || !file_exists(__DIR__ . "/drivers/$driver.php")) { return; } // Load the driver require_once __DIR__ . "/drivers/$driver.php"; $class_name = "kolab_chat_$driver"; $this->driver = new $class_name($this); + $this->add_texts('localization/'); + // Register UI end-points $this->register_task('kolab-chat'); $this->register_action('index', array($this, 'ui')); $this->register_action('action', array($this, 'action')); if ($this->rc->output->type == 'html' && !$this->rc->output->get_env('framed')) { $this->include_stylesheet($this->local_skin_path() . '/kolab_chat.css'); $this->rc->output->set_env('kolab_chat_extwin', (bool) $extwin); $this->rc->output->add_script( "rcmail.addEventListener('beforeswitch-task', function(p) { if (p == 'kolab-chat' && rcmail.env.kolab_chat_extwin) { rcmail.open_window('?_task=kolab-chat&redirect=1', false, false, true); return false; } });", 'foot' ); $this->add_button(array( 'command' => 'kolab-chat', 'class' => 'button-chat', 'classsel' => 'button-chat button-selected', 'innerclass' => 'button-inner', 'label' => 'kolab_chat.chat', 'type' => 'link', ), 'taskbar'); $this->driver->ui(); } if ($this->rc->output->type == 'html' && $args['task'] == 'settings') { // add hooks for Chat settings $this->add_hook('preferences_sections_list', array($this, 'preferences_sections_list')); $this->add_hook('preferences_list', array($this, 'preferences_list')); $this->add_hook('preferences_save', array($this, 'preferences_save')); } } /** * Display chat iframe wrapped by Roundcube interface elements (taskmenu) * or a dummy page with redirect to the chat app. */ function ui() { - if ($this->driver) { - $this->driver->ui(); + $this->driver->ui(); - $url = rcube::JQ($this->driver->url()); + $url = rcube::JQ($this->driver->url()); - if (!empty($_GET['redirect'])) { - echo '' - . '' - . ''; - exit; - } - else { - $this->rc->output->add_script( - "rcmail.addEventListener('init', function() {" - . "rcmail.location_href('$url', rcmail.get_frame_window(rcmail.env.contentframe));" - . "});", - 'foot' - ); - - $this->rc->output->send('kolab_chat.chat'); - } + if (!empty($_GET['redirect'])) { + echo '' + . '' + . ''; + exit; + } + else { + $this->rc->output->add_script( + "rcmail.addEventListener('init', function() {" + . "rcmail.location_href('$url', rcmail.get_frame_window(rcmail.env.contentframe));" + . "});", + 'foot' + ); + + $this->rc->output->send('kolab_chat.chat'); } } /** * Handler for driver specific actions */ function action() { - if ($this->driver) { - $this->driver->action(); - } + $this->driver->action(); } /** * Handler for preferences_sections_list hook. * Adds Chat settings section into preferences sections list. * * @param array Original parameters * * @return array Modified parameters */ function preferences_sections_list($p) { $p['list']['kolab-chat'] = array( 'id' => 'kolab-chat', 'section' => $this->gettext('chat'), 'class' => 'chat' ); return $p; } /** * Handler for preferences_list hook. * Adds options blocks into Chat settings sections in Preferences. * * @param array Original parameters * * @return array Modified parameters */ function preferences_list($p) { if ($p['section'] != 'kolab-chat') { return $p; } $no_override = array_flip((array) $this->rc->config->get('dont_override')); $p['blocks']['main']['name'] = $this->gettext('mainoptions'); if (!isset($no_override['kolab_chat_extwin'])) { if (!$p['current']) { $p['blocks']['main']['content'] = true; return $p; } $field_id = 'rcmfd_kolab_chat_extwin'; $input = new html_checkbox(array('name' => '_kolab_chat_extwin', 'id' => $field_id, 'value' => 1)); $p['blocks']['main']['options']['kolab_chat_extwin'] = array( 'title' => html::label($field_id, rcube::Q($this->gettext('showinextwin'))), 'content' => $input->show($this->rc->config->get('kolab_chat_extwin') ? 1 : 0), ); } if ($p['current']) { // update env flag in the parent window $this->rc->output->command('parent.set_env', array('kolab_chat_extwin' => (bool) $this->rc->config->get('kolab_chat_extwin'))); + + // Add driver-specific options + $this->driver->preferences_list($p); } return $p; } /** * Handler for preferences_save hook. * Executed on Chat settings form submit. * * @param array Original parameters * * @return array Modified parameters */ function preferences_save($p) { if ($p['section'] == 'kolab-chat') { $p['prefs'] = array( 'kolab_chat_extwin' => isset($_POST['_kolab_chat_extwin']), ); + + // Add driver-specific options + $this->driver->preferences_save($p); } return $p; } } diff --git a/plugins/kolab_chat/localization/en_US.inc b/plugins/kolab_chat/localization/en_US.inc index 7447e557..7543f6e0 100644 --- a/plugins/kolab_chat/localization/en_US.inc +++ b/plugins/kolab_chat/localization/en_US.inc @@ -1,15 +1,16 @@