6 commits - config/main.inc.php.dist lib/ext lib/init.php lib/kolab lib/kolab_sync.php lib/kolab_sync_plugin_api.php

Aleksander Machniak machniak at kolabsys.com
Wed Oct 17 14:12:56 CEST 2012


 config/main.inc.php.dist                      |   44 +++++++
 lib/ext/Roundcube/html.php                    |   27 +---
 lib/ext/Roundcube/rcube_addressbook.php       |    5 
 lib/ext/Roundcube/rcube_charset.php           |    2 
 lib/ext/Roundcube/rcube_config.php            |   13 +-
 lib/ext/Roundcube/rcube_db.php                |    2 
 lib/ext/Roundcube/rcube_db_mssql.php          |    2 
 lib/ext/Roundcube/rcube_db_mysql.php          |    2 
 lib/ext/Roundcube/rcube_db_pgsql.php          |    2 
 lib/ext/Roundcube/rcube_db_sqlite.php         |    2 
 lib/ext/Roundcube/rcube_db_sqlsrv.php         |    2 
 lib/ext/Roundcube/rcube_imap.php              |   45 ++++++-
 lib/ext/Roundcube/rcube_imap_generic.php      |   49 ++++++--
 lib/ext/Roundcube/rcube_ldap.php              |   98 ++++++++++------
 lib/ext/Roundcube/rcube_message.php           |    5 
 lib/ext/Roundcube/rcube_output.php            |    2 
 lib/ext/Roundcube/rcube_output_html.php       |    2 
 lib/ext/Roundcube/rcube_plugin.php            |    2 
 lib/ext/Roundcube/rcube_plugin_api.php        |   24 ++--
 lib/ext/Roundcube/rcube_result_thread.php     |   10 +
 lib/ext/Roundcube/rcube_storage.php           |    4 
 lib/ext/Roundcube/rcube_user.php              |   62 ++++++----
 lib/ext/Roundcube/rcube_utils.php             |    5 
 lib/ext/Roundcube/rcube_vcard.php             |   24 +++-
 lib/ext/Syncroton/Model/DeviceInformation.php |   40 ++++++
 lib/init.php                                  |    1 
 lib/kolab/kolab_storage.php                   |   30 ++++-
 lib/kolab_sync.php                            |  155 +++++++++++---------------
 lib/kolab_sync_plugin_api.php                 |  108 ++++++++++++++++++
 29 files changed, 547 insertions(+), 222 deletions(-)

New commits:
commit 5cbac26767206b31fbec96f18b22864440b7a56a
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 17 14:09:21 2012 +0200

    Implemented plugin API. Removed LDAP auth code - now we're using kolab_auth plugin.
    Plugins should be put into RCMAIL_PLUGINS_DIR (iniset.php)
    and activesync_plugins option should be used.
    Note: Now we're supporting only kolab_auth plugin.

diff --git a/lib/init.php b/lib/init.php
index e41bdca..8a96b68 100644
--- a/lib/init.php
+++ b/lib/init.php
@@ -29,6 +29,7 @@ define('RCMAIL_VERSION', '0.9-git');
 define('RCMAIL_CHARSET', 'UTF-8');
 define('INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/');
 define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config');
