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