3 commits - lib/ext lib/kolab_sync.php lib/plugins

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Mon Aug 19 14:01:29 CEST 2013


 lib/ext/Roundcube/README.md                           |  102 +++
 lib/ext/Roundcube/html.php                            |    2 
 lib/ext/Roundcube/rcube.php                           |   14 
 lib/ext/Roundcube/rcube_base_replacer.php             |    4 
 lib/ext/Roundcube/rcube_charset.php                   |   14 
 lib/ext/Roundcube/rcube_config.php                    |   73 ++
 lib/ext/Roundcube/rcube_image.php                     |   91 ++-
 lib/ext/Roundcube/rcube_imap.php                      |   42 -
 lib/ext/Roundcube/rcube_imap_generic.php              |   79 +--
 lib/ext/Roundcube/rcube_ldap.php                      |   70 +-
 lib/ext/Roundcube/rcube_message.php                   |    6 
 lib/ext/Roundcube/rcube_session.php                   |   12 
 lib/ext/Roundcube/rcube_shared.inc                    |  474 ------------------
 lib/ext/Roundcube/rcube_spellchecker.php              |  144 +++++
 lib/ext/Roundcube/rcube_storage.php                   |    2 
 lib/ext/Roundcube/rcube_string_replacer.php           |    4 
 lib/ext/Roundcube/rcube_utils.php                     |   15 
 lib/ext/Roundcube/rcube_washtml.php                   |   19 
 lib/kolab_sync.php                                    |    2 
 lib/plugins/kolab_auth/kolab_auth.php                 |   22 
 lib/plugins/kolab_auth/kolab_auth_ldap.php            |   18 
 lib/plugins/kolab_folders/kolab_folders.php           |    1 
 lib/plugins/libkolab/SQL/postgres.initial.sql         |   31 +
 lib/plugins/libkolab/bin/Date_Recurrence_weekday.diff |  325 ------------
 lib/plugins/libkolab/bin/Date_last_weekday.diff       |   37 -
 lib/plugins/libkolab/bin/get_horde_date.sh            |   64 --
 lib/plugins/libkolab/bin/modcache.sh                  |    4 
 lib/plugins/libkolab/config.inc.php.dist              |    4 
 lib/plugins/libkolab/lib/kolab_format_contact.php     |    2 
 lib/plugins/libkolab/lib/kolab_format_event.php       |   40 -
 lib/plugins/libkolab/lib/kolab_format_xcal.php        |   43 +
 lib/plugins/libkolab/lib/kolab_storage.php            |  185 ++++++-
 lib/plugins/libkolab/lib/kolab_storage_cache.php      |    4 
 lib/plugins/libkolab/lib/kolab_storage_folder.php     |    4 
 34 files changed, 810 insertions(+), 1143 deletions(-)

New commits:
commit 5877185dacd6738af323edb37585c8c5b784d870
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Mon Aug 19 14:01:15 2013 +0200

    This branch now moves forward to 2.3

diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php
index d27608a..544a388 100644
--- a/lib/kolab_sync.php
+++ b/lib/kolab_sync.php
@@ -43,7 +43,7 @@ class kolab_sync extends rcube
     public $user;
 
     const CHARSET = 'UTF-8';
-    const VERSION = "2.2";
+    const VERSION = "2.3";
 
 
     /**


commit 8c5809ea81503e1e988ecbf1ae0e579c30aec449
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Mon Aug 19 13:57:21 2013 +0200

    Rebase lib/ext/Roundcube

diff --git a/lib/ext/Roundcube/README.md b/lib/ext/Roundcube/README.md
new file mode 100644
index 0000000..88f2d07
--- /dev/null
+++ b/lib/ext/Roundcube/README.md
@@ -0,0 +1,102 @@
+Roundcube Framework
+===================
+
+INTRODUCTION
+------------
+The Roundcube Framework is the basic library used for the Roundcube Webmail
+application. It is an extract of classes providing the core functionality for
+an email system. They can be used individually or as package for the following
+tasks:
+
+- IMAP mailbox access with optional caching
+- MIME message handling
+- Email message creation and sending through SMTP
+- General caching utilities using the local database
+- Database abstraction using PDO
+- VCard parsing and writing
+
+
+INSTALLATION
+------------
+Copy all files of this directory to your project or install it in the default
+include_path directory of your webserver. Some classes of the framework require
+one or multiple of the following [PEAR][pear] libraries:
+
+- Mail_Mime 1.8.1 or newer
+- Mail_mimeDecode 1.5.5 or newer
+- Net_SMTP (latest from https://github.com/pear/Net_SMTP/)
+- Net_IDNA2 0.1.1 or newer
+- Auth_SASL 1.0.6 or newer
+
+
+USAGE
+-----
+The Roundcube Framework provides a bootstrapping file which registers an
+autoloader and sets up the environment necessary for the Roundcube classes.
+In order to make use of the framework, simply include the bootstrap.php file
+from this directory in your application and start using the classes by simply
+instantiating them.
+
+If you wanna use more complex functionality like IMAP access with database
+caching or plugins, the rcube singleton helps you loading the necessary files:
+
+```php
+<?php
+
+define('RCUBE_CONFIG_DIR',  '<path-to-config-directory>');
+define('RCUBE_PLUGINS_DIR', '<path-to-roundcube-plugins-directory');
+
+require_once '<path-to-roundcube-framework/bootstrap.php';
+
+$rcube = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS);
+$imap = $rcube->get_storage();
+
+// do cool stuff here...
+
+?>
+```
+
+LICENSE
+-------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License (**with exceptions
+for plugins**) 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see [www.gnu.org/licenses/][gpl].
+
+This file forms part of the Roundcube Webmail Framework for which the
+following exception is added: Plugins which merely make function calls to the
+Roundcube Webmail Framework, and for that purpose include it by reference
+shall not be considered modifications of the software.
+
+If you wish to use this file in another project or create a modified
+version that will not be part of the Roundcube Webmail Framework, you
+may remove the exception above and use this source code under the
+original version of the license.
+
+For more details about licensing and the exceptions for skins and plugins
+see [roundcube.net/license][license]
+
+
+CONTACT
+-------
+For any bug reports or feature requests please refer to the tracking system
+at [trac.roundcube.net][tracreport] or subscribe to our mailing list.
+See [roundcube.net/support][support] for details.
+
+You're always welcome to send a message to the project admins:
+hello(at)roundcube(dot)net
+
+
+[pear]:         http://pear.php.net
+[gpl]:          http://www.gnu.org/licenses/
+[license]:      http://roundcube.net/license
+[support]:      http://roundcube.net/support
+[tracreport]:   http://trac.roundcube.net/wiki/Howto_ReportIssues
\ No newline at end of file
diff --git a/lib/ext/Roundcube/html.php b/lib/ext/Roundcube/html.php
index 3e6e47a..a367112 100644
--- a/lib/ext/Roundcube/html.php
+++ b/lib/ext/Roundcube/html.php
@@ -358,7 +358,7 @@ class html_inputfield extends html
     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',
diff --git a/lib/ext/Roundcube/rcube.php b/lib/ext/Roundcube/rcube.php
index 6543a39..e0f889a 100644
--- a/lib/ext/Roundcube/rcube.php
+++ b/lib/ext/Roundcube/rcube.php
@@ -105,13 +105,14 @@ class rcube
      * 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 @@ class rcube
     /**
      * 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 @@ class rcube
             '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,
@@ -496,11 +498,11 @@ class rcube
 
         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);
                 }
             }
diff --git a/lib/ext/Roundcube/rcube_base_replacer.php b/lib/ext/Roundcube/rcube_base_replacer.php
index e41ccb1..a59bba9 100644
--- a/lib/ext/Roundcube/rcube_base_replacer.php
+++ b/lib/ext/Roundcube/rcube_base_replacer.php
@@ -44,8 +44,8 @@ class rcube_base_replacer
     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);
     }
diff --git a/lib/ext/Roundcube/rcube_charset.php b/lib/ext/Roundcube/rcube_charset.php
index a7f26a3..19dbf6c 100644
--- a/lib/ext/Roundcube/rcube_charset.php
+++ b/lib/ext/Roundcube/rcube_charset.php
@@ -674,23 +674,27 @@ class rcube_charset
 
             // 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',
diff --git a/lib/ext/Roundcube/rcube_config.php b/lib/ext/Roundcube/rcube_config.php
index 18055f7..3cf4da8 100644
--- a/lib/ext/Roundcube/rcube_config.php
+++ b/lib/ext/Roundcube/rcube_config.php
@@ -26,6 +26,8 @@ class rcube_config
 {
     const DEFAULT_SKIN = 'larry';
 
+    private $env = '';
+    private $basedir = 'config/';
     private $prop = array();
     private $errors = array();
     private $userprefs = array();
@@ -50,9 +52,14 @@ class rcube_config
 
     /**
      * Object constructor
+     *
+     * @param string Environment suffix for config files to load
      */
