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