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