-    public function __construct()
+    public function __construct($env = '')
     {
+        $this->env = $env;
+        $this->basedir = RCUBE_CONFIG_DIR;
+
         $this->load();
 
         // Defaults, that we do not require you to configure,
@@ -69,16 +76,26 @@ class rcube_config
      */
     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();
+        if (!empty($_SERVER['HTTP_HOST']))
+            $this->load_host_config();
 
         // set skin (with fallback to old 'skin_path' property)
         if (empty($this->prop['skin'])) {
@@ -155,7 +172,7 @@ class rcube_config
         }
 
         if ($fname) {
-            $this->load_from_file(RCUBE_CONFIG_DIR . $fname);
+            $this->load_from_file($fname);
         }
     }
 
@@ -164,18 +181,24 @@ class rcube_config
      * 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)
     {
-        if (is_file($fpath) && is_readable($fpath)) {
+        $fpath = $this->resolve_path($file);
+        if ($fpath && (is_file($fpath) || file_exists($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)) {
+            if (is_array($config)) {
+                $this->merge($config);
+                return true;
+            }
+            // deprecated name of config variable
+            else if (is_array($rcmail_config)) {
                 $this->merge($rcmail_config);
                 return true;
             }
@@ -184,6 +207,30 @@ class rcube_config
         return false;
     }
 
+    /**
+     * Helper method to resolve the absolute path to the given config file.
+     * This also takes the 'env' property into account.
+     */
+    public function resolve_path($file, $use_env = true)
+    {
+        if (strpos($file, '/') === false) {
+            $file = rtrim($this->basedir, '/') . '/' . $file;
+
+            if (!realpath($file) === false) {
+                $file = realpath($file);
+            }
+        }
+
+        // check if <file>-env.ini exists
+        if ($file && $use_env && !empty($this->env)) {
+            $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $file);
+            if (is_file($envfile))
+                return $envfile;
+        }
+
+        return $file;
+    }
+
 
     /**
      * Getter for a specific config parameter
diff --git a/lib/ext/Roundcube/rcube_image.php b/lib/ext/Roundcube/rcube_image.php
index 09bb4e8..4e4caae 100644
--- a/lib/ext/Roundcube/rcube_image.php
+++ b/lib/ext/Roundcube/rcube_image.php
@@ -105,7 +105,6 @@ class rcube_image
         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 @@ class rcube_image
                 $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 @@ class rcube_image
             // 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 @@ class rcube_image
             $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);
diff --git a/lib/ext/Roundcube/rcube_imap.php b/lib/ext/Roundcube/rcube_imap.php
index 7ef8d62..c5346c8 100644
--- a/lib/ext/Roundcube/rcube_imap.php
+++ b/lib/ext/Roundcube/rcube_imap.php
@@ -2092,17 +2092,18 @@ class rcube_imap extends rcube_storage
     /**
      * 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 +2122,9 @@ class rcube_imap extends rcube_storage
         }
 
         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 +2669,6 @@ class rcube_imap extends rcube_storage
 
         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 +2681,14 @@ class rcube_imap extends rcube_storage
             }
         }
         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);
                 }
             }
         }
diff --git a/lib/ext/Roundcube/rcube_imap_generic.php b/lib/ext/Roundcube/rcube_imap_generic.php
index 1928c70..e119374 100644
--- a/lib/ext/Roundcube/rcube_imap_generic.php
+++ b/lib/ext/Roundcube/rcube_imap_generic.php
@@ -715,6 +715,10 @@ class rcube_imap_generic
             $auth_method = 'CHECK';
         }
 
+        if (!empty($this->prefs['disabled_caps'])) {
+            $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']);
+        }
+
         $result = false;
 
         // initialize connection
@@ -796,23 +800,21 @@ class rcube_imap_generic
 
         // 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
@@ -1329,9 +1331,8 @@ class rcube_imap_generic
                         $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 +2160,18 @@ class rcube_imap_generic
                     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 +2490,7 @@ class rcube_imap_generic
         }
 
         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 +2516,7 @@ class rcube_imap_generic
 
                 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 +2540,11 @@ class rcube_imap_generic
                 $prev  = '';
                 $found = true;
 
-                while ($bytes > 0) {
+                // empty body
+                if (!$bytes) {
+                    $result = '';
+                }
+                else while ($bytes > 0) {
                     $line = $this->readLine(8192);
 
                     if ($line === NULL) {
@@ -2980,7 +2988,7 @@ class rcube_imap_generic
         }
 
         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);
@@ -3477,25 +3485,24 @@ class rcube_imap_generic
 
             // Parenthesized list
             case '(':
-            case '[':
                 $str = substr($str, 1);
                 $result[] = self::tokenizeResponse($str);
                 break;
             case ')':
-            case ']':
                 $str = substr($str, 1);
                 return $result;
                 break;
 
-            // String atom, number, NIL, *, %
+            // String atom, number, astring, NIL, *, %
             default:
                 // empty string
                 if ($str === '' || $str === null) {
                     break 2;
                 }
 
-                // excluded chars: SP, CTL, ), [, ], DEL
-                if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) {
+                // excluded chars: SP, CTL, ), DEL
+                // we do not exclude [ and ] (#1489223)
+                if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) {
                     $result[] = $m[1] == 'NIL' ? NULL : $m[1];
                     $str = substr($str, strlen($m[1]));
                 }
@@ -3686,6 +3693,10 @@ class rcube_imap_generic
 
         $this->capability = explode(' ', strtoupper($str));
 
+        if (!empty($this->prefs['disabled_caps'])) {
+            $this->capability = array_diff($this->capability, $this->prefs['disabled_caps']);
+        }
+
         if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
             $this->prefs['literal+'] = true;
         }
diff --git a/lib/ext/Roundcube/rcube_ldap.php b/lib/ext/Roundcube/rcube_ldap.php
index 522c81d..cb7fa84 100644
--- a/lib/ext/Roundcube/rcube_ldap.php
+++ b/lib/ext/Roundcube/rcube_ldap.php
@@ -27,7 +27,7 @@
  */
 class rcube_ldap extends rcube_addressbook
 {
-    /** public properties */
+    // public properties
     public $primary_key = 'ID';
     public $groups      = false;
     public $readonly    = true;
@@ -35,7 +35,7 @@ class rcube_ldap extends rcube_addressbook
     public $group_id    = 0;
     public $coltypes    = array();
 
-    /** private properties */
+    // private properties
     protected $ldap;
     protected $prop     = array();
     protected $fieldmap = array();
@@ -46,6 +46,21 @@ class rcube_ldap extends rcube_addressbook
     protected $mail_domain = '';
     protected $debug = false;
 
+    /**
+     * Group objectclass (lowercase) to member attribute mapping
+     *
+     * @var array
+     */
+    private static $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;
@@ -499,7 +514,8 @@ class rcube_ldap extends rcube_addressbook
             $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 +523,7 @@ class rcube_ldap extends rcube_addressbook
 
             // 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();
@@ -826,13 +842,13 @@ class rcube_ldap extends rcube_addressbook
         }
         // We have a connection but no result set, attempt to get one.
         else if ($this->ready) {
-            $prop = $this->group_id ? $this->group_data : $this->prop;
+            $prop    = $this->group_id ? $this->group_data : $this->prop;
+            $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn;
 
             if (!empty($this->filter)) {  // Use global search filter
                 $prop['filter'] = $this->filter;
             }
-
-            $count = $this->ldap->search($prop['base_dn'], $prop['filter'], $prop['scope'], array('dn'), $prop, true);
+            $count = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], array('dn'), $prop, true);
         }
 
         return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
