Changeset 3429 in subversion


Ignore:
Timestamp:
Mar 26, 2010 5:03:22 PM (3 years ago)
Author:
alec
Message:
  • Fix bugs on unexpected IMAP connection close (#1486190, #1486270)
  • Iloha's imap.inc rewritten into rcube_imap_generic class
  • rcube_imap code re-formatting
Location:
trunk/roundcubemail
Files:
1 added
1 deleted
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/roundcubemail/CHANGELOG

    r3425 r3429  
    22=========================== 
    33 
     4- Fix bugs on unexpected IMAP connection close (#1486190, #1486270) 
     5- Iloha's imap.inc rewritten into rcube_imap_generic class 
    46- Added contact groups in address book (not finished yet) 
    57- Added PageUp/PageDown/Home/End keys support on lists (#1486430) 
  • trunk/roundcubemail/installer/check.php

    r3242 r3429  
    22<?php 
    33 
    4 $required_php_exts = array('PCRE' => 'pcre', 'DOM' => 'dom', 
    5     'Session' => 'session', 'XML' => 'xml', 'JSON' => 'json'); 
    6  
    7 $optional_php_exts = array('FileInfo' => 'fileinfo', 'Libiconv' => 'iconv', 
    8     'Multibyte' => 'mbstring', 'OpenSSL' => 'openssl', 'Mcrypt' => 'mcrypt', 
    9 ); 
    10  
    11 $required_libs = array('PEAR' => 'PEAR.php', 'MDB2' => 'MDB2.php', 
    12     'Net_SMTP' => 'Net/SMTP.php', 'Mail_mime' => 'Mail/mime.php', 
    13     'iilConnection' => 'lib/imap.inc'); 
    14  
    15 $supported_dbs = array('MySQL' => 'mysql', 'MySQLi' => 'mysqli', 
    16     'PostgreSQL' => 'pgsql', 'SQLite (v2)' => 'sqlite'); 
    17  
    18 $ini_checks = array('file_uploads' => 1, 'session.auto_start' => 0, 
    19     'zend.ze1_compatibility_mode' => 0, 'mbstring.func_overload' => 0, 
    20     'suhosin.session.encrypt' => 0); 
    21  
    22 $optional_checks = array('date.timezone' => '-NOTEMPTY-'); 
     4$required_php_exts = array( 
     5    'PCRE'      => 'pcre', 
     6    'DOM'       => 'dom', 
     7    'Session'   => 'session', 
     8    'XML'       => 'xml', 
     9    'JSON'      => 'json' 
     10); 
     11 
     12$optional_php_exts = array( 
     13    'FileInfo'  => 'fileinfo', 
     14    'Libiconv'  => 'iconv', 
     15    'Multibyte' => 'mbstring', 
     16    'OpenSSL'   => 'openssl', 
     17    'Mcrypt'    => 'mcrypt', 
     18); 
     19 
     20$required_libs = array( 
     21    'PEAR'      => 'PEAR.php', 
     22    'MDB2'      => 'MDB2.php', 
     23    'Net_SMTP'  => 'Net/SMTP.php', 
     24    'Mail_mime' => 'Mail/mime.php', 
     25); 
     26 
     27$supported_dbs = array( 
     28    'MySQL'         => 'mysql', 
     29    'MySQLi'        => 'mysqli', 
     30    'PostgreSQL'    => 'pgsql', 
     31    'SQLite (v2)'   => 'sqlite', 
     32); 
     33 
     34$ini_checks = array( 
     35    'file_uploads'                  => 1, 
     36    'session.auto_start'            => 0, 
     37    'zend.ze1_compatibility_mode'   => 0, 
     38    'mbstring.func_overload'        => 0, 
     39    'suhosin.session.encrypt'       => 0, 
     40); 
     41 
     42$optional_checks = array( 
     43    'date.timezone' => '-NOTEMPTY-', 
     44); 
    2345 
    2446$source_urls = array( 
  • trunk/roundcubemail/program/include/rcmail.php

    r3425 r3429  
    457457  public function imap_connect() 
    458458  { 
    459     $conn = false; 
    460  
    461459    if (!$this->imap) 
    462460      $this->imap_init(); 
    463461     
    464     if ($_SESSION['imap_host'] && !$this->imap->conn) { 
    465       if (!($conn = $this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl']))) { 
     462    if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) { 
     463      if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) { 
    466464        if ($this->output) 
    467465          $this->output->show_message($this->imap->error_code == -1 ? 'imaperror' : 'sessionerror', 'error'); 
    468466      } 
    469  
    470       $this->set_imap_prop(); 
    471     } 
    472  
    473     return $conn; 
     467      else { 
     468        $this->set_imap_prop(); 
     469        return $this->imap->conn; 
     470      } 
     471    } 
     472 
     473    return false; 
    474474  } 
    475475 
     
    958958  public function shutdown() 
    959959  { 
    960     if (is_object($this->imap)) { 
     960    if (is_object($this->imap)) 
    961961      $this->imap->close(); 
    962       $this->imap->write_cache(); 
    963     } 
    964962 
    965963    if (is_object($this->smtp)) 
  • trunk/roundcubemail/program/include/rcube_imap.php

    r3419 r3429  
    1010 |                                                                       | 
    1111 | PURPOSE:                                                              | 
    12  |   IMAP wrapper that implements the Iloha IMAP Library (IIL)           | 
    13  |   See http://ilohamail.org/ for details                               | 
     12 |   IMAP Engine                                                         | 
    1413 |                                                                       | 
    1514 +-----------------------------------------------------------------------+ 
    1615 | Author: Thomas Bruederli <roundcube@gmail.com>                        | 
     16 | Author: Aleksander Machniak <alec@alec.pl>                            | 
    1717 +-----------------------------------------------------------------------+ 
    1818 
     
    2222 
    2323 
    24 /* 
    25  * Obtain classes from the Iloha IMAP library 
    26  */ 
    27 require_once('lib/imap.inc'); 
    2824require_once('lib/mime.inc'); 
    2925require_once('lib/tnef_decoder.inc'); 
     
    3329 * Interface class for accessing an IMAP server 
    3430 * 
    35  * This is a wrapper that implements the Iloha IMAP Library (IIL) 
    36  * 
    3731 * @package    Mail 
    3832 * @author     Thomas Bruederli <roundcube@gmail.com> 
    39  * @version    1.5 
    40  * @link       http://ilohamail.org 
     33 * @version    1.6 
    4134 */ 
    4235class rcube_imap 
    4336{ 
    44   public $debug_level = 1; 
    45   public $error_code = 0; 
    46   public $skip_deleted = false; 
    47   public $root_dir = ''; 
    48   public $page_size = 10; 
    49   public $list_page = 1; 
    50   public $delimiter = NULL; 
    51   public $threading = false; 
    52   public $fetch_add_headers = ''; 
    53   public $conn; 
    54  
    55   private $db; 
    56   private $root_ns = ''; 
    57   private $mailbox = 'INBOX'; 
    58   private $sort_field = ''; 
    59   private $sort_order = 'DESC'; 
    60   private $caching_enabled = false; 
    61   private $default_charset = 'ISO-8859-1'; 
    62   private $struct_charset = NULL; 
    63   private $default_folders = array('INBOX'); 
    64   private $default_folders_lc = array('inbox'); 
    65   private $icache = array(); 
    66   private $cache = array(); 
    67   private $cache_keys = array();   
    68   private $cache_changes = array(); 
    69   private $uid_id_map = array(); 
    70   private $msg_headers = array(); 
    71   public  $search_set = NULL; 
    72   public  $search_string = ''; 
    73   private $search_charset = ''; 
    74   private $search_sort_field = ''; 
    75   private $search_threads = false; 
    76   private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 
    77   private $options = array('auth_method' => 'check'); 
    78   private $host, $user, $pass, $port, $ssl; 
    79  
    80  
    81   /** 
    82    * Object constructor 
    83    * 
    84    * @param object DB Database connection 
    85    */ 
    86   function __construct($db_conn) 
    87     { 
    88     $this->db = $db_conn; 
    89     } 
    90  
    91  
    92   /** 
    93    * Connect to an IMAP server 
    94    * 
    95    * @param  string   Host to connect 
    96    * @param  string   Username for IMAP account 
    97    * @param  string   Password for IMAP account 
    98    * @param  number   Port to connect to 
    99    * @param  string   SSL schema (either ssl or tls) or null if plain connection 
    100    * @return boolean  TRUE on success, FALSE on failure 
    101    * @access public 
    102    */ 
    103   function connect($host, $user, $pass, $port=143, $use_ssl=null) 
    104     { 
    105     global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE; 
    106      
    107     // check for Open-SSL support in PHP build 
    108     if ($use_ssl && extension_loaded('openssl')) 
    109       $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; 
    110     else if ($use_ssl) { 
    111       raise_error(array('code' => 403, 'type' => 'imap', 
    112         'file' => __FILE__, 'line' => __LINE__, 
    113         'message' => "Open SSL not available"), true, false); 
    114       $port = 143; 
    115     } 
    116  
    117     $ICL_PORT = $port; 
    118     $IMAP_USE_INTERNAL_DATE = false; 
    119  
    120     $attempt = 0; 
    121     do { 
    122       $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); 
    123       if (!empty($data['pass'])) 
    124         $pass = $data['pass']; 
    125  
    126       $this->conn = iil_Connect($data['host'], $data['user'], $pass, $this->options); 
    127     } while(!$this->conn && $data['retry']); 
    128  
    129     $this->host = $data['host']; 
    130     $this->user = $data['user']; 
    131     $this->pass = $pass; 
    132     $this->port = $port; 
    133     $this->ssl = $use_ssl; 
    134      
    135     // print trace messages 
    136     if ($this->conn && ($this->debug_level & 8)) 
    137       console($this->conn->message); 
    138      
    139     // write error log 
    140     else if (!$this->conn && $GLOBALS['iil_error']) 
    141       { 
    142       $this->error_code = $GLOBALS['iil_errornum']; 
    143       raise_error(array('code' => 403, 'type' => 'imap', 
    144         'file' => __FILE__, 'line' => __LINE__, 
    145         'message' => $GLOBALS['iil_error']), true, false); 
    146       } 
    147  
    148     // get server properties 
    149     if ($this->conn) 
    150       { 
    151       if (!empty($this->conn->rootdir)) 
    152         $this->set_rootdir($this->conn->rootdir); 
    153       if (empty($this->delimiter)) 
    154         $this->get_hierarchy_delimiter(); 
    155       } 
    156  
    157     return $this->conn ? true : false; 
    158     } 
    159  
    160  
    161   /** 
    162    * Close IMAP connection 
    163    * Usually done on script shutdown 
    164    * 
    165    * @access public 
    166    */ 
    167   function close() 
     37    public $debug_level = 1; 
     38    public $error_code = 0; 
     39    public $skip_deleted = false; 
     40    public $root_dir = ''; 
     41    public $page_size = 10; 
     42    public $list_page = 1; 
     43    public $delimiter = NULL; 
     44    public $threading = false; 
     45    public $fetch_add_headers = ''; 
     46    public $conn; // rcube_imap_generic object 
     47 
     48    private $db; 
     49    private $root_ns = ''; 
     50    private $mailbox = 'INBOX'; 
     51    private $sort_field = ''; 
     52    private $sort_order = 'DESC'; 
     53    private $caching_enabled = false; 
     54    private $default_charset = 'ISO-8859-1'; 
     55    private $struct_charset = NULL; 
     56    private $default_folders = array('INBOX'); 
     57    private $icache = array(); 
     58    private $cache = array(); 
     59    private $cache_keys = array();   
     60    private $cache_changes = array(); 
     61    private $uid_id_map = array(); 
     62    private $msg_headers = array(); 
     63    public  $search_set = NULL; 
     64    public  $search_string = ''; 
     65    private $search_charset = ''; 
     66    private $search_sort_field = ''; 
     67    private $search_threads = false; 
     68    private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 
     69    private $options = array('auth_method' => 'check'); 
     70    private $host, $user, $pass, $port, $ssl; 
     71 
     72 
     73    /** 
     74     * Object constructor 
     75     * 
     76     * @param object DB Database connection 
     77     */ 
     78    function __construct($db_conn) 
     79    { 
     80        $this->db = $db_conn; 
     81        $this->conn = new rcube_imap_generic(); 
     82    } 
     83 
     84 
     85    /** 
     86     * Connect to an IMAP server 
     87     * 
     88     * @param  string   Host to connect 
     89     * @param  string   Username for IMAP account 
     90     * @param  string   Password for IMAP account 
     91     * @param  number   Port to connect to 
     92     * @param  string   SSL schema (either ssl or tls) or null if plain connection 
     93     * @return boolean  TRUE on success, FALSE on failure 
     94     * @access public 
     95     */ 
     96    function connect($host, $user, $pass, $port=143, $use_ssl=null) 
     97    { 
     98        // check for Open-SSL support in PHP build 
     99        if ($use_ssl && extension_loaded('openssl')) 
     100            $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; 
     101        else if ($use_ssl) { 
     102            raise_error(array('code' => 403, 'type' => 'imap', 
     103                'file' => __FILE__, 'line' => __LINE__, 
     104                'message' => "OpenSSL not available"), true, false); 
     105            $port = 143; 
     106        } 
     107 
     108        $this->options['port'] = $port; 
     109 
     110        $attempt = 0; 
     111        do { 
     112            $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', 
     113                array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); 
     114             
     115            if (!empty($data['pass'])) 
     116                $pass = $data['pass']; 
     117 
     118            $this->conn->connect($data['host'], $data['user'], $pass, $this->options); 
     119        } while(!$this->conn->connected() && $data['retry']); 
     120 
     121        $this->host = $data['host']; 
     122        $this->user = $data['user']; 
     123        $this->pass = $pass; 
     124        $this->port = $port; 
     125        $this->ssl  = $use_ssl; 
     126 
     127        // print trace messages 
     128        if ($this->conn->connected()) { 
     129            if ($this->conn->message && ($this->debug_level & 8)) { 
     130                console($this->conn->message); 
     131            } 
     132 
     133            // get server properties 
     134            if (!empty($this->conn->rootdir)) 
     135                $this->set_rootdir($this->conn->rootdir); 
     136            if (empty($this->delimiter)) 
     137                    $this->get_hierarchy_delimiter(); 
     138        } 
     139        // write error log 
     140        else if ($this->conn->error) { 
     141            $this->error_code = $this->conn->errornum; 
     142            raise_error(array('code' => 403, 'type' => 'imap', 
     143                'file' => __FILE__, 'line' => __LINE__, 
     144                'message' => $this->conn->error), true, false); 
     145        } 
     146 
     147        return $this->conn ? true : false; 
     148    } 
     149 
     150 
     151    /** 
     152     * Close IMAP connection 
     153     * Usually done on script shutdown 
     154     * 
     155     * @access public 
     156     */ 
     157    function close() 
    168158    {     
    169     if ($this->conn) 
    170       iil_Close($this->conn); 
    171     } 
    172  
    173  
    174   /** 
    175    * Close IMAP connection and re-connect 
    176    * This is used to avoid some strange socket errors when talking to Courier IMAP 
    177    * 
    178    * @access public 
    179    */ 
    180   function reconnect() 
    181     { 
    182     $this->close(); 
    183     $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); 
    184      
    185     // issue SELECT command to restore connection status 
    186     if ($this->mailbox) 
    187       iil_C_Select($this->conn, $this->mailbox); 
    188     } 
    189  
    190   /** 
    191    * Set options to be used in iil_Connect() 
    192    */ 
    193   function set_options($opt) 
    194   { 
    195     $this->options = array_merge($this->options, (array)$opt); 
    196   } 
    197  
    198   /** 
    199    * Set a root folder for the IMAP connection. 
    200    * 
    201    * Only folders within this root folder will be displayed 
    202    * and all folder paths will be translated using this folder name 
    203    * 
    204    * @param  string   Root folder 
    205    * @access public 
    206    */ 
    207   function set_rootdir($root) 
    208     { 
    209     if (preg_match('/[.\/]$/', $root)) //(substr($root, -1, 1)==='/') 
    210       $root = substr($root, 0, -1); 
    211  
    212     $this->root_dir = $root; 
    213     $this->options['rootdir'] = $root; 
    214      
    215     if (empty($this->delimiter)) 
    216       $this->get_hierarchy_delimiter(); 
    217     } 
    218  
    219  
    220   /** 
    221    * Set default message charset 
    222    * 
    223    * This will be used for message decoding if a charset specification is not available 
    224    * 
    225    * @param  string   Charset string 
    226    * @access public 
    227    */ 
    228   function set_charset($cs) 
    229     { 
    230     $this->default_charset = $cs; 
    231     } 
    232  
    233  
    234   /** 
    235    * This list of folders will be listed above all other folders 
    236    * 
    237    * @param  array  Indexed list of folder names 
    238    * @access public 
    239    */ 
    240   function set_default_mailboxes($arr) 
    241     { 
    242     if (is_array($arr)) 
    243       { 
    244       $this->default_folders = $arr; 
    245  
    246       // add inbox if not included 
    247       if (!in_array('INBOX', $this->default_folders)) 
    248         array_unshift($this->default_folders, 'INBOX'); 
    249       } 
    250     } 
    251  
    252  
    253   /** 
    254    * Set internal mailbox reference. 
    255    * 
    256    * All operations will be perfomed on this mailbox/folder 
    257    * 
    258    * @param  string  Mailbox/Folder name 
    259    * @access public 
    260    */ 
    261   function set_mailbox($new_mbox) 
    262     { 
    263     $mailbox = $this->mod_mailbox($new_mbox); 
    264  
    265     if ($this->mailbox == $mailbox) 
    266       return; 
    267  
    268     $this->mailbox = $mailbox; 
    269  
    270     // clear messagecount cache for this mailbox 
    271     $this->_clear_messagecount($mailbox); 
    272     } 
    273  
    274  
    275   /** 
    276    * Set internal list page 
    277    * 
    278    * @param  number  Page number to list 
    279    * @access public 
    280    */ 
    281   function set_page($page) 
    282     { 
    283     $this->list_page = (int)$page; 
    284     } 
    285  
    286  
    287   /** 
    288    * Set internal page size 
    289    * 
    290    * @param  number  Number of messages to display on one page 
    291    * @access public 
    292    */ 
    293   function set_pagesize($size) 
    294     { 
    295     $this->page_size = (int)$size; 
    296     } 
    297      
    298  
    299   /** 
    300    * Save a set of message ids for future message listing methods 
    301    * 
    302    * @param  string  IMAP Search query 
    303    * @param  array   List of message ids or NULL if empty 
    304    * @param  string  Charset of search string 
    305    * @param  string  Sorting field 
    306    */ 
    307   function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false) 
    308     { 
    309     if (is_array($str) && $msgs == null) 
    310       list($str, $msgs, $charset, $sort_field, $threads) = $str; 
    311     if ($msgs != null && !is_array($msgs)) 
    312       $msgs = explode(',', $msgs); 
    313  
    314     $this->search_string = $str; 
    315     $this->search_set = $msgs; 
    316     $this->search_charset = $charset; 
    317     $this->search_sort_field = $sort_field; 
    318     $this->search_threads = $threads; 
    319     } 
    320  
    321  
    322   /** 
    323    * Return the saved search set as hash array 
    324    * @return array Search set 
    325    */ 
    326   function get_search_set() 
    327     { 
    328     return array($this->search_string, 
    329         $this->search_set, 
    330         $this->search_charset, 
    331         $this->search_sort_field, 
    332         $this->search_threads, 
    333         ); 
    334     } 
    335  
    336  
    337   /** 
    338    * Returns the currently used mailbox name 
    339    * 
    340    * @return  string Name of the mailbox/folder 
    341    * @access  public 
    342    */ 
    343   function get_mailbox_name() 
    344     { 
    345     return $this->conn ? $this->mod_mailbox($this->mailbox, 'out') : ''; 
    346     } 
    347  
    348  
    349   /** 
    350    * Returns the IMAP server's capability 
    351    * 
    352    * @param   string  Capability name 
    353    * @return  mixed   Capability value or TRUE if supported, FALSE if not 
    354    * @access  public 
    355    */ 
    356   function get_capability($cap) 
    357     { 
    358     return iil_C_GetCapability($this->conn, strtoupper($cap)); 
    359     } 
    360  
    361  
    362   /** 
    363    * Sets threading flag to the best supported THREAD algorithm 
    364    * 
    365    * @param  boolean  TRUE to enable and FALSE 
    366    * @return string   Algorithm or false if THREAD is not supported 
    367    * @access public 
    368    */ 
    369   function set_threading($enable=false) 
    370     { 
    371     $this->threading = false; 
    372      
    373     if ($enable) { 
    374       if ($this->get_capability('THREAD=REFS')) 
    375         $this->threading = 'REFS'; 
    376       else if ($this->get_capability('THREAD=REFERENCES')) 
    377         $this->threading = 'REFERENCES'; 
    378       else if ($this->get_capability('THREAD=ORDEREDSUBJECT')) 
    379         $this->threading = 'ORDEREDSUBJECT'; 
    380       } 
     159        if ($this->conn && $this->conn->connected()) 
     160            $this->conn->close(); 
     161        $this->write_cache(); 
     162    } 
     163 
     164 
     165    /** 
     166     * Close IMAP connection and re-connect 
     167     * This is used to avoid some strange socket errors when talking to Courier IMAP 
     168     * 
     169     * @access public 
     170     */ 
     171    function reconnect() 
     172    { 
     173        $this->close(); 
     174        $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); 
     175     
     176        // issue SELECT command to restore connection status 
     177        if ($this->mailbox) 
     178            $this->conn->select($this->mailbox); 
     179    } 
     180 
     181    /** 
     182     * Set options to be used in rcube_imap_generic::connect() 
     183     */ 
     184    function set_options($opt) 
     185    { 
     186        $this->options = array_merge($this->options, (array)$opt); 
     187    } 
     188 
     189    /** 
     190     * Set a root folder for the IMAP connection. 
     191     * 
     192     * Only folders within this root folder will be displayed 
     193     * and all folder paths will be translated using this folder name 
     194     * 
     195     * @param  string   Root folder 
     196     * @access public 
     197     */ 
     198    function set_rootdir($root) 
     199    { 
     200        if (preg_match('/[.\/]$/', $root)) //(substr($root, -1, 1)==='/') 
     201            $root = substr($root, 0, -1); 
     202 
     203        $this->root_dir = $root; 
     204        $this->options['rootdir'] = $root; 
     205     
     206        if (empty($this->delimiter)) 
     207            $this->get_hierarchy_delimiter(); 
     208    } 
     209 
     210 
     211    /** 
     212     * Set default message charset 
     213     * 
     214     * This will be used for message decoding if a charset specification is not available 
     215     * 
     216     * @param  string   Charset string 
     217     * @access public 
     218     */ 
     219    function set_charset($cs) 
     220    { 
     221        $this->default_charset = $cs; 
     222    } 
     223 
     224 
     225    /** 
     226     * This list of folders will be listed above all other folders 
     227     * 
     228     * @param  array  Indexed list of folder names 
     229     * @access public 
     230     */ 
     231    function set_default_mailboxes($arr) 
     232    { 
     233        if (is_array($arr)) { 
     234            $this->default_folders = $arr; 
     235 
     236            // add inbox if not included 
     237            if (!in_array('INBOX', $this->default_folders)) 
     238                array_unshift($this->default_folders, 'INBOX'); 
     239        } 
     240    } 
     241 
     242 
     243    /** 
     244     * Set internal mailbox reference. 
     245     * 
     246     * All operations will be perfomed on this mailbox/folder 
     247     * 
     248     * @param  string  Mailbox/Folder name 
     249     * @access public 
     250     */ 
     251    function set_mailbox($new_mbox) 
     252    { 
     253        $mailbox = $this->mod_mailbox($new_mbox); 
     254 
     255        if ($this->mailbox == $mailbox) 
     256            return; 
     257 
     258        $this->mailbox = $mailbox; 
     259 
     260        // clear messagecount cache for this mailbox 
     261        $this->_clear_messagecount($mailbox); 
     262    } 
     263 
     264 
     265    /** 
     266     * Set internal list page 
     267     * 
     268     * @param  number  Page number to list 
     269     * @access public 
     270     */ 
     271    function set_page($page) 
     272    { 
     273        $this->list_page = (int)$page; 
     274    } 
     275 
     276 
     277    /** 
     278     * Set internal page size 
     279     * 
     280     * @param  number  Number of messages to display on one page 
     281     * @access public 
     282     */ 
     283    function set_pagesize($size) 
     284    { 
     285        $this->page_size = (int)$size; 
     286    } 
     287     
     288 
     289    /** 
     290     * Save a set of message ids for future message listing methods 
     291     * 
     292     * @param  string  IMAP Search query 
     293     * @param  array   List of message ids or NULL if empty 
     294     * @param  string  Charset of search string 
     295     * @param  string  Sorting field 
     296     */ 
     297    function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false) 
     298    { 
     299        if (is_array($str) && $msgs == null) 
     300            list($str, $msgs, $charset, $sort_field, $threads) = $str; 
     301        if ($msgs != null && !is_array($msgs)) 
     302            $msgs = explode(',', $msgs); 
     303 
     304        $this->search_string     = $str; 
     305        $this->search_set        = $msgs; 
     306        $this->search_charset    = $charset; 
     307        $this->search_sort_field = $sort_field; 
     308        $this->search_threads    = $threads; 
     309    } 
     310 
     311 
     312    /** 
     313     * Return the saved search set as hash array 
     314     * @return array Search set 
     315     */ 
     316    function get_search_set() 
     317    { 
     318        return array($this->search_string, 
     319                $this->search_set, 
     320                $this->search_charset, 
     321                $this->search_sort_field, 
     322                $this->search_threads, 
     323            ); 
     324    } 
     325 
     326 
     327    /** 
     328     * Returns the currently used mailbox name 
     329     * 
     330     * @return  string Name of the mailbox/folder 
     331     * @access  public 
     332     */ 
     333    function get_mailbox_name() 
     334    { 
     335        return $this->conn->connected() ? $this->mod_mailbox($this->mailbox, 'out') : ''; 
     336    } 
     337 
     338 
     339    /** 
     340     * Returns the IMAP server's capability 
     341     * 
     342     * @param   string  Capability name 
     343     * @return  mixed   Capability value or TRUE if supported, FALSE if not 
     344     * @access  public 
     345     */ 
     346    function get_capability($cap) 
     347    { 
     348        return $this->conn->getCapability(strtoupper($cap)); 
     349    } 
     350 
     351 
     352    /** 
     353     * Sets threading flag to the best supported THREAD algorithm 
     354     * 
     355     * @param  boolean  TRUE to enable and FALSE 
     356     * @return string   Algorithm or false if THREAD is not supported 
     357     * @access public 
     358     */ 
     359    function set_threading($enable=false) 
     360    { 
     361        $this->threading = false; 
     362     
     363        if ($enable) { 
     364            if ($this->get_capability('THREAD=REFS')) 
     365                $this->threading = 'REFS'; 
     366            else if ($this->get_capability('THREAD=REFERENCES')) 
     367                $this->threading = 'REFERENCES'; 
     368            else if ($this->get_capability('THREAD=ORDEREDSUBJECT')) 
     369                $this->threading = 'ORDEREDSUBJECT'; 
     370        } 
     371 
     372        return $this->threading; 
     373    } 
     374 
     375 
     376    /** 
     377     * Checks the PERMANENTFLAGS capability of the current mailbox 
     378     * and returns true if the given flag is supported by the IMAP server 
     379     * 
     380     * @param   string  Permanentflag name 
     381     * @return  mixed   True if this flag is supported 
     382     * @access  public 
     383     */ 
     384    function check_permflag($flag) 
     385    { 
     386        $flag = strtoupper($flag); 
     387        $imap_flag = $this->conn->flags[$flag]; 
     388        return (in_array_nocase($imap_flag, $this->conn->permanentflags)); 
     389    } 
     390 
     391 
     392    /** 
     393     * Returns the delimiter that is used by the IMAP server for folder separation 
     394     * 
     395     * @return  string  Delimiter string 
     396     * @access  public 
     397     */ 
     398    function get_hierarchy_delimiter() 
     399    { 
     400        if ($this->conn && empty($this->delimiter)) 
     401            $this->delimiter = $this->conn->getHierarchyDelimiter(); 
     402 
     403        if (empty($this->delimiter)) 
     404            $this->delimiter = '/'; 
     405 
     406        return $this->delimiter; 
     407    } 
     408 
     409 
     410    /** 
     411     * Get message count for a specific mailbox 
     412     * 
     413     * @param   string   Mailbox/folder name 
     414     * @param   string   Mode for count [ALL|THREADS|UNSEEN|RECENT] 
     415     * @param   boolean  Force reading from server and update cache 
     416     * @return  int      Number of messages 
     417     * @access  public 
     418     */ 
     419    function messagecount($mbox_name='', $mode='ALL', $force=false) 
     420    { 
     421        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     422        return $this->_messagecount($mailbox, $mode, $force); 
     423    } 
     424 
     425 
     426    /** 
     427     * Private method for getting nr of messages 
     428     * 
     429     * @access  private 
     430     * @see     rcube_imap::messagecount() 
     431     */ 
     432    private function _messagecount($mailbox='', $mode='ALL', $force=false) 
     433    { 
     434        $mode = strtoupper($mode); 
     435 
     436        if (empty($mailbox)) 
     437            $mailbox = $this->mailbox; 
     438 
     439        // count search set 
     440        if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) { 
     441            if ($this->search_threads) 
     442                return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']); 
     443            else 
     444                return count((array)$this->search_set); 
     445        } 
     446     
     447        $a_mailbox_cache = $this->get_cache('messagecount'); 
     448     
     449        // return cached value 
     450        if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode])) 
     451            return $a_mailbox_cache[$mailbox][$mode]; 
     452 
     453        if (!is_array($a_mailbox_cache[$mailbox])) 
     454            $a_mailbox_cache[$mailbox] = array(); 
     455 
     456        if ($mode == 'THREADS') { 
     457            $count = $this->_threadcount($mailbox, $msg_count); 
     458            $_SESSION['maxuid'][$mailbox] = $msg_count ? $this->_id2uid($msg_count) : 0; 
     459        } 
     460        // RECENT count is fetched a bit different 
     461        else if ($mode == 'RECENT') { 
     462            $count = $this->conn->checkForRecent($mailbox); 
     463        } 
     464        // use SEARCH for message counting 
     465        else if ($this->skip_deleted) { 
     466            $search_str = "ALL UNDELETED"; 
     467 
     468            // get message count and store in cache 
     469            if ($mode == 'UNSEEN') 
     470                $search_str .= " UNSEEN"; 
     471 
     472            // get message count using SEARCH 
     473            // not very performant but more precise (using UNDELETED) 
     474            // disable THREADS for this request 
     475            $threads = $this->threading; 
     476            $this->threading = false; 
     477            $index = $this->_search_index($mailbox, $search_str); 
     478            $this->threading = $threads; 
    381479       
    382     return $this->threading; 
    383     } 
    384  
    385  
    386   /** 
    387    * Checks the PERMANENTFLAGS capability of the current mailbox 
    388    * and returns true if the given flag is supported by the IMAP server 
    389    * 
    390    * @param   string  Permanentflag name 
    391    * @return  mixed   True if this flag is supported 
    392    * @access  public 
    393    */ 
    394   function check_permflag($flag) 
    395     { 
    396     $flag = strtoupper($flag); 
    397     $imap_flag = $GLOBALS['IMAP_FLAGS'][$flag]; 
    398     return (in_array_nocase($imap_flag, $this->conn->permanentflags)); 
    399     } 
    400  
    401  
    402   /** 
    403    * Returns the delimiter that is used by the IMAP server for folder separation 
    404    * 
    405    * @return  string  Delimiter string 
    406    * @access  public 
    407    */ 
    408   function get_hierarchy_delimiter() 
    409     { 
    410     if ($this->conn && empty($this->delimiter)) 
    411       $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn); 
    412  
    413     if (empty($this->delimiter)) 
    414       $this->delimiter = '/'; 
    415  
    416     return $this->delimiter; 
    417     } 
    418  
    419  
    420   /** 
    421    * Public method for mailbox listing. 
    422    * 
    423    * Converts mailbox name with root dir first 
    424    * 
    425    * @param   string  Optional root folder 
    426    * @param   string  Optional filter for mailbox listing 
    427    * @return  array   List of mailboxes/folders 
    428    * @access  public 
    429    */ 
    430   function list_mailboxes($root='', $filter='*') 
    431     { 
    432     $a_out = array(); 
    433     $a_mboxes = $this->_list_mailboxes($root, $filter); 
    434  
    435     foreach ($a_mboxes as $mbox_row) 
    436       { 
    437       $name = $this->mod_mailbox($mbox_row, 'out'); 
    438       if (strlen($name)) 
    439         $a_out[] = $name; 
    440       } 
    441  
    442     // INBOX should always be available 
    443     if (!in_array('INBOX', $a_out)) 
    444       array_unshift($a_out, 'INBOX'); 
    445  
    446     // sort mailboxes 
    447     $a_out = $this->_sort_mailbox_list($a_out); 
    448  
    449     return $a_out; 
    450     } 
    451  
    452  
    453   /** 
    454    * Private method for mailbox listing 
    455    * 
    456    * @return  array   List of mailboxes/folders 
    457    * @see     rcube_imap::list_mailboxes() 
    458    * @access  private 
    459    */ 
    460   private function _list_mailboxes($root='', $filter='*') 
    461     { 
    462     $a_defaults = $a_out = array(); 
    463      
    464     // get cached folder list     
    465     $a_mboxes = $this->get_cache('mailboxes'); 
    466     if (is_array($a_mboxes)) 
    467       return $a_mboxes; 
    468  
    469     // Give plugins a chance to provide a list of mailboxes 
    470     $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes',array('root'=>$root,'filter'=>$filter)); 
    471     if (isset($data['folders'])) { 
    472         $a_folders = $data['folders']; 
    473     } 
    474     else { 
    475         // retrieve list of folders from IMAP server 
    476         $a_folders = iil_C_ListSubscribed($this->conn, $this->mod_mailbox($root), $filter); 
    477     } 
    478      
    479     if (!is_array($a_folders) || !sizeof($a_folders)) 
    480       $a_folders = array(); 
    481  
    482     // write mailboxlist to cache 
    483     $this->update_cache('mailboxes', $a_folders); 
    484      
    485     return $a_folders; 
    486     } 
    487  
    488  
    489   /** 
    490    * Get message count for a specific mailbox 
    491    * 
    492    * @param   string   Mailbox/folder name 
    493    * @param   string   Mode for count [ALL|THREADS|UNSEEN|RECENT] 
    494    * @param   boolean  Force reading from server and update cache 
    495    * @return  int      Number of messages 
    496    * @access  public 
    497    */ 
    498   function messagecount($mbox_name='', $mode='ALL', $force=false) 
    499     { 
    500     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    501     return $this->_messagecount($mailbox, $mode, $force); 
    502     } 
    503  
    504  
    505   /** 
    506    * Private method for getting nr of messages 
    507    * 
    508    * @access  private 
    509    * @see     rcube_imap::messagecount() 
    510    */ 
    511   private function _messagecount($mailbox='', $mode='ALL', $force=false) 
    512     { 
    513     $mode = strtoupper($mode); 
    514  
    515     if (empty($mailbox)) 
    516       $mailbox = $this->mailbox; 
    517  
    518     // count search set 
    519     if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) { 
    520       if ($this->search_threads) 
    521         return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']); 
    522       else 
    523         return count((array)$this->search_set); 
    524       } 
    525      
    526     $a_mailbox_cache = $this->get_cache('messagecount'); 
    527      
    528     // return cached value 
    529     if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode])) 
    530       return $a_mailbox_cache[$mailbox][$mode]; 
    531  
    532     if (!is_array($a_mailbox_cache[$mailbox])) 
    533       $a_mailbox_cache[$mailbox] = array(); 
    534  
    535     if ($mode == 'THREADS') { 
    536       $count = $this->_threadcount($mailbox, $msg_count); 
    537       $_SESSION['maxuid'][$mailbox] = $msg_count ? $this->_id2uid($msg_count) : 0; 
    538       } 
    539     // RECENT count is fetched a bit different 
    540     else if ($mode == 'RECENT') { 
    541        $count = iil_C_CheckForRecent($this->conn, $mailbox); 
    542       } 
    543     // use SEARCH for message counting 
    544     else if ($this->skip_deleted) { 
    545       $search_str = "ALL UNDELETED"; 
    546  
    547       // get message count and store in cache 
    548       if ($mode == 'UNSEEN') 
    549         $search_str .= " UNSEEN"; 
    550  
    551       // get message count using SEARCH 
    552       // not very performant but more precise (using UNDELETED) 
    553       // disable THREADS for this request 
    554       $threads = $this->threading; 
    555       $this->threading = false; 
    556       $index = $this->_search_index($mailbox, $search_str); 
    557       $this->threading = $threads; 
    558        
    559       $count = is_array($index) ? count($index) : 0; 
    560  
    561       if ($mode == 'ALL') 
    562         $_SESSION['maxuid'][$mailbox] = $index ? $this->_id2uid(max($index)) : 0; 
    563       } 
    564     else { 
    565       if ($mode == 'UNSEEN') 
    566         $count = iil_C_CountUnseen($this->conn, $mailbox); 
    567       else { 
    568         $count = iil_C_CountMessages($this->conn, $mailbox); 
    569         $_SESSION['maxuid'][$mailbox] = $count ? $this->_id2uid($count) : 0; 
    570       } 
    571     } 
    572  
    573     $a_mailbox_cache[$mailbox][$mode] = (int)$count; 
    574  
    575     // write back to cache 
    576     $this->update_cache('messagecount', $a_mailbox_cache); 
    577  
    578     return (int)$count; 
    579     } 
    580  
    581  
    582   /** 
    583    * Private method for getting nr of threads 
    584    * 
    585    * @access  private 
    586    * @see     rcube_imap::messagecount() 
    587    */ 
    588   private function _threadcount($mailbox, &$msg_count) 
    589     { 
    590     if (!empty($this->icache['threads'])) 
    591       return count($this->icache['threads']['tree']); 
    592      
    593     list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 
    594      
    595     $msg_count = count($msg_depth); 
     480            $count = is_array($index) ? count($index) : 0; 
     481 
     482            if ($mode == 'ALL') 
     483                $_SESSION['maxuid'][$mailbox] = $index ? $this->_id2uid(max($index)) : 0; 
     484        } 
     485        else { 
     486            if ($mode == 'UNSEEN') 
     487                $count = $this->conn->countUnseen($mailbox); 
     488            else { 
     489                $count = $this->conn->countMessages($mailbox); 
     490                $_SESSION['maxuid'][$mailbox] = $count ? $this->_id2uid($count) : 0; 
     491            } 
     492        } 
     493 
     494        $a_mailbox_cache[$mailbox][$mode] = (int)$count; 
     495 
     496        // write back to cache 
     497        $this->update_cache('messagecount', $a_mailbox_cache); 
     498 
     499        return (int)$count; 
     500    } 
     501 
     502 
     503    /** 
     504     * Private method for getting nr of threads 
     505     * 
     506     * @access  private 
     507     * @see     rcube_imap::messagecount() 
     508     */ 
     509    private function _threadcount($mailbox, &$msg_count) 
     510    { 
     511        if (!empty($this->icache['threads'])) 
     512            return count($this->icache['threads']['tree']); 
     513     
     514        list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 
     515     
     516        $msg_count = count($msg_depth); 
    596517 
    597518//    $this->update_thread_cache($mailbox, $thread_tree, $msg_depth, $has_children); 
    598     return count($thread_tree);   
    599     } 
    600  
    601  
    602   /** 
    603    * Public method for listing headers 
    604    * convert mailbox name with root dir first 
    605    * 
    606    * @param   string   Mailbox/folder name 
    607    * @param   int      Current page to list 
    608    * @param   string   Header field to sort by 
    609    * @param   string   Sort order [ASC|DESC] 
    610    * @param   boolean  Number of slice items to extract from result array 
    611    * @return  array    Indexed array with message header objects 
    612    * @access  public    
    613    */ 
    614   function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    615     { 
    616     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    617     return $this->_list_headers($mailbox, $page, $sort_field, $sort_order, false, $slice); 
    618     } 
    619  
    620  
    621   /** 
    622    * Private method for listing message headers 
    623    * 
    624    * @access  private 
    625    * @see     rcube_imap::list_headers 
    626    */ 
    627   private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=false, $slice=0) 
    628     { 
    629     if (!strlen($mailbox)) 
    630       return array(); 
    631  
    632     // use saved message set 
    633     if ($this->search_string && $mailbox == $this->mailbox) 
    634       return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
    635  
    636     if ($this->threading) 
    637       return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice); 
    638  
    639     $this->_set_sort_order($sort_field, $sort_order); 
    640  
    641     $page = $page ? $page : $this->list_page; 
    642     $cache_key = $mailbox.'.msg'; 
    643     $cache_status = $this->check_cache_status($mailbox, $cache_key); 
    644  
    645     // cache is OK, we can get all messages from local cache 
    646     if ($cache_status>0) 
    647       { 
    648       $start_msg = ($page-1) * $this->page_size; 
    649       $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order); 
    650       $result = array_values($a_msg_headers); 
    651       if ($slice) 
    652         $result = array_slice($result, -$slice, $slice); 
    653       return $result; 
    654       } 
    655     // cache is dirty, sync it 
    656     else if ($this->caching_enabled && $cache_status==-1 && !$recursive) 
    657       { 
    658       $this->sync_header_index($mailbox); 
    659       return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); 
    660       } 
    661  
    662     // retrieve headers from IMAP 
    663     $a_msg_headers = array(); 
    664  
    665     // use message index sort as default sorting (for better performance) 
    666     if (!$this->sort_field) 
    667       { 
    668         if ($this->skip_deleted) { 
    669           // @TODO: this could be cached 
    670           if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { 
     519        return count($thread_tree);   
     520    } 
     521 
     522 
     523    /** 
     524     * Public method for listing headers 
     525     * convert mailbox name with root dir first 
     526     * 
     527     * @param   string   Mailbox/folder name 
     528     * @param   int      Current page to list 
     529     * @param   string   Header field to sort by 
     530     * @param   string   Sort order [ASC|DESC] 
     531     * @param   boolean  Number of slice items to extract from result array 
     532     * @return  array    Indexed array with message header objects 
     533     * @access  public    
     534     */ 
     535    function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
     536    { 
     537        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     538        return $this->_list_headers($mailbox, $page, $sort_field, $sort_order, false, $slice); 
     539    } 
     540 
     541 
     542    /** 
     543     * Private method for listing message headers 
     544     * 
     545     * @access  private 
     546     * @see     rcube_imap::list_headers 
     547     */ 
     548    private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=false, $slice=0) 
     549    { 
     550        if (!strlen($mailbox)) 
     551            return array(); 
     552 
     553        // use saved message set 
     554        if ($this->search_string && $mailbox == $this->mailbox) 
     555            return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
     556 
     557        if ($this->threading) 
     558            return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice); 
     559 
     560        $this->_set_sort_order($sort_field, $sort_order); 
     561 
     562        $page         = $page ? $page : $this->list_page; 
     563        $cache_key    = $mailbox.'.msg'; 
     564        $cache_status = $this->check_cache_status($mailbox, $cache_key); 
     565 
     566        // cache is OK, we can get all messages from local cache 
     567        if ($cache_status>0) { 
     568            $start_msg = ($page-1) * $this->page_size; 
     569            $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, 
     570                $start_msg+$this->page_size, $this->sort_field, $this->sort_order); 
     571            $result = array_values($a_msg_headers); 
     572            if ($slice) 
     573                $result = array_slice($result, -$slice, $slice); 
     574            return $result; 
     575        } 
     576        // cache is dirty, sync it 
     577        else if ($this->caching_enabled && $cache_status==-1 && !$recursive) { 
     578            $this->sync_header_index($mailbox); 
     579            return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); 
     580        } 
     581 
     582        // retrieve headers from IMAP 
     583        $a_msg_headers = array(); 
     584 
     585        // use message index sort as default sorting (for better performance) 
     586        if (!$this->sort_field) { 
     587            if ($this->skip_deleted) { 
     588                // @TODO: this could be cached 
     589                if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { 
     590                    $max = max($msg_index); 
     591                    list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
     592                    $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     593                } 
     594            } 
     595            else if ($max = $this->conn->countMessages($mailbox)) { 
     596                list($begin, $end) = $this->_get_message_range($max, $page); 
     597                $msg_index = range($begin+1, $end); 
     598            } 
     599            else 
     600                $msg_index = array(); 
     601 
     602            if ($slice) 
     603                $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
     604 
     605            // fetch reqested headers from server 
     606            if ($msg_index) 
     607                $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); 
     608        } 
     609        // use SORT command 
     610        else if ($this->get_capability('SORT')) { 
     611            if ($msg_index = $this->conn->sort($mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { 
     612                list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
     613                $max = max($msg_index); 
     614                $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     615 
     616                if ($slice) 
     617                    $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
     618 
     619                // fetch reqested headers from server 
     620                $this->_fetch_headers($mailbox, join(',', $msg_index), $a_msg_headers, $cache_key); 
     621            } 
     622        } 
     623        // fetch specified header for all messages and sort 
     624        else if ($a_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { 
     625            asort($a_index); // ASC 
     626            $msg_index = array_keys($a_index); 
    671627            $max = max($msg_index); 
    672628            list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    673629            $msg_index = array_slice($msg_index, $begin, $end-$begin); 
    674           } 
    675         } 
    676         else if ($max = iil_C_CountMessages($this->conn, $mailbox)) { 
    677           list($begin, $end) = $this->_get_message_range($max, $page); 
    678           $msg_index = range($begin+1, $end); 
    679         } 
    680         else 
    681           $msg_index = array(); 
    682  
    683         if ($slice) 
    684           $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    685  
    686         // fetch reqested headers from server 
    687         if ($msg_index) 
    688           $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); 
    689       } 
    690     // use SORT command 
    691     else if ($this->get_capability('SORT')) 
    692       { 
    693       if ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { 
    694         list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    695         $max = max($msg_index); 
    696         $msg_index = array_slice($msg_index, $begin, $end-$begin); 
    697  
    698         if ($slice) 
    699           $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    700  
    701         // fetch reqested headers from server 
    702         $this->_fetch_headers($mailbox, join(',', $msg_index), $a_msg_headers, $cache_key); 
    703         } 
    704       } 
    705     // fetch specified header for all messages and sort 
    706     else if ($a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) 
    707       { 
    708       asort($a_index); // ASC 
    709       $msg_index = array_keys($a_index); 
    710       $max = max($msg_index); 
    711       list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    712       $msg_index = array_slice($msg_index, $begin, $end-$begin); 
    713  
    714       if ($slice) 
    715         $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    716  
    717       // fetch reqested headers from server 
    718       $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); 
    719       } 
    720  
    721     // delete cached messages with a higher index than $max+1 
    722     // Changed $max to $max+1 to fix this bug : #1484295 
    723     $this->clear_message_cache($cache_key, $max + 1); 
    724  
    725     // kick child process to sync cache 
    726     // ... 
    727  
    728     // return empty array if no messages found 
    729     if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
    730       return array(); 
    731      
    732     // use this class for message sorting 
    733     $sorter = new rcube_header_sorter(); 
    734     $sorter->set_sequence_numbers($msg_index); 
    735     $sorter->sort_headers($a_msg_headers); 
    736  
    737     if ($this->sort_order == 'DESC') 
    738       $a_msg_headers = array_reverse($a_msg_headers);        
    739  
    740     return array_values($a_msg_headers); 
    741     } 
    742  
    743  
    744   /** 
    745    * Private method for listing message headers using threads 
    746    * 
    747    * @access  private 
    748    * @see     rcube_imap::list_headers 
    749    */ 
    750   private function _list_thread_headers($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=false, $slice=0) 
    751     { 
    752     $this->_set_sort_order($sort_field, $sort_order); 
    753  
    754     $page = $page ? $page : $this->list_page; 
     630 
     631            if ($slice) 
     632                $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
     633 
     634            // fetch reqested headers from server 
     635            $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); 
     636        } 
     637 
     638        // delete cached messages with a higher index than $max+1 
     639        // Changed $max to $max+1 to fix this bug : #1484295 
     640        $this->clear_message_cache($cache_key, $max + 1); 
     641 
     642        // kick child process to sync cache 
     643        // ... 
     644 
     645        // return empty array if no messages found 
     646        if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
     647            return array(); 
     648     
     649        // use this class for message sorting 
     650        $sorter = new rcube_header_sorter(); 
     651        $sorter->set_sequence_numbers($msg_index); 
     652        $sorter->sort_headers($a_msg_headers); 
     653 
     654        if ($this->sort_order == 'DESC') 
     655            $a_msg_headers = array_reverse($a_msg_headers);          
     656 
     657        return array_values($a_msg_headers); 
     658    } 
     659 
     660 
     661    /** 
     662     * Private method for listing message headers using threads 
     663     * 
     664     * @access  private 
     665     * @see     rcube_imap::list_headers 
     666     */ 
     667    private function _list_thread_headers($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=false, $slice=0) 
     668    { 
     669        $this->_set_sort_order($sort_field, $sort_order); 
     670 
     671        $page = $page ? $page : $this->list_page; 
    755672//    $cache_key = $mailbox.'.msg'; 
    756673//    $cache_status = $this->check_cache_status($mailbox, $cache_key); 
    757674 
    758     // get all threads (default sort order) 
    759     list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 
    760  
    761     if (empty($thread_tree)) 
    762       return array(); 
    763  
    764     $msg_index = $this->_sort_threads($mailbox, $thread_tree); 
    765  
    766     return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, 
    767       $msg_index, $page, $slice); 
    768     } 
    769  
    770  
    771   /** 
    772    * Private method for fetching threads data 
    773    * 
    774    * @param   string   Mailbox/folder name 
    775    * @return  array    Array with thread data 
    776    * @access  private 
    777    */ 
    778   private function _fetch_threads($mailbox) 
    779     { 
    780     if (empty($this->icache['threads'])) { 
    781       // get all threads 
    782       list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, 
    783         $mailbox, $this->threading, $this->skip_deleted ? 'UNDELETED' : ''); 
     675        // get all threads (default sort order) 
     676        list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 
     677 
     678        if (empty($thread_tree)) 
     679            return array(); 
     680 
     681        $msg_index = $this->_sort_threads($mailbox, $thread_tree); 
     682 
     683        return $this->_fetch_thread_headers($mailbox, 
     684            $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice); 
     685    } 
     686 
     687 
     688    /** 
     689     * Private method for fetching threads data 
     690     * 
     691     * @param   string   Mailbox/folder name 
     692     * @return  array    Array with thread data 
     693     * @access  private 
     694     */ 
     695    private function _fetch_threads($mailbox) 
     696    { 
     697        if (empty($this->icache['threads'])) { 
     698            // get all threads 
     699            list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( 
     700                $mailbox, $this->threading, $this->skip_deleted ? 'UNDELETED' : ''); 
    784701    
    785       // add to internal (fast) cache 
    786       $this->icache['threads'] = array(); 
    787       $this->icache['threads']['tree'] = $thread_tree; 
    788       $this->icache['threads']['depth'] = $msg_depth; 
    789       $this->icache['threads']['has_children'] = $has_children; 
    790       } 
    791  
    792     return array( 
    793       $this->icache['threads']['tree'], 
    794       $this->icache['threads']['depth'], 
    795       $this->icache['threads']['has_children'], 
    796       ); 
    797     } 
    798  
    799  
    800   /** 
    801    * Private method for fetching threaded messages headers 
    802    * 
    803    * @access  private 
    804    */ 
    805   private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) 
    806     { 
    807     $cache_key = $mailbox.'.msg'; 
    808     // now get IDs for current page 
    809     list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    810     $msg_index = array_slice($msg_index, $begin, $end-$begin); 
    811  
    812     if ($slice) 
    813       $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    814  
    815     if ($this->sort_order == 'DESC') 
    816       $msg_index = array_reverse($msg_index); 
    817  
    818     // flatten threads array 
    819     // @TODO: fetch children only in expanded mode 
    820     $all_ids = array(); 
    821     foreach($msg_index as $root) { 
    822       $all_ids[] = $root; 
    823       if (!empty($thread_tree[$root])) 
    824         $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 
    825       } 
    826  
    827     // fetch reqested headers from server 
    828     $this->_fetch_headers($mailbox, $all_ids, $a_msg_headers, $cache_key); 
    829  
    830     // return empty array if no messages found 
    831     if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
    832       return array(); 
    833      
    834     // use this class for message sorting 
    835     $sorter = new rcube_header_sorter(); 
    836     $sorter->set_sequence_numbers($all_ids); 
    837     $sorter->sort_headers($a_msg_headers); 
    838  
    839     // Set depth, has_children and unread_children fields in headers 
    840     $this->_set_thread_flags($a_msg_headers, $msg_depth, $has_children); 
    841  
    842     return array_values($a_msg_headers); 
    843     } 
    844  
    845  
    846   /** 
    847    * Private method for setting threaded messages flags: 
    848    * depth, has_children and unread_children 
    849    * 
    850    * @param  array   Reference to headers array indexed by message ID 
    851    * @param  array   Array of messages depth indexed by message ID 
    852    * @param  array   Array of messages children flags indexed by message ID 
    853    * @return array   Message headers array indexed by message ID 
    854    * @access private 
    855    */ 
    856   private function _set_thread_flags(&$headers, $msg_depth, $msg_children) 
    857     { 
    858     $parents = array(); 
    859  
    860     foreach ($headers as $idx => $header) { 
    861       $id = $header->id; 
    862       $depth = $msg_depth[$id]; 
    863       $parents = array_slice($parents, 0, $depth); 
    864  
    865       if (!empty($parents)) { 
    866         $headers[$idx]->parent_uid = end($parents); 
    867         if (!$header->seen) 
    868           $headers[$parents[0]]->unread_children++; 
    869         } 
    870       array_push($parents, $header->uid); 
    871  
    872       $headers[$idx]->depth = $depth; 
    873       $headers[$idx]->has_children = $msg_children[$id]; 
    874       } 
    875     } 
    876  
    877  
    878   /** 
    879    * Private method for listing a set of message headers (search results) 
    880    * 
    881    * @param   string   Mailbox/folder name 
    882    * @param   int      Current page to list 
    883    * @param   string   Header field to sort by 
    884    * @param   string   Sort order [ASC|DESC] 
    885    * @param   boolean  Number of slice items to extract from result array 
    886    * @return  array    Indexed array with message header objects 
    887    * @access  private 
    888    * @see     rcube_imap::list_header_set() 
    889    */ 
    890   private function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    891     { 
    892     if (!strlen($mailbox) || empty($this->search_set)) 
    893       return array(); 
    894  
    895     // use saved messages from searching 
    896     if ($this->threading) 
    897       return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
    898  
    899     // search set is threaded, we need a new one 
    900     if ($this->search_threads) 
    901       $this->search('', $this->search_string, $this->search_charset, $sort_field); 
    902      
    903     $msgs = $this->search_set; 
    904     $a_msg_headers = array(); 
    905     $page = $page ? $page : $this->list_page; 
    906     $start_msg = ($page-1) * $this->page_size; 
    907  
    908     $this->_set_sort_order($sort_field, $sort_order); 
    909  
    910     // quickest method (default sorting) 
    911     if (!$this->search_sort_field && !$this->sort_field) 
    912       { 
    913       if ($sort_order == 'DESC') 
    914         $msgs = array_reverse($msgs); 
    915  
    916       // get messages uids for one page 
    917       $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); 
    918  
    919       if ($slice) 
    920         $msgs = array_slice($msgs, -$slice, $slice); 
    921  
    922       // fetch headers 
    923       $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); 
    924  
    925       // I didn't found in RFC that FETCH always returns messages sorted by index 
    926       $sorter = new rcube_header_sorter(); 
    927       $sorter->set_sequence_numbers($msgs); 
    928       $sorter->sort_headers($a_msg_headers); 
    929  
    930       return array_values($a_msg_headers); 
    931       } 
    932  
    933     // sorted messages, so we can first slice array and then fetch only wanted headers 
    934     if ($this->get_capability('SORT')) // SORT searching result 
    935       { 
    936       // reset search set if sorting field has been changed 
    937       if ($this->sort_field && $this->search_sort_field != $this->sort_field) 
    938         $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
    939  
    940       // return empty array if no messages found 
    941       if (empty($msgs)) 
    942         return array(); 
    943  
    944       if ($sort_order == 'DESC') 
    945         $msgs = array_reverse($msgs); 
    946  
    947       // get messages uids for one page 
    948       $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); 
    949  
    950       if ($slice) 
    951         $msgs = array_slice($msgs, -$slice, $slice); 
    952  
    953       // fetch headers 
    954       $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); 
    955  
    956       $sorter = new rcube_header_sorter(); 
    957       $sorter->set_sequence_numbers($msgs); 
    958       $sorter->sort_headers($a_msg_headers); 
    959  
    960       return array_values($a_msg_headers); 
    961       } 
    962     else { // SEARCH result, need sorting 
    963       $cnt = count($msgs); 
    964       // 300: experimantal value for best result 
    965       if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { 
    966         // use memory less expensive (and quick) method for big result set 
    967         $a_index = $this->message_index('', $this->sort_field, $this->sort_order); 
    968         // get messages uids for one page... 
    969         $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size)); 
     702            // add to internal (fast) cache 
     703            $this->icache['threads'] = array(); 
     704            $this->icache['threads']['tree'] = $thread_tree; 
     705            $this->icache['threads']['depth'] = $msg_depth; 
     706            $this->icache['threads']['has_children'] = $has_children; 
     707        } 
     708 
     709        return array( 
     710            $this->icache['threads']['tree'], 
     711            $this->icache['threads']['depth'], 
     712            $this->icache['threads']['has_children'], 
     713        ); 
     714    } 
     715 
     716 
     717    /** 
     718     * Private method for fetching threaded messages headers 
     719     * 
     720     * @access  private 
     721     */ 
     722    private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) 
     723    { 
     724        $cache_key = $mailbox.'.msg'; 
     725        // now get IDs for current page 
     726        list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
     727        $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     728 
    970729        if ($slice) 
    971           $msgs = array_slice($msgs, -$slice, $slice); 
    972         // ...and fetch headers 
    973         $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); 
     730            $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
     731 
     732        if ($this->sort_order == 'DESC') 
     733            $msg_index = array_reverse($msg_index); 
     734 
     735        // flatten threads array 
     736        // @TODO: fetch children only in expanded mode (?) 
     737        $all_ids = array(); 
     738        foreach($msg_index as $root) { 
     739            $all_ids[] = $root; 
     740            if (!empty($thread_tree[$root])) 
     741                $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 
     742        } 
     743 
     744        // fetch reqested headers from server 
     745        $this->_fetch_headers($mailbox, $all_ids, $a_msg_headers, $cache_key); 
    974746 
    975747        // return empty array if no messages found 
    976748        if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
    977           return array(); 
    978  
     749            return array(); 
     750     
     751        // use this class for message sorting 
    979752        $sorter = new rcube_header_sorter(); 
    980         $sorter->set_sequence_numbers($msgs); 
     753        $sorter->set_sequence_numbers($all_ids); 
    981754        $sorter->sort_headers($a_msg_headers); 
    982755 
     756        // Set depth, has_children and unread_children fields in headers 
     757        $this->_set_thread_flags($a_msg_headers, $msg_depth, $has_children); 
     758 
    983759        return array_values($a_msg_headers); 
    984       } 
    985       else { 
    986         // for small result set we can fetch all messages headers 
    987         $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); 
    988      
    989         // return empty array if no messages found 
    990         if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
    991           return array(); 
    992  
    993         // if not already sorted 
    994         $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); 
    995  
    996         // only return the requested part of the set 
    997         $a_msg_headers = array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size)); 
    998         if ($slice) 
    999           $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); 
    1000  
    1001         return $a_msg_headers; 
    1002         } 
    1003       } 
    1004     } 
    1005  
    1006  
    1007   /** 
    1008    * Private method for listing a set of threaded message headers (search results) 
    1009    * 
    1010    * @param   string   Mailbox/folder name 
    1011    * @param   int      Current page to list 
    1012    * @param   string   Header field to sort by 
    1013    * @param   string   Sort order [ASC|DESC] 
    1014    * @param   boolean  Number of slice items to extract from result array 
    1015    * @return  array    Indexed array with message header objects 
    1016    * @access  private 
    1017    * @see     rcube_imap::list_header_set() 
    1018    */ 
    1019   private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    1020     { 
    1021     // update search_set if previous data was fetched with disabled threading 
    1022     if (!$this->search_threads) 
    1023       $this->search('', $this->search_string, $this->search_charset, $sort_field); 
    1024  
    1025     $thread_tree = $this->search_set['tree']; 
    1026     $msg_depth = $this->search_set['depth']; 
    1027     $has_children = $this->search_set['children']; 
    1028     $a_msg_headers = array(); 
    1029  
    1030     $page = $page ? $page : $this->list_page; 
    1031     $start_msg = ($page-1) * $this->page_size; 
    1032  
    1033     $this->_set_sort_order($sort_field, $sort_order); 
    1034  
    1035     $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); 
    1036  
    1037     return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); 
    1038     } 
    1039  
    1040  
    1041   /** 
    1042    * Helper function to get first and last index of the requested set 
    1043    * 
    1044    * @param  int     message count 
    1045    * @param  mixed   page number to show, or string 'all' 
    1046    * @return array   array with two values: first index, last index 
    1047    * @access private 
    1048    */ 
    1049   private function _get_message_range($max, $page) 
    1050     { 
    1051     $start_msg = ($page-1) * $this->page_size; 
    1052      
    1053     if ($page=='all') 
    1054       { 
    1055       $begin = 0; 
    1056       $end = $max; 
    1057       } 
    1058     else if ($this->sort_order=='DESC') 
    1059       { 
    1060       $begin = $max - $this->page_size - $start_msg; 
    1061       $end =   $max - $start_msg; 
    1062       } 
    1063     else 
    1064       { 
    1065       $begin = $start_msg; 
    1066       $end   = $start_msg + $this->page_size; 
    1067       } 
    1068  
    1069     if ($begin < 0) $begin = 0; 
    1070     if ($end < 0) $end = $max; 
    1071     if ($end > $max) $end = $max; 
    1072      
    1073     return array($begin, $end); 
    1074     } 
    1075      
    1076  
    1077   /** 
    1078    * Fetches message headers 
    1079    * Used for loop 
    1080    * 
    1081    * @param  string  Mailbox name 
    1082    * @param  string  Message index to fetch 
    1083    * @param  array   Reference to message headers array 
    1084    * @param  array   Array with cache index 
    1085    * @return int     Messages count 
    1086    * @access private 
    1087    */ 
    1088   private function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) 
    1089     { 
    1090     // fetch reqested headers from server 
    1091     $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs, false, false, $this->fetch_add_headers); 
    1092  
    1093     if (empty($a_header_index)) 
    1094       return 0; 
    1095  
    1096     // cache is incomplete 
    1097     $cache_index = $this->get_message_cache_index($cache_key); 
    1098  
    1099     foreach ($a_header_index as $i => $headers) { 
    1100       if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) { 
    1101         // prevent index duplicates 
    1102         if ($cache_index[$headers->id]) { 
    1103           $this->remove_message_cache($cache_key, $headers->id, true); 
    1104           unset($cache_index[$headers->id]); 
     760    } 
     761 
     762 
     763    /** 
     764     * Private method for setting threaded messages flags: 
     765     * depth, has_children and unread_children 
     766     * 
     767     * @param  array   Reference to headers array indexed by message ID 
     768     * @param  array   Array of messages depth indexed by message ID 
     769     * @param  array   Array of messages children flags indexed by message ID 
     770     * @return array   Message headers array indexed by message ID 
     771     * @access private 
     772     */ 
     773    private function _set_thread_flags(&$headers, $msg_depth, $msg_children) 
     774    { 
     775        $parents = array(); 
     776 
     777        foreach ($headers as $idx => $header) { 
     778            $id = $header->id; 
     779            $depth = $msg_depth[$id]; 
     780            $parents = array_slice($parents, 0, $depth); 
     781 
     782            if (!empty($parents)) { 
     783                $headers[$idx]->parent_uid = end($parents); 
     784                if (!$header->seen) 
     785                    $headers[$parents[0]]->unread_children++; 
     786            } 
     787            array_push($parents, $header->uid); 
     788 
     789            $headers[$idx]->depth = $depth; 
     790            $headers[$idx]->has_children = $msg_children[$id]; 
     791        } 
     792    } 
     793 
     794 
     795    /** 
     796     * Private method for listing a set of message headers (search results) 
     797     * 
     798     * @param   string   Mailbox/folder name 
     799     * @param   int      Current page to list 
     800     * @param   string   Header field to sort by 
     801     * @param   string   Sort order [ASC|DESC] 
     802     * @param   boolean  Number of slice items to extract from result array 
     803     * @return  array    Indexed array with message header objects 
     804     * @access  private 
     805     * @see     rcube_imap::list_header_set() 
     806     */ 
     807    private function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
     808    { 
     809        if (!strlen($mailbox) || empty($this->search_set)) 
     810            return array(); 
     811 
     812        // use saved messages from searching 
     813        if ($this->threading) 
     814            return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
     815 
     816        // search set is threaded, we need a new one 
     817        if ($this->search_threads) 
     818            $this->search('', $this->search_string, $this->search_charset, $sort_field); 
     819     
     820        $msgs = $this->search_set; 
     821        $a_msg_headers = array(); 
     822        $page = $page ? $page : $this->list_page; 
     823        $start_msg = ($page-1) * $this->page_size; 
     824 
     825        $this->_set_sort_order($sort_field, $sort_order); 
     826 
     827        // quickest method (default sorting) 
     828        if (!$this->search_sort_field && !$this->sort_field) { 
     829            if ($sort_order == 'DESC') 
     830                $msgs = array_reverse($msgs); 
     831 
     832            // get messages uids for one page 
     833            $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); 
     834 
     835            if ($slice) 
     836                $msgs = array_slice($msgs, -$slice, $slice); 
     837 
     838            // fetch headers 
     839            $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); 
     840 
     841            // I didn't found in RFC that FETCH always returns messages sorted by index 
     842            $sorter = new rcube_header_sorter(); 
     843            $sorter->set_sequence_numbers($msgs); 
     844            $sorter->sort_headers($a_msg_headers); 
     845 
     846            return array_values($a_msg_headers); 
     847        } 
     848 
     849        // sorted messages, so we can first slice array and then fetch only wanted headers 
     850        if ($this->get_capability('SORT')) { // SORT searching result 
     851            // reset search set if sorting field has been changed 
     852            if ($this->sort_field && $this->search_sort_field != $this->sort_field) 
     853                $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
     854 
     855            // return empty array if no messages found 
     856            if (empty($msgs)) 
     857                return array(); 
     858 
     859            if ($sort_order == 'DESC') 
     860                $msgs = array_reverse($msgs); 
     861 
     862            // get messages uids for one page 
     863            $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); 
     864 
     865            if ($slice) 
     866                $msgs = array_slice($msgs, -$slice, $slice); 
     867 
     868            // fetch headers 
     869            $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); 
     870 
     871            $sorter = new rcube_header_sorter(); 
     872            $sorter->set_sequence_numbers($msgs); 
     873            $sorter->sort_headers($a_msg_headers); 
     874 
     875            return array_values($a_msg_headers); 
     876        } 
     877        else { // SEARCH result, need sorting 
     878            $cnt = count($msgs); 
     879            // 300: experimantal value for best result 
     880            if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { 
     881                // use memory less expensive (and quick) method for big result set 
     882                $a_index = $this->message_index('', $this->sort_field, $this->sort_order); 
     883                // get messages uids for one page... 
     884                $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size)); 
     885                if ($slice) 
     886                    $msgs = array_slice($msgs, -$slice, $slice); 
     887                // ...and fetch headers 
     888                $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); 
     889 
     890                // return empty array if no messages found 
     891                if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
     892                    return array(); 
     893 
     894                $sorter = new rcube_header_sorter(); 
     895                $sorter->set_sequence_numbers($msgs); 
     896                $sorter->sort_headers($a_msg_headers); 
     897 
     898                return array_values($a_msg_headers); 
     899            } 
     900            else { 
     901                // for small result set we can fetch all messages headers 
     902                $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); 
     903     
     904                // return empty array if no messages found 
     905                if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
     906                    return array(); 
     907 
     908                // if not already sorted 
     909                $a_msg_headers = $this->conn->sortHeaders( 
     910                    $a_msg_headers, $this->sort_field, $this->sort_order); 
     911 
     912                // only return the requested part of the set 
     913                $a_msg_headers = array_slice(array_values($a_msg_headers), 
     914                    $start_msg, min($cnt-$start_msg, $this->page_size)); 
     915 
     916                if ($slice) 
     917                    $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); 
     918 
     919                return $a_msg_headers; 
     920            } 
     921        } 
     922    } 
     923 
     924 
     925    /** 
     926     * Private method for listing a set of threaded message headers (search results) 
     927     * 
     928     * @param   string   Mailbox/folder name 
     929     * @param   int      Current page to list 
     930     * @param   string   Header field to sort by 
     931     * @param   string   Sort order [ASC|DESC] 
     932     * @param   boolean  Number of slice items to extract from result array 
     933     * @return  array    Indexed array with message header objects 
     934     * @access  private 
     935     * @see     rcube_imap::list_header_set() 
     936     */ 
     937    private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
     938    { 
     939        // update search_set if previous data was fetched with disabled threading 
     940        if (!$this->search_threads) 
     941            $this->search('', $this->search_string, $this->search_charset, $sort_field); 
     942 
     943        $thread_tree = $this->search_set['tree']; 
     944        $msg_depth = $this->search_set['depth']; 
     945        $has_children = $this->search_set['children']; 
     946        $a_msg_headers = array(); 
     947 
     948        $page = $page ? $page : $this->list_page; 
     949        $start_msg = ($page-1) * $this->page_size; 
     950 
     951        $this->_set_sort_order($sort_field, $sort_order); 
     952 
     953        $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); 
     954 
     955        return $this->_fetch_thread_headers($mailbox, 
     956            $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); 
     957    } 
     958 
     959 
     960    /** 
     961     * Helper function to get first and last index of the requested set 
     962     * 
     963     * @param  int     message count 
     964     * @param  mixed   page number to show, or string 'all' 
     965     * @return array   array with two values: first index, last index 
     966     * @access private 
     967     */ 
     968    private function _get_message_range($max, $page) 
     969    { 
     970        $start_msg = ($page-1) * $this->page_size; 
     971     
     972        if ($page=='all') { 
     973            $begin  = 0; 
     974            $end    = $max; 
     975        } 
     976        else if ($this->sort_order=='DESC') { 
     977            $begin  = $max - $this->page_size - $start_msg; 
     978            $end    = $max - $start_msg; 
     979        } 
     980        else { 
     981            $begin  = $start_msg; 
     982            $end    = $start_msg + $this->page_size; 
     983        } 
     984 
     985        if ($begin < 0) $begin = 0; 
     986        if ($end < 0) $end = $max; 
     987        if ($end > $max) $end = $max; 
     988     
     989        return array($begin, $end); 
     990    } 
     991     
     992 
     993    /** 
     994     * Fetches message headers 
     995     * Used for loop 
     996     * 
     997     * @param  string  Mailbox name 
     998     * @param  string  Message index to fetch 
     999     * @param  array   Reference to message headers array 
     1000     * @param  array   Array with cache index 
     1001     * @return int     Messages count 
     1002     * @access private 
     1003     */ 
     1004    private function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) 
     1005    { 
     1006        // fetch reqested headers from server 
     1007        $a_header_index = $this->conn->fetchHeaders( 
     1008            $mailbox, $msgs, false, false, $this->fetch_add_headers); 
     1009 
     1010        if (empty($a_header_index)) 
     1011            return 0; 
     1012 
     1013        // cache is incomplete 
     1014        $cache_index = $this->get_message_cache_index($cache_key); 
     1015 
     1016        foreach ($a_header_index as $i => $headers) { 
     1017            if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) { 
     1018                // prevent index duplicates 
     1019                if ($cache_index[$headers->id]) { 
     1020                    $this->remove_message_cache($cache_key, $headers->id, true); 
     1021                    unset($cache_index[$headers->id]); 
     1022                } 
     1023                // add message to cache 
     1024                $this->add_message_cache($cache_key, $headers->id, $headers, NULL, 
     1025                    !in_array($headers->uid, $cache_index)); 
     1026            } 
     1027 
     1028            $a_msg_headers[$headers->uid] = $headers; 
     1029        } 
     1030 
     1031        return count($a_msg_headers); 
     1032    } 
     1033   
     1034    /** 
     1035     * Fetches IDS of pseudo recent messages. 
     1036     * 
     1037     * We compare the maximum UID to determine the number of 
     1038     * new messages because the RECENT flag is not reliable. 
     1039     * 
     1040     * @param string  Mailbox/folder name 
     1041     * @return array  List of recent message UIDs 
     1042     */ 
     1043    function recent_uids($mbox_name = null, $nofetch = false) 
     1044    { 
     1045        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     1046        $old_maxuid = intval($_SESSION['maxuid'][$mailbox]); 
     1047     
     1048        // refresh message count -> will update $_SESSION['maxuid'][$mailbox] 
     1049        $this->_messagecount($mailbox, 'ALL', true); 
     1050     
     1051        if ($_SESSION['maxuid'][$mailbox] > $old_maxuid) { 
     1052            $maxuid = max(1, $old_maxuid+1); 
     1053            return array_values((array)$this->conn->fetchHeaderIndex( 
     1054                $mailbox, "$maxuid:*", 'UID', $this->skip_deleted, true)); 
     1055        } 
     1056     
     1057        return array(); 
     1058    } 
     1059   
     1060    /** 
     1061     * Return sorted array of message IDs (not UIDs) 
     1062     * 
     1063     * @param string Mailbox to get index from 
     1064     * @param string Sort column 
     1065     * @param string Sort order [ASC, DESC] 
     1066     * @return array Indexed array with message ids 
     1067     */ 
     1068    function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) 
     1069    { 
     1070        if ($this->threading) 
     1071            return $this->thread_index($mbox_name, $sort_field, $sort_order); 
     1072 
     1073        $this->_set_sort_order($sort_field, $sort_order); 
     1074 
     1075        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     1076        $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi"; 
     1077 
     1078        // we have a saved search result, get index from there 
     1079        if (!isset($this->cache[$key]) && $this->search_string 
     1080            && !$this->search_threads && $mailbox == $this->mailbox) { 
     1081            // use message index sort as default sorting 
     1082            if (!$this->sort_field) { 
     1083                $msgs = $this->search_set; 
     1084 
     1085                if ($this->search_sort_field != 'date') 
     1086                    sort($msgs); 
     1087 
     1088                if ($this->sort_order == 'DESC') 
     1089                    $this->cache[$key] = array_reverse($msgs); 
     1090                else 
     1091                    $this->cache[$key] = $msgs; 
     1092            } 
     1093            // sort with SORT command 
     1094            else if ($this->get_capability('SORT')) { 
     1095                if ($this->sort_field && $this->search_sort_field != $this->sort_field) 
     1096                    $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
     1097 
     1098                if ($this->sort_order == 'DESC') 
     1099                    $this->cache[$key] = array_reverse($this->search_set); 
     1100                else 
     1101                    $this->cache[$key] = $this->search_set; 
     1102            } 
     1103            else { 
     1104                $a_index = $this->conn->fetchHeaderIndex($mailbox, 
     1105                        join(',', $this->search_set), $this->sort_field, $this->skip_deleted); 
     1106                 
     1107                if (is_array($a_index)) { 
     1108                    if ($this->sort_order=="ASC") 
     1109                        asort($a_index); 
     1110                    else if ($this->sort_order=="DESC") 
     1111                        arsort($a_index); 
     1112 
     1113                    $this->cache[$key] = array_keys($a_index); 
     1114                } 
     1115                else { 
     1116                    $this->cache[$key] = array(); 
     1117                } 
     1118            } 
     1119        } 
     1120 
     1121        // have stored it in RAM 
     1122        if (isset($this->cache[$key])) 
     1123            return $this->cache[$key]; 
     1124 
     1125        // check local cache 
     1126        $cache_key = $mailbox.'.msg'; 
     1127        $cache_status = $this->check_cache_status($mailbox, $cache_key); 
     1128 
     1129        // cache is OK 
     1130        if ($cache_status>0) { 
     1131            $a_index = $this->get_message_cache_index($cache_key, 
     1132                true, $this->sort_field, $this->sort_order); 
     1133            return array_keys($a_index); 
     1134        } 
     1135 
     1136        // use message index sort as default sorting 
     1137        if (!$this->sort_field) { 
     1138            if ($this->skip_deleted) { 
     1139                $a_index = $this->_search_index($mailbox, 'ALL'); 
     1140            } else if ($max = $this->_messagecount($mailbox)) { 
     1141                $a_index = range(1, $max); 
     1142            } 
     1143 
     1144            if ($this->sort_order == 'DESC') 
     1145                $a_index = array_reverse($a_index); 
     1146 
     1147            $this->cache[$key] = $a_index; 
     1148        } 
     1149        // fetch complete message index 
     1150        else if ($this->get_capability('SORT')) { 
     1151            if ($a_index = $this->conn->sort($mailbox, 
     1152                $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { 
     1153                if ($this->sort_order == 'DESC') 
     1154                    $a_index = array_reverse($a_index); 
     1155         
     1156                $this->cache[$key] = $a_index; 
     1157                } 
     1158        } 
     1159        else if ($a_index = $this->conn->fetchHeaderIndex( 
     1160            $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { 
     1161            if ($this->sort_order=="ASC") 
     1162                asort($a_index); 
     1163            else if ($this->sort_order=="DESC") 
     1164                arsort($a_index); 
     1165         
     1166            $this->cache[$key] = array_keys($a_index); 
     1167        } 
     1168 
     1169        return $this->cache[$key]; 
     1170    } 
     1171 
     1172 
     1173    /** 
     1174     * Return sorted array of threaded message IDs (not UIDs) 
     1175     * 
     1176     * @param string Mailbox to get index from 
     1177     * @param string Sort column 
     1178     * @param string Sort order [ASC, DESC] 
     1179     * @return array Indexed array with message IDs 
     1180     */ 
     1181    function thread_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) 
     1182    { 
     1183        $this->_set_sort_order($sort_field, $sort_order); 
     1184 
     1185        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     1186        $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.thi"; 
     1187 
     1188        // we have a saved search result, get index from there 
     1189        if (!isset($this->cache[$key]) && $this->search_string 
     1190            && $this->search_threads && $mailbox == $this->mailbox) { 
     1191            // use message IDs for better performance 
     1192            $ids = array_keys_recursive($this->search_set['tree']); 
     1193            $this->cache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids); 
     1194        } 
     1195 
     1196        // have stored it in RAM 
     1197        if (isset($this->cache[$key])) 
     1198            return $this->cache[$key]; 
     1199/* 
     1200        // check local cache 
     1201        $cache_key = $mailbox.'.msg'; 
     1202        $cache_status = $this->check_cache_status($mailbox, $cache_key); 
     1203 
     1204        // cache is OK 
     1205        if ($cache_status>0) { 
     1206            $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); 
     1207            return array_keys($a_index); 
     1208        } 
     1209*/ 
     1210        // get all threads (default sort order) 
     1211        list ($thread_tree) = $this->_fetch_threads($mailbox); 
     1212 
     1213        $this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree); 
     1214     
     1215        return $this->cache[$key]; 
     1216    } 
     1217 
     1218 
     1219    /** 
     1220     * Return array of threaded messages (all, not only roots) 
     1221     * 
     1222     * @param string Mailbox to get index from 
     1223     * @param array  Threaded messages array (see _fetch_threads()) 
     1224     * @param array  Message IDs if we know what we need (e.g. search result) 
     1225     *               for better performance 
     1226     * @return array Indexed array with message IDs 
     1227     * 
     1228     * @access private 
     1229     */ 
     1230    private function _flatten_threads($mailbox, $thread_tree, $ids=null) 
     1231    { 
     1232        if (empty($thread_tree)) 
     1233            return array(); 
     1234 
     1235        $msg_index = $this->_sort_threads($mailbox, $thread_tree, $ids); 
     1236 
     1237        if ($this->sort_order == 'DESC') 
     1238            $msg_index = array_reverse($msg_index); 
     1239           
     1240        // flatten threads array 
     1241        $all_ids = array(); 
     1242        foreach($msg_index as $root) { 
     1243            $all_ids[] = $root; 
     1244            if (!empty($thread_tree[$root])) 
     1245                $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 
     1246        } 
     1247 
     1248        return $all_ids; 
     1249    } 
     1250 
     1251 
     1252    /** 
     1253     * @access private 
     1254     */ 
     1255    private function sync_header_index($mailbox) 
     1256    { 
     1257        $cache_key = $mailbox.'.msg'; 
     1258        $cache_index = $this->get_message_cache_index($cache_key); 
     1259 
     1260        // fetch complete message index 
     1261        $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); 
     1262     
     1263        if ($a_message_index === false) 
     1264            return false; 
     1265         
     1266        foreach ($a_message_index as $id => $uid) { 
     1267            // message in cache at correct position 
     1268            if ($cache_index[$id] == $uid) { 
     1269                unset($cache_index[$id]); 
     1270                continue; 
     1271            } 
     1272 
     1273            // message in cache but in wrong position 
     1274            if (in_array((string)$uid, $cache_index, true)) { 
     1275                unset($cache_index[$id]); 
     1276            } 
     1277       
     1278            // other message at this position 
     1279            if (isset($cache_index[$id])) { 
     1280                $for_remove[] = $cache_index[$id]; 
     1281                unset($cache_index[$id]); 
     1282            } 
     1283         
     1284            $for_update[] = $id; 
     1285        } 
     1286 
     1287        // clear messages at wrong positions and those deleted that are still in cache_index       
     1288        if (!empty($for_remove)) 
     1289            $cache_index = array_merge($cache_index, $for_remove); 
     1290     
     1291        if (!empty($cache_index)) 
     1292            $this->remove_message_cache($cache_key, $cache_index); 
     1293 
     1294        // fetch complete headers and add to cache 
     1295        if (!empty($for_update)) { 
     1296            if ($headers = $this->conn->fetchHeader($mailbox, 
     1297                    join(',', $for_update), false, $this->fetch_add_headers)) { 
     1298                foreach ($headers as $header) { 
     1299                    $this->add_message_cache($cache_key, $header->id, $header, NULL, 
     1300                        in_array($header->uid, (array)$for_remove)); 
     1301                } 
     1302            } 
     1303        } 
     1304    } 
     1305 
     1306 
     1307    /** 
     1308     * Invoke search request to IMAP server 
     1309     * 
     1310     * @param  string  mailbox name to search in 
     1311     * @param  string  search string 
     1312     * @param  string  search string charset 
     1313     * @param  string  header field to sort by 
     1314     * @return array   search results as list of message ids 
     1315     * @access public 
     1316     */ 
     1317    function search($mbox_name='', $str=NULL, $charset=NULL, $sort_field=NULL) 
     1318    { 
     1319        if (!$str) 
     1320            return false; 
     1321     
     1322        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     1323 
     1324        $results = $this->_search_index($mailbox, $str, $charset, $sort_field); 
     1325 
     1326        // try search with US-ASCII charset (should be supported by server) 
     1327        // only if UTF-8 search is not supported 
     1328        if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII') 
     1329        { 
     1330            // convert strings to US_ASCII 
     1331            if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { 
     1332                $last = 0; $res = ''; 
     1333                foreach($matches[1] as $m) 
     1334                { 
     1335                    $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n 
     1336                    $string = substr($str, $string_offset - 1, $m[0]); 
     1337                    $string = rcube_charset_convert($string, $charset, 'US-ASCII'); 
     1338                    if (!$string) 
     1339                        continue; 
     1340                    $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); 
     1341                    $last = $m[0] + $string_offset - 1; 
     1342                } 
     1343                if ($last < strlen($str)) 
     1344                    $res .= substr($str, $last, strlen($str)-$last); 
     1345            } 
     1346            else // strings for conversion not found 
     1347                $res = $str; 
     1348 
     1349            $results = $this->search($mbox_name, $res, NULL, $sort_field); 
     1350        } 
     1351 
     1352        $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading); 
     1353 
     1354        return $results; 
     1355    } 
     1356 
     1357 
     1358    /** 
     1359     * Private search method 
     1360     * 
     1361     * @return array   search results as list of message ids 
     1362     * @access private 
     1363     * @see rcube_imap::search() 
     1364     */ 
     1365    private function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL) 
     1366    { 
     1367        $orig_criteria = $criteria; 
     1368 
     1369        if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria)) 
     1370            $criteria = 'UNDELETED '.$criteria; 
     1371 
     1372        if ($this->threading) { 
     1373            list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( 
     1374                $mailbox, $this->threading, $criteria, $charset); 
     1375 
     1376            $a_messages = array( 
     1377                'tree'  => $thread_tree, 
     1378                    'depth'     => $msg_depth, 
     1379                    'children' => $has_children 
     1380            ); 
     1381        } 
     1382        else if ($sort_field && $this->get_capability('SORT')) { 
     1383            $charset = $charset ? $charset : $this->default_charset; 
     1384            $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset); 
     1385 
     1386            if (!$a_messages) 
     1387                    return array(); 
     1388        } 
     1389        else { 
     1390            if ($orig_criteria == 'ALL') { 
     1391                $max = $this->_messagecount($mailbox); 
     1392                $a_messages = $max ? range(1, $max) : array(); 
     1393            } 
     1394            else { 
     1395                $a_messages = $this->conn->search($mailbox, 
     1396                        ($charset ? "CHARSET $charset " : '') . $criteria); 
     1397 
     1398                if (!$a_messages) 
     1399                    return array(); 
     1400 
     1401            // I didn't found that SEARCH always returns sorted IDs 
     1402            if (!$this->sort_field) 
     1403                sort($a_messages); 
     1404            } 
     1405        } 
     1406 
     1407        // update messagecount cache ? 
     1408//      $a_mailbox_cache = get_cache('messagecount'); 
     1409//      $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages); 
     1410//      $this->update_cache('messagecount', $a_mailbox_cache); 
     1411         
     1412        return $a_messages; 
     1413    } 
     1414     
     1415   
     1416    /** 
     1417     * Sort thread 
     1418     * 
     1419     * @param string Mailbox name 
     1420     * @param  array Unsorted thread tree (rcube_imap_generic::thread() result) 
     1421     * @param  array Message IDs if we know what we need (e.g. search result) 
     1422     * @return array Sorted roots IDs 
     1423     * @access private 
     1424     */ 
     1425    private function _sort_threads($mailbox, $thread_tree, $ids=NULL) 
     1426    { 
     1427        // THREAD=ORDEREDSUBJECT:       sorting by sent date of root message 
     1428        // THREAD=REFERENCES:   sorting by sent date of root message 
     1429        // THREAD=REFS:                 sorting by the most recent date in each thread 
     1430        // default sorting 
     1431        if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { 
     1432            return array_keys((array)$thread_tree); 
    11051433          } 
    1106         // add message to cache 
    1107         $this->add_message_cache($cache_key, $headers->id, $headers, NULL, 
    1108           !in_array($headers->uid, $cache_index)); 
    1109         } 
    1110  
    1111       $a_msg_headers[$headers->uid] = $headers; 
    1112       } 
    1113  
    1114     return count($a_msg_headers); 
    1115     } 
    1116    
    1117   /** 
    1118    * Fetches IDS of pseudo recent messages. 
    1119    * 
    1120    * We compare the maximum UID to determine the number of 
    1121    * new messages because the RECENT flag is not reliable. 
    1122    * 
    1123    * @param string  Mailbox/folder name 
    1124    * @return array  List of recent message UIDs 
    1125    */ 
    1126   function recent_uids($mbox_name = null, $nofetch = false) 
    1127   { 
    1128     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    1129     $old_maxuid = intval($_SESSION['maxuid'][$mailbox]); 
    1130      
    1131     // refresh message count -> will update $_SESSION['maxuid'][$mailbox] 
    1132     $this->_messagecount($mailbox, 'ALL', true); 
    1133      
    1134     if ($_SESSION['maxuid'][$mailbox] > $old_maxuid) { 
    1135       $maxuid = max(1, $old_maxuid+1); 
    1136       return array_values((array)iil_C_FetchHeaderIndex($this->conn, $mailbox, "$maxuid:*", 'UID', $this->skip_deleted, true)); 
    1137     } 
    1138      
    1139     return array(); 
    1140   } 
    1141    
    1142   /** 
    1143    * Return sorted array of message IDs (not UIDs) 
    1144    * 
    1145    * @param string Mailbox to get index from 
    1146    * @param string Sort column 
    1147    * @param string Sort order [ASC, DESC] 
    1148    * @return array Indexed array with message ids 
    1149    */ 
    1150   function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) 
    1151     { 
    1152     if ($this->threading) 
    1153       return $this->thread_index($mbox_name, $sort_field, $sort_order); 
    1154  
    1155     $this->_set_sort_order($sort_field, $sort_order); 
    1156  
    1157     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    1158     $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi"; 
    1159  
    1160     // we have a saved search result, get index from there 
    1161     if (!isset($this->cache[$key]) && $this->search_string 
    1162       && !$this->search_threads && $mailbox == $this->mailbox) 
    1163     { 
    1164       $this->cache[$key] = array(); 
     1434        // here we'll implement REFS sorting, for performance reason 
     1435        else { // ($sort_field == 'date' && $this->threading != 'REFS') 
     1436            // use SORT command 
     1437            if ($this->get_capability('SORT')) { 
     1438                $a_index = $this->conn->sort($mailbox, $this->sort_field, 
     1439                        !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : '')); 
     1440 
     1441                    // return unsorted tree if we've got no index data 
     1442                    if (!$a_index) 
     1443                        return array_keys((array)$thread_tree); 
     1444            } 
     1445            else { 
     1446                // fetch specified headers for all messages and sort them 
     1447                $a_index = $this->conn->fetchHeaderIndex($mailbox, !empty($ids) ? $ids : "1:*", 
     1448                        $this->sort_field, $this->skip_deleted); 
     1449 
     1450                    // return unsorted tree if we've got no index data 
     1451                    if (!$a_index) 
     1452                        return array_keys((array)$thread_tree); 
     1453 
     1454                asort($a_index); // ASC 
     1455                    $a_index = array_values($a_index); 
     1456            } 
     1457 
     1458                return $this->_sort_thread_refs($thread_tree, $a_index); 
     1459        } 
     1460    } 
     1461 
     1462 
     1463    /** 
     1464     * THREAD=REFS sorting implementation 
     1465     * 
     1466     * @param  array   Thread tree array (message identifiers as keys) 
     1467     * @param  array   Array of sorted message identifiers 
     1468     * @return array   Array of sorted roots messages 
     1469     * @access private 
     1470     */ 
     1471    private function _sort_thread_refs($tree, $index) 
     1472    { 
     1473        if (empty($tree)) 
     1474            return array(); 
     1475     
     1476        $index = array_combine(array_values($index), $index); 
     1477 
     1478        // assign roots 
     1479        foreach ($tree as $idx => $val) { 
     1480            $index[$idx] = $idx; 
     1481            if (!empty($val)) { 
     1482                $idx_arr = array_keys_recursive($tree[$idx]); 
     1483                foreach ($idx_arr as $subidx) 
     1484                    $index[$subidx] = $idx; 
     1485            } 
     1486        } 
     1487 
     1488        $index = array_values($index);   
     1489 
     1490        // create sorted array of roots 
     1491        $msg_index = array(); 
     1492        if ($this->sort_order != 'DESC') { 
     1493            foreach ($index as $idx) 
     1494                if (!isset($msg_index[$idx])) 
     1495                    $msg_index[$idx] = $idx; 
     1496            $msg_index = array_values($msg_index); 
     1497        } 
     1498        else { 
     1499            for ($x=count($index)-1; $x>=0; $x--) 
     1500                if (!isset($msg_index[$index[$x]])) 
     1501                    $msg_index[$index[$x]] = $index[$x]; 
     1502            $msg_index = array_reverse($msg_index); 
     1503        } 
     1504 
     1505        return $msg_index; 
     1506    } 
     1507 
     1508 
     1509    /** 
     1510     * Refresh saved search set 
     1511     * 
     1512     * @return array Current search set 
     1513     */ 
     1514    function refresh_search() 
     1515    { 
     1516        if (!empty($this->search_string)) 
     1517            $this->search_set = $this->search('', $this->search_string, $this->search_charset, 
     1518 
     1519        $this->search_sort_field, $this->search_threads); 
    11651520       
    1166       // use message index sort as default sorting 
    1167       if (!$this->sort_field) 
    1168       { 
    1169         $msgs = $this->search_set; 
    1170  
    1171         if ($this->search_sort_field != 'date') 
    1172           sort($msgs); 
    1173  
    1174         if ($this->sort_order == 'DESC') 
    1175           $this->cache[$key] = array_reverse($msgs); 
    1176         else 
    1177           $this->cache[$key] = $msgs; 
    1178       } 
    1179       // sort with SORT command 
    1180       else if ($this->get_capability('SORT')) 
    1181       { 
    1182         if ($this->sort_field && $this->search_sort_field != $this->sort_field) 
    1183           $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
    1184  
    1185         if ($this->sort_order == 'DESC') 
    1186           $this->cache[$key] = array_reverse($this->search_set); 
    1187         else 
    1188           $this->cache[$key] = $this->search_set; 
    1189       } 
    1190       else 
    1191       { 
    1192         $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, 
    1193           join(',', $this->search_set), $this->sort_field, $this->skip_deleted); 
    1194  
    1195         if ($this->sort_order=="ASC") 
    1196           asort($a_index); 
    1197         else if ($this->sort_order=="DESC") 
    1198           arsort($a_index); 
    1199  
    1200         $this->cache[$key] = array_keys($a_index); 
    1201       } 
    1202     } 
    1203  
    1204     // have stored it in RAM 
    1205     if (isset($this->cache[$key])) 
    1206       return $this->cache[$key]; 
    1207  
    1208     // check local cache 
    1209     $cache_key = $mailbox.'.msg'; 
    1210     $cache_status = $this->check_cache_status($mailbox, $cache_key); 
    1211  
    1212     // cache is OK 
    1213     if ($cache_status>0) 
    1214       { 
    1215       $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); 
    1216       return array_keys($a_index); 
    1217       } 
    1218  
    1219     // use message index sort as default sorting 
    1220     if (!$this->sort_field) 
    1221       { 
    1222       if ($this->skip_deleted) { 
    1223         $a_index = $this->_search_index($mailbox, 'ALL'); 
    1224       } else if ($max = $this->_messagecount($mailbox)) { 
    1225         $a_index = range(1, $max); 
    1226       } 
    1227  
    1228       if ($this->sort_order == 'DESC') 
    1229         $a_index = array_reverse($a_index); 
    1230  
    1231       $this->cache[$key] = $a_index; 
    1232       } 
    1233     // fetch complete message index 
    1234     else if ($this->get_capability('SORT')) 
    1235       { 
    1236       if ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { 
    1237         if ($this->sort_order == 'DESC') 
    1238           $a_index = array_reverse($a_index); 
    1239          
    1240         $this->cache[$key] = $a_index; 
    1241         } 
    1242       } 
    1243     else if ($a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { 
    1244       if ($this->sort_order=="ASC") 
    1245         asort($a_index); 
    1246       else if ($this->sort_order=="DESC") 
    1247         arsort($a_index); 
    1248          
    1249       $this->cache[$key] = array_keys($a_index); 
    1250       } 
    1251  
    1252     return $this->cache[$key]; 
    1253     } 
    1254  
    1255  
    1256   /** 
    1257    * Return sorted array of threaded message IDs (not UIDs) 
    1258    * 
    1259    * @param string Mailbox to get index from 
    1260    * @param string Sort column 
    1261    * @param string Sort order [ASC, DESC] 
    1262    * @return array Indexed array with message IDs 
    1263    */ 
    1264   function thread_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) 
    1265     { 
    1266     $this->_set_sort_order($sort_field, $sort_order); 
    1267  
    1268     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    1269     $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.thi"; 
    1270  
    1271     // we have a saved search result, get index from there 
    1272     if (!isset($this->cache[$key]) && $this->search_string 
    1273       && $this->search_threads && $mailbox == $this->mailbox) 
    1274     { 
    1275       // use message IDs for better performance 
    1276       $ids = array_keys_recursive($this->search_set['tree']); 
    1277       $this->cache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids); 
    1278     } 
    1279  
    1280     // have stored it in RAM 
    1281     if (isset($this->cache[$key])) 
    1282       return $this->cache[$key]; 
    1283 /* 
    1284     // check local cache 
    1285     $cache_key = $mailbox.'.msg'; 
    1286     $cache_status = $this->check_cache_status($mailbox, $cache_key); 
    1287  
    1288     // cache is OK 
    1289     if ($cache_status>0) 
    1290       { 
    1291       $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); 
    1292       return array_keys($a_index); 
    1293       } 
    1294 */ 
    1295     // get all threads (default sort order) 
    1296     list ($thread_tree) = $this->_fetch_threads($mailbox); 
    1297  
    1298     $this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree); 
    1299      
    1300     return $this->cache[$key]; 
    1301     } 
    1302  
    1303  
    1304   /** 
    1305    * Return array of threaded messages (all, not only roots) 
    1306    * 
    1307    * @param string Mailbox to get index from 
    1308    * @param array  Threaded messages array (see _fetch_threads()) 
    1309    * @param array  Message IDs if we know what we need (e.g. search result) 
    1310    *               for better performance 
    1311    * @return array Indexed array with message IDs 
    1312    * 
    1313    * @access private 
    1314    */ 
    1315   private function _flatten_threads($mailbox, $thread_tree, $ids=null) 
    1316     { 
    1317     if (empty($thread_tree)) 
    1318       return array(); 
    1319  
    1320     $msg_index = $this->_sort_threads($mailbox, $thread_tree, $ids); 
    1321  
    1322     if ($this->sort_order == 'DESC') 
    1323       $msg_index = array_reverse($msg_index); 
    1324            
    1325     // flatten threads array 
    1326     $all_ids = array(); 
    1327     foreach($msg_index as $root) { 
    1328       $all_ids[] = $root; 
    1329       if (!empty($thread_tree[$root])) 
    1330         $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 
    1331       } 
    1332  
    1333     return $all_ids; 
    1334     } 
    1335  
    1336  
    1337   /** 
    1338    * @access private 
    1339    */ 
    1340   private function sync_header_index($mailbox) 
    1341     { 
    1342     $cache_key = $mailbox.'.msg'; 
    1343     $cache_index = $this->get_message_cache_index($cache_key); 
    1344  
    1345     // fetch complete message index 
    1346     $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", 'UID', $this->skip_deleted); 
    1347      
    1348     if ($a_message_index === false) 
    1349       return false; 
    1350          
    1351     foreach ($a_message_index as $id => $uid) 
    1352       { 
    1353       // message in cache at correct position 
    1354       if ($cache_index[$id] == $uid) 
    1355         { 
    1356         unset($cache_index[$id]); 
    1357         continue; 
    1358         } 
    1359          
    1360       // message in cache but in wrong position 
    1361       if (in_array((string)$uid, $cache_index, true)) 
    1362         { 
    1363         unset($cache_index[$id]); 
    1364         } 
    1365        
    1366       // other message at this position 
    1367       if (isset($cache_index[$id])) 
    1368         { 
    1369         $for_remove[] = $cache_index[$id]; 
    1370         unset($cache_index[$id]); 
    1371         } 
    1372          
    1373         $for_update[] = $id; 
    1374       } 
    1375  
    1376     // clear messages at wrong positions and those deleted that are still in cache_index       
    1377     if (!empty($for_remove)) 
    1378       $cache_index = array_merge($cache_index, $for_remove); 
    1379      
    1380     if (!empty($cache_index)) 
    1381       $this->remove_message_cache($cache_key, $cache_index); 
    1382  
    1383     // fetch complete headers and add to cache 
    1384     if (!empty($for_update)) { 
    1385       if ($headers = iil_C_FetchHeader($this->conn, $mailbox, join(',', $for_update), false, $this->fetch_add_headers)) 
    1386         foreach ($headers as $header) 
    1387           $this->add_message_cache($cache_key, $header->id, $header, NULL, 
    1388             in_array($header->uid, (array)$for_remove)); 
    1389       } 
    1390     } 
    1391  
    1392  
    1393   /** 
    1394    * Invoke search request to IMAP server 
    1395    * 
    1396    * @param  string  mailbox name to search in 
    1397    * @param  string  search string 
    1398    * @param  string  search string charset 
    1399    * @param  string  header field to sort by 
    1400    * @return array   search results as list of message ids 
    1401    * @access public 
    1402    */ 
    1403   function search($mbox_name='', $str=NULL, $charset=NULL, $sort_field=NULL) 
    1404     { 
    1405     if (!$str) 
    1406       return false; 
    1407      
    1408     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    1409  
    1410     $results = $this->_search_index($mailbox, $str, $charset, $sort_field); 
    1411  
    1412     // try search with US-ASCII charset (should be supported by server) 
    1413     // only if UTF-8 search is not supported 
    1414     if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII') 
    1415     { 
    1416       // convert strings to US_ASCII 
    1417       if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) 
    1418       { 
    1419         $last = 0; $res = ''; 
    1420         foreach($matches[1] as $m) 
    1421         { 
    1422           $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n 
    1423           $string = substr($str, $string_offset - 1, $m[0]); 
    1424           $string = rcube_charset_convert($string, $charset, 'US-ASCII'); 
    1425           if (!$string) continue; 
    1426           $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); 
    1427           $last = $m[0] + $string_offset - 1; 
    1428         } 
    1429         if ($last < strlen($str)) 
    1430           $res .= substr($str, $last, strlen($str)-$last); 
    1431       } 
    1432       else // strings for conversion not found 
    1433       $res = $str; 
    1434  
    1435       $results = $this->search($mbox_name, $res, NULL, $sort_field); 
    1436     } 
    1437  
    1438     $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading); 
    1439  
    1440     return $results; 
    1441     } 
    1442  
    1443  
    1444   /** 
    1445    * Private search method 
    1446    * 
    1447    * @return array   search results as list of message ids 
    1448    * @access private 
    1449    * @see rcube_imap::search() 
    1450    */ 
    1451   private function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL) 
    1452     { 
    1453     $orig_criteria = $criteria; 
    1454  
    1455     if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria)) 
    1456       $criteria = 'UNDELETED '.$criteria; 
    1457  
    1458     if ($this->threading) { 
    1459       list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, 
    1460             $mailbox, $this->threading, $criteria, $charset); 
    1461  
    1462       $a_messages = array( 
    1463         'tree'  => $thread_tree, 
    1464         'depth' => $msg_depth, 
    1465         'children' => $has_children 
    1466         ); 
    1467       } 
    1468     else if ($sort_field && $this->get_capability('SORT')) { 
    1469       $charset = $charset ? $charset : $this->default_charset; 
    1470       $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, false, $charset); 
    1471  
    1472       if (!$a_messages) 
    1473         return array(); 
    1474       } 
    1475     else { 
    1476       if ($orig_criteria == 'ALL') { 
    1477         $max = $this->_messagecount($mailbox); 
    1478         $a_messages = $max ? range(1, $max) : array(); 
    1479         } 
    1480       else { 
    1481         $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria); 
    1482  
    1483         if (!$a_messages) 
    1484           return array(); 
    1485  
    1486         // I didn't found that SEARCH always returns sorted IDs 
    1487         if (!$this->sort_field) 
    1488           sort($a_messages); 
    1489         } 
    1490       } 
    1491     // update messagecount cache ? 
    1492 //    $a_mailbox_cache = get_cache('messagecount'); 
    1493 //    $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages); 
    1494 //    $this->update_cache('messagecount', $a_mailbox_cache); 
    1495          
    1496     return $a_messages; 
    1497     } 
    1498      
    1499    
    1500   /** 
    1501    * Sort thread 
    1502    * 
    1503    * @param string Mailbox name 
    1504    * @param  array Unsorted thread tree (iil_C_Thread() result) 
    1505    * @param  array Message IDs if we know what we need (e.g. search result) 
    1506    * @return array Sorted roots IDs 
    1507    * @access private 
    1508    */ 
    1509   private function _sort_threads($mailbox, $thread_tree, $ids=NULL) 
    1510     { 
    1511     // THREAD=ORDEREDSUBJECT:   sorting by sent date of root message 
    1512     // THREAD=REFERENCES:       sorting by sent date of root message 
    1513     // THREAD=REFS:             sorting by the most recent date in each thread 
    1514     // default sorting 
    1515     if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { 
    1516         return array_keys((array)$thread_tree); 
    1517       } 
    1518     // here we'll implement REFS sorting, for performance reason 
    1519     else { // ($sort_field == 'date' && $this->threading != 'REFS') 
    1520       // use SORT command 
    1521       if ($this->get_capability('SORT')) { 
    1522         $a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, 
    1523             !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : '')); 
    1524  
    1525         // return unsorted tree if we've got no index data 
    1526         if (!$a_index) 
    1527           return array_keys((array)$thread_tree); 
    1528         } 
    1529       else { 
    1530         // fetch specified headers for all messages and sort them 
    1531         $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, !empty($ids) ? $ids : "1:*", 
    1532             $this->sort_field, $this->skip_deleted); 
    1533  
    1534         // return unsorted tree if we've got no index data 
    1535         if (!$a_index) 
    1536           return array_keys((array)$thread_tree); 
    1537  
    1538         asort($a_index); // ASC 
    1539         $a_index = array_values($a_index); 
    1540         } 
    1541  
    1542         return $this->_sort_thread_refs($thread_tree, $a_index); 
    1543       } 
    1544     } 
    1545  
    1546  
    1547   /** 
    1548    * THREAD=REFS sorting implementation 
    1549    * 
    1550    * @param  array   Thread tree array (message identifiers as keys) 
    1551    * @param  array   Array of sorted message identifiers 
    1552    * @return array   Array of sorted roots messages 
    1553    * @access private 
    1554    */ 
    1555   private function _sort_thread_refs($tree, $index) 
    1556     { 
    1557     if (empty($tree)) 
    1558       return array(); 
    1559      
    1560     $index = array_combine(array_values($index), $index); 
    1561  
    1562     // assign roots 
    1563     foreach ($tree as $idx => $val) { 
    1564       $index[$idx] = $idx; 
    1565       if (!empty($val)) { 
    1566         $idx_arr = array_keys_recursive($tree[$idx]); 
    1567         foreach ($idx_arr as $subidx) 
    1568           $index[$subidx] = $idx; 
    1569         } 
    1570       } 
    1571  
    1572     $index = array_values($index);   
    1573  
    1574     // create sorted array of roots 
    1575     $msg_index = array(); 
    1576     if ($this->sort_order != 'DESC') { 
    1577       foreach ($index as $idx) 
    1578         if (!isset($msg_index[$idx])) 
    1579           $msg_index[$idx] = $idx; 
    1580       $msg_index = array_values($msg_index); 
    1581       } 
    1582     else { 
    1583       for ($x=count($index)-1; $x>=0; $x--) 
    1584         if (!isset($msg_index[$index[$x]])) 
    1585           $msg_index[$index[$x]] = $index[$x]; 
    1586       $msg_index = array_reverse($msg_index); 
    1587       } 
    1588  
    1589     return $msg_index; 
    1590     } 
    1591  
    1592  
    1593   /** 
    1594    * Refresh saved search set 
    1595    * 
    1596    * @return array Current search set 
    1597    */ 
    1598   function refresh_search() 
    1599     { 
    1600     if (!empty($this->search_string)) 
    1601       $this->search_set = $this->search('', $this->search_string, $this->search_charset, 
    1602             $this->search_sort_field, $this->search_threads); 
    1603        
    1604     return $this->get_search_set(); 
     1521        return $this->get_search_set(); 
    16051522    } 
    16061523   
    16071524   
    1608   /** 
    1609    * Check if the given message ID is part of the current search set 
    1610    * 
    1611    * @return boolean True on match or if no search request is stored 
    1612    */ 
    1613   function in_searchset($msgid) 
    1614   { 
    1615     if (!empty($this->search_string)) 
    1616       return in_array("$msgid", (array)$this->search_set, true); 
    1617     else 
    1618       return true; 
    1619   } 
    1620  
    1621  
    1622   /** 
    1623    * Return message headers object of a specific message 
    1624    * 
    1625    * @param int     Message ID 
    1626    * @param string  Mailbox to read from  
    1627    * @param boolean True if $id is the message UID 
    1628    * @param boolean True if we need also BODYSTRUCTURE in headers 
    1629    * @return object Message headers representation 
    1630    */ 
    1631   function get_headers($id, $mbox_name=NULL, $is_uid=true, $bodystr=false) 
    1632     { 
    1633     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    1634     $uid = $is_uid ? $id : $this->_id2uid($id); 
    1635  
    1636     // get cached headers 
    1637     if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) 
    1638       return $headers; 
    1639  
    1640     $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); 
    1641  
    1642     // write headers cache 
    1643     if ($headers) 
    1644       { 
    1645       if ($headers->uid && $headers->id) 
    1646         $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; 
    1647  
    1648       $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, true); 
    1649       } 
    1650  
    1651     return $headers; 
    1652     } 
    1653  
    1654  
    1655   /** 
    1656    * Fetch body structure from the IMAP server and build 
    1657    * an object structure similar to the one generated by PEAR::Mail_mimeDecode 
    1658    * 
    1659    * @param int Message UID to fetch 
    1660    * @param string Message BODYSTRUCTURE string (optional) 
    1661    * @return object rcube_message_part Message part tree or False on failure 
    1662    */ 
    1663   function &get_structure($uid, $structure_str='') 
    1664     { 
    1665     $cache_key = $this->mailbox.'.msg'; 
    1666     $headers = &$this->get_cached_message($cache_key, $uid); 
    1667  
    1668     // return cached message structure 
    1669     if (is_object($headers) && is_object($headers->structure)) { 
    1670       return $headers->structure; 
    1671     } 
    1672  
    1673     if (!$structure_str) 
    1674       $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true); 
    1675     $structure = iml_GetRawStructureArray($structure_str); 
    1676     $struct = false; 
    1677  
    1678     // parse structure and add headers 
    1679     if (!empty($structure)) 
    1680       { 
    1681       $headers = $this->get_headers($uid); 
    1682       $this->_msg_id = $headers->id; 
    1683  
    1684       // set message charset from message headers 
    1685       if ($headers->charset) 
    1686         $this->struct_charset = $headers->charset; 
    1687       else 
    1688         $this->struct_charset = $this->_structure_charset($structure); 
    1689  
    1690       // Here we can recognize malformed BODYSTRUCTURE and  
    1691       // 1. [@TODO] parse the message in other way to create our own message structure 
    1692       // 2. or just show the raw message body. 
    1693       // Example of structure for malformed MIME message: 
    1694       // ("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 2154 70 NIL NIL NIL) 
    1695       if ($headers->ctype && $headers->ctype != 'text/plain' 
    1696           && $structure[0] == 'text' && $structure[1] == 'plain') { 
     1525    /** 
     1526     * Check if the given message ID is part of the current search set 
     1527     * 
     1528     * @return boolean True on match or if no search request is stored 
     1529     */ 
     1530    function in_searchset($msgid) 
     1531    { 
     1532        if (!empty($this->search_string)) { 
     1533            if ($this->search_threads) 
     1534                return isset($this->search_set['depth']["$msgid"]); 
     1535            else 
     1536                return in_array("$msgid", (array)$this->search_set, true); 
     1537        } 
     1538        else 
     1539            return true; 
     1540    } 
     1541 
     1542 
     1543    /** 
     1544     * Return message headers object of a specific message 
     1545     * 
     1546     * @param int     Message ID 
     1547     * @param string  Mailbox to read from  
     1548     * @param boolean True if $id is the message UID 
     1549     * @param boolean True if we need also BODYSTRUCTURE in headers 
     1550     * @return object Message headers representation 
     1551     */ 
     1552    function get_headers($id, $mbox_name=NULL, $is_uid=true, $bodystr=false) 
     1553    { 
     1554        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     1555        $uid = $is_uid ? $id : $this->_id2uid($id); 
     1556 
     1557        // get cached headers 
     1558        if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) 
     1559            return $headers; 
     1560 
     1561        $headers = $this->conn->fetchHeader( 
     1562            $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); 
     1563 
     1564        // write headers cache 
     1565        if ($headers) { 
     1566            if ($headers->uid && $headers->id) 
     1567                $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; 
     1568 
     1569            $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, true); 
     1570        } 
     1571 
     1572        return $headers; 
     1573    } 
     1574 
     1575 
     1576    /** 
     1577     * Fetch body structure from the IMAP server and build 
     1578     * an object structure similar to the one generated by PEAR::Mail_mimeDecode 
     1579     * 
     1580     * @param int Message UID to fetch 
     1581     * @param string Message BODYSTRUCTURE string (optional) 
     1582     * @return object rcube_message_part Message part tree or False on failure 
     1583     */ 
     1584    function &get_structure($uid, $structure_str='') 
     1585    { 
     1586        $cache_key = $this->mailbox.'.msg'; 
     1587        $headers = &$this->get_cached_message($cache_key, $uid); 
     1588 
     1589        // return cached message structure 
     1590        if (is_object($headers) && is_object($headers->structure)) { 
     1591            return $headers->structure; 
     1592        } 
     1593 
     1594        if (!$structure_str) 
     1595            $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); 
     1596        $structure = iml_GetRawStructureArray($structure_str); 
     1597        $struct = false; 
     1598 
     1599        // parse structure and add headers 
     1600        if (!empty($structure)) { 
     1601            $headers = $this->get_headers($uid); 
     1602            $this->_msg_id = $headers->id; 
     1603 
     1604        // set message charset from message headers 
     1605        if ($headers->charset) 
     1606            $this->struct_charset = $headers->charset; 
     1607        else 
     1608            $this->struct_charset = $this->_structure_charset($structure); 
     1609 
     1610        // Here we can recognize malformed BODYSTRUCTURE and  
     1611        // 1. [@TODO] parse the message in other way to create our own message structure 
     1612        // 2. or just show the raw message body. 
     1613        // Example of structure for malformed MIME message: 
     1614        // ("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 2154 70 NIL NIL NIL) 
     1615        if ($headers->ctype && $headers->ctype != 'text/plain' 
     1616            && $structure[0] == 'text' && $structure[1] == 'plain') { 
     1617            return false; 
     1618        } 
     1619 
     1620        $struct = &$this->_structure_part($structure); 
     1621        $struct->headers = get_object_vars($headers); 
     1622 
     1623        // don't trust given content-type 
     1624        if (empty($struct->parts) && !empty($struct->headers['ctype'])) { 
     1625            $struct->mime_id = '1'; 
     1626            $struct->mimetype = strtolower($struct->headers['ctype']); 
     1627            list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); 
     1628        } 
     1629 
     1630        // write structure to cache 
     1631        if ($this->caching_enabled) 
     1632            $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct); 
     1633        } 
     1634 
     1635        return $struct; 
     1636    } 
     1637 
     1638   
     1639    /** 
     1640     * Build message part object 
     1641     * 
     1642     * @access private 
     1643     */ 
     1644    function &_structure_part($part, $count=0, $parent='', $mime_headers=null, $raw_headers=null) 
     1645    { 
     1646        $struct = new rcube_message_part; 
     1647        $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; 
     1648 
     1649        // multipart 
     1650        if (is_array($part[0])) { 
     1651            $struct->ctype_primary = 'multipart'; 
     1652       
     1653            // find first non-array entry 
     1654            for ($i=1; $i<count($part); $i++) { 
     1655                if (!is_array($part[$i])) { 
     1656                    $struct->ctype_secondary = strtolower($part[$i]); 
     1657                    break; 
     1658                } 
     1659            } 
     1660             
     1661            $struct->mimetype = 'multipart/'.$struct->ctype_secondary; 
     1662 
     1663            // build parts list for headers pre-fetching 
     1664            for ($i=0, $count=0; $i<count($part); $i++) { 
     1665                if (is_array($part[$i]) && count($part[$i]) > 3) { 
     1666                    // fetch message headers if message/rfc822 
     1667                    // or named part (could contain Content-Location header) 
     1668                    if (!is_array($part[$i][0])) { 
     1669                        $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; 
     1670                        if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') { 
     1671                            $raw_part_headers[] = $tmp_part_id; 
     1672                            $mime_part_headers[] = $tmp_part_id; 
     1673                        } 
     1674                        else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) { 
     1675                            $mime_part_headers[] = $tmp_part_id; 
     1676                        } 
     1677                    } 
     1678                } 
     1679            } 
     1680         
     1681            // pre-fetch headers of all parts (in one command for better performance) 
     1682            // @TODO: we could do this before _structure_part() call, to fetch 
     1683            // headers for parts on all levels 
     1684            if ($mime_part_headers) { 
     1685                $mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, 
     1686                    $this->_msg_id, $mime_part_headers); 
     1687            } 
     1688            // we'll need a real content-type of message/rfc822 part 
     1689            if ($raw_part_headers) { 
     1690                $raw_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, 
     1691                    $this->_msg_id, $raw_part_headers, false); 
     1692            } 
     1693            $struct->parts = array(); 
     1694            for ($i=0, $count=0; $i<count($part); $i++) { 
     1695                if (is_array($part[$i]) && count($part[$i]) > 3) { 
     1696                    $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; 
     1697                    $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id, 
     1698                    $mime_part_headers[$tmp_part_id], $raw_part_headers[$tmp_part_id]); 
     1699                } 
     1700            } 
     1701 
     1702            return $struct; 
     1703        } 
     1704 
     1705        // regular part 
     1706        $struct->ctype_primary = strtolower($part[0]); 
     1707        $struct->ctype_secondary = strtolower($part[1]); 
     1708        $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary; 
     1709 
     1710        // read content type parameters 
     1711        if (is_array($part[2])) { 
     1712            $struct->ctype_parameters = array(); 
     1713            for ($i=0; $i<count($part[2]); $i+=2) 
     1714                $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1]; 
     1715         
     1716            if (isset($struct->ctype_parameters['charset'])) 
     1717                $struct->charset = $struct->ctype_parameters['charset']; 
     1718        } 
     1719     
     1720        // read content encoding 
     1721        if (!empty($part[5]) && $part[5]!='NIL') { 
     1722            $struct->encoding = strtolower($part[5]); 
     1723            $struct->headers['content-transfer-encoding'] = $struct->encoding; 
     1724        } 
     1725     
     1726        // get part size 
     1727        if (!empty($part[6]) && $part[6]!='NIL') 
     1728            $struct->size = intval($part[6]); 
     1729 
     1730        // read part disposition 
     1731        $di = count($part) - 2; 
     1732        if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) || 
     1733            (is_array($part[--$di]) && count($part[$di]) == 2)) { 
     1734            $struct->disposition = strtolower($part[$di][0]); 
     1735 
     1736            if (is_array($part[$di][1])) 
     1737                for ($n=0; $n<count($part[$di][1]); $n+=2) 
     1738                    $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; 
     1739        } 
     1740       
     1741        // get child parts 
     1742        if (is_array($part[8]) && $di != 8) { 
     1743            $struct->parts = array(); 
     1744            for ($i=0, $count=0; $i<count($part[8]); $i++) 
     1745                if (is_array($part[8][$i]) && count($part[8][$i]) > 5) 
     1746                    $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id); 
     1747        } 
     1748 
     1749        // get part ID 
     1750        if (!empty($part[3]) && $part[3]!='NIL') { 
     1751            $struct->content_id = $part[3]; 
     1752            $struct->headers['content-id'] = $part[3]; 
     1753     
     1754            if (empty($struct->disposition)) 
     1755                $struct->disposition = 'inline'; 
     1756        } 
     1757     
     1758        // fetch message headers if message/rfc822 or named part (could contain Content-Location header) 
     1759        if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) { 
     1760            if (empty($mime_headers)) { 
     1761                $mime_headers = $this->conn->fetchPartHeader( 
     1762                    $this->mailbox, $this->_msg_id, false, $struct->mime_id); 
     1763            } 
     1764            $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; 
     1765 
     1766            // get real headers for message of type 'message/rfc822' 
     1767            if ($struct->mimetype == 'message/rfc822') { 
     1768                if (empty($raw_headers)) { 
     1769                    $raw_headers = $this->conn->fetchMIMEHeaders( 
     1770                        $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false); 
     1771                } 
     1772                $struct->real_headers = $this->_parse_headers($raw_headers); 
     1773 
     1774                // get real content-type of message/rfc822 
     1775                if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) { 
     1776                    $struct->real_mimetype = strtolower($matches[1]); 
     1777                }                                                     
     1778            } 
     1779        } 
     1780 
     1781        if ($struct->ctype_primary=='message') { 
     1782            if (is_array($part[8]) && $di != 8 && empty($struct->parts)) 
     1783                $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id); 
     1784        } 
     1785 
     1786        // normalize filename property 
     1787        $this->_set_part_filename($struct, $mime_headers); 
     1788 
     1789        return $struct; 
     1790    } 
     1791     
     1792 
     1793    /** 
     1794     * Set attachment filename from message part structure  
     1795     * 
     1796     * @access private 
     1797     * @param  object rcube_message_part Part object 
     1798     * @param  string Part's raw headers 
     1799     */ 
     1800    private function _set_part_filename(&$part, $headers=null) 
     1801    { 
     1802        if (!empty($part->d_parameters['filename'])) 
     1803            $filename_mime = $part->d_parameters['filename']; 
     1804        else if (!empty($part->d_parameters['filename*'])) 
     1805            $filename_encoded = $part->d_parameters['filename*']; 
     1806        else if (!empty($part->ctype_parameters['name*'])) 
     1807            $filename_encoded = $part->ctype_parameters['name*']; 
     1808        // RFC2231 value continuations 
     1809        // TODO: this should be rewrited to support RFC2231 4.1 combinations 
     1810        else if (!empty($part->d_parameters['filename*0'])) { 
     1811            $i = 0; 
     1812            while (isset($part->d_parameters['filename*'.$i])) { 
     1813                $filename_mime .= $part->d_parameters['filename*'.$i]; 
     1814                $i++; 
     1815            } 
     1816            // some servers (eg. dovecot-1.x) have no support for parameter value continuations 
     1817            // we must fetch and parse headers "manually" 
     1818            if ($i<2) { 
     1819                if (!$headers) { 
     1820                    $headers = $this->conn->fetchPartHeader( 
     1821                        $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     1822                } 
     1823                $filename_mime = ''; 
     1824                $i = 0; 
     1825                while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
     1826                    $filename_mime .= $matches[1]; 
     1827                    $i++; 
     1828                } 
     1829            } 
     1830        } 
     1831        else if (!empty($part->d_parameters['filename*0*'])) { 
     1832            $i = 0; 
     1833            while (isset($part->d_parameters['filename*'.$i.'*'])) { 
     1834                $filename_encoded .= $part->d_parameters['filename*'.$i.'*']; 
     1835                $i++; 
     1836            } 
     1837            if ($i<2) { 
     1838                if (!$headers) { 
     1839                    $headers = $this->conn->fetchPartHeader( 
     1840                            $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     1841                } 
     1842                $filename_encoded = ''; 
     1843                $i = 0; $matches = array(); 
     1844                while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
     1845                    $filename_encoded .= $matches[1]; 
     1846                    $i++; 
     1847                } 
     1848            } 
     1849        } 
     1850        else if (!empty($part->ctype_parameters['name*0'])) { 
     1851            $i = 0; 
     1852            while (isset($part->ctype_parameters['name*'.$i])) { 
     1853                $filename_mime .= $part->ctype_parameters['name*'.$i]; 
     1854                $i++; 
     1855            } 
     1856            if ($i<2) { 
     1857                if (!$headers) { 
     1858                    $headers = $this->conn->fetchPartHeader( 
     1859                        $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     1860                } 
     1861                $filename_mime = ''; 
     1862                $i = 0; $matches = array(); 
     1863                while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
     1864                    $filename_mime .= $matches[1]; 
     1865                    $i++; 
     1866                } 
     1867            } 
     1868        } 
     1869        else if (!empty($part->ctype_parameters['name*0*'])) { 
     1870            $i = 0; 
     1871            while (isset($part->ctype_parameters['name*'.$i.'*'])) { 
     1872                $filename_encoded .= $part->ctype_parameters['name*'.$i.'*']; 
     1873                $i++; 
     1874            } 
     1875            if ($i<2) { 
     1876                if (!$headers) { 
     1877                    $headers = $this->conn->fetchPartHeader( 
     1878                        $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     1879                } 
     1880                $filename_encoded = ''; 
     1881                $i = 0; $matches = array(); 
     1882                while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
     1883                    $filename_encoded .= $matches[1]; 
     1884                    $i++; 
     1885                } 
     1886            } 
     1887        } 
     1888        // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird) 
     1889        else if (!empty($part->ctype_parameters['name'])) 
     1890            $filename_mime = $part->ctype_parameters['name']; 
     1891        // Content-Disposition 
     1892        else if (!empty($part->headers['content-description'])) 
     1893            $filename_mime = $part->headers['content-description']; 
     1894        else 
     1895            return; 
     1896 
     1897        // decode filename 
     1898        if (!empty($filename_mime)) { 
     1899            $part->filename = rcube_imap::decode_mime_string($filename_mime,  
     1900                $part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset : 
     1901                rc_detect_encoding($filename_mime, $this->default_charset))); 
     1902        }  
     1903        else if (!empty($filename_encoded)) { 
     1904            // decode filename according to RFC 2231, Section 4 
     1905            if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) { 
     1906                $filename_charset = $fmatches[1]; 
     1907                $filename_encoded = $fmatches[2]; 
     1908            } 
     1909            $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset); 
     1910        } 
     1911    } 
     1912 
     1913 
     1914    /** 
     1915     * Get charset name from message structure (first part) 
     1916     * 
     1917     * @access private 
     1918     * @param  array  Message structure 
     1919     * @return string Charset name 
     1920     */ 
     1921    function _structure_charset($structure) 
     1922    { 
     1923        while (is_array($structure)) { 
     1924            if (is_array($structure[2]) && $structure[2][0] == 'charset') 
     1925                return $structure[2][1]; 
     1926            $structure = $structure[0]; 
     1927        } 
     1928    }  
     1929 
     1930 
     1931    /** 
     1932     * Fetch message body of a specific message from the server 
     1933     * 
     1934     * @param  int    Message UID 
     1935     * @param  string Part number 
     1936     * @param  object rcube_message_part Part object created by get_structure() 
     1937     * @param  mixed  True to print part, ressource to write part contents in 
     1938     * @param  resource File pointer to save the message part 
     1939     * @return string Message/part body if not printed 
     1940     */ 
     1941    function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL) 
     1942    { 
     1943        // get part encoding if not provided 
     1944        if (!is_object($o_part)) { 
     1945            $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true);  
     1946            $structure = iml_GetRawStructureArray($structure_str); 
     1947            // error or message not found 
     1948            if (empty($structure)) 
     1949                return false; 
     1950 
     1951            $part_type = iml_GetPartTypeCode($structure, $part); 
     1952            $o_part = new rcube_message_part; 
     1953            $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other'); 
     1954            $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part)); 
     1955            $o_part->charset = iml_GetPartCharset($structure, $part); 
     1956        } 
     1957       
     1958        // TODO: Add caching for message parts 
     1959 
     1960        if (!$part) $part = 'TEXT'; 
     1961 
     1962        $body = $this->conn->handlePartBody($this->mailbox, $uid, true, $part, 
     1963            $o_part->encoding, $print, $fp); 
     1964 
     1965        if ($fp || $print) 
     1966            return true; 
     1967 
     1968        // convert charset (if text or message part) 
     1969        if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') { 
     1970            // assume default if no charset specified 
     1971            if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') 
     1972                $o_part->charset = $this->default_charset; 
     1973 
     1974            $body = rcube_charset_convert($body, $o_part->charset); 
     1975        } 
     1976     
     1977        return $body; 
     1978    } 
     1979 
     1980 
     1981    /** 
     1982     * Fetch message body of a specific message from the server 
     1983     * 
     1984     * @param  int    Message UID 
     1985     * @return string Message/part body 
     1986     * @see    rcube_imap::get_message_part() 
     1987     */ 
     1988    function &get_body($uid, $part=1) 
     1989    { 
     1990        $headers = $this->get_headers($uid); 
     1991        return rcube_charset_convert($this->get_message_part($uid, $part, NULL), 
     1992            $headers->charset ? $headers->charset : $this->default_charset); 
     1993    } 
     1994 
     1995 
     1996    /** 
     1997     * Returns the whole message source as string 
     1998     * 
     1999     * @param int  Message UID 
     2000     * @return string Message source string 
     2001     */ 
     2002    function &get_raw_body($uid) 
     2003    { 
     2004        return $this->conn->handlePartBody($this->mailbox, $uid, true); 
     2005    } 
     2006 
     2007 
     2008    /** 
     2009     * Returns the message headers as string 
     2010     * 
     2011     * @param int  Message UID 
     2012     * @return string Message headers string 
     2013     */ 
     2014    function &get_raw_headers($uid) 
     2015    { 
     2016        return $this->conn->fetchPartHeader($this->mailbox, $uid, true); 
     2017    } 
     2018     
     2019 
     2020    /** 
     2021     * Sends the whole message source to stdout 
     2022     * 
     2023     * @param int  Message UID 
     2024     */  
     2025    function print_raw_body($uid) 
     2026    { 
     2027        $this->conn->handlePartBody($this->mailbox, $uid, true, NULL, NULL, true); 
     2028    } 
     2029 
     2030 
     2031    /** 
     2032     * Set message flag to one or several messages 
     2033     * 
     2034     * @param mixed   Message UIDs as array or comma-separated string, or '*' 
     2035     * @param string  Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT 
     2036     * @param string  Folder name 
     2037     * @param boolean True to skip message cache clean up 
     2038     * @return int    Number of flagged messages, -1 on failure 
     2039     */ 
     2040    function set_flag($uids, $flag, $mbox_name=NULL, $skip_cache=false) 
     2041    { 
     2042        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     2043 
     2044        $flag = strtoupper($flag); 
     2045        list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); 
     2046 
     2047        if (strpos($flag, 'UN') === 0) 
     2048            $result = $this->conn->unflag($mailbox, $uids, substr($flag, 2)); 
     2049        else 
     2050            $result = $this->conn->flag($mailbox, $uids, $flag); 
     2051 
     2052        if ($result >= 0) { 
     2053            // reload message headers if cached 
     2054            if ($this->caching_enabled && !$skip_cache) { 
     2055                $cache_key = $mailbox.'.msg'; 
     2056                if ($all_mode) 
     2057                    $this->clear_message_cache($cache_key); 
     2058                else 
     2059                    $this->remove_message_cache($cache_key, explode(',', $uids)); 
     2060            } 
     2061            // update counters 
     2062            if ($flag=='SEEN') 
     2063                $this->_set_messagecount($mailbox, 'UNSEEN', $result*(-1)); 
     2064            else if ($flag=='UNSEEN') 
     2065                $this->_set_messagecount($mailbox, 'UNSEEN', $result); 
     2066            else if ($flag=='DELETED') 
     2067                $this->_set_messagecount($mailbox, 'ALL', $result*(-1)); 
     2068        } 
     2069 
     2070        return $result; 
     2071    } 
     2072 
     2073 
     2074    /** 
     2075     * Remove message flag for one or several messages 
     2076     * 
     2077     * @param mixed  Message UIDs as array or comma-separated string, or '*' 
     2078     * @param string Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT 
     2079     * @param string Folder name 
     2080     * @return int   Number of flagged messages, -1 on failure 
     2081     * @see set_flag 
     2082     */ 
     2083    function unset_flag($uids, $flag, $mbox_name=NULL) 
     2084    { 
     2085        return $this->set_flag($uids, 'UN'.$flag, $mbox_name); 
     2086    } 
     2087 
     2088 
     2089    /** 
     2090     * Append a mail message (source) to a specific mailbox 
     2091     * 
     2092     * @param string   Target mailbox 
     2093     * @param string   The message source string or filename 
     2094     * @param string   Headers string if $message contains only the body 
     2095     * @param boolean  True if $message is a filename 
     2096     * 
     2097     * @return boolean True on success, False on error 
     2098     */ 
     2099    function save_message($mbox_name, &$message, $headers='', $is_file=false) 
     2100    { 
     2101        $mailbox = $this->mod_mailbox($mbox_name); 
     2102 
     2103        // make sure mailbox exists 
     2104        if ($this->mailbox_exists($mbox_name, true)) { 
     2105            if ($is_file) { 
     2106                $separator = rcmail::get_instance()->config->header_delimiter(); 
     2107                $saved = $this->conn->appendFromFile($mailbox, $message, 
     2108                    $headers, $separator.$separator); 
     2109            } 
     2110            else 
     2111                $saved = $this->conn->append($mailbox, $message); 
     2112        } 
     2113 
     2114        if ($saved) { 
     2115            // increase messagecount of the target mailbox 
     2116            $this->_set_messagecount($mailbox, 'ALL', 1); 
     2117        } 
     2118 
     2119        return $saved; 
     2120    } 
     2121 
     2122 
     2123    /** 
     2124     * Move a message from one mailbox to another 
     2125     * 
     2126     * @param mixed  Message UIDs as array or comma-separated string, or '*' 
     2127     * @param string Target mailbox 
     2128     * @param string Source mailbox 
     2129     * @return boolean True on success, False on error 
     2130     */ 
     2131    function move_message($uids, $to_mbox, $from_mbox='') 
     2132    { 
     2133        $fbox = $from_mbox; 
     2134        $tbox = $to_mbox; 
     2135        $to_mbox = $this->mod_mailbox($to_mbox); 
     2136        $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; 
     2137 
     2138        list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); 
     2139 
     2140        // exit if no message uids are specified 
     2141        if (empty($uids)) 
     2142            return false; 
     2143 
     2144        // make sure mailbox exists 
     2145        if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { 
     2146            if (in_array($tbox, $this->default_folders)) 
     2147                $this->create_mailbox($tbox, true); 
     2148            else 
     2149                return false; 
     2150        } 
     2151 
     2152        // flag messages as read before moving them 
     2153        $config = rcmail::get_instance()->config; 
     2154        if ($config->get('read_when_deleted') && $tbox == $config->get('trash_mbox')) { 
     2155            // don't flush cache (4th argument) 
     2156            $this->set_flag($uids, 'SEEN', $fbox, true); 
     2157        } 
     2158 
     2159        // move messages 
     2160        $move = $this->conn->move($uids, $from_mbox, $to_mbox); 
     2161        $moved = !($move === false || $move < 0); 
     2162 
     2163        // send expunge command in order to have the moved message 
     2164        // really deleted from the source mailbox 
     2165        if ($moved) { 
     2166            $this->_expunge($from_mbox, false, $uids); 
     2167            $this->_clear_messagecount($from_mbox); 
     2168            $this->_clear_messagecount($to_mbox); 
     2169        } 
     2170        // moving failed 
     2171        else if ($config->get('delete_always', false) && $tbox == $config->get('trash_mbox')) { 
     2172            $moved = $this->delete_message($uids, $fbox); 
     2173        } 
     2174 
     2175        if ($moved) { 
     2176            // unset threads internal cache 
     2177            unset($this->icache['threads']); 
     2178 
     2179            // remove message ids from search set 
     2180            if ($this->search_set && $from_mbox == $this->mailbox) { 
     2181                // threads are too complicated to just remove messages from set 
     2182                if ($this->search_threads || $all_mode) 
     2183                    $this->refresh_search(); 
     2184                else { 
     2185                    $uids = explode(',', $uids); 
     2186                    foreach ($uids as $uid) 
     2187                        $a_mids[] = $this->_uid2id($uid, $from_mbox); 
     2188                    $this->search_set = array_diff($this->search_set, $a_mids); 
     2189                } 
     2190            } 
     2191 
     2192            // update cached message headers 
     2193            $cache_key = $from_mbox.'.msg'; 
     2194            if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { 
     2195                // clear cache from the lowest index on 
     2196                $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); 
     2197            } 
     2198        } 
     2199 
     2200        return $moved; 
     2201    } 
     2202 
     2203 
     2204    /** 
     2205     * Copy a message from one mailbox to another 
     2206     * 
     2207     * @param mixed  Message UIDs as array or comma-separated string, or '*' 
     2208     * @param string Target mailbox 
     2209     * @param string Source mailbox 
     2210     * @return boolean True on success, False on error 
     2211     */ 
     2212    function copy_message($uids, $to_mbox, $from_mbox='') 
     2213    { 
     2214        $fbox = $from_mbox; 
     2215        $tbox = $to_mbox; 
     2216        $to_mbox = $this->mod_mailbox($to_mbox); 
     2217        $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; 
     2218 
     2219        list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); 
     2220 
     2221        // exit if no message uids are specified 
     2222        if (empty($uids)) 
     2223            return false; 
     2224 
     2225        // make sure mailbox exists 
     2226        if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { 
     2227            if (in_array($tbox, $this->default_folders)) 
     2228                $this->create_mailbox($tbox, true); 
     2229            else 
     2230                return false; 
     2231        } 
     2232 
     2233        // copy messages 
     2234        $copy = $this->conn->copy($uids, $from_mbox, $to_mbox); 
     2235        $copied = !($copy === false || $copy < 0); 
     2236 
     2237        if ($copied) { 
     2238            $this->_clear_messagecount($to_mbox); 
     2239        } 
     2240 
     2241        return $copied; 
     2242    } 
     2243 
     2244 
     2245    /** 
     2246     * Mark messages as deleted and expunge mailbox 
     2247     * 
     2248     * @param mixed  Message UIDs as array or comma-separated string, or '*' 
     2249     * @param string Source mailbox 
     2250     * @return boolean True on success, False on error 
     2251     */ 
     2252    function delete_message($uids, $mbox_name='') 
     2253    { 
     2254        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     2255 
     2256        list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); 
     2257 
     2258        // exit if no message uids are specified 
     2259        if (empty($uids)) 
     2260            return false; 
     2261 
     2262        $deleted = $this->conn->delete($mailbox, $uids); 
     2263 
     2264        if ($deleted) { 
     2265            // send expunge command in order to have the deleted message 
     2266            // really deleted from the mailbox 
     2267            $this->_expunge($mailbox, false, $uids); 
     2268            $this->_clear_messagecount($mailbox); 
     2269            unset($this->uid_id_map[$mailbox]); 
     2270 
     2271            // unset threads internal cache 
     2272            unset($this->icache['threads']); 
     2273       
     2274            // remove message ids from search set 
     2275            if ($this->search_set && $mailbox == $this->mailbox) { 
     2276                // threads are too complicated to just remove messages from set 
     2277                if ($this->search_threads || $all_mode) 
     2278                    $this->refresh_search(); 
     2279                else { 
     2280                    $uids = explode(',', $uids); 
     2281                    foreach ($uids as $uid) 
     2282                        $a_mids[] = $this->_uid2id($uid, $mailbox); 
     2283                    $this->search_set = array_diff($this->search_set, $a_mids); 
     2284                } 
     2285            } 
     2286 
     2287            // remove deleted messages from cache 
     2288            $cache_key = $mailbox.'.msg'; 
     2289            if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { 
     2290                // clear cache from the lowest index on 
     2291                $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); 
     2292            } 
     2293        } 
     2294 
     2295        return $deleted; 
     2296    } 
     2297 
     2298 
     2299    /** 
     2300     * Clear all messages in a specific mailbox 
     2301     * 
     2302     * @param string Mailbox name 
     2303     * @return int Above 0 on success 
     2304     */ 
     2305    function clear_mailbox($mbox_name=NULL) 
     2306    { 
     2307        $mailbox = !empty($mbox_name) ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     2308        $msg_count = $this->_messagecount($mailbox, 'ALL'); 
     2309     
     2310        if (!$msg_count) { 
     2311            return 0; 
     2312        } 
     2313         
     2314        $cleared = $this->conn->clearFolder($mailbox); 
     2315       
     2316        // make sure the message count cache is cleared as well 
     2317        if ($cleared) { 
     2318            $this->clear_message_cache($mailbox.'.msg');       
     2319            $a_mailbox_cache = $this->get_cache('messagecount'); 
     2320            unset($a_mailbox_cache[$mailbox]); 
     2321            $this->update_cache('messagecount', $a_mailbox_cache); 
     2322        } 
     2323         
     2324        return $cleared; 
     2325    } 
     2326 
     2327 
     2328    /** 
     2329     * Send IMAP expunge command and clear cache 
     2330     * 
     2331     * @param string Mailbox name 
     2332     * @param boolean False if cache should not be cleared 
     2333     * @return boolean True on success 
     2334     */ 
     2335    function expunge($mbox_name='', $clear_cache=true) 
     2336    { 
     2337        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     2338        return $this->_expunge($mailbox, $clear_cache); 
     2339    } 
     2340 
     2341 
     2342    /** 
     2343     * Send IMAP expunge command and clear cache 
     2344     * 
     2345     * @param string     Mailbox name 
     2346     * @param boolean  False if cache should not be cleared 
     2347     * @param mixed    Message UIDs as array or comma-separated string, or '*' 
     2348     * @return boolean True on success 
     2349     * @access private 
     2350     * @see rcube_imap::expunge() 
     2351     */ 
     2352    private function _expunge($mailbox, $clear_cache=true, $uids=NULL) 
     2353    { 
     2354        if ($uids && $this->get_capability('UIDPLUS'))  
     2355            $a_uids = is_array($uids) ? join(',', $uids) : $uids; 
     2356        else 
     2357            $a_uids = NULL; 
     2358 
     2359        $result = $this->conn->expunge($mailbox, $a_uids); 
     2360 
     2361        if ($result>=0 && $clear_cache) { 
     2362            $this->clear_message_cache($mailbox.'.msg'); 
     2363            $this->_clear_messagecount($mailbox); 
     2364        } 
     2365       
     2366        return $result; 
     2367    } 
     2368 
     2369 
     2370    /** 
     2371     * Parse message UIDs input 
     2372     * 
     2373     * @param mixed  UIDs array or comma-separated list or '*' or '1:*' 
     2374     * @param string Mailbox name 
     2375     * @return array Two elements array with UIDs converted to list and ALL flag  
     2376     * @access private 
     2377     */ 
     2378    private function _parse_uids($uids, $mailbox) 
     2379    { 
     2380        if ($uids === '*' || $uids === '1:*') { 
     2381            if (empty($this->search_set)) { 
     2382                $uids = '1:*'; 
     2383                $all = true; 
     2384            } 
     2385            // get UIDs from current search set 
     2386            // @TODO: skip fetchUIDs() and work with IDs instead of UIDs (?) 
     2387            else { 
     2388                if ($this->search_threads) 
     2389                    $uids = $this->conn->fetchUIDs($mailbox, array_keys($this->search_set['depth'])); 
     2390                else 
     2391                    $uids = $this->conn->fetchUIDs($mailbox, $this->search_set); 
     2392       
     2393                // save ID-to-UID mapping in local cache 
     2394                if (is_array($uids)) 
     2395                    foreach ($uids as $id => $uid) 
     2396                        $this->uid_id_map[$mailbox][$uid] = $id; 
     2397 
     2398                $uids = join(',', $uids); 
     2399            } 
     2400        } 
     2401        else { 
     2402            if (is_array($uids)) 
     2403                $uids = join(',', $uids); 
     2404 
     2405            if (preg_match('/[^0-9,]/', $uids)) 
     2406                $uids = ''; 
     2407        } 
     2408 
     2409        return array($uids, (bool) $all); 
     2410    } 
     2411 
     2412 
     2413    /** 
     2414     * Translate UID to message ID 
     2415     * 
     2416     * @param int    Message UID 
     2417     * @param string Mailbox name 
     2418     * @return int   Message ID 
     2419     */ 
     2420    function get_id($uid, $mbox_name=NULL)  
     2421    { 
     2422        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     2423        return $this->_uid2id($uid, $mailbox); 
     2424    } 
     2425 
     2426 
     2427    /** 
     2428     * Translate message number to UID 
     2429     * 
     2430     * @param int    Message ID 
     2431     * @param string Mailbox name 
     2432     * @return int   Message UID 
     2433     */ 
     2434    function get_uid($id,$mbox_name=NULL) 
     2435    { 
     2436        $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
     2437        return $this->_id2uid($id, $mailbox); 
     2438    } 
     2439 
     2440 
     2441 
     2442    /* -------------------------------- 
     2443     *        folder managment 
     2444     * --------------------------------*/ 
     2445 
     2446    /** 
     2447     * Public method for mailbox listing. 
     2448     * 
     2449     * Converts mailbox name with root dir first 
     2450     * 
     2451     * @param   string  Optional root folder 
     2452     * @param   string  Optional filter for mailbox listing 
     2453     * @return  array   List of mailboxes/folders 
     2454     * @access  public 
     2455     */ 
     2456    function list_mailboxes($root='', $filter='*') 
     2457    { 
     2458        $a_out = array(); 
     2459        $a_mboxes = $this->_list_mailboxes($root, $filter); 
     2460 
     2461        foreach ($a_mboxes as $mbox_row) { 
     2462            $name = $this->mod_mailbox($mbox_row, 'out'); 
     2463            if (strlen($name)) 
     2464                $a_out[] = $name; 
     2465        } 
     2466 
     2467        // INBOX should always be available 
     2468        if (!in_array('INBOX', $a_out)) 
     2469            array_unshift($a_out, 'INBOX'); 
     2470 
     2471        // sort mailboxes 
     2472        $a_out = $this->_sort_mailbox_list($a_out); 
     2473 
     2474        return $a_out; 
     2475    } 
     2476 
     2477 
     2478    /** 
     2479     * Private method for mailbox listing 
     2480     * 
     2481     * @return  array   List of mailboxes/folders 
     2482     * @see     rcube_imap::list_mailboxes() 
     2483     * @access  private 
     2484     */ 
     2485    private function _list_mailboxes($root='', $filter='*') 
     2486    { 
     2487        $a_defaults = $a_out = array(); 
     2488     
     2489        // get cached folder list     
     2490        $a_mboxes = $this->get_cache('mailboxes'); 
     2491        if (is_array($a_mboxes)) 
     2492            return $a_mboxes; 
     2493 
     2494        // Give plugins a chance to provide a list of mailboxes 
     2495        $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes', 
     2496            array('root'=>$root,'filter'=>$filter)); 
     2497     
     2498        if (isset($data['folders'])) { 
     2499            $a_folders = $data['folders']; 
     2500        } 
     2501        else { 
     2502            // retrieve list of folders from IMAP server 
     2503            $a_folders = $this->conn->listSubscribed($this->mod_mailbox($root), $filter); 
     2504        } 
     2505     
     2506        if (!is_array($a_folders) || !sizeof($a_folders)) 
     2507            $a_folders = array(); 
     2508 
     2509        // write mailboxlist to cache 
     2510        $this->update_cache('mailboxes', $a_folders); 
     2511     
     2512        return $a_folders; 
     2513    } 
     2514 
     2515 
     2516    /** 
     2517     * Get a list of all folders available on the IMAP server 
     2518     *  
     2519     * @param string IMAP root dir 
     2520     * @return array Indexed array with folder names 
     2521     */ 
     2522    function list_unsubscribed($root='') 
     2523    { 
     2524        static $sa_unsubscribed; 
     2525     
     2526        if (is_array($sa_unsubscribed)) 
     2527            return $sa_unsubscribed; 
     2528       
     2529        // retrieve list of folders from IMAP server 
     2530        $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), '*'); 
     2531 
     2532        // modify names with root dir 
     2533        foreach ($a_mboxes as $mbox_name) { 
     2534            $name = $this->mod_mailbox($mbox_name, 'out'); 
     2535            if (strlen($name)) 
     2536                $a_folders[] = $name; 
     2537        } 
     2538 
     2539        // filter folders and sort them 
     2540        $sa_unsubscribed = $this->_sort_mailbox_list($a_folders); 
     2541        return $sa_unsubscribed; 
     2542    } 
     2543 
     2544 
     2545    /** 
     2546     * Get mailbox quota information 
     2547     * added by Nuny 
     2548     *  
     2549     * @return mixed Quota info or False if not supported 
     2550     */ 
     2551    function get_quota() 
     2552    { 
     2553        if ($this->get_capability('QUOTA')) 
     2554            return $this->conn->getQuota(); 
     2555         
    16972556        return false; 
    1698       } 
    1699  
    1700       $struct = &$this->_structure_part($structure); 
    1701       $struct->headers = get_object_vars($headers); 
    1702  
    1703       // don't trust given content-type 
    1704       if (empty($struct->parts) && !empty($struct->headers['ctype'])) 
    1705         { 
    1706         $struct->mime_id = '1'; 
    1707         $struct->mimetype = strtolower($struct->headers['ctype']); 
    1708         list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); 
    1709         } 
    1710  
    1711       // write structure to cache 
    1712       if ($this->caching_enabled) 
    1713         $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct); 
    1714       } 
    1715  
    1716     return $struct; 
    1717     } 
    1718  
    1719    
    1720   /** 
    1721    * Build message part object 
    1722    * 
    1723    * @access private 
    1724    */ 
    1725   function &_structure_part($part, $count=0, $parent='', $mime_headers=null, $raw_headers=null) 
    1726     { 
    1727     $struct = new rcube_message_part; 
    1728     $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; 
    1729  
    1730     // multipart 
    1731     if (is_array($part[0])) 
    1732       { 
    1733       $struct->ctype_primary = 'multipart'; 
     2557    } 
     2558 
     2559 
     2560    /** 
     2561     * Subscribe to a specific mailbox(es) 
     2562     * 
     2563     * @param array Mailbox name(s) 
     2564     * @return boolean True on success 
     2565     */  
     2566    function subscribe($a_mboxes) 
     2567    { 
     2568        if (!is_array($a_mboxes)) 
     2569            $a_mboxes = array($a_mboxes); 
     2570 
     2571        // let this common function do the main work 
     2572        return $this->_change_subscription($a_mboxes, 'subscribe'); 
     2573    } 
     2574 
     2575 
     2576    /** 
     2577     * Unsubscribe mailboxes 
     2578     * 
     2579     * @param array Mailbox name(s) 
     2580     * @return boolean True on success 
     2581     */ 
     2582    function unsubscribe($a_mboxes) 
     2583    { 
     2584        if (!is_array($a_mboxes)) 
     2585            $a_mboxes = array($a_mboxes); 
     2586 
     2587        // let this common function do the main work 
     2588        return $this->_change_subscription($a_mboxes, 'unsubscribe'); 
     2589    } 
     2590 
     2591 
     2592    /** 
     2593     * Create a new mailbox on the server and register it in local cache 
     2594     * 
     2595     * @param string  New mailbox name (as utf-7 string) 
     2596     * @param boolean True if the new mailbox should be subscribed 
     2597     * @param string  Name of the created mailbox, false on error 
     2598     */ 
     2599    function create_mailbox($name, $subscribe=false) 
     2600    { 
     2601        $result = false; 
     2602     
     2603        // reduce mailbox name to 100 chars 
     2604        $name = substr($name, 0, 100); 
     2605        $abs_name = $this->mod_mailbox($name); 
     2606        $result = $this->conn->createFolder($abs_name); 
     2607 
     2608        // try to subscribe it 
     2609        if ($result && $subscribe) 
     2610            $this->subscribe($name); 
     2611 
     2612        return $result ? $name : false; 
     2613    } 
     2614 
     2615 
     2616    /** 
     2617     * Set a new name to an existing mailbox 
     2618     * 
     2619     * @param string Mailbox to rename (as utf-7 string) 
     2620     * @param string New mailbox name (as utf-7 string) 
     2621     * @return string Name of the renames mailbox, False on error 
     2622     */ 
     2623    function rename_mailbox($mbox_name, $new_name) 
     2624    { 
     2625        $result = false; 
     2626 
     2627        // encode mailbox name and reduce it to 100 chars 
     2628        $name = substr($new_name, 0, 100); 
     2629 
     2630        // make absolute path 
     2631        $mailbox = $this->mod_mailbox($mbox_name); 
     2632        $abs_name = $this->mod_mailbox($name); 
     2633     
     2634        // check if mailbox is subscribed 
     2635        $a_subscribed = $this->_list_mailboxes(); 
     2636        $subscribed = in_array($mailbox, $a_subscribed); 
     2637     
     2638        // unsubscribe folder 
     2639        if ($subscribed) 
     2640            $this->conn->unsubscribe($mailbox); 
     2641 
     2642        if (strlen($abs_name)) 
     2643            $result = $this->conn->renameFolder($mailbox, $abs_name); 
     2644 
     2645        if ($result) { 
     2646            $delm = $this->get_hierarchy_delimiter(); 
    17342647       
    1735       // find first non-array entry 
    1736       for ($i=1; $i<count($part); $i++) 
    1737         if (!is_array($part[$i])) 
    1738           { 
    1739           $struct->ctype_secondary = strtolower($part[$i]); 
    1740           break; 
    1741           } 
    1742            
    1743       $struct->mimetype = 'multipart/'.$struct->ctype_secondary; 
    1744  
    1745       // build parts list for headers pre-fetching 
    1746       for ($i=0, $count=0; $i<count($part); $i++) 
    1747         if (is_array($part[$i]) && count($part[$i]) > 3) { 
    1748           // fetch message headers if message/rfc822 or named part (could contain Content-Location header) 
    1749           if (!is_array($part[$i][0])) { 
    1750             $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; 
    1751             if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') { 
    1752               $raw_part_headers[] = $tmp_part_id; 
    1753               $mime_part_headers[] = $tmp_part_id; 
    1754             } 
    1755             else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) { 
    1756               $mime_part_headers[] = $tmp_part_id; 
    1757             } 
    1758           } 
    1759         } 
    1760          
    1761       // pre-fetch headers of all parts (in one command for better performance) 
    1762       // @TODO: we could do this before _structure_part() call, to fetch 
    1763       // headers for parts on all levels 
    1764       if ($mime_part_headers) 
    1765         $mime_part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, 
    1766           $this->_msg_id, $mime_part_headers); 
    1767       // we'll need a real content-type of message/rfc822 part 
    1768       if ($raw_part_headers) 
    1769         $raw_part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, 
    1770           $this->_msg_id, $raw_part_headers, false); 
    1771  
    1772       $struct->parts = array(); 
    1773       for ($i=0, $count=0; $i<count($part); $i++) 
    1774         if (is_array($part[$i]) && count($part[$i]) > 3) { 
    1775           $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; 
    1776           $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id, 
    1777             $mime_part_headers[$tmp_part_id], $raw_part_headers[$tmp_part_id]); 
    1778         } 
    1779  
    1780       return $struct; 
    1781       } 
    1782  
    1783  
    1784     // regular part 
    1785     $struct->ctype_primary = strtolower($part[0]); 
    1786     $struct->ctype_secondary = strtolower($part[1]); 
    1787     $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary; 
    1788  
    1789     // read content type parameters 
    1790     if (is_array($part[2])) 
    1791       { 
    1792       $struct->ctype_parameters = array(); 
    1793       for ($i=0; $i<count($part[2]); $i+=2) 
    1794         $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1]; 
    1795          
    1796       if (isset($struct->ctype_parameters['charset'])) 
    1797         $struct->charset = $struct->ctype_parameters['charset']; 
    1798       } 
    1799      
    1800     // read content encoding 
    1801     if (!empty($part[5]) && $part[5]!='NIL') 
    1802       { 
    1803       $struct->encoding = strtolower($part[5]); 
    1804       $struct->headers['content-transfer-encoding'] = $struct->encoding; 
    1805       } 
    1806      
    1807     // get part size 
    1808     if (!empty($part[6]) && $part[6]!='NIL') 
    1809       $struct->size = intval($part[6]); 
    1810  
    1811     // read part disposition 
    1812     $di = count($part) - 2; 
    1813     if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) || 
    1814         (is_array($part[--$di]) && count($part[$di]) == 2)) 
    1815       { 
    1816       $struct->disposition = strtolower($part[$di][0]); 
    1817  
    1818       if (is_array($part[$di][1])) 
    1819         for ($n=0; $n<count($part[$di][1]); $n+=2) 
    1820           $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; 
    1821       } 
     2648            // check if mailbox children are subscribed 
     2649            foreach ($a_subscribed as $c_subscribed) 
     2650                if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) { 
     2651                    $this->conn->unsubscribe($c_subscribed); 
     2652                    $this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/', 
     2653                        $abs_name, $c_subscribed)); 
     2654                } 
     2655 
     2656            // clear cache 
     2657            $this->clear_message_cache($mailbox.'.msg'); 
     2658            $this->clear_cache('mailboxes');       
     2659        } 
     2660 
     2661        // try to subscribe it 
     2662        if ($result && $subscribed) 
     2663            $this->conn->subscribe($abs_name); 
     2664 
     2665        return $result ? $name : false; 
     2666    } 
     2667 
     2668 
     2669    /** 
     2670     * Remove mailboxes from server 
     2671     * 
     2672     * @param string Mailbox name(s) string/array 
     2673     * @return boolean True on success 
     2674     */ 
     2675    function delete_mailbox($mbox_name) 
     2676    { 
     2677        $deleted = false; 
     2678 
     2679        if (is_array($mbox_name)) 
     2680            $a_mboxes = $mbox_name; 
     2681        else if (is_string($mbox_name) && strlen($mbox_name)) 
     2682            $a_mboxes = explode(',', $mbox_name); 
     2683 
     2684        if (is_array($a_mboxes)) { 
     2685            foreach ($a_mboxes as $mbox_name) { 
     2686                $mailbox = $this->mod_mailbox($mbox_name); 
     2687                $sub_mboxes = $this->conn->listMailboxes($this->mod_mailbox(''), 
     2688                    $mbox_name . $this->delimiter . '*'); 
     2689 
     2690                // unsubscribe mailbox before deleting 
     2691                $this->conn->unsubscribe($mailbox); 
     2692 
     2693                // send delete command to server 
     2694                $result = $this->conn->deleteFolder($mailbox); 
     2695                if ($result >= 0) { 
     2696                    $deleted = true; 
     2697                    $this->clear_message_cache($mailbox.'.msg'); 
     2698                    } 
     2699           
     2700                foreach ($sub_mboxes as $c_mbox) { 
     2701                    if ($c_mbox != 'INBOX') { 
     2702                        $this->conn->unsubscribe($c_mbox); 
     2703                        $result = $this->conn->deleteFolder($c_mbox); 
     2704                        if ($result >= 0) { 
     2705                            $deleted = true; 
     2706                            $this->clear_message_cache($c_mbox.'.msg'); 
     2707                        } 
     2708                    } 
     2709                } 
     2710            } 
     2711        } 
     2712 
     2713        // clear mailboxlist cache 
     2714        if ($deleted) 
     2715            $this->clear_cache('mailboxes'); 
     2716 
     2717        return $deleted; 
     2718    } 
     2719 
     2720 
     2721    /** 
     2722     * Create all folders specified as default 
     2723     */ 
     2724    function create_default_folders() 
     2725    { 
     2726        // create default folders if they do not exist 
     2727        foreach ($this->default_folders as $folder) { 
     2728            if (!$this->mailbox_exists($folder)) 
     2729                $this->create_mailbox($folder, true); 
     2730            else if (!$this->mailbox_exists($folder, true)) 
     2731                $this->subscribe($folder); 
     2732        } 
     2733    } 
     2734 
     2735 
     2736    /** 
     2737     * Checks if folder exists and is subscribed 
     2738     * 
     2739     * @param string   Folder name 
     2740     * @param boolean  Enable subscription checking 
     2741     * @return boolean TRUE or FALSE 
     2742     */ 
     2743    function mailbox_exists($mbox_name, $subscription=false) 
     2744    { 
     2745        if ($mbox_name) { 
     2746            if ($mbox_name == 'INBOX') 
     2747                return true; 
     2748 
     2749            if ($subscription) { 
     2750                if ($a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name)) 
     2751                    return true; 
     2752            } 
     2753            else { 
     2754                $a_folders = $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_mbox); 
     2755         
     2756                if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) 
     2757                return true; 
     2758            } 
     2759        } 
     2760 
     2761        return false; 
     2762    } 
     2763 
     2764 
     2765    /** 
     2766     * Modify folder name for input/output according to root dir and namespace 
     2767     * 
     2768     * @param string  Folder name 
     2769     * @param string  Mode 
     2770     * @return string Folder name 
     2771     */ 
     2772    function mod_mailbox($mbox_name, $mode='in') 
     2773    { 
     2774        if ($mbox_name == 'INBOX') 
     2775            return $mbox_name; 
     2776 
     2777        if (!empty($this->root_dir)) { 
     2778            if ($mode=='in') 
     2779                $mbox_name = $this->root_dir.$this->delimiter.$mbox_name; 
     2780            else if (!empty($mbox_name)) // $mode=='out' 
     2781                $mbox_name = substr($mbox_name, strlen($this->root_dir)+1); 
     2782        } 
     2783     
     2784        return $mbox_name; 
     2785    } 
     2786 
     2787 
     2788    /* -------------------------------- 
     2789     *   internal caching methods 
     2790     * --------------------------------*/ 
     2791 
     2792    /** 
     2793     * @access public 
     2794     */ 
     2795    function set_caching($set) 
     2796    { 
     2797        if ($set && is_object($this->db)) 
     2798            $this->caching_enabled = true; 
     2799        else 
     2800            $this->caching_enabled = false; 
     2801    } 
     2802 
     2803    /** 
     2804     * @access public 
     2805     */ 
     2806    function get_cache($key) 
     2807    { 
     2808        // read cache (if it was not read before) 
     2809        if (!count($this->cache) && $this->caching_enabled) { 
     2810            return $this->_read_cache_record($key); 
     2811        } 
     2812     
     2813        return $this->cache[$key]; 
     2814    } 
     2815 
     2816    /** 
     2817     * @access private 
     2818     */ 
     2819    private function update_cache($key, $data) 
     2820    { 
     2821        $this->cache[$key] = $data; 
     2822        $this->cache_changed = true; 
     2823        $this->cache_changes[$key] = true; 
     2824    } 
     2825 
     2826    /** 
     2827     * @access private 
     2828     */ 
     2829    private function write_cache() 
     2830    { 
     2831        if ($this->caching_enabled && $this->cache_changed) { 
     2832            foreach ($this->cache as $key => $data) { 
     2833                if ($this->cache_changes[$key]) 
     2834                    $this->_write_cache_record($key, serialize($data)); 
     2835            } 
     2836        } 
     2837    } 
     2838 
     2839    /** 
     2840     * @access public 
     2841     */ 
     2842    function clear_cache($key=NULL) 
     2843    { 
     2844        if (!$this->caching_enabled) 
     2845            return; 
     2846     
     2847        if ($key===NULL) { 
     2848            foreach ($this->cache as $key => $data) 
     2849                $this->_clear_cache_record($key); 
     2850 
     2851            $this->cache = array(); 
     2852            $this->cache_changed = false; 
     2853            $this->cache_changes = array(); 
     2854        } 
     2855        else { 
     2856            $this->_clear_cache_record($key); 
     2857            $this->cache_changes[$key] = false; 
     2858            unset($this->cache[$key]); 
     2859        } 
     2860    } 
     2861 
     2862    /** 
     2863     * @access private 
     2864     */ 
     2865    private function _read_cache_record($key) 
     2866    { 
     2867        if ($this->db) { 
     2868            // get cached data from DB 
     2869            $sql_result = $this->db->query( 
     2870                "SELECT cache_id, data, cache_key ". 
     2871                "FROM ".get_table_name('cache'). 
     2872                " WHERE user_id=? ". 
     2873                    "AND cache_key LIKE 'IMAP.%'", 
     2874                $_SESSION['user_id']); 
     2875 
     2876            while ($sql_arr = $this->db->fetch_assoc($sql_result)) { 
     2877                    $sql_key = preg_replace('/^IMAP\./', '', $sql_arr['cache_key']); 
     2878                $this->cache_keys[$sql_key] = $sql_arr['cache_id']; 
     2879                    if (!isset($this->cache[$sql_key])) 
     2880                        $this->cache[$sql_key] = $sql_arr['data'] ? unserialize($sql_arr['data']) : false; 
     2881            } 
     2882        } 
     2883 
     2884        return $this->cache[$key]; 
     2885    } 
     2886 
     2887    /** 
     2888     * @access private 
     2889     */ 
     2890    private function _write_cache_record($key, $data) 
     2891    { 
     2892        if (!$this->db) 
     2893            return false; 
     2894 
     2895        // update existing cache record 
     2896        if ($this->cache_keys[$key]) { 
     2897            $this->db->query( 
     2898                "UPDATE ".get_table_name('cache'). 
     2899                " SET created=". $this->db->now().", data=? ". 
     2900                "WHERE user_id=? ". 
     2901                "AND cache_key=?", 
     2902                $data, 
     2903                $_SESSION['user_id'], 
     2904                'IMAP.'.$key); 
     2905        } 
     2906        // add new cache record 
     2907        else { 
     2908            $this->db->query( 
     2909                "INSERT INTO ".get_table_name('cache'). 
     2910                " (created, user_id, cache_key, data) ". 
     2911                "VALUES (".$this->db->now().", ?, ?, ?)", 
     2912                $_SESSION['user_id'], 
     2913                'IMAP.'.$key, 
     2914                $data); 
     2915 
     2916            // get cache entry ID for this key 
     2917            $sql_result = $this->db->query( 
     2918                "SELECT cache_id ". 
     2919                "FROM ".get_table_name('cache'). 
     2920                " WHERE user_id=? ". 
     2921                "AND cache_key=?", 
     2922                $_SESSION['user_id'], 
     2923                'IMAP.'.$key); 
     2924 
     2925            if ($sql_arr = $this->db->fetch_assoc($sql_result)) 
     2926                $this->cache_keys[$key] = $sql_arr['cache_id']; 
     2927        } 
     2928    } 
     2929 
     2930    /** 
     2931     * @access private 
     2932     */ 
     2933    private function _clear_cache_record($key) 
     2934    { 
     2935        $this->db->query( 
     2936            "DELETE FROM ".get_table_name('cache'). 
     2937            " WHERE user_id=? ". 
     2938            "AND cache_key=?", 
     2939            $_SESSION['user_id'], 
     2940            'IMAP.'.$key); 
    18222941       
    1823     // get child parts 
    1824     if (is_array($part[8]) && $di != 8) 
    1825       { 
    1826       $struct->parts = array(); 
    1827       for ($i=0, $count=0; $i<count($part[8]); $i++) 
    1828         if (is_array($part[8][$i]) && count($part[8][$i]) > 5) 
    1829           $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id); 
    1830       } 
    1831  
    1832     // get part ID 
    1833     if (!empty($part[3]) && $part[3]!='NIL') 
    1834       { 
    1835       $struct->content_id = $part[3]; 
    1836       $struct->headers['content-id'] = $part[3]; 
    1837      
    1838       if (empty($struct->disposition)) 
    1839         $struct->disposition = 'inline'; 
    1840       } 
    1841      
    1842     // fetch message headers if message/rfc822 or named part (could contain Content-Location header) 
    1843     if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) { 
    1844       if (empty($mime_headers)) 
    1845         $mime_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $struct->mime_id); 
    1846       $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; 
    1847  
    1848       // get real headers for message of type 'message/rfc822' 
    1849       if ($struct->mimetype == 'message/rfc822') { 
    1850         if (empty($raw_headers)) 
    1851           $raw_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false); 
    1852         $struct->real_headers = $this->_parse_headers($raw_headers); 
    1853  
    1854         // get real content-type of message/rfc822 
    1855         if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) { 
    1856           $struct->real_mimetype = strtolower($matches[1]); 
    1857           }                                                     
    1858         } 
    1859       } 
    1860  
    1861     if ($struct->ctype_primary=='message') { 
    1862       if (is_array($part[8]) && $di != 8 && empty($struct->parts)) 
    1863         $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id); 
    1864       } 
    1865  
    1866     // normalize filename property 
    1867     $this->_set_part_filename($struct, $mime_headers); 
    1868  
    1869     return $struct; 
    1870     } 
    1871      
    1872  
    1873   /** 
    1874    * Set attachment filename from message part structure  
    1875    * 
    1876    * @access private 
    1877    * @param  object rcube_message_part Part object 
    1878    * @param  string Part's raw headers 
    1879    */ 
    1880   private function _set_part_filename(&$part, $headers=null) 
    1881     { 
    1882     if (!empty($part->d_parameters['filename'])) 
    1883       $filename_mime = $part->d_parameters['filename']; 
    1884     else if (!empty($part->d_parameters['filename*'])) 
    1885       $filename_encoded = $part->d_parameters['filename*']; 
    1886     else if (!empty($part->ctype_parameters['name*'])) 
    1887       $filename_encoded = $part->ctype_parameters['name*']; 
    1888     // RFC2231 value continuations 
    1889     // TODO: this should be rewrited to support RFC2231 4.1 combinations 
    1890     else if (!empty($part->d_parameters['filename*0'])) { 
    1891       $i = 0; 
    1892       while (isset($part->d_parameters['filename*'.$i])) { 
    1893         $filename_mime .= $part->d_parameters['filename*'.$i]; 
    1894         $i++; 
    1895       } 
    1896       // some servers (eg. dovecot-1.x) have no support for parameter value continuations 
    1897       // we must fetch and parse headers "manually" 
    1898       if ($i<2) { 
    1899         if (!$headers) 
    1900           $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); 
    1901         $filename_mime = ''; 
    1902         $i = 0; 
    1903         while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
    1904           $filename_mime .= $matches[1]; 
    1905           $i++; 
    1906         } 
    1907       } 
    1908     } 
    1909     else if (!empty($part->d_parameters['filename*0*'])) { 
    1910       $i = 0; 
    1911       while (isset($part->d_parameters['filename*'.$i.'*'])) { 
    1912         $filename_encoded .= $part->d_parameters['filename*'.$i.'*']; 
    1913         $i++; 
    1914       } 
    1915       if ($i<2) { 
    1916         if (!$headers) 
    1917           $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); 
    1918         $filename_encoded = ''; 
    1919         $i = 0; $matches = array(); 
    1920         while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
    1921           $filename_encoded .= $matches[1]; 
    1922           $i++; 
    1923         } 
    1924       } 
    1925     } 
    1926     else if (!empty($part->ctype_parameters['name*0'])) { 
    1927       $i = 0; 
    1928       while (isset($part->ctype_parameters['name*'.$i])) { 
    1929         $filename_mime .= $part->ctype_parameters['name*'.$i]; 
    1930         $i++; 
    1931       } 
    1932       if ($i<2) { 
    1933         if (!$headers) 
    1934           $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); 
    1935         $filename_mime = ''; 
    1936         $i = 0; $matches = array(); 
    1937         while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
    1938           $filename_mime .= $matches[1]; 
    1939           $i++; 
    1940         } 
    1941       } 
    1942     } 
    1943     else if (!empty($part->ctype_parameters['name*0*'])) { 
    1944       $i = 0; 
    1945       while (isset($part->ctype_parameters['name*'.$i.'*'])) { 
    1946         $filename_encoded .= $part->ctype_parameters['name*'.$i.'*']; 
    1947         $i++; 
    1948       } 
    1949       if ($i<2) { 
    1950         if (!$headers) 
    1951           $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); 
    1952         $filename_encoded = ''; 
    1953         $i = 0; $matches = array(); 
    1954         while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { 
    1955           $filename_encoded .= $matches[1]; 
    1956           $i++; 
    1957         } 
    1958       } 
    1959     } 
    1960     // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird) 
    1961     else if (!empty($part->ctype_parameters['name'])) 
    1962       $filename_mime = $part->ctype_parameters['name']; 
    1963     // Content-Disposition 
    1964     else if (!empty($part->headers['content-description'])) 
    1965       $filename_mime = $part->headers['content-description']; 
    1966     else 
    1967       return; 
    1968  
    1969     // decode filename 
    1970     if (!empty($filename_mime)) { 
    1971       $part->filename = rcube_imap::decode_mime_string($filename_mime,  
    1972         $part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset : 
    1973           rc_detect_encoding($filename_mime, $this->default_charset))); 
    1974       }  
    1975     else if (!empty($filename_encoded)) { 
    1976       // decode filename according to RFC 2231, Section 4 
    1977       if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) { 
    1978         $filename_charset = $fmatches[1]; 
    1979         $filename_encoded = $fmatches[2]; 
    1980         } 
    1981       $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset); 
    1982       } 
    1983     } 
    1984  
    1985  
    1986   /** 
    1987    * Get charset name from message structure (first part) 
    1988    * 
    1989    * @access private 
    1990    * @param  array  Message structure 
    1991    * @return string Charset name 
    1992    */ 
    1993   function _structure_charset($structure) 
    1994     { 
    1995       while (is_array($structure)) { 
    1996         if (is_array($structure[2]) && $structure[2][0] == 'charset') 
    1997           return $structure[2][1]; 
    1998         $structure = $structure[0]; 
    1999       } 
    2000     }  
    2001  
    2002  
    2003   /** 
    2004    * Fetch message body of a specific message from the server 
    2005    * 
    2006    * @param  int    Message UID 
    2007    * @param  string Part number 
    2008    * @param  object rcube_message_part Part object created by get_structure() 
    2009    * @param  mixed  True to print part, ressource to write part contents in 
    2010    * @param  resource File pointer to save the message part 
    2011    * @return string Message/part body if not printed 
    2012    */ 
    2013   function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL) 
    2014     { 
    2015     // get part encoding if not provided 
    2016     if (!is_object($o_part)) 
    2017       { 
    2018       $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true);  
    2019       $structure = iml_GetRawStructureArray($structure_str); 
    2020       // error or message not found 
    2021       if (empty($structure)) 
    2022         return false; 
    2023  
    2024       $part_type = iml_GetPartTypeCode($structure, $part); 
    2025       $o_part = new rcube_message_part; 
    2026       $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other'); 
    2027       $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part)); 
    2028       $o_part->charset = iml_GetPartCharset($structure, $part); 
    2029       } 
     2942        unset($this->cache_keys[$key]); 
     2943    } 
     2944 
     2945 
     2946 
     2947    /* -------------------------------- 
     2948     *   message caching methods 
     2949     * --------------------------------*/ 
     2950    
     2951    /** 
     2952     * Checks if the cache is up-to-date 
     2953     * 
     2954     * @param string Mailbox name 
     2955     * @param string Internal cache key 
     2956     * @return int   Cache status: -3 = off, -2 = incomplete, -1 = dirty, 1 = OK 
     2957     */ 
     2958    private function check_cache_status($mailbox, $cache_key) 
     2959    { 
     2960        if (!$this->caching_enabled) 
     2961            return -3; 
     2962 
     2963        $cache_index = $this->get_message_cache_index($cache_key); 
     2964        $msg_count = $this->_messagecount($mailbox); 
     2965        $cache_count = count($cache_index); 
     2966 
     2967        // empty mailbox 
     2968        if (!$msg_count) 
     2969            return $cache_count ? -2 : 1; 
     2970 
     2971        // @TODO: We've got one big performance problem in cache status checking method 
     2972        // E.g. mailbox contains 1000 messages, in cache table we've got first 100 
     2973        // of them. Now if we want to display only that 100 (which we've got) 
     2974        // check_cache_status returns 'incomplete' and messages are fetched 
     2975        // from IMAP instead of DB. 
     2976 
     2977        if ($cache_count==$msg_count) { 
     2978            if ($this->skip_deleted) { 
     2979                    $h_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); 
     2980 
     2981                if (empty($h_index)) 
     2982                    return -2; 
     2983 
     2984                    if (sizeof($h_index) == $cache_count) { 
     2985                        $cache_index = array_flip($cache_index); 
     2986                        foreach ($h_index as $idx => $uid) 
     2987                        unset($cache_index[$uid]); 
     2988 
     2989                        if (empty($cache_index)) 
     2990                            return 1; 
     2991                    } 
     2992                    return -2; 
     2993            } else { 
     2994                // get UID of message with highest index 
     2995                $uid = $this->conn->ID2UID($mailbox, $msg_count); 
     2996                $cache_uid = array_pop($cache_index); 
    20302997       
    2031     // TODO: Add caching for message parts 
    2032  
    2033     if (!$part) $part = 'TEXT'; 
    2034  
    2035     $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true, $part, 
    2036         $o_part->encoding, $print, $fp); 
    2037  
    2038     if ($fp || $print) 
    2039       return true; 
    2040  
    2041     // convert charset (if text or message part) 
    2042     if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') { 
    2043       // assume default if no charset specified 
    2044       if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') 
    2045         $o_part->charset = $this->default_charset; 
    2046  
    2047       $body = rcube_charset_convert($body, $o_part->charset); 
    2048       } 
    2049      
    2050     return $body; 
    2051     } 
    2052  
    2053  
    2054   /** 
    2055    * Fetch message body of a specific message from the server 
    2056    * 
    2057    * @param  int    Message UID 
    2058    * @return string Message/part body 
    2059    * @see    rcube_imap::get_message_part() 
    2060    */ 
    2061   function &get_body($uid, $part=1) 
    2062     { 
    2063     $headers = $this->get_headers($uid); 
    2064     return rcube_charset_convert($this->get_message_part($uid, $part, NULL), 
    2065       $headers->charset ? $headers->charset : $this->default_charset); 
    2066     } 
    2067  
    2068  
    2069   /** 
    2070    * Returns the whole message source as string 
    2071    * 
    2072    * @param int  Message UID 
    2073    * @return string Message source string 
    2074    */ 
    2075   function &get_raw_body($uid) 
    2076     { 
    2077     return iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true); 
    2078     } 
    2079  
    2080  
    2081   /** 
    2082    * Returns the message headers as string 
    2083    * 
    2084    * @param int  Message UID 
    2085    * @return string Message headers string 
    2086    */ 
    2087   function &get_raw_headers($uid) 
    2088     { 
    2089     return iil_C_FetchPartHeader($this->conn, $this->mailbox, $uid, true); 
    2090     } 
    2091      
    2092  
    2093   /** 
    2094    * Sends the whole message source to stdout 
    2095    * 
    2096    * @param int  Message UID 
    2097    */  
    2098   function print_raw_body($uid) 
    2099     { 
    2100     iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true, NULL, NULL, true); 
    2101     } 
    2102  
    2103  
    2104   /** 
    2105    * Set message flag to one or several messages 
    2106    * 
    2107    * @param mixed   Message UIDs as array or comma-separated string, or '*' 
    2108    * @param string  Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT 
    2109    * @param string  Folder name 
    2110    * @param boolean True to skip message cache clean up 
    2111    * @return int    Number of flagged messages, -1 on failure 
    2112    */ 
    2113   function set_flag($uids, $flag, $mbox_name=NULL, $skip_cache=false) 
    2114     { 
    2115     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    2116  
    2117     $flag = strtoupper($flag); 
    2118     list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); 
    2119  
    2120     if (strpos($flag, 'UN') === 0) 
    2121       $result = iil_C_UnFlag($this->conn, $mailbox, $uids, substr($flag, 2)); 
    2122     else 
    2123       $result = iil_C_Flag($this->conn, $mailbox, $uids, $flag); 
    2124  
    2125     if ($result >= 0) { 
    2126       // reload message headers if cached 
    2127       if ($this->caching_enabled && !$skip_cache) { 
    2128         $cache_key = $mailbox.'.msg'; 
    2129         if ($all_mode) 
    2130           $this->clear_message_cache($cache_key); 
     2998                // uids of highest message matches -> cache seems OK 
     2999                if ($cache_uid == $uid) 
     3000                    return 1; 
     3001            } 
     3002            // cache is dirty 
     3003            return -1; 
     3004        } 
     3005        // if cache count differs less than 10% report as dirty 
     3006        else if (abs($msg_count - $cache_count) < $msg_count/10) 
     3007            return -1; 
    21313008        else 
    2132           $this->remove_message_cache($cache_key, explode(',', $uids)); 
    2133         } 
    2134       // update counters 
    2135       if ($flag=='SEEN') 
    2136         $this->_set_messagecount($mailbox, 'UNSEEN', $result*(-1)); 
    2137       else if ($flag=='UNSEEN') 
    2138         $this->_set_messagecount($mailbox, 'UNSEEN', $result); 
    2139       else if ($flag=='DELETED') 
    2140         $this->_set_messagecount($mailbox, 'ALL', $result*(-1)); 
    2141       } 
    2142  
    2143     return $result; 
    2144     } 
    2145  
    2146  
    2147   /** 
    2148    * Remove message flag for one or several messages 
    2149    * 
    2150    * @param mixed  Message UIDs as array or comma-separated string, or '*' 
    2151    * @param string Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT 
    2152    * @param string Folder name 
    2153    * @return int   Number of flagged messages, -1 on failure 
    2154    * @see set_flag 
    2155    */ 
    2156   function unset_flag($uids, $flag, $mbox_name=NULL) 
    2157     { 
    2158     return $this->set_flag($uids, 'UN'.$flag, $mbox_name); 
    2159     } 
    2160  
    2161  
    2162   /** 
    2163    * Append a mail message (source) to a specific mailbox 
    2164    * 
    2165    * @param string   Target mailbox 
    2166    * @param string   The message source string or filename 
    2167    * @param string   Headers string if $message contains only the body 
    2168    * @param boolean  True if $message is a filename 
    2169    * 
    2170    * @return boolean True on success, False on error 
    2171    */ 
    2172   function save_message($mbox_name, &$message, $headers='', $is_file=false) 
    2173     { 
    2174     $mailbox = $this->mod_mailbox($mbox_name); 
    2175  
    2176     // make sure mailbox exists 
    2177     if ($this->mailbox_exists($mbox_name, true)) { 
    2178       if ($is_file) { 
    2179         $separator = rcmail::get_instance()->config->header_delimiter(); 
    2180         $saved = iil_C_AppendFromFile($this->conn, $mailbox, $message, 
    2181           $headers, $separator.$separator); 
    2182         } 
    2183       else 
    2184         $saved = iil_C_Append($this->conn, $mailbox, $message); 
    2185       } 
    2186  
    2187     if ($saved) 
    2188       { 
    2189       // increase messagecount of the target mailbox 
    2190       $this->_set_messagecount($mailbox, 'ALL', 1); 
    2191       } 
    2192  
    2193     return $saved; 
    2194     } 
    2195  
    2196  
    2197   /** 
    2198    * Move a message from one mailbox to another 
    2199    * 
    2200    * @param mixed  Message UIDs as array or comma-separated string, or '*' 
    2201    * @param string Target mailbox 
    2202    * @param string Source mailbox 
    2203    * @return boolean True on success, False on error 
    2204    */ 
    2205   function move_message($uids, $to_mbox, $from_mbox='') 
    2206   { 
    2207     $fbox = $from_mbox; 
    2208     $tbox = $to_mbox; 
    2209     $to_mbox = $this->mod_mailbox($to_mbox); 
    2210     $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; 
    2211  
    2212     list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); 
    2213  
    2214     // exit if no message uids are specified 
    2215     if (empty($uids)) 
    2216       return false; 
    2217  
    2218     // make sure mailbox exists 
    2219     if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) 
    2220       { 
    2221       if (in_array($tbox, $this->default_folders)) 
    2222         $this->create_mailbox($tbox, true); 
    2223       else 
    2224         return false; 
    2225       } 
    2226  
    2227     // flag messages as read before moving them 
    2228     $config = rcmail::get_instance()->config; 
    2229     if ($config->get('read_when_deleted') && $tbox == $config->get('trash_mbox')) { 
    2230       // don't flush cache (4th argument) 
    2231       $this->set_flag($uids, 'SEEN', $fbox, true); 
    2232       } 
    2233  
    2234     // move messages 
    2235     $iil_move = iil_C_Move($this->conn, $uids, $from_mbox, $to_mbox); 
    2236     $moved = !($iil_move === false || $iil_move < 0); 
    2237  
    2238     // send expunge command in order to have the moved message 
    2239     // really deleted from the source mailbox 
    2240     if ($moved) { 
    2241       $this->_expunge($from_mbox, false, $uids); 
    2242       $this->_clear_messagecount($from_mbox); 
    2243       $this->_clear_messagecount($to_mbox); 
    2244     } 
    2245     // moving failed 
    2246     else if ($config->get('delete_always', false) && $tbox == $config->get('trash_mbox')) { 
    2247       $moved = $this->delete_message($uids, $fbox); 
    2248     } 
    2249  
    2250     if ($moved) { 
    2251       // unset threads internal cache 
    2252       unset($this->icache['threads']); 
    2253  
    2254       // remove message ids from search set 
    2255       if ($this->search_set && $from_mbox == $this->mailbox) { 
    2256         // threads are too complicated to just remove messages from set 
    2257         if ($this->search_threads || $all_mode) 
    2258           $this->refresh_search(); 
    2259         else { 
    2260           $uids = explode(',', $uids); 
    2261           foreach ($uids as $uid) 
    2262             $a_mids[] = $this->_uid2id($uid, $from_mbox); 
    2263           $this->search_set = array_diff($this->search_set, $a_mids); 
    2264           } 
    2265         } 
    2266  
    2267       // update cached message headers 
    2268       $cache_key = $from_mbox.'.msg'; 
    2269       if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { 
    2270         // clear cache from the lowest index on 
    2271         $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); 
    2272        } 
    2273     } 
    2274  
    2275     return $moved; 
    2276   } 
    2277  
    2278  
    2279   /** 
    2280    * Copy a message from one mailbox to another 
    2281    * 
    2282    * @param mixed  Message UIDs as array or comma-separated string, or '*' 
    2283    * @param string Target mailbox 
    2284    * @param string Source mailbox 
    2285    * @return boolean True on success, False on error 
    2286    */ 
    2287   function copy_message($uids, $to_mbox, $from_mbox='') 
    2288   { 
    2289     $fbox = $from_mbox; 
    2290     $tbox = $to_mbox; 
    2291     $to_mbox = $this->mod_mailbox($to_mbox); 
    2292     $from_mbox = $from_mbox ? $this->mod_mailbox($from_mbox) : $this->mailbox; 
    2293  
    2294     list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox); 
    2295  
    2296     // exit if no message uids are specified 
    2297     if (empty($uids)) 
    2298       return false; 
    2299  
    2300     // make sure mailbox exists 
    2301     if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) 
    2302       { 
    2303       if (in_array($tbox, $this->default_folders)) 
    2304         $this->create_mailbox($tbox, true); 
    2305       else 
    2306         return false; 
    2307       } 
    2308  
    2309     // copy messages 
    2310     $iil_copy = iil_C_Copy($this->conn, $uids, $from_mbox, $to_mbox); 
    2311     $copied = !($iil_copy === false || $iil_copy < 0); 
    2312  
    2313     if ($copied) { 
    2314       $this->_clear_messagecount($to_mbox); 
    2315     } 
    2316  
    2317     return $copied; 
    2318   } 
    2319  
    2320  
    2321   /** 
    2322    * Mark messages as deleted and expunge mailbox 
    2323    * 
    2324    * @param mixed  Message UIDs as array or comma-separated string, or '*' 
    2325    * @param string Source mailbox 
    2326    * @return boolean True on success, False on error 
    2327    */ 
    2328   function delete_message($uids, $mbox_name='') 
    2329   { 
    2330     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    2331  
    2332     list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); 
    2333  
    2334     // exit if no message uids are specified 
    2335     if (empty($uids)) 
    2336       return false; 
    2337  
    2338     $deleted = iil_C_Delete($this->conn, $mailbox, $uids); 
    2339  
    2340     if ($deleted) { 
    2341       // send expunge command in order to have the deleted message 
    2342       // really deleted from the mailbox 
    2343       $this->_expunge($mailbox, false, $uids); 
    2344       $this->_clear_messagecount($mailbox); 
    2345       unset($this->uid_id_map[$mailbox]); 
    2346  
    2347       // unset threads internal cache 
    2348       unset($this->icache['threads']); 
     3009            return -2; 
     3010    } 
     3011 
     3012    /** 
     3013     * @access private 
     3014     */ 
     3015    private function get_message_cache($key, $from, $to, $sort_field, $sort_order) 
     3016    { 
     3017        $cache_key = "$key:$from:$to:$sort_field:$sort_order"; 
     3018     
     3019        // use idx sort as default sorting 
     3020        if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) { 
     3021            $sort_field = 'idx'; 
     3022        } 
     3023     
     3024        if ($this->caching_enabled && !isset($this->cache[$cache_key])) { 
     3025            $this->cache[$cache_key] = array(); 
     3026            $sql_result = $this->db->limitquery( 
     3027                "SELECT idx, uid, headers". 
     3028                " FROM ".get_table_name('messages'). 
     3029                " WHERE user_id=?". 
     3030                " AND cache_key=?". 
     3031                " ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".strtoupper($sort_order), 
     3032                $from, 
     3033                $to - $from, 
     3034                $_SESSION['user_id'], 
     3035                $key); 
     3036 
     3037            while ($sql_arr = $this->db->fetch_assoc($sql_result)) { 
     3038                $uid = $sql_arr['uid']; 
     3039                $this->cache[$cache_key][$uid] =  $this->db->decode(unserialize($sql_arr['headers'])); 
     3040 
     3041                // featch headers if unserialize failed 
     3042                if (empty($this->cache[$cache_key][$uid])) 
     3043                    $this->cache[$cache_key][$uid] = $this->conn->fetchHeader( 
     3044                            preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); 
     3045            } 
     3046        } 
     3047 
     3048        return $this->cache[$cache_key]; 
     3049    } 
     3050 
     3051    /** 
     3052     * @access private 
     3053     */ 
     3054    private function &get_cached_message($key, $uid) 
     3055    { 
     3056        $internal_key = 'message'; 
     3057     
     3058        if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) { 
     3059            $sql_result = $this->db->query( 
     3060                "SELECT idx, headers, structure". 
     3061                " FROM ".get_table_name('messages'). 
     3062                " WHERE user_id=?". 
     3063                " AND cache_key=?". 
     3064                " AND uid=?", 
     3065                $_SESSION['user_id'], 
     3066                $key, 
     3067                $uid); 
     3068 
     3069            if ($sql_arr = $this->db->fetch_assoc($sql_result)) { 
     3070                    $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx']; 
     3071                $this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); 
     3072                if (is_object($this->icache[$internal_key][$uid]) && !empty($sql_arr['structure'])) 
     3073                    $this->icache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure'])); 
     3074            } 
     3075        } 
     3076 
     3077        return $this->icache[$internal_key][$uid]; 
     3078    } 
     3079 
     3080    /** 
     3081     * @access private 
     3082     */   
     3083    private function get_message_cache_index($key, $force=false, $sort_field='idx', $sort_order='ASC') 
     3084    { 
     3085        static $sa_message_index = array(); 
     3086     
     3087        // empty key -> empty array 
     3088        if (!$this->caching_enabled || empty($key)) 
     3089            return array(); 
     3090     
     3091        if (!empty($sa_message_index[$key]) && !$force) 
     3092            return $sa_message_index[$key]; 
     3093 
     3094        // use idx sort as default 
     3095        if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) 
     3096            $sort_field = 'idx'; 
     3097     
     3098        $sa_message_index[$key] = array(); 
     3099        $sql_result = $this->db->query( 
     3100            "SELECT idx, uid". 
     3101            " FROM ".get_table_name('messages'). 
     3102            " WHERE user_id=?". 
     3103            " AND cache_key=?". 
     3104            " ORDER BY ".$this->db->quote_identifier($sort_field)." ".$sort_order, 
     3105            $_SESSION['user_id'], 
     3106            $key); 
     3107 
     3108        while ($sql_arr = $this->db->fetch_assoc($sql_result)) 
     3109            $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid']; 
    23493110       
    2350       // remove message ids from search set 
    2351       if ($this->search_set && $mailbox == $this->mailbox) { 
    2352         // threads are too complicated to just remove messages from set 
    2353         if ($this->search_threads || $all_mode) 
    2354           $this->refresh_search(); 
    2355         else { 
    2356           $uids = explode(',', $uids); 
    2357           foreach ($uids as $uid) 
    2358             $a_mids[] = $this->_uid2id($uid, $mailbox); 
    2359           $this->search_set = array_diff($this->search_set, $a_mids); 
    2360           } 
    2361         } 
    2362  
    2363       // remove deleted messages from cache 
    2364       $cache_key = $mailbox.'.msg'; 
    2365       if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { 
    2366         // clear cache from the lowest index on 
    2367         $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); 
    2368       } 
    2369     } 
    2370  
    2371     return $deleted; 
    2372   } 
    2373  
    2374  
    2375   /** 
    2376    * Clear all messages in a specific mailbox 
    2377    * 
    2378    * @param string Mailbox name 
    2379    * @return int Above 0 on success 
    2380    */ 
    2381   function clear_mailbox($mbox_name=NULL) 
    2382     { 
    2383     $mailbox = !empty($mbox_name) ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    2384     $msg_count = $this->_messagecount($mailbox, 'ALL'); 
    2385      
    2386     if ($msg_count>0) 
    2387       { 
    2388       $cleared = iil_C_ClearFolder($this->conn, $mailbox); 
     3111        return $sa_message_index[$key]; 
     3112    } 
     3113 
     3114    /** 
     3115     * @access private 
     3116     */ 
     3117    private function add_message_cache($key, $index, $headers, $struct=null, $force=false) 
     3118    { 
     3119        if (empty($key) || !is_object($headers) || empty($headers->uid)) 
     3120            return; 
     3121 
     3122        // add to internal (fast) cache 
     3123        $this->icache['message'][$headers->uid] = clone $headers; 
     3124        $this->icache['message'][$headers->uid]->structure = $struct; 
     3125 
     3126        // no further caching 
     3127        if (!$this->caching_enabled) 
     3128            return; 
     3129     
     3130        // check for an existing record (probly headers are cached but structure not) 
     3131        if (!$force) { 
     3132            $sql_result = $this->db->query( 
     3133                "SELECT message_id". 
     3134                " FROM ".get_table_name('messages'). 
     3135                " WHERE user_id=?". 
     3136                " AND cache_key=?". 
     3137                " AND uid=?", 
     3138                $_SESSION['user_id'], 
     3139                $key, 
     3140                $headers->uid); 
     3141 
     3142            if ($sql_arr = $this->db->fetch_assoc($sql_result)) 
     3143                $message_id = $sql_arr['message_id']; 
     3144        } 
     3145 
     3146        // update cache record 
     3147        if ($message_id) { 
     3148            $this->db->query( 
     3149                "UPDATE ".get_table_name('messages'). 
     3150                " SET idx=?, headers=?, structure=?". 
     3151                " WHERE message_id=?", 
     3152                $index, 
     3153                serialize($this->db->encode(clone $headers)), 
     3154                is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL, 
     3155                $message_id 
     3156            ); 
     3157        } 
     3158        else { // insert new record 
     3159            $this->db->query( 
     3160                "INSERT INTO ".get_table_name('messages'). 
     3161                " (user_id, del, cache_key, created, idx, uid, subject, ". 
     3162                $this->db->quoteIdentifier('from').", ". 
     3163                $this->db->quoteIdentifier('to').", ". 
     3164                "cc, date, size, headers, structure)". 
     3165                " VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ". 
     3166                $this->db->fromunixtime($headers->timestamp).", ?, ?, ?)", 
     3167                $_SESSION['user_id'], 
     3168                $key, 
     3169                $index, 
     3170                $headers->uid, 
     3171                (string)mb_substr($this->db->encode($this->decode_header($headers->subject, true)), 0, 128), 
     3172                (string)mb_substr($this->db->encode($this->decode_header($headers->from, true)), 0, 128), 
     3173                (string)mb_substr($this->db->encode($this->decode_header($headers->to, true)), 0, 128), 
     3174                (string)mb_substr($this->db->encode($this->decode_header($headers->cc, true)), 0, 128), 
     3175                (int)$headers->size, 
     3176                serialize($this->db->encode(clone $headers)), 
     3177                is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL 
     3178            ); 
     3179        } 
     3180    } 
     3181     
     3182    /** 
     3183     * @access private 
     3184     */ 
     3185    private function remove_message_cache($key, $ids, $idx=false) 
     3186    { 
     3187        if (!$this->caching_enabled) 
     3188            return; 
     3189     
     3190        $this->db->query( 
     3191            "DELETE FROM ".get_table_name('messages'). 
     3192            " WHERE user_id=?". 
     3193            " AND cache_key=?". 
     3194            " AND ".($idx ? "idx" : "uid")." IN (".$this->db->array2list($ids, 'integer').")", 
     3195            $_SESSION['user_id'], 
     3196            $key); 
     3197    } 
     3198 
     3199    /** 
     3200     * @access private 
     3201     */ 
     3202    private function clear_message_cache($key, $start_index=1) 
     3203    { 
     3204        if (!$this->caching_enabled) 
     3205            return; 
     3206     
     3207        $this->db->query( 
     3208            "DELETE FROM ".get_table_name('messages'). 
     3209            " WHERE user_id=?". 
     3210            " AND cache_key=?". 
     3211            " AND idx>=?", 
     3212            $_SESSION['user_id'], $key, $start_index); 
     3213    } 
     3214 
     3215    /** 
     3216     * @access private 
     3217     */ 
     3218    private function get_message_cache_index_min($key, $uids=NULL) 
     3219    { 
     3220        if (!$this->caching_enabled) 
     3221            return; 
     3222     
     3223        if (!empty($uids) && !is_array($uids)) { 
     3224            if ($uids == '*' || $uids == '1:*') 
     3225                $uids = NULL; 
     3226            else 
     3227                $uids = explode(',', $uids); 
     3228        } 
     3229 
     3230        $sql_result = $this->db->query( 
     3231            "SELECT MIN(idx) AS minidx". 
     3232            " FROM ".get_table_name('messages'). 
     3233            " WHERE  user_id=?". 
     3234            " AND    cache_key=?" 
     3235            .(!empty($uids) ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : ''), 
     3236            $_SESSION['user_id'], 
     3237            $key); 
     3238 
     3239        if ($sql_arr = $this->db->fetch_assoc($sql_result)) 
     3240            return $sql_arr['minidx']; 
     3241        else 
     3242            return 0;   
     3243    } 
     3244 
     3245 
     3246    /* -------------------------------- 
     3247     *   encoding/decoding methods 
     3248     * --------------------------------*/ 
     3249 
     3250    /** 
     3251     * Split an address list into a structured array list 
     3252     * 
     3253     * @param string  Input string 
     3254     * @param int     List only this number of addresses 
     3255     * @param boolean Decode address strings 
     3256     * @return array  Indexed list of addresses 
     3257     */ 
     3258    function decode_address_list($input, $max=null, $decode=true) 
     3259    { 
     3260        $a = $this->_parse_address_list($input, $decode); 
     3261        $out = array(); 
     3262        // Special chars as defined by RFC 822 need to in quoted string (or escaped). 
     3263        $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]'; 
     3264     
     3265        if (!is_array($a)) 
     3266            return $out; 
     3267 
     3268        $c = count($a); 
     3269        $j = 0; 
     3270 
     3271        foreach ($a as $val) { 
     3272            $j++; 
     3273            $address = trim($val['address']); 
     3274            $name = trim($val['name']); 
     3275 
     3276            if (preg_match('/^[\'"]/', $name) && preg_match('/[\'"]$/', $name)) 
     3277                $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', $name); 
     3278 
     3279            if ($name && $address && $name != $address) 
     3280                $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); 
     3281            else if ($address) 
     3282                $string = $address; 
     3283            else if ($name) 
     3284                $string = $name; 
    23893285       
    2390       // make sure the message count cache is cleared as well 
    2391       if ($cleared) 
    2392         { 
    2393         $this->clear_message_cache($mailbox.'.msg');       
    2394         $a_mailbox_cache = $this->get_cache('messagecount'); 
    2395         unset($a_mailbox_cache[$mailbox]); 
    2396         $this->update_cache('messagecount', $a_mailbox_cache); 
    2397         } 
    2398          
    2399       return $cleared; 
    2400       } 
    2401     else 
    2402       return 0; 
    2403     } 
    2404  
    2405  
    2406   /** 
    2407    * Send IMAP expunge command and clear cache 
    2408    * 
    2409    * @param string Mailbox name 
    2410    * @param boolean False if cache should not be cleared 
    2411    * @return boolean True on success 
    2412    */ 
    2413   function expunge($mbox_name='', $clear_cache=true) 
    2414     { 
    2415     $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; 
    2416     return $this->_expunge($mailbox, $clear_cache); 
    2417     } 
    2418  
    2419  
    2420   /** 
    2421    * Send IMAP expunge command and clear cache 
    2422    * 
    2423    * @param string       Mailbox name 
    2424    * @param boolean  False if cache should not be cleared 
    2425    * @param mixed    Message UIDs as array or comma-separated string, or '*' 
    2426    * @return boolean True on success 
    2427    * @access private 
    2428    * @see rcube_imap::expunge() 
    2429    */ 
    2430   private function _expunge($mailbox, $clear_cache=true, $uids=NULL) 
    2431     { 
    2432     if ($uids && $this->get_capability('UIDPLUS'))  
    2433       $a_uids = is_array($uids) ? join(',', $uids) : $uids; 
    2434     else 
    2435       $a_uids = NULL; 
    2436  
    2437     $result = iil_C_Expunge($this->conn, $mailbox, $a_uids); 
    2438  
    2439     if ($result>=0 && $clear_cache) 
    2440       { 
    2441       $this->clear_message_cache($mailbox.'.msg'); 
    2442       $this->_clear_messagecount($mailbox); 
    2443       } 
    2444        
    2445     return $result; 
    2446     } 
    2447  
    2448  
    2449   /** 
    2450    * Parse message UIDs input 
    2451    * 
    2452    * @param mixed  UIDs array or comma-separated list or '*' or '1:*' 
    2453    * @param string Mailbox name 
    2454    * @return array Two elements array with UIDs converted to list and ALL flag  
    2455    * @access private 
    2456    */ 
    2457   private function _parse_uids($uids, $mailbox) 
    2458     { 
    2459     if ($uids === '*' || $uids === '1:*') { 
    2460       if (empty($this->search_set)) { 
    2461         $uids = '1:*'; 
    2462         $all = true; 
    2463         } 
    2464       // get UIDs from current search set 
    2465       // @TODO: skip iil_C_FetchUIDs() and work with IDs instead of UIDs (?) 
    2466       else { 
    2467         if ($this->search_threads) 
    2468           $uids = iil_C_FetchUIDs($this->conn, $mailbox, array_keys($this->search_set['depth'])); 
    2469         else 
    2470           $uids = iil_C_FetchUIDs($this->conn, $mailbox, $this->search_set); 
    2471        
    2472         // save ID-to-UID mapping in local cache 
    2473         if (is_array($uids)) 
    2474           foreach ($uids as $id => $uid) 
    2475             $this->uid_id_map[$mailbox][$uid] = $id; 
    2476  
    2477         $uids = join(',', $uids); 
    2478         } 
    2479       } 
    2480     else { 
    2481       if (is_array($uids)) 
    2482         $uids = join(',', $uids); 
    2483  
    2484       if (preg_match('/[^0-9,]/', $uids)) 
    2485         $uids = ''; 
    2486       } 
    2487  
    2488     return array($uids, (bool) $all); 
    2489     } 
    2490      
    2491  
    2492   /* -------------------------------- 
    2493    *        folder managment 
    2494    * --------------------------------*/ 
    2495  
    2496   /** 
    2497    * Get a list of all folders available on the IMAP server 
    2498    *  
    2499    * @param string IMAP root dir 
    2500    * @return array Indexed array with folder names 
    2501    */ 
    2502   function list_unsubscribed($root='') 
    2503     { 
    2504     static $sa_unsubscribed; 
    2505      
    2506     if (is_array($sa_unsubscribed)) 
    2507       return $sa_unsubscribed; 
    2508        
    2509     // retrieve list of folders from IMAP server 
    2510     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox($root), '*'); 
    2511  
    2512     // modify names with root dir 
    2513     foreach ($a_mboxes as $mbox_name) 
    2514       { 
    2515       $name = $this->mod_mailbox($mbox_name, 'out'); 
    2516       if (strlen($name)) 
    2517         $a_folders[] = $name; 
    2518       } 
    2519  
    2520     // filter folders and sort them 
    2521     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders); 
    2522     return $sa_unsubscribed; 
    2523     } 
    2524  
    2525  
    2526   /** 
    2527    * Get mailbox quota information 
    2528    * added by Nuny 
    2529    *  
    2530    * @return mixed Quota info or False if not supported 
    2531    */ 
    2532   function get_quota() 
    2533     { 
    2534     if ($this->get_capability('QUOTA')) 
    2535       return iil_C_GetQuota($this->conn); 
    2536          
    2537     return false; 
    2538     } 
    2539  
    2540  
    2541   /** 
    2542    * Subscribe to a specific mailbox(es) 
    2543    * 
    2544    * @param array Mailbox name(s) 
    2545    * @return boolean True on success 
    2546    */  
    2547   function subscribe($a_mboxes) 
    2548     { 
    2549     if (!is_array($a_mboxes)) 
    2550       $a_mboxes = array($a_mboxes); 
    2551  
    2552     // let this common function do the main work 
    2553     return $this->_change_subscription($a_mboxes, 'subscribe'); 
    2554     } 
    2555  
    2556  
    2557   /** 
    2558    * Unsubscribe mailboxes 
    2559    * 
    2560    * @param array Mailbox name(s) 
    2561    * @return boolean True on success 
    2562    */ 
    2563   function unsubscribe($a_mboxes) 
    2564     { 
    2565     if (!is_array($a_mboxes)) 
    2566       $a_mboxes = array($a_mboxes); 
    2567  
    2568     // let this common function do the main work 
    2569     return $this->_change_subscription($a_mboxes, 'unsubscribe'); 
    2570     } 
    2571  
    2572  
    2573   /** 
    2574    * Create a new mailbox on the server and register it in local cache 
    2575    * 
    2576    * @param string  New mailbox name (as utf-7 string) 
    2577    * @param boolean True if the new mailbox should be subscribed 
    2578    * @param string  Name of the created mailbox, false on error 
    2579    */ 
    2580   function create_mailbox($name, $subscribe=false) 
    2581     { 
    2582     $result = false; 
    2583      
    2584     // reduce mailbox name to 100 chars 
    2585     $name = substr($name, 0, 100); 
    2586     $abs_name = $this->mod_mailbox($name); 
    2587     $result = iil_C_CreateFolder($this->conn, $abs_name); 
    2588  
    2589     // try to subscribe it 
    2590     if ($result && $subscribe) 
    2591       $this->subscribe($name); 
    2592  
    2593     return $result ? $name : false; 
    2594     } 
    2595  
    2596  
    2597   /** 
    2598    * Set a new name to an existing mailbox 
    2599    * 
    2600    * @param string Mailbox to rename (as utf-7 string) 
    2601    * @param string New mailbox name (as utf-7 string) 
    2602    * @return string Name of the renames mailbox, False on error 
    2603    */ 
    2604   function rename_mailbox($mbox_name, $new_name) 
    2605     { 
    2606     $result = false; 
    2607  
    2608     // encode mailbox name and reduce it to 100 chars 
    2609     $name = substr($new_name, 0, 100); 
    2610  
    2611     // make absolute path 
    2612     $mailbox = $this->mod_mailbox($mbox_name); 
    2613     $abs_name = $this->mod_mailbox($name); 
    2614      
    2615     // check if mailbox is subscribed 
    2616     $a_subscribed = $this->_list_mailboxes(); 
    2617     $subscribed = in_array($mailbox, $a_subscribed); 
    2618      
    2619     // unsubscribe folder 
    2620     if ($subscribed) 
    2621       iil_C_UnSubscribe($this->conn, $mailbox); 
    2622  
    2623     if (strlen($abs_name)) 
    2624       $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name); 
    2625  
    2626     if ($result) 
    2627       { 
    2628       $delm = $this->get_hierarchy_delimiter(); 
    2629        
    2630       // check if mailbox children are subscribed 
    2631       foreach ($a_subscribed as $c_subscribed) 
    2632         if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) 
    2633           { 
    2634           iil_C_UnSubscribe($this->conn, $c_subscribed); 
    2635           iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed)); 
    2636           } 
    2637  
    2638       // clear cache 
    2639       $this->clear_message_cache($mailbox.'.msg'); 
    2640       $this->clear_cache('mailboxes');       
    2641       } 
    2642  
    2643     // try to subscribe it 
    2644     if ($result && $subscribed) 
    2645       iil_C_Subscribe($this->conn, $abs_name); 
    2646  
    2647     return $result ? $name : false; 
    2648     } 
    2649  
    2650  
    2651   /** 
    2652    * Remove mailboxes from server 
    2653    * 
    2654    * @param string Mailbox name(s) string/array 
    2655    * @return boolean True on success 
    2656    */ 
    2657   function delete_mailbox($mbox_name) 
    2658     { 
    2659     $deleted = false; 
    2660  
    2661     if (is_array($mbox_name)) 
    2662       $a_mboxes = $mbox_name; 
    2663     else if (is_string($mbox_name) && strlen($mbox_name)) 
    2664       $a_mboxes = explode(',', $mbox_name); 
    2665  
    2666     if (is_array($a_mboxes)) 
    2667       foreach ($a_mboxes as $mbox_name) 
    2668         { 
    2669         $mailbox = $this->mod_mailbox($mbox_name); 
    2670         $sub_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox(''), 
    2671           $mbox_name . $this->delimiter . '*'); 
    2672  
    2673         // unsubscribe mailbox before deleting 
    2674         iil_C_UnSubscribe($this->conn, $mailbox); 
    2675  
    2676         // send delete command to server 
    2677         $result = iil_C_DeleteFolder($this->conn, $mailbox); 
    2678         if ($result >= 0) { 
    2679           $deleted = true; 
    2680           $this->clear_message_cache($mailbox.'.msg'); 
    2681           } 
    2682            
    2683         foreach ($sub_mboxes as $c_mbox) 
    2684           if ($c_mbox != 'INBOX') { 
    2685             iil_C_UnSubscribe($this->conn, $c_mbox); 
    2686             $result = iil_C_DeleteFolder($this->conn, $c_mbox); 
    2687             if ($result >= 0) { 
    2688               $deleted = true; 
    2689               $this->clear_message_cache($c_mbox.'.msg'); 
    2690             } 
    2691           } 
    2692         } 
    2693  
    2694     // clear mailboxlist cache 
    2695     if ($deleted) 
    2696       $this->clear_cache('mailboxes'); 
    2697  
    2698     return $deleted; 
    2699     } 
    2700  
    2701  
    2702   /** 
    2703    * Create all folders specified as default 
    2704    */ 
    2705   function create_default_folders() 
    2706     { 
    2707     // create default folders if they do not exist 
    2708     foreach ($this->default_folders as $folder) 
    2709       { 
    2710       if (!$this->mailbox_exists($folder)) 
    2711         $this->create_mailbox($folder, true); 
    2712       else if (!$this->mailbox_exists($folder, true)) 
    2713         $this->subscribe($folder); 
    2714       } 
    2715     } 
    2716  
    2717  
    2718   /** 
    2719    * Checks if folder exists and is subscribed 
    2720    * 
    2721    * @param string   Folder name 
    2722    * @param boolean  Enable subscription checking 
    2723    * @return boolean TRUE or FALSE 
    2724    */ 
    2725   function mailbox_exists($mbox_name, $subscription=false) 
    2726     { 
    2727     if ($mbox_name) { 
    2728       if ($mbox_name == 'INBOX') 
    2729         return true; 
    2730  
    2731       if ($subscription) { 
    2732         if ($a_folders = iil_C_ListSubscribed($this->conn, $this->mod_mailbox(''), $mbox_name)) 
    2733           return true; 
    2734         } 
    2735       else { 
    2736         $a_folders = iil_C_ListMailboxes($this->conn, $this->mod_mailbox(''), $mbox_mbox); 
    2737          
    2738         if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) 
    2739           return true; 
    2740         } 
    2741       } 
    2742  
    2743     return false; 
    2744     } 
    2745  
    2746  
    2747  
    2748   /* -------------------------------- 
    2749    *   internal caching methods 
    2750    * --------------------------------*/ 
    2751  
    2752   /** 
    2753    * @access private 
    2754    */ 
    2755   function set_caching($set) 
    2756     { 
    2757     if ($set && is_object($this->db)) 
    2758       $this->caching_enabled = true; 
    2759     else 
    2760       $this->caching_enabled = false; 
    2761     } 
    2762  
    2763   /** 
    2764    * @access private 
    2765    */ 
    2766   function get_cache($key) 
    2767     { 
    2768     // read cache (if it was not read before) 
    2769     if (!count($this->cache) && $this->caching_enabled) 
    2770       { 
    2771       return $this->_read_cache_record($key); 
    2772       } 
    2773      
    2774     return $this->cache[$key]; 
    2775     } 
    2776  
    2777   /** 
    2778    * @access private 
    2779    */ 
    2780   function update_cache($key, $data) 
    2781     { 
    2782     $this->cache[$key] = $data; 
    2783     $this->cache_changed = true; 
    2784     $this->cache_changes[$key] = true; 
    2785     } 
    2786  
    2787   /** 
    2788    * @access private 
    2789    */ 
    2790   function write_cache() 
    2791     { 
    2792     if ($this->caching_enabled && $this->cache_changed) 
    2793       { 
    2794       foreach ($this->cache as $key => $data) 
    2795         { 
    2796         if ($this->cache_changes[$key]) 
    2797           $this->_write_cache_record($key, serialize($data)); 
    2798         } 
    2799       }     
    2800     } 
    2801  
    2802   /** 
    2803    * @access private 
    2804    */ 
    2805   function clear_cache($key=NULL) 
    2806     { 
    2807     if (!$this->caching_enabled) 
    2808       return; 
    2809      
    2810     if ($key===NULL) 
    2811       { 
    2812       foreach ($this->cache as $key => $data) 
    2813         $this->_clear_cache_record($key); 
    2814  
    2815       $this->cache = array(); 
    2816       $this->cache_changed = false; 
    2817       $this->cache_changes = array(); 
    2818       } 
    2819     else 
    2820       { 
    2821       $this->_clear_cache_record($key); 
    2822       $this->cache_changes[$key] = false; 
    2823       unset($this->cache[$key]); 
    2824       } 
    2825     } 
    2826  
    2827   /** 
    2828    * @access private 
    2829    */ 
    2830   private function _read_cache_record($key) 
    2831     { 
    2832     if ($this->db) 
    2833       { 
    2834       // get cached data from DB 
    2835       $sql_result = $this->db->query( 
    2836         "SELECT cache_id, data, cache_key 
    2837          FROM ".get_table_name('cache')." 
    2838          WHERE  user_id=? 
    2839          AND cache_key LIKE 'IMAP.%'", 
    2840         $_SESSION['user_id']); 
    2841  
    2842       while ($sql_arr = $this->db->fetch_assoc($sql_result)) 
    2843         { 
    2844         $sql_key = preg_replace('/^IMAP\./', '', $sql_arr['cache_key']); 
    2845         $this->cache_keys[$sql_key] = $sql_arr['cache_id']; 
    2846         if (!isset($this->cache[$sql_key])) 
    2847           $this->cache[$sql_key] = $sql_arr['data'] ? unserialize($sql_arr['data']) : false; 
    2848         } 
    2849       } 
    2850  
    2851     return $this->cache[$key]; 
    2852     } 
    2853  
    2854   /** 
    2855    * @access private 
    2856    */ 
    2857   private function _write_cache_record($key, $data) 
    2858     { 
    2859     if (!$this->db) 
    2860       return false; 
    2861  
    2862     // update existing cache record 
    2863     if ($this->cache_keys[$key]) 
    2864       { 
    2865       $this->db->query( 
    2866         "UPDATE ".get_table_name('cache')." 
    2867          SET    created=". $this->db->now().", data=? 
    2868          WHERE  user_id=? 
    2869          AND    cache_key=?", 
    2870         $data, 
    2871         $_SESSION['user_id'], 
    2872         'IMAP.'.$key); 
    2873       } 
    2874     // add new cache record 
    2875     else 
    2876       { 
    2877       $this->db->query( 
    2878         "INSERT INTO ".get_table_name('cache')." 
    2879          (created, user_id, cache_key, data) 
    2880          VALUES (".$this->db->now().", ?, ?, ?)", 
    2881         $_SESSION['user_id'], 
    2882         'IMAP.'.$key, 
    2883         $data); 
    2884  
    2885       // get cache entry ID for this key 
    2886       $sql_result = $this->db->query( 
    2887         "SELECT cache_id 
    2888          FROM ".get_table_name('cache')." 
    2889          WHERE  user_id=? 
    2890          AND    cache_key=?", 
    2891         $_SESSION['user_id'], 
    2892         'IMAP.'.$key); 
    2893                                       
    2894         if ($sql_arr = $this->db->fetch_assoc($sql_result)) 
    2895           $this->cache_keys[$key] = $sql_arr['cache_id']; 
    2896       } 
    2897     } 
    2898  
    2899   /** 
    2900    * @access private 
    2901    */ 
    2902   private function _clear_cache_record($key) 
    2903     { 
    2904     $this->db->query( 
    2905       "DELETE FROM ".get_table_name('cache')." 
    2906        WHERE  user_id=? 
    2907        AND    cache_key=?", 
    2908       $_SESSION['user_id'], 
    2909       'IMAP.'.$key); 
    2910        
    2911     unset($this->cache_keys[$key]); 
    2912     } 
    2913  
    2914  
    2915  
    2916   /* -------------------------------- 
    2917    *   message caching methods 
    2918    * --------------------------------*/ 
    2919     
    2920  
    2921   /** 
    2922    * Checks if the cache is up-to-date 
    2923    * 
    2924    * @param string Mailbox name 
    2925    * @param string Internal cache key 
    2926    * @return int   Cache status: -3 = off, -2 = incomplete, -1 = dirty 
    2927    */ 
    2928   private function check_cache_status($mailbox, $cache_key) 
    2929   { 
    2930     if (!$this->caching_enabled) 
    2931       return -3; 
    2932  
    2933     $cache_index = $this->get_message_cache_index($cache_key); 
    2934     $msg_count = $this->_messagecount($mailbox); 
    2935     $cache_count = count($cache_index); 
    2936  
    2937     // empty mailbox 
    2938     if (!$msg_count) 
    2939       return $cache_count ? -2 : 1;