Projects
Kolab:3.4:Updates
chwala
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 20
View file
chwala.spec
Changed
@@ -31,8 +31,8 @@ %global _ap_sysconfdir %{_sysconfdir}/%{httpd_name} Name: chwala -Version: 0.1 -Release: 0.5.dev20130809.git69738e%{?dist} +Version: 0.2 +Release: 1%{?dist} Summary: Glorified WebDAV, done right Group: Applications/Internet @@ -43,14 +43,8 @@ BuildArch: noarch -Requires: php-pear -Requires: php-pear(Auth_SASL) Requires: php-pear(HTTP_Request2) -Requires: php-pear(Mail_Mime) -Requires: php-pear(Net_IDNA2) -Requires: php-pear(Net_SMTP) -Requires: php-pear(Net_Socket) -Requires: php-pear(Net_URL2) +Requires: php-Smarty >= 3.1.7 Requires: roundcubemail Requires: roundcubemail-plugins-kolab %if 0%{?suse_version} @@ -132,6 +126,9 @@ %attr(0750,%{httpd_user},%{httpd_group}) %{_localstatedir}/log/%{name} %changelog +* Sun Nov 24 2013 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 0.2-1 +- New upstream version + * Tue Oct 29 2013 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 0.1-0.5 - Require only "webserver" or "http_daemon"
View file
chwala-0.2.tar.gz/.tx
Added
+(directory)
View file
chwala-0.2.tar.gz/.tx/config
Added
@@ -0,0 +1,8 @@ +[main] +host = https://www.transifex.com +lang_map = en: en_US, de: de_DE, es: es_ES, fi: fi_FI, fr: fr_FR, ja: ja_JP, nl: nl_NL, cs: cs_CZ + +[kolab.kolab-chwala] +file_filter = lib/locale/<lang>.php +source_file = lib/locale/en_US.php +source_lang = en_US
View file
chwala-0.1.tar.gz/README.md -> chwala-0.2.tar.gz/README.md
Changed
@@ -11,8 +11,8 @@ 1. Create local config The configuration for this service inherits basic options from the Roundcube -config. To make that available, symlink the Roundcube config files -(main.inc.php and db.inc.php) into the local config/ directory. +config. To make that available, symlink the Roundcube config file +(config.inc.php) into the local config/ directory. 2. Give write access for the webserver user to the logs, cache and temp folders: @@ -20,7 +20,9 @@ $ chown <www-user> cache $ chown <www-user> temp -3. Optionally, configure your webserver to point to the 'public_html' directory of this +3. Execute database initialization scripts from doc/SQL/ on Roundcube database. + +4. Optionally, configure your webserver to point to the 'public_html' directory of this package as document root.
View file
chwala-0.2.tar.gz/doc
Added
+(directory)
View file
chwala-0.2.tar.gz/doc/SQL
Added
+(directory)
View file
chwala-0.2.tar.gz/doc/SQL/mysql.initial.sql
Added
@@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS `chwala_locks` ( + `uri` varchar(512) BINARY NOT NULL, + `owner` varchar(256), + `timeout` integer unsigned, + `expires` datetime DEFAULT NULL, + `token` varchar(256), + `scope` tinyint, + `depth` tinyint, + INDEX `uri_index` (`uri`, `depth`), + INDEX `expires_index` (`expires`), + INDEX `token_index` (`token`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +INSERT INTO `system` (`name`, `value`) VALUES ('chwala-version', '2013111300');
View file
chwala-0.2.tar.gz/doc/chwala.conf
Added
@@ -0,0 +1,17 @@ +# A suggested default configuration for chwala under httpd + +Alias /chwala /usr/share/chwala/public_html + +<Directory "/usr/share/chwala/public_html/"> + AllowOverride All + <IfModule mod_authz_core.c> + # Apache 2.4 + Require all granted + </IfModule> + <IfModule !mod_authz_core.c> + # Apache 2.2 + Order Allow,Deny + Allow from All + </IfModule> +</Directory> +
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/bootstrap.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/bootstrap.php
Changed
@@ -26,25 +26,25 @@ */ $config = array( - 'error_reporting' => E_ALL &~ (E_NOTICE | E_STRICT), + 'error_reporting' => E_ALL & ~E_NOTICE & ~E_STRICT, // Some users are not using Installer, so we'll check some // critical PHP settings here. Only these, which doesn't provide // an error/warning in the logs later. See (#1486307). 'mbstring.func_overload' => 0, - 'magic_quotes_runtime' => 0, - 'magic_quotes_sybase' => 0, // #1488506 + 'magic_quotes_runtime' => false, + 'magic_quotes_sybase' => false, // #1488506 ); // check these additional ini settings if not called via CLI if (php_sapi_name() != 'cli') { $config += array( - 'suhosin.session.encrypt' => 0, - 'file_uploads' => 1, + 'suhosin.session.encrypt' => false, + 'file_uploads' => true, ); } foreach ($config as $optname => $optval) { - $ini_optval = filter_var(ini_get($optname), FILTER_VALIDATE_BOOLEAN); + $ini_optval = filter_var(ini_get($optname), is_bool($optval) ? FILTER_VALIDATE_BOOLEAN : FILTER_VALIDATE_INT); if ($optval != $ini_optval && @ini_set($optname, $optval) === false) { $error = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n" . "Check your PHP configuration (including php_admin_flag)."; @@ -58,7 +58,7 @@ define('RCUBE_CHARSET', 'UTF-8'); if (!defined('RCUBE_LIB_DIR')) { - define('RCUBE_LIB_DIR', dirname(__FILE__).'/'); + define('RCUBE_LIB_DIR', dirname(__FILE__).DIRECTORY_SEPARATOR); } if (!defined('RCUBE_INSTALL_PATH')) { @@ -83,6 +83,16 @@ @mb_regex_encoding(RCUBE_CHARSET); } +// make sure the Roundcube lib directory is in the include_path +$rcube_path = realpath(RCUBE_LIB_DIR . '..'); +$sep = PATH_SEPARATOR; +$regexp = "!(^|$sep)" . preg_quote($rcube_path, '!') . "($sep|\$)!"; +$path = ini_get('include_path'); + +if (!preg_match($regexp, $path)) { + set_include_path($path . PATH_SEPARATOR . $rcube_path); +} + // Register autoloader spl_autoload_register('rcube_autoload');
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/html.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/html.php
Changed
@@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2011, The Roundcube Dev Team | + | Copyright (C) 2005-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -32,8 +32,8 @@ public static $doctype = 'xhtml'; public static $lc_tags = true; - public static $common_attrib = array('id','class','style','title','align'); - public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script'); + public static $common_attrib = array('id','class','style','title','align','unselectable'); + public static $containers = array('iframe','div','span','p','h1','h2','h3','ul','form','textarea','table','thead','tbody','tr','th','td','style','script'); /** @@ -358,7 +358,7 @@ protected $tagname = 'input'; protected $type = 'text'; protected $allowed = array( - 'type','name','value','size','tabindex','autocapitalize', + 'type','name','value','size','tabindex','autocapitalize','required', 'autocomplete','checked','onchange','onclick','disabled','readonly', 'spellcheck','results','maxlength','src','multiple','accept', 'placeholder','autofocus', @@ -604,16 +604,17 @@ * * @param mixed $names Option name or array with option names * @param mixed $values Option value or array with option values + * @param array $attrib Additional attributes for the option entry */ - public function add($names, $values = null) + public function add($names, $values = null, $attrib = array()) { if (is_array($names)) { foreach ($names as $i => $text) { - $this->options[] = array('text' => $text, 'value' => $values[$i]); + $this->options[] = array('text' => $text, 'value' => $values[$i]) + $attrib; } } else { - $this->options[] = array('text' => $names, 'value' => $values); + $this->options[] = array('text' => $names, 'value' => $values) + $attrib; } } @@ -644,7 +645,7 @@ $option_content = self::quote($option_content); } - $this->content .= self::tag('option', $attr, $option_content); + $this->content .= self::tag('option', $attr + $option, $option_content, array('value','label','class','style','title','disabled','selected')); } return parent::show();
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube.php
Changed
@@ -105,13 +105,14 @@ * This implements the 'singleton' design pattern * * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants + * @param string Environment name to run (e.g. live, dev, test) * * @return rcube The one and only instance */ - static function get_instance($mode = 0) + static function get_instance($mode = 0, $env = '') { if (!self::$instance) { - self::$instance = new rcube(); + self::$instance = new rcube($env); self::$instance->init($mode); } @@ -122,10 +123,10 @@ /** * Private constructor */ - protected function __construct() + protected function __construct($env = '') { // load configuration - $this->config = new rcube_config; + $this->config = new rcube_config($env); $this->plugins = new rcube_dummy_plugin_api; register_shutdown_function(array($this, 'shutdown')); @@ -377,6 +378,7 @@ 'auth_pw' => $this->config->get("{$driver}_auth_pw"), 'debug' => (bool) $this->config->get("{$driver}_debug"), 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), + 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"), 'timeout' => (int) $this->config->get("{$driver}_timeout"), 'skip_deleted' => (bool) $this->config->get('skip_deleted'), 'driver' => $driver, @@ -465,6 +467,10 @@ $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); $this->session->set_ip_check($this->config->get('ip_check')); + if ($this->config->get('session_auth_name')) { + $this->session->set_cookiename($this->config->get('session_auth_name')); + } + // start PHP session (if not in CLI mode) if ($_SERVER['REMOTE_ADDR']) { $this->session->start(); @@ -492,15 +498,22 @@ public function gc_temp() { $tmp = unslashify($this->config->get('temp_dir')); - $expire = time() - 172800; // expire in 48 hours + + // expire in 48 hours by default + $temp_dir_ttl = $this->config->get('temp_dir_ttl', '48h'); + $temp_dir_ttl = get_offset_sec($temp_dir_ttl); + if ($temp_dir_ttl < 6*3600) + $temp_dir_ttl = 6*3600; // 6 hours sensible lower bound. + + $expire = time() - $temp_dir_ttl; if ($tmp && ($dir = opendir($tmp))) { while (($fname = readdir($dir)) !== false) { - if ($fname{0} == '.') { + if ($fname[0] == '.') { continue; } - if (filemtime($tmp.'/'.$fname) < $expire) { + if (@filemtime($tmp.'/'.$fname) < $expire) { @unlink($tmp.'/'.$fname); } } @@ -689,7 +702,11 @@ // user HTTP_ACCEPT_LANGUAGE if no language is specified if (empty($lang) || $lang == 'auto') { $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); - $lang = str_replace('-', '_', $accept_langs[0]); + $lang = $accept_langs[0]; + + if (preg_match('/^([a-z]+)[_-]([a-z]+)$/i', $lang, $m)) { + $lang = $m[1] . '_' . strtoupper($m[2]); + } } if (empty($rcube_languages)) { @@ -1119,8 +1136,8 @@ * - code: Error code (required) * - type: Error type [php|db|imap|javascript] (required) * - message: Error message - * - file: File where error occured - * - line: Line where error occured + * - file: File where error occurred + * - line: Line where error occurred * @param boolean True to log the error * @param boolean Terminate script execution */ @@ -1391,6 +1408,10 @@ 'options' => $options, )); + if ($plugin['abort']) { + return isset($plugin['result']) ? $plugin['result'] : false; + } + $from = $plugin['from']; $mailto = $plugin['mailto']; $options = $plugin['options'];
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_addressbook.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_addressbook.php
Changed
@@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2006-2012, The Roundcube Dev Team | + | Copyright (C) 2006-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -35,6 +35,7 @@ /** public properties (mandatory) */ public $primary_key; public $groups = false; + public $export_groups = true; public $readonly = true; public $searchonly = false; public $undelete = false; @@ -133,7 +134,7 @@ abstract function get_record($id, $assoc=false); /** - * Returns the last error occured (e.g. when updating/inserting failed) + * Returns the last error occurred (e.g. when updating/inserting failed) * * @return array Hash array with the following fields: type, message */ @@ -208,6 +209,7 @@ public function validate(&$save_data, $autofix = false) { $rcube = rcube::get_instance(); + $valid = true; // check validity of email addresses foreach ($this->get_col_values('email', $save_data, true) as $email) { @@ -215,12 +217,28 @@ if (!rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) { $error = $rcube->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email))); $this->set_error(self::ERROR_VALIDATE, $error); - return false; + $valid = false; + break; } } } - return true; + // allow plugins to do contact validation and auto-fixing + $plugin = $rcube->plugins->exec_hook('contact_validate', array( + 'record' => $save_data, + 'autofix' => $autofix, + 'valid' => $valid, + )); + + if ($valid && !$plugin['valid']) { + $this->set_error(self::ERROR_VALIDATE, $plugin['error']); + } + + if (is_array($plugin['record'])) { + $save_data = $plugin['record']; + } + + return $plugin['valid']; } /** @@ -423,7 +441,7 @@ * @param boolean True to return one array with all values, False for hash array with values grouped by type * @return array List of column values */ - function get_col_values($col, $data, $flat = false) + public static function get_col_values($col, $data, $flat = false) { $out = array(); foreach ((array)$data as $c => $values) { @@ -476,7 +494,8 @@ $fn = trim(join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix'])))); // use email address part for name - $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email']; + $email = self::get_col_values('email', $contact, true); + $email = $email[0]; if ($email && (empty($fn) || $fn == $email)) { // return full email @@ -513,8 +532,12 @@ $fn = join(' ', array($contact['surname'], $contact['firstname'], $contact['middlename'])); else if ($compose_mode == 1) $fn = join(' ', array($contact['firstname'], $contact['middlename'], $contact['surname'])); - else + else if ($compose_mode == 0) $fn = !empty($contact['name']) ? $contact['name'] : join(' ', array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix'])); + else { + $plugin = rcube::get_instance()->plugins->exec_hook('contact_listname', array('contact' => $contact)); + $fn = $plugin['fn']; + } $fn = trim($fn, ', '); @@ -523,9 +546,9 @@ $fn = $contact['name']; // fallback to email address - $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email']; - if (empty($fn) && $email) - return $email; + if (empty($fn) && ($email = self::get_col_values('email', $contact, true)) && !empty($email)) { + return $email[0]; + } return $fn; } @@ -538,8 +561,8 @@ $key = $contact[$sort_col] . ':' . $contact['sourceid']; // add email to a key to not skip contacts with the same name (#1488375) - if (!empty($contact['email'])) { - $key .= ':' . implode(':', (array)$contact['email']); + if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) { + $key .= ':' . implode(':', (array)$email); } return $key; @@ -561,9 +584,9 @@ // use only strict comparison (mode = 1) // @TODO: partial search, e.g. match only day and month if (in_array($colname, $this->date_cols)) { - return (($value = rcube_utils::strtotime($value)) - && ($search = rcube_utils::strtotime($search)) - && date('Ymd', $value) == date('Ymd', $search)); + return (($value = rcube_utils::anytodatetime($value)) + && ($search = rcube_utils::anytodatetime($search)) + && $value->format('Ymd') == $search->format('Ymd')); } // composite field, e.g. address
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_base_replacer.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_base_replacer.php
Changed
@@ -44,8 +44,8 @@ public function replace($body) { return preg_replace_callback(array( - '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui', - '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui', + '/(src|background|href)=(["\']?)([^"\'\s>]+)(\2|\s|>)/i', + '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/i', ), array($this, 'callback'), $body); } @@ -90,8 +90,8 @@ if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) { foreach ($matches as $a_match) { - if (strrpos($base_url, '/')) { - $base_url = substr($base_url, 0, strrpos($base_url, '/')); + if ($pos = strrpos($base_url, '/')) { + $base_url = substr($base_url, 0, $pos); } $path = substr($path, 3); }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_charset.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_charset.php
Changed
@@ -674,23 +674,27 @@ // Prioritize charsets according to current language (#1485669) switch ($language) { - case 'ja_JP': // for Japanese + case 'ja_JP': $prio = array('ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win'); break; - case 'zh_CN': // for Chinese (Simplified) - case 'zh_TW': // for Chinese (Traditional) + case 'zh_CN': + case 'zh_TW': $prio = array('UTF-8', 'BIG-5', 'GB2312', 'EUC-TW'); break; - case 'ko_KR': // for Korean + case 'ko_KR': $prio = array('UTF-8', 'EUC-KR', 'ISO-2022-KR'); break; - case 'ru_RU': // for Russian + case 'ru_RU': $prio = array('UTF-8', 'WINDOWS-1251', 'KOI8-R'); break; + case 'tr_TR': + $prio = array('UTF-8', 'ISO-8859-9', 'WINDOWS-1254'); + break; + default: $prio = array('UTF-8', 'SJIS', 'GB2312', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_config.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_config.php
Changed
@@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2012, The Roundcube Dev Team | + | Copyright (C) 2008-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -26,6 +26,8 @@ { const DEFAULT_SKIN = 'larry'; + private $env = ''; + private $paths = array(); private $prop = array(); private $errors = array(); private $userprefs = array(); @@ -50,9 +52,39 @@ /** * Object constructor + * + * @param string Environment suffix for config files to load */ - public function __construct() + public function __construct($env = '') { + $this->env = $env; + + if ($paths = getenv('RCUBE_CONFIG_PATH')) { + $this->paths = explode(PATH_SEPARATOR, $paths); + // make all paths absolute + foreach ($this->paths as $i => $path) { + if (!$this->_is_absolute($path)) { + if ($realpath = realpath(RCUBE_INSTALL_PATH . $path)) { + $this->paths[$i] = unslashify($realpath) . '/'; + } + else { + unset($this->paths[$i]); + } + } + else { + $this->paths[$i] = unslashify($path) . '/'; + } + } + } + + if (defined('RCUBE_CONFIG_DIR') && !in_array(RCUBE_CONFIG_DIR, $this->paths)) { + $this->paths[] = RCUBE_CONFIG_DIR; + } + + if (empty($this->paths)) { + $this->paths[] = RCUBE_INSTALL_PATH . 'config/'; + } + $this->load(); // Defaults, that we do not require you to configure, @@ -69,13 +101,22 @@ */ private function load() { - // load main config file - if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'main.inc.php')) - $this->errors[] = 'main.inc.php was not found.'; + // Load default settings + if (!$this->load_from_file('defaults.inc.php')) { + $this->errors[] = 'defaults.inc.php was not found.'; + } - // load database config - if (!$this->load_from_file(RCUBE_CONFIG_DIR . 'db.inc.php')) - $this->errors[] = 'db.inc.php was not found.'; + // load main config file + if (!$this->load_from_file('config.inc.php')) { + // Old configuration files + if (!$this->load_from_file('main.inc.php') || + !$this->load_from_file('db.inc.php')) { + $this->errors[] = 'config.inc.php was not found.'; + } + else if (rand(1,100) == 10) { // log warning on every 100th request (average) + trigger_error("config.inc.php was not found. Please migrate your config by running bin/update.sh", E_USER_WARNING); + } + } // load host-specific configuration $this->load_host_config(); @@ -121,17 +162,6 @@ // enable display_errors in 'show' level, but not for ajax requests ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4))); - // set timezone auto settings values - if ($this->prop['timezone'] == 'auto') { - $this->prop['_timezone_value'] = $this->client_timezone(); - } - else if (is_numeric($this->prop['timezone']) && ($tz = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0))) { - $this->prop['timezone'] = $tz; - } - else if (empty($this->prop['timezone'])) { - $this->prop['timezone'] = 'UTC'; - } - // remove deprecated properties unset($this->prop['dst_active']); @@ -145,45 +175,107 @@ */ private function load_host_config() { - $fname = null; - - if (is_array($this->prop['include_host_config'])) { - $fname = $this->prop['include_host_config'][$_SERVER['HTTP_HOST']]; - } - else if (!empty($this->prop['include_host_config'])) { - $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $_SERVER['HTTP_HOST']) . '.inc.php'; + if (empty($this->prop['include_host_config'])) { + return; } - if ($fname) { - $this->load_from_file(RCUBE_CONFIG_DIR . $fname); + foreach (array('HTTP_HOST', 'SERVER_NAME', 'SERVER_ADDR') as $key) { + $fname = null; + $name = $_SERVER[$key]; + + if (!$name) { + continue; + } + + if (is_array($this->prop['include_host_config'])) { + $fname = $this->prop['include_host_config'][$name]; + } + else { + $fname = preg_replace('/[^a-z0-9\.\-_]/i', '', $name) . '.inc.php'; + } + + if ($fname && $this->load_from_file($fname)) { + return; + } } } - /** * Read configuration from a file * and merge with the already stored config values * - * @param string $fpath Full path to the config file to be loaded + * @param string $file Name of the config file to be loaded * @return booelan True on success, false on failure */ - public function load_from_file($fpath) + public function load_from_file($file) + { + $success = false; + + foreach ($this->resolve_paths($file) as $fpath) { + if ($fpath && is_file($fpath) && is_readable($fpath)) { + // use output buffering, we don't need any output here + ob_start(); + include($fpath); + ob_end_clean(); + + if (is_array($config)) { + $this->merge($config); + $success = true; + } + // deprecated name of config variable + if (is_array($rcmail_config)) { + $this->merge($rcmail_config); + $success = true; + } + } + } + + return $success; + } + + /** + * Helper method to resolve absolute paths to the given config file. + * This also takes the 'env' property into account. + * + * @param string Filename or absolute file path + * @param boolean Return -$env file path if exists + * @return array List of candidates in config dir path(s) + */ + public function resolve_paths($file, $use_env = true) { - if (is_file($fpath) && is_readable($fpath)) { - // use output buffering, we don't need any output here - ob_start(); - include($fpath); - ob_end_clean(); - - if (is_array($rcmail_config)) { - $this->merge($rcmail_config); - return true; + $files = array(); + $abs_path = $this->_is_absolute($file); + + foreach ($this->paths as $basepath) { + $realpath = $abs_path ? $file : realpath($basepath . '/' . $file); + + // check if <file>-env.ini exists + if ($realpath && $use_env && !empty($this->env)) { + $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $realpath); + if (is_file($envfile)) + $realpath = $envfile; + } + + if ($realpath) { + $files[] = $realpath; + + // no need to continue the loop if an absolute file path is given + if ($abs_path) { + break; + } } } - return false; + return $files; } + /** + * Determine whether the given file path is absolute or relative + */ + private function _is_absolute($path) + { + return $path[0] == DIRECTORY_SEPARATOR || preg_match('!^[a-z]:[\\\\/]!i', $path); + } /** * Getter for a specific config parameter @@ -203,8 +295,10 @@ $rcube = rcube::get_instance(); - if ($name == 'timezone' && isset($this->prop['_timezone_value'])) { - $result = $this->prop['_timezone_value']; + if ($name == 'timezone') { + if (empty($result) || $result == 'auto') { + $result = $this->client_timezone(); + } } else if ($name == 'client_mimetypes') { if ($result == null && $def == null) @@ -239,8 +333,8 @@ */ public function merge($prefs) { + $prefs = $this->fix_legacy_props($prefs); $this->prop = array_merge($this->prop, $prefs, $this->userprefs); - $this->fix_legacy_props(); } @@ -252,6 +346,8 @@ */ public function set_user_prefs($prefs) { + $prefs = $this->fix_legacy_props($prefs); + // Honor the dont_override setting for any existing user preferences $dont_override = $this->get('dont_override'); if (is_array($dont_override) && !empty($dont_override)) { @@ -260,11 +356,6 @@ } } - // convert user's timezone into the new format - if (is_numeric($prefs['timezone']) && ($tz = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0))) { - $prefs['timezone'] = $tz; - } - // larry is the new default skin :-) if ($prefs['skin'] == 'default') { $prefs['skin'] = self::DEFAULT_SKIN; @@ -272,22 +363,13 @@ $this->userprefs = $prefs; $this->prop = array_merge($this->prop, $prefs); - - $this->fix_legacy_props(); - - // override timezone settings with client values - if ($this->prop['timezone'] == 'auto') { - $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value']; - } - else if (isset($this->prop['_timezone_value'])) - unset($this->prop['_timezone_value']); } /** * Getter for all config options * - * @return array Hash array containg all config properties + * @return array Hash array containing all config properties */ public function all() { @@ -421,13 +503,12 @@ */ private function client_timezone() { - if (isset($_SESSION['timezone']) && is_numeric($_SESSION['timezone']) - && ($ctz = timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0))) { - return $ctz; - } - else if (!empty($_SESSION['timezone'])) { + // @TODO: remove this legacy timezone handling in the future + $props = $this->fix_legacy_props(array('timezone' => $_SESSION['timezone'])); + + if (!empty($props['timezone'])) { try { - $tz = timezone_open($_SESSION['timezone']); + $tz = new DateTimeZone($props['timezone']); return $tz->getName(); } catch (Exception $e) { /* gracefully ignore */ } @@ -439,16 +520,93 @@ /** * Convert legacy options into new ones + * + * @param array $props Hash array with config props + * + * @return array Converted config props */ - private function fix_legacy_props() + private function fix_legacy_props($props) { foreach ($this->legacy_props as $new => $old) { - if (isset($this->prop[$old])) { - if (!isset($this->prop[$new])) { - $this->prop[$new] = $this->prop[$old]; + if (isset($props[$old])) { + if (!isset($props[$new])) { + $props[$new] = $props[$old]; } - unset($this->prop[$old]); + unset($props[$old]); + } + } + + // convert deprecated numeric timezone value + if (isset($props['timezone']) && is_numeric($props['timezone'])) { + if ($tz = self::timezone_name_from_abbr($props['timezone'])) { + $props['timezone'] = $tz; + } + else { + unset($props['timezone']); } } + + return $props; + } + + /** + * timezone_name_from_abbr() replacement. Converts timezone offset + * into timezone name abbreviation. + * + * @param float $offset Timezone offset (in hours) + * + * @return string Timezone abbreviation + */ + static public function timezone_name_from_abbr($offset) + { + // List of timezones here is not complete - https://bugs.php.net/bug.php?id=44780 + if ($tz = timezone_name_from_abbr('', $offset * 3600, 0)) { + return $tz; + } + + // try with more complete list (#1489261) + $timezones = array( + '-660' => "Pacific/Apia", + '-600' => "Pacific/Honolulu", + '-570' => "Pacific/Marquesas", + '-540' => "America/Anchorage", + '-480' => "America/Los_Angeles", + '-420' => "America/Denver", + '-360' => "America/Chicago", + '-300' => "America/New_York", + '-270' => "America/Caracas", + '-240' => "America/Halifax", + '-210' => "Canada/Newfoundland", + '-180' => "America/Sao_Paulo", + '-60' => "Atlantic/Azores", + '0' => "Europe/London", + '60' => "Europe/Paris", + '120' => "Europe/Helsinki", + '180' => "Europe/Moscow", + '210' => "Asia/Tehran", + '240' => "Asia/Dubai", + '300' => "Asia/Karachi", + '270' => "Asia/Kabul", + '300' => "Asia/Karachi", + '330' => "Asia/Kolkata", + '345' => "Asia/Katmandu", + '360' => "Asia/Yekaterinburg", + '390' => "Asia/Rangoon", + '420' => "Asia/Krasnoyarsk", + '480' => "Asia/Shanghai", + '525' => "Australia/Eucla", + '540' => "Asia/Tokyo", + '570' => "Australia/Adelaide", + '600' => "Australia/Melbourne", + '630' => "Australia/Lord_Howe", + '660' => "Asia/Vladivostok", + '690' => "Pacific/Norfolk", + '720' => "Pacific/Auckland", + '765' => "Pacific/Chatham", + '780' => "Pacific/Enderbury", + '840' => "Pacific/Kiritimati", + ); + + return $timezones[(string) intval($offset * 60)]; } }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_contacts.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_contacts.php
Changed
@@ -592,8 +592,8 @@ // validate e-mail addresses $valid = parent::validate($save_data, $autofix); - // require at least one e-mail address (syntax check is already done) - if ($valid && !array_filter($this->get_col_values('email', $save_data, true))) { + // require at least one email address or a name + if ($valid && !strlen($save_data['firstname'].$save_data['surname'].$save_data['name']) && !array_filter($this->get_col_values('email', $save_data, true))) { $this->set_error(self::ERROR_VALIDATE, 'noemailwarning'); $valid = false; } @@ -718,6 +718,10 @@ foreach ($save_data as $key => $values) { list($field, $section) = explode(':', $key); $fulltext = in_array($field, $this->fulltext_cols); + // avoid casting DateTime objects to array + if (is_object($values) && is_a($values, 'DateTime')) { + $values = array(0 => $values); + } foreach ((array)$values as $value) { if (isset($value)) $vcard->set($field, $value, $section);
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_csv2vcard.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_csv2vcard.php
Changed
@@ -145,6 +145,7 @@ 'work_mobile' => 'phone:work,cell', 'work_title' => 'jobtitle', 'work_zip' => 'zipcode:work', + 'group' => 'groups', ); /** @@ -268,6 +269,7 @@ 'work_mobile' => "Work Mobile", 'work_title' => "Work Title", 'work_zip' => "Work Zip", + 'groups' => "Group", ); protected $local_label_map = array();
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_db.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_db.php
Changed
@@ -31,7 +31,10 @@ protected $db_dsnr; // DSN for read operations protected $db_connected = false; // Already connected ? protected $db_mode; // Connection mode + protected $db_table_dsn_map = array(); protected $dbh; // Connection handle + protected $dbhs = array(); + protected $table_connections = array(); protected $db_error = false; protected $db_error_msg = ''; @@ -97,9 +100,12 @@ $this->db_dsnw = $db_dsnw; $this->db_dsnr = $db_dsnr; $this->db_pconn = $pconn; + $this->db_dsnw_noread = rcube::get_instance()->config->get('db_dsnw_noread', false); $this->db_dsnw_array = self::parse_dsn($db_dsnw); $this->db_dsnr_array = self::parse_dsn($db_dsnr); + + $this->db_table_dsn_map = array_map(array($this, 'table_name'), rcube::get_instance()->config->get('db_table_dsn', array())); } /** @@ -113,6 +119,13 @@ $this->db_error = false; $this->db_error_msg = null; + // return existing handle + if ($this->dbhs[$mode]) { + $this->dbh = $this->dbhs[$mode]; + $this->db_mode = $mode; + return $this->dbh; + } + // Get database specific connection options $dsn_string = $this->dsn_string($dsn); $dsn_options = $this->dsn_options($dsn); @@ -147,6 +160,7 @@ } $this->dbh = $dbh; + $this->dbhs[$mode] = $dbh; $this->db_mode = $mode; $this->db_connected = true; $this->conn_configure($dsn, $dbh); @@ -175,8 +189,9 @@ * Connect to appropriate database depending on the operation * * @param string $mode Connection mode (r|w) + * @param boolean $force Enforce using the given mode */ - public function db_connect($mode) + public function db_connect($mode, $force = false) { // previous connection failed, don't attempt to connect again if ($this->conn_failure) { @@ -190,14 +205,13 @@ // Already connected if ($this->db_connected) { - // connected to db with the same or "higher" mode - if ($this->db_mode == 'w' || $this->db_mode == $mode) { + // connected to db with the same or "higher" mode (if allowed) + if ($this->db_mode == $mode || $this->db_mode == 'w' && !$force && !$this->db_dsnw_noread) { return; } } $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array; - $this->dsn_connect($dsn, $mode); // use write-master when read-only fails @@ -209,6 +223,46 @@ } /** + * Analyze the given SQL statement and select the appropriate connection to use + */ + protected function dsn_select($query) + { + // no replication + if ($this->db_dsnw == $this->db_dsnr) { + return 'w'; + } + + // Read or write ? + $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w'; + + // find tables involved in this query + if (preg_match_all('/(?:^|\s)(from|update|into|join)\s+'.$this->options['identifier_start'].'?([a-z0-9._]+)'.$this->options['identifier_end'].'?\s+/i', $query, $matches, PREG_SET_ORDER)) { + foreach ($matches as $m) { + $table = $m[2]; + + // always use direct mapping + if ($this->db_table_dsn_map[$table]) { + $mode = $this->db_table_dsn_map[$table]; + break; // primary table rules + } + else if ($mode == 'r') { + // connected to db with the same or "higher" mode for this table + $db_mode = $this->table_connections[$table]; + if ($db_mode == 'w' && !$this->db_dsnw_noread) { + $mode = $db_mode; + } + } + } + + // remember mode chosen (for primary table) + $table = $matches[0][2]; + $this->table_connections[$table] = $mode; + } + + return $mode; + } + + /** * Activate/deactivate debug mode * * @param boolean $dbg True if SQL queries should be logged @@ -340,10 +394,7 @@ { $query = trim($query); - // Read or write ? - $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w'; - - $this->db_connect($mode); + $this->db_connect($this->dsn_select($query), true); // check connection before proceeding if (!$this->is_connected()) { @@ -386,17 +437,7 @@ $result = $this->dbh->query($query); if ($result === false) { - $error = $this->dbh->errorInfo(); - - if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') { - $this->db_error = true; - $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); - - rcube::raise_error(array('code' => 500, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => $this->db_error_msg . " (SQL Query: $query)" - ), true, false); - } + $result = $this->handle_error($query); } $this->last_result = $result; @@ -405,6 +446,30 @@ } /** + * Helper method to handle DB errors. + * This by default logs the error but could be overriden by a driver implementation + * + * @param string Query that triggered the error + * @return mixed Result to be stored and returned + */ + protected function handle_error($query) + { + $error = $this->dbh->errorInfo(); + + if (empty($this->options['ignore_key_errors']) || !in_array($error[0], array('23000', '23505'))) { + $this->db_error = true; + $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); + + rcube::raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $this->db_error_msg . " (SQL Query: $query)" + ), true, false); + } + + return false; + } + + /** * Get number of affected rows for the last query * * @param mixed $result Optional query handle @@ -854,10 +919,14 @@ */ public function table_name($table) { - $rcube = rcube::get_instance(); + static $rcube; + + if (!$rcube) { + $rcube = rcube::get_instance(); + } // add prefix to the table name if configured - if ($prefix = $rcube->config->get('db_prefix')) { + if (($prefix = $rcube->config->get('db_prefix')) && strpos($table, $prefix) !== 0) { return $prefix . $table; } @@ -876,6 +945,17 @@ } /** + * Set DSN connection to be used for the given table + * + * @param string Table name + * @param string DSN connection ('r' or 'w') to be used + */ + public function set_table_dsn($table, $mode) + { + $this->db_table_dsn_map[$this->table_name($table)] = $mode; + } + + /** * MDB2 DSN string parser * * @param string $sequence Secuence name
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_db_mssql.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_db_mssql.php
Changed
@@ -52,7 +52,7 @@ protected function conn_configure($dsn, $dbh) { // Set date format in case of non-default language (#1488918) - $this->query("SET DATEFORMAT ymd"); + $dbh->query("SET DATEFORMAT ymd"); } /**
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_db_mysql.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_db_mysql.php
Changed
@@ -60,7 +60,7 @@ */ protected function conn_configure($dsn, $dbh) { - $this->query("SET NAMES 'utf8'"); + $dbh->query("SET NAMES 'utf8'"); } /** @@ -179,4 +179,29 @@ return isset($this->variables[$varname]) ? $this->variables[$varname] : $default; } + /** + * Handle DB errors, re-issue the query on deadlock errors from InnoDB row-level locking + * + * @param string Query that triggered the error + * @return mixed Result to be stored and returned + */ + protected function handle_error($query) + { + $error = $this->dbh->errorInfo(); + + // retry after "Deadlock found when trying to get lock" errors + $retries = 2; + while ($error[1] == 1213 && $retries >= 0) { + usleep(50000); // wait 50 ms + $result = $this->dbh->query($query); + if ($result !== false) { + return $result; + } + $error = $this->dbh->errorInfo(); + $retries--; + } + + return parent::handle_error($query); + } + }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_db_pgsql.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_db_pgsql.php
Changed
@@ -36,7 +36,7 @@ */ protected function conn_configure($dsn, $dbh) { - $this->query("SET NAMES 'utf8'"); + $dbh->query("SET NAMES 'utf8'"); } /**
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_db_sqlsrv.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_db_sqlsrv.php
Changed
@@ -52,7 +52,7 @@ protected function conn_configure($dsn, $dbh) { // Set date format in case of non-default language (#1488918) - $this->query("SET DATEFORMAT ymd"); + $dbh->query("SET DATEFORMAT ymd"); } /**
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_html2text.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_html2text.php
Changed
@@ -608,24 +608,27 @@ $this->width = $p_width; // Add citation markers and create <pre> block - $body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_ballback'), trim($body)); + $body = preg_replace_callback('/((?:^|\n)>*)([^\n]*)/', array($this, 'blockquote_citation_callback'), trim($body)); $body = '<pre>' . htmlspecialchars($body) . '</pre>'; - $text = substr($text, 0, $start) . $body . "\n" . substr($text, $end + 13); + $text = substr_replace($text, $body . "\n", $start, $end + 13 - $start); $offset = 0; + break; } - } while ($end || $next); + } + while ($end || $next); } } /** * Callback function to correctly add citation markers for blockquote contents */ - public function blockquote_citation_ballback($m) + public function blockquote_citation_callback($m) { - $line = ltrim($m[2]); + $line = ltrim($m[2]); $space = $line[0] == '>' ? '' : ' '; + return $m[1] . '>' . $space . $line; }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_image.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_image.php
Changed
@@ -105,7 +105,6 @@ if ($convert) { $p['out'] = $filename; $p['in'] = $this->image_file; - $p['size'] = $size.'x'.$size; $type = $props['type']; if (!$type && ($data = $this->identify())) { @@ -120,11 +119,37 @@ $type = 'jpg'; } - $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75); - $p['-opts'] = array('-resize' => $p['size'].'>'); + // If only one dimension is greater than the limit convert doesn't + // work as expected, we need to calculate new dimensions + $scale = $size / max($props['width'], $props['height']); - if (in_array($type, explode(',', $p['types']))) { // Valid type? - $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {intype}:{in} {type}:{out}', $p); + // if file is smaller than the limit, we do nothing + // but copy original file to destination file + if ($scale >= 1 && $p['intype'] == $type) { + $result = ($this->image_file == $filename || copy($this->image_file, $filename)) ? '' : false; + } + else { + if ($scale >= 1) { + $width = $props['width']; + $height = $props['height']; + } + else { + $width = intval($props['width'] * $scale); + $height = intval($props['height'] * $scale); + } + + $valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif"; + + $p += array( + 'type' => $type, + 'quality' => 75, + 'size' => $width . 'x' . $height, + ); + + if (in_array($type, explode(',', $valid_types))) { // Valid type? + $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip' + . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p); + } } if ($result === '') { @@ -161,34 +186,34 @@ // Imagemagick resize is implemented in shrinking mode (see -resize argument above) // we do the same here, if an image is smaller than specified size // we do nothing but copy original file to destination file - if ($scale > 1) { - return $this->image_file == $filename || copy($this->image_file, $filename) ? $type : false; - } - - $width = $props['width'] * $scale; - $height = $props['height'] * $scale; - - $new_image = imagecreatetruecolor($width, $height); - - // Fix transparency of gif/png image - if ($props['gd_type'] != IMAGETYPE_JPEG) { - imagealphablending($new_image, false); - imagesavealpha($new_image, true); - $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127); - imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent); + if ($scale >= 1) { + $result = $this->image_file == $filename || copy($this->image_file, $filename); } - - imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']); - $image = $new_image; - - if ($props['gd_type'] == IMAGETYPE_JPEG) { - $result = imagejpeg($image, $filename, 75); - } - elseif($props['gd_type'] == IMAGETYPE_GIF) { - $result = imagegif($image, $filename); - } - elseif($props['gd_type'] == IMAGETYPE_PNG) { - $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); + else { + $width = intval($props['width'] * $scale); + $height = intval($props['height'] * $scale); + $new_image = imagecreatetruecolor($width, $height); + + // Fix transparency of gif/png image + if ($props['gd_type'] != IMAGETYPE_JPEG) { + imagealphablending($new_image, false); + imagesavealpha($new_image, true); + $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127); + imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent); + } + + imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']); + $image = $new_image; + + if ($props['gd_type'] == IMAGETYPE_JPEG) { + $result = imagejpeg($image, $filename, 75); + } + elseif($props['gd_type'] == IMAGETYPE_GIF) { + $result = imagegif($image, $filename); + } + elseif($props['gd_type'] == IMAGETYPE_PNG) { + $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); + } } if ($result) { @@ -230,7 +255,7 @@ $p['out'] = $filename; $p['type'] = self::$extensions[$type]; - $result = rcube::exec($convert . ' 2>&1 -colorspace RGB -quality 75 {in} {type}:{out}', $p); + $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p); if ($result === '') { @chmod($filename, 0600);
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_imap.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_imap.php
Changed
@@ -70,7 +70,7 @@ protected $search_sort_field = ''; protected $search_threads = false; protected $search_sorted = false; - protected $options = array('auth_method' => 'check'); + protected $options = array('auth_type' => 'check'); protected $caching = false; protected $messages_caching = false; protected $threading = false; @@ -391,10 +391,10 @@ public function check_permflag($flag) { $flag = strtoupper($flag); - $imap_flag = $this->conn->flags[$flag]; $perm_flags = $this->get_permflags($this->folder); + $imap_flag = $this->conn->flags[$flag]; - return in_array_nocase($imap_flag, $perm_flags); + return $imap_flag && !empty($perm_flags) && in_array_nocase($imap_flag, $perm_flags); } @@ -410,17 +410,7 @@ if (!strlen($folder)) { return array(); } -/* - Checking PERMANENTFLAGS is rather rare, so we disable caching of it - Re-think when we'll use it for more than only MDNSENT flag - $cache_key = 'mailboxes.permanentflags.' . $folder; - $permflags = $this->get_cache($cache_key); - - if ($permflags !== null) { - return explode(' ', $permflags); - } -*/ if (!$this->check_connection()) { return array(); } @@ -435,10 +425,7 @@ if (!is_array($permflags)) { $permflags = array(); } -/* - // Store permflags as string to limit cached object size - $this->update_cache($cache_key, implode(' ', $permflags)); -*/ + return $permflags; } @@ -2092,17 +2079,18 @@ /** * Fetch message body of a specific message from the server * - * @param int $uid Message UID - * @param string $part Part number - * @param rcube_message_part $o_part Part object created by get_structure() - * @param mixed $print True to print part, ressource to write part contents in - * @param resource $fp File pointer to save the message part - * @param boolean $skip_charset_conv Disables charset conversion - * @param int $max_bytes Only read this number of bytes + * @param int Message UID + * @param string Part number + * @param rcube_message_part Part object created by get_structure() + * @param mixed True to print part, resource to write part contents in + * @param resource File pointer to save the message part + * @param boolean Disables charset conversion + * @param int Only read this number of bytes + * @param boolean Enables formatting of text/* parts bodies * * @return string Message/part body if not printed */ - public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0) + public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0, $formatted=true) { if (!$this->check_connection()) { return null; @@ -2121,8 +2109,9 @@ } if ($o_part && $o_part->size) { + $formatted = $formatted && $o_part->ctype_primary == 'text'; $body = $this->conn->handlePartBody($this->folder, $uid, true, - $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $o_part->ctype_primary == 'text', $max_bytes); + $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $formatted, $max_bytes); } if ($fp || $print) { @@ -2667,7 +2656,6 @@ if ($list_extended) { // unsubscribe non-existent folders, remove from the list - // we can do this only when LIST response is available if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) { foreach ($a_folders as $idx => $folder) { if (($opts = $this->conn->data['LIST'][$folder]) @@ -2680,19 +2668,14 @@ } } else { - // unsubscribe non-existent folders, remove them from the list, - // we can do this only when LIST response is available - if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) { - foreach ($a_folders as $idx => $folder) { - if (!isset($this->conn->data['LIST'][$folder]) - || in_array('\\Noselect', $this->conn->data['LIST'][$folder]) - ) { - // Some servers returns \Noselect for existing folders - if (!$this->folder_exists($folder)) { - $this->conn->unsubscribe($folder); - unset($a_folders[$idx]); - } - } + // unsubscribe non-existent folders, remove them from the list + if (is_array($a_folders) && !empty($a_folders) && $name == '*') { + $existing = $this->list_folders($root, $name); + $nonexisting = array_diff($a_folders, $existing); + $a_folders = array_diff($a_folders, $nonexisting); + + foreach ($nonexisting as $folder) { + $this->conn->unsubscribe($folder); } } } @@ -3777,12 +3760,17 @@ /** * Enable or disable messages caching * - * @param boolean $set Flag + * @param boolean $set Flag + * @param int $mode Cache mode */ - public function set_messages_caching($set) + public function set_messages_caching($set, $mode = null) { if ($set) { $this->messages_caching = true; + + if ($mode && ($cache = $this->get_mcache_engine())) { + $cache->set_mode($mode); + } } else { if ($this->mcache) { @@ -3802,9 +3790,10 @@ if ($this->messages_caching && !$this->mcache) { $rcube = rcube::get_instance(); if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) { - $ttl = $rcube->config->get('messages_cache_ttl', '10d'); + $ttl = $rcube->config->get('messages_cache_ttl', '10d'); + $threshold = $rcube->config->get('messages_cache_threshold', 50); $this->mcache = new rcube_imap_cache( - $dbh, $this, $userid, $this->options['skip_deleted'], $ttl); + $dbh, $this, $userid, $this->options['skip_deleted'], $ttl, $threshold); } } @@ -3816,7 +3805,7 @@ * Clears the messages cache. * * @param string $folder Folder name - * @param array $uids Optional message UIDs to remove from cache + * @param array $uids Optional message UIDs to remove from cache */ protected function clear_message_cache($folder = null, $uids = null) {
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_imap_cache.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_imap_cache.php
Changed
@@ -27,6 +27,9 @@ */ class rcube_imap_cache { + const MODE_INDEX = 1; + const MODE_MESSAGE = 2; + /** * Instance of rcube_imap * @@ -56,6 +59,13 @@ private $ttl; /** + * Maximum cached message size + * + * @var int + */ + private $threshold; + + /** * Internal (in-memory) cache * * @var array @@ -63,6 +73,7 @@ private $icache = array(); private $skip_deleted = false; + private $mode; /** * List of known flags. Thanks to this we can handle flag changes @@ -88,6 +99,7 @@ ); + /** * Object constructor. * @@ -96,9 +108,9 @@ * @param int $userid User identifier * @param bool $skip_deleted skip_deleted flag * @param string $ttl Expiration time of memcache/apc items - * + * @param int $threshold Maximum cached message size */ - function __construct($db, $imap, $userid, $skip_deleted, $ttl=0) + function __construct($db, $imap, $userid, $skip_deleted, $ttl=0, $threshold=0) { // convert ttl string to seconds $ttl = get_offset_sec($ttl); @@ -109,6 +121,10 @@ $this->userid = $userid; $this->skip_deleted = $skip_deleted; $this->ttl = $ttl; + $this->threshold = $threshold; + + // cache all possible information by default + $this->mode = self::MODE_INDEX | self::MODE_MESSAGE; } @@ -123,6 +139,17 @@ /** + * Set cache mode + * + * @param int $mode Cache mode + */ + public function set_mode($mode) + { + $this->mode = $mode; + } + + + /** * Return (sorted) messages index (UIDs). * If index doesn't exist or is invalid, will be updated. * @@ -300,38 +327,46 @@ return array(); } - // Fetch messages from cache - $sql_result = $this->db->query( - "SELECT uid, data, flags" - ." FROM ".$this->db->table_name('cache_messages') - ." WHERE user_id = ?" - ." AND mailbox = ?" - ." AND uid IN (".$this->db->array2list($msgs, 'integer').")", - $this->userid, $mailbox); - - $msgs = array_flip($msgs); $result = array(); - while ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $uid = intval($sql_arr['uid']); - $result[$uid] = $this->build_message($sql_arr); + if ($this->mode & self::MODE_MESSAGE) { + // Fetch messages from cache + $sql_result = $this->db->query( + "SELECT uid, data, flags" + ." FROM ".$this->db->table_name('cache_messages') + ." WHERE user_id = ?" + ." AND mailbox = ?" + ." AND uid IN (".$this->db->array2list($msgs, 'integer').")", + $this->userid, $mailbox); + + $msgs = array_flip($msgs); + + while ($sql_arr = $this->db->fetch_assoc($sql_result)) { + $uid = intval($sql_arr['uid']); + $result[$uid] = $this->build_message($sql_arr); - if (!empty($result[$uid])) { - // save memory, we don't need message body here (?) - $result[$uid]->body = null; + if (!empty($result[$uid])) { + // save memory, we don't need message body here (?) + $result[$uid]->body = null; - unset($msgs[$uid]); + unset($msgs[$uid]); + } } + + $msgs = array_flip($msgs); } // Fetch not found messages from IMAP server if (!empty($msgs)) { - $messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), false, true); + $messages = $this->imap->fetch_headers($mailbox, $msgs, false, true); // Insert to DB and add to result list if (!empty($messages)) { foreach ($messages as $msg) { - $this->add_message($mailbox, $msg, !array_key_exists($msg->uid, $result)); + if ($this->mode & self::MODE_MESSAGE) { + $this->add_message($mailbox, $msg, !array_key_exists($msg->uid, $result)); + } + $result[$msg->uid] = $msg; } } @@ -362,17 +397,19 @@ return $this->icache['__message']['object']; } - $sql_result = $this->db->query( - "SELECT flags, data" - ." FROM ".$this->db->table_name('cache_messages') - ." WHERE user_id = ?" - ." AND mailbox = ?" - ." AND uid = ?", - $this->userid, $mailbox, (int)$uid); + if ($this->mode & self::MODE_MESSAGE) { + $sql_result = $this->db->query( + "SELECT flags, data" + ." FROM ".$this->db->table_name('cache_messages') + ." WHERE user_id = ?" + ." AND mailbox = ?" + ." AND uid = ?", + $this->userid, $mailbox, (int)$uid); - if ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $message = $this->build_message($sql_arr); - $found = true; + if ($sql_arr = $this->db->fetch_assoc($sql_result)) { + $message = $this->build_message($sql_arr); + $found = true; + } } // Get the message from IMAP server @@ -381,6 +418,10 @@ // cache will be updated in close(), see below } + if (!($this->mode & self::MODE_MESSAGE)) { + return $message; + } + // Save the message in internal cache, will be written to DB in close() // Common scenario: user opens unseen message // - get message (SELECT) @@ -416,6 +457,10 @@ return; } + if (!($this->mode & self::MODE_MESSAGE)) { + return; + } + $flags = 0; $msg = clone $message; @@ -487,6 +532,10 @@ return; } + if (!($this->mode & self::MODE_MESSAGE)) { + return; + } + $flag = strtoupper($flag); $idx = (int) array_search($flag, $this->flags); $uids = (array) $uids; @@ -527,6 +576,10 @@ */ function remove_message($mailbox = null, $uids = null) { + if (!($this->mode & self::MODE_MESSAGE)) { + return; + } + if (!strlen($mailbox)) { $this->db->query( "DELETE FROM ".$this->db->table_name('cache_messages') @@ -1028,15 +1081,17 @@ $removed = array(); // Get known UIDs - $sql_result = $this->db->query( - "SELECT uid" - ." FROM ".$this->db->table_name('cache_messages') - ." WHERE user_id = ?" - ." AND mailbox = ?", - $this->userid, $mailbox); + if ($this->mode & self::MODE_MESSAGE) { + $sql_result = $this->db->query( + "SELECT uid" + ." FROM ".$this->db->table_name('cache_messages') + ." WHERE user_id = ?" + ." AND mailbox = ?", + $this->userid, $mailbox); - while ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $uids[] = $sql_arr['uid']; + while ($sql_arr = $this->db->fetch_assoc($sql_result)) { + $uids[] = $sql_arr['uid']; + } } // Synchronize messages data @@ -1155,13 +1210,13 @@ // Save current message from internal cache if ($message = $this->icache['__message']) { // clean up some object's data - $object = $this->message_object_prepare($message['object']); + $this->message_object_prepare($message['object']); // calculate current md5 sum - $md5sum = md5(serialize($object)); + $md5sum = md5(serialize($message['object'])); if ($message['md5sum'] != $md5sum) { - $this->add_message($message['mailbox'], $object, !$message['exists']); + $this->add_message($message['mailbox'], $message['object'], !$message['exists']); } $this->icache['__message']['md5sum'] = $md5sum; @@ -1171,12 +1226,19 @@ /** * Prepares message object to be stored in database. + * + * @param rcube_message_header|rcube_message_part */ - private function message_object_prepare($msg) + private function message_object_prepare(&$msg, &$size = 0) { - // Remove body too big (>25kB) - if ($msg->body && strlen($msg->body) > 25 * 1024) { - unset($msg->body); + // Remove body too big + if ($msg->body && ($length = strlen($msg->body))) { + $size += $length; + + if ($size > $this->threshold * 1024) { + $size -= $length; + unset($msg->body); + } } // Fix mimetype which might be broken by some code when message is displayed @@ -1186,13 +1248,19 @@ list($msg->ctype_primary, $msg->ctype_secondary) = explode('/', $msg->mimetype); } + unset($msg->replaces); + if (is_array($msg->structure->parts)) { - foreach ($msg->structure->parts as $idx => $part) { - $msg->structure->parts[$idx] = $this->message_object_prepare($part); + foreach ($msg->structure->parts as $part) { + $this->message_object_prepare($part, $size); } } - return $msg; + if (is_array($msg->parts)) { + foreach ($msg->parts as $part) { + $this->message_object_prepare($part, $size); + } + } }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_imap_generic.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_imap_generic.php
Changed
@@ -48,6 +48,8 @@ '*' => '\\*', ); + public static $mupdate; + private $fp; private $host; private $logged = false; @@ -704,18 +706,11 @@ */ function connect($host, $user, $password, $options=null) { - // set options - if (is_array($options)) { - $this->prefs = $options; - } - // set auth method - if (!empty($this->prefs['auth_type'])) { - $auth_method = strtoupper($this->prefs['auth_type']); - } else { - $auth_method = 'CHECK'; - } + // configure + $this->set_prefs($options); - $result = false; + $auth_method = $this->prefs['auth_type']; + $result = false; // initialize connection $this->error = ''; @@ -796,23 +791,21 @@ // TLS connection if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { - if (version_compare(PHP_VERSION, '5.1.0', '>=')) { - $res = $this->execute('STARTTLS'); - - if ($res[0] != self::ERROR_OK) { - $this->closeConnection(); - return false; - } + $res = $this->execute('STARTTLS'); - if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { - $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); - $this->closeConnection(); - return false; - } + if ($res[0] != self::ERROR_OK) { + $this->closeConnection(); + return false; + } - // Now we're secure, capabilities need to be reread - $this->clearCapability(); + if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); + $this->closeConnection(); + return false; } + + // Now we're secure, capabilities need to be reread + $this->clearCapability(); } // Send ID info @@ -894,6 +887,36 @@ } /** + * Initializes environment + */ + protected function set_prefs($prefs) + { + // set preferences + if (is_array($prefs)) { + $this->prefs = $prefs; + } + + // set auth method + if (!empty($this->prefs['auth_type'])) { + $this->prefs['auth_type'] = strtoupper($this->prefs['auth_type']); + } + else { + $this->prefs['auth_type'] = 'CHECK'; + } + + // disabled capabilities + if (!empty($this->prefs['disabled_caps'])) { + $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']); + } + + // additional message flags + if (!empty($this->prefs['message_flags'])) { + $this->flags = array_merge($this->flags, $this->prefs['message_flags']); + unset($this->prefs['message_flags']); + } + } + + /** * Checks connection status * * @return bool True if connection is active and user is logged in, False otherwise. @@ -1329,9 +1352,8 @@ $folders[$mailbox] = array(); } - // store LSUB options only if not empty, this way - // we can detect a situation when LIST doesn't return specified folder - if (!empty($opts) || $cmd == 'LIST') { + // store folder options + if ($cmd == 'LIST') { // Add to options array if (empty($this->data['LIST'][$mailbox])) $this->data['LIST'][$mailbox] = $opts; @@ -2159,14 +2181,18 @@ else if ($name == 'RFC822') { $result[$id]->body = $value; } - else if ($name == 'BODY') { - $body = $this->tokenizeResponse($line, 1); - if ($value[0] == 'HEADER.FIELDS') - $headers = $body; - else if (!empty($value)) - $result[$id]->bodypart[$value[0]] = $body; + else if (stripos($name, 'BODY[') === 0) { + $name = str_replace(']', '', substr($name, 5)); + + if ($name == 'HEADER.FIELDS') { + // skip ']' after headers list + $this->tokenizeResponse($line, 1); + $headers = $this->tokenizeResponse($line, 1); + } + else if (strlen($name)) + $result[$id]->bodypart[$name] = $value; else - $result[$id]->body = $body; + $result[$id]->body = $value; } } @@ -2485,7 +2511,7 @@ } if ($binary) { - // WARNING: Use $formatting argument with care, this may break binary data stream + // WARNING: Use $formatted argument with care, this may break binary data stream $mode = -1; } @@ -2511,8 +2537,7 @@ for ($i=0; $i<count($tokens); $i+=2) { if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) { - $i += 2; // skip BODY|BINARY and part number - $result = $tokens[$i]; + $result = $tokens[$i+1]; $found = true; break; } @@ -2536,7 +2561,11 @@ $prev = ''; $found = true; - while ($bytes > 0) { + // empty body + if (!$bytes) { + $result = ''; + } + else while ($bytes > 0) { $line = $this->readLine(8192); if ($line === NULL) { @@ -2980,7 +3009,7 @@ } foreach ($entries as $name => $value) { - $entries[$name] = $this->escape($name) . ' ' . $this->escape($value); + $entries[$name] = $this->escape($name) . ' ' . $this->escape($value, true); } $entries = implode(' ', $entries); @@ -3129,6 +3158,11 @@ } foreach ($data as $entry) { + // Workaround cyrus-murder bug, the entry[2] string needs to be escaped + if (self::$mupdate) { + $entry[2] = addcslashes($entry[2], '\\"'); + } + // ANNOTATEMORE drafts before version 08 require quoted parameters $entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true), $this->escape($entry[1], true), $this->escape($entry[2], true)); @@ -3477,25 +3511,24 @@ // Parenthesized list case '(': - case '[': $str = substr($str, 1); $result[] = self::tokenizeResponse($str); break; case ')': - case ']': $str = substr($str, 1); return $result; break; - // String atom, number, NIL, *, % + // String atom, number, astring, NIL, *, % default: // empty string if ($str === '' || $str === null) { break 2; } - // excluded chars: SP, CTL, ), [, ], DEL - if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) { + // excluded chars: SP, CTL, ), DEL + // we do not exclude [ and ] (#1489223) + if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) { $result[] = $m[1] == 'NIL' ? NULL : $m[1]; $str = substr($str, strlen($m[1])); } @@ -3686,10 +3719,18 @@ $this->capability = explode(' ', strtoupper($str)); + if (!empty($this->prefs['disabled_caps'])) { + $this->capability = array_diff($this->capability, $this->prefs['disabled_caps']); + } + if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) { $this->prefs['literal+'] = true; } + if (preg_match('/(\[| )MUPDATE=.*/', $str)) { + self::$mupdate = true; + } + if ($trusted) { $this->capability_readed = true; }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_ldap.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_ldap.php
Changed
@@ -27,15 +27,16 @@ */ class rcube_ldap extends rcube_addressbook { - /** public properties */ + // public properties public $primary_key = 'ID'; public $groups = false; public $readonly = true; public $ready = false; public $group_id = 0; public $coltypes = array(); + public $export_groups = false; - /** private properties */ + // private properties protected $ldap; protected $prop = array(); protected $fieldmap = array(); @@ -46,6 +47,21 @@ protected $mail_domain = ''; protected $debug = false; + /** + * Group objectclass (lowercase) to member attribute mapping + * + * @var array + */ + private $group_types = array( + 'group' => 'member', + 'groupofnames' => 'member', + 'kolabgroupofnames' => 'member', + 'groupofuniquenames' => 'uniqueMember', + 'kolabgroupofuniquenames' => 'uniqueMember', + 'univentiongroup' => 'uniqueMember', + 'groupofurls' => null, + ); + private $base_dn = ''; private $groups_base_dn = ''; private $group_url; @@ -78,6 +94,9 @@ $this->prop['groups']['name_attr'] = 'cn'; if (empty($this->prop['groups']['scope'])) $this->prop['groups']['scope'] = 'sub'; + // extend group objectclass => member attribute mapping + if (!empty($this->prop['groups']['class_member_attr'])) + $this->group_types = array_merge($this->group_types, $this->prop['groups']['class_member_attr']); // add group name attrib to the list of attributes to be fetched $fetch_attributes[] = $this->prop['groups']['name_attr']; @@ -273,7 +292,17 @@ $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u); // Search for the dn to use to authenticate - if ($this->prop['search_base_dn'] && $this->prop['search_filter']) { + if ($this->prop['search_base_dn'] && $this->prop['search_filter'] + && (strstr($bind_dn, '%dn') || strstr($this->base_dn, '%dn') || strstr($this->groups_base_dn, '%dn')) + ) { + $search_attribs = array('uid'); + if ($search_bind_attrib = (array)$this->prop['search_bind_attrib']) { + foreach ($search_bind_attrib as $r => $attr) { + $search_attribs[] = $attr; + $replaces[$r] = ''; + } + } + $search_bind_dn = strtr($this->prop['search_bind_dn'], $replaces); $search_base_dn = strtr($this->prop['search_base_dn'], $replaces); $search_filter = strtr($this->prop['search_filter'], $replaces); @@ -303,10 +332,18 @@ } } - $res = $ldap->search($search_base_dn, $search_filter, 'sub', array('uid')); + $res = $ldap->search($search_base_dn, $search_filter, 'sub', $search_attribs); if ($res) { $res->rewind(); $replaces['%dn'] = $res->get_dn(); + + // add more replacements from 'search_bind_attrib' config + if ($search_bind_attrib) { + $res = $res->current(); + foreach ($search_bind_attrib as $r => $attr) { + $replaces[$r] = $res[$attr][0]; + } + } } if ($ldap != $this->ldap) { @@ -337,6 +374,23 @@ $this->base_dn = strtr($this->base_dn, $replaces); $this->groups_base_dn = strtr($this->groups_base_dn, $replaces); + // replace placeholders in filter settings + if (!empty($this->prop['filter'])) + $this->prop['filter'] = strtr($this->prop['filter'], $replaces); + if (!empty($this->prop['groups']['filter'])) + $this->prop['groups']['filter'] = strtr($this->prop['groups']['filter'], $replaces); + if (!empty($this->prop['groups']['member_filter'])) + $this->prop['groups']['member_filter'] = strtr($this->prop['groups']['member_filter'], $replaces); + + if (!empty($this->prop['group_filters'])) { + foreach ($this->prop['group_filters'] as $i => $gf) { + if (!empty($gf['base_dn'])) + $this->prop['group_filters'][$i]['base_dn'] = strtr($gf['base_dn'], $replaces); + if (!empty($gf['filter'])) + $this->prop['group_filters'][$i]['filter'] = strtr($gf['filter'], $replaces); + } + } + if (empty($bind_user)) { $bind_user = $u; } @@ -499,7 +553,8 @@ $this->result = new rcube_result_set($entries['count'], ($this->list_page-1) * $this->page_size); } else { - $prop = $this->group_id ? $this->group_data : $this->prop; + $prop = $this->group_id ? $this->group_data : $this->prop; + $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn; // use global search filter if (!empty($this->filter)) @@ -507,7 +562,7 @@ // exec LDAP search if no result resource is stored if ($this->ready && !$this->ldap_result) - $this->ldap_result = $this->ldap->search($prop['base_dn'], $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop); + $this->ldap_result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop); // count contacts for this user $this->result = $this->count(); @@ -540,9 +595,10 @@ /** * Get all members of the given group * - * @param string Group DN - * @param array Group entries (if called recursively) - * @return array Accumulated group members + * @param string Group DN + * @param boolean Count only + * @param array Group entries (if called recursively) + * @return array Accumulated group members */ function list_group_members($dn, $count = false, $entries = null) { @@ -550,7 +606,7 @@ // fetch group object if (empty($entries)) { - $attribs = array('dn','objectClass','member','uniqueMember','memberURL'); + $attribs = array_merge(array('dn','objectClass','memberURL'), array_values($this->group_types)); $entries = $this->ldap->read_entries($dn, '(objectClass=*)', $attribs); if ($entries === false) { return $group_members; @@ -562,17 +618,17 @@ $attrs = array(); foreach ((array)$entry['objectclass'] as $objectclass) { - if (strtolower($objectclass) == 'groupofurls') { - $members = $this->_list_group_memberurl($dn, $entry, $count); - $group_members = array_merge($group_members, $members); - } - else if (($member_attr = $this->get_group_member_attr(array($objectclass), '')) + if (($member_attr = $this->get_group_member_attr(array($objectclass), '')) && ($member_attr = strtolower($member_attr)) && !in_array($member_attr, $attrs) ) { $members = $this->_list_group_members($dn, $entry, $member_attr, $count); $group_members = array_merge($group_members, $members); $attrs[] = $member_attr; } + else if (!empty($entry['memberurl'])) { + $members = $this->_list_group_memberurl($dn, $entry, $count); + $group_members = array_merge($group_members, $members); + } if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) { break 2; @@ -589,6 +645,7 @@ * @param string Group DN * @param array Group entry * @param string Member attribute to use + * @param boolean Count only * @return array Accumulated group members */ private function _list_group_members($dn, $entry, $attr, $count) @@ -602,8 +659,7 @@ // read these attributes for all members $attrib = $count ? array('dn','objectClass') : $this->prop['list_attributes']; - $attrib[] = 'member'; - $attrib[] = 'uniqueMember'; + $attrib = array_merge($attrib, array_values($this->group_types)); $attrib[] = 'memberURL'; $filter = $this->prop['groups']['member_filter'] ? $this->prop['groups']['member_filter'] : '(objectclass=*)'; @@ -650,7 +706,7 @@ if ($result = $this->ldap->search($m[1], $filter, $m[2], $attrs, $this->group_data)) { $entries = $result->entries(); for ($j = 0; $j < $entries['count']; $j++) { - if (self::is_group_entry($entries[$j]) && ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))) + if ($this->is_group_entry($entries[$j]) && ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))) $group_members = array_merge($group_members, $nested_group_members); else $group_members[] = $entries[$j]; @@ -826,13 +882,13 @@ } // We have a connection but no result set, attempt to get one. else if ($this->ready) { - $prop = $this->group_id ? $this->group_data : $this->prop; + $prop = $this->group_id ? $this->group_data : $this->prop; + $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn; if (!empty($this->filter)) { // Use global search filter $prop['filter'] = $this->filter; } - - $count = $this->ldap->search($prop['base_dn'], $prop['filter'], $prop['scope'], array('dn'), $prop, true); + $count = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], array('dn'), $prop, true); } return new rcube_result_set($count, ($this->list_page-1) * $this->page_size); @@ -1191,8 +1247,7 @@ // change the group membership of the contact if ($this->groups) { $group_ids = $this->get_record_groups($dn); - foreach ($group_ids as $group_id => $group_prop) - { + foreach (array_keys($group_ids) as $group_id) { $this->remove_from_group($group_id, $dn); $this->add_to_group($group_id, $newdn); } @@ -1257,7 +1312,7 @@ if ($this->groups) { $dn = self::dn_encode($dn); $group_ids = $this->get_record_groups($dn); - foreach ($group_ids as $group_id => $group_prop) { + foreach (array_keys($group_ids) as $group_id) { $this->remove_from_group($group_id, $dn); } } @@ -1336,7 +1391,7 @@ $out[$this->primary_key] = self::dn_encode($rec['dn']); // determine record type - if (self::is_group_entry($rec)) { + if ($this->is_group_entry($rec)) { $out['_type'] = 'group'; $out['readonly'] = true; $fieldmap['name'] = $this->group_data['name_attr'] ? $this->group_data['name_attr'] : $this->prop['groups']['name_attr']; @@ -1445,11 +1500,11 @@ { // list of known attribute aliases static $aliases = array( - 'gn' => 'givenname', + 'gn' => 'givenname', 'rfc822mailbox' => 'email', - 'userid' => 'uid', - 'emailaddress' => 'email', - 'pkcs9email' => 'email', + 'userid' => 'uid', + 'emailaddress' => 'email', + 'pkcs9email' => 'email', ); list($name, $limit) = explode(':', $namev, 2); @@ -1461,13 +1516,11 @@ /** * Determines whether the given LDAP entry is a group record */ - private static function is_group_entry($entry) + private function is_group_entry($entry) { - return array_intersect( - array('group', 'groupofnames', 'kolabgroupofnames', 'groupofuniquenames', - 'kolabgroupofuniquenames', 'groupofurls', 'univentiongroup'), - array_map('strtolower', (array)$entry['objectclass']) - ); + $classes = array_map('strtolower', (array)$entry['objectclass']); + + return count(array_intersect(array_keys($this->group_types), $classes)) > 0; } /** @@ -1573,6 +1626,7 @@ $base_dn = $this->groups_base_dn; $filter = $this->prop['groups']['filter']; + $scope = $this->prop['groups']['scope']; $name_attr = $this->prop['groups']['name_attr']; $email_attr = $this->prop['groups']['email_attr'] ? $this->prop['groups']['email_attr'] : 'mail'; $sort_attrs = $this->prop['groups']['sort'] ? (array)$this->prop['groups']['sort'] : array($name_attr); @@ -1593,7 +1647,7 @@ } $attrs = array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)); - $ldap_data = $ldap->search($base_dn, $filter, $this->prop['groups']['scope'], $attrs, $this->prop['groups']); + $ldap_data = $ldap->search($base_dn, $filter, $scope, $attrs, $this->prop['groups']); if ($ldap_data === false) { return array(); @@ -1864,6 +1918,7 @@ $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; $member_attr = $this->get_group_member_attr(); $add_filter = ''; + if ($member_attr != 'member' && $member_attr != 'uniqueMember') $add_filter = "($member_attr=$contact_dn)"; $filter = strtr("(|(member=$contact_dn)(uniqueMember=$contact_dn)$add_filter)", array('\\' => '\\\\')); @@ -1879,8 +1934,9 @@ $entry['dn'] = $ldap_data->get_dn(); $group_name = $entry[$name_attr][0]; $group_id = self::dn_encode($entry['dn']); - $groups[$group_id] = array('ID' => $group_id, 'name' => $group_name, 'dn' => $entry['dn']); + $groups[$group_id] = $group_name; } + return $groups; } @@ -1895,16 +1951,8 @@ if (!empty($object_classes)) { foreach ((array)$object_classes as $oc) { - switch (strtolower($oc)) { - case 'group': - case 'groupofnames': - case 'kolabgroupofnames': - return 'member'; - - case 'groupofuniquenames': - case 'kolabgroupofuniquenames': - case 'univentiongroup': - return 'uniqueMember'; + if ($attr = $this->group_types[strtolower($oc)]) { + return $attr; } } }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_ldap_generic.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_ldap_generic.php
Changed
@@ -696,11 +696,17 @@ * Turn an LDAP entry into a regular PHP array with attributes as keys. * * @param array $entry Attributes array as retrieved from ldap_get_attributes() or ldap_get_entries() + * * @return array Hash array with attributes as keys */ public static function normalize_entry($entry) { + if (!isset($entry['count'])) { + return $entry; + } + $rec = array(); + for ($i=0; $i < $entry['count']; $i++) { $attr = $entry[$i]; if ($entry[$attr]['count'] == 1) {
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_message.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_message.php
Changed
@@ -168,10 +168,11 @@ * @param resource $fp File pointer to save the message part * @param boolean $skip_charset_conv Disables charset conversion * @param int $max_bytes Only read this number of bytes + * @param boolean $formatted Enables formatting of text/* parts bodies * * @return string Part content */ - public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0) + public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0, $formatted = true) { if ($part = $this->mime_parts[$mime_id]) { // stored in message structure (winmail/inline-uuencode) @@ -185,7 +186,8 @@ // get from IMAP $this->storage->set_folder($this->folder); - return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv, $max_bytes); + return $this->storage->get_message_part($this->uid, $mime_id, $part, + NULL, $fp, $skip_charset_conv, $max_bytes, $formatted); } } @@ -193,8 +195,6 @@ /** * Determine if the message contains a HTML part. This must to be * a real part not an attachment (or its part) - * This must to be - * a real part not an attachment (or its part) * * @param bool $enriched Enables checking for text/enriched parts too * @@ -212,14 +212,15 @@ $level = explode('.', $part->mime_id); - // Check if the part belongs to higher-level's alternative/related + // Check if the part belongs to higher-level's multipart part + // this can be alternative/related/signed/encrypted, but not mixed while (array_pop($level) !== null) { if (!count($level)) { return true; } $parent = $this->mime_parts[join('.', $level)]; - if ($parent->mimetype != 'multipart/alternative' && $parent->mimetype != 'multipart/related') { + if (!preg_match('/^multipart\/(alternative|related|signed|encrypted)$/', $parent->mimetype)) { continue 2; } } @@ -433,17 +434,24 @@ continue; } + // We've encountered (malformed) messages with more than + // one text/plain or text/html part here. There's no way to choose + // which one is better, so we'll display first of them and add + // others as attachments (#1489358) + // check if sub part is if ($is_multipart) $related_part = $p; - else if ($sub_mimetype == 'text/plain') + else if ($sub_mimetype == 'text/plain' && !$plain_part) $plain_part = $p; - else if ($sub_mimetype == 'text/html') + else if ($sub_mimetype == 'text/html' && !$html_part) $html_part = $p; - else if ($sub_mimetype == 'text/enriched') + else if ($sub_mimetype == 'text/enriched' && !$enriched_part) $enriched_part = $p; - else - $attach_part = $p; + else { + // add unsupported/unrecognized parts to attachments list + $this->attachments[] = $sub_part; + } } // parse related part (alternative part could be in here) @@ -484,11 +492,6 @@ $this->parts[] = $c; } - - // add unsupported/unrecognized parts to attachments list - if ($attach_part) { - $this->attachments[] = $structure->parts[$attach_part]; - } } // this is an ecrypted message -> create a plaintext body with the according message else if ($mimetype == 'multipart/encrypted') {
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_mime.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_mime.php
Changed
@@ -637,7 +637,8 @@ if ($nextChar === ' ' || $nextChar === $separator) { $afterNextChar = mb_substr($string, $width + 1, 1); - if ($afterNextChar === false) { + // Note: mb_substr() does never return False + if ($afterNextChar === false || $afterNextChar === '') { $subString .= $nextChar; } @@ -650,24 +651,23 @@ $subString = mb_substr($subString, 0, $spacePos); $cutLength = $spacePos + 1; } - else if ($cut === false && $breakPos === false) { - $subString = $string; - $cutLength = null; - } else if ($cut === false) { $spacePos = mb_strpos($string, ' ', 0); - if ($spacePos !== false && $spacePos < $breakPos) { + if ($spacePos !== false && ($breakPos === false || $spacePos < $breakPos)) { $subString = mb_substr($string, 0, $spacePos); $cutLength = $spacePos + 1; } + else if ($breakPos === false) { + $subString = $string; + $cutLength = null; + } else { $subString = mb_substr($string, 0, $breakPos); $cutLength = $breakPos + 1; } } else { - $subString = mb_substr($subString, 0, $width); $cutLength = $width; } } @@ -708,12 +708,20 @@ */ public static function file_content_type($path, $name, $failover = 'application/octet-stream', $is_stream = false, $skip_suffix = false) { + static $mime_ext = array(); + $mime_type = null; - $mime_magic = rcube::get_instance()->config->get('mime_magic'); - $mime_ext = $skip_suffix ? null : @include(RCUBE_CONFIG_DIR . '/mimetypes.php'); + $config = rcube::get_instance()->config; + $mime_magic = $config->get('mime_magic'); + + if (!$skip_suffix && empty($mime_ext)) { + foreach ($config->resolve_paths('mimetypes.php') as $fpath) { + $mime_ext = array_merge($mime_ext, (array) @include($fpath)); + } + } // use file name suffix with hard-coded mime-type map - if (is_array($mime_ext) && $name) { + if (!$skip_suffix && is_array($mime_ext) && $name) { if ($suffix = substr($name, strrpos($name, '.')+1)) { $mime_type = $mime_ext[strtolower($suffix)]; } @@ -802,7 +810,7 @@ } $mime_types = $mime_extensions = array(); - $regex = "/([\w\+\-\.\/]+)\t+([\w\s]+)/i"; + $regex = "/([\w\+\-\.\/]+)\s+([\w\s]+)/i"; foreach((array)$lines as $line) { // skip comments or mime types w/o any extensions if ($line[0] == '#' || !preg_match($regex, $line, $matches)) @@ -818,7 +826,9 @@ // fallback to some well-known types most important for daily emails if (empty($mime_types)) { - $mime_extensions = (array) @include(RCUBE_CONFIG_DIR . '/mimetypes.php'); + foreach (rcube::get_instance()->config->resolve_paths('mimetypes.php') as $fpath) { + $mime_extensions = array_merge($mime_extensions, (array) @include($fpath)); + } foreach ($mime_extensions as $ext => $mime) { $mime_types[$mime][] = $ext;
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_plugin_api.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_plugin_api.php
Changed
@@ -35,8 +35,9 @@ public $url = 'plugins/'; public $task = ''; public $output; - public $handlers = array(); - public $allowed_prefs = array(); + public $handlers = array(); + public $allowed_prefs = array(); + public $allowed_session_prefs = array(); protected $plugins = array(); protected $tasks = array(); @@ -403,7 +404,7 @@ $args = $ret + $args; } - if ($args['abort']) { + if ($args['break']) { break; } }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_session.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_session.php
Changed
@@ -54,7 +54,7 @@ { $this->db = $db; $this->start = microtime(true); - $this->ip = $_SERVER['REMOTE_ADDR']; + $this->ip = rcube_utils::remote_addr(); $this->logging = $config->get('log_session', false); $lifetime = $config->get('session_lifetime', 1) * 60; @@ -333,9 +333,9 @@ $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars; - if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) { + if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 3) { return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), - MEMCACHE_COMPRESSED, $this->lifetime); + MEMCACHE_COMPRESSED, $this->lifetime + 60); } return true; @@ -480,7 +480,7 @@ public function kill() { $this->vars = null; - $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed) + $this->ip = rcube_utils::remote_addr(); // update IP (might have changed) $this->destroy(session_id()); rcube_utils::setcookie($this->cookiename, '-del-', time() - 60); } @@ -694,10 +694,10 @@ function check_auth() { $this->cookie = $_COOKIE[$this->cookiename]; - $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true; + $result = $this->ip_check ? rcube_utils::remote_addr() == $this->ip : true; if (!$result) { - $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']); + $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . rcube_utils::remote_addr()); } if ($result && $this->_mkcookie($this->now) != $this->cookie) {
View file
chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_spellcheck_atd.php
Added
@@ -0,0 +1,204 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | | + | Copyright (C) 2013, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Spellchecking backend implementation for afterthedeadline services | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Spellchecking backend implementation to work with an After the Deadline service + * See http://www.afterthedeadline.com/ for more information + * + * @package Framework + * @subpackage Utils + */ +class rcube_spellcheck_atd extends rcube_spellcheck_engine +{ + const SERVICE_HOST = 'service.afterthedeadline.com'; + const SERVICE_PORT = 80; + + private $matches = array(); + private $content; + private $langhosts = array( + 'fr' => 'fr.', + 'de' => 'de.', + 'pt' => 'pt.', + 'es' => 'es.', + ); + + /** + * Return a list of languages supported by this backend + * + * @see rcube_spellcheck_engine::languages() + */ + function languages() + { + $langs = array_values($this->langhosts); + $langs[] = 'en'; + return $langs; + } + + /** + * Set content and check spelling + * + * @see rcube_spellcheck_engine::check() + */ + function check($text) + { + $this->content = $text; + + // spell check uri is configured + $rcube = rcube::get_instance(); + $url = $rcube->config->get('spellcheck_uri'); + $key = $rcube->config->get('spellcheck_atd_key'); + + if ($url) { + $a_uri = parse_url($url); + $ssl = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl'); + $port = $a_uri['port'] ? $a_uri['port'] : ($ssl ? 443 : 80); + $host = ($ssl ? 'ssl://' : '') . $a_uri['host']; + $path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang; + } + else { + $host = self::SERVICE_HOST; + $port = self::SERVICE_PORT; + $path = '/checkDocument'; + + // prefix host for other languages than 'en' + $lang = substr($this->lang, 0, 2); + if ($this->langhosts[$lang]) + $host = $this->langhosts[$lang] . $host; + } + + $postdata = 'data=' . urlencode($text); + + if (!empty($key)) + $postdata .= '&key=' . urlencode($key); + + $response = $headers = ''; + $in_header = true; + if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) { + $out = "POST $path HTTP/1.0\r\n"; + $out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n"; + $out .= "Content-Length: " . strlen($postdata) . "\r\n"; + $out .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $out .= "Connection: Close\r\n\r\n"; + $out .= $postdata; + fwrite($fp, $out); + + while (!feof($fp)) { + if ($in_header) { + $line = fgets($fp, 512); + $headers .= $line; + if (trim($line) == '') + $in_header = false; + } + else { + $response .= fgets($fp, 1024); + } + } + fclose($fp); + } + + // parse HTTP response headers + if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $headers, $m)) { + $http_status = $m[1]; + if ($http_status != '200') + $this->error = 'HTTP ' . $m[1] . $m[2]; + } + + if (!$response) { + $this->error = "Empty result from spelling engine"; + } + + try { + $result = new SimpleXMLElement($response); + } + catch (Exception $e) { + $thid->error = "Unexpected response from server: " . $store; + return array(); + } + + foreach ($result->error as $error) { + if (strval($error->type) == 'spelling') { + $word = strval($error->string); + + // skip exceptions + if ($this->dictionary->is_exception($word)) { + continue; + } + + $prefix = strval($error->precontext); + $start = $prefix ? mb_strpos($text, $prefix) : 0; + $pos = mb_strpos($text, $word, $start); + $len = mb_strlen($word); + $num = 0; + + $match = array($word, $pos, $len, null, array()); + foreach ($error->suggestions->option as $option) { + $match[4][] = strval($option); + if (++$num == self::MAX_SUGGESTIONS) + break; + } + $matches[] = $match; + } + } + + $this->matches = $matches; + return $matches; + } + + /** + * Returns suggestions for the specified word + * + * @see rcube_spellcheck_engine::get_words() + */ + function get_suggestions($word) + { + $matches = $word ? $this->check($word) : $this->matches; + + if ($matches[0][4]) { + return $matches[0][4]; + } + + return array(); + } + + /** + * Returns misspelled words + * + * @see rcube_spellcheck_engine::get_suggestions() + */ + function get_words($text = null) + { + if ($text) { + $matches = $this->check($text); + } + else { + $matches = $this->matches; + $text = $this->content; + } + + $result = array(); + + foreach ($matches as $m) { + $result[] = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET); + } + + return $result; + } + +} +
View file
chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_spellcheck_enchant.php
Added
@@ -0,0 +1,182 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | | + | Copyright (C) 2011-2013, Kolab Systems AG | + | Copyright (C) 20011-2013, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Spellchecking backend implementation to work with Enchant | + +-----------------------------------------------------------------------+ + | Author: Aleksander Machniak <machniak@kolabsys.com> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Spellchecking backend implementation to work with Pspell + * + * @package Framework + * @subpackage Utils + */ +class rcube_spellcheck_enchant extends rcube_spellcheck_engine +{ + private $enchant_broker; + private $enchant_dictionary; + private $matches = array(); + + /** + * Return a list of languages supported by this backend + * + * @see rcube_spellcheck_engine::languages() + */ + function languages() + { + $this->init(); + + $langs = array(); + $dicts = enchant_broker_list_dicts($this->enchant_broker); + foreach ($dicts as $dict) { + $langs[] = preg_replace('/-.*$/', '', $dict['lang_tag']); + } + + return array_unique($langs); + } + + /** + * Initializes Enchant dictionary + */ + private function init() + { + if (!$this->enchant_broker) { + if (!extension_loaded('enchant')) { + $this->error = "Enchant extension not available"; + return; + } + + $this->enchant_broker = enchant_broker_init(); + } + + if (!enchant_broker_dict_exists($this->enchant_broker, $this->lang)) { + $this->error = "Unable to load dictionary for selected language using Enchant"; + return; + } + + $this->enchant_dictionary = enchant_broker_request_dict($this->enchant_broker, $this->lang); + } + + /** + * Set content and check spelling + * + * @see rcube_spellcheck_engine::check() + */ + function check($text) + { + $this->init(); + + if (!$this->enchant_dictionary) { + return array(); + } + + // tokenize + $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); + + $diff = 0; + $matches = array(); + + foreach ($text as $w) { + $word = trim($w[0]); + $pos = $w[1] - $diff; + $len = mb_strlen($word); + + // skip exceptions + if ($this->dictionary->is_exception($word)) { + } + else if (!enchant_dict_check($this->enchant_dictionary, $word)) { + $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word); + + if (sizeof($suggestions) > self::MAX_SUGGESTIONS) { + $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); + } + + $matches[] = array($word, $pos, $len, null, $suggestions); + } + + $diff += (strlen($word) - $len); + } + + $this->matches = $matches; + return $matches; + } + + /** + * Returns suggestions for the specified word + * + * @see rcube_spellcheck_engine::get_words() + */ + function get_suggestions($word) + { + $this->init(); + + if (!$this->enchant_dictionary) { + return array(); + } + + $suggestions = enchant_dict_suggest($this->enchant_dictionary, $word); + + if (sizeof($suggestions) > self::MAX_SUGGESTIONS) + $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); + + return is_array($suggestions) ? $suggestions : array(); + } + + /** + * Returns misspelled words + * + * @see rcube_spellcheck_engine::get_suggestions() + */ + function get_words($text = null) + { + $result = array(); + + if ($text) { + // init spellchecker + $this->init(); + + if (!$this->enchant_dictionary) { + return array(); + } + + // With Enchant we don't need to get suggestions to return misspelled words + $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); + + foreach ($text as $w) { + $word = trim($w[0]); + + // skip exceptions + if ($this->dictionary->is_exception($word)) { + continue; + } + + if (!enchant_dict_check($this->enchant_dictionary, $word)) { + $result[] = $word; + } + } + + return $result; + } + + foreach ($this->matches as $m) { + $result[] = $m[0]; + } + + return $result; + } + +} +
View file
chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_spellcheck_engine.php
Added
@@ -0,0 +1,91 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | | + | Copyright (C) 2011-2013, Kolab Systems AG | + | Copyright (C) 2008-2013, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Interface class for a spell-checking backend | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Interface class for a spell-checking backend + * + * @package Framework + * @subpackage Utils + */ +abstract class rcube_spellcheck_engine +{ + const MAX_SUGGESTIONS = 10; + + protected $lang; + protected $error; + protected $dictionary; + protected $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.](?=\W|$)/'; + + /** + * Default constructor + */ + public function __construct($dict, $lang) + { + $this->dictionary = $dict; + $this->lang = $lang; + } + + /** + * Return a list of languages supported by this backend + * + * @return array Indexed list of language codes + */ + abstract function languages(); + + /** + * Set content and check spelling + * + * @param string $text Text content for spellchecking + * + * @return bool True when no mispelling found, otherwise false + */ + abstract function check($text); + + /** + * Returns suggestions for the specified word + * + * @param string $word The word + * + * @return array Suggestions list + */ + abstract function get_suggestions($word); + + /** + * Returns misspelled words + * + * @param string $text The content for spellchecking. If empty content + * used for check() method will be used. + * + * @return array List of misspelled words + */ + abstract function get_words($text = null); + + /** + * Returns error message + * + * @return string Error message + */ + public function error() + { + return $this->error; + } + +} +
View file
chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_spellcheck_googie.php
Added
@@ -0,0 +1,176 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | | + | Copyright (C) 2008-2013, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Spellchecking backend implementation to work with Googiespell | + +-----------------------------------------------------------------------+ + | Author: Aleksander Machniak <machniak@kolabsys.com> | + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Spellchecking backend implementation to work with a Googiespell service + * + * @package Framework + * @subpackage Utils + */ +class rcube_spellcheck_googie extends rcube_spellcheck_engine +{ + const GOOGIE_HOST = 'ssl://spell.roundcube.net'; + const GOOGIE_PORT = 443; + + private $matches = array(); + private $content; + + /** + * Return a list of languages supported by this backend + * + * @see rcube_spellcheck_engine::languages() + */ + function languages() + { + return array('am','ar','ar','bg','br','ca','cs','cy','da', + 'de_CH','de_DE','el','en_GB','en_US', + 'eo','es','et','eu','fa','fi','fr_FR','ga','gl','gl', + 'he','hr','hu','hy','is','it','ku','lt','lv','nl', + 'pl','pt_BR','pt_PT','ro','ru', + 'sk','sl','sv','uk'); + } + + /** + * Set content and check spelling + * + * @see rcube_spellcheck_engine::check() + */ + function check($text) + { + $this->content = $text; + + // spell check uri is configured + $url = rcube::get_instance()->config->get('spellcheck_uri'); + + if ($url) { + $a_uri = parse_url($url); + $ssl = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl'); + $port = $a_uri['port'] ? $a_uri['port'] : ($ssl ? 443 : 80); + $host = ($ssl ? 'ssl://' : '') . $a_uri['host']; + $path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang; + } + else { + $host = self::GOOGIE_HOST; + $port = self::GOOGIE_PORT; + $path = '/tbproxy/spell?lang=' . $this->lang; + } + + $path .= sprintf('&key=%06d', $_SESSION['user_id']); + + $gtext = '<?xml version="1.0" encoding="utf-8" ?>' + .'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">' + .'<text>' . htmlspecialchars($text, ENT_QUOTES, RCUBE_CHARSET) . '</text>' + .'</spellrequest>'; + + $store = ''; + if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) { + $out = "POST $path HTTP/1.0\r\n"; + $out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n"; + $out .= "User-Agent: Roundcube Webmail/" . RCMAIL_VERSION . " (Googiespell Wrapper)\r\n"; + $out .= "Content-Length: " . strlen($gtext) . "\r\n"; + $out .= "Content-Type: text/xml\r\n"; + $out .= "Connection: Close\r\n\r\n"; + $out .= $gtext; + fwrite($fp, $out); + + while (!feof($fp)) + $store .= fgets($fp, 128); + fclose($fp); + } + + // parse HTTP response + if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) { + $http_status = $m[1]; + if ($http_status != '200') { + $this->error = 'HTTP ' . $m[1] . $m[2]; + $this->error .= "\n" . $store; + } + } + + if (!$store) { + $this->error = "Empty result from spelling engine"; + } + else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m) && $m[1]) { + $this->error = "Error code $m[1] returned"; + $this->error .= preg_match('/<errortext>([^<]+)/', $store, $m) ? ": " . html_entity_decode($m[1]) : ''; + } + + preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER); + + // skip exceptions (if appropriate options are enabled) + foreach ($matches as $idx => $m) { + $word = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET); + // skip exceptions + if ($this->dictionary->is_exception($word)) { + unset($matches[$idx]); + } + } + + $this->matches = $matches; + return $matches; + } + + /** + * Returns suggestions for the specified word + * + * @see rcube_spellcheck_engine::get_words() + */ + function get_suggestions($word) + { + $matches = $word ? $this->check($word) : $this->matches; + + if ($matches[0][4]) { + $suggestions = explode("\t", $matches[0][4]); + if (sizeof($suggestions) > self::MAX_SUGGESTIONS) { + $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); + } + + return $suggestions; + } + + return array(); + } + + /** + * Returns misspelled words + * + * @see rcube_spellcheck_engine::get_suggestions() + */ + function get_words($text = null) + { + if ($text) { + $matches = $this->check($text); + } + else { + $matches = $this->matches; + $text = $this->content; + } + + $result = array(); + + foreach ($matches as $m) { + $result[] = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET); + } + + return $result; + } + +} +
View file
chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_spellcheck_pspell.php
Added
@@ -0,0 +1,189 @@ +<?php + +/* + +-----------------------------------------------------------------------+ + | This file is part of the Roundcube Webmail client | + | | + | Copyright (C) 2008-2013, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Spellchecking backend implementation to work with Pspell | + +-----------------------------------------------------------------------+ + | Author: Aleksander Machniak <machniak@kolabsys.com> | + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ +*/ + +/** + * Spellchecking backend implementation to work with Pspell + * + * @package Framework + * @subpackage Utils + */ +class rcube_spellcheck_pspell extends rcube_spellcheck_engine +{ + private $plink; + private $matches = array(); + + /** + * Return a list of languages supported by this backend + * + * @see rcube_spellcheck_engine::languages() + */ + function languages() + { + $defaults = array('en'); + $langs = array(); + + // get aspell dictionaries + exec('aspell dump dicts', $dicts); + if (!empty($dicts)) { + $seen = array(); + foreach ($dicts as $lang) { + $lang = preg_replace('/-.*$/', '', $lang); + $langc = strlen($lang) == 2 ? $lang.'_'.strtoupper($lang) : $lang; + if (!$seen[$langc]++) + $langs[] = $lang; + } + $langs = array_unique($langs); + } + else { + $langs = $defaults; + } + + return $langs; + } + + /** + * Initializes PSpell dictionary + */ + private function init() + { + if (!$this->plink) { + if (!extension_loaded('pspell')) { + $this->error = "Pspell extension not available"; + return; + } + + $this->plink = pspell_new($this->lang, null, null, RCUBE_CHARSET, PSPELL_FAST); + } + + if (!$this->plink) { + $this->error = "Unable to load Pspell engine for selected language"; + } + } + + /** + * Set content and check spelling + * + * @see rcube_spellcheck_engine::check() + */ + function check($text) + { + $this->init(); + + if (!$this->plink) { + return array(); + } + + // tokenize + $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); + + $diff = 0; + $matches = array(); + + foreach ($text as $w) { + $word = trim($w[0]); + $pos = $w[1] - $diff; + $len = mb_strlen($word); + + // skip exceptions + if ($this->dictionary->is_exception($word)) { + } + else if (!pspell_check($this->plink, $word)) { + $suggestions = pspell_suggest($this->plink, $word); + + if (sizeof($suggestions) > self::MAX_SUGGESTIONS) { + $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); + } + + $matches[] = array($word, $pos, $len, null, $suggestions); + } + + $diff += (strlen($word) - $len); + } + + $this->matches = $matches; + return $matches; + } + + /** + * Returns suggestions for the specified word + * + * @see rcube_spellcheck_engine::get_words() + */ + function get_suggestions($word) + { + $this->init(); + + if (!$this->plink) { + return array(); + } + + $suggestions = pspell_suggest($this->plink, $word); + + if (sizeof($suggestions) > self::MAX_SUGGESTIONS) + $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); + + return is_array($suggestions) ? $suggestions : array(); + } + + /** + * Returns misspelled words + * + * @see rcube_spellcheck_engine::get_suggestions() + */ + function get_words($text = null) + { + $result = array(); + + if ($text) { + // init spellchecker + $this->init(); + + if (!$this->plink) { + return array(); + } + + // With PSpell we don't need to get suggestions to return misspelled words + $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); + + foreach ($text as $w) { + $word = trim($w[0]); + + // skip exceptions + if ($this->dictionary->is_exception($word)) { + continue; + } + + if (!pspell_check($this->plink, $word)) { + $result[] = $word; + } + } + + return $result; + } + + foreach ($this->matches as $m) { + $result[] = $m[0]; + } + + return $result; + } + +} +
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_spellchecker.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_spellchecker.php
Changed
@@ -3,8 +3,8 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2011, Kolab Systems AG | - | Copyright (C) 2008-2011, The Roundcube Dev Team | + | Copyright (C) 2011-2013, Kolab Systems AG | + | Copyright (C) 2008-2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -28,21 +28,15 @@ { private $matches = array(); private $engine; + private $backend; private $lang; private $rc; private $error; - private $separator = '/[\s\r\n\t\(\)\/\[\]{}<>\\"]+|[:;?!,\.](?=\W|$)/'; private $options = array(); private $dict; private $have_dict; - // default settings - const GOOGLE_HOST = 'ssl://www.google.com'; - const GOOGLE_PORT = 443; - const MAX_SUGGESTIONS = 10; - - /** * Constructor * @@ -60,8 +54,60 @@ 'ignore_caps' => $this->rc->config->get('spellcheck_ignore_caps'), 'dictionary' => $this->rc->config->get('spellcheck_dictionary'), ); + + $cls = 'rcube_spellcheck_' . $this->engine; + if (class_exists($cls)) { + $this->backend = new $cls($this, $this->lang); + $this->backend->options = $this->options; + } + else { + $this->error = "Unknown spellcheck engine '$this->engine'"; + } } + /** + * Return a list of supported languages + */ + function languages() + { + // trust configuration + $configured = $this->rc->config->get('spellcheck_languages'); + if (!empty($configured) && is_array($configured) && !$configured[0]) { + return $configured; + } + else if (!empty($configured)) { + $langs = (array)$configured; + } + else if ($this->backend) { + $langs = $this->backend->languages(); + } + + // load index + @include(RCUBE_LOCALIZATION_DIR . 'index.inc'); + + // add correct labels + $languages = array(); + foreach ($langs as $lang) { + $langc = strtolower(substr($lang, 0, 2)); + $alias = $rcube_language_aliases[$langc]; + if (!$alias) { + $alias = $langc.'_'.strtoupper($langc); + } + if ($rcube_languages[$lang]) { + $languages[$lang] = $rcube_languages[$lang]; + } + else if ($rcube_languages[$alias]) { + $languages[$lang] = $rcube_languages[$alias]; + } + else { + $languages[$lang] = ucfirst($lang); + } + } + + asort($languages); + + return $languages; + } /** * Set content and check spelling @@ -81,11 +127,8 @@ $this->content = $text; } - if ($this->engine == 'pspell') { - $this->matches = $this->_pspell_check($this->content); - } - else { - $this->matches = $this->_googie_check($this->content); + if ($this->backend) { + $this->matches = $this->backend->check($this->content); } return $this->found() == 0; @@ -112,11 +155,11 @@ */ function get_suggestions($word) { - if ($this->engine == 'pspell') { - return $this->_pspell_suggestions($word); + if ($this->backend) { + return $this->backend->get_suggestions($word); } - return $this->_googie_suggestions($word); + return array(); } @@ -130,11 +173,15 @@ */ function get_words($text = null, $is_html=false) { - if ($this->engine == 'pspell') { - return $this->_pspell_words($text, $is_html); + if ($is_html) { + $text = $this->html2text($text); + } + + if ($this->backend) { + return $this->backend->get_words($text); } - return $this->_googie_words($text, $is_html); + return array(); } @@ -148,7 +195,7 @@ // send output $out = '<?xml version="1.0" encoding="'.RCUBE_CHARSET.'"?><spellresult charschecked="'.mb_strlen($this->content).'">'; - foreach ($this->matches as $item) { + foreach ((array)$this->matches as $item) { $out .= '<c o="'.$item[1].'" l="'.$item[2].'">'; $out .= is_array($item[4]) ? implode("\t", $item[4]) : $item[4]; $out .= '</c>'; @@ -169,7 +216,7 @@ { $result = array(); - foreach ($this->matches as $item) { + foreach ((array)$this->matches as $item) { if ($this->engine == 'pspell') { $word = $item[0]; } @@ -190,259 +237,7 @@ */ function error() { - return $this->error; - } - - - /** - * Checks the text using pspell - * - * @param string $text Text content for spellchecking - */ - private function _pspell_check($text) - { - // init spellchecker - $this->_pspell_init(); - - if (!$this->plink) { - return array(); - } - - // tokenize - $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); - - $diff = 0; - $matches = array(); - - foreach ($text as $w) { - $word = trim($w[0]); - $pos = $w[1] - $diff; - $len = mb_strlen($word); - - // skip exceptions - if ($this->is_exception($word)) { - } - else if (!pspell_check($this->plink, $word)) { - $suggestions = pspell_suggest($this->plink, $word); - - if (sizeof($suggestions) > self::MAX_SUGGESTIONS) { - $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); - } - - $matches[] = array($word, $pos, $len, null, $suggestions); - } - - $diff += (strlen($word) - $len); - } - - return $matches; - } - - - /** - * Returns the misspelled words - */ - private function _pspell_words($text = null, $is_html=false) - { - $result = array(); - - if ($text) { - // init spellchecker - $this->_pspell_init(); - - if (!$this->plink) { - return array(); - } - - // With PSpell we don't need to get suggestions to return misspelled words - if ($is_html) { - $text = $this->html2text($text); - } - - $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); - - foreach ($text as $w) { - $word = trim($w[0]); - - // skip exceptions - if ($this->is_exception($word)) { - continue; - } - - if (!pspell_check($this->plink, $word)) { - $result[] = $word; - } - } - - return $result; - } - - foreach ($this->matches as $m) { - $result[] = $m[0]; - } - - return $result; - } - - - /** - * Returns suggestions for misspelled word - */ - private function _pspell_suggestions($word) - { - // init spellchecker - $this->_pspell_init(); - - if (!$this->plink) { - return array(); - } - - $suggestions = pspell_suggest($this->plink, $word); - - if (sizeof($suggestions) > self::MAX_SUGGESTIONS) - $suggestions = array_slice($suggestions, 0, self::MAX_SUGGESTIONS); - - return is_array($suggestions) ? $suggestions : array(); - } - - - /** - * Initializes PSpell dictionary - */ - private function _pspell_init() - { - if (!$this->plink) { - if (!extension_loaded('pspell')) { - $this->error = "Pspell extension not available"; - return; - } - - $this->plink = pspell_new($this->lang, null, null, RCUBE_CHARSET, PSPELL_FAST); - } - - if (!$this->plink) { - $this->error = "Unable to load Pspell engine for selected language"; - } - } - - - private function _googie_check($text) - { - // spell check uri is configured - $url = $this->rc->config->get('spellcheck_uri'); - - if ($url) { - $a_uri = parse_url($url); - $ssl = ($a_uri['scheme'] == 'https' || $a_uri['scheme'] == 'ssl'); - $port = $a_uri['port'] ? $a_uri['port'] : ($ssl ? 443 : 80); - $host = ($ssl ? 'ssl://' : '') . $a_uri['host']; - $path = $a_uri['path'] . ($a_uri['query'] ? '?'.$a_uri['query'] : '') . $this->lang; - } - else { - $host = self::GOOGLE_HOST; - $port = self::GOOGLE_PORT; - $path = '/tbproxy/spell?lang=' . $this->lang; - } - - // Google has some problem with spaces, use \n instead - $gtext = str_replace(' ', "\n", $text); - - $gtext = '<?xml version="1.0" encoding="utf-8" ?>' - .'<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">' - .'<text>' . $gtext . '</text>' - .'</spellrequest>'; - - $store = ''; - if ($fp = fsockopen($host, $port, $errno, $errstr, 30)) { - $out = "POST $path HTTP/1.0\r\n"; - $out .= "Host: " . str_replace('ssl://', '', $host) . "\r\n"; - $out .= "Content-Length: " . strlen($gtext) . "\r\n"; - $out .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $out .= "Connection: Close\r\n\r\n"; - $out .= $gtext; - fwrite($fp, $out); - - while (!feof($fp)) - $store .= fgets($fp, 128); - fclose($fp); - } - - // parse HTTP response - if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) { - $http_status = $m[1]; - if ($http_status != '200') - $this->error = 'HTTP ' . $m[1] . $m[2]; - } - - if (!$store) { - $this->error = "Empty result from spelling engine"; - } - else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m) && $m[1]) { - $this->error = "Error code $m[1] returned"; - } - - preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER); - - // skip exceptions (if appropriate options are enabled) - if (!empty($this->options['ignore_syms']) || !empty($this->options['ignore_nums']) - || !empty($this->options['ignore_caps']) || !empty($this->options['dictionary']) - ) { - foreach ($matches as $idx => $m) { - $word = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET); - // skip exceptions - if ($this->is_exception($word)) { - unset($matches[$idx]); - } - } - } - - return $matches; - } - - - private function _googie_words($text = null, $is_html=false) - { - if ($text) { - if ($is_html) { - $text = $this->html2text($text); - } - - $matches = $this->_googie_check($text); - } - else { - $matches = $this->matches; - $text = $this->content; - } - - $result = array(); - - foreach ($matches as $m) { - $result[] = mb_substr($text, $m[1], $m[2], RCUBE_CHARSET); - } - - return $result; - } - - - private function _googie_suggestions($word) - { - if ($word) { - $matches = $this->_googie_check($word); - } - else { - $matches = $this->matches; - } - - if ($matches[0][4]) { - $suggestions = explode("\t", $matches[0][4]); - if (sizeof($suggestions) > self::MAX_SUGGESTIONS) { - $suggestions = array_slice($suggestions, 0, MAX_SUGGESTIONS); - } - - return $suggestions; - } - - return array(); + return $this->error ? $this->error : ($this->backend ? $this->backend->error() : false); }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_storage.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_storage.php
Changed
@@ -39,7 +39,7 @@ protected $default_charset = 'ISO-8859-1'; protected $default_folders = array('INBOX'); protected $search_set; - protected $options = array('auth_method' => 'check'); + protected $options = array('auth_type' => 'check'); protected $page_size = 10; protected $threading = false; @@ -61,8 +61,6 @@ 'MAIL-FOLLOWUP-TO', 'MAIL-REPLY-TO', 'RETURN-PATH', - 'DELIVERED-TO', - 'ENVELOPE-TO', ); const UNKNOWN = 0;
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_string_replacer.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_string_replacer.php
Changed
@@ -24,11 +24,16 @@ */ class rcube_string_replacer { - public static $pattern = '/##str_replacement\[([0-9]+)\]##/'; + public static $pattern = '/##str_replacement_(\d+)##/'; public $mailto_pattern; public $link_pattern; + public $linkref_index; + public $linkref_pattern; + private $values = array(); private $options = array(); + private $linkrefs = array(); + private $urls = array(); function __construct($options = array()) @@ -37,14 +42,16 @@ // Support unicode/punycode in top-level domain part $utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})'; $url1 = '.:;,'; - $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-'; + $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*-'; - $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/"; + $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]*[$url2]+)*)/"; $this->mailto_pattern = "/(" ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*" // local-part ."@$utf_domain" // domain-part ."(\?[$url1$url2]+)?" // e.g. ?subject=test... .")/"; + $this->linkref_index = '/\[([^\]#]+)\](:?\s*##str_replacement_(\d+)##)/'; + $this->linkref_pattern = '/\[([^\]#]+)\]/'; $this->options = $options; } @@ -67,7 +74,7 @@ */ public function get_replacement($i) { - return '##str_replacement['.$i.']##'; + return '##str_replacement_' . $i . '##'; } /** @@ -96,6 +103,7 @@ $attrib['href'] = $url_prefix . $url; $i = $this->add(html::a($attrib, rcube::Q($url)) . $suffix); + $this->urls[$i] = $attrib['href']; } // Return valid link for recognized schemes, otherwise @@ -104,6 +112,32 @@ } /** + * Callback to add an entry to the link index + */ + public function linkref_addindex($matches) + { + $key = $matches[1]; + $this->linkrefs[$key] = $this->urls[$matches[3]]; + + return $this->get_replacement($this->add('['.$key.']')) . $matches[2]; + } + + /** + * Callback to replace link references with real links + */ + public function linkref_callback($matches) + { + $i = 0; + if ($url = $this->linkrefs[$matches[1]]) { + $attrib = (array)$this->options['link_attribs']; + $attrib['href'] = $url; + $i = $this->add(html::a($attrib, rcube::Q($matches[1]))); + } + + return $i > 0 ? '['.$this->get_replacement($i).']' : $matches[0]; + } + + /** * Callback function used to build mailto: links around e-mail strings * * @param array Matches result from preg_replace_callback @@ -142,6 +176,9 @@ // search for patterns like links and e-mail addresses $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str); $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str); + // resolve link references + $str = preg_replace_callback($this->linkref_index, array($this, 'linkref_addindex'), $str); + $str = preg_replace_callback($this->linkref_pattern, array($this, 'linkref_callback'), $str); return $str; }
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_user.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_user.php
Changed
@@ -163,8 +163,16 @@ if (!$this->ID) return false; - $config = $this->rc->config; - $old_prefs = (array)$this->get_prefs(); + $plugin = $this->rc->plugins->exec_hook('preferences_update', array( + 'userid' => $this->ID, 'prefs' => $a_user_prefs, 'old' => (array)$this->get_prefs())); + + if (!empty($plugin['abort'])) { + return; + } + + $a_user_prefs = $plugin['prefs']; + $old_prefs = $plugin['old']; + $config = $this->rc->config; // merge (partial) prefs array with existing settings $save_prefs = $a_user_prefs + $old_prefs;
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_utils.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_utils.php
Changed
@@ -390,12 +390,13 @@ * Convert array of request parameters (prefixed with _) * to a regular array with non-prefixed keys. * - * @param int $mode Source to get value from (GPC) - * @param string $ignore PCRE expression to skip parameters by name + * @param int $mode Source to get value from (GPC) + * @param string $ignore PCRE expression to skip parameters by name + * @param boolean $allow_html Allow HTML tags in field value * * @return array Hash array with all request parameters */ - public static function request2param($mode = null, $ignore = 'task|action') + public static function request2param($mode = null, $ignore = 'task|action', $allow_html = false) { $out = array(); $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST); @@ -403,7 +404,7 @@ foreach (array_keys($src) as $key) { $fname = $key[0] == '_' ? substr($key, 1) : $key; if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) { - $out[$fname] = self::get_input_value($key, $mode); + $out[$fname] = self::get_input_value($key, $mode, $allow_html); } } @@ -444,41 +445,48 @@ $source = self::xss_entity_decode($source); $stripped = preg_replace('/[^a-z\(:;]/i', '', $source); $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : ''); + if (preg_match("/$evilexpr/i", $stripped)) { return '/* evil! */'; } + $strict_url_regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims'; + // cut out all contents between { and } while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) { - $styles = substr($source, $pos+1, $pos2-($pos+1)); + $nested = strpos($source, '{', $pos+1); + if ($nested && $nested < $pos2) // when dealing with nested blocks (e.g. @media), take the inner one + $pos = $nested; + $length = $pos2 - $pos - 1; + $styles = substr($source, $pos+1, $length); // check every line of a style block... if ($allow_remote) { $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY); + foreach ($a_styles as $line) { $stripped = preg_replace('/[^a-z\(:;]/i', '', $line); // ... and only allow strict url() values - $regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims'; - if (stripos($stripped, 'url(') && !preg_match($regexp, $line)) { + if (stripos($stripped, 'url(') && !preg_match($strict_url_regexp, $line)) { $a_styles = array('/* evil! */'); break; } } + $styles = join(";\n", $a_styles); } - $key = $replacements->add($styles); - $source = substr($source, 0, $pos+1) - . $replacements->get_replacement($key) - . substr($source, $pos2, strlen($source)-$pos2); - $last_pos = $pos+2; + $key = $replacements->add($styles); + $repl = $replacements->get_replacement($key); + $source = substr_replace($source, $repl, $pos+1, $length); + $last_pos = $pos2 - ($length - strlen($repl)); } // remove html comments and add #container to each tag selector. // also replace body definition because we also stripped off the <body> tag - $styles = preg_replace( + $source = preg_replace( array( - '/(^\s*<!--)|(-->\s*$)/', + '/(^\s*<\!--)|(-->\s*$)/m', '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im', '/'.preg_quote($container_id, '/').'\s+body/i', ), @@ -490,9 +498,9 @@ $source); // put block contents back in - $styles = $replacements->resolve($styles); + $source = $replacements->resolve($source); - return $styles; + return $source; } @@ -666,6 +674,21 @@ /** + * Returns the real remote IP address + * + * @return string Remote IP address + */ + public static function remote_addr() + { + foreach (array('HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP','REMOTE_ADDR') as $prop) { + if (!empty($_SERVER[$prop])) + return $_SERVER[$prop]; + } + + return ''; + } + + /** * Read a specific HTTP request header. * * @param string $name Header name @@ -724,19 +747,87 @@ */ public static function strtotime($date) { - // check for MS Outlook vCard date format YYYYMMDD - if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) { - return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1])); - } - else if (is_numeric($date)) { + $date = self::clean_datestr($date); + + // unix timestamp + if (is_numeric($date)) { return (int) $date; } + // if date parsing fails, we have a date in non-rfc format. + // remove token from the end and try again + while ((($ts = @strtotime($date)) === false) || ($ts < 0)) { + $d = explode(' ', $date); + array_pop($d); + if (!$d) { + break; + } + $date = implode(' ', $d); + } + + return (int) $ts; + } + + /** + * Date parsing function that turns the given value into a DateTime object + * + * @param string $date Date string + * + * @return object DateTime instance or false on failure + */ + public static function anytodatetime($date) + { + if (is_object($date) && is_a($date, 'DateTime')) { + return $date; + } + + $dt = false; + $date = self::clean_datestr($date); + + // try to parse string with DateTime first + if (!empty($date)) { + try { + $dt = new DateTime($date); + } + catch (Exception $e) { + // ignore + } + } + + // try our advanced strtotime() method + if (!$dt && ($timestamp = self::strtotime($date))) { + try { + $dt = new DateTime("@".$timestamp); + } + catch (Exception $e) { + // ignore + } + } + + return $dt; + } + + /** + * Clean up date string for strtotime() input + * + * @param string $date Date string + * + * @return string Date string + */ + public static function clean_datestr($date) + { + $date = trim($date); + + // check for MS Outlook vCard date format YYYYMMDD + if (preg_match('/^([12][90]\d\d)([01]\d)([0123]\d)$/', $date, $m)) { + return sprintf('%04d-%02d-%02d 00:00:00', intval($m[1]), intval($m[2]), intval($m[3])); + } + // Clean malformed data $date = preg_replace( array( '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal - '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters + '/[^a-z0-9\x20\x09:+-\/]/i', // remove any invalid characters '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names ), array( @@ -747,21 +838,21 @@ $date = trim($date); - // if date parsing fails, we have a date in non-rfc format. - // remove token from the end and try again - while ((($ts = @strtotime($date)) === false) || ($ts < 0)) { - $d = explode(' ', $date); - array_pop($d); - if (!$d) { - break; - } - $date = implode(' ', $d); + // try to fix dd/mm vs. mm/dd discrepancy, we can't do more here + if (preg_match('/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})$/', $date, $m)) { + $mdy = $m[2] > 12 && $m[1] <= 12; + $day = $mdy ? $m[2] : $m[1]; + $month = $mdy ? $m[1] : $m[2]; + $date = sprintf('%04d-%02d-%02d 00:00:00', intval($m[3]), $month, $day); + } + // I've found that YYYY.MM.DD is recognized wrong, so here's a fix + else if (preg_match('/^(\d{4})\.(\d{1,2})\.(\d{1,2})$/', $date)) { + $date = str_replace('.', '-', $date) . ' 00:00:00'; } - return (int) $ts; + return $date; } - /* * Idn_to_ascii wrapper. * Intl/Idn modules version of this function doesn't work with e-mail address
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_vcard.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_vcard.php
Changed
@@ -47,6 +47,7 @@ 'manager' => 'X-MANAGER', 'spouse' => 'X-SPOUSE', 'edit' => 'X-AB-EDIT', + 'groups' => 'CATEGORIES', ); private $typemap = array( 'IPHONE' => 'mobile', @@ -357,8 +358,8 @@ case 'birthday': case 'anniversary': - if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) { - $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date')); + if (($val = rcube_utils::anytodatetime($value)) && ($fn = self::$fieldmap[$field])) { + $this->raw[$fn][] = array(0 => $val->format('Y-m-d'), 'value' => array('date')); } break; @@ -517,20 +518,28 @@ */ public static function cleanup($vcard) { - // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;) - $vcard = preg_replace( - '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', - '\2;type=\5\3:\4', - $vcard); - // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility $vcard = preg_replace_callback( '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', array('self', 'x_abrelatednames_callback'), $vcard); - // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines - $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard); + // Cleanup + $vcard = preg_replace(array( + // convert special types (like Skype) to normal type='skype' classes with this simple regex ;) + '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', + '/^item\d*\.X-AB.*$/m', // remove cruft like item1.X-AB* + '/^item\d*\./m', // remove item1.ADR instead of ADR + '/\n+/', // remove empty lines + '/^(N:[^;\R]*)$/m', // if N doesn't have any semicolons, add some + ), + array( + '\2;type=\5\3:\4', + '', + '', + "\n", + '\1;;;;', + ), $vcard); // convert X-WAB-GENDER to X-GENDER if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) { @@ -538,9 +547,6 @@ $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard); } - // if N doesn't have any semicolons, add some - $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard); - return $vcard; } @@ -611,8 +617,8 @@ $enc = null; foreach($regs2[1] as $attrid => $attr) { + $attr = preg_replace('/[\s\t\n\r\0\x0B]/', '', $attr); if ((list($key, $value) = explode('=', $attr)) && $value) { - $value = trim($value); if ($key == 'ENCODING') { $value = strtoupper($value); // add next line(s) to value string if QP line end detected @@ -756,7 +762,7 @@ * * @return string Joined and quoted string */ - private static function vcard_quote($s, $sep = ';') + public static function vcard_quote($s, $sep = ';') { if (is_array($s)) { foreach($s as $part) { @@ -765,7 +771,7 @@ return(implode($sep, (array)$r)); } - return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;')); + return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', $sep => '\\'.$sep)); } /** @@ -791,7 +797,7 @@ return $result; } - $s = strtr($s, $rep2); + $s = trim(strtr($s, $rep2)); } // some implementations (GMail) use non-standard backslash before colon (#1489085)
View file
chwala-0.1.tar.gz/lib/ext/Roundcube/rcube_washtml.php -> chwala-0.2.tar.gz/lib/ext/Roundcube/rcube_washtml.php
Changed
@@ -377,7 +377,14 @@ // Detect max nesting level (for dumpHTML) (#1489110) $this->max_nesting_level = (int) @ini_get('xdebug.max_nesting_level'); - @$node->loadHTML($html); + // Use optimizations if supported + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + @$node->loadHTML($html, LIBXML_PARSEHUGE | LIBXML_COMPACT); + } + else { + @$node->loadHTML($html); + } + return $this->dumpHtml($node); } @@ -410,6 +417,25 @@ ); $html = preg_replace($html_search, $html_replace, trim($html)); + //-> Replace all of those weird MS Word quotes and other high characters + $badwordchars=array( + "\xe2\x80\x98", // left single quote + "\xe2\x80\x99", // right single quote + "\xe2\x80\x9c", // left double quote + "\xe2\x80\x9d", // right double quote + "\xe2\x80\x94", // em dash + "\xe2\x80\xa6" // elipses + ); + $fixedwordchars=array( + "'", + "'", + '"', + '"', + '—', + '...' + ); + $html = str_replace($badwordchars,$fixedwordchars, $html); + // PCRE errors handling (#1486856), should we use something like for every preg_* use? if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) { $errstr = "Could not clean up HTML message! PCRE Error: $preg_error."; @@ -429,7 +455,7 @@ } // fix (unknown/malformed) HTML tags before "wash" - $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html); + $html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html); // Remove invalid HTML comments (#1487759) // Don't remove valid conditional comments
View file
chwala-0.1.tar.gz/lib/file_api.php -> chwala-0.2.tar.gz/lib/file_api.php
Changed
@@ -502,6 +502,32 @@ } return $quota; + + case 'lock': + // arguments: uri, owner, timeout, scope, depth, token + foreach (array('uri', 'token') as $arg) { + if (!isset($args[$arg]) || $args[$arg] === '') { + throw new Exception("Missing lock $arg", file_api::ERROR_CODE); + } + } + + $this->api->lock($args['uri'], $args); + return; + + case 'unlock': + foreach (array('uri', 'token') as $arg) { + if (!isset($args[$arg]) || $args[$arg] === '') { + throw new Exception("Missing lock $arg", file_api::ERROR_CODE); + } + } + + $this->api->unlock($args['uri'], $args); + return; + + case 'lock_list': + $child_locks = !empty($args['child_locks']) && rcube_utils::get_boolean($args['child_locks']); + + return $this->api->lock_list($args['uri'], $child_locks); } if ($request) { @@ -759,11 +785,15 @@ $response['status'] = 'ERROR'; + if ($code) { + $response['code'] = $code; + } + if (!empty($_REQUEST['req_id'])) { $response['req_id'] = $_REQUEST['req_id']; } - if (!$response['code']) { + if (empty($response['code'])) { $response['code'] = file_api::ERROR_CODE; }
View file
chwala-0.2.tar.gz/lib/file_locks.php
Added
@@ -0,0 +1,268 @@ +<?php + +/* + +--------------------------------------------------------------------------+ + | This file is part of the Kolab File API | + | | + | Copyright (C) 2012-2013, 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 <http://www.gnu.org/licenses/> | + +--------------------------------------------------------------------------+ + | Author: Aleksander Machniak <machniak@kolabsys.com> | + +--------------------------------------------------------------------------+ +*/ + +/** + * The Lock manager allows you to handle all file-locks centrally. + * It stores all its data in a sql database. Derived from SabreDAV's + * PDO Lock manager. + */ +class file_locks { + + const SHARED = 1; + const EXCLUSIVE = 2; + const INFINITE = -1; + + /** + * The database connection object + * + * @var rcube_db + */ + private $db; + + /** + * The tablename this backend uses. + * + * @var string + */ + protected $table; + + /** + * Internal cache + * + * @var array + */ + protected $icache = array(); + + /** + * Constructor + * + * @param string $table Table name + */ + public function __construct($table = 'chwala_locks') + { + $rcube = rcube::get_instance(); + + $this->db = $rcube->get_dbh(); + $this->table = $this->db->table_name($table); + + if ($rcube->session) { + $rcube->session->register_gc_handler(array($this, 'gc')); + } + else { + // run garbage collector with probability based on + // session settings if session does not exist. + $probability = (int) ini_get('session.gc_probability'); + $divisor = (int) ini_get('session.gc_divisor'); + + if ($divisor > 0 && $probability > 0) { + $random = mt_rand(1, $divisor); + if ($random <= $probability) { + $this->gc(); + } + } + } + } + + /** + * Returns a list of locks + * + * This method should return all the locks for a particular URI, including + * locks that might be set on a parent URI. + * + * If child_locks is set to true, this method should also look for + * any locks in the subtree of the URI for locks. + * + * @param string $uri URI + * @param bool $child_locks Enables subtree checks + * + * @return array List of locks + */ + public function lock_list($uri, $child_locks = false) + { + if ($this->icache['uri'] == $uri && $this->icache['child'] == $child_locks) { + return $this->icache['list']; + } + + $query = "SELECT * FROM " . $this->db->quote_identifier($this->table) . " WHERE (uri = ?"; + $params = array($uri); + + if ($child_locks) { + $query .= " OR uri LIKE ?"; + $params[] = $uri . '/%'; + } + + $path = ''; + $key = $uri; + $list = array(); + + // in case uri contains protocol/host specification e.g. imap://user@host/ + // handle prefix separately + if (preg_match('~^([a-z]+://[^/]+/)~i', $uri, $matches)) { + $path = $matches[1]; + $uri = substr($uri, strlen($matches[1])); + } + + // We need to check locks for every part in the path + $path_parts = explode('/', $uri); + + // We already covered the last part of the uri + array_pop($path_parts); + + if (!empty($path_parts)) { + $root_path = $path . implode('/', $path_parts); + + // this path is already cached, extract locks from cached result + // we do this because it is a common scenario to request + // for lock on every file/folder in specified location + if ($this->icache['root_path'] == $root_path) { + $length = strlen($root_path); + foreach ($this->icache['list'] as $lock) { + if ($lock['depth'] != 0 && strlen($lock['token']) <= $length) { + $list[] = $lock; + } + } + } + else { + foreach ($path_parts as $part) { + $path .= $part; + $params[] = $path; + $path .= '/'; + } + + $query .= " OR (uri IN (" . implode(',', array_pad(array(), count($path_parts), '?')) . ") AND depth <> 0)"; + } + } + + // finally, skip expired locks + $query .= ") AND expires > " . $this->db->now(); + + // run the query and parse result + $result = $this->db->query($query, $params); + + while ($row = $this->db->fetch_assoc($result)) { + $created = strtotime($row['expires']) - $row['timeout']; + $list[] = array( + 'uri' => $row['uri'], + 'owner' => $row['owner'], + 'token' => $row['token'], + 'timeout' => (int) $row['timeout'], + 'created' => (int) $created, + 'scope' => $row['scope'] == self::EXCLUSIVE ? file_storage::LOCK_EXCLUSIVE : file_storage::LOCK_SHARED, + 'depth' => $row['depth'] == self::INFINITE ? file_storage::LOCK_INFINITE : (int) $row['depth'], + ); + } + + // remember last result in memory, sometimes we need it (or part of it) again + $this->icache['list'] = $list; + $this->icache['uri'] = $key; + $this->icache['root_path'] = $root_path; + $this->icache['child_locks'] = $child_locks; + + return $list; + } + + /** + * Locks a uri + * + * @param string $uri URI + * @param array $lock Lock data + * + * @return bool + */ + public function lock($uri, $lock) + { + // We're making the lock timeout max. 30 minutes + $timeout = min($lock['timeout'], 30*60); + + $data = array( + $this->db->quote_identifier('uri') => $uri, + $this->db->quote_identifier('owner') => $lock['owner'], + $this->db->quote_identifier('scope') => $lock['scope'] == file_storage::LOCK_EXCLUSIVE ? self::EXCLUSIVE : self::SHARED, + $this->db->quote_identifier('depth') => $lock['depth'] == file_storage::LOCK_INFINITE ? self::INFINITE : 0, + $this->db->quote_identifier('timeout') => $timeout, + ); + + // check if lock exists + $locks = $this->lock_list($uri, false); + $exists = false; + + foreach ($locks as $l) { + if ($l['token'] == $lock['token']) { + $exists = true; + break; + } + } + + if ($exists) { + foreach (array_keys($data) as $key) { + $update_cols[] = "$key = ?"; + } + + $result = $this->db->query("UPDATE " . $this->db->quote_identifier($this->table) + . " SET " . implode(', ', $update_cols) + . ", " . $this->db->quote_identifier('expires') . " = " . $this->db->now($timeout) + . " WHERE token = ?", + array_merge(array_values($data), array($lock['token'])) + ); + } + else { + $data[$this->db->quote_identifier('token')] = $lock['token']; + + $result = $this->db->query("INSERT INTO " . $this->db->quote_identifier($this->table) + . " (".join(', ', array_keys($data)) . ", " . $this->db->quote_identifier('expires') . ")" + . " VALUES (" . str_repeat('?, ', count($data)) . $this->db->now($timeout) . ")", + array_values($data) + ); + } + + return $this->db->affected_rows(); + } + + /** + * Removes a lock from a URI + * + * @param string $path URI + * @param array $lock Lock data + * + * @return bool + */ + public function unlock($uri, $lock) + { + $stmt = $this->db->query("DELETE FROM " . $this->db->quote_identifier($this->table) + . " WHERE uri = ? AND token = ?", + $uri, $lock['token']); + + return $this->db->affected_rows(); + } + + /** + * Remove expired locks + */ + public function gc() + { + $this->db->query("DELETE FROM " . $this->db->quote_identifier($this->table) + . " WHERE expires < " . $this->db->now()); + } +}
View file
chwala-0.1.tar.gz/lib/file_storage.php -> chwala-0.2.tar.gz/lib/file_storage.php
Changed
@@ -30,13 +30,21 @@ const CAPS_PROGRESS_NAME = 'PROGRESS_NAME'; const CAPS_PROGRESS_TIME = 'PROGRESS_TIME'; const CAPS_QUOTA = 'QUOTA'; + const CAPS_LOCKS = 'LOCKS'; // config const SEPARATOR = '/'; // error codes const ERROR = 500; + const ERROR_LOCKED = 423; const ERROR_FILE_EXISTS = 550; + const ERROR_UNSUPPORTED = 570; + + // locks + const LOCK_SHARED = 'shared'; + const LOCK_EXCLUSIVE = 'exclusive'; + const LOCK_INFINITE = 'infinite'; /** * Authenticates a user @@ -179,6 +187,48 @@ public function folder_list(); /** + * Returns a list of locks + * + * This method should return all the locks for a particular URI, including + * locks that might be set on a parent URI. + * + * If child_locks is set to true, this method should also look for + * any locks in the subtree of the URI for locks. + * + * @param string $uri URI + * @param bool $child_locks Enables subtree checks + * + * @return array List of locks + * @throws Exception + */ + public function lock_list($uri, $child_locks = false); + + /** + * Locks a URI + * + * @param string $uri URI + * @param array $lock Lock data + * - depth: 0/'infinite' + * - scope: 'shared'/'exclusive' + * - owner: string + * - token: string + * - timeout: int + * + * @throws Exception + */ + public function lock($uri, $lock); + + /** + * Removes a lock from a URI + * + * @param string $path URI + * @param array $lock Lock data + * + * @throws Exception + */ + public function unlock($uri, $lock); + + /** * Return disk quota information for specified folder. * * @param string $folder_name Name of a folder with full path
View file
chwala-0.1.tar.gz/lib/file_ui_api.php -> chwala-0.2.tar.gz/lib/file_ui_api.php
Changed
@@ -69,28 +69,39 @@ public static function configure($request) { // Configure connection options - $config = rcube::get_instance()->config; - $options = array( - 'ssl_verify_peer', - 'ssl_verify_host', - 'ssl_cafile', - 'ssl_capath', - 'ssl_local_cert', - 'ssl_passphrase', - 'follow_redirects', - ); - - foreach ($options as $optname) { - if (($optvalue = $config->get($optname)) !== null) { - try { - $request->setConfig($optname, $optvalue); - } - catch (Exception $e) { -// rcube::log_error("HTTP: " . $e->getMessage()); + $config = rcube::get_instance()->config; + $http_config = (array) $config->get('http_request', $config->get('kolab_http_request')); + + // Deprecated config, all options are separated variables + if (empty($http_config)) { + $options = array( + 'ssl_verify_peer', + 'ssl_verify_host', + 'ssl_cafile', + 'ssl_capath', + 'ssl_local_cert', + 'ssl_passphrase', + 'follow_redirects', + ); + + foreach ($options as $optname) { + if (($optvalue = $config->get($optname)) !== null + || ($optvalue = $config->get('kolab_' . $optname)) !== null + ) { + $http_config[$optname] = $optvalue; } } } + if (!empty($http_config)) { + try { + $request->setConfig($http_config); + } + catch (Exception $e) { +// rcube::log_error("HTTP: " . $e->getMessage()); + } + } + // proxy User-Agent $request->setHeader('user-agent', $_SERVER['HTTP_USER_AGENT']); }
View file
chwala-0.1.tar.gz/lib/kolab/kolab_file_storage.php -> chwala-0.2.tar.gz/lib/kolab/kolab_file_storage.php
Changed
@@ -270,6 +270,7 @@ return array( file_storage::CAPS_MAX_UPLOAD => $max_filesize, file_storage::CAPS_QUOTA => $quota, + file_storage::CAPS_LOCKS => true, ); } @@ -748,6 +749,84 @@ } /** + * Returns a list of locks + * + * This method should return all the locks for a particular URI, including + * locks that might be set on a parent URI. + * + * If child_locks is set to true, this method should also look for + * any locks in the subtree of the URI for locks. + * + * @param string $uri URI + * @param bool $child_locks Enables subtree checks + * + * @return array List of locks + * @throws Exception + */ + public function lock_list($uri, $child_locks = false) + { + $this->init_lock_db(); + + // convert URI to global resource string + $uri = $this->uri2resource($uri); + + // get locks list + $list = $this->lock_db->lock_list($uri, $child_locks); + + // convert back resource string into URIs + foreach ($list as $idx => $lock) { + $list[$idx]['uri'] = $this->resource2uri($lock['uri']); + } + + return $list; + } + + /** + * Locks a URI + * + * @param string $uri URI + * @param array $lock Lock data + * - depth: 0/'infinite' + * - scope: 'shared'/'exclusive' + * - owner: string + * - token: string + * - timeout: int + * + * @throws Exception + */ + public function lock($uri, $lock) + { + $this->init_lock_db(); + + // convert URI to global resource string + $uri = $this->uri2resource($uri); + + if (!$this->lock_db->lock($uri, $lock)) { + throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR); + } + } + + /** + * Removes a lock from a URI + * + * @param string $path URI + * @param array $lock Lock data + * + * @throws Exception + */ + public function unlock($uri, $lock) + { + $this->init_lock_db(); + + // convert URI to global resource string + $uri = $this->uri2resource($uri); + + if (!$this->lock_db->unlock($uri, $lock)) { + throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR); + } + } + + /** * Return disk quota information for specified folder. * * @param string $folder_name Name of a folder with full path @@ -873,4 +952,100 @@ return $file; } + + protected function uri2resource($uri) + { + $storage = $this->rc->get_storage(); + $namespace = $storage->get_namespace(); + $separator = $storage->get_hierarchy_delimiter(); + $uri = str_replace(file_storage::SEPARATOR, $separator, $uri); + $owner = $this->rc->get_user_name(); + + // find the owner and remove namespace prefix + foreach ($namespace as $type => $ns) { + foreach ($ns as $root) { + if (is_array($root) && $root[0] && strpos($uri, $root[0]) === 0) { + $uri = substr($uri, strlen($root[0])); + + switch ($type) { + case 'shared': + // in theory there can be more than one shared root + // we add it to dummy user name, so we can revert conversion + $owner = "shared({$root[0]})"; + break; + + case 'other': + list($user, $uri) = explode($separator, $uri, 2); + + if (strpos($user, '@') === false) { + $domain = strstr($owner, '@'); + if (!empty($domain)) { + $user .= $domain; + } + } + + $owner = $user; + break; + } + + break 2; + } + } + } + + // convert to imap charset (to be safe to store in DB) + $uri = rcube_charset::convert($uri, RCUBE_CHARSET, 'UTF7-IMAP'); + + return 'imap://' . urlencode($owner) . '@' . $storage->options['host'] . '/' . $uri; + } + + protected function resource2uri($resource) + { + if (!preg_match('|^imap://([^@]+)@([^/]+)/(.*)$|', $resource, $matches)) { + throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR); + } + + $storage = $this->rc->get_storage(); + $separator = $storage->get_hierarchy_delimiter(); + $owner = $this->rc->get_user_name(); + + $user = urldecode($matches[1]); + $uri = $matches[3]; + + // convert from imap charset (to be safe to store in DB) + $uri = rcube_charset::convert($uri, 'UTF7-IMAP', RCUBE_CHARSET); + + // personal namespace + if ($user == $owner) { + // do nothing + // Note: that might not work if personal namespace uses e.g. INBOX/ prefix. + } + // shared namespace + else if (preg_match('/^shared\((.*)\)$/', $user, $matches)) { + $uri = $matches[1] . $uri; + } + // other users namespace + else { + $namespace = $storage->get_namespace('other'); + + list($local, $domain) = explode('@', $user); + + // here we assume there's only one other users namespace root + $uri = $namespace[0][0] . $local . $separator . $uri; + } + + $uri = str_replace($separator, file_storage::SEPARATOR, $uri); + + return $uri; + } + + /** + * Initializes file_locks object + */ + protected function init_lock_db() + { + if (!$this->lock_db) { + $this->lock_db = new file_locks; + } + } }
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/config.inc.php.dist -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/config.inc.php.dist
Changed
@@ -72,5 +72,9 @@ ), ); +// List of LDAP addressbooks (keys of ldap_public configuration array) +// for which base_dn variables (%dc, etc.) will be replaced according to authenticated user DN +// Note: special name '*' for all LDAP addressbooks +$rcmail_config['kolab_auth_ldap_addressbooks'] = array('*'); ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/kolab_auth.php -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/kolab_auth.php
Changed
@@ -12,7 +12,7 @@ * @version @package_version@ * @author Aleksander Machniak <machniak@kolabsys.com> * - * Copyright (C) 2011-2012, Kolab Systems AG <contact@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 @@ -37,56 +37,103 @@ { $rcmail = rcube::get_instance(); + $this->load_config(); + $this->add_hook('authenticate', array($this, 'authenticate')); $this->add_hook('startup', array($this, 'startup')); $this->add_hook('user_create', array($this, 'user_create')); + // Hook for password change + $this->add_hook('password_ldap_bind', array($this, 'password_ldap_bind')); + // Hooks related to "Login As" feature $this->add_hook('template_object_loginform', array($this, 'login_form')); $this->add_hook('storage_connect', array($this, 'imap_connect')); $this->add_hook('managesieve_connect', array($this, 'imap_connect')); $this->add_hook('smtp_connect', array($this, 'smtp_connect')); + $this->add_hook('identity_form', array($this, 'identity_form')); + + // Hook to modify some configuration, e.g. ldap + $this->add_hook('config_get', array($this, 'config_get')); - $this->add_hook('write_log', array($this, 'write_log')); + // Enable debug logs per-user, this enables logging only after + // user has logged in + if (!empty($_SESSION['username']) && $rcmail->config->get('kolab_auth_auditlog')) { + $this->add_hook('write_log', array($this, 'write_log')); - // TODO: This section does not actually seem to work - if ($rcmail->config->get('kolab_auth_auditlog', false)) { $rcmail->config->set('debug_level', 1); $rcmail->config->set('devel_mode', true); $rcmail->config->set('smtp_log', true); $rcmail->config->set('log_logins', true); $rcmail->config->set('log_session', true); - $rcmail->config->set('sql_debug', true); $rcmail->config->set('memcache_debug', true); $rcmail->config->set('imap_debug', true); $rcmail->config->set('ldap_debug', true); $rcmail->config->set('smtp_debug', true); - } + $rcmail->config->set('sql_debug', true); + // SQL debug need to be set directly on DB object + // setting config variable will not work here because + // the object is already initialized/configured + if ($db = $rcmail->get_dbh()) { + $db->set_debug(true); + } + } } public function startup($args) { - // Arguments are task / action, not interested - if (!empty($_SESSION['user_roledns'])) { - $this->load_user_role_plugins_and_settings($_SESSION['user_roledns']); + $this->load_user_role_plugins_and_settings(); + + return $args; + } + + /** + * Modify some configuration according to LDAP user record + */ + public function config_get($args) + { + // Replaces ldap_vars (%dc, etc) in public kolab ldap addressbooks + // config based on the users base_dn. (for multi domain support) + if ($args['name'] == 'ldap_public' && !empty($args['result'])) { + $rcmail = rcube::get_instance(); + $kolab_books = (array) $rcmail->config->get('kolab_auth_ldap_addressbooks'); + + foreach ($args['result'] as $name => $config) { + if (in_array($name, $kolab_books) || in_array('*', $kolab_books)) { + $args['result'][$name]['base_dn'] = self::parse_ldap_vars($config['base_dn']); + $args['result'][$name]['search_base_dn'] = self::parse_ldap_vars($config['search_base_dn']); + $args['result'][$name]['bind_dn'] = str_replace('%dn', $_SESSION['kolab_dn'], $config['bind_dn']); + + if (!empty($config['groups'])) { + $args['result'][$name]['groups']['base_dn'] = self::parse_ldap_vars($config['groups']['base_dn']); + } + } + } } return $args; } - public function load_user_role_plugins_and_settings($role_dns) + /** + * Modifies list of plugins and settings according to + * specified LDAP roles + */ + public function load_user_role_plugins_and_settings() { - $rcmail = rcube::get_instance(); - $this->load_config(); + if (empty($_SESSION['user_roledns'])) { + return; + } - // Check role dependent plugins to enable and settings to modify + $rcmail = rcube::get_instance(); // Example 'kolab_auth_role_plugins' = // // Array( // '<role_dn>' => Array('plugin1', 'plugin2'), // ); + // + // NOTE that <role_dn> may in fact be something like: 'cn=role,%dc' $role_plugins = $rcmail->config->get('kolab_auth_role_plugins'); @@ -101,10 +148,24 @@ // ), // ), // ); + // + // NOTE that <role_dn> may in fact be something like: 'cn=role,%dc' $role_settings = $rcmail->config->get('kolab_auth_role_settings'); - foreach ($role_dns as $role_dn) { + if (!empty($role_plugins)) { + foreach ($role_plugins as $role_dn => $plugins) { + $role_plugins[self::parse_ldap_vars($role_dn)] = $plugins; + } + } + + if (!empty($role_settings)) { + foreach ($role_settings as $role_dn => $settings) { + $role_settings[self::parse_ldap_vars($role_dn)] = $settings; + } + } + + foreach ($_SESSION['user_roledns'] as $role_dn) { if (isset($role_plugins[$role_dn]) && is_array($role_plugins[$role_dn])) { foreach ($role_plugins[$role_dn] as $plugin) { $this->require_plugin($plugin); @@ -152,53 +213,49 @@ } } + /** + * Logging method replacement to print debug/errors into + * a separate (sub)folder for each user + */ public function write_log($args) { $rcmail = rcube::get_instance(); - if (!$rcmail->config->get('kolab_auth_auditlog', false)) { - return $args; - } - - $args['abort'] = true; - if ($rcmail->config->get('log_driver') == 'syslog') { - $prio = $args['name'] == 'errors' ? LOG_ERR : LOG_INFO; - syslog($prio, $args['line']); return $args; } - else { - $line = sprintf("[%s]: %s\n", $args['date'], $args['line']); - // log_driver == 'file' is assumed here - $log_dir = $rcmail->config->get('log_dir', INSTALL_PATH . 'logs'); - $log_path = $log_dir.'/'.strtolower($_SESSION['kolab_auth_admin']).'/'.strtolower($_SESSION['username']); + $line = sprintf("[%s]: %s\n", $args['date'], $args['line']); - // Append original username + target username - if (!is_dir($log_path)) { - // Attempt to create the directory - if (@mkdir($log_path, 0750, true)) { - $log_dir = $log_path; - } - } - else { + // log_driver == 'file' is assumed here + $log_dir = $rcmail->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); + $log_path = $log_dir.'/'.strtolower($_SESSION['kolab_auth_admin']).'/'.strtolower($_SESSION['username']); + + // Append original username + target username + if (!is_dir($log_path)) { + // Attempt to create the directory + if (@mkdir($log_path, 0750, true)) { $log_dir = $log_path; } + } + else { + $log_dir = $log_path; + } - // try to open specific log file for writing - $logfile = $log_dir.'/'.$args['name']; + // try to open specific log file for writing + $logfile = $log_dir.'/'.$args['name']; - if ($fp = fopen($logfile, 'a')) { - fwrite($fp, $line); - fflush($fp); - fclose($fp); - return $args; - } - else { - trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING); - } + if ($fp = fopen($logfile, 'a')) { + fwrite($fp, $line); + fflush($fp); + fclose($fp); + } + else { + trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING); } + $args['abort'] = true; + return $args; } @@ -241,7 +298,6 @@ */ public function login_form($args) { - $this->load_config(); $this->add_texts('localization/'); $rcmail = rcube::get_instance(); @@ -284,6 +340,16 @@ $ldap = self::ldap(); if (!$ldap || !$ldap->ready) { $args['abort'] = true; + $message = sprintf( + 'Login failure for user %s from %s in session %s (error %s)', + $user, + rcube_utils::remote_ip(), + session_id(), + "LDAP not ready" + ); + + rcube::write_log('userlogins', $message); + return $args; } @@ -292,6 +358,16 @@ if (empty($record)) { $args['abort'] = true; + $message = sprintf( + 'Login failure for user %s from %s in session %s (error %s)', + $user, + rcube_utils::remote_ip(), + session_id(), + "No user record found" + ); + + rcube::write_log('userlogins', $message); + return $args; } @@ -303,11 +379,21 @@ $email_attr = $rcmail->config->get('kolab_auth_email'); $org_attr = $rcmail->config->get('kolab_auth_organization'); $role_attr = $rcmail->config->get('kolab_auth_role'); + $imap_attr = $rcmail->config->get('kolab_auth_mailhost'); if (!empty($role_attr) && !empty($record[$role_attr])) { $_SESSION['user_roledns'] = (array)($record[$role_attr]); } + if (!empty($imap_attr) && !empty($record[$imap_attr])) { + $default_host = $rcmail->config->get('default_host'); + if (!empty($default_host)) { + rcube::write_log("errors", "Both default host and kolab_auth_mailhost set. Incompatible."); + } else { + $args['host'] = "tls://" . $record[$imap_attr]; + } + } + // Login As... if (!empty($loginas) && $admin_login) { // Authenticate to LDAP @@ -315,6 +401,16 @@ if (!$result) { $args['abort'] = true; + $message = sprintf( + 'Login failure for user %s from %s in session %s (error %s)', + $user, + rcube_utils::remote_ip(), + session_id(), + "Unable to bind with '" . $record['dn'] . "'" + ); + + rcube::write_log('userlogins', $message); + return $args; } @@ -356,6 +452,17 @@ if (empty($record)) { $args['abort'] = true; + $message = sprintf( + 'Login failure for user %s (as user %s) from %s in session %s (error %s)', + $user, + $loginas, + rcube_utils::remote_ip(), + session_id(), + "No user record found for '" . $loginas . "'" + ); + + rcube::write_log('userlogins', $message); + return $args; } @@ -371,6 +478,12 @@ $_SESSION['kolab_uid'] = is_array($record['uid']) ? $record['uid'][0] : $record['uid']; $_SESSION['kolab_dn'] = $record['dn']; + // Store LDAP replacement variables used for current user + // This improves performance of load_user_role_plugins_and_settings() + // which is executed on every request (via startup hook) and where + // we don't like to use LDAP (connection + bind + search) + $_SESSION['kolab_auth_vars'] = $ldap->get_parse_vars(); + // Set user login if ($login_attr) { $this->data['user_login'] = is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr]; @@ -413,6 +526,20 @@ } /** + * Set user DN for password change (password plugin with ldap_simple driver) + */ + public function password_ldap_bind($args) + { + $args['user_dn'] = $_SESSION['kolab_dn']; + + $rcmail = rcube::get_instance(); + + $rcmail->config->set('password_ldap_method', 'user'); + + return $args; + } + + /** * Sets SASL Proxy login/password for IMAP and Managesieve auth */ public function imap_connect($args) @@ -447,6 +574,48 @@ } /** + * Hook to replace the plain text input field for email address by a drop-down list + * with all email addresses (including aliases) from this user's LDAP record. + */ + public function identity_form($args) + { + $rcmail = rcube::get_instance(); + $ident_level = intval($rcmail->config->get('identities_level', 0)); + + // do nothing if email address modification is disabled + if ($ident_level == 1 || $ident_level == 3) { + return $args; + } + + $ldap = self::ldap(); + if (!$ldap || !$ldap->ready || empty($_SESSION['kolab_dn'])) { + return $args; + } + + $emails = array(); + $user_record = $ldap->get_record($_SESSION['kolab_dn']); + + foreach ((array)$rcmail->config->get('kolab_auth_email', array()) as $col) { + $values = rcube_addressbook::get_col_values($col, $user_record, true); + if (!empty($values)) + $emails = array_merge($emails, array_filter($values)); + } + + // kolab_delegation might want to modify this addresses list + $plugin = $rcmail->plugins->exec_hook('kolab_auth_emails', array('emails' => $emails)); + $emails = $plugin['emails']; + + if (!empty($emails)) { + $args['form']['addressing']['content']['email'] = array( + 'type' => 'select', + 'options' => array_combine($emails, $emails), + ); + } + + return $args; + } + + /** * Initializes LDAP object and connects to LDAP server */ public static function ldap() @@ -455,18 +624,7 @@ return self::$ldap; } - $rcmail = rcube::get_instance(); - - // $this->load_config(); - // we're in static method, load config manually - $fpath = $rcmail->plugins->dir . '/kolab_auth/config.inc.php'; - if (is_file($fpath) && !$rcmail->config->load_from_file($fpath)) { - rcube::raise_error(array( - 'code' => 527, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Failed to load config from $fpath"), true, false); - } - + $rcmail = rcube::get_instance(); $addressbook = $rcmail->config->get('kolab_auth_addressbook'); if (!is_array($addressbook)) { @@ -484,4 +642,21 @@ return self::$ldap; } + + /** + * Parses LDAP DN string with replacing supported variables. + * See kolab_auth_ldap::parse_vars() + * + * @param string $str LDAP DN string + * + * @return string Parsed DN string + */ + public static function parse_ldap_vars($str) + { + if (!empty($_SESSION['kolab_auth_vars'])) { + $str = strtr($str, $_SESSION['kolab_auth_vars']); + } + + return $str; + } }
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/kolab_auth_ldap.php -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/kolab_auth_ldap.php
Changed
@@ -287,7 +287,8 @@ if ($limit && $limit <= $i) { break; } - $dn = $result->get_dn(); + $dn = $result->get_dn(); + $entry = rcube_ldap_generic::normalize_entry($entry); $list[$dn] = $this->field_mapping($dn, $entry); $i++; } @@ -369,7 +370,8 @@ if (!$user) { $user = $_SESSION['username']; } - else if (isset($this->icache[$user])) { + + if (isset($this->icache[$user])) { list($user, $dc) = $this->icache[$user]; } else { @@ -391,17 +393,11 @@ list($usr, $dom) = explode('@', $user); // unrealm domain, user login can contain a domain alias - if ($dom != $domain && ($r_domain = $this->find_domain($dom))) { - // $dom is a domain DN string? - if (strpos($r_domain, '=')) { - $dc = $r_domain; - } - else { - $user = $usr . '@' . $r_domain; - } + if ($dom != $domain && ($dc = $this->find_domain($dom))) { + // @FIXME: we should replace domain in $user, I suppose } } - else if ($domain && !strpos($user, '@')) { + else if ($domain) { $user .= '@' . $domain; } @@ -418,6 +414,8 @@ $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u); + $this->parse_replaces = $replaces; + return strtr($str, $replaces); } @@ -426,7 +424,7 @@ * * @param string $domain Domain name * - * @return string Domain name or domain DN string + * @return string Domain DN string */ function find_domain($domain) { @@ -458,11 +456,23 @@ return $entry['inetdomainbasedn']; } - return is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr]; + $domain = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr]; + + return $domain ? 'dc=' . implode(',dc=', explode('.', $domain)) : null; } } /** + * Returns variables used for replacement in (last) parse_vars() call + * + * @return array Variable-value hash array + */ + public function get_parse_vars() + { + return $this->parse_replaces; + } + + /** * HTML-safe DN string encoding * * @param string $str DN string
View file
chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/bg_BG.inc
Added
@@ -0,0 +1,3 @@ +<?php +$labels['loginas'] = 'Влизане като'; +?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/de_CH.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/de_CH.inc
Changed
@@ -1,5 +1,3 @@ <?php - $labels['loginas'] = 'Anmelden als'; - ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/de_DE.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/de_DE.inc
Changed
@@ -1,5 +1,3 @@ <?php - $labels['loginas'] = 'Anmelden als'; - ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/es_ES.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/es_ES.inc
Changed
@@ -1,5 +1,2 @@ <?php - -$labels['loginas'] = 'Login As'; - ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/et_EE.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/et_EE.inc
Changed
@@ -1,5 +1,2 @@ <?php - -$labels['loginas'] = 'Login As'; - ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/fr_FR.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/fr_FR.inc
Changed
@@ -1,5 +1,3 @@ <?php - $labels['loginas'] = 'Se connecter en tant que'; - ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/ja_JP.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/ja_JP.inc
Changed
@@ -1,5 +1,3 @@ <?php - -$labels['loginas'] = 'Login As'; - +$labels['loginas'] = 'ログイン'; ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/nl_NL.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/nl_NL.inc
Changed
@@ -1,5 +1,3 @@ <?php - $labels['loginas'] = 'Log in als'; - ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/pl_PL.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/pl_PL.inc
Changed
@@ -1,5 +1,3 @@ <?php - $labels['loginas'] = 'Zaloguj jako'; - ?>
View file
chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/pt_BR.inc
Added
@@ -0,0 +1,3 @@ +<?php +$labels['loginas'] = 'Logar como'; +?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/localization/ru_RU.inc -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/localization/ru_RU.inc
Changed
@@ -1,5 +1,3 @@ <?php - $labels['loginas'] = 'Войти как'; - ?>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/kolab_auth/package.xml -> chwala-0.2.tar.gz/lib/kolab/plugins/kolab_auth/package.xml
Changed
@@ -18,10 +18,10 @@ <email>machniak@kolabsys.com</email> <active>yes</active> </lead> - <date>2013-06-25</date> + <date>2013-10-04</date> <version> - <release>0.7</release> - <api>0.2</api> + <release>1.0</release> + <api>1.0</api> </version> <stability> <release>stable</release>
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/SQL/mysql.initial.sql -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/SQL/mysql.initial.sql
Changed
@@ -1,29 +1,175 @@ /** * libkolab database schema * - * @version @package_version@ + * @version 1.0 * @author Thomas Bruederli * @licence GNU AGPL **/ + +DROP TABLE IF EXISTS `kolab_folders`; + +CREATE TABLE `kolab_folders` ( + `folder_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `resource` VARCHAR(255) NOT NULL, + `type` VARCHAR(32) NOT NULL, + `synclock` INT(10) NOT NULL DEFAULT '0', + `ctag` VARCHAR(40) DEFAULT NULL, + PRIMARY KEY(`folder_id`), + INDEX `resource_type` (`resource`, `type`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + DROP TABLE IF EXISTS `kolab_cache`; -CREATE TABLE `kolab_cache` ( - `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL, +DROP TABLE IF EXISTS `kolab_cache_contact`; + +CREATE TABLE `kolab_cache_contact` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, + CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), + INDEX `contact_type` (`folder_id`,`type`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +DROP TABLE IF EXISTS `kolab_cache_event`; + +CREATE TABLE `kolab_cache_event` ( + `folder_id` BIGINT UNSIGNED NOT NULL, `msguid` BIGINT UNSIGNED NOT NULL, `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, `created` DATETIME DEFAULT NULL, `changed` DATETIME DEFAULT NULL, `data` TEXT NOT NULL, `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, `dtstart` DATETIME, `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +DROP TABLE IF EXISTS `kolab_cache_task`; + +CREATE TABLE `kolab_cache_task` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +DROP TABLE IF EXISTS `kolab_cache_journal`; + +CREATE TABLE `kolab_cache_journal` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +DROP TABLE IF EXISTS `kolab_cache_note`; + +CREATE TABLE `kolab_cache_note` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +DROP TABLE IF EXISTS `kolab_cache_file`; + +CREATE TABLE `kolab_cache_file` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, `tags` VARCHAR(255) NOT NULL, `words` TEXT NOT NULL, `filename` varchar(255) DEFAULT NULL, - PRIMARY KEY(`resource`,`type`,`msguid`), - INDEX `resource_filename` (`resource`, `filename`) + CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), + INDEX `folder_filename` (`folder_id`, `filename`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2013041900'); +DROP TABLE IF EXISTS `kolab_cache_configuration`; + +CREATE TABLE `kolab_cache_configuration` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, + CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), + INDEX `configuration_type` (`folder_id`,`type`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +DROP TABLE IF EXISTS `kolab_cache_freebusy`; + +CREATE TABLE `kolab_cache_freebusy` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + + +INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2013100400');
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/SQL/mysql/2013100400.sql
Added
@@ -0,0 +1,174 @@ +CREATE TABLE `kolab_folders` ( + `folder_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `resource` VARCHAR(255) NOT NULL, + `type` VARCHAR(32) NOT NULL, + `synclock` INT(10) NOT NULL DEFAULT '0', + `ctag` VARCHAR(40) DEFAULT NULL, + PRIMARY KEY(`folder_id`), + INDEX `resource_type` (`resource`, `type`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_contact` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, + CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), + INDEX `contact_type` (`folder_id`,`type`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_event` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_task` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_journal` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_note` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_file` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `filename` varchar(255) DEFAULT NULL, + CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), + INDEX `folder_filename` (`folder_id`, `filename`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_configuration` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, + CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), + INDEX `configuration_type` (`folder_id`,`type`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + +CREATE TABLE `kolab_cache_freebusy` ( + `folder_id` BIGINT UNSIGNED NOT NULL, + `msguid` BIGINT UNSIGNED NOT NULL, + `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL, + `created` DATETIME DEFAULT NULL, + `changed` DATETIME DEFAULT NULL, + `data` TEXT NOT NULL, + `xml` TEXT NOT NULL, + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `dtstart` DATETIME, + `dtend` DATETIME, + CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`) +) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + + +-- Migrate data from old kolab_cache table + +INSERT INTO kolab_folders (resource, type) + SELECT DISTINCT resource, type + FROM kolab_cache WHERE type IN ('event','contact','task','file'); + +INSERT INTO kolab_cache_event (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend) + SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend + FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) + WHERE kolab_cache.type = 'event' AND kolab_folders.folder_id IS NOT NULL; + +INSERT INTO kolab_cache_task (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend) + SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend + FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) + WHERE kolab_cache.type = 'task' AND kolab_folders.folder_id IS NOT NULL; + +INSERT INTO kolab_cache_contact (folder_id, msguid, uid, created, changed, data, xml, tags, words, type) + SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, kolab_cache.type + FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) + WHERE kolab_cache.type IN ('contact','distribution-list') AND kolab_folders.folder_id IS NOT NULL; + +INSERT INTO kolab_cache_file (folder_id, msguid, uid, created, changed, data, xml, tags, words, filename) + SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, filename + FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource) + WHERE kolab_cache.type = 'file' AND kolab_folders.folder_id IS NOT NULL; + + +DROP TABLE IF EXISTS `kolab_cache`; +
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/SQL/postgres.initial.sql
Added
@@ -0,0 +1,31 @@ +/** + * libkolab database schema + * + * @version @package_version@ + * @author Sidlyarenko Sergey + * @licence GNU AGPL + **/ + +DROP TABLE IF EXISTS kolab_cache; + +CREATE TABLE kolab_cache ( + resource character varying(255) NOT NULL, + type character varying(32) NOT NULL, + msguid NUMERIC(20) NOT NULL, + uid character varying(128) NOT NULL, + created timestamp without time zone DEFAULT NULL, + changed timestamp without time zone DEFAULT NULL, + data text NOT NULL, + xml text NOT NULL, + dtstart timestamp without time zone, + dtend timestamp without time zone, + tags character varying(255) NOT NULL, + words text NOT NULL, + filename character varying(255) DEFAULT NULL, + PRIMARY KEY(resource, type, msguid) +); + +CREATE INDEX kolab_cache_resource_filename_idx ON kolab_cache (resource, filename); + + +INSERT INTO system (name, value) VALUES ('libkolab-version', '2013041900');
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/bin/modcache.sh -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/bin/modcache.sh
Changed
@@ -4,7 +4,7 @@ /** * Kolab storage cache modification script * - * @version 3.0 + * @version 3.1 * @author Thomas Bruederli <bruederli@kolabsys.com> * * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> @@ -56,7 +56,14 @@ $opts['username'] = !empty($opts[1]) ? $opts[1] : $opts['user']; $action = $opts[0]; -$rcmail = rcube::get_instance(); +$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS); + + +// connect to database +$db = $rcmail->get_dbh(); +$db->db_connect('w'); +if (!$db->is_connected() || $db->is_error()) + die("No DB connection\n"); /* @@ -68,32 +75,42 @@ * Clear/expunge all cache records */ case 'expunge': + $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','configuration','event','file','journal','note','task'); + $folder_types_db = array_map(array($db, 'quote'), $folder_types); $expire = strtotime(!empty($opts[2]) ? $opts[2] : 'now - 10 days'); - $sql_add = " AND created <= '" . date('Y-m-d 00:00:00', $expire) . "'"; + $sql_where = "type IN (" . join(',', $folder_types_db) . ")"; + + if ($opts['username']) { + $sql_where .= ' AND resource LIKE ?'; + } + + $sql_query = "DELETE FROM %s WHERE folder_id IN (SELECT folder_id FROM kolab_folders WHERE $sql_where) AND created <= " . $db->quote(date('Y-m-d 00:00:00', $expire)); if ($opts['limit']) { - $sql_add .= ' LIMIT ' . intval($opts['limit']); + $sql_query = ' LIMIT ' . intval($opts['limit']); + } + foreach ($folder_types as $type) { + $table_name = 'kolab_cache_' . $type; + $db->query(sprintf($sql_query, $table_name), resource_prefix($opts).'%'); + echo $db->affected_rows() . " records deleted from '$table_name'\n"; } -case 'clear': - // connect to database - $db = $rcmail->get_dbh(); - $db->db_connect('w'); - if (!$db->is_connected() || $db->is_error()) - die("No DB connection\n"); + $db->query("UPDATE kolab_folders SET ctag='' WHERE $sql_where", resource_prefix($opts).'%'); + break; - $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','distribution-list','event','task','configuration'); +case 'clear': + $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','configuration','event','file','journal','note','task'); $folder_types_db = array_map(array($db, 'quote'), $folder_types); if ($opts['all']) { - $sql_query = "DELETE FROM kolab_cache WHERE type IN (" . join(',', $folder_types_db) . ")"; + $sql_query = "DELETE FROM kolab_folders WHERE 1"; } else if ($opts['username']) { - $sql_query = "DELETE FROM kolab_cache WHERE type IN (" . join(',', $folder_types_db) . ") AND resource LIKE ?"; + $sql_query = "DELETE FROM kolab_folders WHERE type IN (" . join(',', $folder_types_db) . ") AND resource LIKE ?"; } if ($sql_query) { $db->query($sql_query . $sql_add, resource_prefix($opts).'%'); - echo $db->affected_rows() . " records deleted from 'kolab_cache'\n"; + echo $db->affected_rows() . " records deleted from 'kolab_folders'\n"; } break; @@ -106,7 +123,7 @@ $rcmail->plugins->load_plugin('libkolab'); if (authenticate($opts)) { - $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','event','task','configuration'); + $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','configuration','event','file','task'); foreach ($folder_types as $type) { // sync every folder of the given type foreach (kolab_storage::get_folders($type) as $folder) { @@ -140,7 +157,7 @@ */ function resource_prefix($opts) { - return 'imap://' . urlencode($opts['username']) . '@' . $opts['host'] . '/'; + return 'imap://' . str_replace('%', '\\%', urlencode($opts['username'])) . '@' . $opts['host'] . '/'; }
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/config.inc.php.dist -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/config.inc.php.dist
Changed
@@ -12,11 +12,21 @@ // Defaults to https://<imap-server->/freebusy $rcmail_config['kolab_freebusy_server'] = 'https://<some-host>/<freebusy-path>'; -// Set this option to disable SSL certificate checks when triggering Free/Busy (enabled by default) -$rcmail_config['kolab_ssl_verify_peer'] = false; - // Enables listing of only subscribed folders. This e.g. will limit // folders in calendar view or available addressbooks $rcmail_config['kolab_use_subscriptions'] = false; -?> +// Enables the use of displayname folder annotations as introduced in KEP:? +// for displaying resource folder names (experimental!) +$rcmail_config['kolab_custom_display_names'] = false; + +// Configuration of HTTP requests. +// See http://pear.php.net/manual/en/package.http.http-request2.config.php +// for list of supported configuration options (array keys) +$rcmail_config['kolab_http_request'] = array(); + +// When kolab_cache is enabled Roundcube's messages cache will be redundant +// when working on kolab folders. Here we can: +// 2 - bypass messages/indexes cache completely +// 1 - bypass only messages, but use index cache +$rcmail_config['kolab_messages_cache_bypass'] = 0;
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_date_recurrence.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_date_recurrence.php
Changed
@@ -101,16 +101,35 @@ /** * Get the end date of the occurence of this recurrence cycle * - * @param string Date limit (where infinite recurrences should abort) * @return mixed Timestamp with end date of the last event or False if recurrence exceeds limit */ - public function end($limit = 'now +1 year') + public function end() { - $limit_dt = new DateTime($limit); - if ($this->engine && ($cend = $this->engine->getLastOccurrence()) && ($end_dt = kolab_format::php_datetime(new cDateTime($cend))) && $end_dt < $limit_dt) { + $event = $this->object->to_array(); + + // recurrence end date is given + if ($event['recurrence']['UNTIL'] instanceof DateTime) { + return $event['recurrence']['UNTIL']->format('U'); + } + + // let libkolab do the work + if ($this->engine && ($cend = $this->engine->getLastOccurrence()) && ($end_dt = kolab_format::php_datetime(new cDateTime($cend)))) { return $end_dt->format('U'); } + // determine a reasonable end date if none given + if (!$event['recurrence']['COUNT']) { + switch ($event['recurrence']['FREQ']) { + case 'YEARLY': $intvl = 'P100Y'; break; + case 'MONTHLY': $intvl = 'P20Y'; break; + default: $intvl = 'P10Y'; break; + } + + $end_dt = clone $event['start']; + $end_dt->add(new DateInterval($intvl)); + return $end_dt->format('U'); + } + return false; } }
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format.php
Changed
@@ -40,6 +40,7 @@ protected $data; protected $xmldata; protected $xmlobject; + protected $formaterror; protected $loaded = false; protected $version = '3.0'; @@ -104,13 +105,17 @@ } $result = new cDateTime(); - // got a unix timestamp (in UTC) - if (is_numeric($datetime)) { - $datetime = new DateTime('@'.$datetime, new DateTimeZone('UTC')); - if ($tz) $datetime->setTimezone($tz); + try { + // got a unix timestamp (in UTC) + if (is_numeric($datetime)) { + $datetime = new DateTime('@'.$datetime, new DateTimeZone('UTC')); + if ($tz) $datetime->setTimezone($tz); + } + else if (is_string($datetime) && strlen($datetime)) { + $datetime = new DateTime($datetime, $tz ?: null); + } } - else if (is_string($datetime) && strlen($datetime)) - $datetime = new DateTime($datetime, $tz ?: null); + catch (Exception $e) {} if ($datetime instanceof DateTime) { $result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j')); @@ -244,7 +249,7 @@ $log = "Error"; } - if ($log) { + if ($log && !isset($this->formaterror)) { rcube::raise_error(array( 'code' => 660, 'type' => 'php', @@ -252,6 +257,8 @@ 'line' => __LINE__, 'message' => "kolabformat $log: " . kolabformat::errorMessage(), ), true); + + $this->formaterror = $ret; } return $ret; @@ -338,6 +345,7 @@ */ public function load($xml) { + $this->formaterror = null; $read_func = $this->libfunc($this->read_func); if (is_array($read_func)) @@ -361,6 +369,8 @@ */ public function write($version = null) { + $this->formaterror = null; + $this->init(); $write_func = $this->libfunc($this->write_func); if (is_array($write_func)) @@ -389,24 +399,33 @@ $this->obj->setUid($object['uid']); // set some automatic values if missing - if (method_exists($this->obj, 'setCreated') && !$this->obj->created()) { - if (empty($object['created'])) - $object['created'] = new DateTime('now', self::$timezone); - $this->obj->setCreated(self::get_datetime($object['created'])); + if (empty($object['created']) && method_exists($this->obj, 'setCreated')) { + $cdt = $this->obj->created(); + $object['created'] = $cdt && $cdt->isValid() ? self::php_datetime($cdt) : new DateTime('now', new DateTimeZone('UTC')); + if (!$cdt || !$cdt->isValid()) + $this->obj->setCreated(self::get_datetime($object['created'])); } - $object['changed'] = new DateTime('now', self::$timezone); - $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); + $object['changed'] = new DateTime('now', new DateTimeZone('UTC')); + $this->obj->setLastModified(self::get_datetime($object['changed'])); // Save custom properties of the given object - if (!empty($object['x-custom'])) { + if (isset($object['x-custom'])) { $vcustom = new vectorcs; - foreach ($object['x-custom'] as $cp) { + foreach ((array)$object['x-custom'] as $cp) { if (is_array($cp)) $vcustom->push(new CustomProperty($cp[0], $cp[1])); } $this->obj->setCustomProperties($vcustom); } + else { // load custom properties from XML for caching (#2238) + $object['x-custom'] = array(); + $vcustom = $this->obj->customProperties(); + for ($i=0; $i < $vcustom->size(); $i++) { + $cp = $vcustom->get($i); + $object['x-custom'][] = array($cp->identifier, $cp->value); + } + } } /**
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_contact.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_contact.php
Changed
@@ -255,6 +255,8 @@ // TODO: handle language, gpslocation, etc. + // set type property for proper caching + $object['_type'] = 'contact'; // cache this data $this->data = $object; @@ -266,7 +268,7 @@ */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->uid() /*$this->obj->isValid()*/); + return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->uid() /*$this->obj->isValid()*/)); } /** @@ -384,7 +386,7 @@ public function get_words() { $data = ''; - foreach (self::$fulltext_cols as $col) { + foreach (self::$fulltext_cols as $colname) { list($col, $field) = explode(':', $colname); if ($field) {
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_distributionlist.php
Changed
@@ -44,7 +44,6 @@ $this->obj->setName($object['name']); - $seen = array(); $members = new vectorcontactref; foreach ((array)$object['member'] as $member) { if ($member['uid']) @@ -56,7 +55,6 @@ $m->setName($member['name']); $members->push($m); - $seen[$member['email']]++; } $this->obj->setMembers($members); @@ -71,7 +69,7 @@ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); } /** @@ -100,11 +98,11 @@ $members = $this->obj->members(); for ($i=0; $i < $members->size(); $i++) { $member = $members->get($i); -# if ($member->type() == ContactReference::UidReference && ($uid = $member->uid())) +// if ($member->type() == ContactReference::UidReference && ($uid = $member->uid())) $object['member'][] = array( - 'uid' => $member->uid(), + 'uid' => $member->uid(), 'email' => $member->email(), - 'name' => $member->name(), + 'name' => $member->name(), ); }
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_event.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_event.php
Changed
@@ -89,25 +89,6 @@ $status = kolabformat::StatusCancelled; $this->obj->setStatus($status); - // save attachments - $vattach = new vectorattachment; - foreach ((array)$object['_attachments'] as $cid => $attr) { - if (empty($attr)) - continue; - $attach = new Attachment; - $attach->setLabel((string)$attr['name']); - $attach->setUri('cid:' . $cid, $attr['mimetype']); - $vattach->push($attach); - } - - foreach ((array)$object['links'] as $link) { - $attach = new Attachment; - $attach->setUri($link, null); - $vattach->push($attach); - } - - $this->obj->setAttachments($vattach); - // save recurrence exceptions if ($object['recurrence']['EXCEPTIONS']) { $vexceptions = new vectorevent; @@ -130,7 +111,8 @@ */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid() && $this->obj->uid()); + return !$this->formaterror && (($this->data && !empty($this->data['start']) && !empty($this->data['end'])) || + (is_object($this->obj) && $this->obj->isValid() && $this->obj->uid())); } /** @@ -157,6 +139,17 @@ 'attendees' => array(), ); + // derive event end from duration (#1916) + if (!$object['end'] && $object['start'] && ($duration = $this->obj->duration()) && $duration->isValid()) { + $interval = new DateInterval('PT0S'); + $interval->d = $duration->weeks() * 7 + $duration->days(); + $interval->h = $duration->hours(); + $interval->i = $duration->minutes(); + $interval->s = $duration->seconds(); + $object['end'] = clone $object['start']; + $object['end']->add($interval); + } + // organizer is part of the attendees list in Roundcube if ($object['organizer']) { $object['organizer']['role'] = 'ORGANIZER'; @@ -170,27 +163,6 @@ else if ($status == kolabformat::StatusCancelled) $object['cancelled'] = true; - // handle attachments - $vattach = $this->obj->attachments(); - for ($i=0; $i < $vattach->size(); $i++) { - $attach = $vattach->get($i); - - // skip cid: attachments which are mime message parts handled by kolab_storage_folder - if (substr($attach->uri(), 0, 4) != 'cid:' && $attach->label()) { - $name = $attach->label(); - $content = $attach->data(); - $object['_attachments'][$name] = array( - 'name' => $name, - 'mimetype' => $attach->mimetype(), - 'size' => strlen($content), - 'content' => $content, - ); - } - else if (substr($attach->uri(), 0, 4) == 'http') { - $object['links'][] = $attach->uri(); - } - } - // read exception event objects if (($exceptions = $this->obj->exceptions()) && is_object($exceptions) && $exceptions->size()) { for ($i=0; $i < $exceptions->size(); $i++) {
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_file.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_file.php
Changed
@@ -67,12 +67,11 @@ // make sure size is set, so object saved in cache contains this info if (!isset($attach_attr['size'])) { $size = 0; + if (!empty($attach_attr['content'])) { if (is_resource($attach_attr['content'])) { - $stat = fstat($msg[$i]); - if ($stat) { - $size = $stat['size']; - } + $stat = fstat($attach_attr['content']); + $size = $stat ? $stat['size'] : 0; } else { $size = strlen($attach_attr['content']); @@ -81,6 +80,7 @@ else if (isset($attach_attr['path'])) { $size = @filesize($attach_attr['path']); } + $object['_attachments'][$cid]['size'] = $size; } } @@ -91,11 +91,11 @@ } /** - * + * Check if object's data validity */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); } /**
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_journal.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_journal.php
Changed
@@ -54,7 +54,7 @@ */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); } /**
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_note.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_note.php
Changed
@@ -54,7 +54,7 @@ */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); } /**
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_task.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_task.php
Changed
@@ -63,7 +63,7 @@ */ public function is_valid() { - return $this->data || (is_object($this->obj) && $this->obj->isValid()); + return !$this->formaterror && ($this->data || (is_object($this->obj) && $this->obj->isValid())); } /**
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_xcal.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_format_xcal.php
Changed
@@ -218,6 +218,27 @@ } } + // handle attachments + $vattach = $this->obj->attachments(); + for ($i=0; $i < $vattach->size(); $i++) { + $attach = $vattach->get($i); + + // skip cid: attachments which are mime message parts handled by kolab_storage_folder + if (substr($attach->uri(), 0, 4) != 'cid:' && $attach->label()) { + $name = $attach->label(); + $content = $attach->data(); + $object['_attachments'][$name] = array( + 'name' => $name, + 'mimetype' => $attach->mimetype(), + 'size' => strlen($content), + 'content' => $content, + ); + } + else if (substr($attach->uri(), 0, 4) == 'http') { + $object['links'][] = $attach->uri(); + } + } + return $object; } @@ -237,7 +258,8 @@ parent::set($object); // increment sequence on updates - $object['sequence'] = !$is_new ? $this->obj->sequence()+1 : 0; + if (empty($object['sequence'])) + $object['sequence'] = !$is_new ? $this->obj->sequence()+1 : 0; $this->obj->setSequence($object['sequence']); $this->obj->setSummary($object['title']); @@ -286,8 +308,10 @@ } // save recurrence rule + $rr = new RecurrenceRule; + $rr->setFrequency(RecurrenceRule::FreqNone); + if ($object['recurrence']) { - $rr = new RecurrenceRule; $rr->setFrequency($this->rrule_type_map[$object['recurrence']['FREQ']]); if ($object['recurrence']['INTERVAL']) @@ -327,8 +351,6 @@ $rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true)); if ($rr->isValid()) { - $this->obj->setRecurrenceRule($rr); - // add exception dates (only if recurrence rule is valid) $exdates = new vectordatetime; foreach ((array)$object['recurrence']['EXDATE'] as $exdate) @@ -344,12 +366,14 @@ } } + $this->obj->setRecurrenceRule($rr); + // save alarm $valarms = new vectoralarm; if ($object['alarms']) { list($offset, $type) = explode(":", $object['alarms']); - if ($type == 'EMAIL') { // email alarms implicitly go to event owner + if ($type == 'EMAIL' && !empty($object['_owner'])) { // email alarms implicitly go to event owner $recipients = new vectorcontactref; $recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner'])); $alarm = new Alarm($object['title'], strval($object['description']), $recipients); @@ -361,7 +385,7 @@ if (preg_match('/^@(\d+)/', $offset, $d)) { $alarm->setStart(self::get_datetime($d[1], new DateTimeZone('UTC'))); } - else if (preg_match('/^([-+]?)(\d+)([SMHDW])/', $offset, $d)) { + else if (preg_match('/^([-+]?)P?T?(\d+)([SMHDW])/', $offset, $d)) { $days = $hours = $minutes = $seconds = 0; switch ($d[3]) { case 'W': $days = 7*intval($d[2]); break; @@ -376,6 +400,25 @@ $valarms->push($alarm); } $this->obj->setAlarms($valarms); + + // save attachments + $vattach = new vectorattachment; + foreach ((array)$object['_attachments'] as $cid => $attr) { + if (empty($attr)) + continue; + $attach = new Attachment; + $attach->setLabel((string)$attr['name']); + $attach->setUri('cid:' . $cid, $attr['mimetype']); + $vattach->push($attach); + } + + foreach ((array)$object['links'] as $link) { + $attach = new Attachment; + $attach->setUri($link, 'unknown'); + $vattach->push($attach); + } + + $this->obj->setAttachments($vattach); } /**
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage.php
Changed
@@ -31,6 +31,9 @@ const COLOR_KEY_PRIVATE = '/private/vendor/kolab/color'; const NAME_KEY_SHARED = '/shared/vendor/kolab/displayname'; const NAME_KEY_PRIVATE = '/private/vendor/kolab/displayname'; + const UID_KEY_SHARED = '/shared/vendor/kolab/uniqueid'; + const UID_KEY_PRIVATE = '/private/vendor/kolab/uniqueid'; + const UID_KEY_CYRUS = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; public static $version = '3.0'; public static $last_error; @@ -41,6 +44,23 @@ private static $config; private static $imap; + // Default folder names + private static $default_folders = array( + 'event' => 'Calendar', + 'contact' => 'Contacts', + 'task' => 'Tasks', + 'note' => 'Notes', + 'file' => 'Files', + 'configuration' => 'Configuration', + 'journal' => 'Journal', + 'mail.inbox' => 'INBOX', + 'mail.drafts' => 'Drafts', + 'mail.sentitems' => 'Sent', + 'mail.wastebasket' => 'Trash', + 'mail.outbox' => 'Outbox', + 'mail.junkemail' => 'Junk', + ); + /** * Setup the environment needed by the libs @@ -86,15 +106,16 @@ * Get a list of storage folders for the given data type * * @param string Data type to list folders for (contact,distribution-list,event,task,note) + * @param boolean Enable to return subscribed folders only (null to use configured subscription mode) * * @return array List of Kolab_Folder objects (folder names in UTF7-IMAP) */ - public static function get_folders($type) + public static function get_folders($type, $subscribed = null) { $folders = $folderdata = array(); if (self::setup()) { - foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) { + foreach ((array)self::list_folders('', '*', $type, $subscribed, $folderdata) as $foldername) { $folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]); } } @@ -137,7 +158,7 @@ * This will search all folders storing objects of the given type. * * @param string Object UID - * @param string Object type (contact,distribution-list,event,task,note) + * @param string Object type (contact,event,task,journal,file,note,configuration) * @return array The Kolab object represented as hash array or false if not found */ public static function get_object($uid, $type) @@ -150,7 +171,7 @@ else $folder->set_folder($foldername); - if ($object = $folder->get_object($uid)) + if ($object = $folder->get_object($uid, '*')) return $object; } @@ -259,9 +280,22 @@ { self::setup(); + $oldfolder = self::get_folder($oldname); + $active = self::folder_is_active($oldname); $success = self::$imap->rename_folder($oldname, $newname); self::$last_error = self::$imap->get_error_str(); + // pass active state to new folder name + if ($success && $active) { + self::set_state($oldnam, false); + self::set_state($newname, true); + } + + // assign existing cache entries to new resource uri + if ($success && $oldfolder) { + $oldfolder->cache->rename($newname); + } + return $success; } @@ -349,25 +383,8 @@ $result = self::folder_create($folder, $prop['type'], $prop['subscribed'], $prop['active']); } - // save displayname and color in METADATA - // TODO: also save 'showalarams' and other properties here if ($result) { - $ns = null; - foreach (array('color' => array(self::COLOR_KEY_SHARED,self::COLOR_KEY_PRIVATE), - 'displayname' => array(self::NAME_KEY_SHARED,self::NAME_KEY_PRIVATE)) as $key => $metakeys) { - if (!empty($prop[$key])) { - if (!isset($ns)) - $ns = self::$imap->folder_namespace($folder); - - $meta_saved = false; - if ($ns == 'personal') // save in shared namespace for personal folders - $meta_saved = self::$imap->set_metadata($folder, array($metakeys[0] => $prop[$key])); - if (!$meta_saved) // try in private namespace - $meta_saved = self::$imap->set_metadata($folder, array($metakeys[1] => $prop[$key])); - if ($meta_saved) - unset($prop[$key]); // unsetting will prevent fallback to local user prefs - } - } + self::set_folder_props($folder, $prop); } return $result ? $folder : false; @@ -388,8 +405,7 @@ self::setup(); // find custom display name in folder METADATA - $metadata = self::$imap->get_metadata($folder, array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED)); - if (($name = $metadata[$folder][self::NAME_KEY_PRIVATE]) || ($name = $metadata[$folder][self::NAME_KEY_SHARED])) { + if ($name = self::custom_displayname($folder)) { return $name; } @@ -459,6 +475,21 @@ return $folder; } + /** + * Get custom display name (saved in metadata) for the given folder + */ + public static function custom_displayname($folder) + { + // find custom display name in folder METADATA + if (self::$config->get('kolab_custom_display_names', true)) { + $metadata = self::$imap->get_metadata($folder, array(self::NAME_KEY_PRIVATE, self::NAME_KEY_SHARED)); + if (($name = $metadata[$folder][self::NAME_KEY_PRIVATE]) || ($name = $metadata[$folder][self::NAME_KEY_SHARED])) { + return $name; + } + } + + return false; + } /** * Helper method to generate a truncated folder name to display @@ -473,7 +504,7 @@ $length = strlen($names[$i] . ' » '); $prefix = substr($name, 0, $length); $count = count(explode(' » ', $prefix)); - $name = str_repeat(' ', $count-1) . '» ' . substr($name, $length); + $name = str_repeat(' ', $count-1) . '» ' . substr($name, $length); break; } } @@ -495,7 +526,7 @@ public static function folder_selector($type, $attrs, $current = '') { // get all folders of specified type - $folders = self::get_folders($type); + $folders = self::get_folders($type, false); $delim = self::$imap->get_hierarchy_delimiter(); $names = array(); @@ -568,7 +599,7 @@ * * @param string Optional root folder * @param string Optional name pattern - * @param string Data type to list folders for (contact,distribution-list,event,task,note,mail) + * @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration) * @param boolean Enable to return subscribed folders only (null to use configured subscription mode) * @param array Will be filled with folder-types data * @@ -596,17 +627,15 @@ } $prefix = $root . $mbox; + $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/'; // get folders types - $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); + $folderdata = self::folders_typedata($prefix); if (!is_array($folderdata)) { return array(); } - $folderdata = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); - $regexp = '/^' . preg_quote($filter, '/') . '(\..+)?$/'; - // In some conditions we can skip LIST command (?) if (!$subscribed && $filter != 'mail' && $prefix == '*') { foreach ($folderdata as $folder => $type) { @@ -647,9 +676,63 @@ /** + * Sort the given list of kolab folders by namespace/name + * + * @param array List of kolab_storage_folder objects + * @return array Sorted list of folders + */ + public static function sort_folders($folders) + { + $pad = ' '; + $nsnames = array('personal' => array(), 'shared' => array(), 'other' => array()); + foreach ($folders as $folder) { + $folders[$folder->name] = $folder; + $ns = $folder->get_namespace(); + $nsnames[$ns][$folder->name] = strtolower(html_entity_decode(self::object_name($folder->name, $ns), ENT_COMPAT, RCUBE_CHARSET)) . $pad; // decode » + } + + $names = array(); + foreach ($nsnames as $ns => $dummy) { + asort($nsnames[$ns], SORT_LOCALE_STRING); + $names += $nsnames[$ns]; + } + + $out = array(); + foreach ($names as $utf7name => $name) { + $out[] = $folders[$utf7name]; + } + + return $out; + } + + + /** + * Returns folder types indexed by folder name + * + * @param string $prefix Folder prefix (Default '*' for all folders) + * + * @return array|bool List of folders, False on failure + */ + public static function folders_typedata($prefix = '*') + { + if (!self::setup()) { + return false; + } + + $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); + + if (!is_array($folderdata)) { + return false; + } + + return array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); + } + + + /** * Callback for array_map to select the correct annotation value */ - static function folder_select_metadata($types) + public static function folder_select_metadata($types) { if (!empty($types[self::CTYPE_KEY_PRIVATE])) { return $types[self::CTYPE_KEY_PRIVATE]; @@ -669,7 +752,7 @@ * * @return string Folder type */ - static function folder_type($folder) + public static function folder_type($folder) { self::setup(); @@ -695,7 +778,7 @@ * * @return boolean True on success */ - static function set_folder_type($folder, $type='mail') + public static function set_folder_type($folder, $type='mail') { self::setup(); @@ -860,4 +943,107 @@ $rcube = rcube::get_instance(); return $rcube->user->save_prefs(array('kolab_active_folders' => $folders)); } + + /** + * Creates default folder of specified type + * To be run when none of subscribed folders (of specified type) is found + * + * @param string $type Folder type + * @param string $props Folder properties (color, etc) + * + * @return string Folder name + */ + public static function create_default_folder($type, $props = array()) + { + if (!self::setup()) { + return; + } + + $folders = self::$imap->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE)); + + // from kolab_folders config + $folder_type = strpos($type, '.') ? str_replace('.', '_', $type) : $type . '_default'; + $default_name = self::$config->get('kolab_folders_' . $folder_type); + $folder_type = str_replace('_', '.', $folder_type); + + // check if we have any folder in personal namespace + // folder(s) may exist but not subscribed + foreach ($folders as $f => $data) { + if (strpos($data[self::CTYPE_KEY_PRIVATE], $type) === 0) { + $folder = $f; + break; + } + } + + if (!$folder) { + if (!$default_name) { + $default_name = self::$default_folders[$type]; + } + + if (!$default_name) { + return; + } + + $folder = rcube_charset::convert($default_name, RCUBE_CHARSET, 'UTF7-IMAP'); + $prefix = self::$imap->get_namespace('prefix'); + + // add personal namespace prefix if needed + if ($prefix && strpos($folder, $prefix) !== 0 && $folder != 'INBOX') { + $folder = $prefix . $folder; + } + + if (!self::$imap->folder_exists($folder)) { + if (!self::$imap->create_folder($folder)) { + return; + } + } + + self::set_folder_type($folder, $folder_type); + } + + self::folder_subscribe($folder); + + if ($props['active']) { + self::set_state($folder, true); + } + + if (!empty($props)) { + self::set_folder_props($folder, $props); + } + + return $folder; + } + + /** + * Sets folder metadata properties + * + * @param string $folder Folder name + * @param array $prop Folder properties + */ + public static function set_folder_props($folder, &$prop) + { + if (!self::setup()) { + return; + } + + // TODO: also save 'showalarams' and other properties here + $ns = self::$imap->folder_namespace($folder); + $supported = array( + 'color' => array(self::COLOR_KEY_SHARED, self::COLOR_KEY_PRIVATE), + 'displayname' => array(self::NAME_KEY_SHARED, self::NAME_KEY_PRIVATE), + ); + + foreach ($supported as $key => $metakeys) { + if (array_key_exists($key, $prop)) { + $meta_saved = false; + if ($ns == 'personal') // save in shared namespace for personal folders + $meta_saved = self::$imap->set_metadata($folder, array($metakeys[0] => $prop[$key])); + if (!$meta_saved) // try in private namespace + $meta_saved = self::$imap->set_metadata($folder, array($metakeys[1] => $prop[$key])); + if ($meta_saved) + unset($prop[$key]); // unsetting will prevent fallback to local user prefs + } + } + } + }
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache.php
Changed
@@ -6,7 +6,7 @@ * @version @package_version@ * @author Thomas Bruederli <bruederli@kolabsys.com> * - * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> + * Copyright (C) 2012-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 @@ -24,24 +24,44 @@ class kolab_storage_cache { - private $db; - private $imap; - private $folder; - private $uid2msg; - private $objects; - private $index = array(); - private $resource_uri; - private $enabled = true; - private $synched = false; - private $synclock = false; - private $ready = false; - private $max_sql_packet; - private $max_sync_lock_time = 600; - private $binary_items = array( - 'photo' => '|<photo><uri>[^;]+;base64,([^<]+)</uri></photo>|i', - 'pgppublickey' => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></photo>|i', - 'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></photo>|i', - ); + protected $db; + protected $imap; + protected $folder; + protected $uid2msg; + protected $objects; + protected $index = array(); + protected $metadata = array(); + protected $folder_id; + protected $resource_uri; + protected $enabled = true; + protected $synched = false; + protected $synclock = false; + protected $ready = false; + protected $cache_table; + protected $folders_table; + protected $max_sql_packet; + protected $max_sync_lock_time = 600; + protected $binary_items = array(); + protected $extra_cols = array(); + + + /** + * Factory constructor + */ + public static function factory(kolab_storage_folder $storage_folder) + { + $subclass = 'kolab_storage_cache_' . $storage_folder->type; + if (class_exists($subclass)) { + return new $subclass($storage_folder); + } + else { + rcube::raise_error(array( + 'code' => 900, + 'type' => 'php', + 'message' => "No kolab_storage_cache class found for folder of type " . $storage_folder->type + ), true); + } + } /** @@ -55,6 +75,8 @@ $this->enabled = $rcmail->config->get('kolab_cache', false); if ($this->enabled) { + // always read folder cache and lock state from DB master + $this->db->set_table_dsn('kolab_folders', 'w'); // remove sync-lock on script termination $rcmail->add_shutdown_function(array($this, '_sync_unlock')); } @@ -80,9 +102,19 @@ // compose fully qualified ressource uri for this instance $this->resource_uri = $this->folder->get_resource_uri(); + $this->folders_table = $this->db->table_name('kolab_folders'); + $this->cache_table = $this->db->table_name('kolab_cache_' . $this->folder->type); $this->ready = $this->enabled; + $this->folder_id = null; } + /** + * Returns true if this cache supports query by type + */ + public function has_type_col() + { + return in_array('type', $this->extra_cols); + } /** * Synchronize local cache data with remote @@ -96,52 +128,66 @@ // increase time limit @set_time_limit($this->max_sync_lock_time); - // lock synchronization for this folder or wait if locked - $this->_sync_lock(); + // read cached folder metadata + $this->_read_folder_data(); - // synchronize IMAP mailbox cache - $this->imap->folder_sync($this->folder->name); + // check cache status hash first ($this->metadata is set in _read_folder_data()) + if ($this->metadata['ctag'] != $this->folder->get_ctag()) { - // compare IMAP index with object cache index - $imap_index = $this->imap->index($this->folder->name); - $this->index = $imap_index->get(); + // lock synchronization for this folder or wait if locked + $this->_sync_lock(); - // determine objects to fetch or to invalidate - if ($this->ready) { - // read cache index - $sql_result = $this->db->query( - "SELECT msguid, uid FROM kolab_cache WHERE resource=? AND type<>?", - $this->resource_uri, - 'lock' - ); + // disable messages cache if configured to do so + $this->bypass(true); - $old_index = array(); - while ($sql_arr = $this->db->fetch_assoc($sql_result)) { - $old_index[] = $sql_arr['msguid']; - $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid']; - } + // synchronize IMAP mailbox cache + $this->imap->folder_sync($this->folder->name); - // fetch new objects from imap - foreach (array_diff($this->index, $old_index) as $msguid) { - if ($object = $this->folder->read_object($msguid, '*')) { - $this->_extended_insert($msguid, $object); - } - } - $this->_extended_insert(0, null); - - // delete invalid entries from local DB - $del_index = array_diff($old_index, $this->index); - if (!empty($del_index)) { - $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index)); - $this->db->query( - "DELETE FROM kolab_cache WHERE resource=? AND msguid IN ($quoted_ids)", - $this->resource_uri + // compare IMAP index with object cache index + $imap_index = $this->imap->index($this->folder->name); + $this->index = $imap_index->get(); + + // determine objects to fetch or to invalidate + if ($this->ready) { + // read cache index + $sql_result = $this->db->query( + "SELECT msguid, uid FROM $this->cache_table WHERE folder_id=?", + $this->folder_id ); + + $old_index = array(); + while ($sql_arr = $this->db->fetch_assoc($sql_result)) { + $old_index[] = $sql_arr['msguid']; + $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid']; + } + + // fetch new objects from imap + foreach (array_diff($this->index, $old_index) as $msguid) { + if ($object = $this->folder->read_object($msguid, '*')) { + $this->_extended_insert($msguid, $object); + } + } + $this->_extended_insert(0, null); + + // delete invalid entries from local DB + $del_index = array_diff($old_index, $this->index); + if (!empty($del_index)) { + $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index)); + $this->db->query( + "DELETE FROM $this->cache_table WHERE folder_id=? AND msguid IN ($quoted_ids)", + $this->folder_id + ); + } + + // update ctag value (will be written to database in _sync_unlock()) + $this->metadata['ctag'] = $this->folder->get_ctag(); } - } - // remove lock - $this->_sync_unlock(); + $this->bypass(false); + + // remove lock + $this->_sync_unlock(); + } $this->synched = time(); } @@ -165,11 +211,12 @@ // load object if not in memory if (!isset($this->objects[$msguid])) { if ($this->ready) { + $this->_read_folder_data(); + $sql_result = $this->db->query( - "SELECT * FROM kolab_cache ". - "WHERE resource=? AND type=? AND msguid=?", - $this->resource_uri, - $type ?: $this->folder->type, + "SELECT * FROM $this->cache_table ". + "WHERE folder_id=? AND msguid=?", + $this->folder_id, $msguid ); @@ -210,8 +257,9 @@ // remove old entry if ($this->ready) { - $this->db->query("DELETE FROM kolab_cache WHERE resource=? AND msguid=? AND type<>?", - $this->resource_uri, $msguid, 'lock'); + $this->_read_folder_data(); + $this->db->query("DELETE FROM $this->cache_table WHERE folder_id=? AND msguid=?", + $this->folder_id, $msguid); } if ($object) { @@ -235,27 +283,33 @@ { // write to cache if ($this->ready) { + $this->_read_folder_data(); + $sql_data = $this->_serialize($object); - $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; - $result = $this->db->query( - "INSERT INTO kolab_cache ". - " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words, filename)". - " VALUES (?, ?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ?, ?, ?, ?)", - $this->resource_uri, - $objtype, + $extra_cols = $this->extra_cols ? ', ' . join(', ', $this->extra_cols) : ''; + $extra_fields = $this->extra_cols ? str_repeat(', ?', count($this->extra_cols)) : ''; + + $args = array( + "INSERT INTO $this->cache_table ". + " (folder_id, msguid, uid, created, changed, data, xml, tags, words $extra_cols)". + " VALUES (?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ? $extra_fields)", + $this->folder_id, $msguid, $object['uid'], $sql_data['changed'], $sql_data['data'], $sql_data['xml'], - $sql_data['dtstart'], - $sql_data['dtend'], $sql_data['tags'], $sql_data['words'], - $sql_data['filename'] ); + foreach ($this->extra_cols as $col) { + $args[] = $sql_data[$col]; + } + + $result = call_user_func_array(array($this->db, 'query'), $args); + if (!$this->db->affected_rows($result)) { rcube::raise_error(array( 'code' => 900, 'type' => 'php', @@ -265,8 +319,8 @@ } // keep a copy in memory for fast access - $this->objects[$msguid] = $object; - $this->uid2msg[$object['uid']] = $msguid; + $this->objects = array($msguid => $object); + $this->uid2msg = array($object['uid'] => $msguid); } @@ -283,14 +337,15 @@ // resolve new message UID in target folder if ($new_msguid = $target->cache->uid2msguid($uid)) { + $this->_read_folder_data(); + $this->db->query( - "UPDATE kolab_cache SET resource=?, msguid=? ". - "WHERE resource=? AND msguid=? AND type<>?", - $target->get_resource_uri(), + "UPDATE $this->cache_table SET folder_id=?, msguid=? ". + "WHERE folder_id=? AND msguid=?", + $target->folder_id, $new_msguid, - $this->resource_uri, - $msguid, - 'lock' + $this->folder_id, + $msguid ); } else { @@ -307,15 +362,32 @@ */ public function purge($type = null) { + $this->_read_folder_data(); + $result = $this->db->query( - "DELETE FROM kolab_cache WHERE resource=?". - ($type ? ' AND type=?' : ''), - $this->resource_uri, - $type + "DELETE FROM $this->cache_table WHERE folder_id=?". + $this->folder_id ); return $this->db->affected_rows($result); } + /** + * Update resource URI for existing cache entries + * + * @param string Target IMAP folder to move it to + */ + public function rename($new_folder) + { + $target = kolab_storage::get_folder($new_folder); + + // resolve new message UID in target folder + $this->db->query( + "UPDATE $this->folders_table SET resource=? ". + "WHERE resource=?", + $target->get_resource_uri(), + $this->resource_uri + ); + } /** * Select Kolab objects filtered by the given query @@ -331,10 +403,12 @@ // read from local cache DB (assume it to be synchronized) if ($this->ready) { + $this->_read_folder_data(); + $sql_result = $this->db->query( - "SELECT " . ($uids ? 'msguid, uid' : '*') . " FROM kolab_cache ". - "WHERE resource=? " . $this->_sql_where($query), - $this->resource_uri + "SELECT " . ($uids ? 'msguid, uid' : '*') . " FROM $this->cache_table ". + "WHERE folder_id=? " . $this->_sql_where($query), + $this->folder_id ); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { @@ -391,10 +465,12 @@ // cache is in sync, we can count records in local DB if ($this->synched) { + $this->_read_folder_data(); + $sql_result = $this->db->query( - "SELECT COUNT(*) AS numrows FROM kolab_cache ". - "WHERE resource=? " . $this->_sql_where($query), - $this->resource_uri + "SELECT COUNT(*) AS numrows FROM $this->cache_table ". + "WHERE folder_id=? " . $this->_sql_where($query), + $this->folder_id ); $sql_arr = $this->db->fetch_assoc($sql_result); @@ -415,10 +491,10 @@ /** * Helper method to compose a valid SQL query from pseudo filter triplets */ - private function _sql_where($query) + protected function _sql_where($query) { $sql_where = ''; - foreach ($query as $param) { + foreach ((array) $query as $param) { if (is_array($param[0])) { $subq = array(); foreach ($param[0] as $q) { @@ -460,7 +536,7 @@ * Helper method to convert the given pseudo-query triplets into * an associative filter array with 'equals' values only */ - private function _query2assoc($query) + protected function _query2assoc($query) { // extract object type from query parameter $filter = array(); @@ -479,7 +555,7 @@ * @param string IMAP folder to read from * @return array List of parsed Kolab objects */ - private function _fetch($index, $type = null, $folder = null) + protected function _fetch($index, $type = null, $folder = null) { $results = array(); foreach ((array)$index as $msguid) { @@ -501,13 +577,19 @@ * @param string IMAP folder to read from * @return array List of parsed Kolab objects */ - private function _fetch_uids($index, $type = null) + protected function _fetch_uids($index, $type = null) { if (!$type) $type = $this->folder->type; + $this->bypass(true); + $results = array(); - foreach ((array)$this->imap->fetch_headers($this->folder->name, $index, false) as $msguid => $headers) { + $headers = $this->imap->fetch_headers($this->folder->name, $index, false); + + $this->bypass(false); + + foreach ((array)$headers as $msguid => $headers) { $object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']); // check object type header and abort on mismatch @@ -526,35 +608,9 @@ /** * Helper method to convert the given Kolab object into a dataset to be written to cache */ - private function _serialize($object) + protected function _serialize($object) { - $sql_data = array('changed' => null, 'dtstart' => null, 'dtend' => null, 'xml' => '', 'tags' => '', 'words' => ''); - $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; - - // set type specific values - if ($objtype == 'event') { - // database runs in server's timezone so using date() is what we want - $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); - $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['end']) ? $object['end']->format('U') : $object['end']); - - // extend date range for recurring events - if ($object['recurrence'] && $object['_formatobj']) { - $recurrence = new kolab_date_recurrence($object['_formatobj']); - $sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +1 year')); - } - } - else if ($objtype == 'task') { - if ($object['start']) - $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); - if ($object['due']) - $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['due']) ? $object['due']->format('U') : $object['due']); - } - else if ($objtype == 'file') { - if (!empty($object['_attachments'])) { - reset($object['_attachments']); - $sql_data['filename'] = $object['_attachments'][key($object['_attachments'])]['name']; - } - } + $sql_data = array('changed' => null, 'xml' => '', 'tags' => '', 'words' => ''); if ($object['changed']) { $sql_data['changed'] = date('Y-m-d H:i:s', is_object($object['changed']) ? $object['changed']->format('U') : $object['changed']); @@ -596,7 +652,7 @@ /** * Helper method to turn stored cache data into a valid storage object */ - private function _unserialize($sql_arr) + protected function _unserialize($sql_arr) { $object = unserialize($sql_arr['data']); @@ -608,11 +664,11 @@ } // add meta data - $object['_type'] = $sql_arr['type']; + $object['_type'] = $sql_arr['type'] ?: $this->folder->type; $object['_msguid'] = $sql_arr['msguid']; $object['_mailbox'] = $this->folder->name; $object['_size'] = strlen($sql_arr['xml']); - $object['_formatobj'] = kolab_format::factory($sql_arr['type'], 3.0, $sql_arr['xml']); + $object['_formatobj'] = kolab_format::factory($object['_type'], 3.0, $sql_arr['xml']); return $object; } @@ -623,37 +679,35 @@ * @param int Message UID. Set 0 to commit buffered inserts * @param array Kolab object to cache */ - private function _extended_insert($msguid, $object) + protected function _extended_insert($msguid, $object) { static $buffer = ''; $line = ''; if ($object) { $sql_data = $this->_serialize($object); - $objtype = $object['_type'] ? $object['_type'] : $this->folder->type; - $values = array( - $this->db->quote($this->resource_uri), - $this->db->quote($objtype), + $this->db->quote($this->folder_id), $this->db->quote($msguid), $this->db->quote($object['uid']), $this->db->now(), $this->db->quote($sql_data['changed']), $this->db->quote($sql_data['data']), $this->db->quote($sql_data['xml']), - $this->db->quote($sql_data['dtstart']), - $this->db->quote($sql_data['dtend']), $this->db->quote($sql_data['tags']), $this->db->quote($sql_data['words']), - $this->db->quote($sql_data['filename']), ); + foreach ($this->extra_cols as $col) { + $values[] = $this->db->quote($sql_data[$col]); + } $line = '(' . join(',', $values) . ')'; } if ($buffer && (!$msguid || (strlen($buffer) + strlen($line) > $this->max_sql_packet()))) { + $extra_cols = $this->extra_cols ? ', ' . join(', ', $this->extra_cols) : ''; $result = $this->db->query( - "INSERT INTO kolab_cache ". - " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words, filename)". + "INSERT INTO $this->cache_table ". + " (folder_id, msguid, uid, created, changed, data, xml, tags, words $extra_cols)". " VALUES $buffer" ); if (!$this->db->affected_rows($result)) { @@ -672,7 +726,7 @@ /** * Returns max_allowed_packet from mysql config */ - private function max_sql_packet() + protected function max_sql_packet() { if (!$this->max_sql_packet) { // mysql limit or max 4 MB @@ -684,16 +738,36 @@ } /** + * Read this folder's ID and cache metadata + */ + protected function _read_folder_data() + { + // already done + if (!empty($this->folder_id)) + return; + + $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT folder_id, synclock, ctag FROM $this->folders_table WHERE resource=?", $this->resource_uri)); + if ($sql_arr) { + $this->metadata = $sql_arr; + $this->folder_id = $sql_arr['folder_id']; + } + else { + $this->db->query("INSERT INTO $this->folders_table (resource, type) VALUES (?, ?)", $this->resource_uri, $this->folder->type); + $this->folder_id = $this->db->insert_id('kolab_folders'); + $this->metadata = array(); + } + } + + /** * Check lock record for this folder and wait if locked or set lock */ - private function _sync_lock() + protected function _sync_lock() { if (!$this->ready) return; - $sql_query = "SELECT msguid AS locked, ".$this->db->unixtimestamp('created')." AS created FROM kolab_cache ". - "WHERE resource=? AND type=?"; - $sql_arr = $this->db->fetch_assoc($this->db->query($sql_query, $this->resource_uri, 'lock')); + $this->_read_folder_data(); + $sql_query = "SELECT synclock, ctag FROM $this->folders_table WHERE folder_id=?"; // abort if database is not set-up if ($this->db->is_error()) { @@ -704,28 +778,13 @@ $this->synclock = true; // wait if locked (expire locks after 10 minutes) - while ($sql_arr && intval($sql_arr['locked']) > 0 && $sql_arr['created'] + $this->max_sync_lock_time > time()) { + while ($this->metadata && intval($this->metadata['synclock']) > 0 && $this->metadata['synclock'] + $this->max_sync_lock_time > time()) { usleep(500000); - $sql_arr = $this->db->fetch_assoc($this->db->query($sql_query, $this->resource_uri, 'lock')); + $this->metadata = $this->db->fetch_assoc($this->db->query($sql_query, $this->folder_id)); } - // create lock record if not exists - if (!$sql_arr) { - $this->db->query( - "INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml)". - " VALUES (?, ?, 1, " . $this->db->now() . ", '', '', '')", - $this->resource_uri, - 'lock' - ); - } - else { - $this->db->query( - "UPDATE kolab_cache SET msguid = 1, created = " . $this->db->now() . - " WHERE resource = ? AND type = ?", - $this->resource_uri, - 'lock' - ); - } + // set lock + $this->db->query("UPDATE $this->folders_table SET synclock = ? WHERE folder_id = ?", time(), $this->folder_id); } /** @@ -737,9 +796,9 @@ return; $this->db->query( - "UPDATE kolab_cache SET msguid = 0 WHERE resource = ? AND type = ?", - $this->resource_uri, - 'lock' + "UPDATE $this->folders_table SET synclock = 0, ctag = ? WHERE folder_id = ?", + $this->metadata['ctag'], + $this->folder_id ); $this->synclock = false; @@ -765,4 +824,72 @@ return $this->uid2msg[$uid]; } + /** + * Getter for protected member variables + */ + public function __get($name) + { + if ($name == 'folder_id') { + $this->_read_folder_data(); + } + + return $this->$name; + } + + /** + * Bypass Roundcube messages cache. + * Roundcube cache duplicates information already stored in kolab_cache. + * + * @param bool $disable True disables, False enables messages cache + */ + public function bypass($disable = false) + { + // if kolab cache is disabled do nothing + if (!$this->enabled) { + return; + } + + static $messages_cache, $cache_bypass; + + if ($messages_cache === null) { + $rcmail = rcube::get_instance(); + $messages_cache = (bool) $rcmail->config->get('messages_cache'); + $cache_bypass = (int) $rcmail->config->get('kolab_messages_cache_bypass'); + } + + if ($messages_cache) { + // handle recurrent (multilevel) bypass() calls + if ($disable) { + $this->cache_bypassed += 1; + if ($this->cache_bypassed > 1) { + return; + } + } + else { + $this->cache_bypassed -= 1; + if ($this->cache_bypassed > 0) { + return; + } + } + + switch ($cache_bypass) { + case 2: + // Disable messages cache completely + $this->imap->set_messages_caching(!$disable); + break; + + case 1: + // We'll disable messages cache, but keep index cache. + // Default mode is both (MODE_INDEX | MODE_MESSAGE) + $mode = rcube_imap_cache::MODE_INDEX; + + if (!$disable) { + $mode |= rcube_imap_cache::MODE_MESSAGE; + } + + $this->imap->set_messages_caching(true, $mode); + } + } + } + }
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_configuration.php
Added
@@ -0,0 +1,40 @@ +<?php + +/** + * Kolab storage cache class for configuration objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_configuration extends kolab_storage_cache +{ + protected $extra_cols = array('type'); + + /** + * Helper method to convert the given Kolab object into a dataset to be written to cache + * + * @override + */ + protected function _serialize($object) + { + $sql_data = parent::_serialize($object); + $sql_data['type'] = $object['type']; + + return $sql_data; + } +} \ No newline at end of file
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_contact.php
Added
@@ -0,0 +1,45 @@ +<?php + +/** + * Kolab storage cache class for contact objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_contact extends kolab_storage_cache +{ + protected $extra_cols = array('type'); + protected $binary_items = array( + 'photo' => '|<photo><uri>[^;]+;base64,([^<]+)</uri></photo>|i', + 'pgppublickey' => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></key>|i', + 'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></key>|i', + ); + + /** + * Helper method to convert the given Kolab object into a dataset to be written to cache + * + * @override + */ + protected function _serialize($object) + { + $sql_data = parent::_serialize($object); + $sql_data['type'] = $object['_type']; + + return $sql_data; + } +} \ No newline at end of file
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_event.php
Added
@@ -0,0 +1,49 @@ +<?php + +/** + * Kolab storage cache class for calendar event objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_event extends kolab_storage_cache +{ + protected $extra_cols = array('dtstart','dtend'); + + /** + * Helper method to convert the given Kolab object into a dataset to be written to cache + * + * @override + */ + protected function _serialize($object) + { + $sql_data = parent::_serialize($object); + + // database runs in server's timezone so using date() is what we want + $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); + $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['end']) ? $object['end']->format('U') : $object['end']); + + // extend date range for recurring events + if ($object['recurrence'] && $object['_formatobj']) { + $recurrence = new kolab_date_recurrence($object['_formatobj']); + $sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +10 years')); + } + + return $sql_data; + } +} \ No newline at end of file
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_file.php
Added
@@ -0,0 +1,44 @@ +<?php + +/** + * Kolab storage cache class for file objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_file extends kolab_storage_cache +{ + protected $extra_cols = array('filename'); + + /** + * Helper method to convert the given Kolab object into a dataset to be written to cache + * + * @override + */ + protected function _serialize($object) + { + $sql_data = parent::_serialize($object); + + if (!empty($object['_attachments'])) { + reset($object['_attachments']); + $sql_data['filename'] = $object['_attachments'][key($object['_attachments'])]['name']; + } + + return $sql_data; + } +} \ No newline at end of file
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_freebusy.php
Added
@@ -0,0 +1,27 @@ +<?php + +/** + * Kolab storage cache class for freebusy objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_freebusy extends kolab_storage_cache +{ + protected $extra_cols = array('dtstart','dtend'); +} \ No newline at end of file
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_journal.php
Added
@@ -0,0 +1,28 @@ +<?php + +/** + * Kolab storage cache class for journal objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_journal extends kolab_storage_cache +{ + protected $extra_cols = array('dtstart','dtend'); + +} \ No newline at end of file
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_mongodb.php
Added
@@ -0,0 +1,561 @@ +<?php + +/** + * Kolab storage cache class providing a local caching layer for Kolab groupware objects. + * + * @version @package_version@ + * @author Thomas Bruederli <bruederli@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_storage_cache_mongodb +{ + private $db; + private $imap; + private $folder; + private $uid2msg; + private $objects; + private $index = array(); + private $resource_uri; + private $enabled = true; + private $synched = false; + private $synclock = false; + private $ready = false; + private $max_sql_packet = 1046576; // 1 MB - 2000 bytes + private $binary_cols = array('photo','pgppublickey','pkcs7publickey'); + + + /** + * Default constructor + */ + public function __construct(kolab_storage_folder $storage_folder = null) + { + $rcmail = rcube::get_instance(); + $mongo = new Mongo(); + $this->db = $mongo->kolab_cache; + $this->imap = $rcmail->get_storage(); + $this->enabled = $rcmail->config->get('kolab_cache', false); + + if ($this->enabled) { + // remove sync-lock on script termination + $rcmail->add_shutdown_function(array($this, '_sync_unlock')); + } + + if ($storage_folder) + $this->set_folder($storage_folder); + } + + + /** + * Connect cache with a storage folder + * + * @param kolab_storage_folder The storage folder instance to connect with + */ + public function set_folder(kolab_storage_folder $storage_folder) + { + $this->folder = $storage_folder; + + if (empty($this->folder->name)) { + $this->ready = false; + return; + } + + // compose fully qualified ressource uri for this instance + $this->resource_uri = $this->folder->get_resource_uri(); + $this->ready = $this->enabled; + } + + + /** + * Synchronize local cache data with remote + */ + public function synchronize() + { + // only sync once per request cycle + if ($this->synched) + return; + + // increase time limit + @set_time_limit(500); + + // lock synchronization for this folder or wait if locked + $this->_sync_lock(); + + // synchronize IMAP mailbox cache + $this->imap->folder_sync($this->folder->name); + + // compare IMAP index with object cache index + $imap_index = $this->imap->index($this->folder->name); + $this->index = $imap_index->get(); + + // determine objects to fetch or to invalidate + if ($this->ready) { + // read cache index + $old_index = array(); + $cursor = $this->db->cache->find(array('resource' => $this->resource_uri), array('msguid' => 1, 'uid' => 1)); + foreach ($cursor as $doc) { + $old_index[] = $doc['msguid']; + $this->uid2msg[$doc['uid']] = $doc['msguid']; + } + + // fetch new objects from imap + foreach (array_diff($this->index, $old_index) as $msguid) { + if ($object = $this->folder->read_object($msguid, '*')) { + try { + $this->db->cache->insert($this->_serialize($object, $msguid)); + } + catch (Exception $e) { + rcmail::raise_error(array( + 'code' => 900, 'type' => 'php', + 'message' => "Failed to write to mongodb cache: " . $e->getMessage(), + ), true); + } + } + } + + // delete invalid entries from local DB + $del_index = array_diff($old_index, $this->index); + if (!empty($del_index)) { + $this->db->cache->remove(array('resource' => $this->resource_uri, 'msguid' => array('$in' => $del_index))); + } + } + + // remove lock + $this->_sync_unlock(); + + $this->synched = time(); + } + + + /** + * Read a single entry from cache or from IMAP directly + * + * @param string Related IMAP message UID + * @param string Object type to read + * @param string IMAP folder name the entry relates to + * @param array Hash array with object properties or null if not found + */ + public function get($msguid, $type = null, $foldername = null) + { + // delegate to another cache instance + if ($foldername && $foldername != $this->folder->name) { + return kolab_storage::get_folder($foldername)->cache->get($msguid, $object); + } + + // load object if not in memory + if (!isset($this->objects[$msguid])) { + if ($this->ready && ($doc = $this->db->cache->findOne(array('resource' => $this->resource_uri, 'msguid' => $msguid)))) + $this->objects[$msguid] = $this->_unserialize($doc); + + // fetch from IMAP if not present in cache + if (empty($this->objects[$msguid])) { + $result = $this->_fetch(array($msguid), $type, $foldername); + $this->objects[$msguid] = $result[0]; + } + } + + return $this->objects[$msguid]; + } + + + /** + * Insert/Update a cache entry + * + * @param string Related IMAP message UID + * @param mixed Hash array with object properties to save or false to delete the cache entry + * @param string IMAP folder name the entry relates to + */ + public function set($msguid, $object, $foldername = null) + { + // delegate to another cache instance + if ($foldername && $foldername != $this->folder->name) { + kolab_storage::get_folder($foldername)->cache->set($msguid, $object); + return; + } + + // write to cache + if ($this->ready) { + // remove old entry + $this->db->cache->remove(array('resource' => $this->resource_uri, 'msguid' => $msguid)); + + // write new object data if not false (wich means deleted) + if ($object) { + try { + $this->db->cache->insert($this->_serialize($object, $msguid)); + } + catch (Exception $e) { + rcmail::raise_error(array( + 'code' => 900, 'type' => 'php', + 'message' => "Failed to write to mongodb cache: " . $e->getMessage(), + ), true); + } + } + } + + // keep a copy in memory for fast access + $this->objects[$msguid] = $object; + + if ($object) + $this->uid2msg[$object['uid']] = $msguid; + } + + /** + * Move an existing cache entry to a new resource + * + * @param string Entry's IMAP message UID + * @param string Entry's Object UID + * @param string Target IMAP folder to move it to + */ + public function move($msguid, $objuid, $target_folder) + { + $target = kolab_storage::get_folder($target_folder); + + // resolve new message UID in target folder + if ($new_msguid = $target->cache->uid2msguid($objuid)) { +/* + $this->db->query( + "UPDATE kolab_cache SET resource=?, msguid=? ". + "WHERE resource=? AND msguid=? AND type<>?", + $target->get_resource_uri(), + $new_msguid, + $this->resource_uri, + $msguid, + 'lock' + ); +*/ + } + else { + // just clear cache entry + $this->set($msguid, false); + } + + unset($this->uid2msg[$uid]); + } + + + /** + * Remove all objects from local cache + */ + public function purge($type = null) + { + return $this->db->cache->remove(array(), array('safe' => true)); + } + + + /** + * Select Kolab objects filtered by the given query + * + * @param array Pseudo-SQL query as list of filter parameter triplets + * triplet: array('<colname>', '<comparator>', '<value>') + * @return array List of Kolab data objects (each represented as hash array) + */ + public function select($query = array()) + { + $result = array(); + + // read from local cache DB (assume it to be synchronized) + if ($this->ready) { + $cursor = $this->db->cache->find(array('resource' => $this->resource_uri) + $this->_mongo_filter($query)); + foreach ($cursor as $doc) { + if ($object = $this->_unserialize($doc)) + $result[] = $object; + } + } + else { + // extract object type from query parameter + $filter = $this->_query2assoc($query); + + // use 'list' for folder's default objects + if ($filter['type'] == $this->type) { + $index = $this->index; + } + else { // search by object type + $search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_storage_folder::KTYPE_PREFIX . $filter['type']; + $index = $this->imap->search_once($this->folder->name, $search)->get(); + } + + // fetch all messages in $index from IMAP + $result = $this->_fetch($index, $filter['type']); + + // TODO: post-filter result according to query + } + + return $result; + } + + + /** + * Get number of objects mathing the given query + * + * @param array $query Pseudo-SQL query as list of filter parameter triplets + * @return integer The number of objects of the given type + */ + public function count($query = array()) + { + $count = 0; + + // cache is in sync, we can count records in local DB + if ($this->synched) { + $cursor = $this->db->cache->find(array('resource' => $this->resource_uri) + $this->_mongo_filter($query)); + $count = $cursor->valid() ? $cursor->count() : 0; + } + else { + // search IMAP by object type + $filter = $this->_query2assoc($query); + $ctype = kolab_storage_folder::KTYPE_PREFIX . $filter['type']; + $index = $this->imap->search_once($this->folder->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype); + $count = $index->count(); + } + + return $count; + } + + /** + * Helper method to convert the pseudo-SQL query into a valid mongodb filter + */ + private function _mongo_filter($query) + { + $filters = array(); + foreach ($query as $param) { + $filter = array(); + if ($param[1] == '=' && is_array($param[2])) { + $filter[$param[0]] = array('$in' => $param[2]); + $filters[] = $filter; + } + else if ($param[1] == '=') { + $filters[] = array($param[0] => $param[2]); + } + else if ($param[1] == 'LIKE' || $param[1] == '~') { + $filter[$param[0]] = array('$regex' => preg_quote($param[2]), '$options' => 'i'); + $filters[] = $filter; + } + else if ($param[1] == '!~' || $param[1] == '!LIKE') { + $filter[$param[0]] = array('$not' => '/' . preg_quote($param[2]) . '/i'); + $filters[] = $filter; + } + else { + $op = ''; + switch ($param[1]) { + case '>': $op = '$gt'; break; + case '>=': $op = '$gte'; break; + case '<': $op = '$lt'; break; + case '<=': $op = '$lte'; break; + case '!=': + case '<>': $op = '$gte'; break; + } + if ($op) { + $filter[$param[0]] = array($op => $param[2]); + $filters[] = $filter; + } + } + } + + return array('$and' => $filters); + } + + /** + * Helper method to convert the given pseudo-query triplets into + * an associative filter array with 'equals' values only + */ + private function _query2assoc($query) + { + // extract object type from query parameter + $filter = array(); + foreach ($query as $param) { + if ($param[1] == '=') + $filter[$param[0]] = $param[2]; + } + return $filter; + } + + /** + * Fetch messages from IMAP + * + * @param array List of message UIDs to fetch + * @return array List of parsed Kolab objects + */ + private function _fetch($index, $type = null, $folder = null) + { + $results = array(); + foreach ((array)$index as $msguid) { + if ($object = $this->folder->read_object($msguid, $type, $folder)) { + $results[] = $object; + $this->set($msguid, $object); + } + } + + return $results; + } + + + /** + * Helper method to convert the given Kolab object into a dataset to be written to cache + */ + private function _serialize($object, $msguid) + { + $bincols = array_flip($this->binary_cols); + $doc = array( + 'resource' => $this->resource_uri, + 'type' => $object['_type'] ? $object['_type'] : $this->folder->type, + 'msguid' => $msguid, + 'uid' => $object['uid'], + 'xml' => '', + 'tags' => array(), + 'words' => array(), + 'objcols' => array(), + ); + + // set type specific values + if ($this->folder->type == 'event') { + // database runs in server's timezone so using date() is what we want + $doc['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); + $doc['dtend'] = date('Y-m-d H:i:s', is_object($object['end']) ? $object['end']->format('U') : $object['end']); + + // extend date range for recurring events + if ($object['recurrence']) { + $doc['dtend'] = date('Y-m-d H:i:s', $object['recurrence']['UNTIL'] ?: strtotime('now + 2 years')); + } + } + + if ($object['_formatobj']) { + $doc['xml'] = preg_replace('!(</?[a-z0-9:-]+>)[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write()); + $doc['tags'] = $object['_formatobj']->get_tags(); + $doc['words'] = $object['_formatobj']->get_words(); + } + + // extract object data + $data = array(); + foreach ($object as $key => $val) { + if ($val === "" || $val === null) { + // skip empty properties + continue; + } + if (isset($bincols[$key])) { + $data[$key] = base64_encode($val); + } + else if (is_object($val)) { + if (is_a($val, 'DateTime')) { + $data[$key] = array('_class' => 'DateTime', 'date' => $val->format('Y-m-d H:i:s'), 'timezone' => $val->getTimezone()->getName()); + $doc['objcols'][] = $key; + } + } + else if ($key[0] != '_') { + $data[$key] = $val; + } + else if ($key == '_attachments') { + foreach ($val as $k => $att) { + unset($att['content'], $att['path']); + if ($att['id']) + $data[$key][$k] = $att; + } + } + } + + $doc['data'] = $data; + return $doc; + } + + /** + * Helper method to turn stored cache data into a valid storage object + */ + private function _unserialize($doc) + { + $object = $doc['data']; + + // decode binary properties + foreach ($this->binary_cols as $key) { + if (!empty($object[$key])) + $object[$key] = base64_decode($object[$key]); + } + + // restore serialized objects + foreach ((array)$doc['objcols'] as $key) { + switch ($object[$key]['_class']) { + case 'DateTime': + $val = new DateTime($object[$key]['date'], new DateTimeZone($object[$key]['timezone'])); + $object[$key] = $val; + break; + } + } + + // add meta data + $object['_type'] = $doc['type']; + $object['_msguid'] = $doc['msguid']; + $object['_mailbox'] = $this->folder->name; + $object['_formatobj'] = kolab_format::factory($doc['type'], $doc['xml']); + + return $object; + } + + /** + * Check lock record for this folder and wait if locked or set lock + */ + private function _sync_lock() + { + if (!$this->ready) + return; + + $this->synclock = true; + $lock = $this->db->locks->findOne(array('resource' => $this->resource_uri)); + + // create lock record if not exists + if (!$lock) { + $this->db->locks->insert(array('resource' => $this->resource_uri, 'created' => time())); + } + // wait if locked (expire locks after 10 minutes) + else if ((time() - $lock['created']) < 600) { + usleep(500000); + return $this->_sync_lock(); + } + // set lock + else { + $lock['created'] = time(); + $this->db->locks->update(array('_id' => $lock['_id']), $lock, array('safe' => true)); + } + } + + /** + * Remove lock for this folder + */ + public function _sync_unlock() + { + if (!$this->ready || !$this->synclock) + return; + + $this->db->locks->remove(array('resource' => $this->resource_uri)); + } + + /** + * Resolve an object UID into an IMAP message UID + * + * @param string Kolab object UID + * @param boolean Include deleted objects + * @return int The resolved IMAP message UID + */ + public function uid2msguid($uid, $deleted = false) + { + if (!isset($this->uid2msg[$uid])) { + // use IMAP SEARCH to get the right message + $index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid); + $results = $index->get(); + $this->uid2msg[$uid] = $results[0]; + } + + return $this->uid2msg[$uid]; + } + +}
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_note.php
Added
@@ -0,0 +1,27 @@ +<?php + +/** + * Kolab storage cache class for note objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_note extends kolab_storage_cache +{ + +} \ No newline at end of file
View file
chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_cache_task.php
Added
@@ -0,0 +1,44 @@ +<?php + +/** + * Kolab storage cache class for task objects + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 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_storage_cache_task extends kolab_storage_cache +{ + protected $extra_cols = array('dtstart','dtend'); + + /** + * Helper method to convert the given Kolab object into a dataset to be written to cache + * + * @override + */ + protected function _serialize($object) + { + $sql_data = parent::_serialize($object) + array('dtstart' => null, 'dtend' => null); + + if ($object['start']) + $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']); + if ($object['due']) + $sql_data['dtend'] = date('Y-m-d H:i:s', is_object($object['due']) ? $object['due']->format('U') : $object['due']); + + return $sql_data; + } +} \ No newline at end of file
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_folder.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/lib/kolab_storage_folder.php
Changed
@@ -7,7 +7,7 @@ * @author Thomas Bruederli <bruederli@kolabsys.com> * @author Aleksander Machniak <machniak@kolabsys.com> * - * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> + * Copyright (C) 2012-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 @@ -43,8 +43,8 @@ public $default = false; /** - * Is this folder set to be default - * @var boolean + * The kolab_storage_cache instance for caching operations + * @var object */ public $cache; @@ -64,7 +64,6 @@ { $this->imap = rcube::get_instance()->get_storage(); $this->imap->set_options(array('skip_deleted' => true)); - $this->cache = new kolab_storage_cache($this); $this->set_folder($name, $type); } @@ -75,15 +74,20 @@ * @param string The folder name/path * @param string Optional folder type if known */ - public function set_folder($name, $ftype = null) + public function set_folder($name, $type = null) { - $this->type_annotation = $ftype ? $ftype : kolab_storage::folder_type($name); + $this->type_annotation = $type ? $type : kolab_storage::folder_type($name); + $oldtype = $this->type; list($this->type, $suffix) = explode('.', $this->type_annotation); $this->default = $suffix == 'default'; $this->name = $name; $this->resource_uri = null; + // get a new cache instance of folder type changed + if (!$this->cache || $type != $oldtype) + $this->cache = kolab_storage_cache::factory($this); + $this->imap->set_folder($this->name); $this->cache->set_folder($this); } @@ -92,7 +96,7 @@ /** * */ - private function get_folder_info() + public function get_folder_info() { if (!isset($this->info)) $this->info = $this->imap->folder_info($this->name); @@ -260,6 +264,52 @@ } /** + * Helper method to extract folder UID metadata + * + * @return string Folder's UID + */ + public function get_uid() + { + // UID is defined in folder METADATA + $metakeys = array(kolab_storage::UID_KEY_SHARED, kolab_storage::UID_KEY_PRIVATE, kolab_storage::UID_KEY_CYRUS); + $metadata = $this->get_metadata($metakeys); + foreach ($metakeys as $key) { + if (($uid = $metadata[$key])) { + return $uid; + } + } + + // generate a folder UID and set it to IMAP + $uid = rtrim(chunk_split(md5($this->name . $this->get_owner()), 12, '-'), '-'); + $this->set_uid($uid); + + return $uid; + } + + /** + * Helper method to set an UID value to the given IMAP folder instance + * + * @param string Folder's UID + * @return boolean True on succes, False on failure + */ + public function set_uid($uid) + { + if (!($success = $this->set_metadata(array(kolab_storage::UID_KEY_SHARED => $uid)))) { + $success = $this->set_metadata(array(kolab_storage::UID_KEY_PRIVATE => $uid)); + } + return $success; + } + + /** + * Compose a folder Etag identifier + */ + public function get_ctag() + { + $fdata = $this->get_imap_data(); + return sprintf('%d-%d-%d', $fdata['UIDVALIDITY'], $fdata['HIGHESTMODSEQ'], $fdata['UIDNEXT']); + } + + /** * Check activation status of this folder * * @return boolean True if enabled, false if not @@ -303,7 +353,6 @@ return $subscribed ? kolab_storage::folder_subscribe($this->name) : kolab_storage::folder_unsubscribe($this->name); } - /** * Get number of objects stored in this folder * @@ -312,19 +361,12 @@ * @return integer The number of objects of the given type * @see self::select() */ - public function count($type_or_query = null) + public function count($query = null) { - if (!$type_or_query) - $query = array(array('type','=',$this->type)); - else if (is_string($type_or_query)) - $query = array(array('type','=',$type_or_query)); - else - $query = $this->_prepare_query((array)$type_or_query); - // synchronize cache first $this->cache->synchronize(); - return $this->cache->count($query); + return $this->cache->count($this->_prepare_query($query)); } @@ -342,7 +384,7 @@ $this->cache->synchronize(); // fetch objects from cache - return $this->cache->select(array(array('type','=',$type))); + return $this->cache->select($this->_prepare_query($type)); } @@ -388,10 +430,15 @@ */ private function _prepare_query($query) { - $type = null; - foreach ($query as $i => $param) { - if ($param[0] == 'type') { - $type = $param[2]; + // string equals type query + // FIXME: should not be called this way! + if (is_string($query)) { + return $this->cache->has_type_col() && !empty($query) ? array(array('type','=',$query)) : array(); + } + + foreach ((array)$query as $i => $param) { + if ($param[0] == 'type' && !$this->cache->has_type_col()) { + unset($query[$i]); } else if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) { if (is_object($param[2]) && is_a($param[2], 'DateTime')) @@ -401,10 +448,6 @@ } } - // add type selector if not in $query - if (!$type) - $query[] = array('type','=',$this->type); - return $query; } @@ -412,17 +455,22 @@ /** * Getter for a single Kolab object, identified by its UID * - * @param string Object UID + * @param string $uid Object UID + * @param string $type Object type (e.g. contact, event, todo, journal, note, configuration) + * Defaults to folder type + * * @return array The Kolab object represented as hash array */ - public function get_object($uid) + public function get_object($uid, $type = null) { // synchronize caches $this->cache->synchronize(); $msguid = $this->cache->uid2msguid($uid); - if ($msguid && ($object = $this->cache->get($msguid, '*'))) + + if ($msguid && ($object = $this->cache->get($msguid, $type))) { return $object; + } return false; } @@ -460,6 +508,7 @@ * @param string The IMAP message UID to fetch * @param string The object type expected (use wildcard '*' to accept all types) * @param string The folder name where the message is stored + * * @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found */ public function read_object($msguid, $type = null, $folder = null) @@ -469,31 +518,31 @@ $this->imap->set_folder($folder); - $headers = $this->imap->get_message_headers($msguid); - $message = null; + $this->cache->bypass(true); + $message = new rcube_message($msguid); + $this->cache->bypass(false); // Message doesn't exist? - if (empty($headers)) { + if (empty($message->headers)) { return false; } // extract the X-Kolab-Type header from the XML attachment part if missing - if (empty($headers->others['x-kolab-type'])) { - $message = new rcube_message($msguid); + if (empty($message->headers->others['x-kolab-type'])) { foreach ((array)$message->attachments as $part) { if (strpos($part->mimetype, kolab_format::KTYPE_PREFIX) === 0) { - $headers->others['x-kolab-type'] = $part->mimetype; + $message->headers->others['x-kolab-type'] = $part->mimetype; break; } } } // fix buggy messages stating the X-Kolab-Type header twice - else if (is_array($headers->others['x-kolab-type'])) { - $headers->others['x-kolab-type'] = reset($headers->others['x-kolab-type']); + else if (is_array($message->headers->others['x-kolab-type'])) { + $message->headers->others['x-kolab-type'] = reset($message->headers->others['x-kolab-type']); } // no object type header found: abort - if (empty($headers->others['x-kolab-type'])) { + if (empty($message->headers->others['x-kolab-type'])) { rcube::raise_error(array( 'code' => 600, 'type' => 'php', @@ -504,14 +553,13 @@ return false; } - $object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']); - $content_type = kolab_format::KTYPE_PREFIX . $object_type; + $object_type = kolab_format::mime2object_type($message->headers->others['x-kolab-type']); + $content_type = kolab_format::KTYPE_PREFIX . $object_type; // check object type header and abort on mismatch if ($type != '*' && $object_type != $type) return false; - if (!$message) $message = new rcube_message($msguid); $attachments = array(); // get XML part @@ -553,7 +601,7 @@ } // check kolab format version - $format_version = $headers->others['x-kolab-mime-version']; + $format_version = $message->headers->others['x-kolab-mime-version']; if (empty($format_version)) { list($xmltype, $subtype) = explode('.', $object_type); $xmlhead = substr($xml, 0, 512); @@ -645,6 +693,12 @@ if (is_array($object['_attachments'])) { $numatt = count($object['_attachments']); foreach ($object['_attachments'] as $key => $attachment) { + // FIXME: kolab_storage and Roundcube attachment hooks use different fields! + if (empty($attachment['content']) && !empty($attachment['data'])) { + $attachment['content'] = $attachment['data']; + unset($attachment['data'], $object['_attachments'][$key]['data']); + } + // make sure size is set, so object saved in cache contains this info if (!isset($attachment['size'])) { if (!empty($attachment['content'])) { @@ -701,7 +755,9 @@ // delete old message if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) { + $this->cache->bypass(true); $this->imap->delete_message($object['_msguid'], $object['_mailbox']); + $this->cache->bypass(false); $this->cache->set($object['_msguid'], false, $object['_mailbox']); } @@ -796,6 +852,8 @@ $msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object); $success = false; + $this->cache->bypass(true); + if ($msguid && $expunge) { $success = $this->imap->delete_message($msguid, $this->name); } @@ -803,6 +861,8 @@ $success = $this->imap->set_flag($msguid, 'DELETED', $this->name); } + $this->cache->bypass(false); + if ($success) { $this->cache->set($msguid, false); } @@ -817,7 +877,11 @@ public function delete_all() { $this->cache->purge(); - return $this->imap->clear_folder($this->name); + $this->cache->bypass(true); + $result = $this->imap->clear_folder($this->name); + $this->cache->bypass(false); + + return $result; } @@ -830,7 +894,11 @@ public function undelete($uid) { if ($msguid = $this->cache->uid2msguid($uid, true)) { - if ($this->imap->set_flag($msguid, 'UNDELETED', $this->name)) { + $this->cache->bypass(true); + $result = $this->imap->set_flag($msguid, 'UNDELETED', $this->name); + $this->cache->bypass(false); + + if ($result) { return $msguid; } } @@ -849,7 +917,11 @@ public function move($uid, $target_folder) { if ($msguid = $this->cache->uid2msguid($uid)) { - if ($this->imap->move_message($msguid, $target_folder, $this->name)) { + $this->cache->bypass(true); + $result = $this->imap->move_message($msguid, $target_folder, $this->name); + $this->cache->bypass(false); + + if ($result) { $this->cache->move($msguid, $uid, $target_folder); return true; } @@ -1099,9 +1171,7 @@ require_once('HTTP/Request2.php'); try { - $rcmail = rcube::get_instance(); - $request = new HTTP_Request2($url); - $request->setConfig(array('ssl_verify_peer' => $rcmail->config->get('kolab_ssl_verify_peer', true))); + $request = libkolab::http_request($url); // set authentication credentials if ($auth_user && $auth_passwd)
View file
chwala-0.1.tar.gz/lib/kolab/plugins/libkolab/libkolab.php -> chwala-0.2.tar.gz/lib/kolab/plugins/libkolab/libkolab.php
Changed
@@ -27,6 +27,8 @@ class libkolab extends rcube_plugin { + static $http_requests = array(); + /** * Required startup method of a Roundcube plugin */ @@ -59,4 +61,66 @@ $p['fetch_headers'] = trim($p['fetch_headers'] .' X-KOLAB-TYPE X-KOLAB-MIME-VERSION'); return $p; } + + /** + * 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); + } + + $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; + } }
View file
chwala-0.2.tar.gz/lib/locale/de_CH.php
Added
@@ -0,0 +1,74 @@ +<?php + +$LANG['about.community'] = 'This is the Community Edition of the <b>Kolab Server</b>.'; +$LANG['about.warranty'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.'; +$LANG['about.support'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.'; + +$LANG['collection.audio'] = 'Audio'; +$LANG['collection.video'] = 'Video'; +$LANG['collection.image'] = 'Bilder'; +$LANG['collection.document'] = 'Dokumente'; + +$LANG['file.copy'] = 'Kopieren'; +$LANG['file.create'] = 'Datei anlegen'; +$LANG['file.download'] = 'Herunterladen'; +$LANG['file.edit'] = 'Bearbeiten'; +$LANG['file.upload'] = 'Hochladen'; +$LANG['file.name'] = 'Name'; +$LANG['file.move'] = 'Verschieben'; +$LANG['file.mtime'] = 'Geändert'; +$LANG['file.size'] = 'Größe'; +$LANG['file.open'] = 'Öffnen'; +$LANG['file.delete'] = 'Löschen'; +$LANG['file.rename'] = 'Umbenennen'; +$LANG['file.search'] = 'Datei suchen'; +$LANG['file.type'] = 'Typ'; +$LANG['file.save'] = 'Speichern'; +$LANG['file.skip'] = 'Überspringen'; +$LANG['file.skipall'] = 'Alle überspringen'; +$LANG['file.overwrite'] = 'Überschreiben'; +$LANG['file.overwriteall'] = 'Alle überschreiben'; +$LANG['file.moveconfirm'] = 'Diese Aktion wird die Zieldatei <b>$file</b> überschreiben.'; +$LANG['file.progress'] = '$current von $total hochgeladen ($percent%)'; + +$LANG['folder.createtitle'] = 'Ordner anlegen'; +$LANG['folder.delete'] = 'Löschen'; +$LANG['folder.edit'] = 'Bearbeiten'; +$LANG['folder.edittitle'] = 'Ordner bearbeiten'; +$LANG['folder.under'] = 'im aktuellen Ordner'; + +$LANG['form.submit'] = 'Absenden'; +$LANG['form.cancel'] = 'Abbrechen'; + +$LANG['login.username'] = 'Benutzername'; +$LANG['login.password'] = 'Passwort'; +$LANG['login.login'] = 'Anmelden'; + +$LANG['reqtime'] = 'Zeit der Anfrage: $1 Sekunde(n).'; +$LANG['maxupload'] = 'Maximale Dateigröße: $1'; +$LANG['internalerror'] = 'Interner Systemfehler!'; +$LANG['loginerror'] = 'Benutzername oder Passwort inkorrekt!'; +$LANG['loading'] = 'Laden...'; +$LANG['saving'] = 'Speichern...'; +$LANG['deleting'] = 'Löschen...'; +$LANG['copying'] = 'Kopieren...'; +$LANG['moving'] = 'Verschieben...'; +$LANG['logout'] = 'Abmelden'; +$LANG['close'] = 'Schließen'; +$LANG['servererror'] = 'Serverfehler!'; +$LANG['session.expired'] = 'Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.'; + +$LANG['search'] = 'Suchen'; +$LANG['search.loading'] = 'Suchen...'; +$LANG['search.in_all_folders'] = 'in allen Ordnern'; +$LANG['search.in_current_folder'] = 'in diesem Ordner'; + +$LANG['size.B'] = 'B'; +$LANG['size.KB'] = 'KB'; +$LANG['size.MB'] = 'MB'; +$LANG['size.GB'] = 'GB'; + +$LANG['upload.size'] = 'Größe:'; +$LANG['upload.progress'] = 'Fortschritt:'; +$LANG['upload.rate'] = 'Rate:'; +$LANG['upload.eta'] = 'ETA:';
View file
chwala-0.2.tar.gz/lib/locale/de_DE.php
Added
@@ -0,0 +1,74 @@ +<?php + +$LANG['about.community'] = 'This is the Community Edition of the <b>Kolab Server</b>.'; +$LANG['about.warranty'] = 'Professional support is available from <a href="http://kolabsys.com">Kolab Systems</a>.'; +$LANG['about.support'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.'; + +$LANG['collection.audio'] = 'Audio'; +$LANG['collection.video'] = 'Video'; +$LANG['collection.image'] = 'Bilder'; +$LANG['collection.document'] = 'Dokumente'; + +$LANG['file.copy'] = 'Kopieren'; +$LANG['file.create'] = 'Datei anlegen'; +$LANG['file.download'] = 'Herunterladen'; +$LANG['file.edit'] = 'Bearbeiten'; +$LANG['file.upload'] = 'Hochladen'; +$LANG['file.name'] = 'Name'; +$LANG['file.move'] = 'Verschieben'; +$LANG['file.mtime'] = 'Geändert'; +$LANG['file.size'] = 'Größe'; +$LANG['file.open'] = 'Öffnen'; +$LANG['file.delete'] = 'Löschen'; +$LANG['file.rename'] = 'Umbenennen'; +$LANG['file.search'] = 'Datei suchen'; +$LANG['file.type'] = 'Typ'; +$LANG['file.save'] = 'Speichern'; +$LANG['file.skip'] = 'Überspringen'; +$LANG['file.skipall'] = 'Alle überspringen'; +$LANG['file.overwrite'] = 'Überschreiben'; +$LANG['file.overwriteall'] = 'Alle überschreiben'; +$LANG['file.moveconfirm'] = 'Diese Aktion wird die Zieldatei <b>$file</b> überschreiben.'; +$LANG['file.progress'] = '$current von $total hochgeladen ($percent%)'; + +$LANG['folder.createtitle'] = 'Ordner anlegen'; +$LANG['folder.delete'] = 'Löschen'; +$LANG['folder.edit'] = 'Bearbeiten'; +$LANG['folder.edittitle'] = 'Ordner bearbeiten'; +$LANG['folder.under'] = 'im aktuellen Ordner'; + +$LANG['form.submit'] = 'Absenden'; +$LANG['form.cancel'] = 'Abbrechen'; + +$LANG['login.username'] = 'Benutzername'; +$LANG['login.password'] = 'Passwort'; +$LANG['login.login'] = 'Anmelden'; + +$LANG['reqtime'] = 'Zeit der Anfrage: $1 Sekunde(n).'; +$LANG['maxupload'] = 'Maximale Dateigröße: $1'; +$LANG['internalerror'] = 'Interner Systemfehler!'; +$LANG['loginerror'] = 'Benutzername oder Passwort inkorrekt!'; +$LANG['loading'] = 'Laden...'; +$LANG['saving'] = 'Speichern...'; +$LANG['deleting'] = 'Löschen...'; +$LANG['copying'] = 'Kopieren...'; +$LANG['moving'] = 'Verschieben...'; +$LANG['logout'] = 'Abmelden'; +$LANG['close'] = 'Schließen'; +$LANG['servererror'] = 'Serverfehler!'; +$LANG['session.expired'] = 'Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.'; + +$LANG['search'] = 'Suchen'; +$LANG['search.loading'] = 'Suchen...'; +$LANG['search.in_all_folders'] = 'in allen Ordnern'; +$LANG['search.in_current_folder'] = 'in diesem Ordner'; + +$LANG['size.B'] = 'B'; +$LANG['size.KB'] = 'KB'; +$LANG['size.MB'] = 'MB'; +$LANG['size.GB'] = 'GB'; + +$LANG['upload.size'] = 'Größe:'; +$LANG['upload.progress'] = 'Fortschritt:'; +$LANG['upload.rate'] = 'Rate:'; +$LANG['upload.eta'] = 'ETA:';
View file
chwala-0.1.tar.gz/lib/viewers/pdf.php -> chwala-0.2.tar.gz/lib/viewers/pdf.php
Changed
@@ -47,8 +47,14 @@ $browser = $api->get_browser(); - // disable viewer in unsupported browsers - if ($browser->ie && $browser->ver < 9) { + // disable viewer in unsupported browsers according to + // https://github.com/mozilla/pdf.js/wiki/Required-Browser-Features + if (($browser->ie && $browser->ver < 9) + || ($browser->opera && $browser->ver < 9.5) + || ($browser->chrome && $browser->ver < 24) + || ($browser->safari && $browser->ver < 5) + || ($browser->mz && $browser->ver < 6) + ) { $this->mimetypes = array(); } }
View file
chwala.dsc
Changed
@@ -2,16 +2,14 @@ Source: chwala Binary: chwala Architecture: all -Version: 0.1~dev20130809-2~kolab9 +Version: 0.2-0~kolab1 Maintainer: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Uploaders: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>, Paul Klos <kolab@klos2day.nl> Homepage: http://kolab.org/about/chwala/ Standards-Version: 3.9.3 -Vcs-Browser: http://git.kolabsys.com/apt/roundcubemail/ -Vcs-Git: git://git.kolabsys.com/git/apt/roundcubemail/ Build-Depends: debhelper (>= 8) Package-List: roundcubemail deb web extra Files: - 00000000000000000000000000000000 0 chwala-0.1.tar.gz + 00000000000000000000000000000000 0 chwala-0.2.tar.gz 00000000000000000000000000000000 0 debian.tar.gz
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +chwala (0.2-0~kolab1) unstable; urgency=low + + * New upstream version + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Sun, 24 Nov 2013 07:14:39 -0500 + chwala (0.1~dev20130809-2~kolab9) unstable; urgency=low * Install apache configuration in the right location (attempt #2)
View file
debian.control
Changed
@@ -10,8 +10,10 @@ Package: chwala Architecture: all Depends: + php-http-request2, + php-smarty >= 3.1.7, roundcubemail, - roundcubemail-plugins-kolab, + roundcubemail-plugins-kolab, ${misc:Depends} Description: Glorified WebDAV, done right Chwala is a file storage suite purposed for integration in to other
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.