4 +-----------------------------------------------------------------------+
5 | program/include/rcube_imap.inc |
7 | This file is part of the RoundCube Webmail client |
8 | Copyright (C) 2005, 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.inc 293 2006-08-04 13:56:08Z 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/utf7.inc');
33 * Interface class for accessing an IMAP server
35 * This is a wrapper that implements the Iloha IMAP Library (IIL)
37 * @package RoundCube Webmail
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_folders = array('INBOX');
56 var $default_folders_lc = array('inbox');
58 var $cache_keys = array();
59 var $cache_changes = array();
60 var $uid_id_map = array();
61 var $msg_headers = array();
62 var $capabilities = array();
63 var $skip_deleted = FALSE;
70 * @param object Database connection
72 function __construct($db_conn)
79 * PHP 4 object constructor
81 * @see rcube_imap::__construct
83 function rcube_imap($db_conn)
85 $this->__construct($db_conn);
90 * Connect to an IMAP server
92 * @param string Host to connect
93 * @param string Username for IMAP account
94 * @param string Password for IMAP account
95 * @param number Port to connect to
96 * @param boolean Use SSL connection
97 * @return boolean TRUE on success, FALSE on failure
100 function connect($host, $user, $pass, $port=143, $use_ssl=FALSE)
102 global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
104 // check for Open-SSL support in PHP build
105 if ($use_ssl && in_array('openssl', get_loaded_extensions()))
109 raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
110 'message' => 'Open SSL not available;'), TRUE, FALSE);
115 $IMAP_USE_INTERNAL_DATE = false;
117 $this->conn = iil_Connect($host, $user, $pass, array('imap' => 'check'));
122 $this->ssl = $use_ssl;
124 // print trace mesages
125 if ($this->conn && ($this->debug_level & 8))
126 console($this->conn->message);
129 else if (!$this->conn && $GLOBALS['iil_error'])
131 raise_error(array('code' => 403,
133 'message' => $GLOBALS['iil_error']), TRUE, FALSE);
136 // get account namespace
139 $this->_parse_capability($this->conn->capability);
140 iil_C_NameSpace($this->conn);
142 if (!empty($this->conn->delimiter))
143 $this->delimiter = $this->conn->delimiter;
144 if (!empty($this->conn->rootdir))
146 $this->set_rootdir($this->conn->rootdir);
147 $this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
151 return $this->conn ? TRUE : FALSE;
156 * Close IMAP connection
157 * Usually done on script shutdown
164 iil_Close($this->conn);
169 * Close IMAP connection and re-connect
170 * This is used to avoid some strange socket errors when talking to Courier IMAP
177 $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
182 * Set a root folder for the IMAP connection.
184 * Only folders within this root folder will be displayed
185 * and all folder paths will be translated using this folder name
187 * @param string Root folder
190 function set_rootdir($root)
192 if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
193 $root = substr($root, 0, -1);
195 $this->root_dir = $root;
197 if (empty($this->delimiter))
198 $this->get_hierarchy_delimiter();
203 * This list of folders will be listed above all other folders
205 * @param array Indexed list of folder names
208 function set_default_mailboxes($arr)
212 $this->default_folders = $arr;
213 $this->default_folders_lc = array();
215 // add inbox if not included
216 if (!in_array_nocase('INBOX', $this->default_folders))
217 array_unshift($this->default_folders, 'INBOX');
219 // create a second list with lower cased names
220 foreach ($this->default_folders as $mbox)
221 $this->default_folders_lc[] = strtolower($mbox);
227 * Set internal mailbox reference.
229 * All operations will be perfomed on this mailbox/folder
231 * @param string Mailbox/Folder name
234 function set_mailbox($new_mbox)
236 $mailbox = $this->_mod_mailbox($new_mbox);
238 if ($this->mailbox == $mailbox)
241 $this->mailbox = $mailbox;
243 // clear messagecount cache for this mailbox
244 $this->_clear_messagecount($mailbox);
249 * Set internal list page
251 * @param number Page number to list
254 function set_page($page)
256 $this->list_page = (int)$page;
261 * Set internal page size
263 * @param number Number of messages to display on one page
266 function set_pagesize($size)
268 $this->page_size = (int)$size;
273 * Returns the currently used mailbox name
275 * @return string Name of the mailbox/folder
278 function get_mailbox_name()
280 return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
285 * Returns the IMAP server's capability
287 * @param string Capability name
288 * @return mixed Capability value or TRUE if supported, FALSE if not
291 function get_capability($cap)
293 $cap = strtoupper($cap);
294 return $this->capabilities[$cap];
299 * Returns the delimiter that is used by the IMAP server for folder separation
301 * @return string Delimiter string
304 function get_hierarchy_delimiter()
306 if ($this->conn && empty($this->delimiter))
307 $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
309 if (empty($this->delimiter))
310 $this->delimiter = '/';
312 return $this->delimiter;
317 * Public method for mailbox listing.
319 * Converts mailbox name with root dir first
321 * @param string Optional root folder
322 * @param string Optional filter for mailbox listing
323 * @return array List of mailboxes/folders
326 function list_mailboxes($root='', $filter='*')
329 $a_mboxes = $this->_list_mailboxes($root, $filter);
331 foreach ($a_mboxes as $mbox_row)
333 $name = $this->_mod_mailbox($mbox_row, 'out');
338 // INBOX should always be available
339 if (!in_array_nocase('INBOX', $a_out))
340 array_unshift($a_out, 'INBOX');
343 $a_out = $this->_sort_mailbox_list($a_out);
350 * Private method for mailbox listing
352 * @return array List of mailboxes/folders
354 * @see rcube_imap::list_mailboxes
356 function _list_mailboxes($root='', $filter='*')
358 $a_defaults = $a_out = array();
360 // get cached folder list
361 $a_mboxes = $this->get_cache('mailboxes');
362 if (is_array($a_mboxes))
365 // retrieve list of folders from IMAP server
366 $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
368 if (!is_array($a_folders) || !sizeof($a_folders))
369 $a_folders = array();
371 // write mailboxlist to cache
372 $this->update_cache('mailboxes', $a_folders);
379 * Get message count for a specific mailbox
381 * @param string Mailbox/folder name
382 * @param string Mode for count [ALL|UNSEEN|RECENT]
383 * @param boolean Force reading from server and update cache
384 * @return number Number of messages
387 function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
389 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
390 return $this->_messagecount($mailbox, $mode, $force);
395 * Private method for getting nr of messages
398 * @see rcube_imap::messagecount
400 function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
402 $a_mailbox_cache = FALSE;
403 $mode = strtoupper($mode);
406 $mailbox = $this->mailbox;
408 $a_mailbox_cache = $this->get_cache('messagecount');
410 // return cached value
411 if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
412 return $a_mailbox_cache[$mailbox][$mode];
414 // RECENT count is fetched abit different
415 if ($mode == 'RECENT')
416 $count = iil_C_CheckForRecent($this->conn, $mailbox);
418 // use SEARCH for message counting
419 else if ($this->skip_deleted)
421 $search_str = "ALL UNDELETED";
423 // get message count and store in cache
424 if ($mode == 'UNSEEN')
425 $search_str .= " UNSEEN";
427 // get message count using SEARCH
428 // not very performant but more precise (using UNDELETED)
430 $index = $this->_search_index($mailbox, $search_str);
431 if (is_array($index))
433 $str = implode(",", $index);
435 $count = count($index);
440 if ($mode == 'UNSEEN')
441 $count = iil_C_CountUnseen($this->conn, $mailbox);
443 $count = iil_C_CountMessages($this->conn, $mailbox);
446 if (!is_array($a_mailbox_cache[$mailbox]))
447 $a_mailbox_cache[$mailbox] = array();
449 $a_mailbox_cache[$mailbox][$mode] = (int)$count;
451 // write back to cache
452 $this->update_cache('messagecount', $a_mailbox_cache);
459 * Public method for listing headers
460 * convert mailbox name with root dir first
462 * @param string Mailbox/folder name
463 * @param number Current page to list
464 * @param string Header field to sort by
465 * @param string Sort order [ASC|DESC]
466 * @return array Indexed array with message header objects
469 function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
471 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
472 return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
477 * Private method for listing message headers
480 * @see rcube_imap::list_headers
482 function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
484 if (!strlen($mailbox))
487 if ($sort_field!=NULL)
488 $this->sort_field = $sort_field;
489 if ($sort_order!=NULL)
490 $this->sort_order = strtoupper($sort_order);
492 $max = $this->_messagecount($mailbox);
493 $start_msg = ($this->list_page-1) * $this->page_size;
495 list($begin, $end) = $this->_get_message_range($max, $page);
501 $headers_sorted = FALSE;
502 $cache_key = $mailbox.'.msg';
503 $cache_status = $this->check_cache_status($mailbox, $cache_key);
505 // cache is OK, we can get all messages from local cache
508 $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
509 $headers_sorted = TRUE;
511 // cache is dirty, sync it
512 else if ($this->caching_enabled && $cache_status==-1 && !$recursive)
514 $this->sync_header_index($mailbox);
515 return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
519 // retrieve headers from IMAP
520 if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
522 $msgs = $msg_index[$begin];
523 for ($i=$begin+1; $i < $end; $i++)
524 $msgs = $msgs.','.$msg_index[$i];
528 $msgs = sprintf("%d:%d", $begin+1, $end);
531 for ($msg_seqnum = $begin; $msg_seqnum <= $end; $msg_seqnum++)
532 $msg_index[$i++] = $msg_seqnum;
535 // use this class for message sorting
536 $sorter = new rcube_header_sorter();
537 $sorter->set_sequence_numbers($msg_index);
539 // fetch reuested headers from server
540 $a_msg_headers = array();
541 $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
543 // delete cached messages with a higher index than $max
544 $this->clear_message_cache($cache_key, $max);
547 // kick child process to sync cache
553 // return empty array if no messages found
554 if (!is_array($a_msg_headers) || empty($a_msg_headers))
558 // if not already sorted
559 if (!$headers_sorted)
561 $sorter->sort_headers($a_msg_headers);
563 if ($this->sort_order == 'DESC')
564 $a_msg_headers = array_reverse($a_msg_headers);
567 return array_values($a_msg_headers);
573 * Public method for listing a specific set of headers
574 * convert mailbox name with root dir first
576 * @param string Mailbox/folder name
577 * @param array List of message ids to list
578 * @param number Current page to list
579 * @param string Header field to sort by
580 * @param string Sort order [ASC|DESC]
581 * @return array Indexed array with message header objects
584 function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
586 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
587 return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);
592 * Private method for listing a set of message headers
595 * @see rcube_imap::list_header_set
597 function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
599 // also accept a comma-separated list of message ids
600 if (is_string($msgs))
601 $msgs = split(',', $msgs);
603 if (!strlen($mailbox) || empty($msgs))
606 if ($sort_field!=NULL)
607 $this->sort_field = $sort_field;
608 if ($sort_order!=NULL)
609 $this->sort_order = strtoupper($sort_order);
612 $start_msg = ($this->list_page-1) * $this->page_size;
614 // fetch reuested headers from server
615 $a_msg_headers = array();
616 $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
618 // return empty array if no messages found
619 if (!is_array($a_msg_headers) || empty($a_msg_headers))
622 // if not already sorted
623 $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
625 // only return the requested part of the set
626 return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
631 * Helper function to get first and last index of the requested set
633 * @param number message count
634 * @param mixed page number to show, or string 'all'
635 * @return array array with two values: first index, last index
638 function _get_message_range($max, $page)
640 $start_msg = ($this->list_page-1) * $this->page_size;
647 else if ($this->sort_order=='DESC')
649 $begin = $max - $this->page_size - $start_msg;
650 $end = $max - $start_msg;
655 $end = $start_msg + $this->page_size;
658 if ($begin < 0) $begin = 0;
659 if ($end < 0) $end = $max;
660 if ($end > $max) $end = $max;
662 return array($begin, $end);
668 * Fetches message headers
671 * @param string Mailbox name
672 * @param string Message index to fetch
673 * @param array Reference to message headers array
674 * @param array Array with cache index
675 * @return number Number of deleted messages
678 function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
680 // cache is incomplete
681 $cache_index = $this->get_message_cache_index($cache_key);
683 // fetch reuested headers from server
684 $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
687 if (!empty($a_header_index))
689 foreach ($a_header_index as $i => $headers)
691 if ($headers->deleted && $this->skip_deleted)
694 if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
695 $this->remove_message_cache($cache_key, $headers->id);
701 // add message to cache
702 if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
703 $this->add_message_cache($cache_key, $headers->id, $headers);
705 $a_msg_headers[$headers->uid] = $headers;
709 return $deleted_count;
713 // return sorted array of message UIDs
714 function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
716 if ($sort_field!=NULL)
717 $this->sort_field = $sort_field;
718 if ($sort_order!=NULL)
719 $this->sort_order = strtoupper($sort_order);
721 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
722 $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi";
724 // have stored it in RAM
725 if (isset($this->cache[$key]))
726 return $this->cache[$key];
729 $cache_key = $mailbox.'.msg';
730 $cache_status = $this->check_cache_status($mailbox, $cache_key);
735 $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
736 return array_values($a_index);
740 // fetch complete message index
741 $msg_count = $this->_messagecount($mailbox);
742 if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
744 if ($this->sort_order == 'DESC')
745 $a_index = array_reverse($a_index);
747 $this->cache[$key] = $a_index;
752 $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
753 $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
755 if ($this->sort_order=="ASC")
757 else if ($this->sort_order=="DESC")
761 $this->cache[$key] = array();
762 foreach ($a_index as $index => $value)
763 $this->cache[$key][$i++] = $a_uids[$index];
766 return $this->cache[$key];
770 function sync_header_index($mailbox)
772 $cache_key = $mailbox.'.msg';
773 $cache_index = $this->get_message_cache_index($cache_key);
774 $msg_count = $this->_messagecount($mailbox);
776 // fetch complete message index
777 $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
779 foreach ($a_message_index as $id => $uid)
781 // message in cache at correct position
782 if ($cache_index[$id] == $uid)
784 // console("$id / $uid: OK");
785 unset($cache_index[$id]);
789 // message in cache but in wrong position
790 if (in_array((string)$uid, $cache_index, TRUE))
792 // console("$id / $uid: Moved");
793 unset($cache_index[$id]);
796 // other message at this position
797 if (isset($cache_index[$id]))
799 // console("$id / $uid: Delete");
800 $this->remove_message_cache($cache_key, $id);
801 unset($cache_index[$id]);
805 // console("$id / $uid: Add");
807 // fetch complete headers and add to cache
808 $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
809 $this->add_message_cache($cache_key, $headers->id, $headers);
812 // those ids that are still in cache_index have been deleted
813 if (!empty($cache_index))
815 foreach ($cache_index as $id => $uid)
816 $this->remove_message_cache($cache_key, $id);
822 * Invoke search request to IMAP server
824 * @param string mailbox name to search in
825 * @param string search criteria (ALL, TO, FROM, SUBJECT, etc)
826 * @param string search string
827 * @return array search results as list of message ids
830 function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
832 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
833 if ($str && $criteria)
835 $search = (!empty($charset) ? "CHARSET $charset " : '') . sprintf("%s {%d}\r\n%s", $criteria, strlen($str), $str);
836 $results = $this->_search_index($mailbox, $search);
838 // try search without charset (probably not supported by server)
840 $results = $this->_search_index($mailbox, "$criteria $str");
845 return $this->_search_index($mailbox, $criteria);
850 * Private search method
852 * @return array search results as list of message ids
854 * @see rcube_imap::search()
856 function _search_index($mailbox, $criteria='ALL')
858 $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
859 // clean message list (there might be some empty entries)
860 if (is_array($a_messages))
862 foreach ($a_messages as $i => $val)
864 unset($a_messages[$i]);
871 function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
873 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
875 // get cached headers
876 if ($is_uid && ($headers = $this->get_cached_message($mailbox.'.msg', $id)))
879 $msg_id = $is_uid ? $this->_uid2id($id) : $id;
880 $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id);
882 // write headers cache
884 $this->add_message_cache($mailbox.'.msg', $msg_id, $headers);
890 function get_body($uid, $part=1)
892 if (!($msg_id = $this->_uid2id($uid)))
895 $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
896 $structure = iml_GetRawStructureArray($structure_str);
897 $body = iil_C_FetchPartBody($this->conn, $this->mailbox, $msg_id, $part);
899 $encoding = iml_GetPartEncodingCode($structure, $part);
901 if ($encoding==3) $body = $this->mime_decode($body, 'base64');
902 else if ($encoding==4) $body = $this->mime_decode($body, 'quoted-printable');
908 function get_raw_body($uid)
910 if (!($msg_id = $this->_uid2id($uid)))
913 $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
914 $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
920 // set message flag to one or several messages
921 // possible flags are: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT
922 function set_flag($uids, $flag)
924 $flag = strtoupper($flag);
926 if (!is_array($uids))
927 $uids = explode(',',$uids);
929 foreach ($uids as $uid) {
930 $msg_ids[$uid] = $this->_uid2id($uid);
933 if ($flag=='UNDELETED')
934 $result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
935 else if ($flag=='UNSEEN')
936 $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
938 $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
940 // reload message headers if cached
941 $cache_key = $this->mailbox.'.msg';
942 if ($this->caching_enabled)
944 foreach ($msg_ids as $uid => $id)
946 if ($cached_headers = $this->get_cached_message($cache_key, $uid))
948 $this->remove_message_cache($cache_key, $id);
949 //$this->get_headers($uid);
953 // close and re-open connection
954 // this prevents connection problems with Courier
958 // set nr of messages that were flaged
959 $count = count($msg_ids);
961 // clear message count cache
962 if ($result && $flag=='SEEN')
963 $this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
964 else if ($result && $flag=='UNSEEN')
965 $this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
966 else if ($result && $flag=='DELETED')
967 $this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
973 // append a mail message (source) to a specific mailbox
974 function save_message($mbox_name, &$message)
976 $mbox_name = stripslashes($mbox_name);
977 $mailbox = $this->_mod_mailbox($mbox_name);
979 // make sure mailbox exists
980 if (in_array($mailbox, $this->_list_mailboxes()))
981 $saved = iil_C_Append($this->conn, $mailbox, $message);
985 // increase messagecount of the target mailbox
986 $this->_set_messagecount($mailbox, 'ALL', 1);
993 // move a message from one mailbox to another
994 function move_message($uids, $to_mbox, $from_mbox='')
996 $to_mbox = stripslashes($to_mbox);
997 $from_mbox = stripslashes($from_mbox);
998 $to_mbox = $this->_mod_mailbox($to_mbox);
999 $from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
1001 // make sure mailbox exists
1002 if (!in_array($to_mbox, $this->_list_mailboxes()))
1004 if (in_array(strtolower($to_mbox), $this->default_folders))
1005 $this->create_mailbox($to_mbox, TRUE);
1010 // convert the list of uids to array
1011 $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1013 // exit if no message uids are specified
1014 if (!is_array($a_uids))
1017 // convert uids to message ids
1019 foreach ($a_uids as $uid)
1020 $a_mids[] = $this->_uid2id($uid, $from_mbox);
1022 $moved = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
1024 // send expunge command in order to have the moved message
1025 // really deleted from the source mailbox
1028 $this->_expunge($from_mbox, FALSE);
1029 $this->_clear_messagecount($from_mbox);
1030 $this->_clear_messagecount($to_mbox);
1033 // update cached message headers
1034 $cache_key = $from_mbox.'.msg';
1035 if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1037 $start_index = 100000;
1038 foreach ($a_uids as $uid)
1040 if(($index = array_search($uid, $a_cache_index)) !== FALSE)
1041 $start_index = min($index, $start_index);
1044 // clear cache from the lowest index on
1045 $this->clear_message_cache($cache_key, $start_index);
1052 // mark messages as deleted and expunge mailbox
1053 function delete_message($uids, $mbox_name='')
1055 $mbox_name = stripslashes($mbox_name);
1056 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1058 // convert the list of uids to array
1059 $a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
1061 // exit if no message uids are specified
1062 if (!is_array($a_uids))
1066 // convert uids to message ids
1068 foreach ($a_uids as $uid)
1069 $a_mids[] = $this->_uid2id($uid, $mailbox);
1071 $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
1073 // send expunge command in order to have the deleted message
1074 // really deleted from the mailbox
1077 $this->_expunge($mailbox, FALSE);
1078 $this->_clear_messagecount($mailbox);
1081 // remove deleted messages from cache
1082 $cache_key = $mailbox.'.msg';
1083 if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
1085 $start_index = 100000;
1086 foreach ($a_uids as $uid)
1088 $index = array_search($uid, $a_cache_index);
1089 $start_index = min($index, $start_index);
1092 // clear cache from the lowest index on
1093 $this->clear_message_cache($cache_key, $start_index);
1100 // clear all messages in a specific mailbox
1101 function clear_mailbox($mbox_name=NULL)
1103 $mbox_name = stripslashes($mbox_name);
1104 $mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1105 $msg_count = $this->_messagecount($mailbox, 'ALL');
1109 $cleared = iil_C_ClearFolder($this->conn, $mailbox);
1111 // make sure the message count cache is cleared as well
1114 $this->clear_message_cache($mailbox.'.msg');
1115 $a_mailbox_cache = $this->get_cache('messagecount');
1116 unset($a_mailbox_cache[$mailbox]);
1117 $this->update_cache('messagecount', $a_mailbox_cache);
1127 // send IMAP expunge command and clear cache
1128 function expunge($mbox_name='', $clear_cache=TRUE)
1130 $mbox_name = stripslashes($mbox_name);
1131 $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
1132 return $this->_expunge($mailbox, $clear_cache);
1136 // send IMAP expunge command and clear cache
1137 function _expunge($mailbox, $clear_cache=TRUE)
1139 $result = iil_C_Expunge($this->conn, $mailbox);
1141 if ($result>=0 && $clear_cache)
1143 //$this->clear_message_cache($mailbox.'.msg');
1144 $this->_clear_messagecount($mailbox);
1151 /* --------------------------------
1153 * --------------------------------*/
1157 * Get a list of all folders available on the IMAP server
1159 * @param string IMAP root dir
1160 * @return array Inbdexed array with folder names
1162 function list_unsubscribed($root='')
1164 static $sa_unsubscribed;
1166 if (is_array($sa_unsubscribed))
1167 return $sa_unsubscribed;
1169 // retrieve list of folders from IMAP server
1170 $a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
1172 // modify names with root dir
1173 foreach ($a_mboxes as $mbox_name)
1175 $name = $this->_mod_mailbox($mbox_name, 'out');
1177 $a_folders[] = $name;
1180 // filter folders and sort them
1181 $sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
1182 return $sa_unsubscribed;
1190 function get_quota()
1192 if ($this->get_capability('QUOTA'))
1194 $result = iil_C_GetQuota($this->conn);
1195 if ($result["total"])
1196 return sprintf("%.2fMB / %.2fMB (%.0f%%)", $result["used"] / 1000.0, $result["total"] / 1000.0, $result["percent"]);
1204 * subscribe to a specific mailbox(es)
1206 function subscribe($mbox_name, $mode='subscribe')
1208 if (is_array($mbox_name))
1209 $a_mboxes = $mbox_name;
1210 else if (is_string($mbox_name) && strlen($mbox_name))
1211 $a_mboxes = explode(',', $mbox_name);
1213 // let this common function do the main work
1214 return $this->_change_subscription($a_mboxes, 'subscribe');
1219 * unsubscribe mailboxes
1221 function unsubscribe($mbox_name)
1223 if (is_array($mbox_name))
1224 $a_mboxes = $mbox_name;
1225 else if (is_string($mbox_name) && strlen($mbox_name))
1226 $a_mboxes = explode(',', $mbox_name);
1228 // let this common function do the main work
1229 return $this->_change_subscription($a_mboxes, 'unsubscribe');
1234 * create a new mailbox on the server and register it in local cache
1236 function create_mailbox($name, $subscribe=FALSE)
1240 // replace backslashes
1241 $name = preg_replace('/[\\\]+/', '-', $name);
1243 $name_enc = UTF7EncodeString($name);
1245 // reduce mailbox name to 100 chars
1246 $name_enc = substr($name_enc, 0, 100);
1248 $abs_name = $this->_mod_mailbox($name_enc);
1249 $a_mailbox_cache = $this->get_cache('mailboxes');
1251 if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array_nocase($abs_name, $a_mailbox_cache)))
1252 $result = iil_C_CreateFolder($this->conn, $abs_name);
1254 // try to subscribe it
1256 $this->subscribe($name_enc);
1258 return $result ? $name : FALSE;
1263 * set a new name to an existing mailbox
1265 function rename_mailbox($mbox_name, $new_name)
1269 // replace backslashes
1270 $name = preg_replace('/[\\\]+/', '-', $new_name);
1272 // encode mailbox name and reduce it to 100 chars
1273 $name_enc = substr(UTF7EncodeString($new_name), 0, 100);
1275 // make absolute path
1276 $mailbox = $this->_mod_mailbox($mbox_name);
1277 $abs_name = $this->_mod_mailbox($name_enc);
1279 if (strlen($abs_name))
1280 $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
1285 $this->clear_message_cache($mailbox.'.msg');
1286 $this->clear_cache('mailboxes');
1289 return $result ? $name : FALSE;
1294 * remove mailboxes from server
1296 function delete_mailbox($mbox_name)
1300 if (is_array($mbox_name))
1301 $a_mboxes = $mbox_name;
1302 else if (is_string($mbox_name) && strlen($mbox_name))
1303 $a_mboxes = explode(',', $mbox_name);
1305 if (is_array($a_mboxes))
1306 foreach ($a_mboxes as $mbox_name)
1308 $mailbox = $this->_mod_mailbox($mbox_name);
1310 // unsubscribe mailbox before deleting
1311 iil_C_UnSubscribe($this->conn, $mailbox);
1313 // send delete command to server
1314 $result = iil_C_DeleteFolder($this->conn, $mailbox);
1319 // clear mailboxlist cache
1322 $this->clear_message_cache($mailbox.'.msg');
1323 $this->clear_cache('mailboxes');
1331 * Create all folders specified as default
1333 function create_default_folders()
1335 $a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
1336 $a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
1338 // create default folders if they do not exist
1339 foreach ($this->default_folders as $folder)
1341 $abs_name = $this->_mod_mailbox($folder);
1342 if (!in_array_nocase($abs_name, $a_subscribed))
1344 if (!in_array_nocase($abs_name, $a_folders))
1345 $this->create_mailbox($folder, TRUE);
1347 $this->subscribe($folder);
1354 /* --------------------------------
1355 * internal caching methods
1356 * --------------------------------*/
1359 function set_caching($set)
1361 if ($set && is_object($this->db))
1362 $this->caching_enabled = TRUE;
1364 $this->caching_enabled = FALSE;
1368 function get_cache($key)
1371 if (!isset($this->cache[$key]) && $this->caching_enabled)
1373 $cache_data = $this->_read_cache_record('IMAP.'.$key);
1374 $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
1377 return $this->cache[$key];
1381 function update_cache($key, $data)
1383 $this->cache[$key] = $data;
1384 $this->cache_changed = TRUE;
1385 $this->cache_changes[$key] = TRUE;
1389 function write_cache()
1391 if ($this->caching_enabled && $this->cache_changed)
1393 foreach ($this->cache as $key => $data)
1395 if ($this->cache_changes[$key])
1396 $this->_write_cache_record('IMAP.'.$key, serialize($data));
1402 function clear_cache($key=NULL)
1406 foreach ($this->cache as $key => $data)
1407 $this->_clear_cache_record('IMAP.'.$key);
1409 $this->cache = array();
1410 $this->cache_changed = FALSE;
1411 $this->cache_changes = array();
1415 $this->_clear_cache_record('IMAP.'.$key);
1416 $this->cache_changes[$key] = FALSE;
1417 unset($this->cache[$key]);
1423 function _read_cache_record($key)
1425 $cache_data = FALSE;
1429 // get cached data from DB
1430 $sql_result = $this->db->query(
1431 "SELECT cache_id, data
1432 FROM ".get_table_name('cache')."
1435 $_SESSION['user_id'],
1438 if ($sql_arr = $this->db->fetch_assoc($sql_result))
1440 $cache_data = $sql_arr['data'];
1441 $this->cache_keys[$key] = $sql_arr['cache_id'];
1449 function _write_cache_record($key, $data)
1454 // check if we already have a cache entry for this key
1455 if (!isset($this->cache_keys[$key]))
1457 $sql_result = $this->db->query(
1459 FROM ".get_table_name('cache')."
1462 $_SESSION['user_id'],
1465 if ($sql_arr = $this->db->fetch_assoc($sql_result))
1466 $this->cache_keys[$key] = $sql_arr['cache_id'];
1468 $this->cache_keys[$key] = FALSE;
1471 // update existing cache record
1472 if ($this->cache_keys[$key])
1475 "UPDATE ".get_table_name('cache')."
1481 $_SESSION['user_id'],
1484 // add new cache record
1488 "INSERT INTO ".get_table_name('cache')."
1489 (created, user_id, cache_key, data)
1490 VALUES (now(), ?, ?, ?)",
1491 $_SESSION['user_id'],
1498 function _clear_cache_record($key)
1501 "DELETE FROM ".get_table_name('cache')."
1504 $_SESSION['user_id'],
1510 /* --------------------------------
1511 * message caching methods
1512 * --------------------------------*/
1515 // checks if the cache is up-to-date
1516 // return: -3 = off, -2 = incomplete, -1 = dirty
1517 function check_cache_status($mailbox, $cache_key)
1519 if (!$this->caching_enabled)
1522 $cache_index = $this->get_message_cache_index($cache_key, TRUE);
1523 $msg_count = $this->_messagecount($mailbox);
1524 $cache_count = count($cache_index);
1526 // console("Cache check: $msg_count !== ".count($cache_index));
1528 if ($cache_count==$msg_count)
1530 // get highest index
1531 $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
1532 $cache_uid = array_pop($cache_index);
1534 // uids of highest message matches -> cache seems OK
1535 if ($cache_uid == $header->uid)
1541 // if cache count differs less than 10% report as dirty
1542 else if (abs($msg_count - $cache_count) < $msg_count/10)
1550 function get_message_cache($key, $from, $to, $sort_field, $sort_order)
1552 $cache_key = "$key:$from:$to:$sort_field:$sort_order";
1553 $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
1555 if (!in_array($sort_field, $db_header_fields))
1556 $sort_field = 'idx';
1558 if ($this->caching_enabled && !isset($this->cache[$cache_key]))
1560 $this->cache[$cache_key] = array();
1561 $sql_result = $this->db->limitquery(
1562 "SELECT idx, uid, headers
1563 FROM ".get_table_name('messages')."
1566 ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
1567 strtoupper($sort_order),
1570 $_SESSION['user_id'],
1573 while ($sql_arr = $this->db->fetch_assoc($sql_result))
1575 $uid = $sql_arr['uid'];
1576 $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
1580 return $this->cache[$cache_key];
1584 function get_cached_message($key, $uid, $body=FALSE)
1586 if (!$this->caching_enabled)
1589 $internal_key = '__single_msg';
1590 if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body))
1592 $sql_select = "idx, uid, headers";
1594 $sql_select .= ", body";
1596 $sql_result = $this->db->query(
1598 FROM ".get_table_name('messages')."
1602 $_SESSION['user_id'],
1606 if ($sql_arr = $this->db->fetch_assoc($sql_result))
1608 $headers = unserialize($sql_arr['headers']);
1609 if (is_object($headers) && !empty($sql_arr['body']))
1610 $headers->body = $sql_arr['body'];
1612 $this->cache[$internal_key][$uid] = $headers;
1616 return $this->cache[$internal_key][$uid];
1620 function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
1622 static $sa_message_index = array();
1624 // empty key -> empty array
1628 if (!empty($sa_message_index[$key]) && !$force)
1629 return $sa_message_index[$key];
1631 $sa_message_index[$key] = array();
1632 $sql_result = $this->db->query(
1634 FROM ".get_table_name('messages')."
1637 ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
1638 $_SESSION['user_id'],
1641 while ($sql_arr = $this->db->fetch_assoc($sql_result))
1642 $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
1644 return $sa_message_index[$key];
1648 function add_message_cache($key, $index, $headers)
1650 if (!$key || !is_object($headers) || empty($headers->uid))
1654 "INSERT INTO ".get_table_name('messages')."
1655 (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers)
1656 VALUES (?, 0, ?, now(), ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)",
1657 $_SESSION['user_id'],
1661 (string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
1662 (string)substr($this->decode_header($headers->from, TRUE), 0, 128),
1663 (string)substr($this->decode_header($headers->to, TRUE), 0, 128),
1664 (string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
1665 (int)$headers->size,
1666 serialize($headers));
1670 function remove_message_cache($key, $index)
1673 "DELETE FROM ".get_table_name('messages')."
1677 $_SESSION['user_id'],
1683 function clear_message_cache($key, $start_index=1)
1686 "DELETE FROM ".get_table_name('messages')."
1690 $_SESSION['user_id'],
1698 /* --------------------------------
1699 * encoding/decoding methods
1700 * --------------------------------*/
1703 function decode_address_list($input, $max=NULL)
1705 $a = $this->_parse_address_list($input);
1714 foreach ($a as $val)
1717 $address = $val['address'];
1718 $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', trim($val['name']));
1719 $string = $name!==$address ? sprintf('%s <%s>', strpos($name, ',')!==FALSE ? '"'.$name.'"' : $name, $address) : $address;
1721 $out[$j] = array('name' => $name,
1722 'mailto' => $address,
1723 'string' => $string);
1725 if ($max && $j==$max)
1733 function decode_header($input, $remove_quotes=FALSE)
1735 $str = $this->decode_mime_string((string)$input);
1736 if ($str{0}=='"' && $remove_quotes)
1738 $str = str_replace('"', '', $str);
1746 * Decode a mime-encoded string to internal charset
1750 function decode_mime_string($input, $recursive=false)
1754 $pos = strpos($input, '=?');
1757 $out = substr($input, 0, $pos);
1759 $end_cs_pos = strpos($input, "?", $pos+2);
1760 $end_en_pos = strpos($input, "?", $end_cs_pos+1);
1761 $end_pos = strpos($input, "?=", $end_en_pos+1);
1763 $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
1764 $rest = substr($input, $end_pos+2);
1766 $out .= rcube_imap::_decode_mime_string_part($encstr);
1767 $out .= rcube_imap::decode_mime_string($rest);
1772 // no encoding information, defaults to what is specified in the class header
1773 return rcube_charset_convert($input, 'ISO-8859-1');
1778 * Decode a part of a mime-encoded string
1782 function _decode_mime_string_part($str)
1784 $a = explode('?', $str);
1787 // should be in format "charset?encoding?base64_string"
1790 for ($i=2; $i<$count; $i++)
1793 if (($a[1]=="B")||($a[1]=="b"))
1794 $rest = base64_decode($rest);
1795 else if (($a[1]=="Q")||($a[1]=="q"))
1797 $rest = str_replace("_", " ", $rest);
1798 $rest = quoted_printable_decode($rest);
1801 return rcube_charset_convert($rest, $a[0]);
1804 return $str; // we dont' know what to do with this
1808 function mime_decode($input, $encoding='7bit')
1810 switch (strtolower($encoding))
1816 case 'quoted-printable':
1817 return quoted_printable_decode($input);
1821 return base64_decode($input);
1830 function mime_encode($input, $encoding='7bit')
1834 case 'quoted-printable':
1835 return quoted_printable_encode($input);
1839 return base64_encode($input);
1848 // convert body chars according to the ctype_parameters
1849 function charset_decode($body, $ctype_param)
1851 if (is_array($ctype_param) && !empty($ctype_param['charset']))
1852 return rcube_charset_convert($body, $ctype_param['charset']);
1854 // defaults to what is specified in the class header
1855 return rcube_charset_convert($body, 'ISO-8859-1');
1861 /* --------------------------------
1863 * --------------------------------*/
1866 function _mod_mailbox($mbox_name, $mode='in')
1868 if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
1871 if (!empty($this->root_dir) && $mode=='in')
1872 $mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
1873 else if (strlen($this->root_dir) && $mode=='out')
1874 $mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
1880 // sort mailboxes first by default folders and then in alphabethical order
1881 function _sort_mailbox_list($a_folders)
1883 $a_out = $a_defaults = array();
1885 // find default folders and skip folders starting with '.'
1886 foreach($a_folders as $i => $folder)
1888 if ($folder{0}=='.')
1891 if (($p = array_search(strtolower($folder), $this->default_folders_lc))!==FALSE)
1892 $a_defaults[$p] = $folder;
1900 return array_merge($a_defaults, $a_out);
1903 function get_id($uid, $mbox_name=NULL)
1905 return $this->_uid2id($uid, $mbox_name);
1908 function get_uid($id,$mbox_name=NULL)
1910 return $this->_id2uid($id, $mbox_name);
1913 function _uid2id($uid, $mbox_name=NULL)
1916 $mbox_name = $this->mailbox;
1918 if (!isset($this->uid_id_map[$mbox_name][$uid]))
1919 $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
1921 return $this->uid_id_map[$mbox_name][$uid];
1924 function _id2uid($id, $mbox_name=NULL)
1927 $mbox_name = $this->mailbox;
1929 return iil_C_ID2UID($this->conn, $mbox_name, $id);
1933 // parse string or array of server capabilities and put them in internal array
1934 function _parse_capability($caps)
1936 if (!is_array($caps))
1937 $cap_arr = explode(' ', $caps);
1941 foreach ($cap_arr as $cap)
1943 if ($cap=='CAPABILITY')
1946 if (strpos($cap, '=')>0)
1948 list($key, $value) = explode('=', $cap);
1949 if (!is_array($this->capabilities[$key]))
1950 $this->capabilities[$key] = array();
1952 $this->capabilities[$key][] = $value;
1955 $this->capabilities[$cap] = TRUE;
1960 // subscribe/unsubscribe a list of mailboxes and update local cache
1961 function _change_subscription($a_mboxes, $mode)
1965 if (is_array($a_mboxes))
1966 foreach ($a_mboxes as $i => $mbox_name)
1968 $mailbox = $this->_mod_mailbox($mbox_name);
1969 $a_mboxes[$i] = $mailbox;
1971 if ($mode=='subscribe')
1972 $result = iil_C_Subscribe($this->conn, $mailbox);
1973 else if ($mode=='unsubscribe')
1974 $result = iil_C_UnSubscribe($this->conn, $mailbox);
1980 // get cached mailbox list
1983 $a_mailbox_cache = $this->get_cache('mailboxes');
1984 if (!is_array($a_mailbox_cache))
1987 // modify cached list
1988 if ($mode=='subscribe')
1989 $a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
1990 else if ($mode=='unsubscribe')
1991 $a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
1993 // write mailboxlist to cache
1994 $this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
2001 // increde/decrese messagecount for a specific mailbox
2002 function _set_messagecount($mbox_name, $mode, $increment)
2004 $a_mailbox_cache = FALSE;
2005 $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
2006 $mode = strtoupper($mode);
2008 $a_mailbox_cache = $this->get_cache('messagecount');
2010 if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
2013 // add incremental value to messagecount
2014 $a_mailbox_cache[$mailbox][$mode] += $increment;
2016 // there's something wrong, delete from cache
2017 if ($a_mailbox_cache[$mailbox][$mode] < 0)
2018 unset($a_mailbox_cache[$mailbox][$mode]);
2020 // write back to cache
2021 $this->update_cache('messagecount', $a_mailbox_cache);
2027 // remove messagecount of a specific mailbox from cache
2028 function _clear_messagecount($mbox_name='')
2030 $a_mailbox_cache = FALSE;
2031 $mailbox = $mbox_name ? $mbox_name : $this->mailbox;
2033 $a_mailbox_cache = $this->get_cache('messagecount');
2035 if (is_array($a_mailbox_cache[$mailbox]))
2037 unset($a_mailbox_cache[$mailbox]);
2038 $this->update_cache('messagecount', $a_mailbox_cache);
2043 function _parse_address_list($str)
2045 $a = $this->_explode_quoted_string(',', $str);
2048 foreach ($a as $key => $val)
2050 $val = str_replace("\"<", "\" <", $val);
2051 $sub_a = $this->_explode_quoted_string(' ', $this->decode_header($val));
2052 $result[$key]['name'] = '';
2054 foreach ($sub_a as $k => $v)
2056 if ((strpos($v, '@') > 0) && (strpos($v, '.') > 0))
2057 $result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
2059 $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
2062 if (empty($result[$key]['name']))
2063 $result[$key]['name'] = $result[$key]['address'];
2070 function _explode_quoted_string($delimiter, $string)
2072 $quotes = explode("\"", $string);
2073 foreach ($quotes as $key => $val)
2074 if (($key % 2) == 1)
2075 $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
2077 $string = implode("\"", $quotes);
2079 $result = explode($delimiter, $string);
2080 foreach ($result as $key => $val)
2081 $result[$key] = str_replace("_!@!_", $delimiter, $result[$key]);
2090 * rcube_header_sorter
2092 * Class for sorting an array of iilBasicHeader objects in a predetermined order.
2094 * @author Eric Stadtherr
2096 class rcube_header_sorter
2098 var $sequence_numbers = array();
2101 * set the predetermined sort order.
2103 * @param array $seqnums numerically indexed array of IMAP message sequence numbers
2105 function set_sequence_numbers($seqnums)
2107 $this->sequence_numbers = $seqnums;
2111 * sort the array of header objects
2113 * @param array $headers array of iilBasicHeader objects indexed by UID
2115 function sort_headers(&$headers)
2118 * uksort would work if the keys were the sequence number, but unfortunately
2119 * the keys are the UIDs. We'll use uasort instead and dereference the value
2120 * to get the sequence number (in the "id" field).
2122 * uksort($headers, array($this, "compare_seqnums"));
2124 uasort($headers, array($this, "compare_seqnums"));
2128 * get the position of a message sequence number in my sequence_numbers array
2130 * @param integer $seqnum message sequence number contained in sequence_numbers
2132 function position_of($seqnum)
2134 $c = count($this->sequence_numbers);
2135 for ($pos = 0; $pos <= $c; $pos++)
2137 if ($this->sequence_numbers[$pos] == $seqnum)
2144 * Sort method called by uasort()
2146 function compare_seqnums($a, $b)
2148 // First get the sequence number from the header object (the 'id' field).
2152 // then find each sequence number in my ordered list
2153 $posa = $this->position_of($seqa);
2154 $posb = $this->position_of($seqb);
2156 // return the relative position as the comparison value
2157 $ret = $posa - $posb;
2164 * Add quoted-printable encoding to a given string
2166 * @param string $input string to encode
2167 * @param int $line_max add new line after this number of characters
2168 * @param boolena $space_conf true if spaces should be converted into =20
2169 * @return encoded string
2171 function quoted_printable_encode($input, $line_max=76, $space_conv=false)
2173 $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
2174 $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
2179 while( list(, $line) = each($lines))
2181 //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
2182 $linlen = strlen($line);
2184 for($i = 0; $i < $linlen; $i++)
2186 $c = substr( $line, $i, 1 );
2188 if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
2194 if ( $i == ( $linlen - 1 ) ) // convert space at eol only
2198 else if ( $space_conv )
2203 else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) // always encode "\t", which is *not* required
2205 $h2 = floor($dec/16);
2206 $h1 = floor($dec%16);
2207 $c = $escape.$hex["$h2"].$hex["$h1"];
2210 if ( (strlen($newline) + strlen($c)) >= $line_max ) // CRLF is not counted
2212 $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
2214 // check if newline first character will be point or not
2222 $output .= $newline.$eol;
2225 return trim($output);