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