4 +-----------------------------------------------------------------------+
5 | program/include/rcube_imap.php |
7 | This file is part of the RoundCube Webmail client |
8 | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland |
9 | Licensed under the GNU GPL |
12 | IMAP wrapper that implements the Iloha IMAP Library (IIL) |
13 | See http://ilohamail.org/ for details |
15 +-----------------------------------------------------------------------+
16 | Author: Thomas Bruederli <roundcube@gmail.com> |
17 +-----------------------------------------------------------------------+
19 $Id: rcube_imap.php 2483 2009-05-15 10:22:29Z thomasb $
25 * Obtain classes from the Iloha IMAP library
27 require_once('lib/imap.inc');
28 require_once('lib/mime.inc');
29 require_once('lib/tnef_decoder.inc');
33 * Interface class for accessing an IMAP server
35 * This is a wrapper that implements the Iloha IMAP Library (IIL)
38 * @author Thomas Bruederli <roundcube@gmail.com>
40 * @link http://ilohamail.org
48 var $mailbox = 'INBOX';
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 $default_folders = array('INBOX');
57 var $default_folders_lc = array('inbox');
59 var $cache_keys = array();
60 var $cache_changes = array();
61 var $uid_id_map = array();
62 var $msg_headers = array();
63 var $skip_deleted = FALSE;
64 var $search_set = NULL;
65 var $search_string = '';
66 var $search_charset = '';
67 var $search_sort_field = '';
70 var $options = array('imap' => 'check');
76 * @param object DB Database connection
78 function __construct($db_conn)
85 * Connect to an IMAP server
87 * @param string Host to connect
88 * @param string Username for IMAP account
89 * @param string Password for IMAP account
90 * @param number Port to connect to
91 * @param string SSL schema (either ssl or tls) or null if plain connection
92 * @return boolean TRUE on success, FALSE on failure
95 function connect($host, $user, $pass, $port=143, $use_ssl=null)
97 global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
99 // check for Open-SSL support in PHP build
100 if ($use_ssl && extension_loaded('openssl'))
101 $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
104 raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
105 'message' => 'Open SSL not available;'), TRUE, FALSE);
110 $IMAP_USE_INTERNAL_DATE = false;
112 $this->conn = iil_Connect($host, $user, $pass, $this->options);
117 $this->ssl = $use_ssl;
119 // print trace mesages
120 if ($this->conn && ($this->debug_level & 8))
121 console($this->conn->message);
124 else if (!$this->conn && $GLOBALS['iil_error'])
126 $this->error_code = $GLOBALS['iil_errornum'];
127 raise_error(array('code' => 403,
129 'message' => $GLOBALS['iil_error']), TRUE, FALSE);
132 // get server properties
135 if (!empty($this->conn->delimiter))
136 $this->delimiter = $this->conn->delimiter;
137 if (!empty($this->conn->rootdir))
139 $this->set_rootdir($this->conn->rootdir);
140 $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
144 return $this->conn ? TRUE : FALSE;
149 * Close IMAP connection
150 * Usually done on script shutdown
157 iil_Close($this->conn);
162 * Close IMAP connection and re-connect
163 * This is used to avoid some strange socket errors when talking to Courier IMAP
170 $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
172 // issue SELECT command to restore connection status
174 iil_C_Select($this->conn, $this->mailbox);
178 * Set options to be used in iil_Connect()
180 function set_options($opt)
182 $this->options = array_merge($this->options, (array)$opt);
186 * Set a root folder for the IMAP connection.
188 * Only folders within this root folder will be displayed
189 * and all folder paths will be translated using this folder name
191 * @param string Root folder
194 function set_rootdir($root)
196 if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
197 $root = substr($root, 0, -1);
199 $this->root_dir = $root;
200 $this->options['rootdir'] = $root;
202 if (empty($this->delimiter))
203 $this->get_hierarchy_delimiter();
208 * Set default message charset
210 * This will be used for message decoding if a charset specification is not available
212 * @param string Charset string
215 function set_charset($cs)
217 $this->default_charset = $cs;
222 * This list of folders will be listed above all other folders
224 * @param array Indexed list of folder names
227 function set_default_mailboxes($arr)
231 $this->default_folders = $arr;
232 $this->default_folders_lc = array();
234 // add inbox if not included
235 if (!in_array_nocase('INBOX', $this->default_folders))
236 array_unshift($this->default_folders, 'INBOX');
238 // create a second list with lower cased names
239 foreach ($this->default_folders as $mbox)
240 $this->default_folders_lc[] = strtolower($mbox);
246 * Set internal mailbox reference.
248 * All operations will be perfomed on this mailbox/folder
250 * @param string Mailbox/Folder name
253 function set_mailbox($new_mbox)
255 $mailbox = $this->_mod_mailbox($new_mbox);
257 if ($this->mailbox == $mailbox)
260 $this->mailbox = $mailbox;
262 // clear messagecount cache for this mailbox
263 $this->_clear_messagecount($mailbox);
268 * Set internal list page
270 * @param number Page number to list
273 function set_page($page)
275 $this->list_page = (int)$page;
280 * Set internal page size
282 * @param number Number of messages to display on one page
285 function set_pagesize($size)
287 $this->page_size = (int)$size;
292 * Save a set of message ids for future message listing methods
294 * @param string IMAP Search query
295 * @param array List of message ids or NULL if empty
296 * @param string Charset of search string
297 * @param string Sorting field
299 function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null)
301 if (is_array($str) && $msgs == null)
302 list($str, $msgs, $charset, $sort_field) = $str;
303 if ($msgs != null && !is_array($msgs))
304 $msgs = split(',', $msgs);
306 $this->search_string = $str;
307 $this->search_set = $msgs;
308 $this->search_charset = $charset;
309 $this->search_sort_field = $sort_field;
314 * Return the saved search set as hash array
315 * @return array Search set
317 function get_search_set()
319 return array($this->search_string, $this->search_set, $this->search_charset, $this->search_sort_field);
324 * Returns the currently used mailbox name
326 * @return string Name of the mailbox/folder
329 function get_mailbox_name()
331 return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
336 * Returns the IMAP server's capability
338 * @param string Capability name
339 * @return mixed Capability value or TRUE if supported, FALSE if not
342 function get_capability($cap)
344 return iil_C_GetCapability($this->conn, strtoupper($cap));
349 * Checks the PERMANENTFLAGS capability of the current mailbox
350 * and returns true if the given flag is supported by the IMAP server
352 * @param string Permanentflag name
353 * @return mixed True if this flag is supported
356 function check_permflag($flag)
358 $flag = strtoupper($flag);
359 $imap_flag = $GLOBALS['IMAP_FLAGS'][$flag];
360 return (in_array_nocase($imap_flag, $this->conn->permanentflags));
365 * Returns the delimiter that is used by the IMAP server for folder separation
367 * @return string Delimiter string
370 function get_hierarchy_delimiter()
372 if ($this->conn && empty($this->delimiter))
373 $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
375 if (empty($this->delimiter))
376 $this->delimiter = '/';
378 return $this->delimiter;
383 * Public method for mailbox listing.
385 * Converts mailbox name with root dir first
387 * @param string Optional root folder
388 * @param string Optional filter for mailbox listing
389 * @return array List of mailboxes/folders
392 function list_mailboxes($root='', $filter='*')
395 $a_mboxes = $this->_list_mailboxes($root, $filter);
397 foreach ($a_mboxes as $mbox_row)
399 $name = $this->_mod_mailbox($mbox_row, 'out');
404 // INBOX should always be available
405 if (!in_array_nocase('INBOX', $a_out))
406 array_unshift($a_out, 'INBOX');
409 $a_out = $this->_sort_mailbox_list($a_out);
416 * Private method for mailbox listing
418 * @return array List of mailboxes/folders
419 * @see rcube_imap::list_mailboxes()
422 function _list_mailboxes($root='', $filter='*')
424 $a_defaults = $a_out = array();
426 // get cached folder list
427 $a_mboxes = $this->get_cache('mailboxes');
428 if (is_array($a_mboxes))
431 // retrieve list of folders from IMAP server
432 $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
434 if (!is_array($a_folders) || !sizeof($a_folders))
435 $a_folders = array();
437 // write mailboxlist to cache
438 $this->update_cache('mailboxes', $a_folders);
445 * Get message count for a specific mailbox
447 * @param string Mailbox/folder name
448 * @param string Mode for count [ALL|UNSEEN|RECENT]
449 * @param boolean Force reading from server and update cache
450 * @return int Number of messages
453 function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
455 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
456 return $this->_messagecount($mailbox, $mode, $force);
461 * Private method for getting nr of messages
464 * @see rcube_imap::messagecount()
466 function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
468 $a_mailbox_cache = FALSE;
469 $mode = strtoupper($mode);
472 $mailbox = $this->mailbox;
475 if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force)
476 return count((array)$this->search_set);
478 $a_mailbox_cache = $this->get_cache('messagecount');
480 // return cached value
481 if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
482 return $a_mailbox_cache[$mailbox][$mode];
484 // RECENT count is fetched a bit different
485 if ($mode == 'RECENT')
486 $count = iil_C_CheckForRecent($this->conn, $mailbox);
488 // use SEARCH for message counting
489 else if ($this->skip_deleted)
491 $search_str = "ALL UNDELETED";
493 // get message count and store in cache
494 if ($mode == 'UNSEEN')
495 $search_str .= " UNSEEN";
497 // get message count using SEARCH
498 // not very performant but more precise (using UNDELETED)
500 $index = $this->_search_index($mailbox, $search_str);
501 if (is_array($index))
503 $str = implode(",", $index);
505 $count = count($index);
510 if ($mode == 'UNSEEN')
511 $count = iil_C_CountUnseen($this->conn, $mailbox);
513 $count = iil_C_CountMessages($this->conn, $mailbox);
516 if (!is_array($a_mailbox_cache[$mailbox]))
517 $a_mailbox_cache[$mailbox] = array();
519 $a_mailbox_cache[$mailbox][$mode] = (int)$count;
521 // write back to cache
522 $this->update_cache('messagecount', $a_mailbox_cache);
529 * Public method for listing headers
530 * convert mailbox name with root dir first
532 * @param string Mailbox/folder name
533 * @param int Current page to list
534 * @param string Header field to sort by
535 * @param string Sort order [ASC|DESC]
536 * @return array Indexed array with message header objects
539 function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
541 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
542 return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
547 * Private method for listing message headers
550 * @see rcube_imap::list_headers
552 function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
554 if (!strlen($mailbox))
557 // use saved message set
558 if ($this->search_string && $mailbox == $this->mailbox)
559 return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order);
561 $this->_set_sort_order($sort_field, $sort_order);
563 $max = $this->_messagecount($mailbox);
564 $start_msg = ($this->list_page-1) * $this->page_size;
566 list($begin, $end) = $this->_get_message_range($max, $page);
572 $headers_sorted = FALSE;
573 $cache_key = $mailbox.'.msg';
574 $cache_status = $this->check_cache_status($mailbox, $cache_key);
576 // cache is OK, we can get all messages from local cache
579 $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
580 $headers_sorted = TRUE;
582 // cache is dirty, sync it
583 else if ($this->caching_enabled && $cache_status==-1 && !$recursive)
585 $this->sync_header_index($mailbox);
586 return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
590 // retrieve headers from IMAP
591 if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
593 $mymsgidx = array_slice ($msg_index, $begin, $end-$begin);
594 $msgs = join(",", $mymsgidx);
598 $msgs = sprintf("%d:%d", $begin+1, $end);
599 $msg_index = range($begin, $end);
603 // fetch reuested headers from server
604 $a_msg_headers = array();
605 $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
607 // delete cached messages with a higher index than $max+1
608 // Changed $max to $max+1 to fix this bug : #1484295
609 $this->clear_message_cache($cache_key, $max + 1);
611 // kick child process to sync cache
615 // return empty array if no messages found
616 if (!is_array($a_msg_headers) || empty($a_msg_headers)) {
620 // if not already sorted
621 if (!$headers_sorted)
623 // use this class for message sorting
624 $sorter = new rcube_header_sorter();
625 $sorter->set_sequence_numbers($msg_index);
626 $sorter->sort_headers($a_msg_headers);
628 if ($this->sort_order == 'DESC')
629 $a_msg_headers = array_reverse($a_msg_headers);
632 return array_values($a_msg_headers);
637 * Private method for listing a set of message headers (search results)
639 * @param string Mailbox/folder name
640 * @param int Current page to list
641 * @param string Header field to sort by
642 * @param string Sort order [ASC|DESC]
643 * @return array Indexed array with message header objects
645 * @see rcube_imap::list_header_set()
647 function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL)
649 if (!strlen($mailbox) || empty($this->search_set))
652 $msgs = $this->search_set;
653 $a_msg_headers = array();
654 $start_msg = ($this->list_page-1) * $this->page_size;
656 $this->_set_sort_order($sort_field, $sort_order);
658 // sorted messages, so we can first slice array and then fetch only wanted headers
659 if ($this->get_capability('sort')) // SORT searching result
661 // reset search set if sorting field has been changed
662 if ($this->sort_field && $this->search_sort_field != $this->sort_field)
664 $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
667 // return empty array if no messages found
671 if ($sort_order == 'DESC')
672 $msgs = array_reverse($msgs);
674 // get messages uids for one page
675 $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size));
678 $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL);
680 $sorter = new rcube_header_sorter();
681 $sorter->set_sequence_numbers($msgs);
682 $sorter->sort_headers($a_msg_headers);
684 return array_values($a_msg_headers);
686 else { // SEARCH searching result, need sorting
688 if ($cnt > 300 && $cnt > $this->page_size) { // experimantal value for best result
689 // use memory less expensive (and quick) method for big result set
690 $a_index = $this->message_index('', $this->sort_field, $this->sort_order);
691 // get messages uids for one page...
692 $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size));
693 // ...and fetch headers
694 $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
696 // return empty array if no messages found
697 if (!is_array($a_msg_headers) || empty($a_msg_headers))
700 $sorter = new rcube_header_sorter();
701 $sorter->set_sequence_numbers($msgs);
702 $sorter->sort_headers($a_msg_headers);
704 return array_values($a_msg_headers);
707 // for small result set we can fetch all messages headers
708 $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
710 // return empty array if no messages found
711 if (!is_array($a_msg_headers) || empty($a_msg_headers))
714 // if not already sorted
715 $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
717 // only return the requested part of the set
718 return array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size));
725 * Helper function to get first and last index of the requested set
727 * @param int message count
728 * @param mixed page number to show, or string 'all'
729 * @return array array with two values: first index, last index
732 function _get_message_range($max, $page)
734 $start_msg = ($this->list_page-1) * $this->page_size;
741 else if ($this->sort_order=='DESC')
743 $begin = $max - $this->page_size - $start_msg;
744 $end = $max - $start_msg;
749 $end = $start_msg + $this->page_size;
752 if ($begin < 0) $begin = 0;
753 if ($end < 0) $end = $max;
754 if ($end > $max) $end = $max;
756 return array($begin, $end);
762 * Fetches message headers
765 * @param string Mailbox name
766 * @param string Message index to fetch
767 * @param array Reference to message headers array
768 * @param array Array with cache index
769 * @return int Number of deleted messages
772 function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
774 // cache is incomplete
775 $cache_index = $this->get_message_cache_index($cache_key);
777 // fetch reuested headers from server
778 $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
781 if (!empty($a_header_index))
783 foreach ($a_header_index as $i => $headers)
785 if ($headers->deleted && $this->skip_deleted)
788 if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
789 $this->remove_message_cache($cache_key, $headers->id);
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);
799 $a_msg_headers[$headers->uid] = $headers;
803 return $deleted_count;
808 * Return sorted array of message IDs (not UIDs)
810 * @param string Mailbox to get index from
811 * @param string Sort column
812 * @param string Sort order [ASC, DESC]
813 * @return array Indexed array with message ids
815 function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
817 $this->_set_sort_order($sort_field, $sort_order);
819 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
820 $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi";
822 // we have a saved search result. get index from there
823 if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox)
825 $this->cache[$key] = array();
827 if ($this->get_capability('sort'))
829 if ($this->sort_field && $this->search_sort_field != $this->sort_field)
830 $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
832 if ($this->sort_order == 'DESC')
833 $this->cache[$key] = array_reverse($this->search_set);
835 $this->cache[$key] = $this->search_set;
839 $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field);
841 if ($this->sort_order=="ASC")
843 else if ($this->sort_order=="DESC")
846 $this->cache[$key] = array_keys($a_index);
850 // have stored it in RAM
851 if (isset($this->cache[$key]))
852 return $this->cache[$key];
855 $cache_key = $mailbox.'.msg';
856 $cache_status = $this->check_cache_status($mailbox, $cache_key);
861 $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
862 return array_keys($a_index);
865 // fetch complete message index
866 $msg_count = $this->_messagecount($mailbox);
867 if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '')))
869 if ($this->sort_order == 'DESC')
870 $a_index = array_reverse($a_index);
872 $this->cache[$key] = $a_index;
876 $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
878 if ($this->sort_order=="ASC")
880 else if ($this->sort_order=="DESC")
883 $this->cache[$key] = array_keys($a_index);
886 return $this->cache[$key];
893 function sync_header_index($mailbox)
895 $cache_key = $mailbox.'.msg';
896 $cache_index = $this->get_message_cache_index($cache_key);
897 $msg_count = $this->_messagecount($mailbox);
899 // fetch complete message index
900 $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
902 foreach ($a_message_index as $id => $uid)
904 // message in cache at correct position
905 if ($cache_index[$id] == $uid)
907 unset($cache_index[$id]);
911 // message in cache but in wrong position
912 if (in_array((string)$uid, $cache_index, TRUE))
914 unset($cache_index[$id]);
917 // other message at this position
918 if (isset($cache_index[$id]))
920 $this->remove_message_cache($cache_key, $id);
921 unset($cache_index[$id]);
925 // fetch complete headers and add to cache
926 $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
927 $this->add_message_cache($cache_key, $headers->id, $headers);
930 // those ids that are still in cache_index have been deleted
931 if (!empty($cache_index))
933 foreach ($cache_index as $id => $uid)
934 $this->remove_message_cache($cache_key, $id);
940 * Invoke search request to IMAP server
942 * @param string mailbox name to search in
943 * @param string search string
944 * @param string search string charset
945 * @param string header field to sort by
946 * @return array search results as list of message ids
949 function search($mbox_name='', $str=NULL, $charset=NULL, $sort_field=NULL)
954 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
956 $results = $this->_search_index($mailbox, $str, $charset, $sort_field);
958 // try search with US-ASCII charset (should be supported by server)
959 // only if UTF-8 search is not supported
960 if (empty($results) && !is_array($results) && !empty($charset) && $charset!='US-ASCII')
962 // convert strings to US_ASCII
963 if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE))
965 $last = 0; $res = '';
966 foreach($matches[1] as $m)
968 $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
969 $string = substr($str, $string_offset - 1, $m[0]);
970 $string = rcube_charset_convert($string, $charset, 'US-ASCII');
971 if (!$string) continue;
972 $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string);
973 $last = $m[0] + $string_offset - 1;
975 if ($last < strlen($str))
976 $res .= substr($str, $last, strlen($str)-$last);
978 else // strings for conversion not found
981 $results = $this->search($mbox_name, $res, 'US-ASCII', $sort_field);
984 $this->set_search_set($str, $results, $charset, $sort_field);
991 * Private search method
993 * @return array search results as list of message ids
995 * @see rcube_imap::search()
997 function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL)
999 if ($sort_field && $this->get_capability('sort'))
1001 $charset = $charset ? $charset : $this->default_charset;
1002 $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, FALSE, $charset);
1005 $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria);
1007 // clean message list (there might be some empty entries)
1008 if (is_array($a_messages))
1010 foreach ($a_messages as $i => $val)
1012 unset($a_messages[$i]);
1020 * Refresh saved search set
1022 * @return array Current search set
1024 function refresh_search()
1026 if (!empty($this->search_string))
1027 $this->search_set = $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
1029 return $this->get_search_set();
1034 * Check if the given message ID is part of the current search set
1036 * @return boolean True on match or if no search request is stored
1038 function in_searchset($msgid)
1040 if (!empty($this->search_string))
1041 return in_array("$msgid", (array)$this->search_set, true);
1048 * Return message headers object of a specific message
1050 * @param int Message ID
1051 * @param string Mailbox to read from
1052 * @param boolean True if $id is the message UID
1053 * @param boolean True if we need also BODYSTRUCTURE in headers
1054 * @return object Message headers representation
1056 function get_headers($id, $mbox_name=NULL, $is_uid=TRUE, $bodystr=FALSE)
1058 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1059 $uid = $is_uid ? $id : $this->_id2uid($id);
1061 // get cached headers
1062 if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
1065 $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr);
1067 // write headers cache
1070 if ($headers->uid && $headers->id)
1071 $this->uid_id_map[$mailbox][$headers->uid] = $headers->id;
1073 $this->add_message_cache($mailbox.'.msg', $headers->id, $headers);
1081 * Fetch body structure from the IMAP server and build
1082 * an object structure similar to the one generated by PEAR::Mail_mimeDecode
1084 * @param int Message UID to fetch
1085 * @param string Message BODYSTRUCTURE string (optional)
1086 * @return object rcube_message_part Message part tree or False on failure
1088 function &get_structure($uid, $structure_str='')
1090 $cache_key = $this->mailbox.'.msg';
1091 $headers = &$this->get_cached_message($cache_key, $uid, true);
1093 // return cached message structure
1094 if (is_object($headers) && is_object($headers->structure)) {
1095 return $headers->structure;
1098 // resolve message sequence number
1099 if (!($msg_id = $this->_uid2id($uid))) {
1103 if (!$structure_str)
1104 $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
1105 $structure = iml_GetRawStructureArray($structure_str);
1108 // parse structure and add headers
1109 if (!empty($structure))
1111 $this->_msg_id = $msg_id;
1112 $headers = $this->get_headers($uid);
1114 $struct = &$this->_structure_part($structure);
1115 $struct->headers = get_object_vars($headers);
1117 // don't trust given content-type
1118 if (empty($struct->parts) && !empty($struct->headers['ctype']))
1120 $struct->mime_id = '1';
1121 $struct->mimetype = strtolower($struct->headers['ctype']);
1122 list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
1125 // write structure to cache
1126 if ($this->caching_enabled)
1127 $this->add_message_cache($cache_key, $msg_id, $headers, $struct);
1135 * Build message part object
1139 function &_structure_part($part, $count=0, $parent='', $raw_headers=null)
1141 $struct = new rcube_message_part;
1142 $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
1145 if (is_array($part[0]))
1147 $struct->ctype_primary = 'multipart';
1149 // find first non-array entry
1150 for ($i=1; $i<count($part); $i++)
1151 if (!is_array($part[$i]))
1153 $struct->ctype_secondary = strtolower($part[$i]);
1157 $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
1159 // build parts list for headers pre-fetching
1160 for ($i=0, $count=0; $i<count($part); $i++)
1161 if (is_array($part[$i]) && count($part[$i]) > 3)
1162 // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
1163 if (strtolower($part[$i][0]) == 'message' ||
1164 (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL'))) {
1165 $part_headers[] = $struct->mime_id ? $struct->mime_id.'.'.$i+1 : $i+1;
1168 // pre-fetch headers of all parts (in one command for better performance)
1170 $part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, $part_headers);
1172 $struct->parts = array();
1173 for ($i=0, $count=0; $i<count($part); $i++)
1174 if (is_array($part[$i]) && count($part[$i]) > 3)
1175 $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
1176 $part_headers[$struct->mime_id ? $struck->mime_id.'.'.$i+1 : $i+1]);
1183 $struct->ctype_primary = strtolower($part[0]);
1184 $struct->ctype_secondary = strtolower($part[1]);
1185 $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
1187 // read content type parameters
1188 if (is_array($part[2]))
1190 $struct->ctype_parameters = array();
1191 for ($i=0; $i<count($part[2]); $i+=2)
1192 $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
1194 if (isset($struct->ctype_parameters['charset']))
1195 $struct->charset = $struct->ctype_parameters['charset'];
1198 // read content encoding
1199 if (!empty($part[5]) && $part[5]!='NIL')
1201 $struct->encoding = strtolower($part[5]);
1202 $struct->headers['content-transfer-encoding'] = $struct->encoding;
1206 if (!empty($part[6]) && $part[6]!='NIL')
1207 $struct->size = intval($part[6]);
1209 // read part disposition
1210 $di = count($part) - 2;
1211 if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
1212 (is_array($part[--$di]) && count($part[$di]) == 2))
1214 $struct->disposition = strtolower($part[$di][0]);
1216 if (is_array($part[$di][1]))
1217 for ($n=0; $n<count($part[$di][1]); $n+=2)
1218 $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
1222 if (is_array($part[8]) && $di != 8)
1224 $struct->parts = array();
1225 for ($i=0, $count=0; $i<count($part[8]); $i++)
1226 if (is_array($part[8][$i]) && count($part[8][$i]) > 5)
1227 $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
1231 if (!empty($part[3]) && $part[3]!='NIL')
1233 $struct->content_id = $part[3];
1234 $struct->headers['content-id'] = $part[3];
1236 if (empty($struct->disposition))
1237 $struct->disposition = 'inline';
1240 // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
1241 if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
1242 if (empty($raw_headers))
1243 $raw_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id);
1244 $struct->headers = $this->_parse_headers($raw_headers) + $struct->headers;
1247 if ($struct->ctype_primary=='message') {
1248 if (is_array($part[8]) && empty($struct->parts))
1249 $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
1252 // normalize filename property
1253 $this->_set_part_filename($struct, $raw_headers);
1260 * Set attachment filename from message part structure
1263 * @param object rcube_message_part Part object
1264 * @param string Part's raw headers
1266 function _set_part_filename(&$part, $headers=null)
1268 if (!empty($part->d_parameters['filename']))
1269 $filename_mime = $part->d_parameters['filename'];
1270 else if (!empty($part->d_parameters['filename*']))
1271 $filename_encoded = $part->d_parameters['filename*'];
1272 else if (!empty($part->ctype_parameters['name*']))
1273 $filename_encoded = $part->ctype_parameters['name*'];
1274 // RFC2231 value continuations
1275 // TODO: this should be rewrited to support RFC2231 4.1 combinations
1276 else if (!empty($part->d_parameters['filename*0'])) {
1278 while (isset($part->d_parameters['filename*'.$i])) {
1279 $filename_mime .= $part->d_parameters['filename*'.$i];
1282 // some servers (eg. dovecot-1.x) have no support for parameter value continuations
1283 // we must fetch and parse headers "manually"
1286 $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
1287 $filename_mime = '';
1289 while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1290 $filename_mime .= $matches[1];
1295 else if (!empty($part->d_parameters['filename*0*'])) {
1297 while (isset($part->d_parameters['filename*'.$i.'*'])) {
1298 $filename_encoded .= $part->d_parameters['filename*'.$i.'*'];
1303 $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
1304 $filename_encoded = '';
1305 $i = 0; $matches = array();
1306 while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1307 $filename_encoded .= $matches[1];
1312 else if (!empty($part->ctype_parameters['name*0'])) {
1314 while (isset($part->ctype_parameters['name*'.$i])) {
1315 $filename_mime .= $part->ctype_parameters['name*'.$i];
1320 $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
1321 $filename_mime = '';
1322 $i = 0; $matches = array();
1323 while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1324 $filename_mime .= $matches[1];
1329 else if (!empty($part->ctype_parameters['name*0*'])) {
1331 while (isset($part->ctype_parameters['name*'.$i.'*'])) {
1332 $filename_encoded .= $part->ctype_parameters['name*'.$i.'*'];
1337 $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $part->mime_id);
1338 $filename_encoded = '';
1339 $i = 0; $matches = array();
1340 while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
1341 $filename_encoded .= $matches[1];
1346 // read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird)
1347 else if (!empty($part->ctype_parameters['name']))
1348 $filename_mime = $part->ctype_parameters['name'];
1349 // Content-Disposition
1350 else if (!empty($part->headers['content-description']))
1351 $filename_mime = $part->headers['content-description'];
1356 if (!empty($filename_mime)) {
1357 $part->filename = rcube_imap::decode_mime_string($filename_mime,
1358 $part->charset ? $part->charset : rc_detect_encoding($filename_mime, $this->default_charset));
1360 else if (!empty($filename_encoded)) {
1361 // decode filename according to RFC 2231, Section 4
1362 if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) {
1363 $filename_charset = $fmatches[1];
1364 $filename_encoded = $fmatches[2];
1366 $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset);
1372 * Fetch message body of a specific message from the server
1374 * @param int Message UID
1375 * @param string Part number
1376 * @param object rcube_message_part Part object created by get_structure()
1377 * @param mixed True to print part, ressource to write part contents in
1378 * @param resource File pointer to save the message part
1379 * @return string Message/part body if not printed
1381 function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL)
1383 if (!($msg_id = $this->_uid2id($uid)))
1386 // get part encoding if not provided
1387 if (!is_object($o_part))
1389 $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
1390 $structure = iml_GetRawStructureArray($structure_str);
1391 $part_type = iml_GetPartTypeCode($structure, $part);
1392 $o_part = new rcube_message_part;
1393 $o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other');
1394 $o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part));
1395 $o_part->charset = iml_GetPartCharset($structure, $part);
1398 // TODO: Add caching for message parts
1400 if (!$part) $part = 'TEXT';
1404 $mode = $o_part->encoding == 'base64' ? 3 : ($o_part->encoding == 'quoted-printable' ? 1 : 2);
1405 $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, $mode);
1407 // we have to decode the part manually before printing
1410 echo $this->mime_decode($body, $o_part->encoding);
1416 if ($fp && $o_part->encoding == 'base64')
1417 return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 3, $fp);
1419 $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1);
1422 if ($o_part->encoding)
1423 $body = $this->mime_decode($body, $o_part->encoding);
1425 // convert charset (if text or message part)
1426 if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message')
1428 // assume default if no charset specified
1429 if (empty($o_part->charset))
1430 $o_part->charset = $this->default_charset;
1432 $body = rcube_charset_convert($body, $o_part->charset);
1447 * Fetch message body of a specific message from the server
1449 * @param int Message UID
1450 * @return string Message/part body
1451 * @see rcube_imap::get_message_part()
1453 function &get_body($uid, $part=1)
1455 $headers = $this->get_headers($uid);
1456 return rcube_charset_convert(
1457 $this->mime_decode($this->get_message_part($uid, $part), 'quoted-printable'),
1458 $headers->charset ? $headers->charset : $this->default_charset);
1463 * Returns the whole message source as string
1465 * @param int Message UID
1466 * @return string Message source string
1468 function &get_raw_body($uid)
1470 if (!($msg_id = $this->_uid2id($uid)))
1473 return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id);
1478 * Returns the message headers as string
1480 * @param int Message UID
1481 * @return string Message headers string
1483 function &get_raw_headers($uid)
1485 if (!($msg_id = $this->_uid2id($uid)))
1488 $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
1495 * Sends the whole message source to stdout
1497 * @param int Message UID
1499 function print_raw_body($uid)
1501 if (!($msg_id = $this->_uid2id($uid)))
1504 iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
1509 * Set message flag to one or several messages
1511 * @param mixed Message UIDs as array or as comma-separated string
1512 * @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
1513 * @return boolean True on success, False on failure
1515 function set_flag($uids, $flag)
1517 $flag = strtoupper($flag);
1519 if (!is_array($uids))
1520 $uids = explode(',',$uids);
1522 foreach ($uids as $uid) {
1523 $msg_ids[$uid] = $this->_uid2id($uid);
1526 if ($flag=='UNDELETED')
1527 $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
1528 else if ($flag=='UNSEEN')
1529 $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
1530 else if ($flag=='UNFLAGGED')
1531 $result = iil_C_UnFlag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), 'FLAGGED');
1533 $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
1535 // reload message headers if cached
1536 $cache_key = $this->mailbox.'.msg';
1537 if ($this->caching_enabled)
1539 foreach ($msg_ids as $uid => $id)
1541 if ($cached_headers = $this->get_cached_message($cache_key, $uid))
1543 $this->remove_message_cache($cache_key, $id);
1544 //$this->get_headers($uid);
1548 // close and re-open connection
1549 // this prevents connection problems with Courier
1553 // set nr of messages that were flaged
1554 $count = count($msg_ids);
1556 // clear message count cache
1557 if ($result && $flag=='SEEN')
1558 $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
1559 else if ($result && $flag=='UNSEEN')
1560 $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
1561 else if ($result && $flag=='DELETED')
1562 $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
1569 * Append a mail message (source) to a specific mailbox
1571 * @param string Target mailbox
1572 * @param string Message source
1573 * @return boolean True on success, False on error
1575 function save_message($mbox_name, &$message)
1577 $mailbox = $this->_mod_mailbox($mbox_name);
1579 // make sure mailbox exists
1580 if (($mailbox == 'INBOX') || in_array($mailbox, $this->_list_mailboxes()))
1581 $saved = iil_C_Append($this->conn, $mailbox, $message);
1585 // increase messagecount of the target mailbox
1586 $this->_set_messagecount($mailbox, 'ALL', 1);
1594 * Move a message from one mailbox to another
1596 * @param string List of UIDs to move, separated by comma
1597 * @param string Target mailbox
1598 * @param string Source mailbox
1599 * @return boolean True on success, False on error
1601 function move_message($uids, $to_mbox, $from_mbox='')
1603 $to_mbox = $this->_mod_mailbox($to_mbox);
1604 $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
1606 // make sure mailbox exists
1607 if ($to_mbox != 'INBOX' && !in_array($to_mbox, $this->_list_mailboxes()))
1609 if (in_array($to_mbox_in, $this->default_folders))
1610 $this->create_mailbox($to_mbox_in, TRUE);
1615 // convert the list of uids to array
1616 $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1618 // exit if no message uids are specified
1619 if (!is_array($a_uids))
1622 // convert uids to message ids
1624 foreach ($a_uids as $uid)
1625 $a_mids[] = $this->_uid2id($uid, $from_mbox);
1627 $iil_move = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
1628 $moved = !($iil_move === false || $iil_move < 0);
1630 // send expunge command in order to have the moved message
1631 // really deleted from the source mailbox
1633 // but only when flag_for_deletion is set to false
1634 if (!rcmail::get_instance()->config->get('flag_for_deletion', false))
1636 $this->_expunge($from_mbox, FALSE);
1637 $this->_clear_messagecount($from_mbox);
1638 $this->_clear_messagecount($to_mbox);
1642 else if (rcmail::get_instance()->config->get('delete_always', false)) {
1643 return iil_C_Delete($this->conn, $from_mbox, join(',', $a_mids));
1646 // remove message ids from search set
1647 if ($moved && $this->search_set && $from_mbox == $this->mailbox)
1648 $this->search_set = array_diff($this->search_set, $a_mids);
1650 // update cached message headers
1651 $cache_key = $from_mbox.'.msg';
1652 if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1654 $start_index = 100000;
1655 foreach ($a_uids as $uid)
1657 if (($index = array_search($uid, $a_cache_index)) !== FALSE)
1658 $start_index = min($index, $start_index);
1661 // clear cache from the lowest index on
1662 $this->clear_message_cache($cache_key, $start_index);
1670 * Mark messages as deleted and expunge mailbox
1672 * @param string List of UIDs to move, separated by comma
1673 * @param string Source mailbox
1674 * @return boolean True on success, False on error
1676 function delete_message($uids, $mbox_name='')
1678 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1680 // convert the list of uids to array
1681 $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1683 // exit if no message uids are specified
1684 if (!is_array($a_uids))
1687 // convert uids to message ids
1689 foreach ($a_uids as $uid)
1690 $a_mids[] = $this->_uid2id($uid, $mailbox);
1692 $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1694 // send expunge command in order to have the deleted message
1695 // really deleted from the mailbox
1698 $this->_expunge($mailbox, FALSE);
1699 $this->_clear_messagecount($mailbox);
1700 unset($this->uid_id_map[$mailbox]);
1703 // remove message ids from search set
1704 if ($deleted && $this->search_set && $mailbox == $this->mailbox)
1705 $this->search_set = array_diff($this->search_set, $a_mids);
1707 // remove deleted messages from cache
1708 $cache_key = $mailbox.'.msg';
1709 if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1711 $start_index = 100000;
1712 foreach ($a_uids as $uid)
1714 if (($index = array_search($uid, $a_cache_index)) !== FALSE)
1715 $start_index = min($index, $start_index);
1718 // clear cache from the lowest index on
1719 $this->clear_message_cache($cache_key, $start_index);
1727 * Clear all messages in a specific mailbox
1729 * @param string Mailbox name
1730 * @return int Above 0 on success
1732 function clear_mailbox($mbox_name=NULL)
1734 $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1735 $msg_count = $this->_messagecount($mailbox, 'ALL');
1739 $cleared = iil_C_ClearFolder($this->conn, $mailbox);
1741 // make sure the message count cache is cleared as well
1744 $this->clear_message_cache($mailbox.'.msg');
1745 $a_mailbox_cache = $this->get_cache('messagecount');
1746 unset($a_mailbox_cache[$mailbox]);
1747 $this->update_cache('messagecount', $a_mailbox_cache);
1758 * Send IMAP expunge command and clear cache
1760 * @param string Mailbox name
1761 * @param boolean False if cache should not be cleared
1762 * @return boolean True on success
1764 function expunge($mbox_name='', $clear_cache=TRUE)
1766 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1767 return $this->_expunge($mailbox, $clear_cache);
1772 * Send IMAP expunge command and clear cache
1774 * @see rcube_imap::expunge()
1777 function _expunge($mailbox, $clear_cache=TRUE)
1779 $result = iil_C_Expunge($this->conn, $mailbox);
1781 if ($result>=0 && $clear_cache)
1783 $this->clear_message_cache($mailbox.'.msg');
1784 $this->_clear_messagecount($mailbox);
1791 /* --------------------------------
1793 * --------------------------------*/
1797 * Get a list of all folders available on the IMAP server
1799 * @param string IMAP root dir
1800 * @return array Indexed array with folder names
1802 function list_unsubscribed($root='')
1804 static $sa_unsubscribed;
1806 if (is_array($sa_unsubscribed))
1807 return $sa_unsubscribed;
1809 // retrieve list of folders from IMAP server
1810 $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1812 // modify names with root dir
1813 foreach ($a_mboxes as $mbox_name)
1815 $name = $this->_mod_mailbox($mbox_name, 'out');
1817 $a_folders[] = $name;
1820 // filter folders and sort them
1821 $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1822 return $sa_unsubscribed;
1827 * Get mailbox quota information
1830 * @return mixed Quota info or False if not supported
1832 function get_quota()
1834 if ($this->get_capability('QUOTA'))
1835 return iil_C_GetQuota($this->conn);
1842 * Subscribe to a specific mailbox(es)
1844 * @param array Mailbox name(s)
1845 * @return boolean True on success
1847 function subscribe($a_mboxes)
1849 if (!is_array($a_mboxes))
1850 $a_mboxes = array($a_mboxes);
1852 // let this common function do the main work
1853 return $this->_change_subscription($a_mboxes, 'subscribe');
1858 * Unsubscribe mailboxes
1860 * @param array Mailbox name(s)
1861 * @return boolean True on success
1863 function unsubscribe($a_mboxes)
1865 if (!is_array($a_mboxes))
1866 $a_mboxes = array($a_mboxes);
1868 // let this common function do the main work
1869 return $this->_change_subscription($a_mboxes, 'unsubscribe');
1874 * Create a new mailbox on the server and register it in local cache
1876 * @param string New mailbox name (as utf-7 string)
1877 * @param boolean True if the new mailbox should be subscribed
1878 * @param string Name of the created mailbox, false on error
1880 function create_mailbox($name, $subscribe=FALSE)
1884 // reduce mailbox name to 100 chars
1885 $name = substr($name, 0, 100);
1887 $abs_name = $this->_mod_mailbox($name);
1888 $a_mailbox_cache = $this->get_cache('mailboxes');
1890 if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
1891 $result = iil_C_CreateFolder($this->conn, $abs_name);
1893 // try to subscribe it
1894 if ($result && $subscribe)
1895 $this->subscribe($name);
1897 return $result ? $name : FALSE;
1902 * Set a new name to an existing mailbox
1904 * @param string Mailbox to rename (as utf-7 string)
1905 * @param string New mailbox name (as utf-7 string)
1906 * @return string Name of the renames mailbox, False on error
1908 function rename_mailbox($mbox_name, $new_name)
1912 // encode mailbox name and reduce it to 100 chars
1913 $name = substr($new_name, 0, 100);
1915 // make absolute path
1916 $mailbox = $this->_mod_mailbox($mbox_name);
1917 $abs_name = $this->_mod_mailbox($name);
1919 // check if mailbox is subscribed
1920 $a_subscribed = $this->_list_mailboxes();
1921 $subscribed = in_array($mailbox, $a_subscribed);
1923 // unsubscribe folder
1925 iil_C_UnSubscribe($this->conn, $mailbox);
1927 if (strlen($abs_name))
1928 $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
1932 $delm = $this->get_hierarchy_delimiter();
1934 // check if mailbox children are subscribed
1935 foreach ($a_subscribed as $c_subscribed)
1936 if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed))
1938 iil_C_UnSubscribe($this->conn, $c_subscribed);
1939 iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed));
1943 $this->clear_message_cache($mailbox.'.msg');
1944 $this->clear_cache('mailboxes');
1947 // try to subscribe it
1948 if ($result && $subscribed)
1949 iil_C_Subscribe($this->conn, $abs_name);
1951 return $result ? $name : FALSE;
1956 * Remove mailboxes from server
1958 * @param string Mailbox name
1959 * @return boolean True on success
1961 function delete_mailbox($mbox_name)
1965 if (is_array($mbox_name))
1966 $a_mboxes = $mbox_name;
1967 else if (is_string($mbox_name) && strlen($mbox_name))
1968 $a_mboxes = explode(',', $mbox_name);
1970 $all_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1972 if (is_array($a_mboxes))
1973 foreach ($a_mboxes as $mbox_name)
1975 $mailbox = $this->_mod_mailbox($mbox_name);
1977 // unsubscribe mailbox before deleting
1978 iil_C_UnSubscribe($this->conn, $mailbox);
1980 // send delete command to server
1981 $result = iil_C_DeleteFolder($this->conn, $mailbox);
1985 foreach ($all_mboxes as $c_mbox)
1987 $regex = preg_quote($mailbox . $this->delimiter, '/');
1988 $regex = '/^' . $regex . '/';
1989 if (preg_match($regex, $c_mbox))
1991 iil_C_UnSubscribe($this->conn, $c_mbox);
1992 $result = iil_C_DeleteFolder($this->conn, $c_mbox);
1999 // clear mailboxlist cache
2002 $this->clear_message_cache($mailbox.'.msg');
2003 $this->clear_cache('mailboxes');
2011 * Create all folders specified as default
2013 function create_default_folders()
2015 $a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
2016 $a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
2018 // create default folders if they do not exist
2019 foreach ($this->default_folders as $folder)
2021 $abs_name = $this->_mod_mailbox($folder);
2022 if (!in_array_nocase($abs_name, $a_folders))
2023 $this->create_mailbox($folder, TRUE);
2024 else if (!in_array_nocase($abs_name, $a_subscribed))
2025 $this->subscribe($folder);
2031 /* --------------------------------
2032 * internal caching methods
2033 * --------------------------------*/
2038 function set_caching($set)
2040 if ($set && is_object($this->db))
2041 $this->caching_enabled = TRUE;
2043 $this->caching_enabled = FALSE;
2049 function get_cache($key)
2052 if (!isset($this->cache[$key]) && $this->caching_enabled)
2054 $cache_data = $this->_read_cache_record('IMAP.'.$key);
2055 $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
2058 return $this->cache[$key];
2064 function update_cache($key, $data)
2066 $this->cache[$key] = $data;
2067 $this->cache_changed = TRUE;
2068 $this->cache_changes[$key] = TRUE;
2074 function write_cache()
2076 if ($this->caching_enabled && $this->cache_changed)
2078 foreach ($this->cache as $key => $data)
2080 if ($this->cache_changes[$key])
2081 $this->_write_cache_record('IMAP.'.$key, serialize($data));
2089 function clear_cache($key=NULL)
2091 if (!$this->caching_enabled)
2096 foreach ($this->cache as $key => $data)
2097 $this->_clear_cache_record('IMAP.'.$key);
2099 $this->cache = array();
2100 $this->cache_changed = FALSE;
2101 $this->cache_changes = array();
2105 $this->_clear_cache_record('IMAP.'.$key);
2106 $this->cache_changes[$key] = FALSE;
2107 unset($this->cache[$key]);
2114 function _read_cache_record($key)
2116 $cache_data = FALSE;
2120 // get cached data from DB
2121 $sql_result = $this->db->query(
2122 "SELECT cache_id, data
2123 FROM ".get_table_name('cache')."
2126 $_SESSION['user_id'],
2129 if ($sql_arr = $this->db->fetch_assoc($sql_result))
2131 $cache_data = $sql_arr['data'];
2132 $this->cache_keys[$key] = $sql_arr['cache_id'];
2142 function _write_cache_record($key, $data)
2147 // check if we already have a cache entry for this key
2148 if (!isset($this->cache_keys[$key]))
2150 $sql_result = $this->db->query(
2152 FROM ".get_table_name('cache')."
2155 $_SESSION['user_id'],
2158 if ($sql_arr = $this->db->fetch_assoc($sql_result))
2159 $this->cache_keys[$key] = $sql_arr['cache_id'];
2161 $this->cache_keys[$key] = FALSE;
2164 // update existing cache record
2165 if ($this->cache_keys[$key])
2168 "UPDATE ".get_table_name('cache')."
2169 SET created=". $this->db->now().", data=?
2173 $_SESSION['user_id'],
2176 // add new cache record
2180 "INSERT INTO ".get_table_name('cache')."
2181 (created, user_id, cache_key, data)
2182 VALUES (".$this->db->now().", ?, ?, ?)",
2183 $_SESSION['user_id'],
2192 function _clear_cache_record($key)
2195 "DELETE FROM ".get_table_name('cache')."
2198 $_SESSION['user_id'],
2204 /* --------------------------------
2205 * message caching methods
2206 * --------------------------------*/
2210 * Checks if the cache is up-to-date
2212 * @param string Mailbox name
2213 * @param string Internal cache key
2214 * @return int -3 = off, -2 = incomplete, -1 = dirty
2216 function check_cache_status($mailbox, $cache_key)
2218 if (!$this->caching_enabled)
2221 $cache_index = $this->get_message_cache_index($cache_key, TRUE);
2222 $msg_count = $this->_messagecount($mailbox);
2223 $cache_count = count($cache_index);
2225 // console("Cache check: $msg_count !== ".count($cache_index));
2227 if ($cache_count==$msg_count)
2229 // get highest index
2230 $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
2231 $cache_uid = array_pop($cache_index);
2233 // uids of highest message matches -> cache seems OK
2234 if ($cache_uid == $header->uid)
2240 // if cache count differs less than 10% report as dirty
2241 else if (abs($msg_count - $cache_count) < $msg_count/10)
2250 function get_message_cache($key, $from, $to, $sort_field, $sort_order)
2252 $cache_key = "$key:$from:$to:$sort_field:$sort_order";
2253 $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
2255 if (!in_array($sort_field, $db_header_fields))
2256 $sort_field = 'idx';
2258 if ($this->caching_enabled && !isset($this->cache[$cache_key]))
2260 $this->cache[$cache_key] = array();
2261 $sql_result = $this->db->limitquery(
2262 "SELECT idx, uid, headers
2263 FROM ".get_table_name('messages')."
2266 ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
2267 strtoupper($sort_order),
2270 $_SESSION['user_id'],
2273 while ($sql_arr = $this->db->fetch_assoc($sql_result))
2275 $uid = $sql_arr['uid'];
2276 $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
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);
2284 return $this->cache[$cache_key];
2290 function &get_cached_message($key, $uid, $struct=false)
2292 $internal_key = '__single_msg';
2294 if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) ||
2295 ($struct && empty($this->cache[$internal_key][$uid]->structure))))
2297 $sql_select = "idx, uid, headers" . ($struct ? ", structure" : '');
2298 $sql_result = $this->db->query(
2300 FROM ".get_table_name('messages')."
2304 $_SESSION['user_id'],
2308 if ($sql_arr = $this->db->fetch_assoc($sql_result))
2310 $this->cache[$internal_key][$uid] = unserialize($sql_arr['headers']);
2311 if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure']))
2312 $this->cache[$internal_key][$uid]->structure = unserialize($sql_arr['structure']);
2316 return $this->cache[$internal_key][$uid];
2322 function get_message_cache_index($key, $force=FALSE, $sort_field='idx', $sort_order='ASC')
2324 static $sa_message_index = array();
2326 // empty key -> empty array
2327 if (!$this->caching_enabled || empty($key))
2330 if (!empty($sa_message_index[$key]) && !$force)
2331 return $sa_message_index[$key];
2333 $sa_message_index[$key] = array();
2334 $sql_result = $this->db->query(
2336 FROM ".get_table_name('messages')."
2339 ORDER BY ".$this->db->quote_identifier($sort_field)." ".$sort_order,
2340 $_SESSION['user_id'],
2343 while ($sql_arr = $this->db->fetch_assoc($sql_result))
2344 $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
2346 return $sa_message_index[$key];
2352 function add_message_cache($key, $index, $headers, $struct=null)
2354 if (empty($key) || !is_object($headers) || empty($headers->uid))
2357 // add to internal (fast) cache
2358 $this->cache['__single_msg'][$headers->uid] = $headers;
2359 $this->cache['__single_msg'][$headers->uid]->structure = $struct;
2361 // no further caching
2362 if (!$this->caching_enabled)
2365 // check for an existing record (probly headers are cached but structure not)
2366 $sql_result = $this->db->query(
2368 FROM ".get_table_name('messages')."
2373 $_SESSION['user_id'],
2377 // update cache record
2378 if ($sql_arr = $this->db->fetch_assoc($sql_result))
2381 "UPDATE ".get_table_name('messages')."
2382 SET idx=?, headers=?, structure=?
2383 WHERE message_id=?",
2385 serialize($headers),
2386 is_object($struct) ? serialize($struct) : NULL,
2387 $sql_arr['message_id']
2390 else // insert new record
2393 "INSERT INTO ".get_table_name('messages')."
2394 (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers, structure)
2395 VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?, ?)",
2396 $_SESSION['user_id'],
2400 (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
2401 (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
2402 (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
2403 (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
2404 (int)$headers->size,
2405 serialize($headers),
2406 is_object($struct) ? serialize($struct) : NULL
2414 function remove_message_cache($key, $index)
2416 if (!$this->caching_enabled)
2420 "DELETE FROM ".get_table_name('messages')."
2424 $_SESSION['user_id'],
2432 function clear_message_cache($key, $start_index=1)
2434 if (!$this->caching_enabled)
2438 "DELETE FROM ".get_table_name('messages')."
2442 $_SESSION['user_id'],
2450 /* --------------------------------
2451 * encoding/decoding methods
2452 * --------------------------------*/
2455 * Split an address list into a structured array list
2457 * @param string Input string
2458 * @param int List only this number of addresses
2459 * @param boolean Decode address strings
2460 * @return array Indexed list of addresses
2462 function decode_address_list($input, $max=null, $decode=true)
2464 $a = $this->_parse_address_list($input, $decode);
2466 // Special chars as defined by RFC 822 need to in quoted string (or escaped).
2467 $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
2475 foreach ($a as $val)
2478 $address = $val['address'];
2479 $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
2480 if ($name && $address && $name != $address)
2481 $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
2487 $out[$j] = array('name' => $name,
2488 'mailto' => $address,
2489 'string' => $string);
2491 if ($max && $j==$max)
2500 * Decode a Microsoft Outlook TNEF part (winmail.dat)
2502 * @param object rcube_message_part Message part to decode
2503 * @param string UID of the message
2504 * @return array List of rcube_message_parts extracted from windmail.dat
2506 function tnef_decode(&$part, $uid)
2508 if (!isset($part->body))
2509 $part->body = $this->get_message_part($uid, $part->mime_id, $part);
2512 $tnef_parts = array();
2513 $tnef_arr = tnef_decode($part->body);
2514 foreach ($tnef_arr as $winatt) {
2515 $tpart = new rcube_message_part;
2516 $tpart->filename = $winatt["name"];
2517 $tpart->encoding = 'stream';
2518 $tpart->ctype_primary = $winatt["type0"];
2519 $tpart->ctype_secondary = $winatt["type1"];
2520 $tpart->mimetype = strtolower($winatt["type0"] . "/" . $winatt["type1"]);
2521 $tpart->mime_id = "winmail." . $part->mime_id . ".$pid";
2522 $tpart->size = $winatt["size"];
2523 $tpart->body = $winatt['stream'];
2525 $tnef_parts[] = $tpart;
2534 * Decode a message header value
2536 * @param string Header value
2537 * @param boolean Remove quotes if necessary
2538 * @return string Decoded string
2540 function decode_header($input, $remove_quotes=FALSE)
2542 $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset);
2543 if ($str{0}=='"' && $remove_quotes)
2544 $str = str_replace('"', '', $str);
2551 * Decode a mime-encoded string to internal charset
2553 * @param string $input Header value
2554 * @param string $fallback Fallback charset if none specified
2556 * @return string Decoded string
2559 public static function decode_mime_string($input, $fallback=null)
2561 // Initialize variable
2564 // Iterate instead of recursing, this way if there are too many values we don't have stack overflows
2565 // rfc: all line breaks or other characters not found
2566 // in the Base64 Alphabet must be ignored by decoding software
2567 // delete all blanks between MIME-lines, differently we can
2568 // receive unnecessary blanks and broken utf-8 symbols
2569 $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
2571 // Check if there is stuff to decode
2572 if (strpos($input, '=?') !== false) {
2573 // Loop through the string to decode all occurences of =? ?= into the variable $out
2574 while(($pos = strpos($input, '=?')) !== false) {
2575 // Append everything that is before the text to be decoded
2576 $out .= substr($input, 0, $pos);
2578 // Get the location of the text to decode
2579 $end_cs_pos = strpos($input, "?", $pos+2);
2580 $end_en_pos = strpos($input, "?", $end_cs_pos+1);
2581 $end_pos = strpos($input, "?=", $end_en_pos+1);
2583 // Extract the encoded string
2584 $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
2585 // Extract the remaining string
2586 $input = substr($input, $end_pos+2);
2588 // Decode the string fragement
2589 $out .= rcube_imap::_decode_mime_string_part($encstr);
2592 // Deocde the rest (if any)
2593 if (strlen($input) != 0)
2594 $out .= rcube_imap::decode_mime_string($input, $fallback);
2596 // return the results
2600 // no encoding information, use fallback
2601 return rcube_charset_convert($input,
2602 !empty($fallback) ? $fallback : rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'));
2607 * Decode a part of a mime-encoded string
2611 function _decode_mime_string_part($str)
2613 $a = explode('?', $str);
2616 // should be in format "charset?encoding?base64_string"
2619 for ($i=2; $i<$count; $i++)
2622 if (($a[1]=="B")||($a[1]=="b"))
2623 $rest = base64_decode($rest);
2624 else if (($a[1]=="Q")||($a[1]=="q"))
2626 $rest = str_replace("_", " ", $rest);
2627 $rest = quoted_printable_decode($rest);
2630 return rcube_charset_convert($rest, $a[0]);
2633 return $str; // we dont' know what to do with this
2638 * Decode a mime part
2640 * @param string Input string
2641 * @param string Part encoding
2642 * @return string Decoded string
2645 function mime_decode($input, $encoding='7bit')
2647 switch (strtolower($encoding))
2653 case 'quoted-printable':
2654 return quoted_printable_decode($input);
2658 return base64_decode($input);
2668 * Convert body charset to UTF-8 according to the ctype_parameters
2670 * @param string Part body to decode
2671 * @param string Charset to convert from
2672 * @return string Content converted to internal charset
2674 function charset_decode($body, $ctype_param)
2676 if (is_array($ctype_param) && !empty($ctype_param['charset']))
2677 return rcube_charset_convert($body, $ctype_param['charset']);
2679 // defaults to what is specified in the class header
2680 return rcube_charset_convert($body, $this->default_charset);
2685 * Translate UID to message ID
2687 * @param int Message UID
2688 * @param string Mailbox name
2689 * @return int Message ID
2691 function get_id($uid, $mbox_name=NULL)
2693 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
2694 return $this->_uid2id($uid, $mailbox);
2699 * Translate message number to UID
2701 * @param int Message ID
2702 * @param string Mailbox name
2703 * @return int Message UID
2705 function get_uid($id,$mbox_name=NULL)
2707 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
2708 return $this->_id2uid($id, $mailbox);
2713 /* --------------------------------
2715 * --------------------------------*/
2721 function _mod_mailbox($mbox_name, $mode='in')
2723 if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
2726 if (!empty($this->root_dir) && $mode=='in')
2727 $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
2728 else if (strlen($this->root_dir) && $mode=='out')
2729 $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
2735 * Validate the given input and save to local properties
2738 function _set_sort_order($sort_field, $sort_order)
2740 if ($sort_field != null)
2741 $this->sort_field = asciiwords($sort_field);
2742 if ($sort_order != null)
2743 $this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
2747 * Sort mailboxes first by default folders and then in alphabethical order
2750 function _sort_mailbox_list($a_folders)
2752 $a_out = $a_defaults = $folders = array();
2754 $delimiter = $this->get_hierarchy_delimiter();
2756 // find default folders and skip folders starting with '.'
2757 foreach ($a_folders as $i => $folder)
2759 if ($folder{0}=='.')
2762 if (($p = array_search(strtolower($folder), $this->default_folders_lc)) !== false && !$a_defaults[$p])
2763 $a_defaults[$p] = $folder;
2765 $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7'));
2768 // sort folders and place defaults on the top
2769 asort($folders, SORT_LOCALE_STRING);
2771 $folders = array_merge($a_defaults, array_keys($folders));
2773 // finally we must rebuild the list to move
2774 // subfolders of default folders to their place...
2775 // ...also do this for the rest of folders because
2776 // asort() is not properly sorting case sensitive names
2777 while (list($key, $folder) = each($folders)) {
2778 // set the type of folder name variable (#1485527)
2779 $a_out[] = (string) $folder;
2780 unset($folders[$key]);
2781 $this->_rsort($folder, $delimiter, $folders, $a_out);
2791 function _rsort($folder, $delimiter, &$list, &$out)
2793 while (list($key, $name) = each($list)) {
2794 if (strpos($name, $folder.$delimiter) === 0) {
2795 // set the type of folder name variable (#1485527)
2796 $out[] = (string) $name;
2798 $this->_rsort($name, $delimiter, $list, $out);
2808 function _uid2id($uid, $mbox_name=NULL)
2811 $mbox_name = $this->mailbox;
2813 if (!isset($this->uid_id_map[$mbox_name][$uid]))
2814 $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
2816 return $this->uid_id_map[$mbox_name][$uid];
2822 function _id2uid($id, $mbox_name=NULL)
2825 $mbox_name = $this->mailbox;
2827 $index = array_flip((array)$this->uid_id_map[$mbox_name]);
2828 if (isset($index[$id]))
2832 $uid = iil_C_ID2UID($this->conn, $mbox_name, $id);
2833 $this->uid_id_map[$mbox_name][$uid] = $id;
2841 * Subscribe/unsubscribe a list of mailboxes and update local cache
2844 function _change_subscription($a_mboxes, $mode)
2848 if (is_array($a_mboxes))
2849 foreach ($a_mboxes as $i => $mbox_name)
2851 $mailbox = $this->_mod_mailbox($mbox_name);
2852 $a_mboxes[$i] = $mailbox;
2854 if ($mode=='subscribe')
2855 $result = iil_C_Subscribe($this->conn, $mailbox);
2856 else if ($mode=='unsubscribe')
2857 $result = iil_C_UnSubscribe($this->conn, $mailbox);
2863 // get cached mailbox list
2866 $a_mailbox_cache = $this->get_cache('mailboxes');
2867 if (!is_array($a_mailbox_cache))
2870 // modify cached list
2871 if ($mode=='subscribe')
2872 $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
2873 else if ($mode=='unsubscribe')
2874 $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
2876 // write mailboxlist to cache
2877 $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
2885 * Increde/decrese messagecount for a specific mailbox
2888 function _set_messagecount($mbox_name, $mode, $increment)
2890 $a_mailbox_cache = FALSE;
2891 $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
2892 $mode = strtoupper($mode);
2894 $a_mailbox_cache = $this->get_cache('messagecount');
2896 if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
2899 // add incremental value to messagecount
2900 $a_mailbox_cache[$mailbox][$mode] += $increment;
2902 // there's something wrong, delete from cache
2903 if ($a_mailbox_cache[$mailbox][$mode] < 0)
2904 unset($a_mailbox_cache[$mailbox][$mode]);
2906 // write back to cache
2907 $this->update_cache('messagecount', $a_mailbox_cache);
2914 * Remove messagecount of a specific mailbox from cache
2917 function _clear_messagecount($mbox_name='')
2919 $a_mailbox_cache = FALSE;
2920 $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
2922 $a_mailbox_cache = $this->get_cache('messagecount');
2924 if (is_array($a_mailbox_cache[$mailbox]))
2926 unset($a_mailbox_cache[$mailbox]);
2927 $this->update_cache('messagecount', $a_mailbox_cache);
2933 * Split RFC822 header string into an associative array
2936 function _parse_headers($headers)
2938 $a_headers = array();
2939 $lines = explode("\n", $headers);
2941 for ($i=0; $i<$c; $i++)
2943 if ($p = strpos($lines[$i], ': '))
2945 $field = strtolower(substr($lines[$i], 0, $p));
2946 $value = trim(substr($lines[$i], $p+1));
2948 $a_headers[$field] = $value;
2959 function _parse_address_list($str, $decode=true)
2961 // remove any newlines and carriage returns before
2962 $a = rcube_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
2965 foreach ($a as $key => $val)
2967 $val = preg_replace("/([\"\w])</", "$1 <", $val);
2968 $sub_a = rcube_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val);
2969 $result[$key]['name'] = '';
2971 foreach ($sub_a as $k => $v)
2973 // use angle brackets in regexp to not handle names with @ sign
2974 if (preg_match('/^<\S+@\S+>$/', $v))
2975 $result[$key]['address'] = trim($v, '<>');
2977 $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
2980 if (empty($result[$key]['name']))
2981 $result[$key]['name'] = $result[$key]['address'];
2982 elseif (empty($result[$key]['address']))
2983 $result[$key]['address'] = $result[$key]['name'];
2989 } // end class rcube_imap
2993 * Class representing a message part
2997 class rcube_message_part
3000 var $ctype_primary = 'text';
3001 var $ctype_secondary = 'plain';
3002 var $mimetype = 'text/plain';
3003 var $disposition = '';
3005 var $encoding = '8bit';
3008 var $headers = array();
3009 var $d_parameters = array();
3010 var $ctype_parameters = array();
3016 * Class for sorting an array of iilBasicHeader objects in a predetermined order.
3019 * @author Eric Stadtherr
3021 class rcube_header_sorter
3023 var $sequence_numbers = array();
3026 * Set the predetermined sort order.
3028 * @param array Numerically indexed array of IMAP message sequence numbers
3030 function set_sequence_numbers($seqnums)
3032 $this->sequence_numbers = array_flip($seqnums);
3036 * Sort the array of header objects
3038 * @param array Array of iilBasicHeader objects indexed by UID
3040 function sort_headers(&$headers)
3043 * uksort would work if the keys were the sequence number, but unfortunately
3044 * the keys are the UIDs. We'll use uasort instead and dereference the value
3045 * to get the sequence number (in the "id" field).
3047 * uksort($headers, array($this, "compare_seqnums"));
3049 uasort($headers, array($this, "compare_seqnums"));
3053 * Sort method called by uasort()
3055 function compare_seqnums($a, $b)
3057 // First get the sequence number from the header object (the 'id' field).
3061 // then find each sequence number in my ordered list
3062 $posa = isset($this->sequence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1;
3063 $posb = isset($this->sequence_numbers[$seqb]) ? intval($this->sequence_numbers[$seqb]) : -1;
3065 // return the relative position as the comparison value
3066 return $posa - $posb;
3072 * Add quoted-printable encoding to a given string
3074 * @param string String to encode
3075 * @param int Add new line after this number of characters
3076 * @param boolean True if spaces should be converted into =20
3077 * @return string Encoded string
3079 function quoted_printable_encode($input, $line_max=76, $space_conv=false)
3081 $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
3082 $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
3087 while( list(, $line) = each($lines))
3089 //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
3090 $linlen = strlen($line);
3092 for($i = 0; $i < $linlen; $i++)
3094 $c = substr( $line, $i, 1 );
3096 if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
3102 if ( $i == ( $linlen - 1 ) ) // convert space at eol only
3106 else if ( $space_conv )
3111 else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) // always encode "\t", which is *not* required
3113 $h2 = floor($dec/16);
3114 $h1 = floor($dec%16);
3115 $c = $escape.$hex["$h2"].$hex["$h1"];
3118 if ( (strlen($newline) + strlen($c)) >= $line_max ) // CRLF is not counted
3120 $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
3122 // check if newline first character will be point or not
3130 $output .= $newline.$eol;
3133 return trim($output);