| | |
| | | */ |
| | | class rcube_imap |
| | | { |
| | | public $debug_level = 1; |
| | | public $skip_deleted = false; |
| | | public $page_size = 10; |
| | | public $list_page = 1; |
| | |
| | | |
| | | |
| | | /** |
| | | * Activate/deactivate debug mode |
| | | * |
| | | * @param boolean $dbg True if IMAP conversation should be logged |
| | | * @access public |
| | | */ |
| | | function set_debug($dbg = true) |
| | | { |
| | | $this->options['debug'] = $dbg; |
| | | $this->conn->setDebug($dbg, array($this, 'debug_handler')); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Set default message charset |
| | | * |
| | | * This will be used for message decoding if a charset specification is not available |
| | |
| | | */ |
| | | function get_mailbox_name() |
| | | { |
| | | return $this->conn->connected() ? $this->mailbox : ''; |
| | | return $this->mailbox; |
| | | } |
| | | |
| | | |
| | |
| | | // I didn't found that SEARCH should return sorted IDs |
| | | if (is_array($a_index)) |
| | | sort($a_index); |
| | | } else if ($max = $this->_messagecount($mailbox)) { |
| | | } else if ($max = $this->_messagecount($mailbox, 'ALL', true, false)) { |
| | | $a_index = range(1, $max); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if ($orig_criteria == 'ALL') { |
| | | $max = $this->_messagecount($mailbox); |
| | | $max = $this->_messagecount($mailbox, 'ALL', true, false); |
| | | $a_messages = $max ? range(1, $max) : array(); |
| | | } |
| | | else { |
| | |
| | | } |
| | | |
| | | $headers = $this->get_headers($uid, $mailbox); |
| | | |
| | | // message doesn't exist? |
| | | if (empty($headers)) |
| | | return null; |
| | | |
| | | // structure might be cached |
| | | if (!empty($headers->structure)) |
| | |
| | | |
| | | // decode filename |
| | | if (!empty($filename_mime)) { |
| | | $part->filename = rcube_imap::decode_mime_string($filename_mime, |
| | | $part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset : |
| | | rc_detect_encoding($filename_mime, $this->default_charset))); |
| | | if (!empty($part->charset)) |
| | | $charset = $part->charset; |
| | | else if (!empty($this->struct_charset)) |
| | | $charset = $this->struct_charset; |
| | | else |
| | | $charset = rc_detect_encoding($filename_mime, $this->default_charset); |
| | | |
| | | $part->filename = rcube_imap::decode_mime_string($filename_mime, $charset); |
| | | } |
| | | else if (!empty($filename_encoded)) { |
| | | // decode filename according to RFC 2231, Section 4 |
| | |
| | | $filename_charset = $fmatches[1]; |
| | | $filename_encoded = $fmatches[2]; |
| | | } |
| | | |
| | | $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset); |
| | | } |
| | | } |
| | |
| | | */ |
| | | function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false) |
| | | { |
| | | // get part encoding if not provided |
| | | // get part data if not provided |
| | | if (!is_object($o_part)) { |
| | | $structure = $this->conn->getStructure($this->mailbox, $uid, true); |
| | | $part_data = rcube_imap_generic::getStructurePartData($structure, $part); |
| | | |
| | | $o_part = new rcube_message_part; |
| | | $o_part->ctype_primary = strtolower(rcube_imap_generic::getStructurePartType($structure, $part)); |
| | | $o_part->encoding = strtolower(rcube_imap_generic::getStructurePartEncoding($structure, $part)); |
| | | $o_part->charset = rcube_imap_generic::getStructurePartCharset($structure, $part); |
| | | $o_part->ctype_primary = $part_data['type']; |
| | | $o_part->encoding = $part_data['encoding']; |
| | | $o_part->charset = $part_data['charset']; |
| | | $o_part->size = $part_data['size']; |
| | | } |
| | | |
| | | // TODO: Add caching for message parts |
| | | |
| | | if (!$part) { |
| | | $part = 'TEXT'; |
| | | if ($o_part && $o_part->size) { |
| | | $body = $this->conn->handlePartBody($this->mailbox, $uid, true, |
| | | $part ? $part : 'TEXT', $o_part->encoding, $print, $fp); |
| | | } |
| | | |
| | | $body = $this->conn->handlePartBody($this->mailbox, $uid, true, $part, |
| | | $o_part->encoding, $print, $fp); |
| | | |
| | | if ($fp || $print) { |
| | | return true; |
| | |
| | | |
| | | if (!$skip_charset_conv) { |
| | | if (!$o_part->charset || strtoupper($o_part->charset) == 'US-ASCII') { |
| | | $o_part->charset = $this->default_charset; |
| | | // try to extract charset information from HTML meta tag (#1488125) |
| | | if ($o_part->ctype_secondary == 'html' && preg_match('/<meta[^>]+charset=([a-z0-9-_]+)/i', $body, $m)) |
| | | $o_part->charset = strtoupper($m[1]); |
| | | else |
| | | $o_part->charset = $this->default_charset; |
| | | } |
| | | $body = rcube_charset_convert($body, $o_part->charset); |
| | | } |
| | |
| | | * @param string $headers Headers string if $message contains only the body |
| | | * @param boolean $is_file True if $message is a filename |
| | | * |
| | | * @return boolean True on success, False on error |
| | | * @return int|bool Appended message UID or True on success, False on error |
| | | */ |
| | | function save_message($mailbox, &$message, $headers='', $is_file=false) |
| | | { |
| | |
| | | |
| | | // make sure mailbox exists |
| | | if ($to_mbox != 'INBOX' && !$this->mailbox_exists($to_mbox)) { |
| | | if (in_array($to_mbox, $this->default_folders)) |
| | | $this->create_mailbox($to_mbox, true); |
| | | else |
| | | if (in_array($to_mbox, $this->default_folders)) { |
| | | if (!$this->create_mailbox($to_mbox, true)) { |
| | | return false; |
| | | } |
| | | } |
| | | else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | $config = rcmail::get_instance()->config; |
| | |
| | | |
| | | // make sure mailbox exists |
| | | if ($to_mbox != 'INBOX' && !$this->mailbox_exists($to_mbox)) { |
| | | if (in_array($to_mbox, $this->default_folders)) |
| | | $this->create_mailbox($to_mbox, true); |
| | | else |
| | | if (in_array($to_mbox, $this->default_folders)) { |
| | | if (!$this->create_mailbox($to_mbox, true)) { |
| | | return false; |
| | | } |
| | | } |
| | | else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // copy messages |
| | |
| | | /** |
| | | * Public method for listing subscribed folders |
| | | * |
| | | * @param string $root Optional root folder |
| | | * @param string $name Optional name pattern |
| | | * @param string $filter Optional filter |
| | | * @param string $root Optional root folder |
| | | * @param string $name Optional name pattern |
| | | * @param string $filter Optional filter |
| | | * @param string $rights Optional ACL requirements |
| | | * @param bool $skip_sort Enable to return unsorted list (for better performance) |
| | | * |
| | | * @return array List of mailboxes/folders |
| | | * @return array List of folders |
| | | * @access public |
| | | */ |
| | | function list_mailboxes($root='', $name='*', $filter=null) |
| | | function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false) |
| | | { |
| | | $a_mboxes = $this->_list_mailboxes($root, $name, $filter); |
| | | $cache_key = $root.':'.$name; |
| | | if (!empty($filter)) { |
| | | $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter)); |
| | | } |
| | | $cache_key .= ':'.$rights; |
| | | $cache_key = 'mailboxes.'.md5($cache_key); |
| | | |
| | | // get cached folder list |
| | | $a_mboxes = $this->get_cache($cache_key); |
| | | if (is_array($a_mboxes)) { |
| | | return $a_mboxes; |
| | | } |
| | | |
| | | $a_mboxes = $this->_list_mailboxes($root, $name, $filter, $rights); |
| | | |
| | | if (!is_array($a_mboxes)) { |
| | | return array(); |
| | | } |
| | | |
| | | // filter folders list according to rights requirements |
| | | if ($rights && $this->get_capability('ACL')) { |
| | | $a_mboxes = $this->filter_rights($a_mboxes, $rights); |
| | | } |
| | | |
| | | // INBOX should always be available |
| | | if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) { |
| | | array_unshift($a_mboxes, 'INBOX'); |
| | | } |
| | | |
| | | // sort mailboxes |
| | | $a_mboxes = $this->_sort_mailbox_list($a_mboxes); |
| | | // sort mailboxes (always sort for cache) |
| | | if (!$skip_sort || $this->cache) { |
| | | $a_mboxes = $this->_sort_mailbox_list($a_mboxes); |
| | | } |
| | | |
| | | // write mailboxlist to cache |
| | | $this->update_cache($cache_key, $a_mboxes); |
| | | |
| | | return $a_mboxes; |
| | | } |
| | |
| | | * @param string $root Optional root folder |
| | | * @param string $name Optional name pattern |
| | | * @param mixed $filter Optional filter |
| | | * @param string $rights Optional ACL requirements |
| | | * |
| | | * @return array List of mailboxes/folders |
| | | * @see rcube_imap::list_mailboxes() |
| | | * @access private |
| | | */ |
| | | private function _list_mailboxes($root='', $name='*', $filter=null) |
| | | private function _list_mailboxes($root='', $name='*', $filter=null, $rights=null) |
| | | { |
| | | $cache_key = $root.':'.$name; |
| | | if (!empty($filter)) { |
| | | $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter)); |
| | | } |
| | | |
| | | $cache_key = 'mailboxes.'.md5($cache_key); |
| | | |
| | | // get cached folder list |
| | | $a_mboxes = $this->get_cache($cache_key); |
| | | if (is_array($a_mboxes)) { |
| | | return $a_mboxes; |
| | | } |
| | | |
| | | $a_defaults = $a_out = array(); |
| | | |
| | | // Give plugins a chance to provide a list of mailboxes |
| | |
| | | $a_folders = $data['folders']; |
| | | } |
| | | else if (!$this->conn->connected()) { |
| | | return array(); |
| | | return null; |
| | | } |
| | | else { |
| | | // Server supports LIST-EXTENDED, we can use selection options |
| | |
| | | $a_folders = array(); |
| | | } |
| | | |
| | | // write mailboxlist to cache |
| | | $this->update_cache($cache_key, $a_folders); |
| | | |
| | | return $a_folders; |
| | | } |
| | | |
| | |
| | | /** |
| | | * Get a list of all folders available on the IMAP server |
| | | * |
| | | * @param string $root IMAP root dir |
| | | * @param string $name Optional name pattern |
| | | * @param mixed $filter Optional filter |
| | | * @param string $root IMAP root dir |
| | | * @param string $name Optional name pattern |
| | | * @param mixed $filter Optional filter |
| | | * @param string $rights Optional ACL requirements |
| | | * @param bool $skip_sort Enable to return unsorted list (for better performance) |
| | | * |
| | | * @return array Indexed array with folder names |
| | | */ |
| | | function list_unsubscribed($root='', $name='*', $filter=null) |
| | | function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false) |
| | | { |
| | | // @TODO: caching |
| | | $cache_key = $root.':'.$name; |
| | | if (!empty($filter)) { |
| | | $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter)); |
| | | } |
| | | $cache_key .= ':'.$rights; |
| | | $cache_key = 'mailboxes.list.'.md5($cache_key); |
| | | |
| | | // get cached folder list |
| | | $a_mboxes = $this->get_cache($cache_key); |
| | | if (is_array($a_mboxes)) { |
| | | return $a_mboxes; |
| | | } |
| | | |
| | | // Give plugins a chance to provide a list of mailboxes |
| | | $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list', |
| | | array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST')); |
| | |
| | | array_unshift($a_mboxes, 'INBOX'); |
| | | } |
| | | |
| | | // cache folder attributes |
| | | if ($root == '' && $name == '*' && empty($filter)) { |
| | | $this->update_cache('mailboxes.attributes', $this->conn->data['LIST']); |
| | | } |
| | | |
| | | // filter folders list according to rights requirements |
| | | if ($rights && $this->get_capability('ACL')) { |
| | | $a_folders = $this->filter_rights($a_folders, $rights); |
| | | } |
| | | |
| | | // filter folders and sort them |
| | | $a_mboxes = $this->_sort_mailbox_list($a_mboxes); |
| | | if (!$skip_sort) { |
| | | $a_mboxes = $this->_sort_mailbox_list($a_mboxes); |
| | | } |
| | | |
| | | // write mailboxlist to cache |
| | | $this->update_cache($cache_key, $a_mboxes); |
| | | |
| | | return $a_mboxes; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Filter the given list of folders according to access rights |
| | | */ |
| | | private function filter_rights($a_folders, $rights) |
| | | { |
| | | $regex = '/('.$rights.')/'; |
| | | foreach ($a_folders as $idx => $folder) { |
| | | $myrights = join('', (array)$this->my_rights($folder)); |
| | | if ($myrights !== null && !preg_match($regex, $myrights)) |
| | | unset($a_folders[$idx]); |
| | | } |
| | | |
| | | return $a_folders; |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | |
| | | /** |
| | | * Gets folder options from LIST response, e.g. \Noselect, \Noinferiors |
| | | * Gets folder attributes from LIST response, e.g. \Noselect, \Noinferiors |
| | | * |
| | | * @param string $mailbox Folder name |
| | | * @param bool $force Set to True if options should be refreshed |
| | | * Options are available after LIST command only |
| | | * @param bool $force Set to True if attributes should be refreshed |
| | | * |
| | | * @return array Options list |
| | | */ |
| | | function mailbox_options($mailbox, $force=false) |
| | | function mailbox_attributes($mailbox, $force=false) |
| | | { |
| | | if ($mailbox == 'INBOX') { |
| | | return array(); |
| | | // get attributes directly from LIST command |
| | | if (!empty($this->conn->data['LIST']) && is_array($this->conn->data['LIST'][$mailbox])) { |
| | | $opts = $this->conn->data['LIST'][$mailbox]; |
| | | } |
| | | // get cached folder attributes |
| | | else if (!$force) { |
| | | $opts = $this->get_cache('mailboxes.attributes'); |
| | | $opts = $opts[$mailbox]; |
| | | } |
| | | |
| | | if (!is_array($this->conn->data['LIST']) || !is_array($this->conn->data['LIST'][$mailbox])) { |
| | | if ($force) { |
| | | $this->conn->listMailboxes('', $mailbox); |
| | | } |
| | | else { |
| | | return array(); |
| | | } |
| | | if (!is_array($opts)) { |
| | | $this->conn->listMailboxes('', $mailbox); |
| | | $opts = $this->conn->data['LIST'][$mailbox]; |
| | | } |
| | | |
| | | $opts = $this->conn->data['LIST'][$mailbox]; |
| | | |
| | | return is_array($opts) ? $opts : array(); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | $options['name'] = $mailbox; |
| | | $options['options'] = $this->mailbox_options($mailbox, true); |
| | | $options['namespace'] = $this->mailbox_namespace($mailbox); |
| | | $options['rights'] = $acl && !$options['is_root'] ? (array)$this->my_rights($mailbox) : array(); |
| | | $options['special'] = in_array($mailbox, $this->default_folders); |
| | | $options['name'] = $mailbox; |
| | | $options['attributes'] = $this->mailbox_attributes($mailbox, true); |
| | | $options['namespace'] = $this->mailbox_namespace($mailbox); |
| | | $options['rights'] = $acl && !$options['is_root'] ? (array)$this->my_rights($mailbox) : array(); |
| | | $options['special'] = in_array($mailbox, $this->default_folders); |
| | | |
| | | // Set 'noselect' and 'norename' flags |
| | | if (is_array($options['options'])) { |
| | | foreach ($options['options'] as $opt) { |
| | | $opt = strtolower($opt); |
| | | if ($opt == '\noselect' || $opt == '\nonexistent') { |
| | | if (is_array($options['attributes'])) { |
| | | foreach ($options['attributes'] as $attrib) { |
| | | $attrib = strtolower($attrib); |
| | | if ($attrib == '\noselect' || $attrib == '\nonexistent') { |
| | | $options['noselect'] = true; |
| | | } |
| | | } |