From e44f569e3c0337fd4d79af8ee4eeda87884ce575 Mon Sep 17 00:00:00 2001 From: Chuck Hagenbuch Date: Fri, 26 Dec 2008 23:12:45 -0500 Subject: [PATCH] do my best to make sure autoloading is used here add class_exists checks for Auth_SASL that can autoload it and avoid fatal errors if it's not available remove require statements that should be autoloadable split a few classes into their own files --- framework/Imap_Client/lib/Horde/Imap/Client.php | 44 +- .../Imap_Client/lib/Horde/Imap/Client/Base.php | 643 +-------------------- .../Imap_Client/lib/Horde/Imap/Client/Cache.php | 12 +- .../lib/Horde/Imap/Client/Search/Query.php | 505 ++++++++++++++++ .../Imap_Client/lib/Horde/Imap/Client/Socket.php | 6 + .../Imap_Client/lib/Horde/Imap/Client/Thread.php | 137 +++++ 6 files changed, 652 insertions(+), 695 deletions(-) create mode 100644 framework/Imap_Client/lib/Horde/Imap/Client/Search/Query.php create mode 100644 framework/Imap_Client/lib/Horde/Imap/Client/Thread.php diff --git a/framework/Imap_Client/lib/Horde/Imap/Client.php b/framework/Imap_Client/lib/Horde/Imap/Client.php index 7cf715383..8ff3da36f 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client.php @@ -1,9 +1,4 @@ _cacheOb)) { $p = $this->_params; - require_once dirname(__FILE__) . '/Cache.php'; - $this->_cacheOb = &Horde_Imap_Client_Cache::singleton(array_merge($p['cache'], array( + $this->_cacheOb = Horde_Imap_Client_Cache::singleton(array_merge($p['cache'], array( 'debug' => $this->_debug, 'hostspec' => $p['hostspec'], 'username' => $p['username'] @@ -806,7 +805,6 @@ abstract class Horde_Imap_Client_Base extends Horde_Imap_Client $ret = $this->_listMailboxes(Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($pattern), $mode, $options); if (!empty($options['sort'])) { - require_once dirname(__FILE__) . '/Sort.php'; Horde_Imap_Client_Sort::sortMailboxes($ret, array('delimiter' => empty($options['sort_delimiter']) ? '.' : $options['sort_delimiter'], 'index' => false, 'keysort' => empty($options['flat']))); } @@ -2351,644 +2349,5 @@ abstract class Horde_Imap_Client_Base extends Horde_Imap_Client } } } -} - -/* - * Abstraction of the IMAP4rev1 search criteria (see RFC 3501 [6.4.4]). This - * class allows translation between abstracted search criteria and a - * generated IMAP search criteria string suitable for sending to a remote - * IMAP server. - */ -class Horde_Imap_Client_Search_Query -{ - /* Constants for dateSearch() */ - const DATE_BEFORE = 'BEFORE'; - const DATE_ON = 'ON'; - const DATE_SINCE = 'SINCE'; - - /* Constants for intervalSearch() */ - const INTERVAL_OLDER = 'OLDER'; - const INTERVAL_YOUNGER = 'YOUNGER'; - /** - * The charset of the search strings. All text strings must be in - * this charset. - * - * @var string - */ - protected $_charset = 'US-ASCII'; - - /** - * The list of defined system flags (see RFC 3501 [2.3.2]). - * - * @var array - */ - protected $_systemflags = array( - 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN' - ); - - /** - * The list of 'system' headers that have a specific search query. - * - * @var array - */ - protected $_systemheaders = array( - 'BCC', 'CC', 'FROM', 'SUBJECT', 'TO' - ); - - /** - * The list of search params. - * - * @var array - */ - protected $_search = array(); - - /** - * List of extensions needed for advanced queries. - * - * @var array - */ - protected $_exts = array(); - - /** - * Sets the charset of the search text. - * - * @param string $charset The charset to use for the search. - */ - public function charset($charset) - { - $this->_charset = strtoupper($charset); - } - - /** - * Builds an IMAP4rev1 compliant search string. - * - * @return array An array with 3 elements: - *
-     * 'charset' - (string) The charset of the search string.
-     * 'imap4' - (boolean) True if the search uses IMAP4 criteria (as opposed
-     *           to IMAP2 search criteria)
-     * 'query' - (string) The IMAP search string
-     * 
- */ - public function build() - { - $cmds = array(); - $imap4 = false; - $ptr = &$this->_search; - - if (isset($ptr['new'])) { - if ($ptr['new']) { - $cmds[] = 'NEW'; - unset($ptr['flag']['UNSEEN']); - } else { - $cmds[] = 'OLD'; - } - unset($ptr['flag']['RECENT']); - } - - if (!empty($ptr['flag'])) { - foreach ($ptr['flag'] as $key => $val) { - if ($key == 'draft') { - // DRAFT flag was not in IMAP2 - $imap4 = true; - } - - $tmp = ''; - if (!$val['set']) { - // This is a 'NOT' search. All system flags but \Recent - // have 'UN' equivalents. - if ($key == 'RECENT') { - $tmp = 'NOT '; - // NOT searches were not in IMAP2 - $imap4 = true; - } else { - $tmp = 'UN'; - } - } - - $cmds[] = $tmp . ($val['type'] == 'keyword' ? 'KEYWORD ' : '') . $key; - } - } - - if (!empty($ptr['header'])) { - foreach ($ptr['header'] as $val) { - $tmp = ''; - if ($val['not']) { - $tmp = 'NOT '; - // NOT searches were not in IMAP2 - $imap4 = true; - } - - if (!in_array($val['header'], $this->_systemheaders)) { - // HEADER searches were not in IMAP2 - $tmp .= 'HEADER '; - $imap4 = true; - } - $cmds[] = $tmp . $val['header'] . ' ' . Horde_Imap_Client::escape($val['text']); - } - } - - if (!empty($ptr['text'])) { - foreach ($ptr['text'] as $val) { - $tmp = ''; - if ($val['not']) { - $tmp = 'NOT '; - // NOT searches were not in IMAP2 - $imap4 = true; - } - $cmds[] = $tmp . $val['type'] . ' ' . Horde_Imap_Client::escape($val['text']); - } - } - - if (!empty($ptr['size'])) { - foreach ($ptr['size'] as $key => $val) { - $cmds[] = ($val['not'] ? 'NOT ' : '' ) . $key . ' ' . $val['size']; - // LARGER/SMALLER searches were not in IMAP2 - $imap4 = true; - } - } - - if (isset($ptr['sequence'])) { - $cmds[] = ($ptr['sequence']['not'] ? 'NOT ' : '') . ($ptr['sequence']['sequence'] ? '' : 'UID ') . $ptr['sequence']['ids']; - - // sequence searches were not in IMAP2 - $imap4 = true; - } - - if (!empty($ptr['date'])) { - foreach ($ptr['date'] as $key => $val) { - $tmp = ''; - if ($val['not']) { - $tmp = 'NOT '; - // NOT searches were not in IMAP2 - $imap4 = true; - } - - if ($key == 'header') { - $tmp .= 'SENT'; - // 'SENT*' searches were not in IMAP2 - $imap4 = true; - } - $cmds[] = $tmp . $val['range'] . ' ' . $val['date']; - } - } - - if (!empty($ptr['within'])) { - $imap4 = true; - $this->_exts['WITHIN'] = true; - - foreach ($ptr['within'] as $key => $val) { - $cmds[] = ($val['not'] ? 'NOT ' : '') . $key . ' ' . $val['interval']; - } - } - - if (!empty($ptr['modseq'])) { - $imap4 = true; - $this->_exts['CONDSTORE'] = true; - $cmds[] = ($ptr['modseq']['not'] ? 'NOT ' : '') . - 'MODSEQ ' . - (is_null($ptr['modseq']['name']) - ? '' - : Horde_Imap_Client::escape($ptr['modseq']['name']) . ' ' . $ptr['modseq']['type'] . ' ') . - $ptr['modseq']['value']; - } - - if (isset($ptr['prevsearch'])) { - $imap4 = true; - $this->_exts['SEARCHRES'] = true; - $cmds[] = ($ptr['prevsearch'] ? '' : 'NOT ') . '$'; - } - - $query = ''; - - // Add OR'ed queries - if (!empty($ptr['or'])) { - foreach ($ptr['or'] as $key => $val) { - // OR queries were not in IMAP 2 - $imap4 = true; - - if ($key == 0) { - $query = '(' . $query . ')'; - } - - $ret = $val->build(); - $query = 'OR (' . $ret['query'] . ') ' . $query; - } - } - - // Add AND'ed queries - if (!empty($ptr['and'])) { - foreach ($ptr['and'] as $key => $val) { - $ret = $val->build(); - $query .= ' ' . $ret['query']; - } - } - - // Default search is 'ALL' - if (empty($cmds)) { - $query .= empty($query) ? 'ALL' : ''; - } else { - $query .= implode(' ', $cmds); - } - - return array( - 'charset' => $this->_charset, - 'imap4' => $imap4, - 'query' => trim($query) - ); - } - - /** - * Return the list of any IMAP extensions needed to perform the query. - * - * @return array The list of extensions (CAPABILITY responses) needed to - * perform the query. - */ - public function extensionsNeeded() - { - return $this->_exts; - } - - /** - * Search for a flag/keywords. - * - * @param string $name The flag or keyword name. - * @param boolean $set If true, search for messages that have the flag - * set. If false, search for messages that do not - * have the flag set. - */ - public function flag($name, $set = true) - { - $name = strtoupper(ltrim($name, '\\')); - if (!isset($this->_search['flag'])) { - $this->_search['flag'] = array(); - } - $this->_search['flag'][$name] = array( - 'set' => $set, - 'type' => in_array($name, $this->_systemflags) ? 'flag' : 'keyword' - ); - } - - /** - * Search for either new messages (messages that have the '\Recent' flag - * but not the '\Seen' flag) or old messages (messages that do not have - * the '\Recent' flag). If new messages are searched, this will clear - * any '\Recent' or '\Unseen' flag searches. If old messages are searched, - * this will clear any '\Recent' flag search. - * - * @param boolean $newmsgs If true, searches for new messages. Else, - * search for old messages. - */ - public function newMsgs($newmsgs = true) - { - $this->_search['new'] = $newmsgs; - } - - /** - * Search for text in the header of a message. - * - * @param string $header The header field. - * @param string $text The search text. - * @param boolean $not If true, do a 'NOT' search of $text. - */ - public function headerText($header, $text, $not = false) - { - if (!isset($this->_search['header'])) { - $this->_search['header'] = array(); - } - $this->_search['header'][] = array( - 'header' => strtoupper($header), - 'text' => $text, - 'not' => $not - ); - } - - /** - * Search for text in either the entire message, or just the body. - * - * @param string $text The search text. - * @param string $bodyonly If true, only search in the body of the - * message. If false, also search in the headers. - * @param boolean $not If true, do a 'NOT' search of $text. - */ - public function text($text, $bodyonly = true, $not = false) - { - if (!isset($this->_search['text'])) { - $this->_search['text'] = array(); - } - $this->_search['text'][] = array( - 'text' => $text, - 'not' => $not, - 'type' => $bodyonly ? 'BODY' : 'TEXT' - ); - } - - /** - * Search for messages smaller/larger than a certain size. - * - * @param integer $size The size (in bytes). - * @param boolean $larger Search for messages larger than $size? - * @param boolean $not If true, do a 'NOT' search of $text. - */ - public function size($size, $larger = false, $not = false) - { - if (!isset($this->_search['size'])) { - $this->_search['size'] = array(); - } - $this->_search['size'][$larger ? 'LARGER' : 'SMALLER'] = array( - 'size' => (float)$size, - 'not' => $not - ); - } - - /** - * Search for messages within a given message range. Only one message - * range can be specified per query. - * - * @param array $ids The list of messages to search. - * @param boolean $sequence By default, $ids is assumed to be UIDs. If - * this param is true, $ids are taken to be - * message sequence numbers instead. - * @param boolean $not If true, do a 'NOT' search of the sequence. - */ - public function sequence($ids, $sequence = false, $not = false) - { - if (empty($ids)) { - $ids = '1:*'; - } else { - $ids = Horde_Imap_Client::toSequenceString($ids); - } - $this->_search['sequence'] = array( - 'ids' => $ids, - 'not' => $not, - 'sequence' => $sequence - ); - } - - /** - * Search for messages within a date range. Only one internal date and - * one RFC 2822 date can be specified per query. - * - * @param integer $month Month (from 1-12). - * @param integer $day Day of month (from 1-31). - * @param integer $year Year (4-digit year). - * @param string $range Either: - *
-     * Horde_Imap_Client_Search_Query::DATE_BEFORE,
-     * Horde_Imap_Client_Search_Query::DATE_ON, or
-     * Horde_Imap_Client_Search_Query::DATE_SINCE.
-     * 
- * @param boolean $header If true, search using the date in the message - * headers. If false, search using the internal - * IMAP date (usually arrival time). - * @param boolean $not If true, do a 'NOT' search of the range. - */ - public function dateSearch($month, $day, $year, $range, $header = true, - $not = false) - { - $type = $header ? 'header' : 'internal'; - if (!isset($this->_search['date'])) { - $this->_search['date'] = array(); - } - $this->_search['date'][$header ? 'header' : 'internal'] = array( - 'date' => date("d-M-y", mktime(0, 0, 0, $month, $day, $year)), - 'range' => $range, - 'not' => $not - ); - } - - /** - * Search for messages within a given interval. Only one interval of each - * type can be specified per search query. The IMAP server must support - * the WITHIN extension (RFC 5032) for this query to be used. - * - * @param integer $interval Seconds from the present. - * @param string $range Either: - *
-     * Horde_Imap_Client_Search_Query::INTERVAL_OLDER, or
-     * Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER
-     * 
- * @param boolean $not If true, do a 'NOT' search. - */ - public function intervalSearch($interval, $range, $not = false) - { - if (!isset($this->_search['within'])) { - $this->_search['within'] = array(); - } - $this->_search['within'][$range] = array( - 'interval' => $interval, - 'not' => $not - ); - } - - /** - * AND queries - the contents of this query will be AND'ed (in its - * entirety) with the contents of each of the queries passed in. All - * AND'd queries must share the same charset as this query. - * - * @param array $queries An array of queries to AND with this one. Each - * query is a Horde_Imap_Client_Search_Query - * object. - */ - public function andSearch($queries) - { - if (!isset($this->_search['and'])) { - $this->_search['and'] = array(); - } - $this->_search['and'] = array_merge($this->_search['and'], $queries); - } - - /** - * OR a query - the contents of this query will be OR'ed (in its entirety) - * with the contents of each of the queries passed in. All OR'd queries - * must share the same charset as this query. All contents of any single - * query will be AND'ed together. - * - * @param array $queries An array of queries to OR with this one. Each - * query is a Horde_Imap_Client_Search_Query - * object. - */ - public function orSearch($queries) - { - if (!isset($this->_search['or'])) { - $this->_search['or'] = array(); - } - $this->_search['or'] = array_merge($this->_search['or'], $queries); - } - - /** - * Search for messages modified since a specific moment. The IMAP server - * must support the CONDSTORE extension (RFC 4551) for this query to be - * used. - * - * @param integer $value The mod-sequence value. - * @param string $name The entry-name string. - * @param string $type Either 'shared', 'priv', or 'all'. Defaults to - * 'all' - * @param boolean $not If true, do a 'NOT' search. - */ - public function modseq($value, $name = null, $type = null, $not = false) - { - if (!is_null($type)) { - $type = strtolower($type); - if (!in_array($type, array('shared', 'priv', 'all'))) { - $type = 'all'; - } - } - - $this->_search['modseq'] = array( - 'value' => $value, - 'name' => $name, - 'not' => $not, - 'type' => (!is_null($name) && is_null($type)) ? 'all' : $type - ); - } - - /** - * Use the results from the previous SEARCH command. The IMAP server must - * support the SEARCHRES extension (RFC 5032) for this query to be used. - * - * @param boolean $not If true, don't match the previous query. - */ - public function previousSearch($not = false) - { - $this->_search['prevsearch'] = $not; - } -} - -/* - * A class allowing easy access to threaded sort results from - * Horde_Imap_Client::thread(). - */ -class Horde_Imap_Client_Thread -{ - /** - * Internal thread data structure. - * - * @var array - */ - protected $_thread = array(); - - /** - * The index type. - * - * @var string - */ - protected $_type; - - /** - * Constructor. - * - * @param array $data The data as returned by - * Horde_Imap_Client_Base::_thread(). - * @param string $type Either 'uid' or 'sequence'. - */ - function __construct($data, $type) - { - $this->_thread = $data; - $this->_type = $type; - } - - /** - * Return the raw thread data array. - * - * @return array See Horde_Imap_Client_Base::_thread(). - */ - public function getRawData() - { - return $this->_thread; - } - - /** - * Gets the indention level for an index. - * - * @param integer $index The index. - * - * @return mixed Returns the thread indent level if $index found. - * Returns false on failure. - */ - public function getThreadIndent($index) - { - return isset($this->_thread[$index]['level']) - ? $this->_thread[$index]['level'] - : false; - } - - /** - * Gets the base thread index for an index. - * - * @param integer $index The index. - * - * @return mixed Returns the base index if $index is part of a thread. - * Returns false on failure. - */ - public function getThreadBase($index) - { - return !empty($this->_thread[$index]['base']) - ? $this->_thread[$index]['base'] - : false; - } - - /** - * Is this index the last in the current level? - * - * @param integer $index The index. - * - * @return boolean Returns true if $index is the last element in the - * current thread level. - * Returns false if not, or on failure. - */ - public function lastInLevel($index) - { - return !empty($this->_thread[$index]['last']) - ? $this->_thread[$index]['last'] - : false; - } - - /** - * Return the sorted list of messages indices. - * - * @param boolean $new True for newest first, false for oldest first. - * - * @return array The sorted list of messages. - */ - public function messageList($new) - { - return ($new) ? array_reverse(array_keys($this->_thread)) : array_keys($this->_thread); - } - - /** - * Returns the list of messages in the current thread. - * - * @param integer $index The index of the current message. - * - * @return array A list of message indices. - */ - public function getThread($index) - { - /* Find the beginning of the thread. */ - if (($begin = $this->getThreadBase($index)) === false) { - return array($index); - } - - /* Work forward from the first thread element to find the end of the - * thread. */ - $in_thread = false; - $thread_list = array(); - reset($this->_thread); - while (list($k, $v) = each($this->_thread)) { - if ($k == $begin) { - $in_thread = true; - } elseif ($in_thread && ($v['base'] != $begin)) { - break; - } - - if ($in_thread) { - $thread_list[] = $k; - } - } - - return $thread_list; - } } diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Cache.php b/framework/Imap_Client/lib/Horde/Imap/Client/Cache.php index 907be0079..281e0c83d 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client/Cache.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Cache.php @@ -1,8 +1,4 @@ _cacheOb = &Horde_Cache::singleton($params['driver'], $params['driver_params']); + $this->_cacheOb = Horde_Cache::singleton($params['driver'], $params['driver_params']); if (is_a($this->_cacheOb, 'PEAR_Error')) { throw new Horde_Imap_Client_Exception($this->_cacheOb->getMessage()); } diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Search/Query.php b/framework/Imap_Client/lib/Horde/Imap/Client/Search/Query.php new file mode 100644 index 000000000..549097694 --- /dev/null +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Search/Query.php @@ -0,0 +1,505 @@ +_charset = strtoupper($charset); + } + + /** + * Builds an IMAP4rev1 compliant search string. + * + * @return array An array with 3 elements: + *
+     * 'charset' - (string) The charset of the search string.
+     * 'imap4' - (boolean) True if the search uses IMAP4 criteria (as opposed
+     *           to IMAP2 search criteria)
+     * 'query' - (string) The IMAP search string
+     * 
+ */ + public function build() + { + $cmds = array(); + $imap4 = false; + $ptr = &$this->_search; + + if (isset($ptr['new'])) { + if ($ptr['new']) { + $cmds[] = 'NEW'; + unset($ptr['flag']['UNSEEN']); + } else { + $cmds[] = 'OLD'; + } + unset($ptr['flag']['RECENT']); + } + + if (!empty($ptr['flag'])) { + foreach ($ptr['flag'] as $key => $val) { + if ($key == 'draft') { + // DRAFT flag was not in IMAP2 + $imap4 = true; + } + + $tmp = ''; + if (!$val['set']) { + // This is a 'NOT' search. All system flags but \Recent + // have 'UN' equivalents. + if ($key == 'RECENT') { + $tmp = 'NOT '; + // NOT searches were not in IMAP2 + $imap4 = true; + } else { + $tmp = 'UN'; + } + } + + $cmds[] = $tmp . ($val['type'] == 'keyword' ? 'KEYWORD ' : '') . $key; + } + } + + if (!empty($ptr['header'])) { + foreach ($ptr['header'] as $val) { + $tmp = ''; + if ($val['not']) { + $tmp = 'NOT '; + // NOT searches were not in IMAP2 + $imap4 = true; + } + + if (!in_array($val['header'], $this->_systemheaders)) { + // HEADER searches were not in IMAP2 + $tmp .= 'HEADER '; + $imap4 = true; + } + $cmds[] = $tmp . $val['header'] . ' ' . Horde_Imap_Client::escape($val['text']); + } + } + + if (!empty($ptr['text'])) { + foreach ($ptr['text'] as $val) { + $tmp = ''; + if ($val['not']) { + $tmp = 'NOT '; + // NOT searches were not in IMAP2 + $imap4 = true; + } + $cmds[] = $tmp . $val['type'] . ' ' . Horde_Imap_Client::escape($val['text']); + } + } + + if (!empty($ptr['size'])) { + foreach ($ptr['size'] as $key => $val) { + $cmds[] = ($val['not'] ? 'NOT ' : '' ) . $key . ' ' . $val['size']; + // LARGER/SMALLER searches were not in IMAP2 + $imap4 = true; + } + } + + if (isset($ptr['sequence'])) { + $cmds[] = ($ptr['sequence']['not'] ? 'NOT ' : '') . ($ptr['sequence']['sequence'] ? '' : 'UID ') . $ptr['sequence']['ids']; + + // sequence searches were not in IMAP2 + $imap4 = true; + } + + if (!empty($ptr['date'])) { + foreach ($ptr['date'] as $key => $val) { + $tmp = ''; + if ($val['not']) { + $tmp = 'NOT '; + // NOT searches were not in IMAP2 + $imap4 = true; + } + + if ($key == 'header') { + $tmp .= 'SENT'; + // 'SENT*' searches were not in IMAP2 + $imap4 = true; + } + $cmds[] = $tmp . $val['range'] . ' ' . $val['date']; + } + } + + if (!empty($ptr['within'])) { + $imap4 = true; + $this->_exts['WITHIN'] = true; + + foreach ($ptr['within'] as $key => $val) { + $cmds[] = ($val['not'] ? 'NOT ' : '') . $key . ' ' . $val['interval']; + } + } + + if (!empty($ptr['modseq'])) { + $imap4 = true; + $this->_exts['CONDSTORE'] = true; + $cmds[] = ($ptr['modseq']['not'] ? 'NOT ' : '') . + 'MODSEQ ' . + (is_null($ptr['modseq']['name']) + ? '' + : Horde_Imap_Client::escape($ptr['modseq']['name']) . ' ' . $ptr['modseq']['type'] . ' ') . + $ptr['modseq']['value']; + } + + if (isset($ptr['prevsearch'])) { + $imap4 = true; + $this->_exts['SEARCHRES'] = true; + $cmds[] = ($ptr['prevsearch'] ? '' : 'NOT ') . '$'; + } + + $query = ''; + + // Add OR'ed queries + if (!empty($ptr['or'])) { + foreach ($ptr['or'] as $key => $val) { + // OR queries were not in IMAP 2 + $imap4 = true; + + if ($key == 0) { + $query = '(' . $query . ')'; + } + + $ret = $val->build(); + $query = 'OR (' . $ret['query'] . ') ' . $query; + } + } + + // Add AND'ed queries + if (!empty($ptr['and'])) { + foreach ($ptr['and'] as $key => $val) { + $ret = $val->build(); + $query .= ' ' . $ret['query']; + } + } + + // Default search is 'ALL' + if (empty($cmds)) { + $query .= empty($query) ? 'ALL' : ''; + } else { + $query .= implode(' ', $cmds); + } + + return array( + 'charset' => $this->_charset, + 'imap4' => $imap4, + 'query' => trim($query) + ); + } + + /** + * Return the list of any IMAP extensions needed to perform the query. + * + * @return array The list of extensions (CAPABILITY responses) needed to + * perform the query. + */ + public function extensionsNeeded() + { + return $this->_exts; + } + + /** + * Search for a flag/keywords. + * + * @param string $name The flag or keyword name. + * @param boolean $set If true, search for messages that have the flag + * set. If false, search for messages that do not + * have the flag set. + */ + public function flag($name, $set = true) + { + $name = strtoupper(ltrim($name, '\\')); + if (!isset($this->_search['flag'])) { + $this->_search['flag'] = array(); + } + $this->_search['flag'][$name] = array( + 'set' => $set, + 'type' => in_array($name, $this->_systemflags) ? 'flag' : 'keyword' + ); + } + + /** + * Search for either new messages (messages that have the '\Recent' flag + * but not the '\Seen' flag) or old messages (messages that do not have + * the '\Recent' flag). If new messages are searched, this will clear + * any '\Recent' or '\Unseen' flag searches. If old messages are searched, + * this will clear any '\Recent' flag search. + * + * @param boolean $newmsgs If true, searches for new messages. Else, + * search for old messages. + */ + public function newMsgs($newmsgs = true) + { + $this->_search['new'] = $newmsgs; + } + + /** + * Search for text in the header of a message. + * + * @param string $header The header field. + * @param string $text The search text. + * @param boolean $not If true, do a 'NOT' search of $text. + */ + public function headerText($header, $text, $not = false) + { + if (!isset($this->_search['header'])) { + $this->_search['header'] = array(); + } + $this->_search['header'][] = array( + 'header' => strtoupper($header), + 'text' => $text, + 'not' => $not + ); + } + + /** + * Search for text in either the entire message, or just the body. + * + * @param string $text The search text. + * @param string $bodyonly If true, only search in the body of the + * message. If false, also search in the headers. + * @param boolean $not If true, do a 'NOT' search of $text. + */ + public function text($text, $bodyonly = true, $not = false) + { + if (!isset($this->_search['text'])) { + $this->_search['text'] = array(); + } + $this->_search['text'][] = array( + 'text' => $text, + 'not' => $not, + 'type' => $bodyonly ? 'BODY' : 'TEXT' + ); + } + + /** + * Search for messages smaller/larger than a certain size. + * + * @param integer $size The size (in bytes). + * @param boolean $larger Search for messages larger than $size? + * @param boolean $not If true, do a 'NOT' search of $text. + */ + public function size($size, $larger = false, $not = false) + { + if (!isset($this->_search['size'])) { + $this->_search['size'] = array(); + } + $this->_search['size'][$larger ? 'LARGER' : 'SMALLER'] = array( + 'size' => (float)$size, + 'not' => $not + ); + } + + /** + * Search for messages within a given message range. Only one message + * range can be specified per query. + * + * @param array $ids The list of messages to search. + * @param boolean $sequence By default, $ids is assumed to be UIDs. If + * this param is true, $ids are taken to be + * message sequence numbers instead. + * @param boolean $not If true, do a 'NOT' search of the sequence. + */ + public function sequence($ids, $sequence = false, $not = false) + { + if (empty($ids)) { + $ids = '1:*'; + } else { + $ids = Horde_Imap_Client::toSequenceString($ids); + } + $this->_search['sequence'] = array( + 'ids' => $ids, + 'not' => $not, + 'sequence' => $sequence + ); + } + + /** + * Search for messages within a date range. Only one internal date and + * one RFC 2822 date can be specified per query. + * + * @param integer $month Month (from 1-12). + * @param integer $day Day of month (from 1-31). + * @param integer $year Year (4-digit year). + * @param string $range Either: + *
+     * Horde_Imap_Client_Search_Query::DATE_BEFORE,
+     * Horde_Imap_Client_Search_Query::DATE_ON, or
+     * Horde_Imap_Client_Search_Query::DATE_SINCE.
+     * 
+ * @param boolean $header If true, search using the date in the message + * headers. If false, search using the internal + * IMAP date (usually arrival time). + * @param boolean $not If true, do a 'NOT' search of the range. + */ + public function dateSearch($month, $day, $year, $range, $header = true, + $not = false) + { + $type = $header ? 'header' : 'internal'; + if (!isset($this->_search['date'])) { + $this->_search['date'] = array(); + } + $this->_search['date'][$header ? 'header' : 'internal'] = array( + 'date' => date("d-M-y", mktime(0, 0, 0, $month, $day, $year)), + 'range' => $range, + 'not' => $not + ); + } + + /** + * Search for messages within a given interval. Only one interval of each + * type can be specified per search query. The IMAP server must support + * the WITHIN extension (RFC 5032) for this query to be used. + * + * @param integer $interval Seconds from the present. + * @param string $range Either: + *
+     * Horde_Imap_Client_Search_Query::INTERVAL_OLDER, or
+     * Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER
+     * 
+ * @param boolean $not If true, do a 'NOT' search. + */ + public function intervalSearch($interval, $range, $not = false) + { + if (!isset($this->_search['within'])) { + $this->_search['within'] = array(); + } + $this->_search['within'][$range] = array( + 'interval' => $interval, + 'not' => $not + ); + } + + /** + * AND queries - the contents of this query will be AND'ed (in its + * entirety) with the contents of each of the queries passed in. All + * AND'd queries must share the same charset as this query. + * + * @param array $queries An array of queries to AND with this one. Each + * query is a Horde_Imap_Client_Search_Query + * object. + */ + public function andSearch($queries) + { + if (!isset($this->_search['and'])) { + $this->_search['and'] = array(); + } + $this->_search['and'] = array_merge($this->_search['and'], $queries); + } + + /** + * OR a query - the contents of this query will be OR'ed (in its entirety) + * with the contents of each of the queries passed in. All OR'd queries + * must share the same charset as this query. All contents of any single + * query will be AND'ed together. + * + * @param array $queries An array of queries to OR with this one. Each + * query is a Horde_Imap_Client_Search_Query + * object. + */ + public function orSearch($queries) + { + if (!isset($this->_search['or'])) { + $this->_search['or'] = array(); + } + $this->_search['or'] = array_merge($this->_search['or'], $queries); + } + + /** + * Search for messages modified since a specific moment. The IMAP server + * must support the CONDSTORE extension (RFC 4551) for this query to be + * used. + * + * @param integer $value The mod-sequence value. + * @param string $name The entry-name string. + * @param string $type Either 'shared', 'priv', or 'all'. Defaults to + * 'all' + * @param boolean $not If true, do a 'NOT' search. + */ + public function modseq($value, $name = null, $type = null, $not = false) + { + if (!is_null($type)) { + $type = strtolower($type); + if (!in_array($type, array('shared', 'priv', 'all'))) { + $type = 'all'; + } + } + + $this->_search['modseq'] = array( + 'value' => $value, + 'name' => $name, + 'not' => $not, + 'type' => (!is_null($name) && is_null($type)) ? 'all' : $type + ); + } + + /** + * Use the results from the previous SEARCH command. The IMAP server must + * support the SEARCHRES extension (RFC 5032) for this query to be used. + * + * @param boolean $not If true, don't match the previous query. + */ + public function previousSearch($not = false) + { + $this->_search['prevsearch'] = $not; + } + +} diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php index 4a7495fb7..e4bc3e375 100644 --- a/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Socket.php @@ -495,12 +495,18 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base switch ($method) { case 'CRAM-MD5': // RFC 2195 + if (class_exists('Auth_SASL')) { + throw new Horde_Imap_Client_Exception('The Auth_SASL package is required for CRAM-MD5 authentication'); + } $auth_sasl = Auth_SASL::factory('crammd5'); $response = base64_encode($auth_sasl->getResponse($this->_params['username'], $this->_params['password'], base64_decode($ob['line']))); $this->_sendLine($response, array('debug' => '[CRAM-MD5 Response]', 'notag' => true)); break; case 'DIGEST-MD5': + if (class_exists('Auth_SASL')) { + throw new Horde_Imap_Client_Exception('The Auth_SASL package is required for DIGEST-MD5 authentication'); + } $auth_sasl = Auth_SASL::factory('digestmd5'); $response = base64_encode($auth_sasl->getResponse($this->_params['username'], $this->_params['password'], base64_decode($ob['line']), $this->_params['hostspec'], 'imap')); $ob = $this->_sendLine($response, array('debug' => '[DIGEST-MD5 Response]', 'noparse' => true, 'notag' => true)); diff --git a/framework/Imap_Client/lib/Horde/Imap/Client/Thread.php b/framework/Imap_Client/lib/Horde/Imap/Client/Thread.php new file mode 100644 index 000000000..f73acce2b --- /dev/null +++ b/framework/Imap_Client/lib/Horde/Imap/Client/Thread.php @@ -0,0 +1,137 @@ +_thread = $data; + $this->_type = $type; + } + + /** + * Return the raw thread data array. + * + * @return array See Horde_Imap_Client_Base::_thread(). + */ + public function getRawData() + { + return $this->_thread; + } + + /** + * Gets the indention level for an index. + * + * @param integer $index The index. + * + * @return mixed Returns the thread indent level if $index found. + * Returns false on failure. + */ + public function getThreadIndent($index) + { + return isset($this->_thread[$index]['level']) + ? $this->_thread[$index]['level'] + : false; + } + + /** + * Gets the base thread index for an index. + * + * @param integer $index The index. + * + * @return mixed Returns the base index if $index is part of a thread. + * Returns false on failure. + */ + public function getThreadBase($index) + { + return !empty($this->_thread[$index]['base']) + ? $this->_thread[$index]['base'] + : false; + } + + /** + * Is this index the last in the current level? + * + * @param integer $index The index. + * + * @return boolean Returns true if $index is the last element in the + * current thread level. + * Returns false if not, or on failure. + */ + public function lastInLevel($index) + { + return !empty($this->_thread[$index]['last']) + ? $this->_thread[$index]['last'] + : false; + } + + /** + * Return the sorted list of messages indices. + * + * @param boolean $new True for newest first, false for oldest first. + * + * @return array The sorted list of messages. + */ + public function messageList($new) + { + return ($new) ? array_reverse(array_keys($this->_thread)) : array_keys($this->_thread); + } + + /** + * Returns the list of messages in the current thread. + * + * @param integer $index The index of the current message. + * + * @return array A list of message indices. + */ + public function getThread($index) + { + /* Find the beginning of the thread. */ + if (($begin = $this->getThreadBase($index)) === false) { + return array($index); + } + + /* Work forward from the first thread element to find the end of the + * thread. */ + $in_thread = false; + $thread_list = array(); + reset($this->_thread); + while (list($k, $v) = each($this->_thread)) { + if ($k == $begin) { + $in_thread = true; + } elseif ($in_thread && ($v['base'] != $begin)) { + break; + } + + if ($in_thread) { + $thread_list[] = $k; + } + } + + return $thread_list; + } + +} -- 2.11.0