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