@@ -1191,8 +1207,7 @@ class rcube_ldap extends rcube_addressbook
             // change the group membership of the contact
             if ($this->groups) {
                 $group_ids = $this->get_record_groups($dn);
-                foreach ($group_ids as $group_id => $group_prop)
-                {
+                foreach (array_keys($group_ids) as $group_id) {
                     $this->remove_from_group($group_id, $dn);
                     $this->add_to_group($group_id, $newdn);
                 }
@@ -1257,7 +1272,7 @@ class rcube_ldap extends rcube_addressbook
             if ($this->groups) {
                 $dn = self::dn_encode($dn);
                 $group_ids = $this->get_record_groups($dn);
-                foreach ($group_ids as $group_id => $group_prop) {
+                foreach (array_keys($group_ids) as $group_id) {
                     $this->remove_from_group($group_id, $dn);
                 }
             }
@@ -1445,11 +1460,11 @@ class rcube_ldap extends rcube_addressbook
     {
         // list of known attribute aliases
         static $aliases = array(
-            'gn' => 'givenname',
+            'gn'            => 'givenname',
             'rfc822mailbox' => 'email',
-            'userid' => 'uid',
-            'emailaddress' => 'email',
-            'pkcs9email' => 'email',
+            'userid'        => 'uid',
+            'emailaddress'  => 'email',
+            'pkcs9email'    => 'email',
         );
 
         list($name, $limit) = explode(':', $namev, 2);
@@ -1463,11 +1478,9 @@ class rcube_ldap extends rcube_addressbook
      */
     private static function is_group_entry($entry)
     {
-        return array_intersect(
-            array('group', 'groupofnames', 'kolabgroupofnames', 'groupofuniquenames',
-                'kolabgroupofuniquenames', 'groupofurls', 'univentiongroup'),
-            array_map('strtolower', (array)$entry['objectclass'])
-        );
+        $classes = array_map('strtolower', (array)$entry['objectclass']);
+
+        return count(array_intersect(array_keys(self::$group_types), $classes)) > 0;
     }
 
     /**
@@ -1573,6 +1586,7 @@ class rcube_ldap extends rcube_addressbook
 
         $base_dn    = $this->groups_base_dn;
         $filter     = $this->prop['groups']['filter'];
+        $scope      = $this->prop['groups']['scope'];
         $name_attr  = $this->prop['groups']['name_attr'];
         $email_attr = $this->prop['groups']['email_attr'] ? $this->prop['groups']['email_attr'] : 'mail';
         $sort_attrs = $this->prop['groups']['sort'] ? (array)$this->prop['groups']['sort'] : array($name_attr);
@@ -1593,7 +1607,7 @@ class rcube_ldap extends rcube_addressbook
         }
 
         $attrs     = array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr));
-        $ldap_data = $ldap->search($base_dn, $filter, $this->prop['groups']['scope'], $attrs, $this->prop['groups']);
+        $ldap_data = $ldap->search($base_dn, $filter, $scope, $attrs, $this->prop['groups']);
 
         if ($ldap_data === false) {
             return array();
@@ -1864,6 +1878,7 @@ class rcube_ldap extends rcube_addressbook
         $name_attr   = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn';
         $member_attr = $this->get_group_member_attr();
         $add_filter  = '';
+
         if ($member_attr != 'member' && $member_attr != 'uniqueMember')
             $add_filter = "($member_attr=$contact_dn)";
         $filter = strtr("(|(member=$contact_dn)(uniqueMember=$contact_dn)$add_filter)", array('\\' => '\\\\'));
@@ -1879,8 +1894,9 @@ class rcube_ldap extends rcube_addressbook
                 $entry['dn'] = $ldap_data->get_dn();
             $group_name = $entry[$name_attr][0];
             $group_id = self::dn_encode($entry['dn']);
-            $groups[$group_id] = array('ID' => $group_id, 'name' => $group_name, 'dn' => $entry['dn']);
+            $groups[$group_id] = $group_name;
         }
+
         return $groups;
     }
 
@@ -1895,16 +1911,8 @@ class rcube_ldap extends rcube_addressbook
 
         if (!empty($object_classes)) {
             foreach ((array)$object_classes as $oc) {
-                switch (strtolower($oc)) {
-                    case 'group':
-                    case 'groupofnames':
-                    case 'kolabgroupofnames':
-                        return 'member';
-
-                    case 'groupofuniquenames':
-                    case 'kolabgroupofuniquenames':
-                    case 'univentiongroup':
-                        return 'uniqueMember';
+                if ($attr = self::$group_types[strtolower($oc)]) {
+                    return $attr;
                 }
             }
         }
diff --git a/lib/ext/Roundcube/rcube_message.php b/lib/ext/Roundcube/rcube_message.php
index 797ca18..0d33ea4 100644
--- a/lib/ext/Roundcube/rcube_message.php
+++ b/lib/ext/Roundcube/rcube_message.php
@@ -168,10 +168,11 @@ class rcube_message
      * @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 @@ class rcube_message
             // 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);
         }
     }
 
diff --git a/lib/ext/Roundcube/rcube_session.php b/lib/ext/Roundcube/rcube_session.php
index 615ec6f..67072df 100644
--- a/lib/ext/Roundcube/rcube_session.php
+++ b/lib/ext/Roundcube/rcube_session.php
@@ -54,7 +54,7 @@ class rcube_session
     {
         $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 @@ class rcube_session
 
         $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 @@ class rcube_session
     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 @@ class rcube_session
     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) {
diff --git a/lib/ext/Roundcube/rcube_shared.inc b/lib/ext/Roundcube/rcube_shared.inc
deleted file mode 100644
index 4577c6d..0000000
--- a/lib/ext/Roundcube/rcube_shared.inc
+++ /dev/null
@@ -1,474 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_shared.inc                                      |
- |                                                                       |
- | This file is part of the Roundcube PHP suite                          |
- | Copyright (C) 2005-2012, 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.                     |
- |                                                                       |
- | CONTENTS:                                                             |
- |   Shared functions used by Roundcube Framework                        |
- |                                                                       |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube at gmail.com>                        |
- +-----------------------------------------------------------------------+
-*/
-
-
-/**
- * Roundcube shared functions
- *
- * @package Core
- */
-
-
-/**
- * Similar function as in_array() but case-insensitive
- *
- * @param string $needle    Needle value
- * @param array  $heystack  Array to search in
- *
- * @return boolean True if found, False if not
- */
-function in_array_nocase($needle, $haystack)
-{
-    $needle = mb_strtolower($needle);
-    foreach ((array)$haystack as $value) {
-        if ($needle === mb_strtolower($value)) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-
-/**
- * Find out if the string content means true or false
- *
- * @param string $str  Input value
- *
- * @return boolean Boolean value
- */
-function get_boolean($str)
-{
-    $str = strtolower($str);
-
-    return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true);
-}
-
-
-/**
- * Parse a human readable string for a number of bytes.
- *
- * @param string $str  Input string
- *
- * @return float Number of bytes
- */
-function parse_bytes($str)
-{
-    if (is_numeric($str)) {
-        return floatval($str);
-    }
-
-    if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) {
-        $bytes = floatval($regs[1]);
-        switch (strtolower($regs[2])) {
-        case 'g':
-        case 'gb':
-            $bytes *= 1073741824;
-            break;
-        case 'm':
-        case 'mb':
-            $bytes *= 1048576;
-            break;
-        case 'k':
-        case 'kb':
-            $bytes *= 1024;
-            break;
-        }
-    }
-
-    return floatval($bytes);
-}
-
-
-/**
- * Make sure the string ends with a slash
- */
-function slashify($str)
-{
-  return unslashify($str).'/';
-}
-
-
-/**
- * Remove slashes at the end of the string
- */
-function unslashify($str)
-{
-  return preg_replace('/\/+$/', '', $str);
-}
-
-
-/**
- * Delete all files within a folder
- *
- * @param string Path to directory
- *
- * @return boolean True on success, False if directory was not found
- */
-function clear_directory($dir_path)
-{
-    $dir = @opendir($dir_path);
-    if (!$dir) {
-        return false;
-    }
-
-    while ($file = readdir($dir)) {
-        if (strlen($file) > 2) {
-            unlink("$dir_path/$file");
-        }
-    }
-
-    closedir($dir);
-
-    return true;
-}
-
-
-/**
- * Returns number of seconds for a specified offset string.
- *
- * @param string $str  String representation of the offset (e.g. 20min, 5h, 2days, 1week)
- *
- * @return int Number of seconds
- */
-function get_offset_sec($str)
-{
-    if (preg_match('/^([0-9]+)\s*([smhdw])/i', $str, $regs)) {
-        $amount = (int) $regs[1];
-        $unit   = strtolower($regs[2]);
-    }
-    else {
-        $amount = (int) $str;
-        $unit   = 's';
-    }
-
-    switch ($unit) {
-    case 'w':
-        $amount *= 7;
-    case 'd':
-        $amount *= 24;
-    case 'h':
-        $amount *= 60;
-    case 'm':
-        $amount *= 60;
-    }
-
-    return $amount;
-}
-
-
-/**
- * Create a unix timestamp with a specified offset from now.
- *
- * @param string $offset_str  String representation of the offset (e.g. 20min, 5h, 2days)
- * @param int    $factor      Factor to multiply with the offset
- *
- * @return int Unix timestamp
- */
-function get_offset_time($offset_str, $factor=1)
-{
-    return time() + get_offset_sec($offset_str) * $factor;
-}
-
-
-/**
- * Truncate string if it is longer than the allowed length.
- * Replace the middle or the ending part of a string with a placeholder.
- *
- * @param string $str         Input string
- * @param int    $maxlength   Max. length
- * @param string $placeholder Replace removed chars with this
- * @param bool   $ending      Set to True if string should be truncated from the end
- *
- * @return string Abbreviated string
- */
-function abbreviate_string($str, $maxlength, $placeholder='...', $ending=false)
-{
-    $length = mb_strlen($str);
-
-    if ($length > $maxlength) {
-        if ($ending) {
-            return mb_substr($str, 0, $maxlength) . $placeholder;
-        }
-
-        $placeholder_length = mb_strlen($placeholder);
-        $first_part_length  = floor(($maxlength - $placeholder_length)/2);
-        $second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length;
-
-        $str = mb_substr($str, 0, $first_part_length) . $placeholder . mb_substr($str, $second_starting_location);
-    }
-
-    return $str;
-}
-
-
-/**
- * Get all keys from array (recursive).
- *
- * @param array $array  Input array
- *
- * @return array List of array keys
- */
-function array_keys_recursive($array)
-{
-    $keys = array();
-
-    if (!empty($array) && is_array($array)) {
-        foreach ($array as $key => $child) {
-            $keys[] = $key;
-            foreach (array_keys_recursive($child) as $val) {
-                $keys[] = $val;
-            }
-        }
-    }
-
-    return $keys;
-}
-
-
-/**
- * Remove all non-ascii and non-word chars except ., -, _
- */
-function asciiwords($str, $css_id = false, $replace_with = '')
-{
-    $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
-    return preg_replace("/[^$allowed]/i", $replace_with, $str);
-}
-
-
-/**
- * Check if a string contains only ascii characters
- *
- * @param string $str           String to check
- * @param bool   $control_chars Includes control characters
- *
- * @return bool
- */
-function is_ascii($str, $control_chars = true)
-{
-    $regexp = $control_chars ? '/[^\x00-\x7F]/' : '/[^\x20-\x7E]/';
-    return preg_match($regexp, $str) ? false : true;
-}
-
-
-/**
- * Remove single and double quotes from a given string
- *
- * @param string Input value
- *
- * @return string Dequoted string
- */
-function strip_quotes($str)
-{
-    return str_replace(array("'", '"'), '', $str);
-}
-
-
-/**
- * Remove new lines characters from given string
- *
- * @param string $str  Input value
- *
- * @return string Stripped string
- */
-function strip_newlines($str)
-{
-    return preg_replace('/[\r\n]/', '', $str);
-}
-
-
-/**
- * Compose a valid representation of name and e-mail address
- *
- * @param string $email  E-mail address
- * @param string $name   Person name
- *
- * @return string Formatted string
- */
-function format_email_recipient($email, $name = '')
-{
-    $email = trim($email);
-
-    if ($name && $name != $email) {
-        // Special chars as defined by RFC 822 need to in quoted string (or escaped).
-        if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) {
-            $name = '"'.addcslashes($name, '"').'"';
-        }
-
-        return "$name <$email>";
-    }
-
-    return $email;
-}
-
-
-/**
- * Format e-mail address
- *
- * @param string $email E-mail address
- *
- * @return string Formatted e-mail address
- */
-function format_email($email)
-{
-    $email = trim($email);
-    $parts = explode('@', $email);
-    $count = count($parts);
-
-    if ($count > 1) {
-        $parts[$count-1] = mb_strtolower($parts[$count-1]);
-
-        $email = implode('@', $parts);
-    }
-
-    return $email;
-}
-
-
-/**
- * mbstring replacement functions
- */
-if (!extension_loaded('mbstring'))
-{
-    function mb_strlen($str)
-    {
-        return strlen($str);
-    }
-
-    function mb_strtolower($str)
-    {
-        return strtolower($str);
-    }
-
-    function mb_strtoupper($str)
-    {
-        return strtoupper($str);
-    }
-
-    function mb_substr($str, $start, $len=null)
-    {
-        return substr($str, $start, $len);
-    }
-
-    function mb_strpos($haystack, $needle, $offset=0)
-    {
-        return strpos($haystack, $needle, $offset);
-    }
-
-    function mb_strrpos($haystack, $needle, $offset=0)
-    {
-        return strrpos($haystack, $needle, $offset);
-    }
-}
-
-/**
- * intl replacement functions
- */
-
-if (!function_exists('idn_to_utf8'))
-{
-    function idn_to_utf8($domain, $flags=null)
-    {
-        static $idn, $loaded;
-
-        if (!$loaded) {
-            $idn = new Net_IDNA2();
-            $loaded = true;
-        }
-
-        if ($idn && $domain && preg_match('/(^|\.)xn--/i', $domain)) {
-            try {
-                $domain = $idn->decode($domain);
-            }
-            catch (Exception $e) {
-            }
-        }
-        return $domain;
-    }
-}
-
-if (!function_exists('idn_to_ascii'))
-{
-    function idn_to_ascii($domain, $flags=null)
-    {
-        static $idn, $loaded;
-
-        if (!$loaded) {
-            $idn = new Net_IDNA2();
-            $loaded = true;
-        }
-
-        if ($idn && $domain && preg_match('/[^\x20-\x7E]/', $domain)) {
-            try {
-                $domain = $idn->encode($domain);
-            }
-            catch (Exception $e) {
-            }
-        }
-        return $domain;
-    }
-}
-
-/**
- * Use PHP5 autoload for dynamic class loading
- *
- * @todo Make Zend, PEAR etc play with this
- * @todo Make our classes conform to a more straight forward CS.
- */
-function rcube_autoload($classname)
-{
-    $filename = preg_replace(
-        array(
-            '/Mail_(.+)/',
-            '/Net_(.+)/',
-            '/Auth_(.+)/',
-            '/^html_.+/',
-            '/^utf8$/',
-        ),
-        array(
-            'Mail/\\1',
-            'Net/\\1',
-            'Auth/\\1',
-            'html',
-            'utf8.class',
-        ),
-        $classname
-    );
-
-    if ($fp = @fopen("$filename.php", 'r', true)) {
-        fclose($fp);
-        include_once "$filename.php";
-        return true;
-    }
-
-    return false;
-}
-
-/**
- * Local callback function for PEAR errors
- */
-function rcube_pear_error($err)
-{
-    error_log(sprintf("%s (%s): %s",
-        $err->getMessage(),
-        $err->getCode(),
-        $err->getUserinfo()), 0);
-}
diff --git a/lib/ext/Roundcube/rcube_spellchecker.php b/lib/ext/Roundcube/rcube_spellchecker.php
index 166de9b..df43652 100644
--- a/lib/ext/Roundcube/rcube_spellchecker.php
+++ b/lib/ext/Roundcube/rcube_spellchecker.php
@@ -84,6 +84,9 @@ class rcube_spellchecker
         if ($this->engine == 'pspell') {
             $this->matches = $this->_pspell_check($this->content);
         }
+        else if ($this->engine == 'enchant') {
+            $this->matches = $this->_enchant_check($this->content);
+        }
         else {
             $this->matches = $this->_googie_check($this->content);
         }
@@ -115,6 +118,9 @@ class rcube_spellchecker
         if ($this->engine == 'pspell') {
             return $this->_pspell_suggestions($word);
         }
+        else if ($this->engine == 'enchant') {
+            return $this->_enchant_suggestions($word);
+        }
 
         return $this->_googie_suggestions($word);
     }
@@ -133,6 +139,9 @@ class rcube_spellchecker
         if ($this->engine == 'pspell') {
             return $this->_pspell_words($text, $is_html);
         }
+        else if ($this->engine == 'enchant') {
+            return $this->_enchant_words($text, $is_html);
+        }
 
         return $this->_googie_words($text, $is_html);
     }
@@ -326,6 +335,141 @@ class rcube_spellchecker
     }
 
 
