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