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