thomascube
2006-04-04 a403cdacf07656c0bbeb2c6bf00557070ef826ba
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');
a95e0e 29 require_once('lib/utf7.inc');
4e17e6 30
T 31
15a9d1 32 /**
T 33  * Interface class for accessing an IMAP server
34  *
35  * This is a wrapper that implements the Iloha IMAP Library (IIL)
36  *
37  * @package    RoundCube Webmail
38  * @author     Thomas Bruederli <roundcube@gmail.com>
39  * @version    1.22
40  * @link       http://ilohamail.org
41  */
4e17e6 42 class rcube_imap
T 43   {
1cded8 44   var $db;
4e17e6 45   var $conn;
520c36 46   var $root_ns = '';
4e17e6 47   var $root_dir = '';
T 48   var $mailbox = 'INBOX';
49   var $list_page = 1;
50   var $page_size = 10;
31b2ce 51   var $sort_field = 'date';
T 52   var $sort_order = 'DESC';
597170 53   var $delimiter = NULL;
6dc026 54   var $caching_enabled = FALSE;
4e17e6 55   var $default_folders = array('inbox', 'drafts', 'sent', 'junk', 'trash');
T 56   var $cache = array();
1cded8 57   var $cache_keys = array();  
4e17e6 58   var $cache_changes = array();  
T 59   var $uid_id_map = array();
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       {
211       $this->default_folders = array();
212       
213       // add mailbox names lower case
214       foreach ($arr as $mbox)
215         $this->default_folders[] = strtolower($mbox);
216       
217       // add inbox if not included
218       if (!in_array('inbox', $this->default_folders))
219         array_unshift($arr, 'inbox');
220       }
221     }
222
223
15a9d1 224   /**
T 225    * Set internal mailbox reference.
226    *
227    * All operations will be perfomed on this mailbox/folder
228    *
229    * @param  string  Mailbox/Folder name
230    * @access public
231    */
4e17e6 232   function set_mailbox($mbox)
T 233     {
234     $mailbox = $this->_mod_mailbox($mbox);
235
236     if ($this->mailbox == $mailbox)
237       return;
238
239     $this->mailbox = $mailbox;
240
241     // clear messagecount cache for this mailbox
242     $this->_clear_messagecount($mailbox);
243     }
244
245
15a9d1 246   /**
T 247    * Set internal list page
248    *
249    * @param  number  Page number to list
250    * @access public
251    */
4e17e6 252   function set_page($page)
T 253     {
254     $this->list_page = (int)$page;
255     }
256
257
15a9d1 258   /**
T 259    * Set internal page size
260    *
261    * @param  number  Number of messages to display on one page
262    * @access public
263    */
4e17e6 264   function set_pagesize($size)
T 265     {
266     $this->page_size = (int)$size;
267     }
268
269
15a9d1 270   /**
T 271    * Returns the currently used mailbox name
272    *
273    * @return  string Name of the mailbox/folder
274    * @access  public
275    */
4e17e6 276   function get_mailbox_name()
T 277     {
278     return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
1cded8 279     }
T 280
281
15a9d1 282   /**
T 283    * Returns the IMAP server's capability
284    *
285    * @param   string  Capability name
286    * @return  mixed   Capability value or TRUE if supported, FALSE if not
287    * @access  public
288    */
1cded8 289   function get_capability($cap)
T 290     {
291     $cap = strtoupper($cap);
292     return $this->capabilities[$cap];
4e17e6 293     }
T 294
295
15a9d1 296   /**
T 297    * Returns the delimiter that is used by the IMAP server for folder separation
298    *
299    * @return  string  Delimiter string
300    * @access  public
301    */
597170 302   function get_hierarchy_delimiter()
T 303     {
304     if ($this->conn && empty($this->delimiter))
305       $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
306
7902df 307     if (empty($this->delimiter))
T 308       $this->delimiter = '/';
309
597170 310     return $this->delimiter;
T 311     }
312
15a9d1 313
T 314   /**
315    * Public method for mailbox listing.
316    *
317    * Converts mailbox name with root dir first
318    *
319    * @param   string  Optional root folder
320    * @param   string  Optional filter for mailbox listing
321    * @return  array   List of mailboxes/folders
322    * @access  public
323    */
4e17e6 324   function list_mailboxes($root='', $filter='*')
T 325     {
326     $a_out = array();
327     $a_mboxes = $this->_list_mailboxes($root, $filter);
328
329     foreach ($a_mboxes as $mbox)
330       {
331       $name = $this->_mod_mailbox($mbox, 'out');
332       if (strlen($name))
333         $a_out[] = $name;
334       }
335
336     // sort mailboxes
337     $a_out = $this->_sort_mailbox_list($a_out);
338
339     return $a_out;
340     }
341
15a9d1 342
T 343   /**
344    * Private method for mailbox listing
345    *
346    * @return  array   List of mailboxes/folders
347    * @access  private
348    * @see     rcube_imap::list_mailboxes
349    */
4e17e6 350   function _list_mailboxes($root='', $filter='*')
T 351     {
352     $a_defaults = $a_out = array();
353     
354     // get cached folder list    
355     $a_mboxes = $this->get_cache('mailboxes');
356     if (is_array($a_mboxes))
357       return $a_mboxes;
358
359     // retrieve list of folders from IMAP server
360     $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
361     
362     if (!is_array($a_folders) || !sizeof($a_folders))
363       $a_folders = array();
364
365     // create INBOX if it does not exist
366     if (!in_array_nocase('INBOX', $a_folders))
367       {
368       $this->create_mailbox('INBOX', TRUE);
369       array_unshift($a_folders, 'INBOX');
370       }
371
372     $a_mailbox_cache = array();
373
374     // write mailboxlist to cache
375     $this->update_cache('mailboxes', $a_folders);
376     
377     return $a_folders;
378     }
379
380
3f9edb 381   /**
T 382    * Get message count for a specific mailbox
383    *
384    * @param   string   Mailbox/folder name
385    * @param   string   Mode for count [ALL|UNSEEN|RECENT]
386    * @param   boolean  Force reading from server and update cache
387    * @return  number   Number of messages
388    * @access  public   
389    */
4e17e6 390   function messagecount($mbox='', $mode='ALL', $force=FALSE)
T 391     {
392     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
393     return $this->_messagecount($mailbox, $mode, $force);
394     }
395
3f9edb 396
T 397   /**
398    * Private method for getting nr of messages
399    *
400    * @access  private
401    * @see     rcube_imap::messagecount
402    */
4e17e6 403   function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
T 404     {
405     $a_mailbox_cache = FALSE;
406     $mode = strtoupper($mode);
407
15a9d1 408     if (empty($mailbox))
4e17e6 409       $mailbox = $this->mailbox;
T 410
411     $a_mailbox_cache = $this->get_cache('messagecount');
412     
413     // return cached value
414     if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
31b2ce 415       return $a_mailbox_cache[$mailbox][$mode];
4e17e6 416
15a9d1 417     // RECENT count is fetched abit different      
T 418     if ($mode == 'RECENT')
419        $count = iil_C_CheckForRecent($this->conn, $mailbox);
31b2ce 420
15a9d1 421     // use SEARCH for message counting
T 422     else if ($this->skip_deleted)
31b2ce 423       {
15a9d1 424       $search_str = "ALL UNDELETED";
T 425
426       // get message count and store in cache
427       if ($mode == 'UNSEEN')
428         $search_str .= " UNSEEN";
429
430       // get message count using SEARCH
431       // not very performant but more precise (using UNDELETED)
432       $count = 0;
433       $index = $this->_search_index($mailbox, $search_str);
434       if (is_array($index))
435         {
436         $str = implode(",", $index);
437         if (!empty($str))
438           $count = count($index);
439         }
440       }
441     else
442       {
443       if ($mode == 'UNSEEN')
444         $count = iil_C_CountUnseen($this->conn, $mailbox);
445       else
446         $count = iil_C_CountMessages($this->conn, $mailbox);
31b2ce 447       }
4e17e6 448
13c1af 449     if (!is_array($a_mailbox_cache[$mailbox]))
4e17e6 450       $a_mailbox_cache[$mailbox] = array();
T 451       
452     $a_mailbox_cache[$mailbox][$mode] = (int)$count;
31b2ce 453
4e17e6 454     // write back to cache
T 455     $this->update_cache('messagecount', $a_mailbox_cache);
456
457     return (int)$count;
458     }
459
460
3f9edb 461   /**
T 462    * Public method for listing headers
463    * convert mailbox name with root dir first
464    *
465    * @param   string   Mailbox/folder name
466    * @param   number   Current page to list
467    * @param   string   Header field to sort by
468    * @param   string   Sort order [ASC|DESC]
469    * @return  array    Indexed array with message header objects
470    * @access  public   
471    */
31b2ce 472   function list_headers($mbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
4e17e6 473     {
T 474     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
475     return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
476     }
477
478
3f9edb 479   /**
4647e1 480    * Private method for listing message headers
3f9edb 481    *
T 482    * @access  private
483    * @see     rcube_imap::list_headers
484    */
31b2ce 485   function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
4e17e6 486     {
T 487     if (!strlen($mailbox))
17fc71 488       return array();
31b2ce 489       
T 490     if ($sort_field!=NULL)
491       $this->sort_field = $sort_field;
492     if ($sort_order!=NULL)
493       $this->sort_order = strtoupper($sort_order);
4e17e6 494
1cded8 495     $max = $this->_messagecount($mailbox);
T 496     $start_msg = ($this->list_page-1) * $this->page_size;
497     
4647e1 498     list($begin, $end) = $this->_get_message_range($max, $page);
T 499     
500     /*    
1cded8 501     if ($page=='all')
4e17e6 502       {
1cded8 503       $begin = 0;
T 504       $end = $max;
505       }
31b2ce 506     else if ($this->sort_order=='DESC')
1cded8 507       {
T 508       $begin = $max - $this->page_size - $start_msg;
509       $end =   $max - $start_msg;
4e17e6 510       }
T 511     else
b595c9 512       {
1cded8 513       $begin = $start_msg;
15a9d1 514       $end   = $start_msg + $this->page_size;
b595c9 515       }
4e17e6 516
1cded8 517     if ($begin < 0) $begin = 0;
T 518     if ($end < 0) $end = $max;
519     if ($end > $max) $end = $max;
4647e1 520     */
T 521     
1cded8 522 //console("fetch headers $start_msg to ".($start_msg+$this->page_size)." (msg $begin to $end)");
T 523
524     $headers_sorted = FALSE;
525     $cache_key = $mailbox.'.msg';
526     $cache_status = $this->check_cache_status($mailbox, $cache_key);
527
528 //console("Cache status = $cache_status");
529     
530     // cache is OK, we can get all messages from local cache
531     if ($cache_status>0)
532       {
31b2ce 533       $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
1cded8 534       $headers_sorted = TRUE;
T 535       }
536     else
537       {
538       // retrieve headers from IMAP
15a9d1 539       if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
1cded8 540         {
15a9d1 541 //console("$mailbox: ".join(',', $msg_index));
1cded8 542         
T 543         $msgs = $msg_index[$begin];
15a9d1 544         for ($i=$begin+1; $i < $end; $i++)
4647e1 545           $msgs = $msgs.','.$msg_index[$i];
1cded8 546
T 547         $sorted = TRUE;
548         }
549       else
550         {
551         $msgs = sprintf("%d:%d", $begin+1, $end);
552         $sorted = FALSE;
553         }
554
555
556       // cache is dirty, sync it
31b2ce 557       if ($this->caching_enabled && $cache_status==-1 && !$recursive)
1cded8 558         {
T 559         $this->sync_header_index($mailbox);
31b2ce 560         return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
1cded8 561         }      
T 562
563         
564       // fetch reuested headers from server
565       $a_msg_headers = array();
15a9d1 566       $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
1cded8 567
T 568       // delete cached messages with a higher index than $max
569       $this->clear_message_cache($cache_key, $max);
570
15a9d1 571         
1cded8 572       // kick child process to sync cache
31b2ce 573       // ...
1cded8 574       
T 575       }
576
577
578     // return empty array if no messages found
579     if (!is_array($a_msg_headers) || empty($a_msg_headers))
580         return array();
581
582
583     // if not already sorted
584     if (!$headers_sorted)
31b2ce 585       $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
1cded8 586
T 587     return array_values($a_msg_headers);
4e17e6 588     }
4647e1 589     
15a9d1 590
4647e1 591   /**
T 592    * Public method for listing a specific set of headers
593    * convert mailbox name with root dir first
594    *
595    * @param   string   Mailbox/folder name
596    * @param   array    List of message ids to list
597    * @param   number   Current page to list
598    * @param   string   Header field to sort by
599    * @param   string   Sort order [ASC|DESC]
600    * @return  array    Indexed array with message header objects
601    * @access  public   
602    */
603   function list_header_set($mbox='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
604     {
605     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
606     return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);    
607     }
608     
609
610   /**
611    * Private method for listing a set of message headers
612    *
613    * @access  private
614    * @see     rcube_imap::list_header_set
615    */
616   function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
617     {
618     // also accept a comma-separated list of message ids
619     if (is_string($msgs))
620       $msgs = split(',', $msgs);
621       
622     if (!strlen($mailbox) || empty($msgs))
623       return array();
624
625     if ($sort_field!=NULL)
626       $this->sort_field = $sort_field;
627     if ($sort_order!=NULL)
628       $this->sort_order = strtoupper($sort_order);
629
630     $max = count($msgs);
631     $start_msg = ($this->list_page-1) * $this->page_size;
632
633     // fetch reuested headers from server
634     $a_msg_headers = array();
635     $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
636
637     // return empty array if no messages found
638     if (!is_array($a_msg_headers) || empty($a_msg_headers))
639         return array();
640
641     // if not already sorted
642     $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
643
644     // only return the requested part of the set
ac6b87 645     return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
4647e1 646     }
T 647
648
649   /**
650    * Helper function to get first and last index of the requested set
651    *
652    * @param  number  message count
653    * @param  mixed   page number to show, or string 'all'
654    * @return array   array with two values: first index, last index
655    * @access private
656    */
657   function _get_message_range($max, $page)
658     {
659     $start_msg = ($this->list_page-1) * $this->page_size;
660     
661     if ($page=='all')
662       {
663       $begin = 0;
664       $end = $max;
665       }
666     else if ($this->sort_order=='DESC')
667       {
668       $begin = $max - $this->page_size - $start_msg;
669       $end =   $max - $start_msg;
670       }
671     else
672       {
673       $begin = $start_msg;
674       $end   = $start_msg + $this->page_size;
675       }
676
677     if ($begin < 0) $begin = 0;
678     if ($end < 0) $end = $max;
679     if ($end > $max) $end = $max;
680     
681     return array($begin, $end);
682     }
683     
684     
15a9d1 685
T 686   /**
687    * Fetches message headers
688    * Used for loop
689    *
690    * @param  string  Mailbox name
4647e1 691    * @param  string  Message index to fetch
15a9d1 692    * @param  array   Reference to message headers array
T 693    * @param  array   Array with cache index
694    * @return number  Number of deleted messages
695    * @access private
696    */
697   function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
698     {
699     // cache is incomplete
700     $cache_index = $this->get_message_cache_index($cache_key);
4647e1 701     
15a9d1 702     // fetch reuested headers from server
T 703     $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
704     $deleted_count = 0;
705     
706     if (!empty($a_header_index))
707       {
708       foreach ($a_header_index as $i => $headers)
709         {
710         if ($headers->deleted && $this->skip_deleted)
711           {
712           // delete from cache
713           if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
714             $this->remove_message_cache($cache_key, $headers->id);
715
716           $deleted_count++;
717           continue;
718           }
719
720         // add message to cache
721         if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
722           $this->add_message_cache($cache_key, $headers->id, $headers);
723
724         $a_msg_headers[$headers->uid] = $headers;
725         }
726       }
727         
728     return $deleted_count;
729     }
730     
1cded8 731
4e17e6 732
T 733   // return sorted array of message UIDs
31b2ce 734   function message_index($mbox='', $sort_field=NULL, $sort_order=NULL)
4e17e6 735     {
31b2ce 736     if ($sort_field!=NULL)
T 737       $this->sort_field = $sort_field;
738     if ($sort_order!=NULL)
739       $this->sort_order = strtoupper($sort_order);
740
4e17e6 741     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
31b2ce 742     $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
4e17e6 743
31b2ce 744     // have stored it in RAM
T 745     if (isset($this->cache[$key]))
746       return $this->cache[$key];
4e17e6 747
31b2ce 748     // check local cache
T 749     $cache_key = $mailbox.'.msg';
750     $cache_status = $this->check_cache_status($mailbox, $cache_key);
4e17e6 751
31b2ce 752     // cache is OK
T 753     if ($cache_status>0)
754       {
0677ca 755       $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
31b2ce 756       return array_values($a_index);
T 757       }
758
759
760     // fetch complete message index
761     $msg_count = $this->_messagecount($mailbox);
762     if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field)))
763       {
764       $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
765
766       if ($this->sort_order == 'DESC')
767         $a_index = array_reverse($a_index);
768
769       $i = 0;
770       $this->cache[$key] = array();
771       foreach ($a_index as $index => $value)
772         $this->cache[$key][$i++] = $a_uids[$value];
773       }
774     else
775       {
776       $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
777       $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
778     
779       if ($this->sort_order=="ASC")
780         asort($a_index);
781       else if ($this->sort_order=="DESC")
782         arsort($a_index);
783         
784       $i = 0;
785       $this->cache[$key] = array();
786       foreach ($a_index as $index => $value)
787         $this->cache[$key][$i++] = $a_uids[$index];
788       }
789
790     return $this->cache[$key];
4e17e6 791     }
T 792
793
1cded8 794   function sync_header_index($mailbox)
4e17e6 795     {
1cded8 796     $cache_key = $mailbox.'.msg';
T 797     $cache_index = $this->get_message_cache_index($cache_key);
798     $msg_count = $this->_messagecount($mailbox);
799
800     // fetch complete message index
801     $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
802         
803     foreach ($a_message_index as $id => $uid)
804       {
805       // message in cache at correct position
806       if ($cache_index[$id] == $uid)
807         {
808 // console("$id / $uid: OK");
809         unset($cache_index[$id]);
810         continue;
811         }
812         
813       // message in cache but in wrong position
814       if (in_array((string)$uid, $cache_index, TRUE))
815         {
816 // console("$id / $uid: Moved");
817         unset($cache_index[$id]);        
818         }
819       
820       // other message at this position
821       if (isset($cache_index[$id]))
822         {
823 // console("$id / $uid: Delete");
824         $this->remove_message_cache($cache_key, $id);
825         unset($cache_index[$id]);
826         }
827         
828
829 // console("$id / $uid: Add");
830
831       // fetch complete headers and add to cache
832       $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
833       $this->add_message_cache($cache_key, $headers->id, $headers);
834       }
835
836     // those ids that are still in cache_index have been deleted      
837     if (!empty($cache_index))
838       {
839       foreach ($cache_index as $id => $uid)
840         $this->remove_message_cache($cache_key, $id);
841       }
4e17e6 842     }
T 843
844
4647e1 845   /**
T 846    * Invoke search request to IMAP server
847    *
848    * @param  string  mailbox name to search in
849    * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
850    * @param  string  search string
851    * @return array   search results as list of message ids
852    * @access public
853    */
854   function search($mbox='', $criteria='ALL', $str=NULL)
4e17e6 855     {
T 856     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
4647e1 857     if ($str && $criteria)
T 858       {
859       $criteria .= " \"$str\"";
860       return $this->_search_index($mailbox, $criteria);
861       }
862     else
863       return $this->_search_index($mailbox, $criteria);
864     }    
865
866
867   /**
868    * Private search method
869    *
870    * @return array   search results as list of message ids
871    * @access private
872    * @see rcube_imap::search()
873    */
31b2ce 874   function _search_index($mailbox, $criteria='ALL')
T 875     {
4e17e6 876     $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
4647e1 877     
T 878     // clean message list (there might be some empty entries)
879     foreach ($a_messages as $i => $val)
880       if (empty($val))
881         unset($a_messages[$i]);
882         
4e17e6 883     return $a_messages;
T 884     }
885
886
15a9d1 887   function get_headers($id, $mbox=NULL, $is_uid=TRUE)
4e17e6 888     {
T 889     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1cded8 890
4e17e6 891     // get cached headers
bde645 892     if ($is_uid && ($headers = $this->get_cached_message($mailbox.'.msg', $id)))
1cded8 893       return $headers;
520c36 894
15a9d1 895     $msg_id = $is_uid ? $this->_uid2id($id) : $id;
1cded8 896     $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
520c36 897
4e17e6 898     // write headers cache
1cded8 899     if ($headers)
T 900       $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
4e17e6 901
1cded8 902     return $headers;
4e17e6 903     }
T 904
905
906   function get_body($uid, $part=1)
907     {
908     if (!($msg_id = $this->_uid2id($uid)))
909       return FALSE;
910
911     $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
912     $structure = iml_GetRawStructureArray($structure_str);
913     $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
914
915     $encoding = iml_GetPartEncodingCode($structure, $part);
916     
917     if ($encoding==3) $body = $this->mime_decode($body, 'base64');
918     else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
919
920     return $body;
921     }
922
923
924   function get_raw_body($uid)
925     {
926     if (!($msg_id = $this->_uid2id($uid)))
927       return FALSE;
928
929     $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
930     $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
931
932     return $body;    
933     }
934
935
936   // set message flag to one or several messages
937   // possible flgs are: SEEN, DELETED, RECENT, ANSWERED, DRAFT
938   function set_flag($uids, $flag)
939     {
940     $flag = strtoupper($flag);
941     $msg_ids = array();
942     if (!is_array($uids))
943       $uids = array($uids);
944       
945     foreach ($uids as $uid)
31b2ce 946       $msg_ids[$uid] = $this->_uid2id($uid);
4e17e6 947       
T 948     if ($flag=='UNSEEN')
31b2ce 949       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
4e17e6 950     else
31b2ce 951       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
4e17e6 952
T 953     // reload message headers if cached
954     $cache_key = $this->mailbox.'.msg';
1cded8 955     if ($this->caching_enabled)
4e17e6 956       {
31b2ce 957       foreach ($msg_ids as $uid => $id)
4e17e6 958         {
31b2ce 959         if ($cached_headers = $this->get_cached_message($cache_key, $uid))
4e17e6 960           {
1cded8 961           $this->remove_message_cache($cache_key, $id);
T 962           //$this->get_headers($uid);
4e17e6 963           }
T 964         }
1cded8 965
T 966       // close and re-open connection
967       // this prevents connection problems with Courier 
968       $this->reconnect();
4e17e6 969       }
T 970
971     // set nr of messages that were flaged
31b2ce 972     $count = count($msg_ids);
4e17e6 973
T 974     // clear message count cache
975     if ($result && $flag=='SEEN')
976       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
977     else if ($result && $flag=='UNSEEN')
978       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
979     else if ($result && $flag=='DELETED')
980       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
981
982     return $result;
983     }
984
985
986   // append a mail message (source) to a specific mailbox
987   function save_message($mbox, $message)
988     {
989     $mailbox = $this->_mod_mailbox($mbox);
990
f88d41 991     // make sure mailbox exists
4e17e6 992     if (in_array($mailbox, $this->_list_mailboxes()))
T 993       $saved = iil_C_Append($this->conn, $mailbox, $message);
1cded8 994
4e17e6 995     if ($saved)
T 996       {
997       // increase messagecount of the target mailbox
998       $this->_set_messagecount($mailbox, 'ALL', 1);
999       }
1000           
1001     return $saved;
1002     }
1003
1004
1005   // move a message from one mailbox to another
1006   function move_message($uids, $to_mbox, $from_mbox='')
1007     {
1008     $to_mbox = $this->_mod_mailbox($to_mbox);
1009     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
1010
f88d41 1011     // make sure mailbox exists
4e17e6 1012     if (!in_array($to_mbox, $this->_list_mailboxes()))
f88d41 1013       {
T 1014       if (in_array(strtolower($to_mbox), $this->default_folders))
1015         $this->create_mailbox($to_mbox, TRUE);
1016       else
1017         return FALSE;
1018       }
1019
4e17e6 1020     // convert the list of uids to array
T 1021     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1022     
1023     // exit if no message uids are specified
1024     if (!is_array($a_uids))
1025       return false;
520c36 1026
4e17e6 1027     // convert uids to message ids
T 1028     $a_mids = array();
1029     foreach ($a_uids as $uid)
1030       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 1031
4e17e6 1032     $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 1033     
1034     // send expunge command in order to have the moved message
1035     // really deleted from the source mailbox
1036     if ($moved)
1037       {
1cded8 1038       $this->_expunge($from_mbox, FALSE);
4e17e6 1039       $this->_clear_messagecount($from_mbox);
T 1040       $this->_clear_messagecount($to_mbox);
1041       }
1042
1043     // update cached message headers
1044     $cache_key = $from_mbox.'.msg';
1cded8 1045     if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1046       {
1cded8 1047       $start_index = 100000;
4e17e6 1048       foreach ($a_uids as $uid)
1cded8 1049         {
T 1050         $index = array_search($uid, $a_cache_index);
1051         $start_index = min($index, $start_index);
1052         }
4e17e6 1053
1cded8 1054       // clear cache from the lowest index on
T 1055       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1056       }
T 1057
1058     return $moved;
1059     }
1060
1061
1062   // mark messages as deleted and expunge mailbox
1063   function delete_message($uids, $mbox='')
1064     {
1065     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1066
1067     // convert the list of uids to array
1068     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1069     
1070     // exit if no message uids are specified
1071     if (!is_array($a_uids))
1072       return false;
1073
1074
1075     // convert uids to message ids
1076     $a_mids = array();
1077     foreach ($a_uids as $uid)
1078       $a_mids[] = $this->_uid2id($uid, $mailbox);
1079         
1080     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1081     
1082     // send expunge command in order to have the deleted message
1083     // really deleted from the mailbox
1084     if ($deleted)
1085       {
1cded8 1086       $this->_expunge($mailbox, FALSE);
4e17e6 1087       $this->_clear_messagecount($mailbox);
T 1088       }
1089
1090     // remove deleted messages from cache
1cded8 1091     $cache_key = $mailbox.'.msg';
T 1092     if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1093       {
1cded8 1094       $start_index = 100000;
4e17e6 1095       foreach ($a_uids as $uid)
1cded8 1096         {
T 1097         $index = array_search($uid, $a_cache_index);
1098         $start_index = min($index, $start_index);
1099         }
4e17e6 1100
1cded8 1101       // clear cache from the lowest index on
T 1102       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1103       }
T 1104
1105     return $deleted;
1106     }
1107
1108
a95e0e 1109   // clear all messages in a specific mailbox
15a9d1 1110   function clear_mailbox($mbox=NULL)
a95e0e 1111     {
15a9d1 1112     $mailbox = !empty($mbox) ? $this->_mod_mailbox($mbox) : $this->mailbox;
a95e0e 1113     $msg_count = $this->_messagecount($mailbox, 'ALL');
T 1114     
1115     if ($msg_count>0)
1cded8 1116       {
5e3512 1117       $cleared = iil_C_ClearFolder($this->conn, $mailbox);
T 1118       
1119       // make sure the message count cache is cleared as well
1120       if ($cleared)
1121         {
1122         $this->clear_message_cache($mailbox.'.msg');      
1123         $a_mailbox_cache = $this->get_cache('messagecount');
1124         unset($a_mailbox_cache[$mailbox]);
1125         $this->update_cache('messagecount', $a_mailbox_cache);
1126         }
1127         
1128       return $cleared;
1cded8 1129       }
a95e0e 1130     else
T 1131       return 0;
1132     }
1133
1134
4e17e6 1135   // send IMAP expunge command and clear cache
T 1136   function expunge($mbox='', $clear_cache=TRUE)
1137     {
1138     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1cded8 1139     return $this->_expunge($mailbox, $clear_cache);
T 1140     }
1141
1142
1143   // send IMAP expunge command and clear cache
1144   function _expunge($mailbox, $clear_cache=TRUE)
1145     {
4e17e6 1146     $result = iil_C_Expunge($this->conn, $mailbox);
T 1147
1148     if ($result>=0 && $clear_cache)
1149       {
1cded8 1150       //$this->clear_message_cache($mailbox.'.msg');
4e17e6 1151       $this->_clear_messagecount($mailbox);
T 1152       }
1153       
1154     return $result;
1155     }
1156
1157
1158   /* --------------------------------
1159    *        folder managment
1160    * --------------------------------*/
1161
1162
1163   // return an array with all folders available in IMAP server
1164   function list_unsubscribed($root='')
1165     {
1166     static $sa_unsubscribed;
1167     
1168     if (is_array($sa_unsubscribed))
1169       return $sa_unsubscribed;
1170       
1171     // retrieve list of folders from IMAP server
1172     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1173
1174     // modify names with root dir
1175     foreach ($a_mboxes as $mbox)
1176       {
1177       $name = $this->_mod_mailbox($mbox, 'out');
1178       if (strlen($name))
1179         $a_folders[] = $name;
1180       }
1181
1182     // filter folders and sort them
1183     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1184     return $sa_unsubscribed;
1185     }
1186
1187
58e360 1188   /**
T 1189    * Get quota
1190    * added by Nuny
1191    */
1192   function get_quota()
1193     {
1194     if ($this->get_capability('QUOTA'))
1195       {
1196       $result = iil_C_GetQuota($this->conn);
4647e1 1197       if ($result["total"])
T 1198         return sprintf("%.2fMB / %.2fMB (%.0f%%)", $result["used"] / 1000.0, $result["total"] / 1000.0, $result["percent"]);       
58e360 1199       }
4647e1 1200
T 1201     return FALSE;
58e360 1202     }
T 1203
1204
4e17e6 1205   // subscribe to a specific mailbox(es)
T 1206   function subscribe($mbox, $mode='subscribe')
1207     {
1208     if (is_array($mbox))
1209       $a_mboxes = $mbox;
1210     else if (is_string($mbox) && strlen($mbox))
1211       $a_mboxes = explode(',', $mbox);
1212     
1213     // let this common function do the main work
1214     return $this->_change_subscription($a_mboxes, 'subscribe');
1215     }
1216
1217
1218   // unsubscribe mailboxes
1219   function unsubscribe($mbox)
1220     {
1221     if (is_array($mbox))
1222       $a_mboxes = $mbox;
1223     else if (is_string($mbox) && strlen($mbox))
1224       $a_mboxes = explode(',', $mbox);
1225
1226     // let this common function do the main work
1227     return $this->_change_subscription($a_mboxes, 'unsubscribe');
1228     }
1229
1230
1231   // create a new mailbox on the server and register it in local cache
1232   function create_mailbox($name, $subscribe=FALSE)
1233     {
1234     $result = FALSE;
1cded8 1235     
T 1236     // replace backslashes
1237     $name = preg_replace('/[\\\]+/', '-', $name);
1238
a95e0e 1239     $name_enc = UTF7EncodeString($name);
1cded8 1240
T 1241     // reduce mailbox name to 100 chars
1242     $name_enc = substr($name_enc, 0, 100);
1243
a95e0e 1244     $abs_name = $this->_mod_mailbox($name_enc);
4e17e6 1245     $a_mailbox_cache = $this->get_cache('mailboxes');
1cded8 1246         
4e17e6 1247     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 1248       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 1249
T 1250     // update mailboxlist cache
1251     if ($result && $subscribe)
a95e0e 1252       $this->subscribe($name_enc);
4e17e6 1253
7902df 1254     return $result ? $name : FALSE;
4e17e6 1255     }
T 1256
1257
1258   // set a new name to an existing mailbox
1259   function rename_mailbox($mbox, $new_name)
1260     {
1261     // not implemented yet
1262     }
1263
1264
1265   // remove mailboxes from server
1266   function delete_mailbox($mbox)
1267     {
1268     $deleted = FALSE;
1269
1270     if (is_array($mbox))
1271       $a_mboxes = $mbox;
1272     else if (is_string($mbox) && strlen($mbox))
1273       $a_mboxes = explode(',', $mbox);
1274
1275     if (is_array($a_mboxes))
1276       foreach ($a_mboxes as $mbox)
1277         {
1278         $mailbox = $this->_mod_mailbox($mbox);
1279
1280         // unsubscribe mailbox before deleting
1281         iil_C_UnSubscribe($this->conn, $mailbox);
1282         
1283         // send delete command to server
1284         $result = iil_C_DeleteFolder($this->conn, $mailbox);
1285         if ($result>=0)
1286           $deleted = TRUE;
1287         }
1288
1289     // clear mailboxlist cache
1290     if ($deleted)
1cded8 1291       {
T 1292       $this->clear_message_cache($mailbox.'.msg');
4e17e6 1293       $this->clear_cache('mailboxes');
1cded8 1294       }
4e17e6 1295
1cded8 1296     return $deleted;
4e17e6 1297     }
T 1298
1299
1300
1301
1302   /* --------------------------------
1cded8 1303    *   internal caching methods
4e17e6 1304    * --------------------------------*/
6dc026 1305
T 1306
1307   function set_caching($set)
1308     {
1cded8 1309     if ($set && is_object($this->db))
6dc026 1310       $this->caching_enabled = TRUE;
T 1311     else
1312       $this->caching_enabled = FALSE;
1313     }
1cded8 1314
4e17e6 1315
T 1316   function get_cache($key)
1317     {
1318     // read cache
6dc026 1319     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 1320       {
1cded8 1321       $cache_data = $this->_read_cache_record('IMAP.'.$key);
4e17e6 1322       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
T 1323       }
1324     
1cded8 1325     return $this->cache[$key];
4e17e6 1326     }
T 1327
1328
1329   function update_cache($key, $data)
1330     {
1331     $this->cache[$key] = $data;
1332     $this->cache_changed = TRUE;
1333     $this->cache_changes[$key] = TRUE;
1334     }
1335
1336
1337   function write_cache()
1338     {
6dc026 1339     if ($this->caching_enabled && $this->cache_changed)
4e17e6 1340       {
T 1341       foreach ($this->cache as $key => $data)
1342         {
1343         if ($this->cache_changes[$key])
1cded8 1344           $this->_write_cache_record('IMAP.'.$key, serialize($data));
4e17e6 1345         }
T 1346       }    
1347     }
1348
1349
1350   function clear_cache($key=NULL)
1351     {
1352     if ($key===NULL)
1353       {
1354       foreach ($this->cache as $key => $data)
1cded8 1355         $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1356
T 1357       $this->cache = array();
1358       $this->cache_changed = FALSE;
1359       $this->cache_changes = array();
1360       }
1361     else
1362       {
1cded8 1363       $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1364       $this->cache_changes[$key] = FALSE;
T 1365       unset($this->cache[$key]);
1366       }
1367     }
1368
1369
1370
1cded8 1371   function _read_cache_record($key)
T 1372     {
1373     $cache_data = FALSE;
1374     
1375     if ($this->db)
1376       {
1377       // get cached data from DB
1378       $sql_result = $this->db->query(
1379         "SELECT cache_id, data
1380          FROM ".get_table_name('cache')."
1381          WHERE  user_id=?
1382          AND    cache_key=?",
1383         $_SESSION['user_id'],
1384         $key);
1385
1386       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1387         {
1388         $cache_data = $sql_arr['data'];
1389         $this->cache_keys[$key] = $sql_arr['cache_id'];
1390         }
1391       }
1392
1393     return $cache_data;    
1394     }
1395     
1396
1397   function _write_cache_record($key, $data)
1398     {
1399     if (!$this->db)
1400       return FALSE;
1401
1402     // check if we already have a cache entry for this key
1403     if (!isset($this->cache_keys[$key]))
1404       {
1405       $sql_result = $this->db->query(
1406         "SELECT cache_id
1407          FROM ".get_table_name('cache')."
1408          WHERE  user_id=?
1409          AND    cache_key=?",
1410         $_SESSION['user_id'],
1411         $key);
1412                                      
1413       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1414         $this->cache_keys[$key] = $sql_arr['cache_id'];
1415       else
1416         $this->cache_keys[$key] = FALSE;
1417       }
1418
1419     // update existing cache record
1420     if ($this->cache_keys[$key])
1421       {
1422       $this->db->query(
1423         "UPDATE ".get_table_name('cache')."
1424          SET    created=now(),
1425                 data=?
1426          WHERE  user_id=?
1427          AND    cache_key=?",
1428         $data,
1429         $_SESSION['user_id'],
1430         $key);
1431       }
1432     // add new cache record
1433     else
1434       {
1435       $this->db->query(
1436         "INSERT INTO ".get_table_name('cache')."
1437          (created, user_id, cache_key, data)
1438          VALUES (now(), ?, ?, ?)",
1439         $_SESSION['user_id'],
1440         $key,
1441         $data);
1442       }
1443     }
1444
1445
1446   function _clear_cache_record($key)
1447     {
1448     $this->db->query(
1449       "DELETE FROM ".get_table_name('cache')."
1450        WHERE  user_id=?
1451        AND    cache_key=?",
1452       $_SESSION['user_id'],
1453       $key);
1454     }
1455
1456
1457
4e17e6 1458   /* --------------------------------
1cded8 1459    *   message caching methods
T 1460    * --------------------------------*/
1461    
1462
1463   // checks if the cache is up-to-date
1464   // return: -3 = off, -2 = incomplete, -1 = dirty
1465   function check_cache_status($mailbox, $cache_key)
1466     {
1467     if (!$this->caching_enabled)
1468       return -3;
1469
1470     $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1471     $msg_count = $this->_messagecount($mailbox);
1472     $cache_count = count($cache_index);
1473
1474     // console("Cache check: $msg_count !== ".count($cache_index));
1475
1476     if ($cache_count==$msg_count)
1477       {
1478       // get highest index
1479       $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1480       $cache_uid = array_pop($cache_index);
1481       
1482       // uids of highes message matches -> cache seems OK
1483       if ($cache_uid == $header->uid)
1484         return 1;
1485
1486       // cache is dirty
1487       return -1;
1488       }
1489     // if cache count differs less that 10% report as dirty
1490     else if (abs($msg_count - $cache_count) < $msg_count/10)
1491       return -1;
1492     else
1493       return -2;
1494     }
1495
1496
1497
1498   function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1499     {
1500     $cache_key = "$key:$from:$to:$sort_field:$sort_order";
1501     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
1502     
1503     if (!in_array($sort_field, $db_header_fields))
1504       $sort_field = 'idx';
1505     
1506     if ($this->caching_enabled && !isset($this->cache[$cache_key]))
1507       {
1508       $this->cache[$cache_key] = array();
1509       $sql_result = $this->db->limitquery(
1510         "SELECT idx, uid, headers
1511          FROM ".get_table_name('messages')."
1512          WHERE  user_id=?
1513          AND    cache_key=?
1514          ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
1515          strtoupper($sort_order),
1516         $from,
1517         $to-$from,
1518         $_SESSION['user_id'],
1519         $key);
1520
1521       while ($sql_arr = $this->db->fetch_assoc($sql_result))
1522         {
1523         $uid = $sql_arr['uid'];
1524         $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
1525         }
1526       }
1527       
1528     return $this->cache[$cache_key];
1529     }
1530
1531
1532   function get_cached_message($key, $uid, $body=FALSE)
1533     {
1534     if (!$this->caching_enabled)
1535       return FALSE;
1536
1537     $internal_key = '__single_msg';
1538     if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
1539       {
1540       $sql_select = "idx, uid, headers";
1541       if ($body)
1542         $sql_select .= ", body";
1543       
1544       $sql_result = $this->db->query(
1545         "SELECT $sql_select
1546          FROM ".get_table_name('messages')."
1547          WHERE  user_id=?
1548          AND    cache_key=?
1549          AND    uid=?",
1550         $_SESSION['user_id'],
1551         $key,
1552         $uid);
1553       
1554       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1555         {
1556         $headers = unserialize($sql_arr['headers']);
1557         if (is_object($headers) && !empty($sql_arr['body']))
1558           $headers->body = $sql_arr['body'];
1559
1560         $this->cache[$internal_key][$uid] = $headers;
1561         }
1562       }
1563
1564     return $this->cache[$internal_key][$uid];
1565     }
1566
1567    
0677ca 1568   function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
1cded8 1569     {
T 1570     static $sa_message_index = array();
1571     
4647e1 1572     // empty key -> empty array
T 1573     if (empty($key))
1574       return array();
1575     
1cded8 1576     if (!empty($sa_message_index[$key]) && !$force)
T 1577       return $sa_message_index[$key];
1578     
1579     $sa_message_index[$key] = array();
1580     $sql_result = $this->db->query(
1581       "SELECT idx, uid
1582        FROM ".get_table_name('messages')."
1583        WHERE  user_id=?
1584        AND    cache_key=?
0677ca 1585        ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
1cded8 1586       $_SESSION['user_id'],
T 1587       $key);
1588
1589     while ($sql_arr = $this->db->fetch_assoc($sql_result))
1590       $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
1591       
1592     return $sa_message_index[$key];
1593     }
1594
1595
1596   function add_message_cache($key, $index, $headers)
1597     {
4647e1 1598     if (!$key || !is_object($headers) || empty($headers->uid))
31b2ce 1599       return;
T 1600
1cded8 1601     $this->db->query(
T 1602       "INSERT INTO ".get_table_name('messages')."
0677ca 1603        (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
T 1604        VALUES (?, 0, ?, now(), ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
1cded8 1605       $_SESSION['user_id'],
T 1606       $key,
1607       $index,
1608       $headers->uid,
e93c08 1609       (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
T 1610       (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
1611       (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
1612       (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
31b2ce 1613       (int)$headers->size,
1cded8 1614       serialize($headers));
T 1615     }
1616     
1617     
1618   function remove_message_cache($key, $index)
1619     {
1620     $this->db->query(
1621       "DELETE FROM ".get_table_name('messages')."
1622        WHERE  user_id=?
1623        AND    cache_key=?
1624        AND    idx=?",
1625       $_SESSION['user_id'],
1626       $key,
1627       $index);
1628     }
1629
1630
1631   function clear_message_cache($key, $start_index=1)
1632     {
1633     $this->db->query(
1634       "DELETE FROM ".get_table_name('messages')."
1635        WHERE  user_id=?
1636        AND    cache_key=?
1637        AND    idx>=?",
1638       $_SESSION['user_id'],
1639       $key,
1640       $start_index);
1641     }
1642
1643
1644
1645
1646   /* --------------------------------
1647    *   encoding/decoding methods
4e17e6 1648    * --------------------------------*/
T 1649
1650   
1651   function decode_address_list($input, $max=NULL)
1652     {
1653     $a = $this->_parse_address_list($input);
1654     $out = array();
1655
1656     if (!is_array($a))
1657       return $out;
1658
1659     $c = count($a);
1660     $j = 0;
1661
1662     foreach ($a as $val)
1663       {
1664       $j++;
1665       $address = $val['address'];
1666       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
1667       $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
1668       
1669       $out[$j] = array('name' => $name,
1670                        'mailto' => $address,
1671                        'string' => $string);
1672               
1673       if ($max && $j==$max)
1674         break;
1675       }
1676     
1677     return $out;
1678     }
1679
1680
1cded8 1681   function decode_header($input, $remove_quotes=FALSE)
4e17e6 1682     {
31b2ce 1683     $str = $this->decode_mime_string((string)$input);
1cded8 1684     if ($str{0}=='"' && $remove_quotes)
T 1685       {
1686       $str = str_replace('"', '', $str);
1687       }
1688     
1689     return $str;
4b0f65 1690     }
T 1691     
1692     
1693   function decode_mime_string($input)
1694     {
4e17e6 1695     $out = '';
T 1696
1697     $pos = strpos($input, '=?');
1698     if ($pos !== false)
1699       {
1700       $out = substr($input, 0, $pos);
1701   
1702       $end_cs_pos = strpos($input, "?", $pos+2);
1703       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1704       $end_pos = strpos($input, "?=", $end_en_pos+1);
1705   
1706       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1707       $rest = substr($input, $end_pos+2);
1708
4b0f65 1709       $out .= rcube_imap::_decode_mime_string_part($encstr);
T 1710       $out .= rcube_imap::decode_mime_string($rest);
4e17e6 1711
T 1712       return $out;
1713       }
1714     else
1715       return $input;
1716     }
1717
1718
4b0f65 1719   function _decode_mime_string_part($str)
4e17e6 1720     {
T 1721     $a = explode('?', $str);
1722     $count = count($a);
1723
1724     // should be in format "charset?encoding?base64_string"
1725     if ($count >= 3)
1726       {
1727       for ($i=2; $i<$count; $i++)
1728         $rest.=$a[$i];
1729
1730       if (($a[1]=="B")||($a[1]=="b"))
1731         $rest = base64_decode($rest);
1732       else if (($a[1]=="Q")||($a[1]=="q"))
1733         {
1734         $rest = str_replace("_", " ", $rest);
1735         $rest = quoted_printable_decode($rest);
1736         }
1737
3f9edb 1738       return rcube_charset_convert($rest, $a[0]);
4e17e6 1739       }
T 1740     else
3f9edb 1741       return $str;    // we dont' know what to do with this  
4e17e6 1742     }
T 1743
1744
1745   function mime_decode($input, $encoding='7bit')
1746     {
1747     switch (strtolower($encoding))
1748       {
1749       case '7bit':
1750         return $input;
1751         break;
1752       
1753       case 'quoted-printable':
1754         return quoted_printable_decode($input);
1755         break;
1756       
1757       case 'base64':
1758         return base64_decode($input);
1759         break;
1760       
1761       default:
1762         return $input;
1763       }
1764     }
1765
1766
1767   function mime_encode($input, $encoding='7bit')
1768     {
1769     switch ($encoding)
1770       {
1771       case 'quoted-printable':
1772         return quoted_printable_encode($input);
1773         break;
1774
1775       case 'base64':
1776         return base64_encode($input);
1777         break;
1778
1779       default:
1780         return $input;
1781       }
1782     }
1783
1784
1785   // convert body chars according to the ctype_parameters
1786   function charset_decode($body, $ctype_param)
1787     {
a95e0e 1788     if (is_array($ctype_param) && !empty($ctype_param['charset']))
3f9edb 1789       return rcube_charset_convert($body, $ctype_param['charset']);
4e17e6 1790
T 1791     return $body;
1792     }
1793
1794
1cded8 1795
4e17e6 1796   /* --------------------------------
T 1797    *         private methods
1798    * --------------------------------*/
1799
1800
1801   function _mod_mailbox($mbox, $mode='in')
1802     {
f619de 1803     if ((!empty($this->root_ns) && $this->root_ns == $mbox) || ($mbox == 'INBOX' && $mode == 'in'))
7902df 1804       return $mbox;
T 1805
f619de 1806     if (!empty($this->root_dir) && $mode=='in') 
520c36 1807       $mbox = $this->root_dir.$this->delimiter.$mbox;
7902df 1808     else if (strlen($this->root_dir) && $mode=='out') 
4e17e6 1809       $mbox = substr($mbox, strlen($this->root_dir)+1);
T 1810
1811     return $mbox;
1812     }
1813
1814
1815   // sort mailboxes first by default folders and then in alphabethical order
1816   function _sort_mailbox_list($a_folders)
1817     {
1818     $a_out = $a_defaults = array();
1819
1820     // find default folders and skip folders starting with '.'
1821     foreach($a_folders as $i => $folder)
1822       {
1823       if ($folder{0}=='.')
1824           continue;
1825           
1826       if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1827           $a_defaults[$p] = $folder;
1828       else
1829         $a_out[] = $folder;
1830       }
1831
1832     sort($a_out);
1833     ksort($a_defaults);
1834     
1835     return array_merge($a_defaults, $a_out);
1836     }
1837
1838
1839   function _uid2id($uid, $mbox=NULL)
1840     {
1841     if (!$mbox)
1842       $mbox = $this->mailbox;
1843       
1844     if (!isset($this->uid_id_map[$mbox][$uid]))
1845       $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid);
1846
1847     return $this->uid_id_map[$mbox][$uid];
1848     }
1849
1850
1cded8 1851   // parse string or array of server capabilities and put them in internal array
T 1852   function _parse_capability($caps)
1853     {
1854     if (!is_array($caps))
1855       $cap_arr = explode(' ', $caps);
1856     else
1857       $cap_arr = $caps;
1858     
1859     foreach ($cap_arr as $cap)
1860       {
1861       if ($cap=='CAPABILITY')
1862         continue;
1863
1864       if (strpos($cap, '=')>0)
1865         {
1866         list($key, $value) = explode('=', $cap);
1867         if (!is_array($this->capabilities[$key]))
1868           $this->capabilities[$key] = array();
1869           
1870         $this->capabilities[$key][] = $value;
1871         }
1872       else
1873         $this->capabilities[$cap] = TRUE;
1874       }
1875     }
1876
1877
4e17e6 1878   // subscribe/unsubscribe a list of mailboxes and update local cache
T 1879   function _change_subscription($a_mboxes, $mode)
1880     {
1881     $updated = FALSE;
1882     
1883     if (is_array($a_mboxes))
1884       foreach ($a_mboxes as $i => $mbox)
1885         {
1886         $mailbox = $this->_mod_mailbox($mbox);
1887         $a_mboxes[$i] = $mailbox;
1888
1889         if ($mode=='subscribe')
1890           $result = iil_C_Subscribe($this->conn, $mailbox);
1891         else if ($mode=='unsubscribe')
1892           $result = iil_C_UnSubscribe($this->conn, $mailbox);
1893
1894         if ($result>=0)
1895           $updated = TRUE;
1896         }
1897         
1898     // get cached mailbox list    
1899     if ($updated)
1900       {
1901       $a_mailbox_cache = $this->get_cache('mailboxes');
1902       if (!is_array($a_mailbox_cache))
1903         return $updated;
1904
1905       // modify cached list
1906       if ($mode=='subscribe')
1907         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1908       else if ($mode=='unsubscribe')
1909         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1910         
1911       // write mailboxlist to cache
1912       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1913       }
1914
1915     return $updated;
1916     }
1917
1918
1919   // increde/decrese messagecount for a specific mailbox
1920   function _set_messagecount($mbox, $mode, $increment)
1921     {
1922     $a_mailbox_cache = FALSE;
1923     $mailbox = $mbox ? $mbox : $this->mailbox;
1924     $mode = strtoupper($mode);
1925
1926     $a_mailbox_cache = $this->get_cache('messagecount');
1927     
1928     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1929       return FALSE;
1930     
1931     // add incremental value to messagecount
1932     $a_mailbox_cache[$mailbox][$mode] += $increment;
31b2ce 1933     
T 1934     // there's something wrong, delete from cache
1935     if ($a_mailbox_cache[$mailbox][$mode] < 0)
1936       unset($a_mailbox_cache[$mailbox][$mode]);
4e17e6 1937
T 1938     // write back to cache
1939     $this->update_cache('messagecount', $a_mailbox_cache);
1940     
1941     return TRUE;
1942     }
1943
1944
1945   // remove messagecount of a specific mailbox from cache
1946   function _clear_messagecount($mbox='')
1947     {
1948     $a_mailbox_cache = FALSE;
1949     $mailbox = $mbox ? $mbox : $this->mailbox;
1950
1951     $a_mailbox_cache = $this->get_cache('messagecount');
1952
1953     if (is_array($a_mailbox_cache[$mailbox]))
1954       {
1955       unset($a_mailbox_cache[$mailbox]);
1956       $this->update_cache('messagecount', $a_mailbox_cache);
1957       }
1958     }
1959
1960
1961   function _parse_address_list($str)
1962     {
1963     $a = $this->_explode_quoted_string(',', $str);
1964     $result = array();
1965
1966     foreach ($a as $key => $val)
1967       {
1968       $val = str_replace("\"<", "\" <", $val);
1969       $sub_a = $this->_explode_quoted_string(' ', $val);
1970       
1971       foreach ($sub_a as $k => $v)
1972         {
1973         if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
1974           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
1975         else
1976           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
1977         }
1978         
1979       if (empty($result[$key]['name']))
1980         $result[$key]['name'] = $result[$key]['address'];
1981         
1982       $result[$key]['name'] = $this->decode_header($result[$key]['name']);
1983       }
1984     
1985     return $result;
1986     }
1987
1988
1989   function _explode_quoted_string($delimiter, $string)
1990     {
1991     $quotes = explode("\"", $string);
1992     foreach ($quotes as $key => $val)
1993       if (($key % 2) == 1)
1994         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
1995         
1996     $string = implode("\"", $quotes);
1997
1998     $result = explode($delimiter, $string);
1999     foreach ($result as $key => $val) 
2000       $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
2001     
2002     return $result;
2003     }
2004   }
2005
2006
2007
2008
2009
2010 function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
2011   {
2012   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2013   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2014   $eol = "\r\n";
2015   $escape = "=";
2016   $output = "";
2017
2018   while( list(, $line) = each($lines))
2019     {
2020     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2021     $linlen = strlen($line);
2022     $newline = "";
2023     for($i = 0; $i < $linlen; $i++)
2024       {
2025       $c = substr( $line, $i, 1 );
2026       $dec = ord( $c );
2027       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2028         {
2029         $c = "=2E";
2030         }
2031       if ( $dec == 32 )
2032         {
2033         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
2034           {
2035           $c = "=20";
2036           }
2037         else if ( $space_conv )
2038           {
2039           $c = "=20";
2040           }
2041         }
2042       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
2043         {
2044         $h2 = floor($dec/16);
2045         $h1 = floor($dec%16);
2046         $c = $escape.$hex["$h2"].$hex["$h1"];
2047         }
2048          
2049       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
2050         {
2051         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
2052         $newline = "";
2053         // check if newline first character will be point or not
2054         if ( $dec == 46 )
2055           {
2056           $c = "=2E";
2057           }
2058         }
2059       $newline .= $c;
2060       } // end of for
2061     $output .= $newline.$eol;
2062     } // end of while
2063
2064   return trim($output);
2065   }
2066
f88d41 2067 ?>