Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F4682353
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
87 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/kolab_2fa/kolab_2fa.php b/plugins/kolab_2fa/kolab_2fa.php
index f1794588..f3f85197 100644
--- a/plugins/kolab_2fa/kolab_2fa.php
+++ b/plugins/kolab_2fa/kolab_2fa.php
@@ -1,774 +1,774 @@
<?php
/**
* Kolab 2-Factor-Authentication plugin
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class kolab_2fa extends rcube_plugin
{
public $task = '(login|settings)';
protected $login_verified = null;
protected $login_factors = array();
protected $drivers = array();
protected $storage;
/**
* Plugin init
*/
public function init()
{
$this->load_config();
$this->add_hook('startup', array($this, 'startup'));
}
/**
* Startup hook
*/
public function startup($args)
{
$rcmail = rcmail::get_instance();
// register library namespace to autoloader
$loader = include(INSTALL_PATH . 'vendor/autoload.php');
$loader->set('Kolab2FA', array($this->home . '/lib'));
if ($args['task'] === 'login' && $this->api->output) {
$this->add_texts('localization/', false);
$this->add_hook('authenticate', array($this, 'authenticate'));
// process 2nd factor auth step after regular login
if ($args['action'] === 'plugin.kolab-2fa-login' /* || !empty($_SESSION['kolab_2fa_factors']) */) {
return $this->login_verify($args);
}
}
else if ($args['task'] === 'settings') {
$this->add_texts('localization/', !$this->api->output->ajax_call);
$this->add_hook('settings_actions', array($this, 'settings_actions'));
$this->register_action('plugin.kolab-2fa', array($this, 'settings_view'));
$this->register_action('plugin.kolab-2fa-data', array($this, 'settings_data'));
$this->register_action('plugin.kolab-2fa-save', array($this, 'settings_save'));
$this->register_action('plugin.kolab-2fa-verify', array($this, 'settings_verify'));
}
return $args;
}
/**
* Handler for 'authenticate' plugin hook.
*
* ATTENTION: needs to be called *after* kolab_auth::authenticate()
*/
public function authenticate($args)
{
// nothing to be done for me
if ($args['abort'] || $this->login_verified !== null) {
return $args;
}
$rcmail = rcmail::get_instance();
// parse $host URL
$a_host = parse_url($args['host']);
$hostname = $_SESSION['hostname'] = $a_host['host'] ?: $args['host'];
// 1. find user record (and its prefs) before IMAP login
if ($user = rcube_user::query($args['user'], $hostname)) {
$rcmail->config->set_user_prefs($user->get_prefs());
}
// 2a. let plugins provide the list of active authentication factors
$lookup = $rcmail->plugins->exec_hook('kolab_2fa_lookup', array(
'user' => $args['user'],
'host' => $hostname,
'factors' => $rcmail->config->get('kolab_2fa_factors'),
'check' => $rcmail->config->get('kolab_2fa_check', true),
));
if (isset($lookup['factors'])) {
$factors = (array)$lookup['factors'];
}
// 2b. check storage if this user has 2FA enabled
else if ($lookup['check'] !== false && ($storage = $this->get_storage($args['user']))) {
$factors = (array)$storage->enumerate();
}
if (count($factors) > 0) {
$args['abort'] = true;
$factors = array_unique($factors);
// 3. flag session for 2nd factor verification
$_SESSION['kolab_2fa_time'] = time();
$_SESSION['kolab_2fa_nonce'] = bin2hex(openssl_random_pseudo_bytes(32));
$_SESSION['kolab_2fa_factors'] = $factors;
$_SESSION['username'] = $args['user'];
$_SESSION['host'] = $args['host'];
$_SESSION['password'] = $rcmail->encrypt($args['pass']);
// 4. render to 2nd auth step
$this->login_step($factors);
}
return $args;
}
/**
* Handler for the additional login step requesting the 2FA verification code
*/
public function login_step($factors)
{
// replace handler for login form
$this->login_factors = array_values($factors);
$this->api->output->add_handler('loginform', array($this, 'auth_form'));
// focus the code input field on load
$this->api->output->add_script('$("input.kolab2facode").first().select();', 'docready');
$this->api->output->send('login');
}
/**
* Process the 2nd factor code verification form submission
*/
public function login_verify($args)
{
$rcmail = rcmail::get_instance();
$time = $_SESSION['kolab_2fa_time'];
$nonce = $_SESSION['kolab_2fa_nonce'];
$factors = (array)$_SESSION['kolab_2fa_factors'];
$this->login_verified = false;
$expired = $time < time() - $rcmail->config->get('kolab_2fa_timeout', 120);
if (!empty($factors) && !empty($nonce) && !$expired) {
// TODO: check signature
// try to verify each configured factor
foreach ($factors as $factor) {
list($method) = explode(':', $factor, 2);
// verify the submitted code
$code = rcube_utils::get_input_value("_${nonce}_${method}", rcube_utils::INPUT_POST);
$this->login_verified = $this->verify_factor_auth($factor, $code);
// accept first successful method
if ($this->login_verified) {
break;
}
}
}
if ($this->login_verified) {
// restore POST data from session
$_POST['_user'] = $_SESSION['username'];
$_POST['_host'] = $_SESSION['host'];
$_POST['_pass'] = $rcmail->decrypt($_SESSION['password']);
}
// proceed with regular login ...
$args['action'] = 'login';
// session data will be reset in index.php thus additional
// auth attempts with intercepted data will be rejected
// $rcmail->kill_session();
// we can't display any custom error messages on failed login
// but that's actually desired to expose as little information as possible
return $args;
}
/**
* Helper method to verify the given method/code tuple
*/
protected function verify_factor_auth($method, $code)
{
if (strlen($code) && ($driver = $this->get_driver($method))) {
// set properties from login
$driver->username = $_SESSION['username'];
try {
// verify the submitted code
return $driver->verify($code, $_SESSION['kolab_2fa_time']);
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
}
}
return false;
}
/**
* Render 2nd factor authentication form in place of the regular login form
*/
public function auth_form($attrib = array())
{
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
$nonce = $_SESSION['kolab_2fa_nonce'];
$methods = array_unique(array_map(function($factor) {
list($method, $id) = explode(':', $factor);
return $method;
},
$this->login_factors
));
// forward these values as the regular login screen would submit them
$input_task = new html_hiddenfield(array('name' => '_task', 'value' => 'login'));
$input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'plugin.kolab-2fa-login'));
$input_tzone = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_POST)));
$input_url = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)));
// create HTML table with two cols
$table = new html_table(array('cols' => 2));
$required = count($methods) > 1 ? null : 'required';
// render input for each configured auth method
foreach ($methods as $i => $method) {
if ($row++ > 0) {
$table->add(array('colspan' => 2, 'class' => 'title hint', 'style' => 'text-align:center'),
$this->gettext('or'));
}
$field_id = "rcmlogin2fa$method";
$input_code = new html_inputfield(array(
'name' => "_${nonce}_${method}",
'class' => 'kolab2facode',
'id' => $field_id,
'required' => $required,
'autocomplete' => 'off',
'data-icon' => 'key' // for Elastic
) + $attrib);
$table->add('title', html::label($field_id, html::quote($this->gettext($method))));
$table->add('input', $input_code->show(''));
}
$out = $input_task->show();
$out .= $input_action->show();
$out .= $input_tzone->show();
$out .= $input_url->show();
$out .= $table->show();
// add submit button
if (rcube_utils::get_boolean($attrib['submit'])) {
$out .= html::p('formbuttons', html::tag('button', array(
'type' => 'submit',
'id' => 'rcmloginsubmit',
'class' => 'button mainaction save',
), $this->gettext('continue'))
);
}
// surround html output with a form tag
if (empty($attrib['form'])) {
$out = $this->api->output->form_tag(array('name' => $form_name, 'method' => 'post'), $out);
}
return $out;
}
/**
* Load driver class for the given authentication factor
*
* @param string $factor Factor identifier (<method>:<id>)
* @return Kolab2FA\Driver\Base
*/
public function get_driver($factor)
{
list($method) = explode(':', $factor, 2);
$rcmail = rcmail::get_instance();
if ($this->drivers[$factor]) {
return $this->drivers[$factor];
}
$config = $rcmail->config->get('kolab_2fa_' . $method, array());
// use product name as "issuer""
if (empty($config['issuer'])) {
$config['issuer'] = $rcmail->config->get('product_name');
}
try {
// TODO: use external auth service if configured
$driver = \Kolab2FA\Driver\Base::factory($factor, $config);
// attach storage
$driver->storage = $this->get_storage();
if ($rcmail->user->ID) {
$driver->username = $rcmail->get_user_name();
}
$this->drivers[$factor] = $driver;
return $driver;
}
catch (Exception $e) {
$error = strval($e);
}
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => $error),
true, false);
return false;
}
/**
* Getter for a storage instance singleton
*/
public function get_storage($for = null)
{
if (!isset($this->storage) || (!empty($for) && $this->storage->username !== $for)) {
$rcmail = rcmail::get_instance();
try {
$this->storage = \Kolab2FA\Storage\Base::factory(
$rcmail->config->get('kolab_2fa_storage', 'roundcube'),
$rcmail->config->get('kolab_2fa_storage_config', array())
);
$this->storage->set_username($for);
$this->storage->set_logger(new \Kolab2FA\Log\RcubeLogger());
// set user properties from active session
if (!empty($_SESSION['kolab_dn'])) {
$this->storage->userdn = $_SESSION['kolab_dn'];
}
}
catch (Exception $e) {
$this->storage = false;
rcube::raise_error(array(
'code' => 600,
'type' => 'php',
'file' => __FILE__,
'line' => __LINE__,
'message' => $error),
true, false);
}
}
return $this->storage;
}
/**
* Handler for 'settings_actions' hook
*/
public function settings_actions($args)
{
// register as settings action
$args['actions'][] = array(
'action' => 'plugin.kolab-2fa',
'class' => 'twofactorauth',
'label' => 'settingslist',
'title' => 'settingstitle',
'domain' => 'kolab_2fa',
);
return $args;
}
/**
* Handler for settings/plugin.kolab-2fa requests
*/
public function settings_view()
{
$this->register_handler('plugin.settingsform', array($this, 'settings_form'));
$this->register_handler('plugin.settingslist', array($this, 'settings_list'));
$this->register_handler('plugin.factoradder', array($this, 'settings_factoradder'));
$this->register_handler('plugin.highsecuritydialog', array($this, 'settings_highsecuritydialog'));
$this->include_script('kolab2fa.js');
- $this->include_stylesheet($this->local_skin_path() . '/kolab2fa.css', true);
+ $this->include_stylesheet($this->local_skin_path() . '/kolab2fa.css');
if ($this->check_secure_mode()) {
$this->api->output->set_env('session_secured', $_SESSION['kolab_2fa_secure_mode']);
}
$this->api->output->add_label('save','cancel');
$this->api->output->set_pagetitle($this->gettext('settingstitle'));
$this->api->output->send('kolab_2fa.config');
}
/**
* Render the menu to add another authentication factor
*/
public function settings_factoradder($attrib)
{
$rcmail = rcmail::get_instance();
$attrib['id'] = 'kolab2fa-add';
$select = new html_select($attrib);
$select->add($this->gettext('addfactor') . '...', '');
foreach ((array)$rcmail->config->get('kolab_2fa_drivers', array()) as $method) {
$select->add($this->gettext($method), $method);
}
return $select->show();
}
/**
* Render a list of active factor this user has configured
*/
public function settings_list($attrib = array())
{
$attrib['id'] = 'kolab2fa-factors';
$table = new html_table(array('cols' => 3));
$table->add_header('name', $this->gettext('factor'));
$table->add_header('created', $this->gettext('created'));
$table->add_header('actions', '');
return $table->show($attrib);
}
/**
* Render the settings form template object
*/
public function settings_form($attrib = array())
{
$rcmail = rcmail::get_instance();
$storage = $this->get_storage($rcmail->get_user_name());
$factors = $storage ? (array)$storage->enumerate() : array();
$drivers = (array)$rcmail->config->get('kolab_2fa_drivers', array());
$env_methods = array();
foreach ($drivers as $j => $method) {
$out .= $this->settings_factor($method, $attrib);
$env_methods[$method] = array(
'name' => $this->gettext($method),
'active' => 0,
);
}
$me = $this;
$factors = array_combine(
$factors,
array_map(function($id) use ($me, &$env_methods) {
$props = array('id' => $id);
if ($driver = $me->get_driver($id)) {
$props += $this->format_props($driver->props());
$props['method'] = $driver->method;
$props['name'] = $me->gettext($driver->method);
$env_methods[$driver->method]['active']++;
}
return $props;
}, $factors)
);
$this->api->output->set_env('kolab_2fa_methods', $env_methods);
$this->api->output->set_env('kolab_2fa_factors', !empty($factors) ? $factors : null);
return html::div(array('id' => 'kolab2fapropform'), $out);
}
/**
* Render the settings UI for the given method/driver
*/
protected function settings_factor($method, $attrib)
{
$out = '';
$rcmail = rcmail::get_instance();
$attrib += array('class' => 'propform');
if ($driver = $this->get_driver($method)) {
$table = new html_table(array('cols' => 2, 'class' => $attrib['class']));
foreach ($driver->props() as $field => $prop) {
if (!$prop['editable']) {
continue;
}
switch ($prop['type']) {
case 'boolean':
case 'checkbox':
$input = new html_checkbox(array('value' => '1'));
break;
case 'enum':
case 'select':
$input = new html_select(array('disabled' => $prop['readonly']));
$input->add(array_map(array($this, 'gettext'), $prop['options']), $prop['options']);
break;
default:
$input = new html_inputfield(array('size' => $prop['size'] ?: 30, 'disabled' => !$prop['editable']));
}
$explain_label = $field . 'explain' . $method;
$explain_html = $rcmail->text_exists($explain_label, 'kolab_2fa') ? html::div('explain form-text', $this->gettext($explain_label)) : '';
$field_id = 'rcmk2fa' . $method . $field;
$table->add('title', html::label($field_id, $this->gettext($field)));
$table->add(null, $input->show('', array('id' => $field_id, 'name' => "_prop[$field]")) . $explain_html);
}
// add row for displaying the QR code
if (method_exists($driver, 'get_provisioning_uri')) {
$table->add('title', $this->gettext('qrcode'));
$table->add(null,
html::div('explain form-text', $this->gettext("qrcodeexplain$method"))
. html::tag('img', array('src' => 'data:image/gif;base64,R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7', 'class' => 'qrcode', 'rel' => $method))
);
// add row for testing the factor
$field_id = 'rcmk2faverify' . $method;
$table->add('title', html::label($field_id, $this->gettext('verifycode')));
$table->add(null,
html::tag('input', array('type' => 'text', 'name' => '_verify_code', 'id' => $field_id, 'class' => 'k2fa-verify', 'size' => 20, 'required' => true)) .
html::div('explain form-text', $this->gettext("verifycodeexplain$method"))
);
}
$input_id = new html_hiddenfield(array('name' => '_prop[id]', 'value' => ''));
$out .= html::tag('form', array(
'method' => 'post',
'action' => '#',
'id' => 'kolab2fa-prop-' . $method,
'style' => 'display:none',
'class' => 'propform',
),
html::tag('fieldset', array(),
html::tag('legend', array(), $this->gettext($method)) .
html::div('factorprop', $table->show()) .
$input_id->show()
)
);
}
return $out;
}
/**
* Render the high-security-dialog content
*/
public function settings_highsecuritydialog($attrib = array())
{
$attrib += array('id' => 'kolab2fa-highsecuritydialog');
$field_id = 'rcmk2facode';
$input = new html_inputfield(array('name' => '_code', 'id' => $field_id, 'class' => 'verifycode', 'size' => 20));
$label = html::label(array('for' => $field_id, 'class' => 'col-form-label col-sm-4'), '$name');
return html::div($attrib,
html::div('explain form-text', $this->gettext('highsecuritydialog'))
. html::div('propform row form-group', $label . html::div('col-sm-8', $input->show('')))
);
}
/**
* Handler for settings/plugin.kolab-2fa-save requests
*/
public function settings_save()
{
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
$data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
$rcmail = rcmail::get_instance();
$storage = $this->get_storage($rcmail->get_user_name());
$success = false;
$errors = 0;
$save_data = array();
if ($driver = $this->get_driver($method)) {
if ($data === false) {
if ($this->check_secure_mode()) {
// remove method from active factors and clear stored settings
$success = $driver->clear();
}
else {
$errors++;
}
}
else {
// verify the submitted code before saving
$verify_code = rcube_utils::get_input_value('_verify_code', rcube_utils::INPUT_POST);
$timestamp = intval(rcube_utils::get_input_value('_timestamp', rcube_utils::INPUT_POST));
if (!empty($verify_code)) {
if (!$driver->verify($verify_code, $timestamp)) {
$this->api->output->command('plugin.verify_response', array(
'id' => $driver->id,
'method' => $driver->method,
'success' => false,
'message' => str_replace('$method', $this->gettext($driver->method), $this->gettext('codeverificationfailed'))
));
$this->api->output->send();
}
}
foreach ($data as $prop => $value) {
if (!$driver->set($prop, $value)) {
$errors++;
}
}
$driver->set('active', true);
}
// commit changes to the user properties
if (!$errors) {
if ($success = $driver->commit()) {
$save_data = $data !== false ? $this->format_props($driver->props()) : array();
}
else {
$errors++;
}
}
}
if ($success) {
$this->api->output->show_message($data === false ? $this->gettext('factorremovesuccess') : $this->gettext('factorsavesuccess'), 'confirmation');
$this->api->output->command('plugin.save_success', array(
'method' => $method,
'active' => $data !== false,
'id' => $driver->id) + $save_data);
}
else if ($errors) {
$this->api->output->show_message($this->gettext('factorsaveerror'), 'error');
$this->api->output->command('plugin.reset_form', $method);
}
$this->api->output->send();
}
/**
* Handler for settings/plugin.kolab-2fa-data requests
*/
public function settings_data()
{
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
if ($driver = $this->get_driver($method)) {
$data = array('method' => $method, 'id' => $driver->id);
foreach ($driver->props(true) as $field => $prop) {
$data[$field] = $prop['text'] ?: $prop['value'];
}
// generate QR code for provisioning URI
if (method_exists($driver, 'get_provisioning_uri')) {
try {
$uri = $driver->get_provisioning_uri();
$qr = new Endroid\QrCode\QrCode();
$qr->setText($uri)
->setSize(240)
->setPadding(10)
->setErrorCorrection('high')
->setForegroundColor(array('r' => 0, 'g' => 0, 'b' => 0, 'a' => 0))
->setBackgroundColor(array('r' => 255, 'g' => 255, 'b' => 255, 'a' => 0));
$data['qrcode'] = base64_encode($qr->get());
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
}
}
$this->api->output->command('plugin.render_data', $data);
}
$this->api->output->send();
}
/**
* Handler for settings/plugin.kolab-2fa-verify requests
*/
public function settings_verify()
{
$method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
$timestamp = intval(rcube_utils::get_input_value('_timestamp', rcube_utils::INPUT_POST));
$success = false;
if ($driver = $this->get_driver($method)) {
$data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
if (is_array($data)) {
foreach ($data as $key => $value) {
if ($value !== '******') {
$driver->$key = $value;
}
}
}
$success = $driver->verify(rcube_utils::get_input_value('_code', rcube_utils::INPUT_POST), $timestamp);
$method = $driver->method;
}
// put session into high-security mode
if ($success && !empty($_POST['_session'])) {
$_SESSION['kolab_2fa_secure_mode'] = time();
}
$this->api->output->command('plugin.verify_response', array(
'method' => $method,
'success' => $success,
'message' => str_replace('$method', $this->gettext($method),
$this->gettext($success ? 'codeverificationpassed' : 'codeverificationfailed'))
));
$this->api->output->send();
}
/**
*
*/
protected function format_props($props)
{
$rcmail = rcmail::get_instance();
$values = array();
foreach ($props as $key => $prop) {
switch ($prop['type']) {
case 'datetime':
$value = $rcmail->format_date($prop['value']);
break;
default:
$value = $prop['value'];
}
$values[$key] = $value;
}
return $values;
}
/**
*
*/
protected function check_secure_mode()
{
$valid = ($_SESSION['kolab_2fa_secure_mode'] && $_SESSION['kolab_2fa_secure_mode'] > time() - 180);
return $valid;
}
}
\ No newline at end of file
diff --git a/plugins/kolab_activesync/kolab_activesync_ui.php b/plugins/kolab_activesync/kolab_activesync_ui.php
index 4959e4a9..636f97c6 100644
--- a/plugins/kolab_activesync/kolab_activesync_ui.php
+++ b/plugins/kolab_activesync/kolab_activesync_ui.php
@@ -1,299 +1,299 @@
<?php
/**
* ActiveSync configuration user interface builder
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011-2013, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class kolab_activesync_ui
{
private $rc;
private $plugin;
public $device = array();
const SETUP_URL = 'http://docs.kolab.org/client-configuration';
public function __construct($plugin)
{
$this->plugin = $plugin;
$this->rc = rcube::get_instance();
$skin_path = $this->plugin->local_skin_path() . '/';
$this->skin_path = 'plugins/kolab_activesync/' . $skin_path;
- $this->plugin->include_stylesheet($skin_path . 'config.css', true);
+ $this->plugin->include_stylesheet($skin_path . 'config.css');
}
public function device_list($attrib = array())
{
$attrib += array('id' => 'devices-list');
$devices = $this->plugin->list_devices();
$table = new html_table();
foreach ($devices as $id => $device) {
$name = $device['ALIAS'] ? $device['ALIAS'] : $id;
$table->add_row(array('id' => 'rcmrow' . $id));
$table->add(null, html::span('devicealias', rcube::Q($name))
. ' ' . html::span('devicetype secondary', rcube::Q($device['TYPE'])));
}
$this->rc->output->add_gui_object('devicelist', $attrib['id']);
$this->rc->output->set_env('devicecount', count($devices));
$this->rc->output->include_script('list.js');
return $table->show($attrib);
}
public function device_config_form($attrib = array())
{
$table = new html_table(array('cols' => 2));
$field_id = 'config-device-alias';
$input = new html_inputfield(array('name' => 'devicealias', 'id' => $field_id, 'size' => 40));
$table->add('title', html::label($field_id, $this->plugin->gettext('devicealias')));
$table->add(null, $input->show($this->device['ALIAS'] ? $this->device['ALIAS'] : $this->device['_id']));
// read-only device information
$info = $this->plugin->device_info($this->device['ID']);
if (!empty($info)) {
foreach ($info as $key => $value) {
if ($value) {
$table->add('title', html::label(null, rcube::Q($this->plugin->gettext($key))));
$table->add(null, rcube::Q($value));
}
}
}
if ($attrib['form']) {
$this->rc->output->add_gui_object('editform', $attrib['form']);
}
return $table->show($attrib);
}
public function folder_subscriptions($attrib = array())
{
if (!$attrib['id']) {
$attrib['id'] = 'foldersubscriptions';
}
// group folders by type (show only known types)
$folder_groups = array('mail' => array(), 'contact' => array(), 'event' => array(), 'task' => array(), 'note' => array());
$folder_types = kolab_storage::folders_typedata();
$use_fieldsets = rcube_utils::get_boolean($attrib['use-fieldsets']);
$imei = $this->device['_id'];
$subscribed = array();
if ($imei) {
$folder_meta = $this->plugin->folder_meta();
}
foreach ($this->plugin->list_folders() as $folder) {
if ($folder_types[$folder]) {
list($type, ) = explode('.', $folder_types[$folder]);
}
else {
$type = 'mail';
}
if (is_array($folder_groups[$type])) {
$folder_groups[$type][] = $folder;
if (!empty($folder_meta) && ($meta = $folder_meta[$folder])
&& $meta['FOLDER'] && $meta['FOLDER'][$imei]['S']
) {
$subscribed[$folder] = intval($meta['FOLDER'][$imei]['S']);
}
}
}
// build block for every folder type
foreach ($folder_groups as $type => $group) {
if (empty($group)) {
continue;
}
$attrib['type'] = $type;
$table = $this->folder_subscriptions_block($group, $attrib, $subscribed);
$label = $this->plugin->gettext($type);
if ($use_fieldsets) {
$html .= html::tag('fieldset', 'subscriptionblock', html::tag('legend', $type, $label) . $table);
}
else {
$html .= html::div('subscriptionblock', html::tag('h3', $type, $label) . $table);
}
}
$this->rc->output->add_gui_object('subscriptionslist', $attrib['id']);
return html::div($attrib, $html);
}
public function folder_subscriptions_block($a_folders, $attrib, $subscribed)
{
$alarms = ($attrib['type'] == 'event' || $attrib['type'] == 'task');
$table = new html_table(array('cellspacing' => 0));
$table->add_header(array(
'class' => 'subscription checkbox-cell',
'title' => $this->plugin->gettext('synchronize'),
'tabindex' => 0
),
$attrib['syncicon'] ? html::img(array('src' => $this->skin_path . $attrib['syncicon'])) :
$this->plugin->gettext('synchronize')
);
if ($alarms) {
$table->add_header(array(
'class' => 'alarm checkbox-cell',
'title' => $this->plugin->gettext('withalarms'),
'tabindex' => 0
),
$attrib['alarmicon'] ? html::img(array('src' => $this->skin_path . $attrib['alarmicon'])) :
$this->plugin->gettext('withalarms')
);
}
$table->add_header('foldername', $this->plugin->gettext('folder'));
$checkbox_sync = new html_checkbox(array('name' => 'subscribed[]', 'class' => 'subscription'));
$checkbox_alarm = new html_checkbox(array('name' => 'alarm[]', 'class' => 'alarm'));
$names = array();
foreach ($a_folders as $folder) {
$foldername = $origname = preg_replace('/^INBOX »\s+/', '', kolab_storage::object_prettyname($folder));
// find folder prefix to truncate (the same code as in kolab_addressbook plugin)
for ($i = count($names)-1; $i >= 0; $i--) {
if (strpos($foldername, $names[$i].' » ') === 0) {
$length = strlen($names[$i].' » ');
$prefix = substr($foldername, 0, $length);
$count = count(explode(' » ', $prefix));
$foldername = str_repeat(' ', $count-1) . '» ' . substr($foldername, $length);
break;
}
}
$folder_id = 'rcmf' . rcube_utils::html_identifier($folder);
$names[] = $origname;
$classes = array('mailbox');
if ($folder_class = $this->rc->folder_classname($folder)) {
$foldername = html::quote($this->rc->gettext($folder_class));
$classes[] = $folder_class;
}
$table->add_row();
$table->add('subscription checkbox-cell', $checkbox_sync->show(
!empty($subscribed[$folder]) ? $folder : null,
array('value' => $folder, 'id' => $folder_id)));
if ($alarms) {
$table->add('alarm checkbox-cell', $checkbox_alarm->show(
intval($subscribed[$folder]) > 1 ? $folder : null,
array('value' => $folder, 'id' => $folder_id.'_alarm')));
}
$table->add(join(' ', $classes), html::label($folder_id, $foldername));
}
return $table->show();
}
public function folder_options_table($folder_name, $devices, $type)
{
$alarms = $type == 'event' || $type == 'task';
$meta = $this->plugin->folder_meta();
$folder_data = (array) ($meta[$folder_name] ? $meta[$folder_name]['FOLDER'] : null);
$table = new html_table(array('cellspacing' => 0, 'id' => 'folder-sync-options', 'class' => 'records-table'));
// table header
$table->add_header(array('class' => 'device'), $this->plugin->gettext('devicealias'));
$table->add_header(array('class' => 'subscription'), $this->plugin->gettext('synchronize'));
if ($alarms) {
$table->add_header(array('class' => 'alarm'), $this->plugin->gettext('withalarms'));
}
// table records
foreach ($devices as $id => $device) {
$info = $this->plugin->device_info($device['ID']);
$name = $id;
$title = '';
$checkbox = new html_checkbox(array('name' => "_subscriptions[$id]", 'value' => 1,
'onchange' => 'return activesync_object.update_sync_data(this)'));
if (!empty($info)) {
$_name = trim($info['friendlyname'] . ' ' . $info['os']);
$title = $info['useragent'];
if ($_name) {
$name .= " ($_name)";
}
}
$table->add_row();
$table->add(array('class' => 'device', 'title' => $title), $name);
$table->add('subscription checkbox-cell', $checkbox->show(!empty($folder_data[$id]['S']) ? 1 : 0));
if ($alarms) {
$checkbox_alarm = new html_checkbox(array('name' => "_alarms[$id]", 'value' => 1,
'onchange' => 'return activesync_object.update_sync_data(this)'));
$table->add('alarm checkbox-cell', $checkbox_alarm->show($folder_data[$id]['S'] > 1 ? 1 : 0));
}
}
return $table->show();
}
/**
* Displays initial page (when no devices are registered)
*/
function init_message()
{
$this->plugin->load_config();
$this->rc->output->add_handlers(array(
'initmessage' => array($this, 'init_message_content')
));
$this->rc->output->send('kolab_activesync.configempty');
}
/**
* Handler for initmessage template object
*/
function init_message_content()
{
$url = $this->rc->config->get('activesync_setup_url', self::SETUP_URL);
$vars = array('url' => $url);
$msg = $this->plugin->gettext(array('name' => 'nodevices', 'vars' => $vars));
return $msg;
}
}
diff --git a/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php
index 0454e8b0..726b66cf 100644
--- a/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php
+++ b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php
@@ -1,363 +1,363 @@
<?php
/**
* Kolab address book UI
*
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class kolab_addressbook_ui
{
private $plugin;
private $rc;
/**
* Class constructor
*
* @param kolab_addressbook $plugin Plugin object
*/
public function __construct($plugin)
{
$this->rc = rcube::get_instance();
$this->plugin = $plugin;
$this->init_ui();
}
/**
* Adds folders management functionality to Addressbook UI
*/
private function init_ui()
{
if (!empty($this->rc->action) && !preg_match('/^plugin\.book/', $this->rc->action) && $this->rc->action != 'show') {
return;
}
// Include script
$this->plugin->include_script('kolab_addressbook.js');
if (empty($this->rc->action)) {
// Include stylesheet (for directorylist)
- $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/kolab_addressbook.css', true);
+ $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/kolab_addressbook.css');
// include kolab folderlist widget if available
if (in_array('libkolab', $this->plugin->api->loaded_plugins())) {
$this->plugin->api->include_script('libkolab/js/folderlist.js');
}
$this->rc->output->add_footer($this->rc->output->parse('kolab_addressbook.search_addon', false, false));
// Add actions on address books
$options = array('book-create', 'book-edit', 'book-delete', 'book-remove');
$idx = 0;
if ($dav_url = $this->rc->config->get('kolab_addressbook_carddav_url')) {
$options[] = 'book-showurl';
$this->rc->output->set_env('kolab_addressbook_carddav_url', true);
// set CardDAV URI for specified ldap addressbook
if ($ldap_abook = $this->rc->config->get('kolab_addressbook_carddav_ldap')) {
$dav_ldap_url = strtr($dav_url, array(
'%h' => $_SERVER['HTTP_HOST'],
'%u' => urlencode($this->rc->get_user_name()),
'%i' => 'ldap-directory',
'%n' => '',
));
$this->rc->output->set_env('kolab_addressbook_carddav_ldap', $ldap_abook);
$this->rc->output->set_env('kolab_addressbook_carddav_ldap_url', $dav_ldap_url);
}
}
foreach ($options as $command) {
$content = html::tag('li', $idx ? null : array('class' => 'separator_above'),
$this->plugin->api->output->button(array(
'label' => 'kolab_addressbook.'.str_replace('-', '', $command),
'domain' => $this->ID,
'class' => str_replace('-', ' ', $command) . ' disabled',
'classact' => str_replace('-', ' ', $command) . ' active',
'command' => $command,
'type' => 'link'
)));
$this->plugin->api->add_content($content, 'groupoptions');
$idx++;
}
// Link to Settings/Folders
$content = html::tag('li', array('class' => 'separator_above'),
$this->plugin->api->output->button(array(
'label' => 'managefolders',
'type' => 'link',
'class' => 'folders disabled',
'classact' => 'folders active',
'command' => 'folders',
'task' => 'settings',
)));
$this->plugin->api->add_content($content, 'groupoptions');
$this->rc->output->add_label('kolab_addressbook.bookdeleteconfirm',
'kolab_addressbook.bookdeleting', 'kolab_addressbook.bookshowurl',
'kolab_addressbook.carddavurldescription',
'kolab_addressbook.bookedit',
'kolab_addressbook.bookdelete',
'kolab_addressbook.bookshowurl',
'kolab_addressbook.findaddressbooks',
'kolab_addressbook.searchterms',
'kolab_addressbook.foldersearchform',
'kolab_addressbook.listsearchresults',
'kolab_addressbook.nraddressbooksfound',
'kolab_addressbook.noaddressbooksfound',
'kolab_addressbook.foldersubscribe',
'resetsearch'
);
if ($this->plugin->bonnie_api) {
$this->rc->output->set_env('kolab_audit_trail', true);
$this->plugin->api->include_script('libkolab/js/audittrail.js');
$this->rc->output->add_label(
'kolab_addressbook.showhistory',
'kolab_addressbook.objectchangelog',
'kolab_addressbook.objectdiff',
'kolab_addressbook.objectdiffnotavailable',
'kolab_addressbook.objectchangelognotavailable',
'kolab_addressbook.revisionrestoreconfirm'
);
$this->plugin->add_hook('render_page', array($this, 'render_audittrail_page'));
$this->plugin->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table'));
}
}
// include stylesheet for audit trail
else if ($this->rc->action == 'show' && $this->plugin->bonnie_api) {
$this->plugin->include_stylesheet($this->plugin->local_skin_path().'/kolab_addressbook.css', true);
$this->rc->output->add_label('kolab_addressbook.showhistory');
}
// book create/edit form
else {
$this->rc->output->add_label('kolab_addressbook.nobooknamewarning',
'kolab_addressbook.booksaving');
}
}
/**
* Handler for address book create/edit action
*/
public function book_edit()
{
$this->rc->output->add_handler('bookdetails', array($this, 'book_form'));
$this->rc->output->send('kolab_addressbook.bookedit');
}
/**
* Handler for 'bookdetails' object returning form content for book create/edit
*
* @param array $attr Object attributes
*
* @return string HTML output
*/
public function book_form($attrib)
{
$action = trim(rcube_utils::get_input_value('_act', rcube_utils::INPUT_GPC));
$folder = trim(rcube_utils::get_input_value('_source', rcube_utils::INPUT_GPC, true)); // UTF8
$hidden_fields[] = array('name' => '_source', 'value' => $folder);
$folder = rcube_charset::convert($folder, RCUBE_CHARSET, 'UTF7-IMAP');
$storage = $this->rc->get_storage();
$delim = $storage->get_hierarchy_delimiter();
if ($this->rc->action == 'plugin.book-save') {
// save error
$name = trim(rcube_utils::get_input_value('_name', rcube_utils::INPUT_GPC, true)); // UTF8
$old = trim(rcube_utils::get_input_value('_oldname', rcube_utils::INPUT_GPC, true)); // UTF7-IMAP
$path_imap = trim(rcube_utils::get_input_value('_parent', rcube_utils::INPUT_GPC, true)); // UTF7-IMAP
$hidden_fields[] = array('name' => '_oldname', 'value' => $old);
$folder = $old;
}
else if ($action == 'edit') {
$path_imap = explode($delim, $folder);
$name = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP');
$path_imap = implode($path_imap, $delim);
}
else { // create
$path_imap = $folder;
$name = '';
$folder = '';
}
// Store old name, get folder options
if (strlen($folder)) {
$hidden_fields[] = array('name' => '_oldname', 'value' => $folder);
$options = $storage->folder_info($folder);
}
$form = array();
// General tab
$form['props'] = array(
'name' => $this->rc->gettext('properties'),
);
if (!empty($options) && ($options['norename'] || $options['protected'])) {
$foldername = rcube::Q(str_replace($delim, ' » ', kolab_storage::object_name($folder)));
}
else {
$foldername = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30));
$foldername = $foldername->show($name);
}
$form['props']['fieldsets']['location'] = array(
'name' => $this->rc->gettext('location'),
'content' => array(
'name' => array(
'label' => $this->plugin->gettext('bookname'),
'value' => $foldername,
),
),
);
if (!empty($options) && ($options['norename'] || $options['protected'])) {
// prevent user from moving folder
$hidden_fields[] = array('name' => '_parent', 'value' => $path_imap);
}
else {
$prop = array('name' => '_parent', 'id' => '_parent');
$select = kolab_storage::folder_selector('contact', $prop, $folder);
$form['props']['fieldsets']['location']['content']['parent'] = array(
'label' => $this->plugin->gettext('parentbook'),
'value' => $select->show(strlen($folder) ? $path_imap : ''),
);
}
// Allow plugins to modify address book form content (e.g. with ACL form)
$plugin = $this->rc->plugins->exec_hook('addressbook_form',
array('form' => $form, 'options' => $options, 'name' => $folder));
$form = $plugin['form'];
// Set form tags and hidden fields
list($form_start, $form_end) = $this->get_form_tags($attrib, 'plugin.book-save', null, $hidden_fields);
unset($attrib['form']);
// return the complete edit form as table
$out = "$form_start\n";
// Create form output
foreach ($form as $idx => $tab) {
if (!empty($tab['fieldsets']) && is_array($tab['fieldsets'])) {
$content = '';
foreach ($tab['fieldsets'] as $fieldset) {
$subcontent = $this->get_form_part($fieldset);
if ($subcontent) {
$content .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($fieldset['name'])) . $subcontent) ."\n";
}
}
}
else {
$content = $this->get_form_part($tab);
}
if ($content) {
if ($idx != 'props') {
$out .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($tab['name'])) . $content) ."\n";
}
else {
$out .= $content ."\n";
}
}
}
$out .= "\n$form_end";
return $out;
}
/**
*
*/
public function render_audittrail_page($p)
{
// append audit trail UI elements to contact page
if ($p['template'] === 'addressbook' && !$p['kolab-audittrail']) {
$this->rc->output->add_footer($this->rc->output->parse('kolab_addressbook.audittrail', false, false));
$p['kolab-audittrail'] = true;
}
return $p;
}
private function get_form_part($form)
{
$content = '';
if (is_array($form['content']) && !empty($form['content'])) {
$table = new html_table(array('cols' => 2, 'class' => 'propform'));
foreach ($form['content'] as $col => $colprop) {
$colprop['id'] = '_'.$col;
$label = !empty($colprop['label']) ? $colprop['label'] : $this->rc->gettext($col);
$table->add('title', sprintf('<label for="%s">%s</label>', $colprop['id'], rcube::Q($label)));
$table->add(null, $colprop['value']);
}
$content = $table->show();
}
else {
$content = $form['content'];
}
return $content;
}
private function get_form_tags($attrib, $action, $id = null, $hidden = null)
{
$form_start = $form_end = '';
$request_key = $action . (isset($id) ? '.'.$id : '');
$form_start = $this->rc->output->request_form(array(
'name' => 'form',
'method' => 'post',
'task' => $this->rc->task,
'action' => $action,
'request' => $request_key,
'noclose' => true,
) + $attrib);
if (is_array($hidden)) {
foreach ($hidden as $field) {
$hiddenfield = new html_hiddenfield($field);
$form_start .= $hiddenfield->show();
}
}
$form_end = !strlen($attrib['form']) ? '</form>' : '';
$EDIT_FORM = !empty($attrib['form']) ? $attrib['form'] : 'form';
$this->rc->output->add_gui_object('editform', $EDIT_FORM);
return array($form_start, $form_end);
}
}
diff --git a/plugins/kolab_delegation/kolab_delegation.php b/plugins/kolab_delegation/kolab_delegation.php
index 306564aa..0752d0cf 100644
--- a/plugins/kolab_delegation/kolab_delegation.php
+++ b/plugins/kolab_delegation/kolab_delegation.php
@@ -1,576 +1,576 @@
<?php
/**
* Delegation configuration utility for Kolab accounts
*
* @version @package_version@
* @author Aleksander Machniak <machniak@kolabsys.com>
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class kolab_delegation extends rcube_plugin
{
public $task = 'login|mail|settings|calendar|tasks';
private $rc;
private $engine;
/**
* Plugin initialization.
*/
public function init()
{
$this->rc = rcube::get_instance();
$this->require_plugin('libkolab');
$this->require_plugin('kolab_auth');
// on-login delegation initialization
$this->add_hook('login_after', array($this, 'login_hook'));
// on-check-recent delegation support
$this->add_hook('check_recent', array($this, 'check_recent_hook'));
// on-message-send delegation support
$this->add_hook('message_before_send', array($this, 'message_before_send'));
// delegation support in Calendar and Tasklist plugins
$this->add_hook('message_load', array($this, 'message_load'));
$this->add_hook('calendar_user_emails', array($this, 'calendar_user_emails'));
$this->add_hook('calendar_list_filter', array($this, 'calendar_list_filter'));
$this->add_hook('calendar_load_itip', array($this, 'calendar_load_itip'));
$this->add_hook('tasklist_list_filter', array($this, 'tasklist_list_filter'));
// delegation support in kolab_auth plugin
$this->add_hook('kolab_auth_emails', array($this, 'kolab_auth_emails'));
if ($this->rc->task == 'settings') {
// delegation management interface
$this->register_action('plugin.delegation', array($this, 'controller_ui'));
$this->register_action('plugin.delegation-delete', array($this, 'controller_action'));
$this->register_action('plugin.delegation-save', array($this, 'controller_action'));
$this->register_action('plugin.delegation-autocomplete', array($this, 'controller_action'));
$this->add_hook('settings_actions', array($this, 'settings_actions'));
if ($this->rc->output->type == 'html'
&& ($this->rc->action == 'plugin.delegation' || empty($_REQUEST['_framed']))
) {
$this->add_texts('localization/', array('deleteconfirm', 'savingdata', 'yes', 'no'));
if ($this->rc->action == 'plugin.delegation') {
$this->include_script('kolab_delegation.js');
}
$this->skin_path = $this->local_skin_path();
- $this->include_stylesheet($this->skin_path . '/style.css', true);
+ $this->include_stylesheet($this->skin_path . '/style.css');
}
}
// Calendar/Tasklist plugin UI bindings
else if (($this->rc->task == 'calendar' || $this->rc->task == 'tasks')
&& empty($_REQUEST['_framed'])
) {
if ($this->rc->output->type == 'html') {
$this->calendar_ui();
}
}
}
/**
* Adds Delegation section in Settings
*/
function settings_actions($args)
{
$args['actions'][] = array(
'action' => 'plugin.delegation',
'class' => 'delegation',
'label' => 'tabtitle',
'domain' => 'kolab_delegation',
'title' => 'delegationtitle',
);
return $args;
}
/**
* Engine object getter
*/
private function engine()
{
if (!$this->engine) {
require_once $this->home . '/kolab_delegation_engine.php';
$this->load_config();
$this->engine = new kolab_delegation_engine();
}
return $this->engine;
}
/**
* On-login action
*/
public function login_hook($args)
{
// Manage (create) identities for delegator's email addresses
// and subscribe to delegator's folders. Also remove identities
// after delegation is removed
$engine = $this->engine();
$engine->delegation_init();
return $args;
}
/**
* Check-recent action
*/
public function check_recent_hook($args)
{
// Checking for new messages shall be extended to Inbox folders of all
// delegators if 'check_all_folders' is set to false.
if ($this->rc->task != 'mail') {
return $args;
}
if (!empty($args['all'])) {
return $args;
}
if (empty($_SESSION['delegators'])) {
return $args;
}
$storage = $this->rc->get_storage();
$other_ns = $storage->get_namespace('other');
$folders = $storage->list_folders_subscribed('', '*', 'mail');
foreach (array_keys($_SESSION['delegators']) as $uid) {
foreach ($other_ns as $ns) {
$folder = $ns[0] . $uid;
if (in_array($folder, $folders) && !in_array($folder, $args['folders'])) {
$args['folders'][] = $folder;
}
}
}
return $args;
}
/**
* Mail send action
*/
public function message_before_send($args)
{
// Checking headers of email being send, we'll add
// Sender: header if mail is send on behalf of someone else
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$engine->delegator_delivery_filter($args);
}
return $args;
}
/**
* E-mail message loading action
*/
public function message_load($args)
{
// This is a place where we detect delegate context
// So we can handle event invitations on behalf of delegator
// @TODO: should we do this only in delegators' folders?
// skip invalid messages or Kolab objects (for better performance)
if (empty($args['object']->headers) || $args['object']->headers->get('x-kolab-type', false)) {
return $args;
}
$engine = $this->engine();
$context = $engine->delegator_context_from_message($args['object']);
if ($context) {
$this->rc->output->set_env('delegator_context', $context);
$this->include_script('kolab_delegation.js');
}
return $args;
}
/**
* calendar::get_user_emails() handler
*/
public function calendar_user_emails($args)
{
// In delegator context we'll use delegator's addresses
// instead of current user addresses
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$engine->delegator_emails_filter($args);
}
return $args;
}
/**
* calendar_driver::list_calendars() handler
*/
public function calendar_list_filter($args)
{
// In delegator context we'll use delegator's folders
// instead of current user folders
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$engine->delegator_folder_filter($args, 'calendars');
}
return $args;
}
/**
* tasklist_driver::get_lists() handler
*/
public function tasklist_list_filter($args)
{
// In delegator context we'll use delegator's folders
// instead of current user folders
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$engine->delegator_folder_filter($args, 'tasklists');
}
return $args;
}
/**
* calendar::load_itip() handler
*/
public function calendar_load_itip($args)
{
// In delegator context we'll use delegator's address/name
// for invitation responses
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$engine->delegator_identity_filter($args);
}
return $args;
}
/**
* Delegation support in Calendar/Tasks plugin UI
*/
public function calendar_ui()
{
// Initialize handling of delegators' identities in event form
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$this->rc->output->set_env('namespace', $engine->namespace_js());
$this->rc->output->set_env('delegators', $engine->list_delegators_js());
$this->include_script('kolab_delegation.js');
}
}
/**
* Delegation support in kolab_auth plugin
*/
public function kolab_auth_emails($args)
{
// Add delegators addresses to address selector in user identity form
if (!empty($_SESSION['delegators'])) {
// @TODO: Consider not adding all delegator addresses to the list.
// Instead add only address of currently edited identity
foreach ($_SESSION['delegators'] as $emails) {
$args['emails'] = array_merge($args['emails'], $emails);
}
$args['emails'] = array_unique($args['emails']);
sort($args['emails']);
}
return $args;
}
/**
* Delegation UI handler
*/
public function controller_ui()
{
// main interface (delegates list)
if (empty($_REQUEST['_framed'])) {
$this->register_handler('plugin.delegatelist', array($this, 'delegate_list'));
$this->rc->output->include_script('list.js');
$this->rc->output->send('kolab_delegation.settings');
}
// delegate frame
else {
$this->register_handler('plugin.delegateform', array($this, 'delegate_form'));
$this->register_handler('plugin.delegatefolders', array($this, 'delegate_folders'));
$this->rc->output->set_env('autocomplete_max', (int)$this->rc->config->get('autocomplete_max', 15));
$this->rc->output->set_env('autocomplete_min_length', $this->rc->config->get('autocomplete_min_length'));
$this->rc->output->add_label('autocompletechars', 'autocompletemore');
$this->rc->output->send('kolab_delegation.editform');
}
}
/**
* Delegation action handler
*/
public function controller_action()
{
$this->add_texts('localization/');
$engine = $this->engine();
// Delegate delete
if ($this->rc->action == 'plugin.delegation-delete') {
$id = rcube_utils::get_input_value('id', rcube_utils::INPUT_GPC);
$error = $engine->delegate_delete($id, (bool) rcube_utils::get_input_value('acl', rcube_utils::INPUT_GPC));
if (!$error) {
$this->rc->output->show_message($this->gettext('deletesuccess'), 'confirmation');
$this->rc->output->command('plugin.delegate_save_complete', array('deleted' => $id));
}
else {
$this->rc->output->show_message($this->gettext($error), 'error');
}
}
// Delegate add/update
else if ($this->rc->action == 'plugin.delegation-save') {
$id = rcube_utils::get_input_value('id', rcube_utils::INPUT_GPC);
$acl = rcube_utils::get_input_value('folders', rcube_utils::INPUT_GPC);
// update
if ($id) {
$delegate = $engine->delegate_get($id);
$error = $engine->delegate_acl_update($delegate['uid'], $acl);
if (!$error) {
$this->rc->output->show_message($this->gettext('updatesuccess'), 'confirmation');
$this->rc->output->command('plugin.delegate_save_complete', array('updated' => $id));
}
else {
$this->rc->output->show_message($this->gettext($error), 'error');
}
}
// new
else {
$login = rcube_utils::get_input_value('newid', rcube_utils::INPUT_GPC);
$delegate = $engine->delegate_get_by_name($login);
$error = $engine->delegate_add($delegate, $acl);
if (!$error) {
$this->rc->output->show_message($this->gettext('createsuccess'), 'confirmation');
$this->rc->output->command('plugin.delegate_save_complete', array(
'created' => $delegate['ID'],
'name' => $delegate['name'],
));
}
else {
$this->rc->output->show_message($this->gettext($error), 'error');
}
}
}
// Delegate autocompletion
else if ($this->rc->action == 'plugin.delegation-autocomplete') {
$search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true);
$reqid = rcube_utils::get_input_value('_reqid', rcube_utils::INPUT_GPC);
$users = $engine->list_users($search);
$this->rc->output->command('ksearch_query_results', $users, $search, $reqid);
}
$this->rc->output->send();
}
/**
* Template object of delegates list
*/
public function delegate_list($attrib = array())
{
$attrib += array('id' => 'delegate-list');
$engine = $this->engine();
$list = $engine->list_delegates();
$table = new html_table();
// sort delegates list
asort($list, SORT_LOCALE_STRING);
foreach ($list as $id => $delegate) {
$table->add_row(array('id' => 'rcmrow' . $id));
$table->add(null, rcube::Q($delegate));
}
$this->rc->output->add_gui_object('delegatelist', $attrib['id']);
$this->rc->output->set_env('delegatecount', count($list));
return $table->show($attrib);
}
/**
* Template object of delegate form
*/
public function delegate_form($attrib = array())
{
$engine = $this->engine();
$table = new html_table(array('cols' => 2));
$id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
$field_id = 'delegate';
if ($id) {
$delegate = $engine->delegate_get($id);
}
if ($delegate) {
$input = new html_hiddenfield(array('name' => $field_id, 'id' => $field_id, 'size' => 40));
$input = rcube::Q($delegate['name']) . $input->show($id);
$this->rc->output->set_env('active_delegate', $id);
$this->rc->output->command('parent.enable_command','delegate-delete', true);
}
else {
$input = new html_inputfield(array('name' => $field_id, 'id' => $field_id, 'size' => 40));
$input = $input->show();
}
$table->add('title', html::label($field_id, $this->gettext('delegate')));
$table->add(null, $input);
if ($attrib['form']) {
$this->rc->output->add_gui_object('editform', $attrib['form']);
}
return $table->show($attrib);
}
/**
* Template object of folders list
*/
public function delegate_folders($attrib = array())
{
if (!$attrib['id']) {
$attrib['id'] = 'delegatefolders';
}
$engine = $this->engine();
$id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
if ($id) {
$delegate = $engine->delegate_get($id);
}
$folder_data = $engine->list_folders($delegate['uid']);
$use_fieldsets = rcube_utils::get_boolean($attrib['use-fieldsets']);
$rights = array();
$folder_groups = array();
foreach ($folder_data as $folder_name => $folder) {
$folder_groups[$folder['type']][] = $folder_name;
$rights[$folder_name] = $folder['rights'];
}
// build block for every folder type
foreach ($folder_groups as $type => $group) {
if (empty($group)) {
continue;
}
$attrib['type'] = $type;
$table = $this->delegate_folders_block($group, $attrib, $rights);
$label = $this->gettext($type);
if ($use_fieldsets) {
$html .= html::tag('fieldset', 'foldersblock', html::tag('legend', $type, $label) . $table);
}
else {
$html .= html::div('foldersblock', html::tag('h3', $type, $label) . $table);
}
}
$this->rc->output->add_gui_object('folderslist', $attrib['id']);
return html::div($attrib, $html);
}
/**
* List of folders in specified group
*/
private function delegate_folders_block($a_folders, $attrib, $rights)
{
$path = 'plugins/kolab_delegation/' . $this->skin_path . '/';
$read_ico = $attrib['readicon'] ? html::img(array('src' => $path . $attrib['readicon'], 'title' => $this->gettext('read'))) : '';
$write_ico = $attrib['writeicon'] ? html::img(array('src' => $path . $attrib['writeicon'], 'title' => $this->gettext('write'))) : '';
$table = new html_table(array('cellspacing' => 0));
$table->add_header(array('class' => 'read checkbox-cell', 'title' => $this->gettext('read'), 'tabindex' => 0), $read_ico);
$table->add_header(array('class' => 'write checkbox-cell', 'title' => $this->gettext('write'), 'tabindex' => 0), $write_ico);
$table->add_header('foldername', $this->rc->gettext('folder'));
$checkbox_read = new html_checkbox(array('name' => 'read[]', 'class' => 'read'));
$checkbox_write = new html_checkbox(array('name' => 'write[]', 'class' => 'write'));
$names = array();
foreach ($a_folders as $folder) {
$foldername = $origname = preg_replace('/^INBOX »\s+/', '', kolab_storage::object_prettyname($folder));
// find folder prefix to truncate (the same code as in kolab_addressbook plugin)
for ($i = count($names)-1; $i >= 0; $i--) {
if (strpos($foldername, $names[$i].' » ') === 0) {
$length = strlen($names[$i].' » ');
$prefix = substr($foldername, 0, $length);
$count = count(explode(' » ', $prefix));
$foldername = str_repeat(' ', $count-1) . '» ' . substr($foldername, $length);
break;
}
}
$folder_id = 'rcmf' . rcube_utils::html_identifier($folder);
$names[] = $origname;
$classes = array('mailbox');
if ($folder_class = $this->rc->folder_classname($folder)) {
$foldername = html::quote($this->rc->gettext($folder_class));
$classes[] = $folder_class;
}
$table->add_row();
$table->add('read checkbox-cell', $checkbox_read->show(
$rights[$folder] >= kolab_delegation_engine::ACL_READ ? $folder : null,
array('value' => $folder)));
$table->add('write checkbox-cell', $checkbox_write->show(
$rights[$folder] >= kolab_delegation_engine::ACL_WRITE ? $folder : null,
array('value' => $folder, 'id' => $folder_id)));
$table->add(join(' ', $classes), html::label($folder_id, $foldername));
}
return $table->show();
}
}
diff --git a/plugins/libkolab/libkolab.php b/plugins/libkolab/libkolab.php
index a3152915..cee2df9c 100644
--- a/plugins/libkolab/libkolab.php
+++ b/plugins/libkolab/libkolab.php
@@ -1,345 +1,345 @@
<?php
/**
* Kolab core library
*
* Plugin to setup a basic environment for the interaction with a Kolab server.
* Other Kolab-related plugins will depend on it and can use the library classes
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012-2015, Kolab Systems AG <contact@kolabsys.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class libkolab extends rcube_plugin
{
static $http_requests = array();
static $bonnie_api = false;
/**
* Required startup method of a Roundcube plugin
*/
public function init()
{
// load local config
$this->load_config();
// extend include path to load bundled lib classes
$include_path = $this->home . '/lib' . PATH_SEPARATOR . ini_get('include_path');
set_include_path($include_path);
$this->add_hook('storage_init', array($this, 'storage_init'));
$this->add_hook('storage_connect', array($this, 'storage_connect'));
$this->add_hook('user_delete', array('kolab_storage', 'delete_user_folders'));
$rcmail = rcube::get_instance();
try {
kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT'));
}
catch (Exception $e) {
rcube::raise_error($e, true);
kolab_format::$timezone = new DateTimeZone('GMT');
}
$this->add_texts('localization/', false);
if ($rcmail->output->type == 'html') {
- $this->include_stylesheet($this->local_skin_path() . '/libkolab.css', true);
+ $this->include_stylesheet($this->local_skin_path() . '/libkolab.css');
}
// embed scripts and templates for email message audit trail
if ($rcmail->task == 'mail' && self::get_bonnie_api()) {
if ($rcmail->output->type == 'html') {
$this->add_hook('render_page', array($this, 'bonnie_render_page'));
$this->include_script('js/audittrail.js');
// add 'Show history' item to message menu
$this->api->add_content(html::tag('li', null,
$this->api->output->button(array(
'command' => 'kolab-mail-history',
'label' => 'libkolab.showhistory',
'type' => 'link',
'classact' => 'icon history active',
'class' => 'icon history',
'innerclass' => 'icon history',
))),
'messagemenu');
}
$this->register_action('plugin.message-changelog', array($this, 'message_changelog'));
}
}
/**
* Hook into IMAP FETCH HEADER.FIELDS command and request Kolab-specific headers
*/
function storage_init($p)
{
$p['fetch_headers'] = trim($p['fetch_headers'] .' X-KOLAB-TYPE X-KOLAB-MIME-VERSION MESSAGE-ID');
return $p;
}
/**
* Hook into IMAP connection to replace client identity
*/
function storage_connect($p)
{
$client_name = 'Roundcube/Kolab';
if (empty($p['ident'])) {
$p['ident'] = array(
'name' => $client_name,
'version' => RCUBE_VERSION,
/*
'php' => PHP_VERSION,
'os' => PHP_OS,
'command' => $_SERVER['REQUEST_URI'],
*/
);
}
else {
$p['ident']['name'] = $client_name;
}
return $p;
}
/**
* Getter for a singleton instance of the Bonnie API
*
* @return mixed kolab_bonnie_api instance if configured, false otherwise
*/
public static function get_bonnie_api()
{
// get configuration for the Bonnie API
if (!self::$bonnie_api && ($bonnie_config = rcube::get_instance()->config->get('kolab_bonnie_api', false))) {
self::$bonnie_api = new kolab_bonnie_api($bonnie_config);
}
return self::$bonnie_api;
}
/**
* Hook to append the message history dialog template to the mail view
*/
function bonnie_render_page($p)
{
if (($p['template'] === 'mail' || $p['template'] === 'message') && !$p['kolab-audittrail']) {
// append a template for the audit trail dialog
$this->api->output->add_footer(
html::div(array('id' => 'mailmessagehistory', 'class' => 'uidialog', 'aria-hidden' => 'true', 'style' => 'display:none'),
self::object_changelog_table(array('class' => 'records-table changelog-table'))
)
);
$this->api->output->set_env('kolab_audit_trail', true);
$p['kolab-audittrail'] = true;
}
return $p;
}
/**
* Handler for message audit trail changelog requests
*/
public function message_changelog()
{
if (!self::$bonnie_api) {
return false;
}
$rcmail = rcube::get_instance();
$msguid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST, true);
$mailbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
$result = $msguid && $mailbox ? self::$bonnie_api->changelog('mail', null, $mailbox, $msguid) : null;
if (is_array($result)) {
if (is_array($result['changes'])) {
$dtformat = $rcmail->config->get('date_format') . ' ' . $rcmail->config->get('time_format');
array_walk($result['changes'], function(&$change) use ($dtformat, $rcmail) {
if ($change['date']) {
$dt = rcube_utils::anytodatetime($change['date']);
if ($dt instanceof DateTime) {
$change['date'] = $rcmail->format_date($dt, $dtformat);
}
}
});
}
$this->api->output->command('plugin.message_render_changelog', $result['changes']);
}
else {
$this->api->output->command('plugin.message_render_changelog', false);
}
$this->api->output->send();
}
/**
* Wrapper function to load and initalize the HTTP_Request2 Object
*
* @param string|Net_Url2 Request URL
* @param string Request method ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT')
* @param array Configuration for this Request instance, that will be merged
* with default configuration
*
* @return HTTP_Request2 Request object
*/
public static function http_request($url = '', $method = 'GET', $config = array())
{
$rcube = rcube::get_instance();
$http_config = (array) $rcube->config->get('kolab_http_request');
// deprecated configuration options
if (empty($http_config)) {
foreach (array('ssl_verify_peer', 'ssl_verify_host') as $option) {
$value = $rcube->config->get('kolab_' . $option, true);
if (is_bool($value)) {
$http_config[$option] = $value;
}
}
}
if (!empty($config)) {
$http_config = array_merge($http_config, $config);
}
// force CURL adapter, this allows to handle correctly
// compressed responses with SplObserver registered (kolab_files) (#4507)
$http_config['adapter'] = 'HTTP_Request2_Adapter_Curl';
$key = md5(serialize($http_config));
if (!($request = self::$http_requests[$key])) {
// load HTTP_Request2
require_once 'HTTP/Request2.php';
try {
$request = new HTTP_Request2();
$request->setConfig($http_config);
}
catch (Exception $e) {
rcube::raise_error($e, true, true);
}
// proxy User-Agent string
$request->setHeader('user-agent', $_SERVER['HTTP_USER_AGENT']);
self::$http_requests[$key] = $request;
}
// cleanup
try {
$request->setBody('');
$request->setUrl($url);
$request->setMethod($method);
}
catch (Exception $e) {
rcube::raise_error($e, true, true);
}
return $request;
}
/**
* Table oultine for object changelog display
*/
public static function object_changelog_table($attrib = array())
{
$rcube = rcube::get_instance();
$attrib += array('domain' => 'libkolab');
$table = new html_table(array('cols' => 5, 'border' => 0, 'cellspacing' => 0));
$table->add_header('diff', '');
$table->add_header('revision', $rcube->gettext('revision', $attrib['domain']));
$table->add_header('date', $rcube->gettext('date', $attrib['domain']));
$table->add_header('user', $rcube->gettext('user', $attrib['domain']));
$table->add_header('operation', $rcube->gettext('operation', $attrib['domain']));
$table->add_header('actions', ' ');
$rcube->output->add_label(
'libkolab.showrevision',
'libkolab.actionreceive',
'libkolab.actionappend',
'libkolab.actionmove',
'libkolab.actiondelete',
'libkolab.actionread',
'libkolab.actionflagset',
'libkolab.actionflagclear',
'libkolab.objectchangelog',
'libkolab.objectchangelognotavailable',
'close'
);
return $table->show($attrib);
}
/**
* Wrapper function for generating a html diff using the FineDiff class by Raymond Hill
*/
public static function html_diff($from, $to, $is_html = null)
{
// auto-detect text/html format
if ($is_html === null) {
$from_html = (preg_match('/<(html|body)(\s+[a-z]|>)/', $from, $m) && strpos($from, '</'.$m[1].'>') > 0);
$to_html = (preg_match('/<(html|body)(\s+[a-z]|>)/', $to, $m) && strpos($to, '</'.$m[1].'>') > 0);
$is_html = $from_html || $to_html;
// ensure both parts are of the same format
if ($is_html && !$from_html) {
$converter = new rcube_text2html($from, false, array('wrap' => true));
$from = $converter->get_html();
}
if ($is_html && !$to_html) {
$converter = new rcube_text2html($to, false, array('wrap' => true));
$to = $converter->get_html();
}
}
// compute diff from HTML
if ($is_html) {
include_once __dir__ . '/vendor/Caxy/HtmlDiff/Match.php';
include_once __dir__ . '/vendor/Caxy/HtmlDiff/Operation.php';
include_once __dir__ . '/vendor/Caxy/HtmlDiff/HtmlDiff.php';
// replace data: urls with a transparent image to avoid memory problems
$from = preg_replace('/src="data:image[^"]+/', 'src="data:image/gif;base64,R0lGODlhAQABAPAAAOjq6gAAACH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAAAsAAAAAAEAAQAAAgJEAQA7', $from);
$to = preg_replace('/src="data:image[^"]+/', 'src="data:image/gif;base64,R0lGODlhAQABAPAAAOjq6gAAACH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAAAsAAAAAAEAAQAAAgJEAQA7', $to);
$diff = new Caxy\HtmlDiff\HtmlDiff($from, $to);
$diffhtml = $diff->build();
// remove empty inserts (from tables)
return preg_replace('!<ins class="diff\w+">\s*</ins>!Uims', '', $diffhtml);
}
else {
include_once __dir__ . '/vendor/finediff.php';
$diff = new FineDiff($from, $to, FineDiff::$wordGranularity);
return $diff->renderDiffToHTML();
}
}
/**
* Return a date() format string to render identifiers for recurrence instances
*
* @param array Hash array with event properties
* @return string Format string
*/
public static function recurrence_id_format($event)
{
return $event['allday'] ? 'Ymd' : 'Ymd\THis';
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, May 1, 2:00 PM (2 h, 20 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
661310
Default Alt Text
(87 KB)
Attached To
Mode
R14 roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline
Log In to Comment