do my best to make sure autoloading is used here
authorChuck Hagenbuch <chuck@horde.org>
Sat, 27 Dec 2008 04:12:45 +0000 (23:12 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Sat, 27 Dec 2008 04:12:45 +0000 (23:12 -0500)
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
framework/Imap_Client/lib/Horde/Imap/Client/Base.php
framework/Imap_Client/lib/Horde/Imap/Client/Cache.php
framework/Imap_Client/lib/Horde/Imap/Client/Search/Query.php [new file with mode: 0644]
framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
framework/Imap_Client/lib/Horde/Imap/Client/Thread.php [new file with mode: 0644]

index 7cf7153..8ff3da3 100644 (file)
@@ -1,9 +1,4 @@
 <?php
-
-require_once dirname(__FILE__) . '/Client/Base.php';
-require_once dirname(__FILE__) . '/Client/Exception.php';
-require_once dirname(__FILE__) . '/Client/Utf7imap.php';
-
 /**
  * Horde_Imap_Client:: provides an abstracted API interface to various IMAP
  * backends (RFC 3501).
@@ -151,36 +146,6 @@ class Horde_Imap_Client
     static public $encryptKey = null;
 
     /**
-     * Autoload handler.
-     */
-    static public function autoload($classname)
-    {
-        $res = false;
-
-        $old_error = error_reporting(0);
-        switch ($classname) {
-        case 'Horde_Mime':
-            $res = require_once 'Horde/Mime.php';
-            break;
-
-        case 'Horde_Mime_Headers':
-            $res = require_once 'Horde/Mime/Headers.php';
-            break;
-
-        case 'Horde_Mime_Part':
-            $res = require_once 'Horde/Mime/Part.php';
-            break;
-
-        case 'Secret':
-            $res = require_once 'Horde/Secret.php';
-            break;
-        }
-        error_reporting($old_error);
-
-        return $res;
-    }
-
-    /**
      * Attempts to return a concrete Horde_Imap_Client instance based on
      * $driver.
      * Throws a Horde_Imap_Client_Exception on error.
@@ -196,12 +161,6 @@ class Horde_Imap_Client
     {
         $class = 'Horde_Imap_Client_' . strtr(basename($driver), '-', '_');
         if (!class_exists($class)) {
-            $fname = dirname(__FILE__) . '/Client/' . $driver . '.php';
-            if (is_file($fname)) {
-                require_once $fname;
-            }
-        }
-        if (!class_exists($class)) {
             throw new Horde_Imap_Client_Exception('Driver ' . $driver . ' not found', Horde_Imap_Client_Exception::DRIVER_NOT_FOUND);
         }
         return new $class($params);
@@ -550,6 +509,5 @@ class Horde_Imap_Client
 
         return $ret_array;
     }
-}
 
-spl_autoload_register(array('Horde_Imap_Client_Base', 'autoload'));
+}
index 8b5136a..6f578f5 100644 (file)
@@ -189,8 +189,7 @@ abstract class Horde_Imap_Client_Base extends Horde_Imap_Client
 
         if (is_null($this->_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:
-     * <pre>
-     * '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
-     * </pre>
-     */
-    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:
-     * <pre>
-     * Horde_Imap_Client_Search_Query::DATE_BEFORE,
-     * Horde_Imap_Client_Search_Query::DATE_ON, or
-     * Horde_Imap_Client_Search_Query::DATE_SINCE.
-     * </pre>
-     * @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:
-     * <pre>
-     * Horde_Imap_Client_Search_Query::INTERVAL_OLDER, or
-     * Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER
-     * </pre>
-     * @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;
-    }
 }
index 907be00..281e0c8 100644 (file)
@@ -1,8 +1,4 @@
 <?php
-
-require_once 'Horde/Cache.php';
-require_once 'Horde/Serialize.php';
-
 /**
  * Horde_Imap_Client_Cache:: provides an interface to cache various data
  * retrieved from the IMAP server.
@@ -86,19 +82,15 @@ class Horde_Imap_Client_Cache
     /**
      * Return a reference to a concrete Horde_Imap_Client_Cache instance.
      *
-     * This method must be invoked as:
-     *   $var = &IMP_MessageCache::singleton();
-     *
      * @param array $params  The configuration parameters.
      *
      * @return Horde_Imap_Client_Cache  The global instance.
      */
-    static public function &singleton($params = array())
+    static public function singleton($params = array())
     {
         static $instance = array();
 
         $sig = md5(serialize($params));
-
         if (!isset($instance[$sig])) {
             $instance[$sig] = new Horde_Imap_Client_Cache($params);
         }
@@ -122,7 +114,7 @@ class Horde_Imap_Client_Cache
         }
 
         /* Initialize the Cache object. */
-        $this->_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 (file)
index 0000000..5490976
--- /dev/null
@@ -0,0 +1,505 @@
+<?php
+/**
+ * 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:
+     * <pre>
+     * '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
+     * </pre>
+     */
+    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:
+     * <pre>
+     * Horde_Imap_Client_Search_Query::DATE_BEFORE,
+     * Horde_Imap_Client_Search_Query::DATE_ON, or
+     * Horde_Imap_Client_Search_Query::DATE_SINCE.
+     * </pre>
+     * @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:
+     * <pre>
+     * Horde_Imap_Client_Search_Query::INTERVAL_OLDER, or
+     * Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER
+     * </pre>
+     * @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;
+    }
+
+}
index 4a7495f..e4bc3e3 100644 (file)
@@ -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 (file)
index 0000000..f73acce
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+/**
+ * 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;
+    }
+
+}