+    /**
+     * Checks the text using enchant
+     *
+     * @param string $text Text content for spellchecking
+     */
+    private function _enchant_check($text)
+    {
+        // init spellchecker
+        $this->_enchant_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->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);
+        }
+
+        return $matches;
+    }
+
+
+    /**
+     * Returns the misspelled words
+     */
+    private function _enchant_words($text = null, $is_html=false)
+    {
+        $result = array();
+
+        if ($text) {
+            // init spellchecker
+            $this->_enchant_init();
+
+            if (!$this->enchant_dictionary) {
+                return array();
+            }
+
+            // With Enchant we don't need to get suggestions to return misspelled words
+            if ($is_html) {
+                $text = $this->html2text($text);
+            }
+
+            $text = preg_split($this->separator, $text, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
+
+            foreach ($text as $w) {
+                $word = trim($w[0]);
+
+                // skip exceptions
+                if ($this->is_exception($word)) {
+                    continue;
+                }
+
+                if (!enchant_dict_check($this->enchant_dictionary, $word)) {
+                    $result[] = $word;
+                }
+            }
+
+            return $result;
+        }
+
+        foreach ($this->matches as $m) {
+            $result[] = $m[0];
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * Returns suggestions for misspelled word
+     */
+    private function _enchant_suggestions($word)
+    {
+        // init spellchecker
+        $this->_enchant_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();
+    }
+
+
+    /**
+     * Initializes PSpell dictionary
+     */
+    private function _enchant_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);
+    }
+
+
     private function _googie_check($text)
     {
         // spell check uri is configured
diff --git a/lib/ext/Roundcube/rcube_storage.php b/lib/ext/Roundcube/rcube_storage.php
index 4b336f2..de83345 100644
--- a/lib/ext/Roundcube/rcube_storage.php
+++ b/lib/ext/Roundcube/rcube_storage.php
@@ -61,8 +61,6 @@ abstract class rcube_storage
         'MAIL-FOLLOWUP-TO',
         'MAIL-REPLY-TO',
         'RETURN-PATH',
-        'DELIVERED-TO',
-        'ENVELOPE-TO',
     );
 
     const UNKNOWN       = 0;
diff --git a/lib/ext/Roundcube/rcube_string_replacer.php b/lib/ext/Roundcube/rcube_string_replacer.php
index 0fc90a5..354b459 100644
--- a/lib/ext/Roundcube/rcube_string_replacer.php
+++ b/lib/ext/Roundcube/rcube_string_replacer.php
@@ -37,9 +37,9 @@ class rcube_string_replacer
         // 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
diff --git a/lib/ext/Roundcube/rcube_utils.php b/lib/ext/Roundcube/rcube_utils.php
index 6c3bd21..cf87ded 100644
--- a/lib/ext/Roundcube/rcube_utils.php
+++ b/lib/ext/Roundcube/rcube_utils.php
@@ -666,6 +666,21 @@ class rcube_utils
 
 
     /**
+     * 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
diff --git a/lib/ext/Roundcube/rcube_washtml.php b/lib/ext/Roundcube/rcube_washtml.php
index 6b2efcc..8f7fe97 100644
--- a/lib/ext/Roundcube/rcube_washtml.php
+++ b/lib/ext/Roundcube/rcube_washtml.php
@@ -410,6 +410,25 @@ class rcube_washtml
         );
         $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.";


commit 38e9e41c862e6d4b0c185fb06ba13248c166e2c0
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Mon Aug 19 13:55:41 2013 +0200

    Rebase lib/plugins/*

diff --git a/lib/plugins/kolab_auth/kolab_auth.php b/lib/plugins/kolab_auth/kolab_auth.php
index 5579743..b139e32 100644
--- a/lib/plugins/kolab_auth/kolab_auth.php
+++ b/lib/plugins/kolab_auth/kolab_auth.php
@@ -87,6 +87,8 @@ class kolab_auth extends rcube_plugin
         //  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,9 +103,29 @@ class kolab_auth extends rcube_plugin
         //          ),
         //      ),
         //  );
+        //
+        // NOTE that <role_dn> may in fact be something like: 'cn=role,%dc'
 
         $role_settings = $rcmail->config->get('kolab_auth_role_settings');
 
+        $ldap = self::ldap();
+        if (!$ldap || !$ldap->ready) {
+            $args['abort'] = true;
+            return $args;
+        }
+
+        if (!empty($role_plugins)) {
+            foreach ($role_plugins as $role_dn => $plugins) {
+                $role_plugins[$ldap->parse_vars($role_dn)] = $plugins;
+            }
+        }
+
+        if (!empty($role_settings)) {
+            foreach ($role_settings as $role_dn => $settings) {
+                $role_settings[$ldap->parse_vars($role_dn)] = $settings;
+            }
+        }
+
         foreach ($role_dns as $role_dn) {
             if (isset($role_plugins[$role_dn]) && is_array($role_plugins[$role_dn])) {
                 foreach ($role_plugins[$role_dn] as $plugin) {
diff --git a/lib/plugins/kolab_auth/kolab_auth_ldap.php b/lib/plugins/kolab_auth/kolab_auth_ldap.php
index c8ebcd1..4584c60 100644
--- a/lib/plugins/kolab_auth/kolab_auth_ldap.php
+++ b/lib/plugins/kolab_auth/kolab_auth_ldap.php
@@ -391,17 +391,11 @@ class kolab_auth_ldap extends rcube_ldap_generic
                 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;
             }
 
@@ -426,7 +420,7 @@ class kolab_auth_ldap extends rcube_ldap_generic
      *
      * @param string $domain Domain name
      *
-     * @return string Domain name or domain DN string
+     * @return string Domain DN string
      */
     function find_domain($domain)
     {
@@ -458,7 +452,9 @@ class kolab_auth_ldap extends rcube_ldap_generic
                 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;
         }
     }
 
