Aleksander Machniak
2015-12-22 6521c20768d6368b1e068f8ba7857fb614b7d775
program/lib/Roundcube/rcube_imap.php
@@ -1,6 +1,6 @@
<?php
/*
/**
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
@@ -56,21 +56,19 @@
     */
    protected $icache = array();
    protected $list_page = 1;
    protected $plugins;
    protected $delimiter;
    protected $namespace;
    protected $sort_field = '';
    protected $sort_order = 'DESC';
    protected $struct_charset;
    protected $uid_id_map = array();
    protected $msg_headers = array();
    protected $search_set;
    protected $search_string = '';
    protected $search_charset = '';
    protected $search_sort_field = '';
    protected $search_threads = false;
    protected $search_sorted = false;
    protected $options = array('auth_method' => 'check');
    protected $options = array('auth_type' => 'check');
    protected $caching = false;
    protected $messages_caching = false;
    protected $threading = false;
@@ -81,7 +79,8 @@
     */
    public function __construct()
    {
        $this->conn = new rcube_imap_generic();
        $this->conn    = new rcube_imap_generic();
        $this->plugins = rcube::get_instance()->plugins;
        // Set namespace and delimiter from session,
        // so some methods would work before connection
@@ -92,7 +91,6 @@
            $this->delimiter = $_SESSION['imap_delimiter'];
        }
    }
    /**
     * Magic getter for backward compat.
@@ -106,17 +104,16 @@
        }
    }
    /**
     * Connect to an IMAP server
     *
     * @param  string   $host    Host to connect
     * @param  string   $user    Username for IMAP account
     * @param  string   $pass    Password for IMAP account
     * @param  integer  $port    Port to connect to
     * @param  string   $use_ssl SSL schema (either ssl or tls) or null if plain connection
     * @param string  $host    Host to connect
     * @param string  $user    Username for IMAP account
     * @param string  $pass    Password for IMAP account
     * @param integer $port    Port to connect to
     * @param string  $use_ssl SSL schema (either ssl or tls) or null if plain connection
     *
     * @return boolean  TRUE on success, FALSE on failure
     * @return boolean True on success, False on failure
     */
    public function connect($host, $user, $pass, $port=143, $use_ssl=null)
    {
@@ -147,7 +144,7 @@
        $attempt = 0;
        do {
            $data = rcube::get_instance()->plugins->exec_hook('storage_connect',
            $data = $this->plugins->exec_hook('storage_connect',
                array_merge($this->options, array('host' => $host, 'user' => $user,
                    'attempt' => ++$attempt)));
@@ -170,8 +167,20 @@
        $this->connect_done = true;
        if ($this->conn->connected()) {
            // check for session identifier
            $session = null;
            if (preg_match('/\s+SESSIONID=([^=\s]+)/', $this->conn->result, $m)) {
                $session = $m[1];
            }
            // get namespace and delimiter
            $this->set_env();
            // trigger post-connect hook
            $this->plugins->exec_hook('storage_connected', array(
                'host' => $host, 'user' => $user, 'session' => $session
            ));
            return true;
        }
        // write error log
@@ -189,7 +198,6 @@
        return false;
    }
    /**
     * Close IMAP connection.
     * Usually done on script shutdown
@@ -201,7 +209,6 @@
            $this->mcache->close();
        }
    }
    /**
     * Check connection state, connect if not connected.
@@ -224,7 +231,6 @@
        return $this->is_connected();
    }
    /**
     * Checks IMAP connection.
     *
@@ -234,7 +240,6 @@
    {
        return $this->conn->connected();
    }
    /**
     * Returns code of last error
@@ -246,7 +251,6 @@
        return $this->conn->errornum;
    }
    /**
     * Returns text of last error
     *
@@ -256,7 +260,6 @@
    {
        return $this->conn->error;
    }
    /**
     * Returns code of last command response
@@ -287,7 +290,6 @@
        }
    }
    /**
     * Activate/deactivate debug mode
     *
@@ -299,30 +301,21 @@
        $this->conn->setDebug($dbg, array($this, 'debug_handler'));
    }
    /**
     * Set internal folder reference.
     * All operations will be perfomed on this folder.
     *
     * @param  string $folder Folder name
     * @param string $folder Folder name
     */
    public function set_folder($folder)
    {
        if ($this->folder == $folder) {
            return;
        }
        $this->folder = $folder;
        // clear messagecount cache for this folder
        $this->clear_messagecount($folder);
    }
    /**
     * Save a search result for future message listing methods
     *
     * @param  array  $set  Search set, result from rcube_imap::get_search_set():
     * @param array $set Search set, result from rcube_imap::get_search_set():
     *                      0 - searching criteria, string
     *                      1 - search result, rcube_result_index|rcube_result_thread
     *                      2 - searching character set, string
@@ -339,8 +332,11 @@
        $this->search_sort_field = $set[3];
        $this->search_sorted     = $set[4];
        $this->search_threads    = is_a($this->search_set, 'rcube_result_thread');
    }
        if (is_a($this->search_set, 'rcube_result_multifolder')) {
            $this->set_threading(false);
        }
    }
    /**
     * Return the saved search set as hash array
@@ -361,7 +357,6 @@
            $this->search_sorted,
        );
    }
    /**
     * Returns the IMAP server's capability.
@@ -386,29 +381,27 @@
        return $_SESSION[$sess_key];
    }
    /**
     * Checks the PERMANENTFLAGS capability of the current folder
     * and returns true if the given flag is supported by the IMAP server
     *
     * @param   string  $flag Permanentflag name
     * @param string $flag Permanentflag name
     *
     * @return  boolean True if this flag is supported
     * @return boolean True if this flag is supported
     */
    public function check_permflag($flag)
    {
        $flag       = strtoupper($flag);
        $imap_flag  = $this->conn->flags[$flag];
        $perm_flags = $this->get_permflags($this->folder);
        $imap_flag  = $this->conn->flags[$flag];
        return in_array_nocase($imap_flag, $perm_flags);
        return $imap_flag && !empty($perm_flags) && in_array_nocase($imap_flag, $perm_flags);
    }
    /**
     * Returns PERMANENTFLAGS of the specified folder
     *
     * @param  string $folder Folder name
     * @param string $folder Folder name
     *
     * @return array Flags
     */
@@ -417,17 +410,7 @@
        if (!strlen($folder)) {
            return array();
        }
/*
        Checking PERMANENTFLAGS is rather rare, so we disable caching of it
        Re-think when we'll use it for more than only MDNSENT flag
        $cache_key = 'mailboxes.permanentflags.' . $folder;
        $permflags = $this->get_cache($cache_key);
        if ($permflags !== null) {
            return explode(' ', $permflags);
        }
*/
        if (!$this->check_connection()) {
            return array();
        }
@@ -442,32 +425,26 @@
        if (!is_array($permflags)) {
            $permflags = array();
        }
/*
        // Store permflags as string to limit cached object size
        $this->update_cache($cache_key, implode(' ', $permflags));
*/
        return $permflags;
    }
    /**
     * Returns the delimiter that is used by the IMAP server for folder separation
     *
     * @return  string  Delimiter string
     * @access  public
     * @return string Delimiter string
     */
    public function get_hierarchy_delimiter()
    {
        return $this->delimiter;
    }
    /**
     * Get namespace
     *
     * @param string $name Namespace array index: personal, other, shared, prefix
     *
     * @return  array  Namespace data
     * @return array Namespace data
     */
    public function get_namespace($name = null)
    {
@@ -481,7 +458,6 @@
        return $ns;
    }
    /**
     * Sets delimiter and namespaces
     */
@@ -491,7 +467,7 @@
            return;
        }
        $config = rcube::get_instance()->config;
        $config         = rcube::get_instance()->config;
        $imap_personal  = $config->get('imap_ns_personal');
        $imap_other     = $config->get('imap_ns_other');
        $imap_shared    = $config->get('imap_ns_shared');
