Projects
Kolab:3.4:Updates
chwala
Log In
Username
Password
We truncated the diff of some files because they were too big. If you want to see the full diff for every file,
click here
.
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();
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; }
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 @@
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 '(':
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();
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); + } +
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); -
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
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(
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; }
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) {
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); +
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)); + }
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();
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
.