+define('RCMAIL_PLUGINS_DIR', INSTALL_PATH . 'plugins');
 
 // PHP configuration
 $config = array(
diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php
index f34df68..ab0e3d5 100644
--- a/lib/kolab_sync.php
+++ b/lib/kolab_sync.php
@@ -42,10 +42,8 @@ class kolab_sync extends rcube
      */
     public $user;
 
-    private $data = array();
-
     const CHARSET = 'UTF-8';
-    const VERSION = "1.1";
+    const VERSION = "2.0";
 
 
     /**
@@ -68,6 +66,17 @@ class kolab_sync extends rcube
         // Initialize Syncroton Logger
         $debug_mode   = $this->config->get('activesync_debug') ? kolab_sync_logger::DEBUG : kolab_sync_logger::WARN;
         $this->logger = new kolab_sync_logger($debug_mode);
+
+        // Get list of plugins
+        // WARNING: We can use only plugins that are prepared for this
+        //          e.g. are not using output or rcmail objects or
+        //          doesn't throw errors when using them
+        $plugins = (array)$this->config->get('activesync_plugins');
+
+        // Initialize/load plugins
+        $this->plugins = kolab_sync_plugin_api::get_instance();
+        $this->plugins->init($this, $this->task);
+        $this->plugins->load_plugins($plugins);
     }
 
 
@@ -76,6 +85,8 @@ class kolab_sync extends rcube
      */
     public function run()
     {
+        $this->plugins->exec_hook('startup', array('task' => 'login'));
+
         // when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule
         if (!isset($_SERVER['PHP_AUTH_USER'])) {
             // "Basic didhfiefdhfu4fjfjdsa34drsdfterrde..."
@@ -136,105 +147,75 @@ class kolab_sync extends rcube
 
 
     /**
-     * Authenticates a user in LDAP and Roundcube
+     * Authenticates a user
+     *
+     * @param string $username User name
+     * @param string $password User password
+     *
+     * @param int User ID
      */
     public function authenticate($username, $password)
     {
-        // Get IMAP host
-        $host  = $this->config->get('default_host');
-        $host  = rcube_utils::parse_host($host);
-
-        // Get user
-        $user = $this->get_ldap_user($username, $host);
-
-        // Get Roundcube user ID
-        $userid = $this->get_rcube_user($user, $password, $host);
-
-        return $userid;
-    }
-
-
-    /**
-     * Returns user login attribute from LDAP server
-     */
-    private function get_ldap_user($username, $host)
-    {
-        @include_once(RCMAIL_CONFIG_DIR . "/kolab_auth.inc.php");
-
-        $login_attr  = $this->config->get('kolab_auth_login');
-        $addressbook = $this->config->get('kolab_auth_addressbook');
-        $filter      = $this->config->get('kolab_auth_filter');
-        $filter      = $this->parse_vars($filter, $username, $host);
-
-        if (!is_array($addressbook)) {
-            $ldap_config = (array)$this->config->get('ldap_public');
-            $addressbook = $ldap_config[$addressbook];
-        }
-
-        if (empty($addressbook)) {
-            $this->logger->warn("No address book configured to authenticate the user.");
-            return null;
+        $auth = $this->plugins->exec_hook('authenticate', array(
+            'host'  => $this->select_host($username),
+            'user'  => $username,
+            'pass'  => $password,
+            'valid' => true,
+        ));
+
+        // Authenticate - get Roundcube user ID
+        if ($auth['valid'] && !$auth['abort']
+            && ($userid = $this->login($auth['user'], $auth['pass'], $auth['host']))) {
+            return $userid;
         }
 
-        if (empty($login_attr)) {
-            $this->logger->warn("No login attribute configured.");
-            return null;
-        }
-
-        $addressbook['filter']    = $filter;
-        $addressbook['sizelimit'] = 2;
-
-        $this->ldap = new rcube_ldap(
-            $addressbook,
-            $this->config->get('ldap_debug'),
-            $this->config->mail_domain($host)
-        );
-
-        if (!$this->ldap->ready) {
-            return null;
-        }
-
-        // get record
-        $results = $this->ldap->list_records();
-
-        if (count($results->records) != 1) {
-            return null;
-        }
-
-        if ($record = $results->records[0]) {
-            return is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr];
-        }
+        $this->plugins->exec_hook('login_failed', array(
+            'host' => $auth['host'],
+            'user' => $auth['user'],
+        ));
     }
 
 
     /**
-     * Prepares filter query for LDAP search
+     * Storage host selection
      */
-    private function parse_vars($str, $user, $host)
+    private function select_host($username)
     {
-        $domain = $this->config->get('username_domain');
+        // Get IMAP host
+        $host = $this->config->get('default_host');
+
+        if (is_array($host)) {
+            list($user, $domain) = explode('@', $username);
+
+            // try to select host by mail domain
+            if (!empty($domain)) {
+                foreach ($host as $storage_host => $mail_domains) {
+                    if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
+                        $host = $storage_host;
+                        break;
+                    }
+                    else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
+                        $host = is_numeric($storage_host) ? $mail_domains : $storage_host;
+                        break;
+                    }
+                }
+            }
 
-        if (!empty($domain) && strpos($user, '@') === false) {
-            if (is_array($domain) && isset($domain[$host]))
-                $user .= '@'.rcube_utils::parse_host($domain[$host], $host);
-            else if (is_string($domain))
-                $user .= '@'.rcube_utils::parse_host($domain, $host);
+            // take the first entry if $host is not found
+            if (is_array($host)) {
+                list($key, $val) = each($default_host);
+                $host = is_numeric($key) ? $val : $key;
+            }
         }
 
-        // replace variables in filter
-        list($u, $d) = explode('@', $user);
-        $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string
-        $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u);
-
-        return strtr($str, $replaces);
+        return rcube_utils::parse_host($host);
     }
 
 
     /**
-     * Returns Roundcube user ID for specified username and host.
-     * Also sets IMAP connection credentials.
+     * Authenticates a user in IMAP and returns Roundcube user ID.
      */
-    private function get_rcube_user($username, $password, $host)
+    private function login($username, $password, $host)
     {
         if (empty($username)) {
             return null;
@@ -314,10 +295,6 @@ class kolab_sync extends rcube
     {
         parent::shutdown();
 
-        if ($this->ldap) {
-            $this->ldap->close();
-        }
-
         // write performance stats to logs/console
         if ($this->config->get('devel_mode')) {
             if (function_exists('memory_get_usage'))
@@ -325,7 +302,9 @@ class kolab_sync extends rcube
             if (function_exists('memory_get_peak_usage'))
                 $mem .= '/' . sprintf('%.1f', memory_get_peak_usage() / 1048576);
 
-            $log = $_SERVER['QUERY_STRING'] . ($mem ? " [$mem]" : '');
+            $query = $_SERVER['QUERY_STRING'];
+            $log   = $query . ($mem ? ($query ? ' ' : '') . "[$mem]" : '');
+
             if (defined('RCMAIL_START'))
                 self::print_timer(RCMAIL_START, $log);
             else
diff --git a/lib/kolab_sync_plugin_api.php b/lib/kolab_sync_plugin_api.php
new file mode 100644
index 0000000..1b7bac4
--- /dev/null
+++ b/lib/kolab_sync_plugin_api.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ +--------------------------------------------------------------------------+
+ | Kolab Sync (ActiveSync for Kolab)                                        |
+ |                                                                          |
+ | Copyright (C) 2011-2012, Kolab Systems AG <contact at kolabsys.com>         |
+ |                                                                          |
+ | This program is free software: you can redistribute it and/or modify     |
+ | it under the terms of the GNU Affero General Public License as published |
+ | by the Free Software Foundation, either version 3 of the License, or     |
+ | (at your option) any later version.                                      |
+ |                                                                          |
+ | This program is distributed in the hope that it will be useful,          |
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of           |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the             |
+ | GNU Affero General Public License for more details.                      |
+ |                                                                          |
+ | You should have received a copy of the GNU Affero General Public License |
+ | along with this program. If not, see <http://www.gnu.org/licenses/>      |
+ +--------------------------------------------------------------------------+
+ | Author: Aleksander Machniak <machniak at kolabsys.com>                      |
+ +--------------------------------------------------------------------------+
+*/
+
+/**
+ * The plugin loader and global API
+ *
+ * @package PluginAPI
+ */
+class kolab_sync_plugin_api extends rcube_plugin_api
+{
+    /**
+     * This implements the 'singleton' design pattern
+     *
+     * @return rcube_plugin_api The one and only instance if this class
+     */
+    static function get_instance()
+    {
+        if (!self::$instance) {
+            self::$instance = new kolab_sync_plugin_api();
+        }
+
+        return self::$instance;
+    }
+
+
+    /**
+     * Initialize plugin engine
+     *
+     * This has to be done after rcmail::load_gui() or rcmail::json_init()
+     * was called because plugins need to have access to rcmail->output
+     *
+     * @param object rcube Instance of the rcube base class
+     * @param string Current application task (used for conditional plugin loading)
+     */
+    public function init($app, $task = '')
+    {
+        $this->task = $task;
+    }
+
+
+    /**
+     * Register a handler function for template objects
+     *
+     * @param string $name Object name
+     * @param string $owner Plugin name that registers this action
+     * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
+     */
+    public function register_handler($name, $owner, $callback)
+    {
+        // empty
+    }
+
+
+    /**
+     * Register this plugin to be responsible for a specific task
+     *
+     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+     * @param string $owner Plugin name that registers this action
+     */
+    public function register_task($task, $owner)
+    {
+        $this->tasks[$task] = $owner;
+    }
+
+
+    /**
+     * Include a plugin script file in the current HTML page
+     *
+     * @param string $fn Path to script
+     */
+    public function include_script($fn)
+    {
+        //empty
+    }
+
+
+    /**
+     * Include a plugin stylesheet in the current HTML page
+     *
+     * @param string $fn Path to stylesheet
+     */
+    public function include_stylesheet($fn)
+    {
+        //empty
+    }
+}


commit 78fd935a194a584d84393febb0f62fe05e231f16
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 17 13:42:10 2012 +0200

    Sample config file with all activesync options

diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
new file mode 100644
index 0000000..77f04ac
--- /dev/null
+++ b/config/main.inc.php.dist
@@ -0,0 +1,44 @@
+<?php
+
+// This file lists all ActiveSync-related configuration options
+
+// Enables ActiveSync protocol debuging
+$rcmail_config['activesync_debug'] = true;
+
+// If specified all ActiveSync-related logs will be saved to this file
+// Note: This doesn't change Roundcube Framework log locations
+$rcmail_config['activesync_log_file'] = null;
+
+// Type of ActiveSync cache. Supported values: 'db', 'apc' and 'memcache'.
+// Note: This is only for some additional data like timezones mapping.
+//       Main ActiveSync cache uses Roundcube SQL database
+$rcmail_config['activesync_cache'] = null;
+
+// lifetime of ActiveSync cache
+// possible units: s, m, h, d, w
+$rcmail_config['activesync_cache_lifetime'] = '10d';
+
+// List of global addressbooks (GAL)
+// Note: If empty 'autocomplete_addressbooks' setting will be used
+$rcmail_config['activesync_addressbooks'] = array();
+
+// ActiveSync => Roundcube contact fields map for GAL search
+/* Default: array(
+       'alias'         => 'nickname',
+       'company'       => 'organization',
+       'displayName'   => 'name',
+       'emailAddress'  => 'email',
+       'firstName'     => 'firstname',
+       'lastName'      => 'surname',
+       'mobilePhone'   => 'phone.mobile',
+       'office'        => 'office',
+       'picture'       => 'photo',
+       'phone'         => 'phone',
+       'title'         => 'jobtitle',
+);
+*/
+$rcmail_config['activesync_gal_fieldmap'] = null;
+
+// List of Roundcube plugins
+// WARNING: Not all plugins used in Roundcube can be listed here
+$rcmail_config['activesync_plugins'] = array();


commit b0d6e406235fa74da515b348d0e98a3acab5dd90
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 17 12:51:00 2012 +0200

    Fixed folder type handling in kolab_storage

diff --git a/lib/kolab/kolab_storage.php b/lib/kolab/kolab_storage.php
index 1292b8b..b29b416 100644
--- a/lib/kolab/kolab_storage.php
+++ b/lib/kolab/kolab_storage.php
@@ -191,7 +191,7 @@ class kolab_storage
         if ($saved = self::$imap->create_folder($name, $subscribed)) {
             // set metadata for folder type
             if ($type) {
-                $saved = self::$imap->set_metadata($name, array(self::CTYPE_KEY => $type));
+                $saved = self::set_folder_type($name, $type);
 
                 // revert if metadata could not be set
                 if (!$saved) {
@@ -595,7 +595,14 @@ class kolab_storage
      */
     static function folder_select_metadata($types)
     {
-        return $types[self::CTYPE_KEY_PRIVATE] ?: $types[self::CTYPE_KEY];
+        if (!empty($types[self::CTYPE_KEY_PRIVATE])) {
+            return $types[self::CTYPE_KEY_PRIVATE];
+        }
+        else if (!empty($types[self::CTYPE_KEY])) {
+            list($ctype, $suffix) = explode('.', $types[self::CTYPE_KEY]);
+            return $ctype;
+        }
+        return null;
     }
 
 
@@ -623,4 +630,23 @@ class kolab_storage
         return 'mail';
     }
 
+    /**
+     * Sets folder content-type.
+     *
+     * @param string $folder Folder name
+     * @param string $type   Content type
+     *
+     * @return boolean True on success
+     */
+    static function set_folder_type($folder, $type='mail')
+    {
+        list($ctype, $subtype) = explode('.', $type);
+
+        $success = self::$imap->set_metadata($folder, array(self::CTYPE_KEY => $ctype, self::CTYPE_KEY_PRIVATE => $subtype ? $type : null));
+
+        if (!$success)  // fallback: only set private annotation
+            $success |= self::$imap->set_metadata($folder, array(self::CTYPE_KEY_PRIVATE => $type));
+
+        return $success;
+    }
 }


commit 89ff90998fe47b52c28183fedead4765e4cb9013
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 17 12:50:03 2012 +0200

    Small fixes in Roundcube Framework

diff --git a/lib/ext/Roundcube/rcube_message.php b/lib/ext/Roundcube/rcube_message.php
index 7bf95d1..4e1b5a0 100644
--- a/lib/ext/Roundcube/rcube_message.php
+++ b/lib/ext/Roundcube/rcube_message.php
@@ -371,9 +371,10 @@ class rcube_message
             foreach ($structure->parts as $p => $sub_part) {
                 $sub_mimetype = $sub_part->mimetype;
 
-                // skip empty parts
-                if (!$sub_part->size)
+                // skip empty text parts
+                if (!$sub_part->size && preg_match('#^text/(plain|html|enriched)$#', $sub_mimetype)) {
                     continue;
+                }
 
                 // check if sub part is
                 if ($sub_mimetype == 'text/plain')
diff --git a/lib/ext/Roundcube/rcube_output.php b/lib/ext/Roundcube/rcube_output.php
index 9aa7c9e..5c582e6 100644
--- a/lib/ext/Roundcube/rcube_output.php
+++ b/lib/ext/Roundcube/rcube_output.php
@@ -44,7 +44,7 @@ abstract class rcube_output
      */
     public function __construct($task = null, $framed = false)
     {
-        $this->app     = rcmail::get_instance();
+        $this->app     = rcube::get_instance();
         $this->config  = $this->app->config;
         $this->browser = new rcube_browser();
     }
diff --git a/lib/ext/Roundcube/rcube_plugin.php b/lib/ext/Roundcube/rcube_plugin.php
index c103573..4508885 100644
--- a/lib/ext/Roundcube/rcube_plugin.php
+++ b/lib/ext/Roundcube/rcube_plugin.php
@@ -202,7 +202,7 @@ abstract class rcube_plugin
       foreach ($texts as $key => $value)
         $add[$domain.'.'.$key] = $value;
 
-      $rcmail = rcmail::get_instance();
+      $rcmail = rcube::get_instance();
       $rcmail->load_language($lang, $add);
 
       // add labels to client
diff --git a/lib/ext/Roundcube/rcube_plugin_api.php b/lib/ext/Roundcube/rcube_plugin_api.php
index 9ef68ca..107c810 100644
--- a/lib/ext/Roundcube/rcube_plugin_api.php
+++ b/lib/ext/Roundcube/rcube_plugin_api.php
@@ -31,7 +31,7 @@ if (!defined('RCMAIL_PLUGINS_DIR'))
  */
 class rcube_plugin_api
 {
-  static private $instance;
+  static protected $instance;
 
   public $dir;
   public $url = 'plugins/';
@@ -39,16 +39,16 @@ class rcube_plugin_api
   public $output;
 
   public $handlers = array();
-  private $plugins = array();
-  private $tasks = array();
-  private $actions = array();
-  private $actionmap = array();
-  private $objectsmap = array();
-  private $template_contents = array();
-  private $active_hook = false;
+  protected $plugins = array();
+  protected $tasks = array();
+  protected $actions = array();
+  protected $actionmap = array();
+  protected $objectsmap = array();
+  protected $template_contents = array();
+  protected $active_hook = false;
 
   // Deprecated names of hooks, will be removed after 0.5-stable release
-  private $deprecated_hooks = array(
+  protected $deprecated_hooks = array(
     'create_user'       => 'user_create',
     'kill_session'      => 'session_destroy',
     'upload_attachment' => 'attachment_upload',
@@ -98,7 +98,7 @@ class rcube_plugin_api
   /**
    * Private constructor
    */
-  private function __construct()
+  protected function __construct()
   {
     $this->dir = slashify(RCMAIL_PLUGINS_DIR);
   }
@@ -470,7 +470,7 @@ class rcube_plugin_api
    * @param array $attrib
    * @return array
    */
-  private function template_container_hook($attrib)
+  protected function template_container_hook($attrib)
   {
     $container = $attrib['name'];
     return array('content' => $attrib['content'] . $this->template_contents[$container]);
@@ -483,7 +483,7 @@ class rcube_plugin_api
    * @param string $fn Filename
    * @return string 
    */
-  private function resource_url($fn)
+  protected function resource_url($fn)
   {
     if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
       return $this->url . $fn;


commit bb112bd0231b2c69016461fc0029ee3a0c12cd63
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 17 09:29:42 2012 +0200

    Add lost Syncroton file

diff --git a/lib/ext/Syncroton/Model/DeviceInformation.php b/lib/ext/Syncroton/Model/DeviceInformation.php
new file mode 100644
index 0000000..adab7b0
--- /dev/null
+++ b/lib/ext/Syncroton/Model/DeviceInformation.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Model
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * class to handle ActiveSync device information
+ *
+ * @package     Model
+ * @property    string  friendlyName
+ * @property    string  iMEI
+ * @property    string  mobileOperator
+ * @property    string  model
+ * @property    string  oS
+ * @property    string  oSLanguage
+ * @property    string  phoneNumber
+ */
+
+class Syncroton_Model_DeviceInformation extends Syncroton_Model_AEntry
+{
+    protected $_xmlBaseElement = 'Set';
+    
+    protected $_properties = array(
+        'Settings' => array(
+            'enableOutboundSMS' => array('type' => 'number'),
+            'friendlyName'      => array('type' => 'string'),
+            'iMEI'              => array('type' => 'string'),
+            'mobileOperator'    => array('type' => 'string'),
+            'model'             => array('type' => 'string'),
+            'oS'                => array('type' => 'string'),
+            'oSLanguage'        => array('type' => 'string'),
+            'phoneNumber'       => array('type' => 'string')
+        ),
+    );
+}
\ No newline at end of file


commit c5a61c1891b5c1005c80422951269fcb24d5e9ba
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Oct 17 09:29:11 2012 +0200

    Update Roundcube Framework

diff --git a/lib/ext/Roundcube/html.php b/lib/ext/Roundcube/html.php
index c6507f8..2349852 100644
--- a/lib/ext/Roundcube/html.php
+++ b/lib/ext/Roundcube/html.php
@@ -295,7 +295,7 @@ class html
                 }
             }
             else {
-                $attrib_arr[] = $key . '="' . self::quote($value, true) . '"';
+                $attrib_arr[] = $key . '="' . self::quote($value) . '"';
             }
         }
 
@@ -328,22 +328,13 @@ class html
     /**
      * Replacing specials characters in html attribute value
      *
-     * @param  string  $str       Input string
-     * @param  bool    $validate  Enables double quotation prevention
+     * @param string $str Input string
      *
-     * @return string  The quoted string
+     * @return string The quoted string
      */
-    public static function quote($str, $validate = false)
+    public static function quote($str)
     {
-        $str = htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET);
-
-        // avoid douple quotation of &
-        // @TODO: get rid of it
-        if ($validate) {
-            $str = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $str);
-        }
-
-        return $str;
+        return htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET);
     }
 }
 
@@ -559,7 +550,7 @@ class html_textarea extends html
         }
 
         if (!empty($value) && empty($this->attrib['is_escaped'])) {
-            $value = self::quote($value, true);
+            $value = self::quote($value);
         }
 
         return self::tag($this->tagname, $this->attrib, $value,
@@ -635,7 +626,7 @@ class html_select extends html
 
             $option_content = $option['text'];
             if (empty($this->attrib['is_escaped'])) {
-                $option_content = self::quote($option_content, true);
+                $option_content = self::quote($option_content);
             }
 
             $this->content .= self::tag('option', $attr, $option_content);
@@ -690,9 +681,9 @@ class html_table extends html
         $cell->content = $cont;
 
         $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
-        $this->colindex++;
+        $this->colindex += max(1, intval($attr['colspan']));
 
-        if ($this->attrib['cols'] && $this->colindex == $this->attrib['cols']) {
+        if ($this->attrib['cols'] && $this->colindex >= $this->attrib['cols']) {
             $this->add_row();
         }
     }
diff --git a/lib/ext/Roundcube/rcube_addressbook.php b/lib/ext/Roundcube/rcube_addressbook.php
index f4f2553..892ae26 100644
--- a/lib/ext/Roundcube/rcube_addressbook.php
+++ b/lib/ext/Roundcube/rcube_addressbook.php
@@ -434,6 +434,11 @@ abstract class rcube_addressbook
             }
         }
 
+        // remove duplicates
+        if ($flat && !empty($out)) {
+            $out = array_unique($out);
+        }
+
         return $out;
     }
 
diff --git a/lib/ext/Roundcube/rcube_charset.php b/lib/ext/Roundcube/rcube_charset.php
index 35c6972..ff4c2bb 100644
--- a/lib/ext/Roundcube/rcube_charset.php
+++ b/lib/ext/Roundcube/rcube_charset.php
@@ -179,7 +179,7 @@ class rcube_charset
         static $mbstring_sch    = null;
         static $conv            = null;
 
-        $to   = empty($to) ? strtoupper(RCMAIL_CHARSET) : $to;
+        $to   = empty($to) ? RCMAIL_CHARSET : $to;
         $from = self::parse_charset($from);
 
         // It is a common case when UTF-16 charset is used with US-ASCII content (#1488654)
diff --git a/lib/ext/Roundcube/rcube_config.php b/lib/ext/Roundcube/rcube_config.php
index 41acc80..b9fd955 100644
--- a/lib/ext/Roundcube/rcube_config.php
+++ b/lib/ext/Roundcube/rcube_config.php
@@ -123,8 +123,11 @@ class rcube_config
         if ($this->prop['timezone'] == 'auto') {
           $this->prop['_timezone_value'] = $this->client_timezone();
         }
-        else if (is_numeric($this->prop['timezone'])) {
-          $this->prop['timezone'] = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0);
+        else if (is_numeric($this->prop['timezone']) && ($tz = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0))) {
+          $this->prop['timezone'] = $tz;
+        }
+        else if (empty($this->prop['timezone'])) {
+          $this->prop['timezone'] = 'UTC';
         }
 
         // remove deprecated properties
@@ -251,8 +254,8 @@ class rcube_config
         }
 
         // convert user's timezone into the new format
