alecpl
2008-08-29 53bd8fae60e1663447efba9f44fc6b79b3a5c1de
commit | author | age
4e17e6 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
47124c 5  | program/include/rcube_imap.php                                        |
4e17e6 6  |                                                                       |
T 7  | This file is part of the RoundCube Webmail client                     |
47124c 8  | Copyright (C) 2005-2008, 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
6d969b 24 /*
15a9d1 25  * Obtain classes from the Iloha IMAP library
T 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  *
6d969b 36  * @package    Mail
15a9d1 37  * @author     Thomas Bruederli <roundcube@gmail.com>
d5342a 38  * @version    1.40
15a9d1 39  * @link       http://ilohamail.org
T 40  */
4e17e6 41 class rcube_imap
6d969b 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';
9490b7 52   var $delimiter = NULL;
6dc026 53   var $caching_enabled = FALSE;
17b5fb 54   var $default_charset = 'ISO-8859-1';
fa4cd2 55   var $default_folders = array('INBOX');
T 56   var $default_folders_lc = array('inbox');
4e17e6 57   var $cache = array();
1cded8 58   var $cache_keys = array();  
326e87 59   var $cache_changes = array();
4e17e6 60   var $uid_id_map = array();
T 61   var $msg_headers = array();
1cded8 62   var $capabilities = array();
15a9d1 63   var $skip_deleted = FALSE;
04c618 64   var $search_set = NULL;
T 65   var $search_subject = '';
66   var $search_string = '';
67   var $search_charset = '';
15a9d1 68   var $debug_level = 1;
fc6725 69   var $error_code = 0;
4e17e6 70
T 71
15a9d1 72   /**
T 73    * Object constructor
74    *
6d969b 75    * @param object DB Database connection
15a9d1 76    */
1cded8 77   function __construct($db_conn)
4e17e6 78     {
3f9edb 79     $this->db = $db_conn;
4e17e6 80     }
T 81
15a9d1 82
T 83   /**
84    * Connect to an IMAP server
85    *
86    * @param  string   Host to connect
87    * @param  string   Username for IMAP account
88    * @param  string   Password for IMAP account
89    * @param  number   Port to connect to
5bc0ab 90    * @param  string   SSL schema (either ssl or tls) or null if plain connection
15a9d1 91    * @return boolean  TRUE on success, FALSE on failure
T 92    * @access public
93    */
1fb78c 94   function connect($host, $user, $pass, $port=143, $use_ssl=null, $auth_type=null)
4e17e6 95     {
4647e1 96     global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
42b113 97     
T 98     // check for Open-SSL support in PHP build
99     if ($use_ssl && in_array('openssl', get_loaded_extensions()))
5bc0ab 100       $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
520c36 101     else if ($use_ssl)
T 102       {
15a9d1 103       raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
520c36 104                         'message' => 'Open SSL not available;'), TRUE, FALSE);
T 105       $port = 143;
106       }
4e17e6 107
T 108     $ICL_PORT = $port;
4647e1 109     $IMAP_USE_INTERNAL_DATE = false;
b026c3 110
1fb78c 111     $this->conn = iil_Connect($host, $user, $pass, array('imap' => $auth_type ? $auth_type : 'check'));
4e17e6 112     $this->host = $host;
T 113     $this->user = $user;
114     $this->pass = $pass;
520c36 115     $this->port = $port;
T 116     $this->ssl = $use_ssl;
42b113 117     
520c36 118     // print trace mesages
15a9d1 119     if ($this->conn && ($this->debug_level & 8))
520c36 120       console($this->conn->message);
T 121     
122     // write error log
42b113 123     else if (!$this->conn && $GLOBALS['iil_error'])
T 124       {
fc6725 125       $this->error_code = $GLOBALS['iil_errornum'];
42b113 126       raise_error(array('code' => 403,
T 127                        'type' => 'imap',
128                        'message' => $GLOBALS['iil_error']), TRUE, FALSE);
520c36 129       }
T 130
f7bfec 131     // get server properties
520c36 132     if ($this->conn)
T 133       {
1cded8 134       $this->_parse_capability($this->conn->capability);
520c36 135       
T 136       if (!empty($this->conn->delimiter))
137         $this->delimiter = $this->conn->delimiter;
138       if (!empty($this->conn->rootdir))
7902df 139         {
T 140         $this->set_rootdir($this->conn->rootdir);
141         $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
142         }
42b113 143       }
4e17e6 144
T 145     return $this->conn ? TRUE : FALSE;
146     }
147
148
15a9d1 149   /**
T 150    * Close IMAP connection
151    * Usually done on script shutdown
152    *
153    * @access public
154    */
4e17e6 155   function close()
T 156     {    
157     if ($this->conn)
158       iil_Close($this->conn);
159     }
160
161
15a9d1 162   /**
T 163    * Close IMAP connection and re-connect
164    * This is used to avoid some strange socket errors when talking to Courier IMAP
165    *
166    * @access public
167    */
520c36 168   function reconnect()
T 169     {
170     $this->close();
171     $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
172     }
173
174
15a9d1 175   /**
T 176    * Set a root folder for the IMAP connection.
177    *
178    * Only folders within this root folder will be displayed
179    * and all folder paths will be translated using this folder name
180    *
181    * @param  string   Root folder
182    * @access public
183    */
4e17e6 184   function set_rootdir($root)
T 185     {
520c36 186     if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
4e17e6 187       $root = substr($root, 0, -1);
T 188
189     $this->root_dir = $root;
520c36 190     
T 191     if (empty($this->delimiter))
192       $this->get_hierarchy_delimiter();
17b5fb 193     }
T 194
195
196   /**
197    * Set default message charset
198    *
199    * This will be used for message decoding if a charset specification is not available
200    *
201    * @param  string   Charset string
202    * @access public
203    */
204   function set_charset($cs)
205     {
f94a80 206     $this->default_charset = $cs;
4e17e6 207     }
T 208
209
15a9d1 210   /**
T 211    * This list of folders will be listed above all other folders
212    *
213    * @param  array  Indexed list of folder names
214    * @access public
215    */
4e17e6 216   function set_default_mailboxes($arr)
T 217     {
218     if (is_array($arr))
219       {
fa4cd2 220       $this->default_folders = $arr;
T 221       $this->default_folders_lc = array();
222
4e17e6 223       // add inbox if not included
fa4cd2 224       if (!in_array_nocase('INBOX', $this->default_folders))
T 225         array_unshift($this->default_folders, 'INBOX');
226
227       // create a second list with lower cased names
228       foreach ($this->default_folders as $mbox)
229         $this->default_folders_lc[] = strtolower($mbox);
4e17e6 230       }
T 231     }
232
233
15a9d1 234   /**
T 235    * Set internal mailbox reference.
236    *
237    * All operations will be perfomed on this mailbox/folder
238    *
239    * @param  string  Mailbox/Folder name
240    * @access public
241    */
aadfa1 242   function set_mailbox($new_mbox)
4e17e6 243     {
aadfa1 244     $mailbox = $this->_mod_mailbox($new_mbox);
4e17e6 245
T 246     if ($this->mailbox == $mailbox)
247       return;
248
249     $this->mailbox = $mailbox;
250
251     // clear messagecount cache for this mailbox
252     $this->_clear_messagecount($mailbox);
253     }
254
255
15a9d1 256   /**
T 257    * Set internal list page
258    *
259    * @param  number  Page number to list
260    * @access public
261    */
4e17e6 262   function set_page($page)
T 263     {
264     $this->list_page = (int)$page;
265     }
266
267
15a9d1 268   /**
T 269    * Set internal page size
270    *
271    * @param  number  Number of messages to display on one page
272    * @access public
273    */
4e17e6 274   function set_pagesize($size)
T 275     {
276     $this->page_size = (int)$size;
277     }
04c618 278     
T 279
280   /**
281    * Save a set of message ids for future message listing methods
282    *
283    * @param  array  List of IMAP fields to search in
284    * @param  string Search string
285    * @param  array  List of message ids or NULL if empty
286    */
287   function set_search_set($subject, $str=null, $msgs=null, $charset=null)
288     {
289     if (is_array($subject) && $str == null && $msgs == null)
290       list($subject, $str, $msgs, $charset) = $subject;
291     if ($msgs != null && !is_array($msgs))
292       $msgs = split(',', $msgs);
293       
294     $this->search_subject = $subject;
295     $this->search_string = $str;
4845a1 296     $this->search_set = (array)$msgs;
04c618 297     $this->search_charset = $charset;
T 298     }
299
300
301   /**
302    * Return the saved search set as hash array
6d969b 303    * @return array Search set
04c618 304    */
T 305   function get_search_set()
306     {
307     return array($this->search_subject, $this->search_string, $this->search_set, $this->search_charset);
308     }
4e17e6 309
T 310
15a9d1 311   /**
T 312    * Returns the currently used mailbox name
313    *
314    * @return  string Name of the mailbox/folder
315    * @access  public
316    */
4e17e6 317   function get_mailbox_name()
T 318     {
319     return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
1cded8 320     }
T 321
322
15a9d1 323   /**
T 324    * Returns the IMAP server's capability
325    *
326    * @param   string  Capability name
327    * @return  mixed   Capability value or TRUE if supported, FALSE if not
328    * @access  public
329    */
1cded8 330   function get_capability($cap)
T 331     {
332     $cap = strtoupper($cap);
333     return $this->capabilities[$cap];
4e17e6 334     }
T 335
336
15a9d1 337   /**
5b3dd4 338    * Checks the PERMANENTFLAGS capability of the current mailbox
T 339    * and returns true if the given flag is supported by the IMAP server
340    *
341    * @param   string  Permanentflag name
342    * @return  mixed   True if this flag is supported
343    * @access  public
344    */
345   function check_permflag($flag)
346     {
347     $flagsmap = $GLOBALS['IMAP_FLAGS'];
348     return (($imap_flag = $flagsmap[strtoupper($flag)]) && in_array_nocase($imap_flag, $this->conn->permanentflags));
349     }
350
351
352   /**
15a9d1 353    * Returns the delimiter that is used by the IMAP server for folder separation
T 354    *
355    * @return  string  Delimiter string
356    * @access  public
357    */
597170 358   function get_hierarchy_delimiter()
T 359     {
360     if ($this->conn && empty($this->delimiter))
361       $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
362
7902df 363     if (empty($this->delimiter))
T 364       $this->delimiter = '/';
365
597170 366     return $this->delimiter;
T 367     }
368
15a9d1 369
T 370   /**
371    * Public method for mailbox listing.
372    *
373    * Converts mailbox name with root dir first
374    *
375    * @param   string  Optional root folder
376    * @param   string  Optional filter for mailbox listing
377    * @return  array   List of mailboxes/folders
378    * @access  public
379    */
4e17e6 380   function list_mailboxes($root='', $filter='*')
T 381     {
382     $a_out = array();
383     $a_mboxes = $this->_list_mailboxes($root, $filter);
384
aadfa1 385     foreach ($a_mboxes as $mbox_row)
4e17e6 386       {
aadfa1 387       $name = $this->_mod_mailbox($mbox_row, 'out');
4e17e6 388       if (strlen($name))
T 389         $a_out[] = $name;
390       }
391
fa4cd2 392     // INBOX should always be available
T 393     if (!in_array_nocase('INBOX', $a_out))
394       array_unshift($a_out, 'INBOX');
395
4e17e6 396     // sort mailboxes
T 397     $a_out = $this->_sort_mailbox_list($a_out);
398
399     return $a_out;
400     }
401
15a9d1 402
T 403   /**
404    * Private method for mailbox listing
405    *
406    * @return  array   List of mailboxes/folders
6d969b 407    * @see     rcube_imap::list_mailboxes()
15a9d1 408    * @access  private
T 409    */
4e17e6 410   function _list_mailboxes($root='', $filter='*')
T 411     {
412     $a_defaults = $a_out = array();
413     
414     // get cached folder list    
415     $a_mboxes = $this->get_cache('mailboxes');
416     if (is_array($a_mboxes))
417       return $a_mboxes;
418
419     // retrieve list of folders from IMAP server
420     $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
421     
422     if (!is_array($a_folders) || !sizeof($a_folders))
423       $a_folders = array();
424
425     // write mailboxlist to cache
426     $this->update_cache('mailboxes', $a_folders);
427     
428     return $a_folders;
429     }
430
431
3f9edb 432   /**
T 433    * Get message count for a specific mailbox
434    *
435    * @param   string   Mailbox/folder name
436    * @param   string   Mode for count [ALL|UNSEEN|RECENT]
437    * @param   boolean  Force reading from server and update cache
6d969b 438    * @return  int      Number of messages
T 439    * @access  public
3f9edb 440    */
aadfa1 441   function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
4e17e6 442     {
aadfa1 443     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4e17e6 444     return $this->_messagecount($mailbox, $mode, $force);
T 445     }
446
3f9edb 447
T 448   /**
449    * Private method for getting nr of messages
450    *
451    * @access  private
6d969b 452    * @see     rcube_imap::messagecount()
3f9edb 453    */
4e17e6 454   function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
T 455     {
456     $a_mailbox_cache = FALSE;
457     $mode = strtoupper($mode);
458
15a9d1 459     if (empty($mailbox))
4e17e6 460       $mailbox = $this->mailbox;
04c618 461       
T 462     // count search set
110748 463     if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force)
4845a1 464       return count((array)$this->search_set);
4e17e6 465
T 466     $a_mailbox_cache = $this->get_cache('messagecount');
467     
468     // return cached value
469     if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
31b2ce 470       return $a_mailbox_cache[$mailbox][$mode];
4e17e6 471
197601 472     // RECENT count is fetched a bit different
15a9d1 473     if ($mode == 'RECENT')
T 474        $count = iil_C_CheckForRecent($this->conn, $mailbox);
31b2ce 475
15a9d1 476     // use SEARCH for message counting
T 477     else if ($this->skip_deleted)
31b2ce 478       {
15a9d1 479       $search_str = "ALL UNDELETED";
T 480
481       // get message count and store in cache
482       if ($mode == 'UNSEEN')
483         $search_str .= " UNSEEN";
484
485       // get message count using SEARCH
486       // not very performant but more precise (using UNDELETED)
487       $count = 0;
488       $index = $this->_search_index($mailbox, $search_str);
489       if (is_array($index))
490         {
491         $str = implode(",", $index);
492         if (!empty($str))
493           $count = count($index);
494         }
495       }
496     else
497       {
498       if ($mode == 'UNSEEN')
499         $count = iil_C_CountUnseen($this->conn, $mailbox);
500       else
501         $count = iil_C_CountMessages($this->conn, $mailbox);
31b2ce 502       }
4e17e6 503
13c1af 504     if (!is_array($a_mailbox_cache[$mailbox]))
4e17e6 505       $a_mailbox_cache[$mailbox] = array();
T 506       
507     $a_mailbox_cache[$mailbox][$mode] = (int)$count;
31b2ce 508
4e17e6 509     // write back to cache
T 510     $this->update_cache('messagecount', $a_mailbox_cache);
511
512     return (int)$count;
513     }
514
515
3f9edb 516   /**
T 517    * Public method for listing headers
518    * convert mailbox name with root dir first
519    *
520    * @param   string   Mailbox/folder name
6d969b 521    * @param   int      Current page to list
3f9edb 522    * @param   string   Header field to sort by
T 523    * @param   string   Sort order [ASC|DESC]
524    * @return  array    Indexed array with message header objects
525    * @access  public   
526    */
aadfa1 527   function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
4e17e6 528     {
aadfa1 529     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4e17e6 530     return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
T 531     }
532
533
3f9edb 534   /**
4647e1 535    * Private method for listing message headers
3f9edb 536    *
T 537    * @access  private
538    * @see     rcube_imap::list_headers
539    */
31b2ce 540   function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
4e17e6 541     {
T 542     if (!strlen($mailbox))
17fc71 543       return array();
04c618 544
T 545     // use saved message set
4845a1 546     if ($this->search_string && $mailbox == $this->mailbox)
04c618 547       return $this->_list_header_set($mailbox, $this->search_set, $page, $sort_field, $sort_order);
T 548
d5342a 549     $this->_set_sort_order($sort_field, $sort_order);
4e17e6 550
1cded8 551     $max = $this->_messagecount($mailbox);
T 552     $start_msg = ($this->list_page-1) * $this->page_size;
06ec1f 553
4647e1 554     list($begin, $end) = $this->_get_message_range($max, $page);
06ec1f 555
04c618 556     // mailbox is empty
078adf 557     if ($begin >= $end)
T 558       return array();
04c618 559       
1cded8 560     $headers_sorted = FALSE;
T 561     $cache_key = $mailbox.'.msg';
562     $cache_status = $this->check_cache_status($mailbox, $cache_key);
563
564     // cache is OK, we can get all messages from local cache
565     if ($cache_status>0)
566       {
31b2ce 567       $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
1cded8 568       $headers_sorted = TRUE;
T 569       }
c4e7e4 570     // cache is dirty, sync it
T 571     else if ($this->caching_enabled && $cache_status==-1 && !$recursive)
572       {
573       $this->sync_header_index($mailbox);
574       return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
575       }
1cded8 576     else
T 577       {
578       // retrieve headers from IMAP
15a9d1 579       if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
05d180 580         {
T 581         $mymsgidx = array_slice ($msg_index, $begin, $end-$begin);
257782 582         $msgs = join(",", $mymsgidx);
1cded8 583         }
T 584       else
585         {
c4e7e4 586         $msgs = sprintf("%d:%d", $begin+1, $end);
257782 587         $msg_index = range($begin, $end);
1cded8 588         }
T 589
06ec1f 590
1cded8 591       // fetch reuested headers from server
T 592       $a_msg_headers = array();
15a9d1 593       $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
257782 594       if ($this->sort_order == 'DESC' && $headers_sorted) {  
A 595         //since the sort order is not used in the iil_c_sort function we have to do it here
596         $a_msg_headers = array_reverse($a_msg_headers);
597       }
f388a8 598       // delete cached messages with a higher index than $max+1
S 599       // Changed $max to $max+1 to fix this bug : #1484295
600       $this->clear_message_cache($cache_key, $max + 1);
1cded8 601
06ec1f 602
1cded8 603       // kick child process to sync cache
31b2ce 604       // ...
06ec1f 605
1cded8 606       }
T 607
608     // return empty array if no messages found
257782 609     if (!is_array($a_msg_headers) || empty($a_msg_headers)) {
A 610       return array();
611     }
1cded8 612
T 613     // if not already sorted
06ec1f 614     if (!$headers_sorted)
7e93ff 615       {
257782 616       // use this class for message sorting
A 617       $sorter = new rcube_header_sorter();
618       $sorter->set_sequence_numbers($msg_index);
7e93ff 619       $sorter->sort_headers($a_msg_headers);
e6f360 620
7e93ff 621       if ($this->sort_order == 'DESC')
T 622         $a_msg_headers = array_reverse($a_msg_headers);
623       }
1cded8 624
T 625     return array_values($a_msg_headers);
4e17e6 626     }
7e93ff 627
T 628
15a9d1 629
4647e1 630   /**
T 631    * Public method for listing a specific set of headers
632    * convert mailbox name with root dir first
633    *
634    * @param   string   Mailbox/folder name
635    * @param   array    List of message ids to list
6d969b 636    * @param   int      Current page to list
4647e1 637    * @param   string   Header field to sort by
T 638    * @param   string   Sort order [ASC|DESC]
639    * @return  array    Indexed array with message header objects
640    * @access  public   
641    */
aadfa1 642   function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
4647e1 643     {
aadfa1 644     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4647e1 645     return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);    
T 646     }
647     
648
649   /**
650    * Private method for listing a set of message headers
651    *
652    * @access  private
6d969b 653    * @see     rcube_imap::list_header_set()
4647e1 654    */
T 655   function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
656     {
657     if (!strlen($mailbox) || empty($msgs))
658       return array();
659
257782 660     // also accept a comma-separated list of message ids
A 661     if (is_array ($msgs)) {
662       $max = count ($msgs);
663       $msgs = join (',', $msgs);
664     } else {
665       $max = count(split(',', $msgs));
666     } 
667
d5342a 668     $this->_set_sort_order($sort_field, $sort_order);
4647e1 669
T 670     $start_msg = ($this->list_page-1) * $this->page_size;
671
672     // fetch reuested headers from server
673     $a_msg_headers = array();
c5cc38 674     $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, NULL);
4647e1 675
T 676     // return empty array if no messages found
04c618 677     if (!is_array($a_msg_headers) || empty($a_msg_headers))
T 678       return array();
4647e1 679
T 680     // if not already sorted
681     $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
682
04c618 683     // only return the requested part of the set
T 684     return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
4647e1 685     }
T 686
687
688   /**
689    * Helper function to get first and last index of the requested set
690    *
6d969b 691    * @param  int     message count
4647e1 692    * @param  mixed   page number to show, or string 'all'
T 693    * @return array   array with two values: first index, last index
694    * @access private
695    */
696   function _get_message_range($max, $page)
697     {
698     $start_msg = ($this->list_page-1) * $this->page_size;
699     
700     if ($page=='all')
701       {
702       $begin = 0;
703       $end = $max;
704       }
705     else if ($this->sort_order=='DESC')
706       {
707       $begin = $max - $this->page_size - $start_msg;
708       $end =   $max - $start_msg;
709       }
710     else
711       {
712       $begin = $start_msg;
713       $end   = $start_msg + $this->page_size;
714       }
715
716     if ($begin < 0) $begin = 0;
717     if ($end < 0) $end = $max;
718     if ($end > $max) $end = $max;
719     
720     return array($begin, $end);
721     }
722     
723     
15a9d1 724
T 725   /**
726    * Fetches message headers
727    * Used for loop
728    *
729    * @param  string  Mailbox name
4647e1 730    * @param  string  Message index to fetch
15a9d1 731    * @param  array   Reference to message headers array
T 732    * @param  array   Array with cache index
6d969b 733    * @return int     Number of deleted messages
15a9d1 734    * @access private
T 735    */
736   function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
737     {
738     // cache is incomplete
739     $cache_index = $this->get_message_cache_index($cache_key);
4647e1 740     
15a9d1 741     // fetch reuested headers from server
T 742     $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
743     $deleted_count = 0;
744     
745     if (!empty($a_header_index))
746       {
747       foreach ($a_header_index as $i => $headers)
748         {
749         if ($headers->deleted && $this->skip_deleted)
750           {
751           // delete from cache
752           if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
753             $this->remove_message_cache($cache_key, $headers->id);
754
755           $deleted_count++;
756           continue;
757           }
758
759         // add message to cache
760         if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
761           $this->add_message_cache($cache_key, $headers->id, $headers);
762
763         $a_msg_headers[$headers->uid] = $headers;
764         }
765       }
766         
767     return $deleted_count;
768     }
769     
e6f360 770   
8d4bcd 771   /**
T 772    * Return sorted array of message UIDs
773    *
774    * @param string Mailbox to get index from
775    * @param string Sort column
776    * @param string Sort order [ASC, DESC]
777    * @return array Indexed array with message ids
778    */
aadfa1 779   function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
4e17e6 780     {
d5342a 781     $this->_set_sort_order($sort_field, $sort_order);
31b2ce 782
aadfa1 783     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
df0da2 784     $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi";
T 785
786     // we have a saved search result. get index from there
787     if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox)
788     {
789       $this->cache[$key] = $a_msg_headers = array();
790       $this->_fetch_headers($mailbox, join(',', $this->search_set), $a_msg_headers, NULL);
791
792       foreach (iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order) as $i => $msg)
793         $this->cache[$key][] = $msg->uid;
794     }
4e17e6 795
31b2ce 796     // have stored it in RAM
T 797     if (isset($this->cache[$key]))
798       return $this->cache[$key];
4e17e6 799
31b2ce 800     // check local cache
T 801     $cache_key = $mailbox.'.msg';
802     $cache_status = $this->check_cache_status($mailbox, $cache_key);
4e17e6 803
31b2ce 804     // cache is OK
T 805     if ($cache_status>0)
806       {
0677ca 807       $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
31b2ce 808       return array_values($a_index);
T 809       }
810
811
812     // fetch complete message index
813     $msg_count = $this->_messagecount($mailbox);
e6f360 814     if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
31b2ce 815       {
T 816       if ($this->sort_order == 'DESC')
817         $a_index = array_reverse($a_index);
818
e6f360 819       $this->cache[$key] = $a_index;
T 820
31b2ce 821       }
T 822     else
823       {
824       $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
825       $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
826     
827       if ($this->sort_order=="ASC")
828         asort($a_index);
829       else if ($this->sort_order=="DESC")
830         arsort($a_index);
831         
832       $i = 0;
833       $this->cache[$key] = array();
834       foreach ($a_index as $index => $value)
835         $this->cache[$key][$i++] = $a_uids[$index];
836       }
837
838     return $this->cache[$key];
4e17e6 839     }
T 840
841
6d969b 842   /**
T 843    * @access private
844    */
1cded8 845   function sync_header_index($mailbox)
4e17e6 846     {
1cded8 847     $cache_key = $mailbox.'.msg';
T 848     $cache_index = $this->get_message_cache_index($cache_key);
849     $msg_count = $this->_messagecount($mailbox);
850
851     // fetch complete message index
852     $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
853         
854     foreach ($a_message_index as $id => $uid)
855       {
856       // message in cache at correct position
857       if ($cache_index[$id] == $uid)
858         {
859         unset($cache_index[$id]);
860         continue;
861         }
862         
863       // message in cache but in wrong position
864       if (in_array((string)$uid, $cache_index, TRUE))
865         {
866         unset($cache_index[$id]);        
867         }
868       
869       // other message at this position
870       if (isset($cache_index[$id]))
871         {
872         $this->remove_message_cache($cache_key, $id);
873         unset($cache_index[$id]);
874         }
875         
876
877       // fetch complete headers and add to cache
878       $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
879       $this->add_message_cache($cache_key, $headers->id, $headers);
880       }
881
882     // those ids that are still in cache_index have been deleted      
883     if (!empty($cache_index))
884       {
885       foreach ($cache_index as $id => $uid)
886         $this->remove_message_cache($cache_key, $id);
887       }
4e17e6 888     }
T 889
890
4647e1 891   /**
T 892    * Invoke search request to IMAP server
893    *
894    * @param  string  mailbox name to search in
895    * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
896    * @param  string  search string
897    * @return array   search results as list of message ids
898    * @access public
899    */
42000a 900   function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
4e17e6 901     {
aadfa1 902     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
04c618 903
T 904     // have an array of criterias => execute multiple searches
905     if (is_array($criteria) && $str)
906       {
907       $results = array();
908       foreach ($criteria as $crit)
e6c7c3 909         if ($search_result = $this->search($mbox_name, $crit, $str, $charset))
T 910           $results = array_merge($results, $search_result);
04c618 911       
T 912       $results = array_unique($results);
913       $this->set_search_set($criteria, $str, $results, $charset);
914       return $results;
915       }
916     else if ($str && $criteria)
4647e1 917       {
42000a 918       $search = (!empty($charset) ? "CHARSET $charset " : '') . sprintf("%s {%d}\r\n%s", $criteria, strlen($str), $str);
T 919       $results = $this->_search_index($mailbox, $search);
920
4d4264 921       // try search with ISO charset (should be supported by server)
T 922       if (empty($results) && !empty($charset) && $charset!='ISO-8859-1')
923         $results = $this->search($mbox_name, $criteria, rcube_charset_convert($str, $charset, 'ISO-8859-1'), 'ISO-8859-1');
42000a 924       
04c618 925       $this->set_search_set($criteria, $str, $results, $charset);
42000a 926       return $results;
4647e1 927       }
T 928     else
929       return $this->_search_index($mailbox, $criteria);
930     }    
931
932
933   /**
934    * Private search method
935    *
936    * @return array   search results as list of message ids
937    * @access private
938    * @see rcube_imap::search()
939    */
31b2ce 940   function _search_index($mailbox, $criteria='ALL')
T 941     {
4e17e6 942     $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
4647e1 943     // clean message list (there might be some empty entries)
4f2d81 944     if (is_array($a_messages))
T 945       {
946       foreach ($a_messages as $i => $val)
947         if (empty($val))
948           unset($a_messages[$i]);
949       }
4647e1 950         
4e17e6 951     return $a_messages;
04c618 952     }
T 953     
954   
955   /**
956    * Refresh saved search set
6d969b 957    *
T 958    * @return array Current search set
04c618 959    */
T 960   function refresh_search()
961     {
962     if (!empty($this->search_subject) && !empty($this->search_string))
963       $this->search_set = $this->search('', $this->search_subject, $this->search_string, $this->search_charset);
964       
965     return $this->get_search_set();
4e17e6 966     }
110748 967   
T 968   
969   /**
970    * Check if the given message ID is part of the current search set
971    *
ed5407 972    * @return boolean True on match or if no search request is stored
110748 973    */
T 974   function in_searchset($msgid)
975   {
976     if (!empty($this->search_string))
977       return in_array("$msgid", (array)$this->search_set, true);
978     else
979       return true;
980   }
4e17e6 981
T 982
8d4bcd 983   /**
T 984    * Return message headers object of a specific message
985    *
986    * @param int     Message ID
987    * @param string  Mailbox to read from 
988    * @param boolean True if $id is the message UID
989    * @return object Message headers representation
990    */
aadfa1 991   function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
4e17e6 992     {
aadfa1 993     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
8d4bcd 994     $uid = $is_uid ? $id : $this->_id2uid($id);
1cded8 995
4e17e6 996     // get cached headers
f7bfec 997     if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
1cded8 998       return $headers;
520c36 999
f7bfec 1000     $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid);
520c36 1001
4e17e6 1002     // write headers cache
1cded8 1003     if ($headers)
f7bfec 1004       {
017def 1005       if ($headers->uid && $headers->id)
T 1006         $this->uid_id_map[$mailbox][$headers->uid] = $headers->id;
f7bfec 1007
T 1008       $this->add_message_cache($mailbox.'.msg', $headers->id, $headers);
1009       }
4e17e6 1010
1cded8 1011     return $headers;
4e17e6 1012     }
T 1013
1014
8d4bcd 1015   /**
T 1016    * Fetch body structure from the IMAP server and build
1017    * an object structure similar to the one generated by PEAR::Mail_mimeDecode
1018    *
6d969b 1019    * @param int Message UID to fetch
T 1020    * @return object stdClass Message part tree or False on failure
8d4bcd 1021    */
T 1022   function &get_structure($uid)
4e17e6 1023     {
f7bfec 1024     $cache_key = $this->mailbox.'.msg';
T 1025     $headers = &$this->get_cached_message($cache_key, $uid, true);
1026
1027     // return cached message structure
1028     if (is_object($headers) && is_object($headers->structure))
1029       return $headers->structure;
1030     
1031     // resolve message sequence number
4e17e6 1032     if (!($msg_id = $this->_uid2id($uid)))
T 1033       return FALSE;
1034
5cc4b1 1035     $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
T 1036     $structure = iml_GetRawStructureArray($structure_str);
1037     $struct = false;
1038
8d4bcd 1039     // parse structure and add headers
T 1040     if (!empty($structure))
1041       {
1042       $this->_msg_id = $msg_id;
017def 1043       $headers = $this->get_headers($uid);
8d4bcd 1044       
T 1045       $struct = &$this->_structure_part($structure);
1046       $struct->headers = get_object_vars($headers);
58afbe 1047
8d4bcd 1048       // don't trust given content-type
58afbe 1049       if (empty($struct->parts) && !empty($struct->headers['ctype']))
8d4bcd 1050         {
T 1051         $struct->mime_id = '1';
1052         $struct->mimetype = strtolower($struct->headers['ctype']);
1053         list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
1054         }
f7bfec 1055
T 1056       // write structure to cache
1057       if ($this->caching_enabled)
1058         $this->add_message_cache($cache_key, $msg_id, $headers, $struct);
8d4bcd 1059       }
5cc4b1 1060       
T 1061     return $struct;
1062     }
4e17e6 1063
8d4bcd 1064   
T 1065   /**
1066    * Build message part object
1067    *
1068    * @access private
1069    */
1070   function &_structure_part($part, $count=0, $parent='')
1071     {
1072     $struct = new rcube_message_part;
1073     $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
4e17e6 1074     
8d4bcd 1075     // multipart
T 1076     if (is_array($part[0]))
1077       {
1078       $struct->ctype_primary = 'multipart';
1079       
1080       // find first non-array entry
cfe4a6 1081       for ($i=1; $i<count($part); $i++)
8d4bcd 1082         if (!is_array($part[$i]))
T 1083           {
1084           $struct->ctype_secondary = strtolower($part[$i]);
1085           break;
1086           }
1087           
1088       $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
1089
1090       $struct->parts = array();
1091       for ($i=0, $count=0; $i<count($part); $i++)
cfe4a6 1092         if (is_array($part[$i]) && count($part[$i]) > 3)
8d4bcd 1093           $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id);
5cc4b1 1094           
T 1095       return $struct;
8d4bcd 1096       }
T 1097     
1098     
1099     // regular part
1100     $struct->ctype_primary = strtolower($part[0]);
1101     $struct->ctype_secondary = strtolower($part[1]);
1102     $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
5cc4b1 1103
8d4bcd 1104     // read content type parameters
5cc4b1 1105     if (is_array($part[2]))
T 1106       {
1107       $struct->ctype_parameters = array();
8d4bcd 1108       for ($i=0; $i<count($part[2]); $i+=2)
T 1109         $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
1110         
1111       if (isset($struct->ctype_parameters['charset']))
1112         $struct->charset = $struct->ctype_parameters['charset'];
5cc4b1 1113       }
T 1114     
1115     // read content encoding
1116     if (!empty($part[5]) && $part[5]!='NIL')
1117       {
1118       $struct->encoding = strtolower($part[5]);
1119       $struct->headers['content-transfer-encoding'] = $struct->encoding;
1120       }
1121     
1122     // get part size
1123     if (!empty($part[6]) && $part[6]!='NIL')
1124       $struct->size = intval($part[6]);
2f2f15 1125
5cc4b1 1126     // read part disposition
ea206d 1127     $di = count($part) - 2;
2f2f15 1128     if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
T 1129         (is_array($part[--$di]) && count($part[$di]) == 2))
8d4bcd 1130       {
T 1131       $struct->disposition = strtolower($part[$di][0]);
1132
1133       if (is_array($part[$di][1]))
1134         for ($n=0; $n<count($part[$di][1]); $n+=2)
1135           $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
1136       }
1137       
1138     // get child parts
1139     if (is_array($part[8]) && $di != 8)
1140       {
1141       $struct->parts = array();
1142       for ($i=0, $count=0; $i<count($part[8]); $i++)
1143         if (is_array($part[8][$i]) && count($part[8][$i]) > 5)
1144           $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
1145       }
5cc4b1 1146
T 1147     // get part ID
1148     if (!empty($part[3]) && $part[3]!='NIL')
1149       {
1150       $struct->content_id = $part[3];
1151       $struct->headers['content-id'] = $part[3];
1152     
1153       if (empty($struct->disposition))
1154         $struct->disposition = 'inline';
1155       }
8d4bcd 1156
T 1157     // fetch message headers if message/rfc822
1158     if ($struct->ctype_primary=='message')
1159       {
1160       $headers = iil_C_FetchPartBody($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id.'.HEADER');
1161       $struct->headers = $this->_parse_headers($headers);
5cc4b1 1162       
T 1163       if (is_array($part[8]) && empty($struct->parts))
1164         $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
8d4bcd 1165       }
b54121 1166
5cc4b1 1167     // normalize filename property
ddc34f 1168     if ($filename_mime = $struct->d_parameters['filename'] ? $struct->d_parameters['filename'] : $struct->ctype_parameters['name'])
b54121 1169     {
A 1170       $struct->filename = rcube_imap::decode_mime_string($filename_mime, 
1171             $struct->charset ? $struct->charset : rc_detect_encoding($filename_mime, $this->default_charset));
1172     }
ddc34f 1173     else if ($filename_encoded = $struct->d_parameters['filename*'] ? $struct->d_parameters['filename*'] : $struct->ctype_parameters['name*'])
T 1174     {
1175       // decode filename according to RFC 2231, Section 4
1176       list($filename_charset,, $filename_urlencoded) = split('\'', $filename_encoded);
1177       $struct->filename = rcube_charset_convert(urldecode($filename_urlencoded), $filename_charset);
1178     }
5cc4b1 1179     else if (!empty($struct->headers['content-description']))
b54121 1180       $struct->filename = rcube_imap::decode_mime_string($struct->headers['content-description'],
A 1181             $struct->charset ? $struct->charset : rc_detect_encoding($struct->headers['content-description'],$this->default_charset));
5cc4b1 1182       
T 1183     return $struct;
8d4bcd 1184     }
T 1185     
1186   
1187   /**
1188    * Fetch message body of a specific message from the server
1189    *
1190    * @param  int    Message UID
1191    * @param  string Part number
6d969b 1192    * @param  object rcube_message_part Part object created by get_structure()
8d4bcd 1193    * @param  mixed  True to print part, ressource to write part contents in
6d969b 1194    * @return string Message/part body if not printed
8d4bcd 1195    */
T 1196   function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL)
1197     {
1198     if (!($msg_id = $this->_uid2id($uid)))
1199       return FALSE;
1200     
1201     // get part encoding if not provided
1202     if (!is_object($o_part))
1203       {
1204       $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id); 
1205       $structure = iml_GetRawStructureArray($structure_str);
1206       $part_type = iml_GetPartTypeCode($structure, $part);
1207       $o_part = new rcube_message_part;
1208       $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other');
1209       $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part));
1210       $o_part->charset = iml_GetPartCharset($structure, $part);
1211       }
1212       
1213     // TODO: Add caching for message parts
1214
1215     if ($print)
1216       {
a9cc52 1217       $mode = $o_part->encoding == 'base64' ? 3 : ($o_part->encoding == 'quoted-printable' ? 1 : 2);
T 1218       $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, $mode);
1219       
1220       // we have to decode the part manually before printing
1221       if ($mode == 1)
1222         {
1223         echo $this->mime_decode($body, $o_part->encoding);
1224         $body = true;
1225         }
8d4bcd 1226       }
T 1227     else
1228       {
1229       $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1);
1230
1231       // decode part body
a9cc52 1232       if ($o_part->encoding)
8d4bcd 1233         $body = $this->mime_decode($body, $o_part->encoding);
T 1234
1235       // convert charset (if text or message part)
f7bfec 1236       if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message')
T 1237         {
17b5fb 1238         // assume default if no charset specified
f7bfec 1239         if (empty($o_part->charset))
17b5fb 1240           $o_part->charset = $this->default_charset;
f7bfec 1241
8d4bcd 1242         $body = rcube_charset_convert($body, $o_part->charset);
f7bfec 1243         }
8d4bcd 1244       }
4e17e6 1245
T 1246     return $body;
1247     }
1248
1249
8d4bcd 1250   /**
T 1251    * Fetch message body of a specific message from the server
1252    *
1253    * @param  int    Message UID
6d969b 1254    * @return string Message/part body
T 1255    * @see    rcube_imap::get_message_part()
8d4bcd 1256    */
T 1257   function &get_body($uid, $part=1)
1258     {
0a9989 1259     $headers = $this->get_headers($uid);
T 1260     return rcube_charset_convert(
1261       $this->mime_decode($this->get_message_part($uid, $part), 'quoted-printable'),
1262       $headers->charset ? $headers->charset : $this->default_charset);
8d4bcd 1263     }
T 1264
1265
1266   /**
1267    * Returns the whole message source as string
1268    *
1269    * @param int  Message UID
6d969b 1270    * @return string Message source string
8d4bcd 1271    */
T 1272   function &get_raw_body($uid)
4e17e6 1273     {
T 1274     if (!($msg_id = $this->_uid2id($uid)))
1275       return FALSE;
1276
6d969b 1277     $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
T 1278     $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
4e17e6 1279
T 1280     return $body;    
1281     }
8d4bcd 1282     
T 1283
1284   /**
1285    * Sends the whole message source to stdout
1286    *
1287    * @param int  Message UID
1288    */ 
1289   function print_raw_body($uid)
1290     {
1291     if (!($msg_id = $this->_uid2id($uid)))
1292       return FALSE;
1293
6d969b 1294     print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
T 1295     flush();
1296     iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
8d4bcd 1297     }
4e17e6 1298
T 1299
8d4bcd 1300   /**
T 1301    * Set message flag to one or several messages
1302    *
1303    * @param mixed  Message UIDs as array or as comma-separated string
ed5407 1304    * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
6d969b 1305    * @return boolean True on success, False on failure
8d4bcd 1306    */
4e17e6 1307   function set_flag($uids, $flag)
T 1308     {
1309     $flag = strtoupper($flag);
1310     $msg_ids = array();
1311     if (!is_array($uids))
8fae1e 1312       $uids = explode(',',$uids);
4e17e6 1313       
8fae1e 1314     foreach ($uids as $uid) {
31b2ce 1315       $msg_ids[$uid] = $this->_uid2id($uid);
8fae1e 1316     }
4e17e6 1317       
8fae1e 1318     if ($flag=='UNDELETED')
S 1319       $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
1320     else if ($flag=='UNSEEN')
31b2ce 1321       $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
e189a6 1322     else if ($flag=='UNFLAGGED')
A 1323       $result = iil_C_UnFlag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), 'FLAGGED');
4e17e6 1324     else
31b2ce 1325       $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
4e17e6 1326
T 1327     // reload message headers if cached
1328     $cache_key = $this->mailbox.'.msg';
1cded8 1329     if ($this->caching_enabled)
4e17e6 1330       {
31b2ce 1331       foreach ($msg_ids as $uid => $id)
4e17e6 1332         {
31b2ce 1333         if ($cached_headers = $this->get_cached_message($cache_key, $uid))
4e17e6 1334           {
1cded8 1335           $this->remove_message_cache($cache_key, $id);
T 1336           //$this->get_headers($uid);
4e17e6 1337           }
T 1338         }
1cded8 1339
T 1340       // close and re-open connection
1341       // this prevents connection problems with Courier 
1342       $this->reconnect();
4e17e6 1343       }
T 1344
1345     // set nr of messages that were flaged
31b2ce 1346     $count = count($msg_ids);
4e17e6 1347
T 1348     // clear message count cache
1349     if ($result && $flag=='SEEN')
1350       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
1351     else if ($result && $flag=='UNSEEN')
1352       $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
1353     else if ($result && $flag=='DELETED')
1354       $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
1355
1356     return $result;
1357     }
1358
1359
6d969b 1360   /**
T 1361    * Append a mail message (source) to a specific mailbox
1362    *
1363    * @param string Target mailbox
1364    * @param string Message source
1365    * @return boolean True on success, False on error
1366    */
b068a0 1367   function save_message($mbox_name, &$message)
4e17e6 1368     {
a894ba 1369     $mbox_name = stripslashes($mbox_name);
aadfa1 1370     $mailbox = $this->_mod_mailbox($mbox_name);
4e17e6 1371
f88d41 1372     // make sure mailbox exists
4e17e6 1373     if (in_array($mailbox, $this->_list_mailboxes()))
T 1374       $saved = iil_C_Append($this->conn, $mailbox, $message);
1cded8 1375
4e17e6 1376     if ($saved)
T 1377       {
1378       // increase messagecount of the target mailbox
1379       $this->_set_messagecount($mailbox, 'ALL', 1);
1380       }
1381           
1382     return $saved;
1383     }
1384
1385
6d969b 1386   /**
T 1387    * Move a message from one mailbox to another
1388    *
1389    * @param string List of UIDs to move, separated by comma
1390    * @param string Target mailbox
1391    * @param string Source mailbox
1392    * @return boolean True on success, False on error
1393    */
4e17e6 1394   function move_message($uids, $to_mbox, $from_mbox='')
T 1395     {
a05793 1396     $to_mbox_in = stripslashes($to_mbox);
a894ba 1397     $from_mbox = stripslashes($from_mbox);
a05793 1398     $to_mbox = $this->_mod_mailbox($to_mbox_in);
4e17e6 1399     $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
T 1400
f88d41 1401     // make sure mailbox exists
4e17e6 1402     if (!in_array($to_mbox, $this->_list_mailboxes()))
f88d41 1403       {
a05793 1404       if (in_array($to_mbox_in, $this->default_folders))
T 1405         $this->create_mailbox($to_mbox_in, TRUE);
f88d41 1406       else
T 1407         return FALSE;
1408       }
1409
4e17e6 1410     // convert the list of uids to array
T 1411     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1412     
1413     // exit if no message uids are specified
1414     if (!is_array($a_uids))
1415       return false;
520c36 1416
4e17e6 1417     // convert uids to message ids
T 1418     $a_mids = array();
1419     foreach ($a_uids as $uid)
1420       $a_mids[] = $this->_uid2id($uid, $from_mbox);
520c36 1421
4379bb 1422     $iil_move = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
T 1423     $moved = !($iil_move === false || $iil_move < 0);
4e17e6 1424     
T 1425     // send expunge command in order to have the moved message
1426     // really deleted from the source mailbox
bf0cb9 1427     if ($moved) {
d87fc2 1428       // but only when flag_for_deletion is set to false
A 1429       if (!rcmail::get_instance()->config->get('flag_for_deletion', false))
1430         {
1431         $this->_expunge($from_mbox, FALSE);
1432         $this->_clear_messagecount($from_mbox);
1433         $this->_clear_messagecount($to_mbox);
1434         }
bf0cb9 1435     }
T 1436     // moving failed
1437     else if (rcmail::get_instance()->config->get('delete_always', false)) {
1438       return iil_C_Delete($this->conn, $from_mbox, join(',', $a_mids));
1439     }
04c618 1440       
T 1441     // remove message ids from search set
1442     if ($moved && $this->search_set && $from_mbox == $this->mailbox)
1443       $this->search_set = array_diff($this->search_set, $a_mids);
4e17e6 1444
T 1445     // update cached message headers
1446     $cache_key = $from_mbox.'.msg';
1cded8 1447     if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1448       {
1cded8 1449       $start_index = 100000;
4e17e6 1450       foreach ($a_uids as $uid)
1cded8 1451         {
04c618 1452         if (($index = array_search($uid, $a_cache_index)) !== FALSE)
T 1453           $start_index = min($index, $start_index);
1cded8 1454         }
4e17e6 1455
1cded8 1456       // clear cache from the lowest index on
T 1457       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1458       }
T 1459
1460     return $moved;
1461     }
1462
1463
6d969b 1464   /**
T 1465    * Mark messages as deleted and expunge mailbox
1466    *
1467    * @param string List of UIDs to move, separated by comma
1468    * @param string Source mailbox
1469    * @return boolean True on success, False on error
1470    */
aadfa1 1471   function delete_message($uids, $mbox_name='')
4e17e6 1472     {
a894ba 1473     $mbox_name = stripslashes($mbox_name);
aadfa1 1474     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
4e17e6 1475
T 1476     // convert the list of uids to array
1477     $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1478     
1479     // exit if no message uids are specified
1480     if (!is_array($a_uids))
1481       return false;
1482
1483     // convert uids to message ids
1484     $a_mids = array();
1485     foreach ($a_uids as $uid)
1486       $a_mids[] = $this->_uid2id($uid, $mailbox);
1487         
1488     $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1489     
1490     // send expunge command in order to have the deleted message
1491     // really deleted from the mailbox
1492     if ($deleted)
1493       {
1cded8 1494       $this->_expunge($mailbox, FALSE);
4e17e6 1495       $this->_clear_messagecount($mailbox);
c719f3 1496       unset($this->uid_id_map[$mailbox]);
4e17e6 1497       }
T 1498
04c618 1499     // remove message ids from search set
077070 1500     if ($deleted && $this->search_set && $mailbox == $this->mailbox)
04c618 1501       $this->search_set = array_diff($this->search_set, $a_mids);
T 1502
4e17e6 1503     // remove deleted messages from cache
1cded8 1504     $cache_key = $mailbox.'.msg';
T 1505     if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
4e17e6 1506       {
1cded8 1507       $start_index = 100000;
4e17e6 1508       foreach ($a_uids as $uid)
1cded8 1509         {
6ce04b 1510         if (($index = array_search($uid, $a_cache_index)) !== FALSE)
S 1511           $start_index = min($index, $start_index);
1cded8 1512         }
4e17e6 1513
1cded8 1514       // clear cache from the lowest index on
T 1515       $this->clear_message_cache($cache_key, $start_index);
4e17e6 1516       }
T 1517
1518     return $deleted;
1519     }
1520
1521
6d969b 1522   /**
T 1523    * Clear all messages in a specific mailbox
1524    *
1525    * @param string Mailbox name
1526    * @return int Above 0 on success
1527    */
aadfa1 1528   function clear_mailbox($mbox_name=NULL)
a95e0e 1529     {
a894ba 1530     $mbox_name = stripslashes($mbox_name);
aadfa1 1531     $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
a95e0e 1532     $msg_count = $this->_messagecount($mailbox, 'ALL');
T 1533     
1534     if ($msg_count>0)
1cded8 1535       {
5e3512 1536       $cleared = iil_C_ClearFolder($this->conn, $mailbox);
T 1537       
1538       // make sure the message count cache is cleared as well
1539       if ($cleared)
1540         {
1541         $this->clear_message_cache($mailbox.'.msg');      
1542         $a_mailbox_cache = $this->get_cache('messagecount');
1543         unset($a_mailbox_cache[$mailbox]);
1544         $this->update_cache('messagecount', $a_mailbox_cache);
1545         }
1546         
1547       return $cleared;
1cded8 1548       }
a95e0e 1549     else
T 1550       return 0;
1551     }
1552
1553
6d969b 1554   /**
T 1555    * Send IMAP expunge command and clear cache
1556    *
1557    * @param string Mailbox name
1558    * @param boolean False if cache should not be cleared
1559    * @return boolean True on success
1560    */
aadfa1 1561   function expunge($mbox_name='', $clear_cache=TRUE)
4e17e6 1562     {
a894ba 1563     $mbox_name = stripslashes($mbox_name);
aadfa1 1564     $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1cded8 1565     return $this->_expunge($mailbox, $clear_cache);
T 1566     }
1567
1568
6d969b 1569   /**
T 1570    * Send IMAP expunge command and clear cache
1571    *
1572    * @see rcube_imap::expunge()
1573    * @access private
1574    */
1cded8 1575   function _expunge($mailbox, $clear_cache=TRUE)
T 1576     {
4e17e6 1577     $result = iil_C_Expunge($this->conn, $mailbox);
T 1578
1579     if ($result>=0 && $clear_cache)
1580       {
5c69aa 1581       $this->clear_message_cache($mailbox.'.msg');
4e17e6 1582       $this->_clear_messagecount($mailbox);
T 1583       }
1584       
1585     return $result;
1586     }
1587
1588
1589   /* --------------------------------
1590    *        folder managment
1591    * --------------------------------*/
1592
1593
fa4cd2 1594   /**
T 1595    * Get a list of all folders available on the IMAP server
1596    * 
1597    * @param string IMAP root dir
6d969b 1598    * @return array Indexed array with folder names
fa4cd2 1599    */
4e17e6 1600   function list_unsubscribed($root='')
T 1601     {
1602     static $sa_unsubscribed;
1603     
1604     if (is_array($sa_unsubscribed))
1605       return $sa_unsubscribed;
1606       
1607     // retrieve list of folders from IMAP server
1608     $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1609
1610     // modify names with root dir
aadfa1 1611     foreach ($a_mboxes as $mbox_name)
4e17e6 1612       {
aadfa1 1613       $name = $this->_mod_mailbox($mbox_name, 'out');
4e17e6 1614       if (strlen($name))
T 1615         $a_folders[] = $name;
1616       }
1617
1618     // filter folders and sort them
1619     $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1620     return $sa_unsubscribed;
1621     }
1622
1623
58e360 1624   /**
6d969b 1625    * Get mailbox quota information
58e360 1626    * added by Nuny
6d969b 1627    * 
T 1628    * @return mixed Quota info or False if not supported
58e360 1629    */
T 1630   function get_quota()
1631     {
1632     if ($this->get_capability('QUOTA'))
fda695 1633       return iil_C_GetQuota($this->conn);
3ea0e3 1634     
4647e1 1635     return FALSE;
58e360 1636     }
T 1637
1638
fa4cd2 1639   /**
6d969b 1640    * Subscribe to a specific mailbox(es)
T 1641    *
c5097c 1642    * @param array Mailbox name(s)
6d969b 1643    * @return boolean True on success
fa4cd2 1644    */ 
c5097c 1645   function subscribe($a_mboxes)
4e17e6 1646     {
c5097c 1647     if (!is_array($a_mboxes))
A 1648       $a_mboxes = array($a_mboxes);
1649
4e17e6 1650     // let this common function do the main work
T 1651     return $this->_change_subscription($a_mboxes, 'subscribe');
1652     }
1653
1654
fa4cd2 1655   /**
6d969b 1656    * Unsubscribe mailboxes
T 1657    *
c5097c 1658    * @param array Mailbox name(s)
6d969b 1659    * @return boolean True on success
fa4cd2 1660    */
c5097c 1661   function unsubscribe($a_mboxes)
4e17e6 1662     {
c5097c 1663     if (!is_array($a_mboxes))
A 1664       $a_mboxes = array($a_mboxes);
4e17e6 1665
T 1666     // let this common function do the main work
1667     return $this->_change_subscription($a_mboxes, 'unsubscribe');
1668     }
1669
1670
fa4cd2 1671   /**
4d4264 1672    * Create a new mailbox on the server and register it in local cache
T 1673    *
1674    * @param string  New mailbox name (as utf-7 string)
1675    * @param boolean True if the new mailbox should be subscribed
1676    * @param string  Name of the created mailbox, false on error
fa4cd2 1677    */
4e17e6 1678   function create_mailbox($name, $subscribe=FALSE)
T 1679     {
1680     $result = FALSE;
1cded8 1681     
T 1682     // replace backslashes
1683     $name = preg_replace('/[\\\]+/', '-', $name);
1684
1685     // reduce mailbox name to 100 chars
4d4264 1686     $name = substr($name, 0, 100);
1cded8 1687
4d4264 1688     $abs_name = $this->_mod_mailbox($name);
4e17e6 1689     $a_mailbox_cache = $this->get_cache('mailboxes');
fa4cd2 1690
719a25 1691     if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
a95e0e 1692       $result = iil_C_CreateFolder($this->conn, $abs_name);
4e17e6 1693
fa4cd2 1694     // try to subscribe it
719a25 1695     if ($result && $subscribe)
4d4264 1696       $this->subscribe($name);
4e17e6 1697
7902df 1698     return $result ? $name : FALSE;
4e17e6 1699     }
T 1700
1701
fa4cd2 1702   /**
4d4264 1703    * Set a new name to an existing mailbox
T 1704    *
1705    * @param string Mailbox to rename (as utf-7 string)
1706    * @param string New mailbox name (as utf-7 string)
6d969b 1707    * @return string Name of the renames mailbox, False on error
fa4cd2 1708    */
f9c107 1709   function rename_mailbox($mbox_name, $new_name)
4e17e6 1710     {
c8c1e0 1711     $result = FALSE;
S 1712
1713     // replace backslashes
1714     $name = preg_replace('/[\\\]+/', '-', $new_name);
f9c107 1715         
T 1716     // encode mailbox name and reduce it to 100 chars
4d4264 1717     $name = substr($new_name, 0, 100);
c8c1e0 1718
f9c107 1719     // make absolute path
T 1720     $mailbox = $this->_mod_mailbox($mbox_name);
4d4264 1721     $abs_name = $this->_mod_mailbox($name);
f7bfec 1722     
T 1723     // check if mailbox is subscribed
1724     $a_subscribed = $this->_list_mailboxes();
1725     $subscribed = in_array($mailbox, $a_subscribed);
1726     
1727     // unsubscribe folder
1728     if ($subscribed)
1729       iil_C_UnSubscribe($this->conn, $mailbox);
4d4264 1730
f9c107 1731     if (strlen($abs_name))
T 1732       $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
4d4264 1733
f9c107 1734     if ($result)
T 1735       {
574564 1736       $delm = $this->get_hierarchy_delimiter();
T 1737       
1738       // check if mailbox children are subscribed
1739       foreach ($a_subscribed as $c_subscribed)
1740         if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed))
1741           {
1742           iil_C_UnSubscribe($this->conn, $c_subscribed);
1743           iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed));
1744           }
1745
1746       // clear cache
f9c107 1747       $this->clear_message_cache($mailbox.'.msg');
f7bfec 1748       $this->clear_cache('mailboxes');      
f9c107 1749       }
f7bfec 1750
4d4264 1751     // try to subscribe it
f7bfec 1752     if ($result && $subscribed)
T 1753       iil_C_Subscribe($this->conn, $abs_name);
c8c1e0 1754
S 1755     return $result ? $name : FALSE;
4e17e6 1756     }
T 1757
1758
fa4cd2 1759   /**
6d969b 1760    * Remove mailboxes from server
T 1761    *
1762    * @param string Mailbox name
1763    * @return boolean True on success
fa4cd2 1764    */
aadfa1 1765   function delete_mailbox($mbox_name)
4e17e6 1766     {
T 1767     $deleted = FALSE;
1768
aadfa1 1769     if (is_array($mbox_name))
S 1770       $a_mboxes = $mbox_name;
1771     else if (is_string($mbox_name) && strlen($mbox_name))
1772       $a_mboxes = explode(',', $mbox_name);
4e17e6 1773
97a656 1774     $all_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
S 1775
4e17e6 1776     if (is_array($a_mboxes))
aadfa1 1777       foreach ($a_mboxes as $mbox_name)
4e17e6 1778         {
aadfa1 1779         $mailbox = $this->_mod_mailbox($mbox_name);
4e17e6 1780
T 1781         // unsubscribe mailbox before deleting
1782         iil_C_UnSubscribe($this->conn, $mailbox);
fa4cd2 1783
4e17e6 1784         // send delete command to server
T 1785         $result = iil_C_DeleteFolder($this->conn, $mailbox);
1786         if ($result>=0)
1787           $deleted = TRUE;
97a656 1788
S 1789         foreach ($all_mboxes as $c_mbox)
fa0152 1790           {
T 1791           $regex = preg_quote($mailbox . $this->delimiter, '/');
1792           $regex = '/^' . $regex . '/';
1793           if (preg_match($regex, $c_mbox))
97a656 1794             {
S 1795             iil_C_UnSubscribe($this->conn, $c_mbox);
1796             $result = iil_C_DeleteFolder($this->conn, $c_mbox);
1797             if ($result>=0)
1798               $deleted = TRUE;
1799             }
fa0152 1800           }
4e17e6 1801         }
T 1802
1803     // clear mailboxlist cache
1804     if ($deleted)
1cded8 1805       {
T 1806       $this->clear_message_cache($mailbox.'.msg');
4e17e6 1807       $this->clear_cache('mailboxes');
1cded8 1808       }
4e17e6 1809
1cded8 1810     return $deleted;
4e17e6 1811     }
T 1812
fa4cd2 1813
T 1814   /**
1815    * Create all folders specified as default
1816    */
1817   function create_default_folders()
1818     {
1819     $a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
1820     $a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
1821     
1822     // create default folders if they do not exist
1823     foreach ($this->default_folders as $folder)
1824       {
1825       $abs_name = $this->_mod_mailbox($folder);
719a25 1826       if (!in_array_nocase($abs_name, $a_folders))
T 1827         $this->create_mailbox($folder, TRUE);
1828       else if (!in_array_nocase($abs_name, $a_subscribed))
1829         $this->subscribe($folder);
fa4cd2 1830       }
T 1831     }
4e17e6 1832
T 1833
1834
1835   /* --------------------------------
1cded8 1836    *   internal caching methods
4e17e6 1837    * --------------------------------*/
6dc026 1838
6d969b 1839   /**
T 1840    * @access private
1841    */
6dc026 1842   function set_caching($set)
T 1843     {
1cded8 1844     if ($set && is_object($this->db))
6dc026 1845       $this->caching_enabled = TRUE;
T 1846     else
1847       $this->caching_enabled = FALSE;
1848     }
1cded8 1849
6d969b 1850   /**
T 1851    * @access private
1852    */
4e17e6 1853   function get_cache($key)
T 1854     {
1855     // read cache
6dc026 1856     if (!isset($this->cache[$key]) && $this->caching_enabled)
4e17e6 1857       {
1cded8 1858       $cache_data = $this->_read_cache_record('IMAP.'.$key);
4e17e6 1859       $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
T 1860       }
1861     
1cded8 1862     return $this->cache[$key];
4e17e6 1863     }
T 1864
6d969b 1865   /**
T 1866    * @access private
1867    */
4e17e6 1868   function update_cache($key, $data)
T 1869     {
1870     $this->cache[$key] = $data;
1871     $this->cache_changed = TRUE;
1872     $this->cache_changes[$key] = TRUE;
1873     }
1874
6d969b 1875   /**
T 1876    * @access private
1877    */
4e17e6 1878   function write_cache()
T 1879     {
6dc026 1880     if ($this->caching_enabled && $this->cache_changed)
4e17e6 1881       {
T 1882       foreach ($this->cache as $key => $data)
1883         {
1884         if ($this->cache_changes[$key])
1cded8 1885           $this->_write_cache_record('IMAP.'.$key, serialize($data));
4e17e6 1886         }
T 1887       }    
1888     }
1889
6d969b 1890   /**
T 1891    * @access private
1892    */
4e17e6 1893   function clear_cache($key=NULL)
T 1894     {
fc2312 1895     if (!$this->caching_enabled)
A 1896       return;
1897     
4e17e6 1898     if ($key===NULL)
T 1899       {
1900       foreach ($this->cache as $key => $data)
1cded8 1901         $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1902
T 1903       $this->cache = array();
1904       $this->cache_changed = FALSE;
1905       $this->cache_changes = array();
1906       }
1907     else
1908       {
1cded8 1909       $this->_clear_cache_record('IMAP.'.$key);
4e17e6 1910       $this->cache_changes[$key] = FALSE;
T 1911       unset($this->cache[$key]);
1912       }
1913     }
1914
6d969b 1915   /**
T 1916    * @access private
1917    */
1cded8 1918   function _read_cache_record($key)
T 1919     {
1920     $cache_data = FALSE;
1921     
1922     if ($this->db)
1923       {
1924       // get cached data from DB
1925       $sql_result = $this->db->query(
1926         "SELECT cache_id, data
1927          FROM ".get_table_name('cache')."
1928          WHERE  user_id=?
1929          AND    cache_key=?",
1930         $_SESSION['user_id'],
1931         $key);
1932
1933       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1934         {
1935         $cache_data = $sql_arr['data'];
1936         $this->cache_keys[$key] = $sql_arr['cache_id'];
1937         }
1938       }
1939
6d969b 1940     return $cache_data;
1cded8 1941     }
T 1942
6d969b 1943   /**
T 1944    * @access private
1945    */
1cded8 1946   function _write_cache_record($key, $data)
T 1947     {
1948     if (!$this->db)
1949       return FALSE;
1950
1951     // check if we already have a cache entry for this key
1952     if (!isset($this->cache_keys[$key]))
1953       {
1954       $sql_result = $this->db->query(
1955         "SELECT cache_id
1956          FROM ".get_table_name('cache')."
1957          WHERE  user_id=?
1958          AND    cache_key=?",
1959         $_SESSION['user_id'],
1960         $key);
1961                                      
1962       if ($sql_arr = $this->db->fetch_assoc($sql_result))
1963         $this->cache_keys[$key] = $sql_arr['cache_id'];
1964       else
1965         $this->cache_keys[$key] = FALSE;
1966       }
1967
1968     // update existing cache record
1969     if ($this->cache_keys[$key])
1970       {
1971       $this->db->query(
1972         "UPDATE ".get_table_name('cache')."
107bde 1973          SET    created=".$this->db->now().",
1cded8 1974                 data=?
T 1975          WHERE  user_id=?
1976          AND    cache_key=?",
1977         $data,
1978         $_SESSION['user_id'],
1979         $key);
1980       }
1981     // add new cache record
1982     else
1983       {
1984       $this->db->query(
1985         "INSERT INTO ".get_table_name('cache')."
1986          (created, user_id, cache_key, data)
107bde 1987          VALUES (".$this->db->now().", ?, ?, ?)",
1cded8 1988         $_SESSION['user_id'],
T 1989         $key,
1990         $data);
1991       }
1992     }
1993
6d969b 1994   /**
T 1995    * @access private
1996    */
1cded8 1997   function _clear_cache_record($key)
T 1998     {
1999     $this->db->query(
2000       "DELETE FROM ".get_table_name('cache')."
2001        WHERE  user_id=?
2002        AND    cache_key=?",
2003       $_SESSION['user_id'],
2004       $key);
2005     }
2006
2007
2008
4e17e6 2009   /* --------------------------------
1cded8 2010    *   message caching methods
T 2011    * --------------------------------*/
2012    
2013
6d969b 2014   /**
T 2015    * Checks if the cache is up-to-date
2016    *
2017    * @param string Mailbox name
2018    * @param string Internal cache key
2019    * @return int -3 = off, -2 = incomplete, -1 = dirty
2020    */
1cded8 2021   function check_cache_status($mailbox, $cache_key)
T 2022     {
2023     if (!$this->caching_enabled)
2024       return -3;
2025
2026     $cache_index = $this->get_message_cache_index($cache_key, TRUE);
2027     $msg_count = $this->_messagecount($mailbox);
2028     $cache_count = count($cache_index);
2029
2030     // console("Cache check: $msg_count !== ".count($cache_index));
2031
2032     if ($cache_count==$msg_count)
2033       {
2034       // get highest index
2035       $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
2036       $cache_uid = array_pop($cache_index);
2037       
e6f360 2038       // uids of highest message matches -> cache seems OK
1cded8 2039       if ($cache_uid == $header->uid)
T 2040         return 1;
2041
2042       // cache is dirty
2043       return -1;
2044       }
e6f360 2045     // if cache count differs less than 10% report as dirty
1cded8 2046     else if (abs($msg_count - $cache_count) < $msg_count/10)
T 2047       return -1;
2048     else
2049       return -2;
2050     }
2051
6d969b 2052   /**
T 2053    * @access private
2054    */
1cded8 2055   function get_message_cache($key, $from, $to, $sort_field, $sort_order)
T 2056     {
2057     $cache_key = "$key:$from:$to:$sort_field:$sort_order";
2058     $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
2059     
2060     if (!in_array($sort_field, $db_header_fields))
2061       $sort_field = 'idx';
2062     
2063     if ($this->caching_enabled && !isset($this->cache[$cache_key]))
2064       {
2065       $this->cache[$cache_key] = array();
2066       $sql_result = $this->db->limitquery(
2067         "SELECT idx, uid, headers
2068          FROM ".get_table_name('messages')."
2069          WHERE  user_id=?
2070          AND    cache_key=?
2071          ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
2072          strtoupper($sort_order),
2073         $from,
2074         $to-$from,
2075         $_SESSION['user_id'],
2076         $key);
2077
2078       while ($sql_arr = $this->db->fetch_assoc($sql_result))
2079         {
2080         $uid = $sql_arr['uid'];
2081         $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
ecd2e7 2082         
T 2083         // featch headers if unserialize failed
2084         if (empty($this->cache[$cache_key][$uid]))
2085           $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true);
1cded8 2086         }
T 2087       }
2088       
2089     return $this->cache[$cache_key];
2090     }
2091
6d969b 2092   /**
T 2093    * @access private
2094    */
f7bfec 2095   function &get_cached_message($key, $uid, $struct=false)
1cded8 2096     {
T 2097     $internal_key = '__single_msg';
017def 2098     
f7bfec 2099     if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) ||
T 2100         ($struct && empty($this->cache[$internal_key][$uid]->structure))))
1cded8 2101       {
f7bfec 2102       $sql_select = "idx, uid, headers" . ($struct ? ", structure" : '');
1cded8 2103       $sql_result = $this->db->query(
T 2104         "SELECT $sql_select
2105          FROM ".get_table_name('messages')."
2106          WHERE  user_id=?
2107          AND    cache_key=?
2108          AND    uid=?",
2109         $_SESSION['user_id'],
2110         $key,
2111         $uid);
f7bfec 2112
1cded8 2113       if ($sql_arr = $this->db->fetch_assoc($sql_result))
T 2114         {
f7bfec 2115         $this->cache[$internal_key][$uid] = unserialize($sql_arr['headers']);
T 2116         if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure']))
2117           $this->cache[$internal_key][$uid]->structure = unserialize($sql_arr['structure']);
1cded8 2118         }
T 2119       }
2120
2121     return $this->cache[$internal_key][$uid];
2122     }
2123
6d969b 2124   /**
T 2125    * @access private
2126    */  
0677ca 2127   function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
1cded8 2128     {
T 2129     static $sa_message_index = array();
2130     
4647e1 2131     // empty key -> empty array
ffb0b0 2132     if (!$this->caching_enabled || empty($key))
4647e1 2133       return array();
T 2134     
1cded8 2135     if (!empty($sa_message_index[$key]) && !$force)
T 2136       return $sa_message_index[$key];
2137     
2138     $sa_message_index[$key] = array();
2139     $sql_result = $this->db->query(
2140       "SELECT idx, uid
2141        FROM ".get_table_name('messages')."
2142        WHERE  user_id=?
2143        AND    cache_key=?
0677ca 2144        ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
1cded8 2145       $_SESSION['user_id'],
T 2146       $key);
2147
2148     while ($sql_arr = $this->db->fetch_assoc($sql_result))
2149       $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
2150       
2151     return $sa_message_index[$key];
2152     }
2153
6d969b 2154   /**
T 2155    * @access private
2156    */
f7bfec 2157   function add_message_cache($key, $index, $headers, $struct=null)
1cded8 2158     {
017def 2159     if (empty($key) || !is_object($headers) || empty($headers->uid))
T 2160         return;
2161     
2162     // add to internal (fast) cache
2163     $this->cache['__single_msg'][$headers->uid] = $headers;
2164     $this->cache['__single_msg'][$headers->uid]->structure = $struct;
2165     
2166     // no further caching
2167     if (!$this->caching_enabled)
31b2ce 2168       return;
017def 2169     
f7bfec 2170     // check for an existing record (probly headers are cached but structure not)
T 2171     $sql_result = $this->db->query(
2172         "SELECT message_id
2173          FROM ".get_table_name('messages')."
2174          WHERE  user_id=?
2175          AND    cache_key=?
2176          AND    uid=?
2177          AND    del<>1",
2178         $_SESSION['user_id'],
2179         $key,
2180         $headers->uid);
31b2ce 2181
f7bfec 2182     // update cache record
T 2183     if ($sql_arr = $this->db->fetch_assoc($sql_result))
2184       {
2185       $this->db->query(
2186         "UPDATE ".get_table_name('messages')."
2187          SET   idx=?, headers=?, structure=?
2188          WHERE message_id=?",
2189         $index,
2190         serialize($headers),
2191         is_object($struct) ? serialize($struct) : NULL,
2192         $sql_arr['message_id']
2193         );
2194       }
2195     else  // insert new record
2196       {
2197       $this->db->query(
2198         "INSERT INTO ".get_table_name('messages')."
2199          (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers, structure)
107bde 2200          VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?, ?)",
f7bfec 2201         $_SESSION['user_id'],
T 2202         $key,
2203         $index,
2204         $headers->uid,
2205         (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
2206         (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
2207         (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
2208         (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
2209         (int)$headers->size,
2210         serialize($headers),
2211         is_object($struct) ? serialize($struct) : NULL
2212         );
2213       }
1cded8 2214     }
T 2215     
6d969b 2216   /**
T 2217    * @access private
2218    */
1cded8 2219   function remove_message_cache($key, $index)
T 2220     {
780527 2221     if (!$this->caching_enabled)
T 2222       return;
2223     
1cded8 2224     $this->db->query(
T 2225       "DELETE FROM ".get_table_name('messages')."
2226        WHERE  user_id=?
2227        AND    cache_key=?
2228        AND    idx=?",
2229       $_SESSION['user_id'],
2230       $key,
2231       $index);
2232     }
2233
6d969b 2234   /**
T 2235    * @access private
2236    */
1cded8 2237   function clear_message_cache($key, $start_index=1)
T 2238     {
780527 2239     if (!$this->caching_enabled)
T 2240       return;
2241     
1cded8 2242     $this->db->query(
T 2243       "DELETE FROM ".get_table_name('messages')."
2244        WHERE  user_id=?
2245        AND    cache_key=?
2246        AND    idx>=?",
2247       $_SESSION['user_id'],
2248       $key,
2249       $start_index);
2250     }
2251
2252
2253
2254
2255   /* --------------------------------
2256    *   encoding/decoding methods
4e17e6 2257    * --------------------------------*/
T 2258
6d969b 2259   /**
T 2260    * Split an address list into a structured array list
2261    *
2262    * @param string  Input string
2263    * @param int     List only this number of addresses
2264    * @param boolean Decode address strings
2265    * @return array  Indexed list of addresses
2266    */
f11541 2267   function decode_address_list($input, $max=null, $decode=true)
4e17e6 2268     {
f11541 2269     $a = $this->_parse_address_list($input, $decode);
4e17e6 2270     $out = array();
0c6f4b 2271     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
T 2272     $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
41fa0b 2273     
4e17e6 2274     if (!is_array($a))
T 2275       return $out;
2276
2277     $c = count($a);
2278     $j = 0;
2279
2280     foreach ($a as $val)
2281       {
2282       $j++;
2283       $address = $val['address'];
2284       $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
3cf664 2285       if ($name && $address && $name != $address)
0c6f4b 2286         $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
3cf664 2287       else if ($address)
T 2288         $string = $address;
2289       else if ($name)
2290         $string = $name;
4e17e6 2291       
T 2292       $out[$j] = array('name' => $name,
2293                        'mailto' => $address,
2294                        'string' => $string);
2295               
2296       if ($max && $j==$max)
2297         break;
2298       }
2299     
2300     return $out;
2301     }
2302
2303
6d969b 2304   /**
T 2305    * Decode a message header value
2306    *
2307    * @param string  Header value
2308    * @param boolean Remove quotes if necessary
2309    * @return string Decoded string
2310    */
1cded8 2311   function decode_header($input, $remove_quotes=FALSE)
4e17e6 2312     {
17b5fb 2313     $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset);
1cded8 2314     if ($str{0}=='"' && $remove_quotes)
T 2315       $str = str_replace('"', '', $str);
2316     
2317     return $str;
4b0f65 2318     }
ba8f44 2319
T 2320
2321   /**
2322    * Decode a mime-encoded string to internal charset
2323    *
6d969b 2324    * @param string  Header value
T 2325    * @param string  Fallback charset if none specified
2326    * @return string Decoded string
2327    * @static
ba8f44 2328    */
f11541 2329   function decode_mime_string($input, $fallback=null)
4b0f65 2330     {
4e17e6 2331     $out = '';
T 2332
2333     $pos = strpos($input, '=?');
2334     if ($pos !== false)
2335       {
399835 2336       // rfc: all line breaks or other characters not found 
A 2337       // in the Base64 Alphabet must be ignored by decoding software
2338       // delete all blanks between MIME-lines, differently we can 
2339       // receive unnecessary blanks and broken utf-8 symbols
9e60d4 2340       $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
T 2341
4e17e6 2342       $out = substr($input, 0, $pos);
T 2343   
2344       $end_cs_pos = strpos($input, "?", $pos+2);
2345       $end_en_pos = strpos($input, "?", $end_cs_pos+1);
2346       $end_pos = strpos($input, "?=", $end_en_pos+1);
2347   
2348       $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
2349       $rest = substr($input, $end_pos+2);
2350
4b0f65 2351       $out .= rcube_imap::_decode_mime_string_part($encstr);
f11541 2352       $out .= rcube_imap::decode_mime_string($rest, $fallback);
4e17e6 2353
T 2354       return $out;
2355       }
399835 2356
f11541 2357     // no encoding information, use fallback
399835 2358     return rcube_charset_convert($input, 
b026c3 2359       !empty($fallback) ? $fallback : rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'));
4e17e6 2360     }
T 2361
2362
ba8f44 2363   /**
T 2364    * Decode a part of a mime-encoded string
2365    *
6d969b 2366    * @access private
ba8f44 2367    */
4b0f65 2368   function _decode_mime_string_part($str)
4e17e6 2369     {
T 2370     $a = explode('?', $str);
2371     $count = count($a);
2372
2373     // should be in format "charset?encoding?base64_string"
2374     if ($count >= 3)
2375       {
2376       for ($i=2; $i<$count; $i++)
2377         $rest.=$a[$i];
2378
2379       if (($a[1]=="B")||($a[1]=="b"))
2380         $rest = base64_decode($rest);
2381       else if (($a[1]=="Q")||($a[1]=="q"))
2382         {
2383         $rest = str_replace("_", " ", $rest);
2384         $rest = quoted_printable_decode($rest);
2385         }
2386
3f9edb 2387       return rcube_charset_convert($rest, $a[0]);
4e17e6 2388       }
T 2389     else
3f9edb 2390       return $str;    // we dont' know what to do with this  
4e17e6 2391     }
T 2392
2393
6d969b 2394   /**
T 2395    * Decode a mime part
2396    *
2397    * @param string Input string
2398    * @param string Part encoding
2399    * @return string Decoded string
2400    * @access private
2401    */
4e17e6 2402   function mime_decode($input, $encoding='7bit')
T 2403     {
2404     switch (strtolower($encoding))
2405       {
2406       case '7bit':
2407         return $input;
2408         break;
2409       
2410       case 'quoted-printable':
2411         return quoted_printable_decode($input);
2412         break;
2413       
2414       case 'base64':
2415         return base64_decode($input);
2416         break;
2417       
2418       default:
2419         return $input;
2420       }
2421     }
2422
2423
6d969b 2424   /**
T 2425    * Convert body charset to UTF-8 according to the ctype_parameters
2426    *
2427    * @param string Part body to decode
2428    * @param string Charset to convert from
2429    * @return string Content converted to internal charset
2430    */
4e17e6 2431   function charset_decode($body, $ctype_param)
T 2432     {
a95e0e 2433     if (is_array($ctype_param) && !empty($ctype_param['charset']))
3f9edb 2434       return rcube_charset_convert($body, $ctype_param['charset']);
4e17e6 2435
fb5f4f 2436     // defaults to what is specified in the class header
17b5fb 2437     return rcube_charset_convert($body,  $this->default_charset);
4e17e6 2438     }
T 2439
2440
6d969b 2441   /**
T 2442    * Translate UID to message ID
2443    *
2444    * @param int    Message UID
2445    * @param string Mailbox name
2446    * @return int   Message ID
2447    */
2448   function get_id($uid, $mbox_name=NULL) 
2449     {
2450       $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
2451       return $this->_uid2id($uid, $mailbox);
2452     }
2453
2454
2455   /**
2456    * Translate message number to UID
2457    *
2458    * @param int    Message ID
2459    * @param string Mailbox name
2460    * @return int   Message UID
2461    */
2462   function get_uid($id,$mbox_name=NULL)
2463     {
2464       $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
2465       return $this->_id2uid($id, $mailbox);
2466     }
2467
1cded8 2468
ba8f44 2469
4e17e6 2470   /* --------------------------------
T 2471    *         private methods
2472    * --------------------------------*/
2473
2474
6d969b 2475   /**
T 2476    * @access private
2477    */
aadfa1 2478   function _mod_mailbox($mbox_name, $mode='in')
4e17e6 2479     {
ae4d74 2480     if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
aadfa1 2481       return $mbox_name;
7902df 2482
f619de 2483     if (!empty($this->root_dir) && $mode=='in') 
aadfa1 2484       $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
7902df 2485     else if (strlen($this->root_dir) && $mode=='out') 
aadfa1 2486       $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
4e17e6 2487
aadfa1 2488     return $mbox_name;
4e17e6 2489     }
T 2490
d5342a 2491   /**
T 2492    * Validate the given input and save to local properties
2493    * @access private
2494    */
2495   function _set_sort_order($sort_field, $sort_order)
2496   {
2497     if ($sort_field != null)
2498       $this->sort_field = asciiwords($sort_field);
2499     if ($sort_order != null)
2500       $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
2501   }
4e17e6 2502
6d969b 2503   /**
T 2504    * Sort mailboxes first by default folders and then in alphabethical order
2505    * @access private
2506    */
4e17e6 2507   function _sort_mailbox_list($a_folders)
T 2508     {
ea400e 2509     $a_out = $a_defaults = $folders = array();
681a59 2510
A 2511     $delimiter = $this->get_hierarchy_delimiter();
4e17e6 2512
T 2513     // find default folders and skip folders starting with '.'
681a59 2514     foreach ($a_folders as $i => $folder)
4e17e6 2515       {
T 2516       if ($folder{0}=='.')
6d969b 2517         continue;
fa4cd2 2518
c007e6 2519       if (($p = array_search(strtolower($folder), $this->default_folders_lc)) !== false && !$a_defaults[$p])
6d969b 2520         $a_defaults[$p] = $folder;
4e17e6 2521       else
70aa90 2522         $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7'));
681a59 2523       }
A 2524
2525     asort($folders, SORT_LOCALE_STRING);
2526     ksort($a_defaults);
2527
2528     $folders = array_merge($a_defaults, array_keys($folders));
2529
2530     // finally we must rebuild the list to move 
f219a2 2531     // subfolders of default folders to their place...
A 2532     // ...also do this for the rest of folders because
2533     // asort() is not properly sorting case sensitive names    
681a59 2534     while (list($key, $folder) = each($folders)) {
A 2535       $a_out[] = $folder;
2536       unset($folders[$key]);
f219a2 2537       foreach ($folders as $idx => $f) {
A 2538     if (strpos($f, $folder.$delimiter) === 0) {
2539           $a_out[] = $f;
2540       unset($folders[$idx]);
681a59 2541       }
f219a2 2542         }
A 2543       reset($folders);  
4e17e6 2544       }
T 2545
681a59 2546     return $a_out;
4e17e6 2547     }
T 2548
6d969b 2549   /**
T 2550    * @access private
2551    */
aadfa1 2552   function _uid2id($uid, $mbox_name=NULL)
4e17e6 2553     {
aadfa1 2554     if (!$mbox_name)
S 2555       $mbox_name = $this->mailbox;
4e17e6 2556       
aadfa1 2557     if (!isset($this->uid_id_map[$mbox_name][$uid]))
S 2558       $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
4e17e6 2559
aadfa1 2560     return $this->uid_id_map[$mbox_name][$uid];
4e17e6 2561     }
T 2562
6d969b 2563   /**
T 2564    * @access private
2565    */
aadfa1 2566   function _id2uid($id, $mbox_name=NULL)
e6f360 2567     {
aadfa1 2568     if (!$mbox_name)
S 2569       $mbox_name = $this->mailbox;
e6f360 2570       
1fb2c8 2571     $index = array_flip((array)$this->uid_id_map[$mbox_name]);
017def 2572     if (isset($index[$id]))
T 2573       $uid = $index[$id];
2574     else
2575       {
2576       $uid = iil_C_ID2UID($this->conn, $mbox_name, $id);
2577       $this->uid_id_map[$mbox_name][$uid] = $id;
2578       }
2579     
2580     return $uid;
e6f360 2581     }
T 2582
4e17e6 2583
6d969b 2584   /**
T 2585    * Parse string or array of server capabilities and put them in internal array
2586    * @access private
2587    */
1cded8 2588   function _parse_capability($caps)
T 2589     {
2590     if (!is_array($caps))
2591       $cap_arr = explode(' ', $caps);
2592     else
2593       $cap_arr = $caps;
2594     
2595     foreach ($cap_arr as $cap)
2596       {
2597       if ($cap=='CAPABILITY')
2598         continue;
2599
2600       if (strpos($cap, '=')>0)
2601         {
2602         list($key, $value) = explode('=', $cap);
2603         if (!is_array($this->capabilities[$key]))
2604           $this->capabilities[$key] = array();
2605           
2606         $this->capabilities[$key][] = $value;
2607         }
2608       else
2609         $this->capabilities[$cap] = TRUE;
2610       }
2611     }
2612
2613
6d969b 2614   /**
T 2615    * Subscribe/unsubscribe a list of mailboxes and update local cache
2616    * @access private
2617    */
4e17e6 2618   function _change_subscription($a_mboxes, $mode)
T 2619     {
2620     $updated = FALSE;
2621     
2622     if (is_array($a_mboxes))
aadfa1 2623       foreach ($a_mboxes as $i => $mbox_name)
4e17e6 2624         {
aadfa1 2625         $mailbox = $this->_mod_mailbox($mbox_name);
4e17e6 2626         $a_mboxes[$i] = $mailbox;
T 2627
2628         if ($mode=='subscribe')
2629           $result = iil_C_Subscribe($this->conn, $mailbox);
2630         else if ($mode=='unsubscribe')
2631           $result = iil_C_UnSubscribe($this->conn, $mailbox);
2632
2633         if ($result>=0)
2634           $updated = TRUE;
2635         }
2636         
2637     // get cached mailbox list    
2638     if ($updated)
2639       {
2640       $a_mailbox_cache = $this->get_cache('mailboxes');
2641       if (!is_array($a_mailbox_cache))
2642         return $updated;
2643
2644       // modify cached list
2645       if ($mode=='subscribe')
2646         $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
2647       else if ($mode=='unsubscribe')
2648         $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
2649         
2650       // write mailboxlist to cache
2651       $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
2652       }
2653
2654     return $updated;
2655     }
2656
2657
6d969b 2658   /**
T 2659    * Increde/decrese messagecount for a specific mailbox
2660    * @access private
2661    */
aadfa1 2662   function _set_messagecount($mbox_name, $mode, $increment)
4e17e6 2663     {
T 2664     $a_mailbox_cache = FALSE;
aadfa1 2665     $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
4e17e6 2666     $mode = strtoupper($mode);
T 2667
2668     $a_mailbox_cache = $this->get_cache('messagecount');
2669     
2670     if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
2671       return FALSE;
2672     
2673     // add incremental value to messagecount
2674     $a_mailbox_cache[$mailbox][$mode] += $increment;
31b2ce 2675     
T 2676     // there's something wrong, delete from cache
2677     if ($a_mailbox_cache[$mailbox][$mode] < 0)
2678       unset($a_mailbox_cache[$mailbox][$mode]);
4e17e6 2679
T 2680     // write back to cache
2681     $this->update_cache('messagecount', $a_mailbox_cache);
2682     
2683     return TRUE;
2684     }
2685
2686
6d969b 2687   /**
T 2688    * Remove messagecount of a specific mailbox from cache
2689    * @access private
2690    */
aadfa1 2691   function _clear_messagecount($mbox_name='')
4e17e6 2692     {
T 2693     $a_mailbox_cache = FALSE;
aadfa1 2694     $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
4e17e6 2695
T 2696     $a_mailbox_cache = $this->get_cache('messagecount');
2697
2698     if (is_array($a_mailbox_cache[$mailbox]))
2699       {
2700       unset($a_mailbox_cache[$mailbox]);
2701       $this->update_cache('messagecount', $a_mailbox_cache);
2702       }
2703     }
2704
2705
6d969b 2706   /**
T 2707    * Split RFC822 header string into an associative array
2708    * @access private
2709    */
8d4bcd 2710   function _parse_headers($headers)
T 2711     {
2712     $a_headers = array();
2713     $lines = explode("\n", $headers);
2714     $c = count($lines);
2715     for ($i=0; $i<$c; $i++)
2716       {
2717       if ($p = strpos($lines[$i], ': '))
2718         {
2719         $field = strtolower(substr($lines[$i], 0, $p));
2720         $value = trim(substr($lines[$i], $p+1));
2721         if (!empty($value))
2722           $a_headers[$field] = $value;
2723         }
2724       }
2725     
2726     return $a_headers;
2727     }
2728
2729
6d969b 2730   /**
T 2731    * @access private
2732    */
f11541 2733   function _parse_address_list($str, $decode=true)
4e17e6 2734     {
d04d20 2735     // remove any newlines and carriage returns before
5a6ad2 2736     $a = $this->_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
4e17e6 2737     $result = array();
41fa0b 2738     
4e17e6 2739     foreach ($a as $key => $val)
T 2740       {
568ba3 2741       $val = preg_replace("/([\"\w])</", "$1 <", $val);
f11541 2742       $sub_a = $this->_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val);
41fa0b 2743       $result[$key]['name'] = '';
T 2744
4e17e6 2745       foreach ($sub_a as $k => $v)
T 2746         {
3cf664 2747         if (strpos($v, '@') > 0)
4e17e6 2748           $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
T 2749         else
2750           $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
2751         }
2752         
2753       if (empty($result[$key]['name']))
41fa0b 2754         $result[$key]['name'] = $result[$key]['address'];        
4e17e6 2755       }
T 2756     
2757     return $result;
2758     }
2759
2760
6d969b 2761   /**
T 2762    * @access private
2763    */
4e17e6 2764   function _explode_quoted_string($delimiter, $string)
T 2765     {
5a6ad2 2766     $result = array();
T 2767     $strlen = strlen($string);
2768     for ($q=$p=$i=0; $i < $strlen; $i++)
2769     {
2770       if ($string{$i} == "\"" && $string{$i-1} != "\\")
2771         $q = $q ? false : true;
2772       else if (!$q && preg_match("/$delimiter/", $string{$i}))
2773       {
2774         $result[] = substr($string, $p, $i - $p);
2775         $p = $i + 1;
2776       }
2777     }
4e17e6 2778     
5a6ad2 2779     $result[] = substr($string, $p);
4e17e6 2780     return $result;
T 2781     }
6d969b 2782
T 2783 }  // end class rcube_imap
4e17e6 2784
8d4bcd 2785
T 2786 /**
2787  * Class representing a message part
6d969b 2788  *
T 2789  * @package Mail
8d4bcd 2790  */
T 2791 class rcube_message_part
2792 {
2793   var $mime_id = '';
2794   var $ctype_primary = 'text';
2795   var $ctype_secondary = 'plain';
2796   var $mimetype = 'text/plain';
2797   var $disposition = '';
5cc4b1 2798   var $filename = '';
8d4bcd 2799   var $encoding = '8bit';
T 2800   var $charset = '';
2801   var $size = 0;
2802   var $headers = array();
2803   var $d_parameters = array();
2804   var $ctype_parameters = array();
2805
2806 }
4e17e6 2807
T 2808
7e93ff 2809 /**
T 2810  * Class for sorting an array of iilBasicHeader objects in a predetermined order.
2811  *
6d969b 2812  * @package Mail
7e93ff 2813  * @author Eric Stadtherr
T 2814  */
2815 class rcube_header_sorter
2816 {
2817    var $sequence_numbers = array();
2818    
2819    /**
6d969b 2820     * Set the predetermined sort order.
7e93ff 2821     *
6d969b 2822     * @param array Numerically indexed array of IMAP message sequence numbers
7e93ff 2823     */
T 2824    function set_sequence_numbers($seqnums)
2825    {
05d180 2826       $this->sequence_numbers = array_flip($seqnums);
7e93ff 2827    }
T 2828  
2829    /**
6d969b 2830     * Sort the array of header objects
7e93ff 2831     *
6d969b 2832     * @param array Array of iilBasicHeader objects indexed by UID
7e93ff 2833     */
T 2834    function sort_headers(&$headers)
2835    {
2836       /*
2837        * uksort would work if the keys were the sequence number, but unfortunately
2838        * the keys are the UIDs.  We'll use uasort instead and dereference the value
2839        * to get the sequence number (in the "id" field).
2840        * 
2841        * uksort($headers, array($this, "compare_seqnums")); 
2842        */
2843        uasort($headers, array($this, "compare_seqnums"));
2844    }
2845  
2846    /**
2847     * Sort method called by uasort()
2848     */
2849    function compare_seqnums($a, $b)
2850    {
2851       // First get the sequence number from the header object (the 'id' field).
2852       $seqa = $a->id;
2853       $seqb = $b->id;
2854       
2855       // then find each sequence number in my ordered list
05d180 2856       $posa = isset($this->sequence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1;
T 2857       $posb = isset($this->sequence_numbers[$seqb]) ? intval($this->sequence_numbers[$seqb]) : -1;
7e93ff 2858       
T 2859       // return the relative position as the comparison value
05d180 2860       return $posa - $posb;
7e93ff 2861    }
T 2862 }
4e17e6 2863
T 2864
7e93ff 2865 /**
T 2866  * Add quoted-printable encoding to a given string
2867  * 
6d969b 2868  * @param string   String to encode
T 2869  * @param int      Add new line after this number of characters
2870  * @param boolean  True if spaces should be converted into =20
2871  * @return string Encoded string
7e93ff 2872  */
T 2873 function quoted_printable_encode($input, $line_max=76, $space_conv=false)
4e17e6 2874   {
T 2875   $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2876   $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2877   $eol = "\r\n";
2878   $escape = "=";
2879   $output = "";
2880
2881   while( list(, $line) = each($lines))
2882     {
2883     //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2884     $linlen = strlen($line);
2885     $newline = "";
2886     for($i = 0; $i < $linlen; $i++)
2887       {
2888       $c = substr( $line, $i, 1 );
2889       $dec = ord( $c );
2890       if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2891         {
2892         $c = "=2E";
2893         }
2894       if ( $dec == 32 )
2895         {
2896         if ( $i == ( $linlen - 1 ) ) // convert space at eol only
2897           {
2898           $c = "=20";
2899           }
2900         else if ( $space_conv )
2901           {
2902           $c = "=20";
2903           }
2904         }
2905       else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) )  // always encode "\t", which is *not* required
2906         {
2907         $h2 = floor($dec/16);
2908         $h1 = floor($dec%16);
2909         $c = $escape.$hex["$h2"].$hex["$h1"];
2910         }
2911          
2912       if ( (strlen($newline) + strlen($c)) >= $line_max )  // CRLF is not counted
2913         {
2914         $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
2915         $newline = "";
2916         // check if newline first character will be point or not
2917         if ( $dec == 46 )
2918           {
2919           $c = "=2E";
2920           }
2921         }
2922       $newline .= $c;
2923       } // end of for
2924     $output .= $newline.$eol;
2925     } // end of while
2926
2927   return trim($output);
2928   }
2929
8d4bcd 2930