source: github/program/include/rcube_imap.php @ 41caad8

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