diff --git a/lib/plugins/kolab_folders/kolab_folders.php b/lib/plugins/kolab_folders/kolab_folders.php
index 5b1d1d3..cc43390 100644
--- a/lib/plugins/kolab_folders/kolab_folders.php
+++ b/lib/plugins/kolab_folders/kolab_folders.php
@@ -535,6 +535,7 @@ class kolab_folders extends rcube_plugin
             // create folder
             if (!$exists && !$storage->folder_exists($foldername)) {
                 $storage->create_folder($foldername, $type1 == 'mail');
+                $storage->subscribe($foldername);
             }
 
             // set type
diff --git a/lib/plugins/libkolab/SQL/postgres.initial.sql b/lib/plugins/libkolab/SQL/postgres.initial.sql
new file mode 100644
index 0000000..e06346c
--- /dev/null
+++ b/lib/plugins/libkolab/SQL/postgres.initial.sql
@@ -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');
diff --git a/lib/plugins/libkolab/bin/Date_Recurrence_weekday.diff b/lib/plugins/libkolab/bin/Date_Recurrence_weekday.diff
deleted file mode 100644
index e8b767d..0000000
--- a/lib/plugins/libkolab/bin/Date_Recurrence_weekday.diff
+++ /dev/null
@@ -1,325 +0,0 @@
---- Date/Recurrence.php.orig	2012-07-10 19:54:48.000000000 +0200
-+++ Date/Recurrence.php	2012-07-10 19:55:38.000000000 +0200
-@@ -95,6 +95,20 @@
-     public $recurData = null;
- 
-     /**
-+     * BYDAY recurrence number
-+     *
-+     * @var integer
-+     */
-+    public $recurNthDay = null;
-+
-+    /**
-+     * BYMONTH recurrence data
-+     *
-+     * @var array
-+     */
-+    public $recurMonths = array();
-+
-+    /**
-      * All the exceptions from recurrence for this event.
-      *
-      * @var array
-@@ -157,6 +171,44 @@
-     }
- 
-     /**
-+     *
-+     * @param integer $nthDay The nth weekday of month to repeat events on
-+     */
-+    public function setRecurNthWeekday($nth)
-+    {
-+        $this->recurNthDay = (int)$nth;
-+    }
-+
-+    /**
-+     *
-+     * @return integer  The nth weekday of month to repeat events.
-+     */
-+    public function getRecurNthWeekday()
-+    {
-+        return isset($this->recurNthDay) ? $this->recurNthDay : ceil($this->start->mday / 7);
-+    }
-+
-+    /**
-+     * Specifies the months for yearly (weekday) recurrence
-+     *
-+     * @param array $months  List of months (integers) this event recurs on.
-+     */
-+    function setRecurByMonth($months)
-+    {
-+        $this->recurMonths = (array)$months;
-+    }
-+
-+    /**
-+     * Returns a list of months this yearly event recurs on
-+     *
-+     * @return array List of months (integers) this event recurs on.
-+     */
-+    function getRecurByMonth()
-+    {
-+        return $this->recurMonths;
-+    }
-+
-+    /**
-      * Returns the days this event recurs on.
-      *
-      * @return integer  A mask consisting of Horde_Date::MASK_* constants
-@@ -546,8 +598,13 @@
-             $estart = clone $this->start;
- 
-             // What day of the week, and week of the month, do we recur on?
--            $nth = ceil($this->start->mday / 7);
--            $weekday = $estart->dayOfWeek();
-+            if (isset($this->recurNthDay)) {
-+                $nth = $this->recurNthDay;
-+                $weekday = log($this->recurData, 2);
-+            } else {
-+                $nth = ceil($this->start->mday / 7);
-+                $weekday = $estart->dayOfWeek();
-+            }
- 
-             // Adjust $estart to be the first candidate.
-             $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12;
-@@ -660,8 +717,13 @@
-             $estart = clone $this->start;
- 
-             // What day of the week, and week of the month, do we recur on?
--            $nth = ceil($this->start->mday / 7);
--            $weekday = $estart->dayOfWeek();
-+            if (isset($this->recurNthDay)) {
-+                $nth = $this->recurNthDay;
-+                $weekday = log($this->recurData, 2);
-+            } else {
-+                $nth = ceil($this->start->mday / 7);
-+                $weekday = $estart->dayOfWeek();
-+            }
- 
-             // Adjust $estart to be the first candidate.
-             $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
-@@ -894,15 +956,6 @@
-         case 'W':
-             $this->setRecurType(self::RECUR_WEEKLY);
-             if (!empty($remainder)) {
--                $maskdays = array(
--                    'SU' => Horde_Date::MASK_SUNDAY,
--                    'MO' => Horde_Date::MASK_MONDAY,
--                    'TU' => Horde_Date::MASK_TUESDAY,
--                    'WE' => Horde_Date::MASK_WEDNESDAY,
--                    'TH' => Horde_Date::MASK_THURSDAY,
--                    'FR' => Horde_Date::MASK_FRIDAY,
--                    'SA' => Horde_Date::MASK_SATURDAY,
--                );
-                 $mask = 0;
-                 while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) {
-                     $day = trim($matches[0]);
-@@ -953,7 +1006,10 @@
-                 list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d');
-                 $this->setRecurEnd(new Horde_Date(array('year' => $year,
-                                                         'month' => $month,
--                                                        'mday' => $mday)));
-+                                                        'mday' => $mday,
-+                                                        'hour' => 23,
-+                                                        'min' => 59,
-+                                                        'sec' => 59)));
-             }
-         }
-     }
-@@ -1049,6 +1105,16 @@
-             // Always default the recurInterval to 1.
-             $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1);
- 
-+            $maskdays = array(
-+                'SU' => Horde_Date::MASK_SUNDAY,
-+                'MO' => Horde_Date::MASK_MONDAY,
-+                'TU' => Horde_Date::MASK_TUESDAY,
-+                'WE' => Horde_Date::MASK_WEDNESDAY,
-+                'TH' => Horde_Date::MASK_THURSDAY,
-+                'FR' => Horde_Date::MASK_FRIDAY,
-+                'SA' => Horde_Date::MASK_SATURDAY,
-+            );
-+
-             switch (Horde_String::upper($rdata['FREQ'])) {
-             case 'DAILY':
-                 $this->setRecurType(self::RECUR_DAILY);
-@@ -1057,15 +1123,6 @@
-             case 'WEEKLY':
-                 $this->setRecurType(self::RECUR_WEEKLY);
-                 if (isset($rdata['BYDAY'])) {
--                    $maskdays = array(
--                        'SU' => Horde_Date::MASK_SUNDAY,
--                        'MO' => Horde_Date::MASK_MONDAY,
--                        'TU' => Horde_Date::MASK_TUESDAY,
--                        'WE' => Horde_Date::MASK_WEDNESDAY,
--                        'TH' => Horde_Date::MASK_THURSDAY,
--                        'FR' => Horde_Date::MASK_FRIDAY,
--                        'SA' => Horde_Date::MASK_SATURDAY,
--                    );
-                     $days = explode(',', $rdata['BYDAY']);
-                     $mask = 0;
-                     foreach ($days as $day) {
-@@ -1090,6 +1147,10 @@
-             case 'MONTHLY':
-                 if (isset($rdata['BYDAY'])) {
-                     $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
-+                    if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
-+                        $this->setRecurOnDay($maskdays[$m[2]]);
-+                        $this->setRecurNthWeekday($m[1]);
-+                    }
-                 } else {
-                     $this->setRecurType(self::RECUR_MONTHLY_DATE);
-                 }
-@@ -1100,6 +1161,14 @@
-                     $this->setRecurType(self::RECUR_YEARLY_DAY);
-                 } elseif (isset($rdata['BYDAY'])) {
-                     $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
-+                    if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
-+                        $this->setRecurOnDay($maskdays[$m[2]]);
-+                        $this->setRecurNthWeekday($m[1]);
-+                    }
-+                    if ($rdata['BYMONTH']) {
-+                        $months = explode(',', $rdata['BYMONTH']);
-+                        $this->setRecurByMonth($months);
-+                    }
-                 } else {
-                     $this->setRecurType(self::RECUR_YEARLY_DATE);
-                 }
-@@ -1163,13 +1232,19 @@
-             break;
- 
-         case self::RECUR_MONTHLY_WEEKDAY:
--            $nth_weekday = (int)($this->start->mday / 7);
--            if (($this->start->mday % 7) > 0) {
--                $nth_weekday++;
-+            if (isset($this->recurNthDay)) {
-+                $nth_weekday = $this->recurNthDay;
-+                $day_of_week = log($this->recurData, 2);
-+            } else {
-+                $day_of_week = $this->start->dayOfWeek();
-+                $nth_weekday = (int)($this->start->mday / 7);
-+                if (($this->start->mday % 7) > 0) {
-+                    $nth_weekday++;
-+                }
-             }
-             $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
-             $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval
--                . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()];
-+                . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week];
-             break;
- 
-         case self::RECUR_YEARLY_DATE:
-@@ -1182,15 +1257,22 @@
-             break;
- 
-         case self::RECUR_YEARLY_WEEKDAY:
--            $nth_weekday = (int)($this->start->mday / 7);
--            if (($this->start->mday % 7) > 0) {
--                $nth_weekday++;
--            }
-+            if (isset($this->recurNthDay)) {
-+                $nth_weekday = $this->recurNthDay;
-+                $day_of_week = log($this->recurData, 2);
-+            } else {
-+                $day_of_week = $this->start->dayOfWeek();
-+                $nth_weekday = (int)($this->start->mday / 7);
-+                if (($this->start->mday % 7) > 0) {
-+                    $nth_weekday++;
-+                }
-+             }
-+            $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month;
-             $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
-             $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
-                 . ';BYDAY='
-                 . $nth_weekday
--                . $vcaldays[$this->start->dayOfWeek()]
-+                . $vcaldays[$day_of_week]
-                 . ';BYMONTH=' . $this->start->month;
-             break;
-         }
-@@ -1223,6 +1305,21 @@
- 
-         $this->setRecurInterval((int)$hash['interval']);
- 
-+        $month2number = array(
-+            'january'   => 1,
-+            'february'  => 2,
-+            'march'     => 3,
-+            'april'     => 4,
-+            'may'       => 5,
-+            'june'      => 6,
-+            'july'      => 7,
-+            'august'    => 8,
-+            'september' => 9,
-+            'october'   => 10,
-+            'november'  => 11,
-+            'december'  => 12,
-+        );
-+
-         $parse_day = false;
-         $set_daymask = false;
-         $update_month = false;
-@@ -1255,11 +1352,9 @@
- 
-             case 'weekday':
-                 $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
--                $nth_weekday = (int)$hash['daynumber'];
--                $hash['daynumber'] = 1;
-+                $this->setRecurNthWeekday($hash['daynumber']);
-                 $parse_day = true;
--                $update_daynumber = true;
--                $update_weekday = true;
-+                $set_daymask = true;
-                 break;
-             }
-             break;
-@@ -1297,12 +1392,13 @@
-                 }
- 
-                 $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
--                $nth_weekday = (int)$hash['daynumber'];
--                $hash['daynumber'] = 1;
-+                $this->setRecurNthWeekday($hash['daynumber']);
-                 $parse_day = true;
--                $update_month = true;
--                $update_daynumber = true;
--                $update_weekday = true;
-+                $set_daymask = true;
-+
-+                if ($hash['month'] && isset($month2number[$hash['month']])) {
-+                    $this->setRecurByMonth($month2number[$hash['month']]);
-+                }
-                 break;
-             }
-         }
-@@ -1368,21 +1464,6 @@
- 
-         if ($update_month || $update_daynumber || $update_weekday) {
-             if ($update_month) {
--                $month2number = array(
--                    'january'   => 1,
--                    'february'  => 2,
--                    'march'     => 3,
--                    'april'     => 4,
--                    'may'       => 5,
--                    'june'      => 6,
--                    'july'      => 7,
--                    'august'    => 8,
--                    'september' => 9,
--                    'october'   => 10,
--                    'november'  => 11,
--                    'december'  => 12,
--                );
--
-                 if (isset($month2number[$hash['month']])) {
-                     $this->start->month = $month2number[$hash['month']];
-                 }
-@@ -1398,7 +1479,7 @@
-             }
- 
-             if ($update_weekday) {
--                $this->start->setNthWeekday($last_found_day, $nth_weekday);
-+                $this->setNthWeekday($nth_weekday);
-             }
-         }
- 
diff --git a/lib/plugins/libkolab/bin/Date_last_weekday.diff b/lib/plugins/libkolab/bin/Date_last_weekday.diff
deleted file mode 100644
index d260360..0000000
--- a/lib/plugins/libkolab/bin/Date_last_weekday.diff
+++ /dev/null
@@ -1,37 +0,0 @@
---- Date.php.orig	2012-07-10 19:14:26.000000000 +0200
-+++ Date.php	2012-07-10 19:16:22.000000000 +0200
-@@ -627,16 +627,25 @@
-             return;
-         }
- 
--        $this->_mday = 1;
--        $first = $this->dayOfWeek();
--        if ($weekday < $first) {
--            $this->_mday = 8 + $weekday - $first;
--        } else {
--            $this->_mday = $weekday - $first + 1;
-+        if ($nth < 0) {  // last $weekday of month
-+            $this->_mday = $lastday = Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
-+            $last = $this->dayOfWeek();
-+            $this->_mday += ($weekday - $last);
-+            if ($this->_mday > $lastday)
-+                $this->_mday -= 7;
-+        }
-+        else {
-+            $this->_mday = 1;
-+            $first = $this->dayOfWeek();
-+            if ($weekday < $first) {
-+                $this->_mday = 8 + $weekday - $first;
-+            } else {
-+                $this->_mday = $weekday - $first + 1;
-+            }
-+            $diff = 7 * $nth - 7;
-+            $this->_mday += $diff;
-+            $this->_correct(self::MASK_DAY, $diff < 0);
-         }
--        $diff = 7 * $nth - 7;
--        $this->_mday += $diff;
--        $this->_correct(self::MASK_DAY, $diff < 0);
-     }
- 
-     /**
diff --git a/lib/plugins/libkolab/bin/get_horde_date.sh b/lib/plugins/libkolab/bin/get_horde_date.sh
deleted file mode 100755
index b8e663d..0000000
--- a/lib/plugins/libkolab/bin/get_horde_date.sh
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/bin/sh
-
-# Copy Horde_Date_Recurrence classes and dependencies to the given target directory.
-# This will create a standalone copy of the classes requried for date recurrence computation.
-
-SRCDIR=$1
-DESTDIR=$2
-BINDIR=`dirname $0`
-
-if [ ! -d "$SRCDIR" -o ! -d "$DESTDIR" ]; then
-  echo "Usage: get_horde_date.sh SRCDIR DESTDIR"
-  echo "Please enter valid source and destination directories for the Horde libs"
-  exit 1
-fi
-
-
-# concat Date.php and Date/Utils.php
-HORDE_DATE="$DESTDIR/Horde_Date.php"
-
-echo "<?php
-
-/**
- * This is a concatenated copy of the following files:
- *   Horde/Date.php, Horde/Date/Utils.php
- * Pull the latest version of these files from the PEAR channel of the Horde
- * project at http://pear.horde.org by installing the Horde_Date package.
- */
-" > $HORDE_DATE
-
-patch $SRCDIR/Date.php $BINDIR/Date_last_weekday.diff --output=$HORDE_DATE.patched
-sed 's/<?php//; s/?>//' $HORDE_DATE.patched >> $HORDE_DATE
-sed 's/<?php//; s/?>//' $SRCDIR/Date/Utils.php >> $HORDE_DATE
-
-# copy and patch Date/Recurrence.php
-HORDE_DATE_RECURRENCE="$DESTDIR/Horde_Date_Recurrence.php"
-
-echo "<?php
-
-/**
- * This is a modified copy of Horde/Date/Recurrence.php
- * Pull the latest version of this file from the PEAR channel of the Horde
- * project at http://pear.horde.org by installing the Horde_Date package.
- */
-
-if (!class_exists('Horde_Date'))
-	require_once(dirname(__FILE__) . '/Horde_Date.php');
-
-// minimal required implementation of Horde_Date_Translation to avoid a huge dependency nightmare
-class Horde_Date_Translation
-{
-	function t(\$arg) { return \$arg; }
-	function ngettext(\$sing, \$plur, \$num) { return (\$num > 1 ? \$plur : \$sing); }
-}
-" > $HORDE_DATE_RECURRENCE
-
-patch $SRCDIR/Date/Recurrence.php $BINDIR/Date_Recurrence_weekday.diff --output=$HORDE_DATE_RECURRENCE.patched
-sed 's/<?php//; s/?>//' $HORDE_DATE_RECURRENCE.patched >> $HORDE_DATE_RECURRENCE
-
-# remove dependency to Horde_String
-sed -i '' "s/Horde_String::/strto/" $HORDE_DATE_RECURRENCE
-
-rm $DESTDIR/Horde_Date*.patched
-
-
diff --git a/lib/plugins/libkolab/bin/modcache.sh b/lib/plugins/libkolab/bin/modcache.sh
index 04d36a5..5ac9a21 100755
--- a/lib/plugins/libkolab/bin/modcache.sh
+++ b/lib/plugins/libkolab/bin/modcache.sh
@@ -81,7 +81,7 @@ case 'clear':
     if (!$db->is_connected() || $db->is_error())
         die("No DB connection\n");
 