-        if (is_numeric($prefs['timezone'])) {
-            $prefs['timezone'] = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0);
+        if (is_numeric($prefs['timezone']) && ($tz = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0))) {
+            $prefs['timezone'] = $tz;
         }
 
         // larry is the new default skin :-)
@@ -409,7 +412,7 @@ class rcube_config
      */
     private function client_timezone()
     {
-        return isset($_SESSION['timezone']) ? timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0) : date_default_timezone_get();
+        return isset($_SESSION['timezone']) && ($ctz = timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0)) ? $ctz : date_default_timezone_get();
     }
 
 }
diff --git a/lib/ext/Roundcube/rcube_db.php b/lib/ext/Roundcube/rcube_db.php
index eb1ad31..b066101 100644
--- a/lib/ext/Roundcube/rcube_db.php
+++ b/lib/ext/Roundcube/rcube_db.php
@@ -30,6 +30,8 @@
  */
 class rcube_db
 {
+    public $db_provider;
+
     protected $db_dsnw;               // DSN for write operations
     protected $db_dsnr;               // DSN for read operations
     protected $db_connected = false;  // Already connected ?
diff --git a/lib/ext/Roundcube/rcube_db_mssql.php b/lib/ext/Roundcube/rcube_db_mssql.php
index 5cbcfab..119647d 100644
--- a/lib/ext/Roundcube/rcube_db_mssql.php
+++ b/lib/ext/Roundcube/rcube_db_mssql.php
@@ -31,6 +31,8 @@
  */
 class rcube_db_mssql extends rcube_db
 {
+    public $db_provider = 'mssql';
+
     /**
      * Driver initialization
      */
diff --git a/lib/ext/Roundcube/rcube_db_mysql.php b/lib/ext/Roundcube/rcube_db_mysql.php
index 3606ec1..2cdcf30 100644
--- a/lib/ext/Roundcube/rcube_db_mysql.php
+++ b/lib/ext/Roundcube/rcube_db_mysql.php
@@ -31,6 +31,8 @@
  */
 class rcube_db_mysql extends rcube_db
 {
+    public $db_provider = 'mysql';
+
     /**
      * Driver initialization/configuration
      */
diff --git a/lib/ext/Roundcube/rcube_db_pgsql.php b/lib/ext/Roundcube/rcube_db_pgsql.php
index 285b8e2..0d0caad 100644
--- a/lib/ext/Roundcube/rcube_db_pgsql.php
+++ b/lib/ext/Roundcube/rcube_db_pgsql.php
@@ -31,6 +31,8 @@
  */
 class rcube_db_pgsql extends rcube_db
 {
+    public $db_provider = 'postgres';
+
     /**
      * Get last inserted record ID
      *
diff --git a/lib/ext/Roundcube/rcube_db_sqlite.php b/lib/ext/Roundcube/rcube_db_sqlite.php
index a9774cd..a739767 100644
--- a/lib/ext/Roundcube/rcube_db_sqlite.php
+++ b/lib/ext/Roundcube/rcube_db_sqlite.php
@@ -31,6 +31,8 @@
  */
 class rcube_db_sqlite extends rcube_db
 {
+    public $db_provider = 'sqlite';
+
     /**
      * Database character set
      */
diff --git a/lib/ext/Roundcube/rcube_db_sqlsrv.php b/lib/ext/Roundcube/rcube_db_sqlsrv.php
index feddbe7..e58bf07 100644
--- a/lib/ext/Roundcube/rcube_db_sqlsrv.php
+++ b/lib/ext/Roundcube/rcube_db_sqlsrv.php
@@ -31,6 +31,8 @@
  */
 class rcube_db_sqlsrv extends rcube_db
 {
+    public $db_provider = 'mssql';
+
     /**
      * Driver initialization
      */
diff --git a/lib/ext/Roundcube/rcube_imap.php b/lib/ext/Roundcube/rcube_imap.php
index ebf31d5..1e6cf36 100644
--- a/lib/ext/Roundcube/rcube_imap.php
+++ b/lib/ext/Roundcube/rcube_imap.php
@@ -2219,10 +2219,12 @@ class rcube_imap extends rcube_storage
      * @param string  $message The message source string or filename
      * @param string  $headers Headers string if $message contains only the body
      * @param boolean $is_file True if $message is a filename
+     * @param array   $flags   Message flags
+     * @param mixed   $date    Message internal date
      *
      * @return int|bool Appended message UID or True on success, False on error
      */
-    public function save_message($folder, &$message, $headers='', $is_file=false)
+    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null)
     {
         if (!strlen($folder)) {
             $folder = $this->folder;
@@ -2233,13 +2235,17 @@ class rcube_imap extends rcube_storage
         }
 
         // make sure folder exists
-        if ($this->folder_exists($folder)) {
-            if ($is_file) {
-                $saved = $this->conn->appendFromFile($folder, $message, $headers);
-            }
-            else {
-                $saved = $this->conn->append($folder, $message);
-            }
+        if (!$this->folder_exists($folder)) {
+            return false;
+        }
+
+        $date = $this->date_format($date);
+
+        if ($is_file) {
+            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date);
+        }
+        else {
+            $saved = $this->conn->append($folder, $message, $flags, $date);
         }
 
         if ($saved) {
@@ -3982,6 +3988,29 @@ class rcube_imap extends rcube_storage
 
 
     /**
+     * Converts date string/object into IMAP date/time format
+     */
+    protected function date_format($date)
+    {
+        if (empty($date)) {
+            return null;
+        }
+
+        if (!is_object($date) || !is_a($date, 'DateTime')) {
+            try {
+                $timestamp = rcube_utils::strtotime($date);
+                $date      = new DateTime("@".$timestamp);
+            }
+            catch (Exception $e) {
+                return null;
+            }
+        }
+
+        return $date->format('d-M-Y H:i:s O');
+    }
+
+
+    /**
      * This is our own debug handler for the IMAP connection
      * @access public
      */
diff --git a/lib/ext/Roundcube/rcube_imap_generic.php b/lib/ext/Roundcube/rcube_imap_generic.php
index cce53ae..52bf0e3 100644
--- a/lib/ext/Roundcube/rcube_imap_generic.php
+++ b/lib/ext/Roundcube/rcube_imap_generic.php
@@ -2180,7 +2180,7 @@ class rcube_imap_generic
                             $result[$id]->encoding = $string;
                         break;
                         case 'content-type':
-                            $ctype_parts = preg_split('/[; ]/', $string);
+                            $ctype_parts = preg_split('/[; ]+/', $string);
                             $result[$id]->ctype = strtolower(array_shift($ctype_parts));
                             if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
                                 $result[$id]->charset = $regs[1];
@@ -2540,10 +2540,12 @@ class rcube_imap_generic
      *
      * @param string $mailbox Mailbox name
      * @param string $message Message content
+     * @param array  $flags   Message flags
+     * @param string $date    Message internal date
      *
      * @return string|bool On success APPENDUID response (if available) or True, False on failure
      */
-    function append($mailbox, &$message)
+    function append($mailbox, &$message, $flags = array(), $date = null)
     {
         unset($this->data['APPENDUID']);
 
@@ -2559,12 +2561,17 @@ class rcube_imap_generic
             return false;
         }
 
+        // build APPEND command
         $key = $this->nextTag();
-        $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox),
-            $len, ($this->prefs['literal+'] ? '+' : ''));
+        $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
+        if (!empty($date)) {
+            $request .= ' ' . $this->escape($date);
+        }
+        $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
 
+        // send APPEND command
         if ($this->putLine($request)) {
-            // Don't wait when LITERAL+ is supported
+            // Do not wait when LITERAL+ is supported
             if (!$this->prefs['literal+']) {
                 $line = $this->readReply();
 
@@ -2605,10 +2612,12 @@ class rcube_imap_generic
      * @param string $mailbox Mailbox name
      * @param string $path    Path to the file with message body
      * @param string $headers Message headers
+     * @param array  $flags   Message flags
+     * @param string $date    Message internal date
      *
      * @return string|bool On success APPENDUID response (if available) or True, False on failure
      */
-    function appendFromFile($mailbox, $path, $headers=null)
+    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null)
     {
         unset($this->data['APPENDUID']);
 
@@ -2639,11 +2648,15 @@ class rcube_imap_generic
             $len += strlen($headers) + strlen($body_separator);
         }
 
-        // send APPEND command
+        // build APPEND command
         $key = $this->nextTag();
-        $request = sprintf("$key APPEND %s (\\Seen) {%d%s}", $this->escape($mailbox),
-            $len, ($this->prefs['literal+'] ? '+' : ''));
+        $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
+        if (!empty($date)) {
+            $request .= ' ' . $this->escape($date);
+        }
+        $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
 
+        // send APPEND command
         if ($this->putLine($request)) {
             // Don't wait when LITERAL+ is supported
             if (!$this->prefs['literal+']) {
@@ -3546,6 +3559,24 @@ class rcube_imap_generic
     }
 
     /**
+     * Converts flags array into string for inclusion in IMAP command
+     *
+     * @param array $flags Flags (see self::flags)
+     *
+     * @return string Space-separated list of flags
+     */
+    private function flagsToStr($flags)
+    {
+        foreach ((array)$flags as $idx => $flag) {
+            if ($flag = $this->flags[strtoupper($flag)]) {
+                $flags[$idx] = $flag;
+            }
+        }
+
+        return implode(' ', (array)$flags);
+    }
+
+    /**
      * Converts datetime string into unix timestamp
      *
      * @param string $date Date string
diff --git a/lib/ext/Roundcube/rcube_ldap.php b/lib/ext/Roundcube/rcube_ldap.php
index ad2ccdd..61a073f 100644
--- a/lib/ext/Roundcube/rcube_ldap.php
+++ b/lib/ext/Roundcube/rcube_ldap.php
@@ -109,24 +109,22 @@ class rcube_ldap extends rcube_addressbook
 
             if (!is_array($this->coltypes[$col])) {
                 $subtypes = $type ? array($type) : null;
-                $this->coltypes[$col] = array('limit' => $limit, 'subtypes' => $subtypes);
+                $this->coltypes[$col] = array('limit' => $limit, 'subtypes' => $subtypes, 'attributes' => array($lf));
             }
             elseif ($type) {
                 $this->coltypes[$col]['subtypes'][] = $type;
+                $this->coltypes[$col]['attributes'][] = $lf;
                 $this->coltypes[$col]['limit'] += $limit;
             }
 
             if ($delim)
                $this->coltypes[$col]['serialized'][$type] = $delim;
 
-            if ($type && !$this->fieldmap[$col])
-               $this->fieldmap[$col] = $lf;
-
-            $this->fieldmap[$colv] = $lf;
+           $this->fieldmap[$colv] = $lf;
         }
 
         // support for composite address
-        if ($this->fieldmap['street'] && $this->fieldmap['locality']) {
+        if ($this->coltypes['street'] && $this->coltypes['locality']) {
             $this->coltypes['address'] = array(
                'limit'    => max(1, $this->coltypes['locality']['limit'] + $this->coltypes['address']['limit']),
                'subtypes' => array_merge((array)$this->coltypes['address']['subtypes'], (array)$this->coltypes['locality']['subtypes']),
@@ -134,7 +132,7 @@ class rcube_ldap extends rcube_addressbook
                ) + (array)$this->coltypes['address'];
 
             foreach (array('street','locality','zipcode','region','country') as $childcol) {
-                if ($this->fieldmap[$childcol]) {
+                if ($this->coltypes[$childcol]) {
                     $this->coltypes['address']['childs'][$childcol] = array('type' => 'text');
                     unset($this->coltypes[$childcol]);  // remove address child col from global coltypes list
                 }
@@ -468,8 +466,8 @@ class rcube_ldap extends rcube_addressbook
      */
     function set_sort_order($sort_col, $sort_order = null)
     {
-        if ($this->fieldmap[$sort_col])
-            $this->sort_col = $this->fieldmap[$sort_col];
+        if ($this->coltypes[$sort_col]['attributes'])
+            $this->sort_col = $this->coltypes[$sort_col]['attributes'][0];
     }
 
 
@@ -773,8 +771,9 @@ class rcube_ldap extends rcube_addressbook
 
         // use VLV pseudo-search for autocompletion
         $rcube = rcube::get_instance();
+        $list_fields = $rcube->config->get('contactlist_fields');
 
-        if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $rcube->config->get('contactlist_fields')))
+        if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $list_fields))
         {
             // add general filter to query
             if (!empty($this->prop['filter']) && empty($this->filter))
@@ -802,24 +801,26 @@ class rcube_ldap extends rcube_addressbook
 
             for ($i = 0; $i < $entries['count']; $i++) {
                 $rec = $this->_ldap2result($entries[$i]);
-                foreach (array('email', 'name') as $f) {
-                    $val = mb_strtolower($rec[$f]);
-                    switch ($mode) {
-                    case 1:
-                        $got = ($val == $search);
-                        break;
-                    case 2:
-                        $got = ($search == substr($val, 0, strlen($search)));
-                        break;
-                    default:
-                        $got = (strpos($val, $search) !== false);
-                        break;
-                    }
+                foreach ($fields as $f) {
+                    foreach ((array)$rec[$f] as $val) {
+                        $val = mb_strtolower($val);
+                        switch ($mode) {
+                        case 1:
+                            $got = ($val == $search);
+                            break;
+                        case 2:
+                            $got = ($search == substr($val, 0, strlen($search)));
+                            break;
+                        default:
+                            $got = (strpos($val, $search) !== false);
+                            break;
+                        }
 
-                    if ($got) {
-                        $this->result->add($rec);
-                        $this->result->count++;
-                        break;
+                        if ($got) {
+                            $this->result->add($rec);
+                            $this->result->count++;
+                            break 2;
+                        }
                     }
                 }
             }
@@ -858,8 +859,13 @@ class rcube_ldap extends rcube_addressbook
         {
             foreach ((array)$fields as $idx => $field) {
                 $val = is_array($value) ? $value[$idx] : $value;
-                if ($f = $this->_map_field($field)) {
-                    $filter .= "($f=$wp" . $this->_quote_string($val) . "$ws)";
+                if ($attrs = $this->_map_field($field)) {
+                    if (count($attrs) > 1)
+                        $filter .= '(|';
+                    foreach ($attrs as $f)
+                        $filter .= "($f=$wp" . $this->_quote_string($val) . "$ws)";
+                    if (count($attrs) > 1)
+                        $filter .= ')';
                 }
             }
         }
@@ -867,9 +873,16 @@ class rcube_ldap extends rcube_addressbook
 
         // add required (non empty) fields filter
         $req_filter = '';
-        foreach ((array)$required as $field)
-            if ($f = $this->_map_field($field))
-                $req_filter .= "($f=*)";
+        foreach ((array)$required as $field) {
+            if ($attrs = $this->_map_field($field)) {
+                if (count($attrs) > 1)
+                    $req_filter .= '(|';
+                foreach ($attrs as $f)
+                    $req_filter .= "($f=*)";
+                if (count($attrs) > 1)
+                    $req_filter .= ')';
+            }
+        }
 
         if (!empty($req_filter))
             $filter = '(&' . $req_filter . $filter . ')';
@@ -1498,7 +1511,7 @@ class rcube_ldap extends rcube_addressbook
                 list($col, $subtype) = explode(':', $rf);
                 $out['_raw_attrib'][$lf][$i] = $value;
 
-                if ($rf == 'email' && $this->mail_domain && !strpos($value, '@'))
+                if ($col == 'email' && $this->mail_domain && !strpos($value, '@'))
                     $out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain);
                 else if (in_array($col, array('street','zipcode','locality','country','region')))
                     $out['address'.($subtype?':':'').$subtype][$i][$col] = $value;
@@ -1521,11 +1534,11 @@ class rcube_ldap extends rcube_addressbook
 
 
     /**
-     * Return real field name (from fields map)
+     * Return LDAP attribute(s) for the given field
      */
     private function _map_field($field)
     {
-        return $this->fieldmap[$field];
+        return (array)$this->coltypes[$field]['attributes'];
     }
 
 
@@ -1558,8 +1571,19 @@ class rcube_ldap extends rcube_addressbook
         }
 
         $ldap_data = array();
-        foreach ($this->fieldmap as $col => $fld) {
-            $val = $save_cols[$col];
+        foreach ($this->fieldmap as $rf => $fld) {
+            $val = $save_cols[$rf];
+
+            // check for value in base field (eg.g email instead of email:foo)
+            list($col, $subtype) = explode(':', $rf);
+            if (!$val && !empty($save_cols[$col])) {
+                $val = $save_cols[$col];
+                unset($save_cols[$col]);  // only use this value once
+            }
+            else if (!$val && !$subtype) { // extract values from subtype cols
+                $val = $this->get_col_values($col, $save_cols, true);
+            }
+
             if (is_array($val))
                 $val = array_filter($val);  // remove empty entries
             if ($fld && $val) {
diff --git a/lib/ext/Roundcube/rcube_message.php b/lib/ext/Roundcube/rcube_message.php
index fe2fcf3..7bf95d1 100644
--- a/lib/ext/Roundcube/rcube_message.php
+++ b/lib/ext/Roundcube/rcube_message.php
@@ -371,6 +371,10 @@ class rcube_message
             foreach ($structure->parts as $p => $sub_part) {
                 $sub_mimetype = $sub_part->mimetype;
 
+                // skip empty parts
+                if (!$sub_part->size)
+                    continue;
+
                 // check if sub part is
                 if ($sub_mimetype == 'text/plain')
                     $plain_part = $p;
diff --git a/lib/ext/Roundcube/rcube_output_html.php b/lib/ext/Roundcube/rcube_output_html.php
index 2743e77..6138e2a 100644
--- a/lib/ext/Roundcube/rcube_output_html.php
+++ b/lib/ext/Roundcube/rcube_output_html.php
@@ -527,7 +527,7 @@ class rcube_output_html extends rcube_output
     {
         $GLOBALS['__version']   = html::quote(RCMAIL_VERSION);
         $GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
-        $GLOBALS['__skin_path'] = Q($this->config->get('skin_path'));
+        $GLOBALS['__skin_path'] = html::quote($this->config->get('skin_path'));
 
         return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
             array($this, 'globals_callback'), $input);
diff --git a/lib/ext/Roundcube/rcube_result_thread.php b/lib/ext/Roundcube/rcube_result_thread.php
index 09fa465..b2325a4 100644
--- a/lib/ext/Roundcube/rcube_result_thread.php
+++ b/lib/ext/Roundcube/rcube_result_thread.php
@@ -475,16 +475,18 @@ class rcube_result_thread
             $items = explode(self::SEPARATOR_ITEM, $elem);
             $root  = (int) array_shift($items);
 
-            $result[$elem] = $elem;
-            foreach ($items as $item) {
-                list($lv, $id) = explode(self::SEPARATOR_LEVEL, $item);
+            if ($root) {
+                $result[$root] = $root;
+                foreach ($items as $item) {
+                    list($lv, $id) = explode(self::SEPARATOR_LEVEL, $item);
                     $result[$id] = $root;
+                }
             }
         }
 
         // get only unique roots
         $result = array_filter($result); // make sure there are no nulls
-        $result = array_unique($result, SORT_NUMERIC);
+        $result = array_unique($result);
 
         // Re-sort raw data
         $result = array_fill_keys($result, null);
diff --git a/lib/ext/Roundcube/rcube_storage.php b/lib/ext/Roundcube/rcube_storage.php
index f83e240..933ebcc 100644
--- a/lib/ext/Roundcube/rcube_storage.php
+++ b/lib/ext/Roundcube/rcube_storage.php
@@ -545,10 +545,12 @@ abstract class rcube_storage
      * @param string  $message The message source string or filename
      * @param string  $headers Headers string if $message contains only the body
      * @param boolean $is_file True if $message is a filename
+     * @param array   $flags   Message flags
+     * @param mixed   $date    Message internal date
      *
      * @return int|bool Appended message UID or True on success, False on error
      */
-    abstract function save_message($folder, &$message, $headers = '', $is_file = false);
+    abstract function save_message($folder, &$message, $headers = '', $is_file = false, $flags = array(), $date = null);
 
 
     /**
diff --git a/lib/ext/Roundcube/rcube_user.php b/lib/ext/Roundcube/rcube_user.php
index 29eb0f2..5a65a51 100644
--- a/lib/ext/Roundcube/rcube_user.php
+++ b/lib/ext/Roundcube/rcube_user.php
@@ -435,31 +435,33 @@ class rcube_user
     {
         $user_name  = '';
         $user_email = '';
-        $rcube = rcube::get_instance();
+        $rcube      = rcube::get_instance();
+        $dbh        = $rcube->get_dbh();
 
         // try to resolve user in virtuser table and file
         if ($email_list = self::user2email($user, false, true)) {
             $user_email = is_array($email_list[0]) ? $email_list[0]['email'] : $email_list[0];
         }
 
-        $data = $rcube->plugins->exec_hook('user_create',
-            array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
+        $data = $rcube->plugins->exec_hook('user_create', array(
+            'host'       => $host,
+            'user'       => $user,
+            'user_name'  => $user_name,
+            'user_email' => $user_email,
+            'email_list' => $email_list,
+        ));
 
         // plugin aborted this operation
-        if ($data['abort'])
+        if ($data['abort']) {
             return false;
-
-        $user_name  = $data['user_name'];
-        $user_email = $data['user_email'];
-
-        $dbh = $rcube->get_dbh();
+        }
 
         $dbh->query(
             "INSERT INTO ".$dbh->table_name('users').
             " (created, last_login, username, mail_host, language)".
             " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
-            strip_newlines($user),
-            strip_newlines($host),
+            strip_newlines($data['user']),
+            strip_newlines($data['host']),
             strip_newlines($data['language'] ? $data['language'] : $_SESSION['language']));
 
         if ($user_id = $dbh->insert_id('users')) {
@@ -467,20 +469,25 @@ class rcube_user
             $user_instance = new rcube_user($user_id);
             $rcube->user   = $user_instance;
 
-            $mail_domain = $rcube->config->mail_domain($host);
-
-            if ($user_email == '') {
-                $user_email = strpos($user, '@') ? $user : sprintf('%s@%s', $user, $mail_domain);
-            }
-            if ($user_name == '') {
-                $user_name = $user != $user_email ? $user : '';
-            }
+            $mail_domain = $rcube->config->mail_domain($data['host']);
+            $user_name   = $data['user_name'];
+            $user_email  = $data['user_email'];
+            $email_list  = $data['email_list'];
 
-            if (empty($email_list))
+            if (empty($email_list)) {
+                if (empty($user_email)) {
+                    $user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain);
+                }
                 $email_list[] = strip_newlines($user_email);
+            }
             // identities_level check
-            else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1)
+            else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) {
                 $email_list = array($email_list[0]);
+            }
+
+            if (empty($user_name)) {
+                $user_name = $data['user'];
+            }
 
             // create new identities records
             $standard = 1;
@@ -488,16 +495,21 @@ class rcube_user
                 $record = array();
 
                 if (is_array($row)) {
+                    if (empty($row['email'])) {
+                        continue;
+                    }
                     $record = $row;
                 }
                 else {
                     $record['email'] = $row;
                 }
 
-                if (empty($record['name']))
-                    $record['name'] = $user_name;
-                $record['name'] = strip_newlines($record['name']);
-                $record['user_id'] = $user_id;
+                if (empty($record['name'])) {
+                    $record['name'] = $user_name != $record['email'] ? $user_name : '';
+                }
+
+                $record['name']     = strip_newlines($record['name']);
+                $record['user_id']  = $user_id;
                 $record['standard'] = $standard;
 
                 $plugin = $rcube->plugins->exec_hook('identity_create',
diff --git a/lib/ext/Roundcube/rcube_utils.php b/lib/ext/Roundcube/rcube_utils.php
index b278431..2a4d4c4 100644
--- a/lib/ext/Roundcube/rcube_utils.php
+++ b/lib/ext/Roundcube/rcube_utils.php
@@ -250,9 +250,6 @@ class rcube_utils
 
             $out = strtr($str, $encode_arr);
 
-            // avoid douple quotation of &
-            $out = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
-
             return $newlines ? nl2br($out) : $out;
         }
 
@@ -682,7 +679,7 @@ class rcube_utils
         // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
         if (strpos($name, '%s') !== false) {
             $user_email = self::get_input_value('_user', self::INPUT_POST);
-            $user_email = rcube_utils::idn_convert($user_email, true);
+            $user_email = self::idn_convert($user_email, true);
             $matches    = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s);
             if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) {
                 return false;
diff --git a/lib/ext/Roundcube/rcube_vcard.php b/lib/ext/Roundcube/rcube_vcard.php
index 49b312c..00903c2 100644
--- a/lib/ext/Roundcube/rcube_vcard.php
+++ b/lib/ext/Roundcube/rcube_vcard.php
@@ -50,7 +50,7 @@ class rcube_vcard
     'spouse'      => 'X-SPOUSE',
     'edit'        => 'X-AB-EDIT',
   );
-  private $typemap = array('iPhone' => 'mobile', 'CELL' => 'mobile', 'WORK,FAX' => 'workfax');
+  private $typemap = array('IPHONE' => 'mobile', 'CELL' => 'mobile', 'WORK,FAX' => 'workfax');
   private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'BUSINESSFAX' => 'WORK,FAX');
   private $addresstypemap = array('BUSINESS' => 'WORK');
   private $immap = array('X-JABBER' => 'jabber', 'X-ICQ' => 'icq', 'X-MSN' => 'msn', 'X-AIM' => 'aim', 'X-YAHOO' => 'yahoo', 'X-SKYPE' => 'skype', 'X-SKYPE-USERNAME' => 'skype');
@@ -159,7 +159,18 @@ class rcube_vcard
 
           if (!empty($raw['type'])) {
             $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
-            $subtype = $typemap[$combined] ? $typemap[$combined] : ($typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]));
+            $combined = strtoupper($combined);
+
+            if ($typemap[$combined]) {
+                $subtype = $typemap[$combined];
+            }
+            else if ($typemap[$raw['type'][++$k]]) {
+                $subtype = $typemap[$raw['type'][$k]];
+            }
+            else {
+                $subtype = strtolower($raw['type'][$k]);
+            }
+
             while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref'))
               $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
           }
@@ -167,8 +178,11 @@ class rcube_vcard
           // read vcard 2.1 subtype
           if (!$subtype) {
             foreach ($raw as $k => $v) {
-              if (!is_numeric($k) && $v === true && !in_array(strtolower($k), array('pref','internet','voice','base64'))) {
-                $subtype = $typemap[$k] ? $typemap[$k] : strtolower($k);
+              if (!is_numeric($k) && $v === true && ($k = strtolower($k))
+                && !in_array($k, array('pref','internet','voice','base64'))
+              ) {
+                $k_uc    = strtoupper($k);
+                $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
                 break;
               }
             }
@@ -335,7 +349,7 @@ class rcube_vcard
           $index = count($this->raw[$tag]);
           $this->raw[$tag][$index] = (array)$value;
           if ($type)
-            $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type] ? $typemap[$type] : $type));
+            $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
         }
         break;
     }





More information about the commits mailing list