@@ -562,6 +538,51 @@
        $_SESSION['imap_delimiter'] = $this->delimiter;
    }
    /**
     * Returns IMAP server vendor name
     *
     * @return string Vendor name
     * @since 1.2
     */
    public function get_vendor()
    {
        if ($_SESSION['imap_vendor'] !== null) {
            return $_SESSION['imap_vendor'];
        }
        $config      = rcube::get_instance()->config;
        $imap_vendor = $config->get('imap_vendor');
        if ($imap_vendor) {
            return $imap_vendor;
        }
        if (!$this->check_connection()) {
            return;
        }
        if (($ident = $this->conn->data['ID']) === null) {
            $ident = $this->conn->id(array(
                    'name'    => 'Roundcube',
                    'version' => RCUBE_VERSION,
                    'php'     => PHP_VERSION,
                    'os'      => PHP_OS,
            ));
        }
        $vendor  = (string) (!empty($ident) ? $ident['name'] : '');
        $ident   = strtolower($vendor . ' ' . $this->conn->data['GREETING']);
        $vendors = array('cyrus', 'dovecot', 'uw-imap', 'gmail', 'hmail');
        foreach ($vendors as $v) {
            if (strpos($ident, $v) !== false) {
                $vendor = $v;
                break;
            }
        }
        return $_SESSION['imap_vendor'] = $vendor;
    }
    /**
     * Get message count for a specific folder
@@ -572,7 +593,7 @@
     * @param  boolean $status  Enables storing folder status info (max UID/count),
     *                          required for folder_status()
     *
     * @return int     Number of messages
     * @return int Number of messages
     */
    public function count($folder='', $mode='ALL', $force=false, $status=true)
    {
@@ -583,29 +604,31 @@
        return $this->countmessages($folder, $mode, $force, $status);
    }
    /**
     * protected method for getting nr of messages
     * Protected method for getting number of messages
     *
     * @param string  $folder  Folder name
     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
     * @param boolean $force   Force reading from server and update cache
     * @param boolean $status  Enables storing folder status info (max UID/count),
     *                         required for folder_status()
     * @param string  $folder    Folder name
     * @param string  $mode      Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
     * @param boolean $force     Force reading from server and update cache
     * @param boolean $status    Enables storing folder status info (max UID/count),
     *                           required for folder_status()
     * @param boolean $no_search Ignore current search result
     *
     * @return int Number of messages
     * @see rcube_imap::count()
     */
    protected function countmessages($folder, $mode='ALL', $force=false, $status=true)
    protected function countmessages($folder, $mode = 'ALL', $force = false, $status = true, $no_search = false)
    {
        $mode = strtoupper($mode);
        // count search set, assume search set is always up-to-date (don't check $force flag)
        if ($this->search_string && $folder == $this->folder && ($mode == 'ALL' || $mode == 'THREADS')) {
        // Count search set, assume search set is always up-to-date (don't check $force flag)
        // @TODO: this could be handled in more reliable way, e.g. a separate method
        //        maybe in rcube_imap_search
        if (!$no_search && $this->search_string && $folder == $this->folder) {
            if ($mode == 'ALL') {
                return $this->search_set->count_messages();
            }
            else {
            else if ($mode == 'THREADS') {
                return $this->search_set->count();
            }
        }
@@ -626,7 +649,7 @@
        }
        if ($mode == 'THREADS') {
            $res   = $this->fetch_threads($folder, $force);
            $res   = $this->threads($folder);
            $count = $res->count();
            if ($status) {
@@ -656,11 +679,11 @@
                    $keys[] = 'ALL';
                }
                if ($status) {
                    $keys[]   = 'MAX';
                    $keys[] = 'MAX';
                }
            }
            // @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here
            // @TODO: if $mode == 'ALL' we could try to use cache index here
            // get message count using (E)SEARCH
            // not very performant but more precise (using UNDELETED)
@@ -698,6 +721,39 @@
        return (int)$count;
    }
    /**
     * Public method for listing message flags
     *
     * @param string $folder  Folder name
     * @param array  $uids    Message UIDs
     * @param int    $mod_seq Optional MODSEQ value (of last flag update)
     *
     * @return array Indexed array with message flags
     */
    public function list_flags($folder, $uids, $mod_seq = null)
    {
        if (!strlen($folder)) {
            $folder = $this->folder;
        }
        if (!$this->check_connection()) {
            return array();
        }
        // @TODO: when cache was synchronized in this request
        // we might already have asked for flag updates, use it.
        $flags  = $this->conn->fetch($folder, $uids, true, array('FLAGS'), $mod_seq);
        $result = array();
        if (!empty($flags)) {
            foreach ($flags as $message) {
                $result[$message->uid] = $message->flags;
            }
        }
        return $result;
    }
    /**
     * Public method for listing headers
@@ -708,7 +764,7 @@
     * @param   string   $sort_order Sort order [ASC|DESC]
     * @param   int      $slice      Number of slice items to extract from result array
     *
     * @return  array    Indexed array with message header objects
     * @return array Indexed array with message header objects
     */
    public function list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
    {
@@ -719,7 +775,6 @@
        return $this->_list_messages($folder, $page, $sort_field, $sort_order, $slice);
    }
    /**
     * protected method for listing message headers
     *
@@ -729,8 +784,8 @@
     * @param   string   $sort_order Sort order [ASC|DESC]
     * @param   int      $slice      Number of slice items to extract from result array
     *
     * @return  array    Indexed array with message header objects
     * @see     rcube_imap::list_messages
     * @return array Indexed array with message header objects
     * @see rcube_imap::list_messages
     */
    protected function _list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
    {
@@ -742,7 +797,7 @@
        $page = $page ? $page : $this->list_page;
        // use saved message set
        if ($this->search_string && $folder == $this->folder) {
        if ($this->search_string) {
            return $this->list_search_messages($folder, $page, $slice);
        }
@@ -773,16 +828,15 @@
        return array_values($a_msg_headers);
    }
    /**
     * protected method for listing message headers using threads
     *
     * @param   string   $folder     Folder name
     * @param   int      $page       Current page to list
     * @param   int      $slice      Number of slice items to extract from result array
     * @param string $folder Folder name
     * @param int    $page   Current page to list
     * @param int    $slice  Number of slice items to extract from result array
     *
     * @return  array    Indexed array with message header objects
     * @see     rcube_imap::list_messages
     * @return array Indexed array with message header objects
     * @see rcube_imap::list_messages
     */
    protected function list_thread_messages($folder, $page, $slice=0)
    {
@@ -791,7 +845,7 @@
            $threads = $mcache->get_thread($folder);
        }
        else {
            $threads = $this->fetch_threads($folder);
            $threads = $this->threads($folder);
        }
        return $this->fetch_thread_headers($folder, $threads, $page, $slice);
@@ -800,34 +854,47 @@
    /**
     * Method for fetching threads data
     *
     * @param  string $folder  Folder name
     * @param  bool   $force   Use IMAP server, no cache
     * @param string $folder Folder name
     *
     * @return rcube_imap_thread Thread data object
     */
    function fetch_threads($folder, $force = false)
    function threads($folder)
    {
        if (!$force && ($mcache = $this->get_mcache_engine())) {
        if ($mcache = $this->get_mcache_engine()) {
            // don't store in self's internal cache, cache has it's own internal cache
            return $mcache->get_thread($folder);
        }
        if (empty($this->icache['threads'])) {
            if (!$this->check_connection()) {
                return new rcube_result_thread();
        if (!empty($this->icache['threads'])) {
            if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) {
                return $this->icache['threads'];
            }
            // get all threads
            $result = $this->conn->thread($folder, $this->threading,
                $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
            // add to internal (fast) cache
            $this->icache['threads'] = $result;
        }
        return $this->icache['threads'];
        // get all threads
        $result = $this->threads_direct($folder);
        // add to internal (fast) cache
        return $this->icache['threads'] = $result;
    }
    /**
     * Method for direct fetching of threads data
     *
     * @param string $folder Folder name
     *
     * @return rcube_imap_thread Thread data object
     */
    function threads_direct($folder)
    {
        if (!$this->check_connection()) {
            return new rcube_result_thread();
        }
        // get all threads
        return $this->conn->thread($folder, $this->threading,
            $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
    }
    /**
     * protected method for fetching threaded messages headers
@@ -837,7 +904,7 @@
     * @param int                 $page       List page number
     * @param int                 $slice      Number of threads to slice
     *
     * @return array  Messages headers
     * @return array Messages headers
     */
    protected function fetch_thread_headers($folder, $threads, $page, $slice=0)
    {
@@ -867,13 +934,12 @@
        return array_values($a_msg_headers);
    }
    /**
     * protected method for setting threaded messages flags:
     * depth, has_children and unread_children
     *
     * @param  array               $headers  Reference to headers array indexed by message UID
     * @param  rcube_result_thread $threads  Threads data object
     * @param array               $headers  Reference to headers array indexed by message UID
     * @param rcube_result_thread $threads  Threads data object
     *
     * @return array Message headers array indexed by message UID
     */
@@ -899,7 +965,6 @@
        }
    }
    /**
     * protected method for listing a set of message headers (search results)
     *
@@ -907,12 +972,84 @@
     * @param   int      $page     Current page to list
     * @param   int      $slice    Number of slice items to extract from result array
     *
     * @return  array    Indexed array with message header objects
     * @return array Indexed array with message header objects
     */
    protected function list_search_messages($folder, $page, $slice=0)
    {
        if (!strlen($folder) || empty($this->search_set) || $this->search_set->is_empty()) {
            return array();
        }
        // gather messages from a multi-folder search
        if ($this->search_set->multi) {
            $page_size  = $this->page_size;
            $sort_field = $this->sort_field;
            $search_set = $this->search_set;
            // prepare paging
            $cnt   = $search_set->count();
            $from  = ($page-1) * $page_size;
            $to    = $from + $page_size;
            $slice_length = min($page_size, $cnt - $from);
            // fetch resultset headers, sort and slice them
            if (!empty($sort_field) && $search_set->get_parameters('SORT') != $sort_field) {
                $this->sort_field = null;
                $this->page_size  = 1000;  // fetch up to 1000 matching messages per folder
                $this->threading  = false;
                $a_msg_headers = array();
                foreach ($search_set->sets as $resultset) {
                    if (!$resultset->is_empty()) {
                        $this->search_set     = $resultset;
                        $this->search_threads = $resultset instanceof rcube_result_thread;
                        $a_headers     =  $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1);
                        $a_msg_headers = array_merge($a_msg_headers, $a_headers);
                        unset($a_headers);
                    }
                }
                // sort headers
                if (!empty($a_msg_headers)) {
                    $a_msg_headers = rcube_imap_generic::sortHeaders($a_msg_headers, $sort_field, $this->sort_order);
                }
                // store (sorted) message index
                $search_set->set_message_index($a_msg_headers, $sort_field, $this->sort_order);
                // only return the requested part of the set
                $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length);
            }
            else {
                if ($this->sort_order != $search_set->get_parameters('ORDER')) {
                    $search_set->revert();
                }
                // slice resultset first...
                $fetch = array();
                foreach (array_slice($search_set->get(), $from, $slice_length) as $msg_id) {
                    list($uid, $folder) = explode('-', $msg_id, 2);
                    $fetch[$folder][] = $uid;
                }
                // ... and fetch the requested set of headers
                $a_msg_headers = array();
                foreach ($fetch as $folder => $a_index) {
                    $a_msg_headers = array_merge($a_msg_headers, array_values($this->fetch_headers($folder, $a_index)));
                }
            }
            if ($slice) {
                $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice);
            }
            // restore members
            $this->sort_field = $sort_field;
            $this->page_size  = $page_size;
            $this->search_set = $search_set;
            return $a_msg_headers;
        }
        // use saved messages from searching
@@ -1003,12 +1140,8 @@
                return array();
            }
            if (!$this->check_connection()) {
                return array();
            }
            // if not already sorted
            $a_msg_headers = $this->conn->sortHeaders(
            $a_msg_headers = rcube_imap_generic::sortHeaders(
                $a_msg_headers, $this->sort_field, $this->sort_order);
            // only return the requested part of the set
@@ -1023,7 +1156,6 @@
        }
    }
    /**
     * protected method for listing a set of threaded message headers (search results)
     *
@@ -1031,7 +1163,7 @@
     * @param   int      $page       Current page to list
     * @param   int      $slice      Number of slice items to extract from result array
     *
     * @return  array    Indexed array with message header objects
     * @return array Indexed array with message header objects
     * @see rcube_imap::list_search_messages()
     */
    protected function list_search_thread_messages($folder, $page, $slice=0)