-    $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','distribution-list','event','task','configuration');
+    $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','distribution-list','event','task','configuration','file');
     $folder_types_db = array_map(array($db, 'quote'), $folder_types);
 
     if ($opts['all']) {
@@ -106,7 +106,7 @@ case 'prewarm':
     $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','event','task','configuration','file');
         foreach ($folder_types as $type) {
             // sync every folder of the given type
             foreach (kolab_storage::get_folders($type) as $folder) {
diff --git a/lib/plugins/libkolab/config.inc.php.dist b/lib/plugins/libkolab/config.inc.php.dist
index 01e1334..aa0c8d0 100644
--- a/lib/plugins/libkolab/config.inc.php.dist
+++ b/lib/plugins/libkolab/config.inc.php.dist
@@ -19,4 +19,8 @@ $rcmail_config['kolab_ssl_verify_peer'] = false;
 // 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;
+
 ?>
diff --git a/lib/plugins/libkolab/lib/kolab_format_contact.php b/lib/plugins/libkolab/lib/kolab_format_contact.php
index 0c571f2..72867fc 100644
--- a/lib/plugins/libkolab/lib/kolab_format_contact.php
+++ b/lib/plugins/libkolab/lib/kolab_format_contact.php
@@ -386,7 +386,7 @@ class kolab_format_contact extends kolab_format
     public function get_words()
     {
         $data = '';
-        foreach (self::$fulltext_cols as $col) {
+        foreach (self::$fulltext_cols as $colname) {
             list($col, $field) = explode(':', $colname);
 
             if ($field) {
diff --git a/lib/plugins/libkolab/lib/kolab_format_event.php b/lib/plugins/libkolab/lib/kolab_format_event.php
index e050774..f3d0470 100644
--- a/lib/plugins/libkolab/lib/kolab_format_event.php
+++ b/lib/plugins/libkolab/lib/kolab_format_event.php
@@ -89,25 +89,6 @@ class kolab_format_event extends kolab_format_xcal
             $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;
@@ -170,27 +151,6 @@ class kolab_format_event extends kolab_format_xcal
         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++) {
diff --git a/lib/plugins/libkolab/lib/kolab_format_xcal.php b/lib/plugins/libkolab/lib/kolab_format_xcal.php
index ff10a10..085e577 100644
--- a/lib/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/lib/plugins/libkolab/lib/kolab_format_xcal.php
@@ -218,6 +218,27 @@ abstract class kolab_format_xcal extends kolab_format
             }
         }
 
+        // 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 @@ abstract class kolab_format_xcal extends kolab_format
         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']);
@@ -378,6 +400,25 @@ abstract class kolab_format_xcal extends kolab_format
             $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);
     }
 
     /**
diff --git a/lib/plugins/libkolab/lib/kolab_storage.php b/lib/plugins/libkolab/lib/kolab_storage.php
index 2f3cdc5..ee6ede0 100644
--- a/lib/plugins/libkolab/lib/kolab_storage.php
+++ b/lib/plugins/libkolab/lib/kolab_storage.php
@@ -41,6 +41,23 @@ class kolab_storage
     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
@@ -349,25 +366,8 @@ class kolab_storage
             $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,9 +388,11 @@ class kolab_storage
         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])) {
-            return $name;
+        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;
+            }
         }
 
         $found     = false;
@@ -645,13 +647,43 @@ class kolab_storage
 
 
     /**
+     * Sort the given list of kolab folders by namespace/name
+     *
+     * @param array List of kolab_storage_folder objects
+     * @return array Sorted list of folders
+     */
+    public static function sort_folders($folders)
+    {
+        $nsnames = array('personal' => array(), 'shared' => array(), 'other' => array());
+        foreach ($folders as $folder) {
+            $folders[$folder->name] = $folder;
+            $ns = $folder->get_namespace();
+            $nsnames[$ns][$folder->name] = strtolower(html_entity_decode(self::object_name($folder->name, $ns), ENT_COMPAT, RCUBE_CHARSET));  // decode »
+        }
+
+        $names = array();
+        foreach ($nsnames as $ns => $dummy) {
+            asort($nsnames[$ns], SORT_LOCALE_STRING);
+            $names += $nsnames[$ns];
+        }
+
+        $out = array();
+        foreach ($names as $utf7name => $name) {
+            $out[] = $folders[$utf7name];
+        }
+
+        return $out;
+    }
+
+
+    /**
      * Returns folder types indexed by folder name
      *
      * @param string $prefix Folder prefix (Default '*' for all folders)
      *
      * @return array|bool List of folders, False on failure
      */
-    static function folders_typedata($prefix = '*')
+    public static function folders_typedata($prefix = '*')
     {
         if (!self::setup()) {
             return false;
@@ -670,7 +702,7 @@ class kolab_storage
     /**
      * Callback for array_map to select the correct annotation value
      */
-    static function folder_select_metadata($types)
+    public static function folder_select_metadata($types)
     {
         if (!empty($types[self::CTYPE_KEY_PRIVATE])) {
             return $types[self::CTYPE_KEY_PRIVATE];
@@ -690,7 +722,7 @@ class kolab_storage
      *
      * @return string Folder type
      */
-    static function folder_type($folder)
+    public static function folder_type($folder)
     {
         self::setup();
 
@@ -716,7 +748,7 @@ class kolab_storage
      *
      * @return boolean True on success
      */
-    static function set_folder_type($folder, $type='mail')
+    public static function set_folder_type($folder, $type='mail')
     {
         self::setup();
 
@@ -881,4 +913,107 @@ class kolab_storage
         $rcube   = rcube::get_instance();
         return $rcube->user->save_prefs(array('kolab_active_folders' => $folders));
     }
+
+    /**
+     * Creates default folder of specified type
+     * To be run when none of subscribed folders (of specified type) is found
+     *
+     * @param string $type  Folder type
+     * @param string $props Folder properties (color, etc)
+     *
+     * @return string Folder name
+     */
+    public static function create_default_folder($type, $props = array())
+    {
+        if (!self::setup()) {
+            return;
+        }
+
+        $folders = self::$imap->get_metadata('*', array(kolab_storage::CTYPE_KEY_PRIVATE));
+
+        // from kolab_folders config
+        $folder_type  = strpos($type, '.') ? str_replace('.', '_', $type) : $type . '_default';
+        $default_name = self::$config->get('kolab_folders_' . $folder_type);
+        $folder_type  = str_replace('_', '.', $folder_type);
+
+        // check if we have any folder in personal namespace
+        // folder(s) may exist but not subscribed
+        foreach ($folders as $f => $data) {
+            if (strpos($data[self::CTYPE_KEY_PRIVATE], $type) === 0) {
+                $folder = $f;
+                break;
+            }
+        }
+
+        if (!$folder) {
+            if (!$default_name) {
+                $default_name = self::$default_folders[$type];
+            }
+
+            if (!$default_name) {
+                return;
+            }
+
+            $folder = rcube_charset::convert($default_name, RCUBE_CHARSET, 'UTF7-IMAP');
+            $prefix = self::$imap->get_namespace('prefix');
+
+            // add personal namespace prefix if needed
+            if ($prefix && strpos($folder, $prefix) !== 0 && $folder != 'INBOX') {
+                $folder = $prefix . $folder;
+            }
+
+            if (!self::$imap->folder_exists($folder)) {
+                if (!self::$imap->folder_create($folder)) {
+                    return;
+                }
+            }
+
+            self::set_folder_type($folder, $folder_type);
+        }
+
+        self::folder_subscribe($folder);
+
+        if ($props['active']) {
+            self::set_state($folder, true);
+        }
+
+        if (!empty($props)) {
+            self::set_folder_props($folder, $props);
+        }
+
+        return $folder;
+    }
+
+    /**
+     * Sets folder metadata properties
+     *
+     * @param string $folder Folder name
+     * @param array  $prop   Folder properties
+     */
+    public static function set_folder_props($folder, &$prop)
+    {
+        if (!self::setup()) {
+            return;
+        }
+
+        // TODO: also save 'showalarams' and other properties here
+        $ns        = self::$imap->folder_namespace($folder);
+        $supported = array(
+            'color'       => array(self::COLOR_KEY_SHARED, self::COLOR_KEY_PRIVATE),
+            'displayname' => array(self::NAME_KEY_SHARED, self::NAME_KEY_PRIVATE),
+        );
+
+        foreach ($supported as $key => $metakeys) {
+            if (array_key_exists($key, $prop)) {
+                $meta_saved = false;
+                if ($ns == 'personal')  // save in shared namespace for personal folders
+                    $meta_saved = self::$imap->set_metadata($folder, array($metakeys[0] => $prop[$key]));
+                if (!$meta_saved)    // try in private namespace
+                    $meta_saved = self::$imap->set_metadata($folder, array($metakeys[1] => $prop[$key]));
+                if ($meta_saved)
+                    unset($prop[$key]);  // unsetting will prevent fallback to local user prefs
+            }
+        }
+    }
+
 }
diff --git a/lib/plugins/libkolab/lib/kolab_storage_cache.php b/lib/plugins/libkolab/lib/kolab_storage_cache.php
index a3b6626..ba6c106 100644
--- a/lib/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/lib/plugins/libkolab/lib/kolab_storage_cache.php
@@ -712,8 +712,8 @@ class kolab_storage_cache
         // create lock record if not exists
         if (!$sql_arr) {
             $this->db->query(
-                "INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml)".
-                " VALUES (?, ?, 1, " . $this->db->now() . ", '', '', '')",
+                "INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml, tags, words)".
+                " VALUES (?, ?, 1, " . $this->db->now() . ", '', '', '', '', '')",
                 $this->resource_uri,
                 'lock'
             );
diff --git a/lib/plugins/libkolab/lib/kolab_storage_folder.php b/lib/plugins/libkolab/lib/kolab_storage_folder.php
index f046bbf..303ed99 100644
--- a/lib/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/lib/plugins/libkolab/lib/kolab_storage_folder.php
@@ -650,6 +650,10 @@ class kolab_storage_folder
         if (is_array($object['_attachments'])) {
             $numatt = count($object['_attachments']);
             foreach ($object['_attachments'] as $key => $attachment) {
+                // FIXME: kolab_storage and Roundcube attachment hooks use different fields!
+                if (empty($attachment['content']) && !empty($attachment['data']))
+                    $attachment['content'] = $attachment['data'];
+
                 // make sure size is set, so object saved in cache contains this info
                 if (!isset($attachment['size'])) {
                     if (!empty($attachment['content'])) {




More information about the commits mailing list