Projects
Kolab:3.4
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
.