@@ -1046,7 +1178,6 @@
        return $this->fetch_thread_headers($folder, clone $this->search_set, $page, $slice);
    }
    /**
     * Fetches messages headers (by UID)
@@ -1081,6 +1212,7 @@
        }
        foreach ($headers as $h) {
            $h->folder = $folder;
            $a_msg_headers[$h->uid] = $h;
        }
@@ -1093,7 +1225,6 @@
        return $a_msg_headers;
    }
    /**
     * Returns current status of a folder (compared to the last time use)
@@ -1114,7 +1245,7 @@
        $old = $this->get_folder_stats($folder);
        // refresh message count -> will update
        $this->countmessages($folder, 'ALL', true);
        $this->countmessages($folder, 'ALL', true, true, true);
        $result = 0;
@@ -1142,7 +1273,6 @@
        return $result;
    }
    /**
     * Stores folder statistic data in session
     * @TODO: move to separate DB table (cache?)
@@ -1155,7 +1285,6 @@
    {
        $_SESSION['folders'][$folder][$name] = $data;
    }
    /**
     * Gets folder statistic data
@@ -1173,19 +1302,21 @@
        return array();
    }
    /**
     * Return sorted list of message UIDs
     *
     * @param string $folder     Folder to get index from
     * @param string $sort_field Sort column
     * @param string $sort_order Sort order [ASC, DESC]
     * @param bool   $no_threads Get not threaded index
     * @param bool   $no_search  Get index not limited to search result (optionally)
     *
     * @return rcube_result_index|rcube_result_thread List of messages (UIDs)
     */
    public function index($folder = '', $sort_field = NULL, $sort_order = NULL)
    {
        if ($this->threading) {
    public function index($folder = '', $sort_field = NULL, $sort_order = NULL,
        $no_threads = false, $no_search = false
    ) {
        if (!$no_threads && $this->threading) {
            return $this->thread_index($folder, $sort_field, $sort_order);
        }
@@ -1197,66 +1328,79 @@
        // we have a saved search result, get index from there
        if ($this->search_string) {
            if ($this->search_threads) {
                $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
            if ($this->search_set->is_empty()) {
                return new rcube_result_index($folder, '* SORT');
            }
            // use message index sort as default sorting
            if (!$this->sort_field || $this->search_sorted) {
                if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
                    $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
                }
            if ($this->search_set instanceof rcube_result_multifolder) {
                $index = $this->search_set;
                $index->folder = $folder;
                // TODO: handle changed sorting
            }
            // search result is an index with the same sorting?
            else if (($this->search_set instanceof rcube_result_index)
                && ((!$this->sort_field && !$this->search_sorted) ||
                    ($this->search_sorted && $this->search_sort_field == $this->sort_field))
            ) {
                $index = $this->search_set;
            }
            else if (!$this->check_connection()) {
                return new rcube_result_index();
            }
            else {
                $index = $this->conn->index($folder, $this->search_set->get(),
                    $this->sort_field, $this->options['skip_deleted'], true, true);
            // $no_search is enabled when we are not interested in
            // fetching index for search result, e.g. to sort
            // threaded search result we can use full mailbox index.
            // This makes possible to use index from cache
            else if (!$no_search) {
                if (!$this->sort_field) {
                    // No sorting needed, just build index from the search result
                    // @TODO: do we need to sort by UID here?
                    $search = $this->search_set->get_compressed();
                    $index  = new rcube_result_index($folder, '* ESEARCH ALL ' . $search);
                }
                else {
                    $index = $this->index_direct($folder, $this->search_charset,
                        $this->sort_field, $this->search_set);
                }
            }
            if ($this->sort_order != $index->get_parameters('ORDER')) {
                $index->revert();
            }
            if (isset($index)) {
                if ($this->sort_order != $index->get_parameters('ORDER')) {
                    $index->revert();
                }
            return $index;
                return $index;
            }
        }
        // check local cache
        if ($mcache = $this->get_mcache_engine()) {
            $index = $mcache->get_index($folder, $this->sort_field, $this->sort_order);
            return $mcache->get_index($folder, $this->sort_field, $this->sort_order);
        }
        // fetch from IMAP server
        else {
            $index = $this->index_direct(
                $folder, $this->sort_field, $this->sort_order);
        }
        return $index;
        return $this->index_direct($folder, $this->sort_field, $this->sort_order);
    }
    /**
     * Return sorted list of message UIDs ignoring current search settings.
     * Doesn't uses cache by default.
     *
     * @param string $folder     Folder to get index from
     * @param string $sort_field Sort column
     * @param string $sort_order Sort order [ASC, DESC]
     * @param bool   $skip_cache Disables cache usage
     * @param string         $folder     Folder to get index from
     * @param string         $sort_field Sort column
     * @param string         $sort_order Sort order [ASC, DESC]
     * @param rcube_result_* $search     Optional messages set to limit the result
     *
     * @return rcube_result_index Sorted list of message UIDs
     */
    public function index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
    public function index_direct($folder, $sort_field = null, $sort_order = null, $search = null)
    {
        if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
            $index = $mcache->get_index($folder, $sort_field, $sort_order);
        if (!empty($search)) {
            $search = $search->get_compressed();
        }
        // use message index sort as default sorting
        else if (!$sort_field) {
        if (!$sort_field) {
            // use search result from count() if possible
            if ($this->options['skip_deleted'] && !empty($this->icache['undeleted_idx'])
            if (empty($search) && $this->options['skip_deleted']
                && !empty($this->icache['undeleted_idx'])
                && $this->icache['undeleted_idx']->get_parameters('ALL') !== null
                && $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder
            ) {
@@ -1266,8 +1410,12 @@
                return new rcube_result_index();
            }
            else {
                $index = $this->conn->search($folder,
                    'ALL' .($this->options['skip_deleted'] ? ' UNDELETED' : ''), true);
                $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
                if ($search) {
                    $query = trim($query . ' UID ' . $search);
                }
                $index = $this->conn->search($folder, $query, true);
            }
        }
        else if (!$this->check_connection()) {
@@ -1276,13 +1424,18 @@
        // fetch complete message index
        else {
            if ($this->get_capability('SORT')) {
                $index = $this->conn->sort($folder, $sort_field,
                    $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
                $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
                if ($search) {
                    $query = trim($query . ' UID ' . $search);
                }
                $index = $this->conn->sort($folder, $sort_field, $query, true);
            }
            if (empty($index) || $index->is_error()) {
                $index = $this->conn->index($folder, "1:*", $sort_field,
                    $this->options['skip_deleted'], false, true);
                $index = $this->conn->index($folder, $search ? $search : "1:*",
                    $sort_field, $this->options['skip_deleted'],
                    $search ? true : false, true);
            }
        }
@@ -1292,7 +1445,6 @@
        return $index;
    }
    /**
     * Return index of threaded message UIDs
@@ -1315,7 +1467,7 @@
        }
        else {
            // get all threads (default sort order)
            $threads = $this->fetch_threads($folder);
            $threads = $this->threads($folder);
        }
        $this->set_sort_order($sort_field, $sort_order);
@@ -1324,11 +1476,11 @@
        return $threads;
    }
    /**
     * Sort threaded result, using THREAD=REFS method
     * Sort threaded result, using THREAD=REFS method if available.
     * If not, use any method and re-sort the result in THREAD=REFS way.
     *
     * @param rcube_result_thread $threads  Threads result set
     * @param rcube_result_thread $threads Threads result set
     */
    protected function sort_threads($threads)
    {
@@ -1340,47 +1492,93 @@
        // THREAD=REFERENCES:     sorting by sent date of root message
        // THREAD=REFS:           sorting by the most recent date in each thread
        if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) {
            $index = $this->index_direct($this->folder, $this->sort_field, $this->sort_order, false);
        if ($this->threading != 'REFS' || ($this->sort_field && $this->sort_field != 'date')) {
            $sortby = $this->sort_field ? $this->sort_field : 'date';
            $index  = $this->index($this->folder, $sortby, $this->sort_order, true, true);
            if (!$index->is_empty()) {
                $threads->sort($index);
            }
        }
        else {
            if ($this->sort_order != $threads->get_parameters('ORDER')) {
                $threads->revert();
            }
        else if ($this->sort_order != $threads->get_parameters('ORDER')) {
            $threads->revert();
        }
    }
    /**
     * Invoke search request to IMAP server
     *
     * @param  string  $folder     Folder name to search in
     * @param  string  $str        Search criteria
     * @param  string  $search     Search criteria
     * @param  string  $charset    Search charset
     * @param  string  $sort_field Header field to sort by
     *
     * @return rcube_result_index  Search result object
     * @todo: Search criteria should be provided in non-IMAP format, eg. array
     */
    public function search($folder='', $str='ALL', $charset=NULL, $sort_field=NULL)
    public function search($folder = '', $search = 'ALL', $charset = null, $sort_field = null)
    {
        if (!$str) {
            $str = 'ALL';
        if (!$search) {
            $search = 'ALL';
        }
        if (!strlen($folder)) {
        if ((is_array($folder) && empty($folder)) || (!is_array($folder) && !strlen($folder))) {
            $folder = $this->folder;
        }
        $results = $this->search_index($folder, $str, $charset, $sort_field);
        $plugin = $this->plugins->exec_hook('imap_search_before', array(
            'folder'     => $folder,
            'search'     => $search,
            'charset'    => $charset,
            'sort_field' => $sort_field,
            'threading'  => $this->threading,
        ));
        $this->set_search_set(array($str, $results, $charset, $sort_field,
            $this->threading || $this->search_sorted ? true : false));
        $folder     = $plugin['folder'];
        $search     = $plugin['search'];
        $charset    = $plugin['charset'];
        $sort_field = $plugin['sort_field'];
        $results    = $plugin['result'];
        // multi-folder search
        if (!$results && is_array($folder) && count($folder) > 1 && $search != 'ALL') {
            // connect IMAP to have all the required classes and settings loaded
            $this->check_connection();
            // disable threading
            $this->threading = false;
            $searcher = new rcube_imap_search($this->options, $this->conn);
            // set limit to not exceed the client's request timeout
            $searcher->set_timelimit(60);
            // continue existing incomplete search
            if (!empty($this->search_set) && $this->search_set->incomplete && $search == $this->search_string) {
                $searcher->set_results($this->search_set);
            }
            // execute the search
            $results = $searcher->exec(
                $folder,
                $search,
                $charset ? $charset : $this->default_charset,
                $sort_field && $this->get_capability('SORT') ? $sort_field : null,
                $this->threading
            );
        }
        else if (!$results) {
            $folder  = is_array($folder) ? $folder[0] : $folder;
            $search  = is_array($search) ? $search[$folder] : $search;
            $results = $this->search_index($folder, $search, $charset, $sort_field);
        }
        $sorted = $this->threading || $this->search_sorted || $plugin['search_sorted'] ? true : false;
        $this->set_search_set(array($search, $results, $charset, $sort_field, $sorted));
        return $results;
    }
    /**
     * Direct (real and simple) SEARCH request (without result sorting and caching).
@@ -1392,23 +1590,29 @@
     */
    public function search_once($folder = null, $str = 'ALL')
    {
        if (!$str) {
            return 'ALL';
        }
        if (!strlen($folder)) {
            $folder = $this->folder;
        }
        if (!$this->check_connection()) {
            return new rcube_result_index();
        }
        $index = $this->conn->search($folder, $str, true);
        if (!$str) {
            $str = 'ALL';
        }
        // multi-folder search
        if (is_array($folder) && count($folder) > 1) {
            $searcher = new rcube_imap_search($this->options, $this->conn);
            $index = $searcher->exec($folder, $str, $this->default_charset);
        }
        else {
            $folder = is_array($folder) ? $folder[0] : $folder;
            if (!strlen($folder)) {
                $folder = $this->folder;
            }
            $index = $this->conn->search($folder, $str, true);
        }
        return $index;
    }
    /**
     * protected search method
@@ -1449,7 +1653,7 @@
            // but I've seen that Courier doesn't support UTF-8)
            if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
                $threads = $this->conn->thread($folder, $this->threading,
                    $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
                    self::convert_criteria($criteria, $charset), true, 'US-ASCII');
            }
            return $threads;
@@ -1463,7 +1667,7 @@
            // but I've seen Courier with disabled UTF-8 support)
            if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
                $messages = $this->conn->sort($folder, $sort_field,
                    $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
                    self::convert_criteria($criteria, $charset), true, 'US-ASCII');
            }
            if (!$messages->is_error()) {
@@ -1478,14 +1682,13 @@
        // Error, try with US-ASCII (some servers may support only US-ASCII)
        if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
            $messages = $this->conn->search($folder,
                $this->convert_criteria($criteria, $charset), true);
                self::convert_criteria($criteria, $charset), true);
        }
        $this->search_sorted = false;
        return $messages;
    }
    /**
     * Converts charset of search criteria string
@@ -1496,7 +1699,7 @@
     *
     * @return string  Search string
     */
    protected function convert_criteria($str, $charset, $dest_charset='US-ASCII')
    public static function convert_criteria($str, $charset, $dest_charset='US-ASCII')
    {
        // convert strings to US_ASCII
        if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
@@ -1505,12 +1708,15 @@
                $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
                $string = substr($str, $string_offset - 1, $m[0]);
                $string = rcube_charset::convert($string, $charset, $dest_charset);
                if ($string === false) {
                if ($string === false || !strlen($string)) {
                    continue;
                }
                $res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string);
                $last = $m[0] + $string_offset - 1;
            }
            if ($last < strlen($str)) {
                $res .= substr($str, $last, strlen($str)-$last);
            }
@@ -1523,7 +1729,6 @@
        return $res;
    }
    /**
     * Refresh saved search set
     *
@@ -1532,12 +1737,29 @@
    public function refresh_search()
    {
        if (!empty($this->search_string)) {
            $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
            $this->search(
                is_object($this->search_set) ? $this->search_set->get_parameters('MAILBOX') : '',
                $this->search_string,
                $this->search_charset,
                $this->search_sort_field
            );
        }
        return $this->get_search_set();
    }
    /**
     * Flag certain result subsets as 'incomplete'.
     * For subsequent refresh_search() calls to only refresh the updated parts.
     */
    protected function set_search_dirty($folder)
    {
        if ($this->search_set && is_a($this->search_set, 'rcube_result_multifolder')) {
            if ($subset = $this->search_set->get_set($folder)) {
                $subset->incomplete = $this->search_set->incomplete = true;
            }
        }
    }
    /**
     * Return message headers object of a specific message
@@ -1550,6 +1772,11 @@
     */
    public function get_message_headers($uid, $folder = null, $force = false)
    {
        // decode combined UID-folder identifier
        if (preg_match('/^\d+-.+/', $uid)) {
            list($uid, $folder) = explode('-', $uid, 2);
        }
        if (!strlen($folder)) {
            $folder = $this->folder;
        }
@@ -1564,18 +1791,20 @@
        else {
            $headers = $this->conn->fetchHeader(
                $folder, $uid, true, true, $this->get_fetch_headers());
            if (is_object($headers))
                $headers->folder = $folder;
        }
        return $headers;
    }
    /**
     * Fetch message headers and body structure from the IMAP server and build
     * an object structure similar to the one generated by PEAR::Mail_mimeDecode
     * an object structure.
     *
     * @param int     $uid      Message UID to fetch
     * @param string  $folder   Folder to read from
     * @param int    $uid    Message UID to fetch
     * @param string $folder Folder to read from
     *
     * @return object rcube_message_header Message data
     */
@@ -1583,6 +1812,11 @@
    {
        if (!strlen($folder)) {
            $folder = $this->folder;
        }
        // decode combined UID-folder identifier
        if (preg_match('/^\d+-.+/', $uid)) {
            list($uid, $folder) = explode('-', $uid, 2);
        }
        // Check internal cache
@@ -1628,7 +1862,7 @@
            $this->struct_charset = $this->structure_charset($structure);
        }
        $headers->ctype = strtolower($headers->ctype);
        $headers->ctype = @strtolower($headers->ctype);
        // Here we can recognize malformed BODYSTRUCTURE and
        // 1. [@TODO] parse the message in other way to create our own message structure
@@ -1649,8 +1883,8 @@
                $structure[1] = $m[2];
            }
            else {
                // Try to parse the message using Mail_mimeDecode package
                // We need a better solution, Mail_mimeDecode parses message
                // Try to parse the message using rcube_mime_decode.
                // We need a better solution, it parses message
                // in memory, which wouldn't work for very big messages,
                // (it uses up to 10x more memory than the message size)
                // it's also buggy and not actively developed
@@ -1690,7 +1924,6 @@
        return $this->icache['message'] = $headers;
    }
    /**
     * Build message part object
     *
@@ -1698,7 +1931,7 @@
     * @param int    $count
     * @param string $parent
     */
    protected function structure_part($part, $count=0, $parent='', $mime_headers=null)
    protected function structure_part($part, $count = 0, $parent = '', $mime_headers = null)
    {
        $struct = new rcube_message_part;
        $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
@@ -1723,6 +1956,16 @@
            for ($i=1; $i<count($part); $i++) {
                if (!is_array($part[$i])) {
                    $struct->ctype_secondary = strtolower($part[$i]);
                    // read content type parameters
                    if (is_array($part[$i+1])) {
                        $struct->ctype_parameters = array();
                        for ($j=0; $j<count($part[$i+1]); $j+=2) {
                            $param = strtolower($part[$i+1][$j]);
                            $struct->ctype_parameters[$param] = $part[$i+1][$j+1];
                        }
                    }
                    break;
                }
            }
@@ -1906,14 +2149,13 @@
        return $struct;
    }
    /**
     * Set attachment filename from message part structure
     *
     * @param  rcube_message_part $part    Part object
     * @param  string             $headers Part's raw headers
     * @param rcube_message_part $part    Part object
     * @param string             $headers Part's raw headers
     */
    protected function set_part_filename(&$part, $headers=null)
    protected function set_part_filename(&$part, $headers = null)
    {
        if (!empty($part->d_parameters['filename'])) {
            $filename_mime = $part->d_parameters['filename'];
@@ -2041,11 +2283,10 @@
        }
    }
    /**
     * Get charset name from message structure (first part)
     *
     * @param  array $structure Message structure
     * @param array $structure Message structure
     *
     * @return string Charset name
     */
@@ -2063,17 +2304,19 @@
    /**
     * 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;
@@ -2092,8 +2335,9 @@
        }
        if ($o_part && $o_part->size) {
            $formatted = $formatted && $o_part->ctype_primary == 'text';
            $body = $this->conn->handlePartBody($this->folder, $uid, true,
                $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $o_part->ctype_primary == 'text', $max_bytes);
                $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $formatted, $max_bytes);
        }
        if ($fp || $print) {
@@ -2103,7 +2347,7 @@
        // convert charset (if text or message part)
        if ($body && preg_match('/^(text|message)$/', $o_part->ctype_primary)) {
            // Remove NULL characters if any (#1486189)
            if (strpos($body, "\x00") !== false) {
            if ($formatted && strpos($body, "\x00") !== false) {
                $body = str_replace("\x00", '', $body);
            }
@@ -2124,42 +2368,41 @@
        return $body;
    }
    /**
     * Returns the whole message source as string (or saves to a file)
     *
     * @param int      $uid Message UID
     * @param resource $fp  File pointer to save the message
     * @param int      $uid  Message UID
     * @param resource $fp   File pointer to save the message
     * @param string   $part Optional message part ID
     *
     * @return string Message source string
     */
    public function get_raw_body($uid, $fp=null)
    public function get_raw_body($uid, $fp=null, $part = null)
    {
        if (!$this->check_connection()) {
            return null;
        }
        return $this->conn->handlePartBody($this->folder, $uid,
            true, null, null, false, $fp);
            true, $part, null, false, $fp);
    }
    /**
     * Returns the message headers as string
     *
     * @param int $uid  Message UID
     * @param int    $uid  Message UID
     * @param string $part Optional message part ID
     *
     * @return string Message headers string
     */
    public function get_raw_headers($uid)
    public function get_raw_headers($uid, $part = null)
    {
        if (!$this->check_connection()) {
            return null;
        }
        return $this->conn->fetchPartHeader($this->folder, $uid, true);
        return $this->conn->fetchPartHeader($this->folder, $uid, true, $part);
    }
    /**
     * Sends the whole message source to stdout
@@ -2176,13 +2419,12 @@
        $this->conn->handlePartBody($this->folder, $uid, true, null, null, true, null, $formatted);
    }
    /**
     * Set message flag to one or several messages
     *
     * @param mixed   $uids       Message UIDs as array or comma-separated string, or '*'
     * @param string  $flag       Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
     * @param string  $folder    Folder name
     * @param string  $folder     Folder name
     * @param boolean $skip_cache True to skip message cache clean up
     *
     * @return boolean  Operation status
@@ -2229,22 +2471,24 @@
                    $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
                }
            }
            $this->set_search_dirty($folder);
        }
        return $result;
    }
    /**
     * Append a mail message (source) to a specific folder
     *
     * @param string  $folder  Target folder
     * @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
     * @param bool    $binary  Enables BINARY append
     * @param string       $folder  Target folder
     * @param string|array $message The message source string or filename
     *                              or array (of strings and file pointers)
     * @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
     * @param bool         $binary  Enables BINARY append
     *
     * @return int|bool Appended message UID or True on success, False on error
     */
@@ -2275,11 +2519,21 @@
        if ($saved) {
            // increase messagecount of the target folder
            $this->set_messagecount($folder, 'ALL', 1);
            $this->plugins->exec_hook('message_saved', array(
                    'folder'  => $folder,
                    'message' => $message,
                    'headers' => $headers,
                    'is_file' => $is_file,
                    'flags'   => $flags,
                    'date'    => $date,
                    'binary'  => $binary,
                    'result'  => $saved,
            ));
        }
        return $saved;
    }
    /**
     * Move a message from one folder to another
@@ -2311,19 +2565,7 @@
            return false;
        }
        // make sure folder exists
        if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
            if (in_array($to_mbox, $this->default_folders)) {
                if (!$this->create_folder($to_mbox, true)) {
                    return false;
                }
            }
            else {
                return false;
            }
        }
        $config = rcube::get_instance()->config;
        $config   = rcube::get_instance()->config;
        $to_trash = $to_mbox == $config->get('trash_mbox');
        // flag messages as read before moving them
@@ -2338,6 +2580,9 @@
        if ($moved) {
            $this->clear_messagecount($from_mbox);
            $this->clear_messagecount($to_mbox);
            $this->set_search_dirty($from_mbox);
            $this->set_search_dirty($to_mbox);
        }
        // moving failed
        else if ($to_trash && $config->get('delete_always', false)) {
@@ -2354,8 +2599,8 @@
                if ($this->search_threads || $all_mode) {
                    $this->refresh_search();
                }
                else {
                    $this->search_set->filter(explode(',', $uids));
                else if (!$this->search_set->incomplete) {
                    $this->search_set->filter(explode(',', $uids), $this->folder);
                }
            }
@@ -2366,7 +2611,6 @@
        return $moved;
    }
    /**
     * Copy a message from one folder to another
@@ -2394,18 +2638,6 @@
            return false;
        }
        // make sure folder exists
        if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
            if (in_array($to_mbox, $this->default_folders)) {
                if (!$this->create_folder($to_mbox, true)) {
                    return false;
                }
            }
            else {
                return false;
            }
        }
        // copy messages
        $copied = $this->conn->copy($uids, $from_mbox, $to_mbox);
@@ -2415,7 +2647,6 @@
        return $copied;
    }
    /**
     * Mark messages as deleted and expunge them
@@ -2449,10 +2680,11 @@
            // really deleted from the folder
            $this->expunge_message($uids, $folder, false);
            $this->clear_messagecount($folder);
            unset($this->uid_id_map[$folder]);
            // unset threads internal cache
            unset($this->icache['threads']);
            $this->set_search_dirty($folder);
            // remove message ids from search set
            if ($this->search_set && $folder == $this->folder) {
@@ -2460,7 +2692,7 @@
                if ($this->search_threads || $all_mode) {
                    $this->refresh_search();
                }
                else {
                else if (!$this->search_set->incomplete) {
                    $this->search_set->filter(explode(',', $uids));
                }
            }
@@ -2471,7 +2703,6 @@
        return $deleted;
    }
    /**
     * Send IMAP expunge command and clear cache
@@ -2560,7 +2791,7 @@
        }
        // Give plugins a chance to provide a list of folders
        $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
        $data = $this->plugins->exec_hook('storage_folders',
            array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB'));
        if (isset($data['folders'])) {
@@ -2580,7 +2811,7 @@
        }
        // INBOX should always be available
        if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
        if (!strlen($root) && (!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
            array_unshift($a_mboxes, 'INBOX');
        }
@@ -2595,15 +2826,14 @@
        return $a_mboxes;
    }
    /**
     * Method for direct folders listing (LSUB)
     *
     * @param   string  $root   Optional root folder
     * @param   string  $name   Optional name pattern
     * @param string $root Optional root folder
     * @param string $name Optional name pattern
     *
     * @return  array   List of subscribed folders
     * @see     rcube_imap::list_folders_subscribed()
     * @return array List of subscribed folders
     * @see rcube_imap::list_folders_subscribed()
     */
    public function list_folders_subscribed_direct($root='', $name='*')
    {
@@ -2618,58 +2848,56 @@
        $list_extended = !$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED');
        if ($list_extended) {
            // This will also set folder options, LSUB doesn't do that
            $a_folders = $this->conn->listMailboxes($root, $name,
            $result = $this->conn->listMailboxes($root, $name,
                NULL, array('SUBSCRIBED'));
        }
        else {
            // retrieve list of folders from IMAP server using LSUB
            $a_folders = $this->conn->listSubscribed($root, $name);
            $result = $this->conn->listSubscribed($root, $name);
        }
        if (!is_array($a_folders)) {
        if (!is_array($result)) {
            return array();
        }
        // #1486796: some server configurations doesn't return folders in all namespaces
        if ($root == '' && $name == '*' && $config->get('imap_force_ns')) {
            $this->list_folders_update($a_folders, ($list_extended ? 'ext-' : '') . 'subscribed');
            $this->list_folders_update($result, ($list_extended ? 'ext-' : '') . 'subscribed');
        }
        // Remove hidden folders
        if ($config->get('imap_skip_hidden_folders')) {
            $result = array_filter($result, function($v) { return $v[0] != '.'; });
        }
        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 ($name == '*' && !empty($this->conn->data['LIST'])) {
                foreach ($result as $idx => $folder) {
                    if (($opts = $this->conn->data['LIST'][$folder])
                        && in_array('\\NonExistent', $opts)
                        && in_array_nocase('\\NonExistent', $opts)
                    ) {
                        $this->conn->unsubscribe($folder);
                        unset($a_folders[$idx]);
                        unset($result[$idx]);
                    }
                }
            }
        }
        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 (!empty($result) && $name == '*') {
                $existing    = $this->list_folders($root, $name);
                $nonexisting = array_diff($result, $existing);
                $result      = array_diff($result, $nonexisting);
                foreach ($nonexisting as $folder) {
                    $this->conn->unsubscribe($folder);
                }
            }
        }
        return $a_folders;
        return $result;
    }
    /**
     * Get a list of all folders available on the server
@@ -2698,7 +2926,7 @@
        }
        // Give plugins a chance to provide a list of folders
        $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
        $data = $this->plugins->exec_hook('storage_folders',
            array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST'));
        if (isset($data['folders'])) {
@@ -2714,7 +2942,7 @@
        }
        // INBOX should always be available
        if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
        if (!strlen($root) && (!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
            array_unshift($a_mboxes, 'INBOX');
        }
@@ -2739,15 +2967,14 @@
        return $a_mboxes;
    }
    /**
     * Method for direct folders listing (LIST)
     *
     * @param   string  $root   Optional root folder
     * @param   string  $name   Optional name pattern
     * @param string $root Optional root folder
     * @param string $name Optional name pattern
     *
     * @return  array   List of folders
     * @see     rcube_imap::list_folders()
     * @return array List of folders
     * @see rcube_imap::list_folders()
     */
    public function list_folders_direct($root='', $name='*')
    {
@@ -2768,18 +2995,22 @@
            $this->list_folders_update($result);
        }
        // Remove hidden folders
        if ($config->get('imap_skip_hidden_folders')) {
            $result = array_filter($result, function($v) { return $v[0] != '.'; });
        }
        return $result;
    }
    /**
     * Fix folders list by adding folders from other namespaces.
     * Needed on some servers eg. Courier IMAP
     *
     * @param array  $result  Reference to folders list
     * @param string $type    Listing type (ext-subscribed, subscribed or all)
     * @param array  $result Reference to folders list
     * @param string $type   Listing type (ext-subscribed, subscribed or all)
     */
    private function list_folders_update(&$result, $type = null)
    protected function list_folders_update(&$result, $type = null)
    {
        $namespace = $this->get_namespace();
        $search    = array();
@@ -2827,15 +3058,23 @@
        }
    }
    /**
     * Filter the given list of folders according to access rights
     *
     * For performance reasons we assume user has full rights
     * on all personal folders.
     */
    protected function filter_rights($a_folders, $rights)
    {
        $regex = '/('.$rights.')/';
        foreach ($a_folders as $idx => $folder) {
            if ($this->folder_namespace($folder) == 'personal') {
                continue;
            }
            $myrights = join('', (array)$this->my_rights($folder));
            if ($myrights !== null && !preg_match($regex, $myrights)) {
                unset($a_folders[$idx]);
            }
@@ -2844,22 +3083,21 @@
        return $a_folders;
    }
    /**
     * Get mailbox quota information
     * added by Nuny
     *
     * @param string $folder Folder name
     *
     * @return mixed Quota info or False if not supported
     */
    public function get_quota()
    public function get_quota($folder = null)
    {
        if ($this->get_capability('QUOTA') && $this->check_connection()) {
            return $this->conn->getQuota();
            return $this->conn->getQuota($folder);
        }
        return false;
    }
    /**
     * Get folder size (size of all messages in a folder)
@@ -2870,8 +3108,22 @@
     */
    public function folder_size($folder)
    {
        if (!strlen($folder)) {
            return false;
        }
        if (!$this->check_connection()) {
            return 0;
        }
        // On Cyrus we can use special folder annotation, which should be much faster
        if ($this->get_vendor() == 'cyrus') {
            $idx    = '/shared/vendor/cmu/cyrus-imapd/size';
            $result = $this->get_metadata($folder, $idx, array(), true);
            if (!empty($result) && is_numeric($result[$folder][$idx])) {
                return $result[$folder][$idx];
            }
        }
        // @TODO: could we try to use QUOTA here?
@@ -2883,7 +3135,6 @@
        return $result;
    }
    /**
     * Subscribe to a specific folder(s)
@@ -2898,7 +3149,6 @@
        return $this->change_subscription($folders, 'subscribe');
    }
    /**
     * Unsubscribe folder(s)
     *
@@ -2912,22 +3162,22 @@
        return $this->change_subscription($folders, 'unsubscribe');
    }
    /**
     * Create a new folder on the server and register it in local cache
     *
     * @param string  $folder    New folder name
     * @param boolean $subscribe True if the new folder should be subscribed
     * @param string  $type      Optional folder type (junk, trash, drafts, sent, archive)
     *
     * @return boolean True on success
     */
    public function create_folder($folder, $subscribe=false)
    public function create_folder($folder, $subscribe = false, $type = null)
    {
        if (!$this->check_connection()) {
            return false;
        }
        $result = $this->conn->createFolder($folder);
        $result = $this->conn->createFolder($folder, $type ? array("\\" . ucfirst($type)) : null);
        // try to subscribe it
        if ($result) {
@@ -2941,7 +3191,6 @@
        return $result;
    }
    /**
     * Set a new name to an existing folder
@@ -3002,7 +3251,6 @@
        return $result;
    }
    /**
     * Remove folder from server
     *
@@ -3050,23 +3298,87 @@
        return $result;
    }
    /**
     * Create all folders specified as default
     * Detect special folder associations stored in storage backend
     */
    public function create_default_folders()
    public function get_special_folders($forced = false)
    {
        // create default folders if they do not exist
        foreach ($this->default_folders as $folder) {
            if (!$this->folder_exists($folder)) {
                $this->create_folder($folder, true);
            }
            else if (!$this->folder_exists($folder, true)) {
                $this->subscribe($folder);
        $result = parent::get_special_folders();
        if (isset($this->icache['special-use'])) {
            return array_merge($result, $this->icache['special-use']);
        }
        if (!$forced || !$this->get_capability('SPECIAL-USE')) {
            return $result;
        }
        if (!$this->check_connection()) {
            return $result;
        }
        $types   = array_map(function($value) { return "\\" . ucfirst($value); }, rcube_storage::$folder_types);
        $special = array();
        // request \Subscribed flag in LIST response as performance improvement for folder_exists()
        $folders = $this->conn->listMailboxes('', '*', array('SUBSCRIBED'), array('SPECIAL-USE'));
        if (!empty($folders)) {
            foreach ($folders as $folder) {
                if ($flags = $this->conn->data['LIST'][$folder]) {
                    foreach ($types as $type) {
                        if (in_array($type, $flags)) {
                            $type           = strtolower(substr($type, 1));
                            $special[$type] = $folder;
                        }
                    }
                }
            }
        }
        $this->icache['special-use'] = $special;
        unset($this->icache['special-folders']);
        return array_merge($result, $special);
    }
    /**
     * Set special folder associations stored in storage backend
     */
    public function set_special_folders($specials)
    {
        if (!$this->get_capability('SPECIAL-USE') || !$this->get_capability('METADATA')) {
            return false;
        }
        if (!$this->check_connection()) {
            return false;
        }
        $folders = $this->get_special_folders(true);
        $old     = (array) $this->icache['special-use'];
        foreach ($specials as $type => $folder) {
            if (in_array($type, rcube_storage::$folder_types)) {
                $old_folder = $old[$type];
                if ($old_folder !== $folder) {
                    // unset old-folder metadata
                    if ($old_folder !== null) {
                        $this->delete_metadata($old_folder, array('/private/specialuse'));
                    }
                    // set new folder metadata
                    if ($folder) {
                        $this->set_metadata($folder, array('/private/specialuse' => "\\" . ucfirst($type)));
                    }
                }
            }
        }
        $this->icache['special-use'] = $specials;
        unset($this->icache['special-folders']);
        return true;
    }
    /**
     * Checks if folder exists and is subscribed
@@ -3076,13 +3388,13 @@
     *
     * @return boolean TRUE or FALSE
     */
    public function folder_exists($folder, $subscription=false)
    public function folder_exists($folder, $subscription = false)
    {
        if ($folder == 'INBOX') {
            return true;
        }
        $key  = $subscription ? 'subscribed' : 'existing';
        $key = $subscription ? 'subscribed' : 'existing';
        if (is_array($this->icache[$key]) && in_array($folder, $this->icache[$key])) {
            return true;
@@ -3093,10 +3405,24 @@
        }
        if ($subscription) {
            $a_folders = $this->conn->listSubscribed('', $folder);
            // It's possible we already called LIST command, check LIST data
            if (!empty($this->conn->data['LIST']) && !empty($this->conn->data['LIST'][$folder])
                && in_array_nocase('\\Subscribed', $this->conn->data['LIST'][$folder])
            ) {
                $a_folders = array($folder);
            }
            else {
                $a_folders = $this->conn->listSubscribed('', $folder);
            }
        }
        else {
            $a_folders = $this->conn->listMailboxes('', $folder);
            // It's possible we already called LIST command, check LIST data
            if (!empty($this->conn->data['LIST']) && isset($this->conn->data['LIST'][$folder])) {
                $a_folders = array($folder);
            }
            else {
                $a_folders = $this->conn->listMailboxes('', $folder);
            }
        }
        if (is_array($a_folders) && in_array($folder, $a_folders)) {
@@ -3106,7 +3432,6 @@
        return false;
    }
    /**
     * Returns the namespace where the folder is in
@@ -3137,7 +3462,6 @@
        return 'personal';
    }
    /**
     * Modify folder name according to namespace.
@@ -3177,7 +3501,6 @@
        return $folder;
    }
    /**
     * Gets folder attributes from LIST response, e.g. \Noselect, \Noinferiors
     *
@@ -3209,7 +3532,6 @@
        return is_array($opts) ? $opts : array();
    }
    /**
     * Gets connection (and current folder) data: UIDVALIDITY, EXISTS, RECENT,
@@ -3249,7 +3571,6 @@
        return $data;
    }
    /**
     * Returns extended information about the folder
@@ -3307,7 +3628,7 @@
        $options['name']       = $folder;
        $options['attributes'] = $this->folder_attributes($folder, true);
        $options['namespace']  = $this->folder_namespace($folder);
        $options['special']    = in_array($folder, $this->default_folders);
        $options['special']    = $this->is_special_folder($folder);
        // Set 'noselect' flag
        if (is_array($options['attributes'])) {
@@ -3346,7 +3667,6 @@
        return $options;
    }
    /**
     * Synchronizes messages cache.
     *
@@ -3358,7 +3678,6 @@
            $mcache->synchronize($folder);
        }
    }
    /**
     * Get message header names for rcube_imap_generic::fetchHeader(s)
@@ -3411,7 +3730,6 @@
        return $this->conn->setACL($folder, $user, $acl);
    }
    /**
     * Removes any <identifier,rights> pair for the
     * specified user from the ACL for the specified
@@ -3436,7 +3754,6 @@
        return $this->conn->deleteACL($folder, $user);
    }
    /**
     * Returns the access control list for folder (GETACL)
     *
@@ -3457,7 +3774,6 @@
        return $this->conn->getACL($folder);
    }
    /**
     * Returns information about what rights can be granted to the
@@ -3482,7 +3798,6 @@
        return $this->conn->listRights($folder, $user);
    }
    /**
     * Returns the set of rights that the current user has to
     * folder (MYRIGHTS)
@@ -3504,7 +3819,6 @@
        return $this->conn->myRights($folder);
    }
    /**
     * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
@@ -3539,7 +3853,6 @@
        return false;
    }
    /**
     * Unsets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
     *
@@ -3573,34 +3886,36 @@
        return false;
    }
    /**
     * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
     *
     * @param string $folder  Folder name (empty for server metadata)
     * @param array  $entries Entries
     * @param array  $options Command options (with MAXSIZE and DEPTH keys)
     * @param string  $folder   Folder name (empty for server metadata)
     * @param array   $entries  Entries
     * @param array   $options  Command options (with MAXSIZE and DEPTH keys)
     * @param bool    $force    Disables cache use
     *
     * @return array Metadata entry-value hash array on success, NULL on error
     * @since 0.5-beta
     */
    public function get_metadata($folder, $entries, $options=array())
    public function get_metadata($folder, $entries, $options = array(), $force = false)
    {
        $entries = (array)$entries;
        $entries = (array) $entries;
        // create cache key
        // @TODO: this is the simplest solution, but we do the same with folders list
        //        maybe we should store data per-entry and merge on request
        sort($options);
        sort($entries);
        $cache_key = 'mailboxes.metadata.' . $folder;
        $cache_key .= '.' . md5(serialize($options).serialize($entries));
        if (!$force) {
            // create cache key
            // @TODO: this is the simplest solution, but we do the same with folders list
            //        maybe we should store data per-entry and merge on request
            sort($options);
            sort($entries);
            $cache_key = 'mailboxes.metadata.' . $folder;
            $cache_key .= '.' . md5(serialize($options).serialize($entries));
        // get cached data
        $cached_data = $this->get_cache($cache_key);
            // get cached data
            $cached_data = $this->get_cache($cache_key);
        if (is_array($cached_data)) {
            return $cached_data;
            if (is_array($cached_data)) {
                return $cached_data;
            }
        }
        if (!$this->check_connection()) {
@@ -3624,20 +3939,28 @@
            // @TODO: Honor MAXSIZE and DEPTH options
            foreach ($queries as $attrib => $entry) {
                if ($result = $this->conn->getAnnotation($folder, $entry, $attrib)) {
                    $res = array_merge_recursive($res, $result);
                $result = $this->conn->getAnnotation($folder, $entry, $attrib);
                // an error, invalidate any previous getAnnotation() results
                if (!is_array($result)) {
                    return null;
                }
                else {
                    foreach ($result as $fldr => $data) {
                        $res[$fldr] = array_merge((array) $res[$fldr], $data);
                    }
                }
            }
        }
        if (isset($res)) {
            $this->update_cache($cache_key, $res);
            if (!$force) {
                $this->update_cache($cache_key, $res);
            }
            return $res;
        }
        return null;
    }
    /**
     * Converts the METADATA extension entry name into the correct
@@ -3657,7 +3980,6 @@
        }
        // @TODO: log error
        return null;
    }
@@ -3691,7 +4013,7 @@
    {
        if ($this->caching && !$this->cache) {
            $rcube = rcube::get_instance();
            $ttl = $rcube->config->get('imap_cache_ttl', '10d');
            $ttl   = $rcube->config->get('imap_cache_ttl', '10d');
            $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl);
        }
@@ -3739,24 +4061,6 @@
        }
    }
    /**
     * Delete outdated cache entries
     */
    public function expunge_cache()
    {
        if ($this->mcache) {
            $ttl = rcube::get_instance()->config->get('messages_cache_ttl', '10d');
            $this->mcache->expunge($ttl);
        }
/*
        // this cache is expunged by rcube class
        if ($this->cache) {
            $this->cache->expunge();
        }
*/
    }
    /* --------------------------------
     *   message caching methods
@@ -3765,12 +4069,17 @@
    /**
     * Enable or disable messages caching
     *
     * @param boolean $set Flag
     * @param boolean $set  Flag
     * @param int     $mode Cache mode
     */
    public function set_messages_caching($set)
    public function set_messages_caching($set, $mode = null)
    {
        if ($set) {
            $this->messages_caching = true;
            if ($mode && ($cache = $this->get_mcache_engine())) {
                $cache->set_mode($mode);
            }
        }
        else {
            if ($this->mcache) {
@@ -3781,7 +4090,6 @@
        }
    }
    /**
     * Getter for messages cache object
     */
@@ -3790,26 +4098,35 @@
        if ($this->messages_caching && !$this->mcache) {
            $rcube = rcube::get_instance();
            if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) {
                $ttl       = $rcube->config->get('messages_cache_ttl', '10d');
                $threshold = $rcube->config->get('messages_cache_threshold', 50);
                $this->mcache = new rcube_imap_cache(
                    $dbh, $this, $userid, $this->options['skip_deleted']);
                    $dbh, $this, $userid, $this->options['skip_deleted'], $ttl, $threshold);
            }
        }
        return $this->mcache;
    }
    /**
     * Clears the messages cache.
     *
     * @param string $folder Folder name
     * @param array  $uids    Optional message UIDs to remove from cache
     * @param array  $uids   Optional message UIDs to remove from cache
     */
    protected function clear_message_cache($folder = null, $uids = null)
    {
        if ($mcache = $this->get_mcache_engine()) {
            $mcache->clear($folder, $uids);
        }
    }
    /**
     * Delete outdated cache entries
     */
    function cache_gc()
    {
        rcube_imap_cache::gc();
    }
@@ -3833,74 +4150,95 @@
        }
    }
    /**
     * Sort folders first by default folders and then in alphabethical order
     *
     * @param array $a_folders Folders list
     * @param array $a_folders    Folders list
     * @param bool  $skip_default Skip default folders handling
     *
     * @return array Sorted list
     */
    protected function sort_folder_list($a_folders)
    public function sort_folder_list($a_folders, $skip_default = false)
    {
        $a_out = $a_defaults = $folders = array();
        $specials  = array_merge(array('INBOX'), array_values($this->get_special_folders()));
        $folders   = array();
        $delimiter = $this->get_hierarchy_delimiter();
        // find default folders and skip folders starting with '.'
        // convert names to UTF-8
        foreach ($a_folders as $folder) {
            if ($folder[0] == '.') {
                continue;
            }
            if (($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) {
                $a_defaults[$p] = $folder;
            }
            else {
                $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP');
            }
            // for better performance skip encoding conversion
            // if the string does not look like UTF7-IMAP
            $folders[$folder] = strpos($folder, '&') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP');
        }
        // sort folders and place defaults on the top
        asort($folders, SORT_LOCALE_STRING);
        ksort($a_defaults);
        $folders = array_merge($a_defaults, array_keys($folders));
        // sort folders
        // asort($folders, SORT_LOCALE_STRING) is not properly sorting case sensitive names
        uasort($folders, array($this, 'sort_folder_comparator'));
        // finally we must rebuild the list to move
        // subfolders of default folders to their place...
        // ...also do this for the rest of folders because
        // asort() is not properly sorting case sensitive names
        while (list($key, $folder) = each($folders)) {
            // set the type of folder name variable (#1485527)
            $a_out[] = (string) $folder;
            unset($folders[$key]);
            $this->rsort($folder, $delimiter, $folders, $a_out);
        $folders = array_keys($folders);
        if ($skip_default) {
            return $folders;
        }
        return $a_out;
        // force the type of folder name variable (#1485527)
        $folders  = array_map('strval', $folders);
        $out      = array();
        // finally we must put special folders on top and rebuild the list
        // to move their subfolders where they belong...
        $specials = array_unique(array_intersect($specials, $folders));
        $folders  = array_merge($specials, array_diff($folders, $specials));
        $this->sort_folder_specials(null, $folders, $specials, $out);
        return $out;
    }
    /**
     * Recursive method for sorting folders
     * Recursive function to put subfolders of special folders in place
     */
    protected function rsort($folder, $delimiter, &$list, &$out)
    protected function sort_folder_specials($folder, &$list, &$specials, &$out)
    {
        while (list($key, $name) = each($list)) {
            if (strpos($name, $folder.$delimiter) === 0) {
                // set the type of folder name variable (#1485527)
                $out[] = (string) $name;
            if ($folder === null || strpos($name, $folder.$this->delimiter) === 0) {
                $out[] = $name;
                unset($list[$key]);
                $this->rsort($name, $delimiter, $list, $out);
                if (!empty($specials) && ($found = array_search($name, $specials)) !== false) {
                    unset($specials[$found]);
                    $this->sort_folder_specials($name, $list, $specials, $out);
                }
            }
        }
        reset($list);
    }
    /**
     * Callback for uasort() that implements correct
     * locale-aware case-sensitive sorting
     */
    protected function sort_folder_comparator($str1, $str2)
    {
        $path1 = explode($this->delimiter, $str1);
        $path2 = explode($this->delimiter, $str2);
        foreach ($path1 as $idx => $folder1) {
            $folder2 = $path2[$idx];
            if ($folder1 === $folder2) {
                continue;
            }
            return strcoll($folder1, $folder2);
        }
    }
    /**
     * Find UID of the specified message sequence ID
     *
     * @param int    $id       Message (sequence) ID
     * @param string $folder   Folder name
     * @param int    $id     Message (sequence) ID
     * @param string $folder Folder name
     *
     * @return int Message UID
     */
@@ -3910,43 +4248,28 @@
            $folder = $this->folder;
        }
        if ($uid = array_search($id, (array)$this->uid_id_map[$folder])) {
            return $uid;
        }
        if (!$this->check_connection()) {
            return null;
        }
        $uid = $this->conn->ID2UID($folder, $id);
        $this->uid_id_map[$folder][$uid] = $id;
        return $uid;
        return $this->conn->ID2UID($folder, $id);
    }
    /**
     * Subscribe/unsubscribe a list of folders and update local cache
     */
    protected function change_subscription($folders, $mode)
    {
        $updated = false;
        $updated = 0;
        $folders = (array) $folders;
        if (!empty($folders)) {
            if (!$this->check_connection()) {
                return false;
            }
            foreach ((array)$folders as $i => $folder) {
                $folders[$i] = $folder;
                if ($mode == 'subscribe') {
                    $updated = $this->conn->subscribe($folder);
                }
                else if ($mode == 'unsubscribe') {
                    $updated = $this->conn->unsubscribe($folder);
                }
            foreach ($folders as $folder) {
                $updated += (int) $this->conn->{$mode}($folder);
            }
        }
@@ -3955,9 +4278,8 @@
            $this->clear_cache('mailboxes', true);
        }
        return $updated;
        return $updated == count($folders) ? true : false;
    }
    /**
     * Increde/decrese messagecount for a specific folder
@@ -3989,7 +4311,6 @@
        return true;
    }
    /**
     * Remove messagecount of a specific folder from cache
     */
@@ -4007,7 +4328,6 @@
            $this->update_cache('messagecount', $a_folder_cache);
        }
    }
    /**
     * Converts date string/object into IMAP date/time format
@@ -4031,159 +4351,11 @@
        return $date->format('d-M-Y H:i:s O');
    }
    /**
     * This is our own debug handler for the IMAP connection
     * @access public
     */
    public function debug_handler(&$imap, $message)
    {
        rcube::write_log('imap', $message);
    }
    /**
     * Deprecated methods (to be removed)
     */
    public function decode_address_list($input, $max = null, $decode = true, $fallback = null)
    {
        return rcube_mime::decode_address_list($input, $max, $decode, $fallback);
    }
    public function decode_header($input, $fallback = null)
    {
        return rcube_mime::decode_mime_string((string)$input, $fallback);
    }
    public static function decode_mime_string($input, $fallback = null)
    {
        return rcube_mime::decode_mime_string($input, $fallback);
    }
    public function mime_decode($input, $encoding = '7bit')
    {
        return rcube_mime::decode($input, $encoding);
    }
    public static function explode_header_string($separator, $str, $remove_comments = false)
    {
        return rcube_mime::explode_header_string($separator, $str, $remove_comments);
    }
    public function select_mailbox($mailbox)
    {
        // do nothing
    }
    public function set_mailbox($folder)
    {
        $this->set_folder($folder);
    }
    public function get_mailbox_name()
    {
        return $this->get_folder();
    }
    public function list_headers($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
    {
        return $this->list_messages($folder, $page, $sort_field, $sort_order, $slice);
    }
    public function get_headers($uid, $folder = null, $force = false)
    {
        return $this->get_message_headers($uid, $folder, $force);
    }
    public function mailbox_status($folder = null)
    {
        return $this->folder_status($folder);
    }
    public function message_index($folder = '', $sort_field = NULL, $sort_order = NULL)
    {
        return $this->index($folder, $sort_field, $sort_order);
    }
    public function message_index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
    {
        return $this->index_direct($folder, $sort_field, $sort_order, $skip_cache);
    }
    public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
    {
        return $this->list_folders_subscribed($root, $name, $filter, $rights, $skip_sort);
    }
    public function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
    {
        return $this->list_folders($root, $name, $filter, $rights, $skip_sort);
    }
    public function get_mailbox_size($folder)
    {
        return $this->folder_size($folder);
    }
    public function create_mailbox($folder, $subscribe=false)
    {
        return $this->create_folder($folder, $subscribe);
    }
    public function rename_mailbox($folder, $new_name)
    {
        return $this->rename_folder($folder, $new_name);
    }
    function delete_mailbox($folder)
    {
        return $this->delete_folder($folder);
    }
    function clear_mailbox($folder = null)
    {
        return $this->clear_folder($folder);
    }
    public function mailbox_exists($folder, $subscription=false)
    {
        return $this->folder_exists($folder, $subscription);
    }
    public function mailbox_namespace($folder)
    {
        return $this->folder_namespace($folder);
    }
    public function mod_mailbox($folder, $mode = 'out')
    {
        return $this->mod_folder($folder, $mode);
    }
    public function mailbox_attributes($folder, $force=false)
    {
        return $this->folder_attributes($folder, $force);
    }
    public function mailbox_data($folder)
    {
        return $this->folder_data($folder);
    }
    public function mailbox_info($folder)
    {
        return $this->folder_info($folder);
    }
    public function mailbox_sync($folder)
    {
        return $this->folder_sync($folder);
    }
    public function expunge($folder='', $clear_cache=true)
    {
        return $this->expunge_folder($folder, $clear_cache);
    }
}