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