thomascube
2006-05-05 ded2b7e166d4b0acab09c00f22f379fbabba709a
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();  
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       {
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       }
e6f360 506     else if (!$this->get_capability('sort') && $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
e6f360 584 //    if (!$headers_sorted)
T 585 //      $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
586
587
588       if (!$headers_sorted && $this->sort_order == 'DESC')
589         $a_msg_headers = array_reverse($a_msg_headers);
590       
1cded8 591
T 592     return array_values($a_msg_headers);
4e17e6 593     }
4647e1 594     
15a9d1 595
4647e1 596   /**
T 597    * Public method for listing a specific set of headers
598    * convert mailbox name with root dir first
599    *
600    * @param   string   Mailbox/folder name
601    * @param   array    List of message ids to list
602    * @param   number   Current page to list
603    * @param   string   Header field to sort by
604    * @param   string   Sort order [ASC|DESC]
605    * @return  array    Indexed array with message header objects
606    * @access  public   
607    */
608   function list_header_set($mbox='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
609     {
610     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
611     return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);    
612     }
613     
614
615   /**
616    * Private method for listing a set of message headers
617    *
618    * @access  private
619    * @see     rcube_imap::list_header_set
620    */
621   function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
622     {
623     // also accept a comma-separated list of message ids
624     if (is_string($msgs))
625       $msgs = split(',', $msgs);
626       
627     if (!strlen($mailbox) || empty($msgs))
628       return array();
629
630     if ($sort_field!=NULL)
631       $this->sort_field = $sort_field;
632     if ($sort_order!=NULL)
633       $this->sort_order = strtoupper($sort_order);
634
635     $max = count($msgs);
636     $start_msg = ($this->list_page-1) * $this->page_size;
637
638     // fetch reuested headers from server
639     $a_msg_headers = array();
640     $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
641
642     // return empty array if no messages found
643     if (!is_array($a_msg_headers) || empty($a_msg_headers))
644         return array();
645
646     // if not already sorted
647     $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
648
649     // only return the requested part of the set
ac6b87 650     return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
4647e1 651     }
T 652
653
654   /**
655    * Helper function to get first and last index of the requested set
656    *
657    * @param  number  message count
658    * @param  mixed   page number to show, or string 'all'
659    * @return array   array with two values: first index, last index
660    * @access private
661    */
662   function _get_message_range($max, $page)
663     {
664     $start_msg = ($this->list_page-1) * $this->page_size;
665     
666     if ($page=='all')
667       {
668       $begin = 0;
669       $end = $max;
670       }
671     else if ($this->sort_order=='DESC')
672       {
673       $begin = $max - $this->page_size - $start_msg;
674       $end =   $max - $start_msg;
675       }
676     else
677       {
678       $begin = $start_msg;
679       $end   = $start_msg + $this->page_size;
680       }
681
682     if ($begin < 0) $begin = 0;
683     if ($end < 0) $end = $max;
684     if ($end > $max) $end = $max;
685     
686     return array($begin, $end);
687     }
688     
689     
15a9d1 690
T 691   /**
692    * Fetches message headers
693    * Used for loop
694    *
695    * @param  string  Mailbox name
4647e1 696    * @param  string  Message index to fetch
15a9d1 697    * @param  array   Reference to message headers array
T 698    * @param  array   Array with cache index
699    * @return number  Number of deleted messages
700    * @access private
701    */
702   function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
703     {
704     // cache is incomplete
705     $cache_index = $this->get_message_cache_index($cache_key);
4647e1 706     
15a9d1 707     // fetch reuested headers from server
T 708     $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
709     $deleted_count = 0;
710     
711     if (!empty($a_header_index))
712       {
713       foreach ($a_header_index as $i => $headers)
714         {
715         if ($headers->deleted && $this->skip_deleted)
716           {
717           // delete from cache
718           if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
719             $this->remove_message_cache($cache_key, $headers->id);
720
721           $deleted_count++;
722           continue;
723           }
724
725         // add message to cache
726         if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
727           $this->add_message_cache($cache_key, $headers->id, $headers);
728
729         $a_msg_headers[$headers->uid] = $headers;
730         }
731       }
732         
733     return $deleted_count;
734     }
735     
e6f360 736   
4e17e6 737   // return sorted array of message UIDs
31b2ce 738   function message_index($mbox='', $sort_field=NULL, $sort_order=NULL)
4e17e6 739     {
31b2ce 740     if ($sort_field!=NULL)
T 741       $this->sort_field = $sort_field;
742     if ($sort_order!=NULL)
743       $this->sort_order = strtoupper($sort_order);
744
4e17e6 745     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
31b2ce 746     $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
4e17e6 747
31b2ce 748     // have stored it in RAM
T 749     if (isset($this->cache[$key]))
750       return $this->cache[$key];
4e17e6 751
31b2ce 752     // check local cache
T 753     $cache_key = $mailbox.'.msg';
754     $cache_status = $this->check_cache_status($mailbox, $cache_key);
4e17e6 755
31b2ce 756     // cache is OK
T 757     if ($cache_status>0)
758       {
0677ca 759       $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
31b2ce 760       return array_values($a_index);
T 761       }
762
763
764     // fetch complete message index
765     $msg_count = $this->_messagecount($mailbox);
e6f360 766     if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
31b2ce 767       {
T 768       if ($this->sort_order == 'DESC')
769         $a_index = array_reverse($a_index);
770
e6f360 771       $this->cache[$key] = $a_index;
T 772
31b2ce 773       }
T 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       {
326e87 859       $criteria .= ' CHARSET UTF-8 "'.UTF7EncodeString($str).'"';
4647e1 860       return $this->_search_index($mailbox, $criteria);
T 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
8fae1e 937   // possible flags are: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
4e17e6 938   function set_flag($uids, $flag)
T 939     {
940     $flag = strtoupper($flag);
941     $msg_ids = array();
942     if (!is_array($uids))
8fae1e 943       $uids = explode(',',$uids);
4e17e6 944       
8fae1e 945     foreach ($uids as $uid) {
31b2ce 946       $msg_ids[$uid] = $this->_uid2id($uid);
8fae1e 947     }
4e17e6 948       
8fae1e 949     if ($flag=='UNDELETED')
S 950       $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
951     else if ($flag=='UNSEEN')
31b2ce 952       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
4e17e6 953     else
31b2ce 954       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
4e17e6 955
T 956     // reload message headers if cached
957     $cache_key = $this->mailbox.'.msg';
1cded8 958     if ($this->caching_enabled)
4e17e6 959       {
31b2ce 960       foreach ($msg_ids as $uid => $id)
4e17e6 961         {
31b2ce 962         if ($cached_headers = $this->get_cached_message($cache_key, $uid))
4e17e6 963           {
1cded8 964           $this->remove_message_cache($cache_key, $id);
T 965           //$this->get_headers($uid);
4e17e6 966           }
T 967         }
1cded8 968
T 969       // close and re-open connection
970       // this prevents connection problems with Courier 
971       $this->reconnect();
4e17e6 972       }
T 973
974     // set nr of messages that were flaged
31b2ce 975     $count = count($msg_ids);
4e17e6 976
T 977     // clear message count cache
978     if ($result && $flag=='SEEN')
979       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
980     else if ($result && $flag=='UNSEEN')
981       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
982     else if ($result && $flag=='DELETED')
983       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
984
985     return $result;
986     }
987
988
989   // append a mail message (source) to a specific mailbox
990   function save_message($mbox, $message)
991     {
992     $mailbox = $this->_mod_mailbox($mbox);
993
f88d41 994     // make sure mailbox exists
4e17e6 995     if (in_array($mailbox, $this->_list_mailboxes()))
T 996       $saved = iil_C_Append($this->conn, $mailbox, $message);
1cded8 997
4e17e6 998     if ($saved)
T 999       {
1000       // increase messagecount of the target mailbox
1001       $this->_set_messagecount($mailbox, 'ALL', 1);
1002       }
1003           
1004     return $saved;
1005     }
1006
1007
1008   // move a message from one mailbox to another
1009   function move_message($uids, $to_mbox, $from_mbox='')
1010     {
1011     $to_mbox = $this->_mod_mailbox($to_mbox);
1012     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
1013
f88d41 1014     // make sure mailbox exists
4e17e6 1015     if (!in_array($to_mbox, $this->_list_mailboxes()))
f88d41 1016       {
T 1017       if (in_array(strtolower($to_mbox), $this->default_folders))
1018         $this->create_mailbox($to_mbox, TRUE);
1019       else
1020         return FALSE;
1021       }
1022
4e17e6 1023     // convert the list of uids to array
T 1024     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1025     
1026     // exit if no message uids are specified
1027     if (!is_array($a_uids))
1028       return false;
520c36 1029
4e17e6 1030     // convert uids to message ids
T 1031     $a_mids = array();
1032     foreach ($a_uids as $uid)
1033       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 1034
4e17e6 1035     $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 1036     
1037     // send expunge command in order to have the moved message
1038     // really deleted from the source mailbox
1039     if ($moved)
1040       {
1cded8 1041       $this->_expunge($from_mbox, FALSE);
4e17e6 1042       $this->_clear_messagecount($from_mbox);
T 1043       $this->_clear_messagecount($to_mbox);
1044       }
1045
1046     // update cached message headers
1047     $cache_key = $from_mbox.'.msg';
1cded8 1048     if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1049       {
1cded8 1050       $start_index = 100000;
4e17e6 1051       foreach ($a_uids as $uid)
1cded8 1052         {
T 1053         $index = array_search($uid, $a_cache_index);
1054         $start_index = min($index, $start_index);
1055         }
4e17e6 1056
1cded8 1057       // clear cache from the lowest index on
T 1058       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1059       }
T 1060
1061     return $moved;
1062     }
1063
1064
1065   // mark messages as deleted and expunge mailbox
1066   function delete_message($uids, $mbox='')
1067     {
1068     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1069
1070     // convert the list of uids to array
1071     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1072     
1073     // exit if no message uids are specified
1074     if (!is_array($a_uids))
1075       return false;
1076
1077
1078     // convert uids to message ids
1079     $a_mids = array();
1080     foreach ($a_uids as $uid)
1081       $a_mids[] = $this->_uid2id($uid, $mailbox);
1082         
1083     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1084     
1085     // send expunge command in order to have the deleted message
1086     // really deleted from the mailbox
1087     if ($deleted)
1088       {
1cded8 1089       $this->_expunge($mailbox, FALSE);
4e17e6 1090       $this->_clear_messagecount($mailbox);
T 1091       }
1092
1093     // remove deleted messages from cache
1cded8 1094     $cache_key = $mailbox.'.msg';
T 1095     if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1096       {
1cded8 1097       $start_index = 100000;
4e17e6 1098       foreach ($a_uids as $uid)
1cded8 1099         {
T 1100         $index = array_search($uid, $a_cache_index);
1101         $start_index = min($index, $start_index);
1102         }
4e17e6 1103
1cded8 1104       // clear cache from the lowest index on
T 1105       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1106       }
T 1107
1108     return $deleted;
1109     }
1110
1111
a95e0e 1112   // clear all messages in a specific mailbox
15a9d1 1113   function clear_mailbox($mbox=NULL)
a95e0e 1114     {
15a9d1 1115     $mailbox = !empty($mbox) ? $this->_mod_mailbox($mbox) : $this->mailbox;
a95e0e 1116     $msg_count = $this->_messagecount($mailbox, 'ALL');
T 1117     
1118     if ($msg_count>0)
1cded8 1119       {
5e3512 1120       $cleared = iil_C_ClearFolder($this->conn, $mailbox);
T 1121       
1122       // make sure the message count cache is cleared as well
1123       if ($cleared)
1124         {
1125         $this->clear_message_cache($mailbox.'.msg');      
1126         $a_mailbox_cache = $this->get_cache('messagecount');
1127         unset($a_mailbox_cache[$mailbox]);
1128         $this->update_cache('messagecount', $a_mailbox_cache);
1129         }
1130         
1131       return $cleared;
1cded8 1132       }
a95e0e 1133     else
T 1134       return 0;
1135     }
1136
1137
4e17e6 1138   // send IMAP expunge command and clear cache
T 1139   function expunge($mbox='', $clear_cache=TRUE)
1140     {
1141     $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox;
1cded8 1142     return $this->_expunge($mailbox, $clear_cache);
T 1143     }
1144
1145
1146   // send IMAP expunge command and clear cache
1147   function _expunge($mailbox, $clear_cache=TRUE)
1148     {
4e17e6 1149     $result = iil_C_Expunge($this->conn, $mailbox);
T 1150
1151     if ($result>=0 && $clear_cache)
1152       {
1cded8 1153       //$this->clear_message_cache($mailbox.'.msg');
4e17e6 1154       $this->_clear_messagecount($mailbox);
T 1155       }
1156       
1157     return $result;
1158     }
1159
1160
1161   /* --------------------------------
1162    *        folder managment
1163    * --------------------------------*/
1164
1165
1166   // return an array with all folders available in IMAP server
1167   function list_unsubscribed($root='')
1168     {
1169     static $sa_unsubscribed;
1170     
1171     if (is_array($sa_unsubscribed))
1172       return $sa_unsubscribed;
1173       
1174     // retrieve list of folders from IMAP server
1175     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1176
1177     // modify names with root dir
1178     foreach ($a_mboxes as $mbox)
1179       {
1180       $name = $this->_mod_mailbox($mbox, 'out');
1181       if (strlen($name))
1182         $a_folders[] = $name;
1183       }
1184
1185     // filter folders and sort them
1186     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1187     return $sa_unsubscribed;
1188     }
1189
1190
58e360 1191   /**
T 1192    * Get quota
1193    * added by Nuny
1194    */
1195   function get_quota()
1196     {
1197     if ($this->get_capability('QUOTA'))
1198       {
1199       $result = iil_C_GetQuota($this->conn);
4647e1 1200       if ($result["total"])
T 1201         return sprintf("%.2fMB / %.2fMB (%.0f%%)", $result["used"] / 1000.0, $result["total"] / 1000.0, $result["percent"]);       
58e360 1202       }
4647e1 1203
T 1204     return FALSE;
58e360 1205     }
T 1206
1207
4e17e6 1208   // subscribe to a specific mailbox(es)
T 1209   function subscribe($mbox, $mode='subscribe')
1210     {
1211     if (is_array($mbox))
1212       $a_mboxes = $mbox;
1213     else if (is_string($mbox) && strlen($mbox))
1214       $a_mboxes = explode(',', $mbox);
1215     
1216     // let this common function do the main work
1217     return $this->_change_subscription($a_mboxes, 'subscribe');
1218     }
1219
1220
1221   // unsubscribe mailboxes
1222   function unsubscribe($mbox)
1223     {
1224     if (is_array($mbox))
1225       $a_mboxes = $mbox;
1226     else if (is_string($mbox) && strlen($mbox))
1227       $a_mboxes = explode(',', $mbox);
1228
1229     // let this common function do the main work
1230     return $this->_change_subscription($a_mboxes, 'unsubscribe');
1231     }
1232
1233
1234   // create a new mailbox on the server and register it in local cache
1235   function create_mailbox($name, $subscribe=FALSE)
1236     {
1237     $result = FALSE;
1cded8 1238     
T 1239     // replace backslashes
1240     $name = preg_replace('/[\\\]+/', '-', $name);
1241
a95e0e 1242     $name_enc = UTF7EncodeString($name);
1cded8 1243
T 1244     // reduce mailbox name to 100 chars
1245     $name_enc = substr($name_enc, 0, 100);
1246
a95e0e 1247     $abs_name = $this->_mod_mailbox($name_enc);
4e17e6 1248     $a_mailbox_cache = $this->get_cache('mailboxes');
1cded8 1249         
4e17e6 1250     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 1251       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 1252
T 1253     // update mailboxlist cache
1254     if ($result && $subscribe)
a95e0e 1255       $this->subscribe($name_enc);
4e17e6 1256
7902df 1257     return $result ? $name : FALSE;
4e17e6 1258     }
T 1259
1260
1261   // set a new name to an existing mailbox
1262   function rename_mailbox($mbox, $new_name)
1263     {
1264     // not implemented yet
1265     }
1266
1267
1268   // remove mailboxes from server
1269   function delete_mailbox($mbox)
1270     {
1271     $deleted = FALSE;
1272
1273     if (is_array($mbox))
1274       $a_mboxes = $mbox;
1275     else if (is_string($mbox) && strlen($mbox))
1276       $a_mboxes = explode(',', $mbox);
1277
1278     if (is_array($a_mboxes))
1279       foreach ($a_mboxes as $mbox)
1280         {
1281         $mailbox = $this->_mod_mailbox($mbox);
1282
1283         // unsubscribe mailbox before deleting
1284         iil_C_UnSubscribe($this->conn, $mailbox);
1285         
1286         // send delete command to server
1287         $result = iil_C_DeleteFolder($this->conn, $mailbox);
1288         if ($result>=0)
1289           $deleted = TRUE;
1290         }
1291
1292     // clear mailboxlist cache
1293     if ($deleted)
1cded8 1294       {
T 1295       $this->clear_message_cache($mailbox.'.msg');
4e17e6 1296       $this->clear_cache('mailboxes');
1cded8 1297       }
4e17e6 1298
1cded8 1299     return $deleted;
4e17e6 1300     }
T 1301
1302
1303
1304
1305   /* --------------------------------
1cded8 1306    *   internal caching methods
4e17e6 1307    * --------------------------------*/
6dc026 1308
T 1309
1310   function set_caching($set)
1311     {
1cded8 1312     if ($set && is_object($this->db))
6dc026 1313       $this->caching_enabled = TRUE;
T 1314     else
1315       $this->caching_enabled = FALSE;
1316     }
1cded8 1317
4e17e6 1318
T 1319   function get_cache($key)
1320     {
1321     // read cache
6dc026 1322     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 1323       {
1cded8 1324       $cache_data = $this->_read_cache_record('IMAP.'.$key);
4e17e6 1325       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
T 1326       }
1327     
1cded8 1328     return $this->cache[$key];
4e17e6 1329     }
T 1330
1331
1332   function update_cache($key, $data)
1333     {
1334     $this->cache[$key] = $data;
1335     $this->cache_changed = TRUE;
1336     $this->cache_changes[$key] = TRUE;
1337     }
1338
1339
1340   function write_cache()
1341     {
6dc026 1342     if ($this->caching_enabled && $this->cache_changed)
4e17e6 1343       {
T 1344       foreach ($this->cache as $key => $data)
1345         {
1346         if ($this->cache_changes[$key])
1cded8 1347           $this->_write_cache_record('IMAP.'.$key, serialize($data));
4e17e6 1348         }
T 1349       }    
1350     }
1351
1352
1353   function clear_cache($key=NULL)
1354     {
1355     if ($key===NULL)
1356       {
1357       foreach ($this->cache as $key => $data)
1cded8 1358         $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1359
T 1360       $this->cache = array();
1361       $this->cache_changed = FALSE;
1362       $this->cache_changes = array();
1363       }
1364     else
1365       {
1cded8 1366       $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1367       $this->cache_changes[$key] = FALSE;
T 1368       unset($this->cache[$key]);
1369       }
1370     }
1371
1372
1373
1cded8 1374   function _read_cache_record($key)
T 1375     {
1376     $cache_data = FALSE;
1377     
1378     if ($this->db)
1379       {
1380       // get cached data from DB
1381       $sql_result = $this->db->query(
1382         "SELECT cache_id, data
1383          FROM ".get_table_name('cache')."
1384          WHERE  user_id=?
1385          AND    cache_key=?",
1386         $_SESSION['user_id'],
1387         $key);
1388
1389       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1390         {
1391         $cache_data = $sql_arr['data'];
1392         $this->cache_keys[$key] = $sql_arr['cache_id'];
1393         }
1394       }
1395
1396     return $cache_data;    
1397     }
1398     
1399
1400   function _write_cache_record($key, $data)
1401     {
1402     if (!$this->db)
1403       return FALSE;
1404
1405     // check if we already have a cache entry for this key
1406     if (!isset($this->cache_keys[$key]))
1407       {
1408       $sql_result = $this->db->query(
1409         "SELECT cache_id
1410          FROM ".get_table_name('cache')."
1411          WHERE  user_id=?
1412          AND    cache_key=?",
1413         $_SESSION['user_id'],
1414         $key);
1415                                      
1416       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1417         $this->cache_keys[$key] = $sql_arr['cache_id'];
1418       else
1419         $this->cache_keys[$key] = FALSE;
1420       }
1421
1422     // update existing cache record
1423     if ($this->cache_keys[$key])
1424       {
1425       $this->db->query(
1426         "UPDATE ".get_table_name('cache')."
1427          SET    created=now(),
1428                 data=?
1429          WHERE  user_id=?
1430          AND    cache_key=?",
1431         $data,
1432         $_SESSION['user_id'],
1433         $key);
1434       }
1435     // add new cache record
1436     else
1437       {
1438       $this->db->query(
1439         "INSERT INTO ".get_table_name('cache')."
1440          (created, user_id, cache_key, data)
1441          VALUES (now(), ?, ?, ?)",
1442         $_SESSION['user_id'],
1443         $key,
1444         $data);
1445       }
1446     }
1447
1448
1449   function _clear_cache_record($key)
1450     {
1451     $this->db->query(
1452       "DELETE FROM ".get_table_name('cache')."
1453        WHERE  user_id=?
1454        AND    cache_key=?",
1455       $_SESSION['user_id'],
1456       $key);
1457     }
1458
1459
1460
4e17e6 1461   /* --------------------------------
1cded8 1462    *   message caching methods
T 1463    * --------------------------------*/
1464    
1465
1466   // checks if the cache is up-to-date
1467   // return: -3 = off, -2 = incomplete, -1 = dirty
1468   function check_cache_status($mailbox, $cache_key)
1469     {
1470     if (!$this->caching_enabled)
1471       return -3;
1472
1473     $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1474     $msg_count = $this->_messagecount($mailbox);
1475     $cache_count = count($cache_index);
1476
1477     // console("Cache check: $msg_count !== ".count($cache_index));
1478
1479     if ($cache_count==$msg_count)
1480       {
1481       // get highest index
1482       $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1483       $cache_uid = array_pop($cache_index);
1484       
e6f360 1485       // uids of highest message matches -> cache seems OK
1cded8 1486       if ($cache_uid == $header->uid)
T 1487         return 1;
1488
1489       // cache is dirty
1490       return -1;
1491       }
e6f360 1492     // if cache count differs less than 10% report as dirty
1cded8 1493     else if (abs($msg_count - $cache_count) < $msg_count/10)
T 1494       return -1;
1495     else
1496       return -2;
1497     }
1498
1499
1500
1501   function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1502     {
1503     $cache_key = "$key:$from:$to:$sort_field:$sort_order";
1504     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
1505     
1506     if (!in_array($sort_field, $db_header_fields))
1507       $sort_field = 'idx';
1508     
1509     if ($this->caching_enabled && !isset($this->cache[$cache_key]))
1510       {
1511       $this->cache[$cache_key] = array();
1512       $sql_result = $this->db->limitquery(
1513         "SELECT idx, uid, headers
1514          FROM ".get_table_name('messages')."
1515          WHERE  user_id=?
1516          AND    cache_key=?
1517          ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
1518          strtoupper($sort_order),
1519         $from,
1520         $to-$from,
1521         $_SESSION['user_id'],
1522         $key);
1523
1524       while ($sql_arr = $this->db->fetch_assoc($sql_result))
1525         {
1526         $uid = $sql_arr['uid'];
1527         $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
1528         }
1529       }
1530       
1531     return $this->cache[$cache_key];
1532     }
1533
1534
1535   function get_cached_message($key, $uid, $body=FALSE)
1536     {
1537     if (!$this->caching_enabled)
1538       return FALSE;
1539
1540     $internal_key = '__single_msg';
1541     if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
1542       {
1543       $sql_select = "idx, uid, headers";
1544       if ($body)
1545         $sql_select .= ", body";
1546       
1547       $sql_result = $this->db->query(
1548         "SELECT $sql_select
1549          FROM ".get_table_name('messages')."
1550          WHERE  user_id=?
1551          AND    cache_key=?
1552          AND    uid=?",
1553         $_SESSION['user_id'],
1554         $key,
1555         $uid);
1556       
1557       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1558         {
1559         $headers = unserialize($sql_arr['headers']);
1560         if (is_object($headers) && !empty($sql_arr['body']))
1561           $headers->body = $sql_arr['body'];
1562
1563         $this->cache[$internal_key][$uid] = $headers;
1564         }
1565       }
1566
1567     return $this->cache[$internal_key][$uid];
1568     }
1569
1570    
0677ca 1571   function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
1cded8 1572     {
T 1573     static $sa_message_index = array();
1574     
4647e1 1575     // empty key -> empty array
T 1576     if (empty($key))
1577       return array();
1578     
1cded8 1579     if (!empty($sa_message_index[$key]) && !$force)
T 1580       return $sa_message_index[$key];
1581     
1582     $sa_message_index[$key] = array();
1583     $sql_result = $this->db->query(
1584       "SELECT idx, uid
1585        FROM ".get_table_name('messages')."
1586        WHERE  user_id=?
1587        AND    cache_key=?
0677ca 1588        ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
1cded8 1589       $_SESSION['user_id'],
T 1590       $key);
1591
1592     while ($sql_arr = $this->db->fetch_assoc($sql_result))
1593       $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
1594       
1595     return $sa_message_index[$key];
1596     }
1597
1598
1599   function add_message_cache($key, $index, $headers)
1600     {
4647e1 1601     if (!$key || !is_object($headers) || empty($headers->uid))
31b2ce 1602       return;
T 1603
1cded8 1604     $this->db->query(
T 1605       "INSERT INTO ".get_table_name('messages')."
0677ca 1606        (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
T 1607        VALUES (?, 0, ?, now(), ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
1cded8 1608       $_SESSION['user_id'],
T 1609       $key,
1610       $index,
1611       $headers->uid,
e93c08 1612       (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
T 1613       (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
1614       (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
1615       (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
31b2ce 1616       (int)$headers->size,
1cded8 1617       serialize($headers));
T 1618     }
1619     
1620     
1621   function remove_message_cache($key, $index)
1622     {
1623     $this->db->query(
1624       "DELETE FROM ".get_table_name('messages')."
1625        WHERE  user_id=?
1626        AND    cache_key=?
1627        AND    idx=?",
1628       $_SESSION['user_id'],
1629       $key,
1630       $index);
1631     }
1632
1633
1634   function clear_message_cache($key, $start_index=1)
1635     {
1636     $this->db->query(
1637       "DELETE FROM ".get_table_name('messages')."
1638        WHERE  user_id=?
1639        AND    cache_key=?
1640        AND    idx>=?",
1641       $_SESSION['user_id'],
1642       $key,
1643       $start_index);
1644     }
1645
1646
1647
1648
1649   /* --------------------------------
1650    *   encoding/decoding methods
4e17e6 1651    * --------------------------------*/
T 1652
1653   
1654   function decode_address_list($input, $max=NULL)
1655     {
1656     $a = $this->_parse_address_list($input);
1657     $out = array();
1658
1659     if (!is_array($a))
1660       return $out;
1661
1662     $c = count($a);
1663     $j = 0;
1664
1665     foreach ($a as $val)
1666       {
1667       $j++;
1668       $address = $val['address'];
1669       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
1670       $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
1671       
1672       $out[$j] = array('name' => $name,
1673                        'mailto' => $address,
1674                        'string' => $string);
1675               
1676       if ($max && $j==$max)
1677         break;
1678       }
1679     
1680     return $out;
1681     }
1682
1683
1cded8 1684   function decode_header($input, $remove_quotes=FALSE)
4e17e6 1685     {
31b2ce 1686     $str = $this->decode_mime_string((string)$input);
1cded8 1687     if ($str{0}=='"' && $remove_quotes)
T 1688       {
1689       $str = str_replace('"', '', $str);
1690       }
1691     
1692     return $str;
4b0f65 1693     }
T 1694     
1695     
1696   function decode_mime_string($input)
1697     {
4e17e6 1698     $out = '';
T 1699
1700     $pos = strpos($input, '=?');
1701     if ($pos !== false)
1702       {
1703       $out = substr($input, 0, $pos);
1704   
1705       $end_cs_pos = strpos($input, "?", $pos+2);
1706       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1707       $end_pos = strpos($input, "?=", $end_en_pos+1);
1708   
1709       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1710       $rest = substr($input, $end_pos+2);
1711
4b0f65 1712       $out .= rcube_imap::_decode_mime_string_part($encstr);
T 1713       $out .= rcube_imap::decode_mime_string($rest);
4e17e6 1714
T 1715       return $out;
1716       }
1717     else
1718       return $input;
1719     }
1720
1721
4b0f65 1722   function _decode_mime_string_part($str)
4e17e6 1723     {
T 1724     $a = explode('?', $str);
1725     $count = count($a);
1726
1727     // should be in format "charset?encoding?base64_string"
1728     if ($count >= 3)
1729       {
1730       for ($i=2; $i<$count; $i++)
1731         $rest.=$a[$i];
1732
1733       if (($a[1]=="B")||($a[1]=="b"))
1734         $rest = base64_decode($rest);
1735       else if (($a[1]=="Q")||($a[1]=="q"))
1736         {
1737         $rest = str_replace("_", " ", $rest);
1738         $rest = quoted_printable_decode($rest);
1739         }
1740
3f9edb 1741       return rcube_charset_convert($rest, $a[0]);
4e17e6 1742       }
T 1743     else
3f9edb 1744       return $str;    // we dont' know what to do with this  
4e17e6 1745     }
T 1746
1747
1748   function mime_decode($input, $encoding='7bit')
1749     {
1750     switch (strtolower($encoding))
1751       {
1752       case '7bit':
1753         return $input;
1754         break;
1755       
1756       case 'quoted-printable':
1757         return quoted_printable_decode($input);
1758         break;
1759       
1760       case 'base64':
1761         return base64_decode($input);
1762         break;
1763       
1764       default:
1765         return $input;
1766       }
1767     }
1768
1769
1770   function mime_encode($input, $encoding='7bit')
1771     {
1772     switch ($encoding)
1773       {
1774       case 'quoted-printable':
1775         return quoted_printable_encode($input);
1776         break;
1777
1778       case 'base64':
1779         return base64_encode($input);
1780         break;
1781
1782       default:
1783         return $input;
1784       }
1785     }
1786
1787
1788   // convert body chars according to the ctype_parameters
1789   function charset_decode($body, $ctype_param)
1790     {
a95e0e 1791     if (is_array($ctype_param) && !empty($ctype_param['charset']))
3f9edb 1792       return rcube_charset_convert($body, $ctype_param['charset']);
4e17e6 1793
T 1794     return $body;
1795     }
1796
1797
1cded8 1798
4e17e6 1799   /* --------------------------------
T 1800    *         private methods
1801    * --------------------------------*/
1802
1803
1804   function _mod_mailbox($mbox, $mode='in')
1805     {
f619de 1806     if ((!empty($this->root_ns) && $this->root_ns == $mbox) || ($mbox == 'INBOX' && $mode == 'in'))
7902df 1807       return $mbox;
T 1808
f619de 1809     if (!empty($this->root_dir) && $mode=='in') 
520c36 1810       $mbox = $this->root_dir.$this->delimiter.$mbox;
7902df 1811     else if (strlen($this->root_dir) && $mode=='out') 
4e17e6 1812       $mbox = substr($mbox, strlen($this->root_dir)+1);
T 1813
1814     return $mbox;
1815     }
1816
1817
1818   // sort mailboxes first by default folders and then in alphabethical order
1819   function _sort_mailbox_list($a_folders)
1820     {
1821     $a_out = $a_defaults = array();
1822
1823     // find default folders and skip folders starting with '.'
1824     foreach($a_folders as $i => $folder)
1825       {
1826       if ($folder{0}=='.')
1827           continue;
1828           
1829       if (($p = array_search(strtolower($folder), $this->default_folders))!==FALSE)
1830           $a_defaults[$p] = $folder;
1831       else
1832         $a_out[] = $folder;
1833       }
1834
1835     sort($a_out);
1836     ksort($a_defaults);
1837     
1838     return array_merge($a_defaults, $a_out);
1839     }
1840
e6f360 1841   function get_id($uid) 
T 1842     {
1843       return $this->_uid2id($uid);
1844     }
1845   
1846   function get_uid($id)
1847     {
1848       return $this->_id2uid($id);
1849     }
4e17e6 1850
T 1851   function _uid2id($uid, $mbox=NULL)
1852     {
1853     if (!$mbox)
1854       $mbox = $this->mailbox;
1855       
1856     if (!isset($this->uid_id_map[$mbox][$uid]))
1857       $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid);
1858
1859     return $this->uid_id_map[$mbox][$uid];
1860     }
1861
e6f360 1862   function _id2uid($id, $mbox=NULL)
T 1863     {
1864     if (!$mbox)
1865       $mbox = $this->mailbox;
1866       
1867     return iil_C_ID2UID($this->conn, $mbox, $id);
1868     }
1869
4e17e6 1870
1cded8 1871   // parse string or array of server capabilities and put them in internal array
T 1872   function _parse_capability($caps)
1873     {
1874     if (!is_array($caps))
1875       $cap_arr = explode(' ', $caps);
1876     else
1877       $cap_arr = $caps;
1878     
1879     foreach ($cap_arr as $cap)
1880       {
1881       if ($cap=='CAPABILITY')
1882         continue;
1883
1884       if (strpos($cap, '=')>0)
1885         {
1886         list($key, $value) = explode('=', $cap);
1887         if (!is_array($this->capabilities[$key]))
1888           $this->capabilities[$key] = array();
1889           
1890         $this->capabilities[$key][] = $value;
1891         }
1892       else
1893         $this->capabilities[$cap] = TRUE;
1894       }
1895     }
1896
1897
4e17e6 1898   // subscribe/unsubscribe a list of mailboxes and update local cache
T 1899   function _change_subscription($a_mboxes, $mode)
1900     {
1901     $updated = FALSE;
1902     
1903     if (is_array($a_mboxes))
1904       foreach ($a_mboxes as $i => $mbox)
1905         {
1906         $mailbox = $this->_mod_mailbox($mbox);
1907         $a_mboxes[$i] = $mailbox;
1908
1909         if ($mode=='subscribe')
1910           $result = iil_C_Subscribe($this->conn, $mailbox);
1911         else if ($mode=='unsubscribe')
1912           $result = iil_C_UnSubscribe($this->conn, $mailbox);
1913
1914         if ($result>=0)
1915           $updated = TRUE;
1916         }
1917         
1918     // get cached mailbox list    
1919     if ($updated)
1920       {
1921       $a_mailbox_cache = $this->get_cache('mailboxes');
1922       if (!is_array($a_mailbox_cache))
1923         return $updated;
1924
1925       // modify cached list
1926       if ($mode=='subscribe')
1927         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1928       else if ($mode=='unsubscribe')
1929         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1930         
1931       // write mailboxlist to cache
1932       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
1933       }
1934
1935     return $updated;
1936     }
1937
1938
1939   // increde/decrese messagecount for a specific mailbox
1940   function _set_messagecount($mbox, $mode, $increment)
1941     {
1942     $a_mailbox_cache = FALSE;
1943     $mailbox = $mbox ? $mbox : $this->mailbox;
1944     $mode = strtoupper($mode);
1945
1946     $a_mailbox_cache = $this->get_cache('messagecount');
1947     
1948     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
1949       return FALSE;
1950     
1951     // add incremental value to messagecount
1952     $a_mailbox_cache[$mailbox][$mode] += $increment;
31b2ce 1953     
T 1954     // there's something wrong, delete from cache
1955     if ($a_mailbox_cache[$mailbox][$mode] < 0)
1956       unset($a_mailbox_cache[$mailbox][$mode]);
4e17e6 1957
T 1958     // write back to cache
1959     $this->update_cache('messagecount', $a_mailbox_cache);
1960     
1961     return TRUE;
1962     }
1963
1964
1965   // remove messagecount of a specific mailbox from cache
1966   function _clear_messagecount($mbox='')
1967     {
1968     $a_mailbox_cache = FALSE;
1969     $mailbox = $mbox ? $mbox : $this->mailbox;
1970
1971     $a_mailbox_cache = $this->get_cache('messagecount');
1972
1973     if (is_array($a_mailbox_cache[$mailbox]))
1974       {
1975       unset($a_mailbox_cache[$mailbox]);
1976       $this->update_cache('messagecount', $a_mailbox_cache);
1977       }
1978     }
1979
1980
1981   function _parse_address_list($str)
1982     {
1983     $a = $this->_explode_quoted_string(',', $str);
1984     $result = array();
1985
1986     foreach ($a as $key => $val)
1987       {
1988       $val = str_replace("\"<", "\" <", $val);
1989       $sub_a = $this->_explode_quoted_string(' ', $val);
1990       
1991       foreach ($sub_a as $k => $v)
1992         {
1993         if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0)) 
1994           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
1995         else
1996           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
1997         }
1998         
1999       if (empty($result[$key]['name']))
2000         $result[$key]['name'] = $result[$key]['address'];
2001         
2002       $result[$key]['name'] = $this->decode_header($result[$key]['name']);
2003       }
2004     
2005     return $result;
2006     }
2007
2008
2009   function _explode_quoted_string($delimiter, $string)
2010     {
2011     $quotes = explode("\"", $string);
2012     foreach ($quotes as $key => $val)
2013       if (($key % 2) == 1)
2014         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
2015         
2016     $string = implode("\"", $quotes);
2017
2018     $result = explode($delimiter, $string);
2019     foreach ($result as $key => $val) 
2020       $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
2021     
2022     return $result;
2023     }
2024   }
2025
2026
2027
2028
2029
2030 function quoted_printable_encode($input="", $line_max=76, $space_conv=false)
2031   {
2032   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2033   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2034   $eol = "\r\n";
2035   $escape = "=";
2036   $output = "";
2037
2038   while( list(, $line) = each($lines))
2039     {
2040     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2041     $linlen = strlen($line);
2042     $newline = "";
2043     for($i = 0; $i < $linlen; $i++)
2044       {
2045       $c = substr( $line, $i, 1 );
2046       $dec = ord( $c );
2047       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2048         {
2049         $c = "=2E";
2050         }
2051       if ( $dec == 32 )
2052         {
2053         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
2054           {
2055           $c = "=20";
2056           }
2057         else if ( $space_conv )
2058           {
2059           $c = "=20";
2060           }
2061         }
2062       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
2063         {
2064         $h2 = floor($dec/16);
2065         $h1 = floor($dec%16);
2066         $c = $escape.$hex["$h2"].$hex["$h1"];
2067         }
2068          
2069       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
2070         {
2071         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
2072         $newline = "";
2073         // check if newline first character will be point or not
2074         if ( $dec == 46 )
2075           {
2076           $c = "=2E";
2077           }
2078         }
2079       $newline .= $c;
2080       } // end of for
2081     $output .= $newline.$eol;
2082     } // end of while
2083
2084   return trim($output);
2085   }
2086
f88d41 2087 ?>