/lib/horde/framework/Horde/Imap/Client/Base.php
PHP | 4082 lines | 1821 code | 428 blank | 1833 comment | 255 complexity | 352de1c56e0596fa41d6654dba822b6c MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
- <?php
- /**
- * Copyright 2008-2017 Horde LLC (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (LGPL). If you
- * did not receive this file, see http://www.horde.org/licenses/lgpl21.
- *
- * @category Horde
- * @copyright 2008-2017 Horde LLC
- * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
- * @package Imap_Client
- */
- /**
- * An abstracted API interface to IMAP backends supporting the IMAP4rev1
- * protocol (RFC 3501).
- *
- * @author Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @copyright 2008-2017 Horde LLC
- * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
- * @package Imap_Client
- *
- * @property-read Horde_Imap_Client_Base_Alert $alerts_ob
- The alert reporting object (@since 2.26.0)
- * @property-read Horde_Imap_Client_Data_Capability $capability
- * A capability object. (@since 2.24.0)
- * @property-read Horde_Imap_Client_Data_SearchCharset $search_charset
- * A search charset object. (@since 2.24.0)
- * @property-read Horde_Imap_Client_Url $url The URL object for the current
- * connection parameters (@since 2.24.0)
- */
- abstract class Horde_Imap_Client_Base
- implements Serializable, SplObserver
- {
- /** Serialized version. */
- const VERSION = 3;
- /** Cache names for miscellaneous data. */
- const CACHE_MODSEQ = '_m';
- const CACHE_SEARCH = '_s';
- /* @since 2.9.0 */
- const CACHE_SEARCHID = '_i';
- /** Cache names used exclusively within this class. @since 2.11.0 */
- const CACHE_DOWNGRADED = 'HICdg';
- /**
- * The list of fetch fields that can be cached, and their cache names.
- *
- * @var array
- */
- public $cacheFields = array(
- Horde_Imap_Client::FETCH_ENVELOPE => 'HICenv',
- Horde_Imap_Client::FETCH_FLAGS => 'HICflags',
- Horde_Imap_Client::FETCH_HEADERS => 'HIChdrs',
- Horde_Imap_Client::FETCH_IMAPDATE => 'HICdate',
- Horde_Imap_Client::FETCH_SIZE => 'HICsize',
- Horde_Imap_Client::FETCH_STRUCTURE => 'HICstruct'
- );
- /**
- * Has the internal configuration changed?
- *
- * @var boolean
- */
- public $changed = false;
- /**
- * Horde_Imap_Client is optimized for short (i.e. 1 seconds) scripts. It
- * makes heavy use of mailbox caching to save on server accesses. This
- * property should be set to false for long-running scripts, or else
- * status() data may not reflect the current state of the mailbox on the
- * server.
- *
- * @since 2.14.0
- *
- * @var boolean
- */
- public $statuscache = true;
- /**
- * Alerts reporting object.
- *
- * @var Horde_Imap_Client_Base_Alerts
- */
- protected $_alerts;
- /**
- * The Horde_Imap_Client_Cache object.
- *
- * @var Horde_Imap_Client_Cache
- */
- protected $_cache = null;
- /**
- * Connection to the IMAP server.
- *
- * @var Horde\Socket\Client
- */
- protected $_connection = null;
- /**
- * The debug object.
- *
- * @var Horde_Imap_Client_Base_Debug
- */
- protected $_debug = null;
- /**
- * The default ports to use for a connection.
- * First element is non-secure, second is SSL.
- *
- * @var array
- */
- protected $_defaultPorts = array();
- /**
- * The fetch data object type to return.
- *
- * @var string
- */
- protected $_fetchDataClass = 'Horde_Imap_Client_Data_Fetch';
- /**
- * Cached server data.
- *
- * @var array
- */
- protected $_init;
- /**
- * Is there an active authenticated connection to the IMAP Server?
- *
- * @var boolean
- */
- protected $_isAuthenticated = false;
- /**
- * The current mailbox selection mode.
- *
- * @var integer
- */
- protected $_mode = 0;
- /**
- * Hash containing connection parameters.
- * This hash never changes.
- *
- * @var array
- */
- protected $_params = array();
- /**
- * The currently selected mailbox.
- *
- * @var Horde_Imap_Client_Mailbox
- */
- protected $_selected = null;
- /**
- * Temp array (destroyed at end of process).
- *
- * @var array
- */
- protected $_temp = array();
- /**
- * Constructor.
- *
- * @param array $params Configuration parameters:
- * <pre>
- * - cache: (array) If set, caches data from fetch(), search(), and
- * thread() calls. Requires the horde/Cache package to be
- * installed. The array can contain the following keys (see
- * Horde_Imap_Client_Cache for default values):
- * - backend: [REQUIRED (or cacheob)] (Horde_Imap_Client_Cache_Backend)
- * Backend cache driver [@since 2.9.0].
- * - fetch_ignore: (array) A list of mailboxes to ignore when storing
- * fetch data.
- * - fields: (array) The fetch criteria to cache. If not defined, all
- * cacheable data is cached. The following is a list of
- * criteria that can be cached:
- * - Horde_Imap_Client::FETCH_ENVELOPE
- * - Horde_Imap_Client::FETCH_FLAGS
- * Only if server supports CONDSTORE extension
- * - Horde_Imap_Client::FETCH_HEADERS
- * Only for queries that specifically request caching
- * - Horde_Imap_Client::FETCH_IMAPDATE
- * - Horde_Imap_Client::FETCH_SIZE
- * - Horde_Imap_Client::FETCH_STRUCTURE
- * - capability_ignore: (array) A list of IMAP capabilites to ignore, even
- * if they are supported on the server.
- * DEFAULT: No supported capabilities are ignored.
- * - comparator: (string) The search comparator to use instead of the
- * default server comparator. See setComparator() for
- * format.
- * DEFAULT: Use the server default
- * - context: (array) Any context parameters passed to
- * stream_create_context(). @since 2.27.0
- * - debug: (string) If set, will output debug information to the stream
- * provided. The value can be any PHP supported wrapper that can
- * be opened via PHP's fopen() function.
- * DEFAULT: No debug output
- * - hostspec: (string) The hostname or IP address of the server.
- * DEFAULT: 'localhost'
- * - id: (array) Send ID information to the server (only if server
- * supports the ID extension). An array with the keys as the fields
- * to send and the values being the associated values. See RFC 2971
- * [3.3] for a list of standard field values.
- * DEFAULT: No info sent to server
- * - lang: (array) A list of languages (in priority order) to be used to
- * display human readable messages.
- * DEFAULT: Messages output in IMAP server default language
- * - password: (mixed) The user password. Either a string or a
- * Horde_Imap_Client_Base_Password object [@since 2.14.0].
- * - port: (integer) The server port to which we will connect.
- * DEFAULT: 143 (imap or imap w/TLS) or 993 (imaps)
- * - secure: (string) Use SSL or TLS to connect. Values:
- * - false (No encryption)
- * - 'ssl' (Auto-detect SSL version)
- * - 'sslv2' (Force SSL version 3)
- * - 'sslv3' (Force SSL version 2)
- * - 'tls' (TLS; started via protocol-level negotation over
- * unencrypted channel; RECOMMENDED way of initiating secure
- * connection)
- * - 'tlsv1' (TLS direct version 1.x connection to server) [@since
- * 2.16.0]
- * - true (TLS if available/necessary) [@since 2.15.0]
- * DEFAULT: false
- * - timeout: (integer) Connection timeout, in seconds.
- * DEFAULT: 30 seconds
- * - username: (string) [REQUIRED] The username.
- * - authusername (string) The username used for SASL authentication.
- * If specified this is the user name whose password is used
- * (e.g. administrator).
- * Only valid for RFC 2595/4616 - PLAIN SASL mechanism.
- * DEFAULT: the same value provided in the username parameter.
- * </pre>
- */
- public function __construct(array $params = array())
- {
- if (!isset($params['username'])) {
- throw new InvalidArgumentException('Horde_Imap_Client requires a username.');
- }
- $this->_setInit();
- // Default values.
- $params = array_merge(array(
- 'context' => array(),
- 'hostspec' => 'localhost',
- 'secure' => false,
- 'timeout' => 30
- ), array_filter($params));
- if (!isset($params['port']) && strpos($params['hostspec'], 'unix://') !== 0) {
- $params['port'] = (!empty($params['secure']) && in_array($params['secure'], array('ssl', 'sslv2', 'sslv3'), true))
- ? $this->_defaultPorts[1]
- : $this->_defaultPorts[0];
- }
- if (empty($params['cache'])) {
- $params['cache'] = array('fields' => array());
- } elseif (empty($params['cache']['fields'])) {
- $params['cache']['fields'] = $this->cacheFields;
- } else {
- $params['cache']['fields'] = array_flip($params['cache']['fields']);
- }
- if (empty($params['cache']['fetch_ignore'])) {
- $params['cache']['fetch_ignore'] = array();
- }
- $this->_params = $params;
- if (isset($params['password'])) {
- $this->setParam('password', $params['password']);
- }
- $this->changed = true;
- $this->_initOb();
- }
- /**
- * Get encryption key.
- *
- * @deprecated Pass callable into 'password' parameter instead.
- *
- * @return string The encryption key.
- */
- protected function _getEncryptKey()
- {
- if (is_callable($ekey = $this->getParam('encryptKey'))) {
- return call_user_func($ekey);
- }
- throw new InvalidArgumentException('encryptKey parameter is not a valid callback.');
- }
- /**
- * Do initialization tasks.
- */
- protected function _initOb()
- {
- register_shutdown_function(array($this, 'shutdown'));
- $this->_alerts = new Horde_Imap_Client_Base_Alerts();
- // @todo: Remove (BC)
- $this->_alerts->attach($this);
- $this->_debug = ($debug = $this->getParam('debug'))
- ? new Horde_Imap_Client_Base_Debug($debug)
- : new Horde_Support_Stub();
- // @todo: Remove (BC purposes)
- if (isset($this->_init['capability']) &&
- !is_object($this->_init['capability'])) {
- $this->_setInit('capability');
- }
- foreach (array('capability', 'search_charset') as $val) {
- if (isset($this->_init[$val])) {
- $this->_init[$val]->attach($this);
- }
- }
- }
- /**
- * Shutdown actions.
- */
- public function shutdown()
- {
- try {
- $this->logout();
- } catch (Horde_Imap_Client_Exception $e) {
- }
- }
- /**
- * This object can not be cloned.
- */
- public function __clone()
- {
- throw new LogicException('Object cannot be cloned.');
- }
- /**
- */
- public function update(SplSubject $subject)
- {
- if (($subject instanceof Horde_Imap_Client_Data_Capability) ||
- ($subject instanceof Horde_Imap_Client_Data_SearchCharset)) {
- $this->changed = true;
- }
- /* @todo: BC - remove */
- if ($subject instanceof Horde_Imap_Client_Base_Alerts) {
- $this->_temp['alerts'][] = $subject->getLast()->alert;
- }
- }
- /**
- */
- public function serialize()
- {
- return serialize(array(
- 'i' => $this->_init,
- 'p' => $this->_params,
- 'v' => self::VERSION
- ));
- }
- /**
- */
- public function unserialize($data)
- {
- $data = @unserialize($data);
- if (!is_array($data) ||
- !isset($data['v']) ||
- ($data['v'] != self::VERSION)) {
- throw new Exception('Cache version change');
- }
- $this->_init = $data['i'];
- $this->_params = $data['p'];
- $this->_initOb();
- }
- /**
- */
- public function __get($name)
- {
- switch ($name) {
- case 'alerts_ob':
- return $this->_alerts;
- case 'capability':
- return $this->_capability();
- case 'search_charset':
- if (!isset($this->_init['search_charset'])) {
- $this->_init['search_charset'] = new Horde_Imap_Client_Data_SearchCharset();
- $this->_init['search_charset']->attach($this);
- }
- $this->_init['search_charset']->setBaseOb($this);
- return $this->_init['search_charset'];
- case 'url':
- $url = new Horde_Imap_Client_Url();
- $url->hostspec = $this->getParam('hostspec');
- $url->port = $this->getParam('port');
- $url->protocol = 'imap';
- return $url;
- }
- }
- /**
- * Set an initialization value.
- *
- * @param string $key The initialization key. If null, resets all keys.
- * @param mixed $val The cached value. If null, removes the key.
- */
- public function _setInit($key = null, $val = null)
- {
- if (is_null($key)) {
- $this->_init = array();
- } elseif (is_null($val)) {
- unset($this->_init[$key]);
- } else {
- switch ($key) {
- case 'capability':
- if ($ci = $this->getParam('capability_ignore')) {
- $ignored = array();
- foreach ($ci as $val2) {
- $c = explode('=', $val2);
- if ($val->query($c[0], isset($c[1]) ? $c[1] : null)) {
- $ignored[] = $val2;
- $val->remove($c[0], isset($c[1]) ? $c[1] : null);
- }
- }
- if ($this->_debug->debug && !empty($ignored)) {
- $this->_debug->info(sprintf(
- 'CONFIG: IGNORING these IMAP capabilities: %s',
- implode(', ', $ignored)
- ));
- }
- }
- $val->attach($this);
- break;
- }
- /* Nothing has changed. */
- if (isset($this->_init[$key]) && ($this->_init[$key] === $val)) {
- return;
- }
- $this->_init[$key] = $val;
- }
- $this->changed = true;
- }
- /**
- * Initialize the Horde_Imap_Client_Cache object, if necessary.
- *
- * @param boolean $current If true, we are going to update the currently
- * selected mailbox. Add an additional check to
- * see if caching is available in current
- * mailbox.
- *
- * @return boolean Returns true if caching is enabled.
- */
- protected function _initCache($current = false)
- {
- $c = $this->getParam('cache');
- if (empty($c['fields'])) {
- return false;
- }
- if (is_null($this->_cache)) {
- if (isset($c['backend'])) {
- $backend = $c['backend'];
- } elseif (isset($c['cacheob'])) {
- /* Deprecated */
- $backend = new Horde_Imap_Client_Cache_Backend_Cache($c);
- } else {
- return false;
- }
- $this->_cache = new Horde_Imap_Client_Cache(array(
- 'backend' => $backend,
- 'baseob' => $this,
- 'debug' => $this->_debug
- ));
- }
- return $current
- /* If UIDs are labeled as not sticky, don't cache since UIDs will
- * change on every access. */
- ? !($this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_UIDNOTSTICKY))
- : true;
- }
- /**
- * Returns a value from the internal params array.
- *
- * @param string $key The param key.
- *
- * @return mixed The param value, or null if not found.
- */
- public function getParam($key)
- {
- /* Passwords may be stored encrypted. */
- switch ($key) {
- case 'password':
- if (isset($this->_params[$key]) &&
- ($this->_params[$key] instanceof Horde_Imap_Client_Base_Password)) {
- return $this->_params[$key]->getPassword();
- }
- // DEPRECATED
- if (!empty($this->_params['_passencrypt'])) {
- try {
- $secret = new Horde_Secret();
- return $secret->read($this->_getEncryptKey(), $this->_params['password']);
- } catch (Exception $e) {
- return null;
- }
- }
- break;
- }
- return isset($this->_params[$key])
- ? $this->_params[$key]
- : null;
- }
- /**
- * Sets a configuration parameter value.
- *
- * @param string $key The param key.
- * @param mixed $val The param value.
- */
- public function setParam($key, $val)
- {
- switch ($key) {
- case 'password':
- if ($val instanceof Horde_Imap_Client_Base_Password) {
- break;
- }
- // DEPRECATED: Encrypt password.
- try {
- $encrypt_key = $this->_getEncryptKey();
- if (strlen($encrypt_key)) {
- $secret = new Horde_Secret();
- $val = $secret->write($encrypt_key, $val);
- $this->_params['_passencrypt'] = true;
- }
- } catch (Exception $e) {}
- break;
- }
- $this->_params[$key] = $val;
- $this->changed = true;
- }
- /**
- * Returns the Horde_Imap_Client_Cache object used, if available.
- *
- * @return mixed Either the cache object or null.
- */
- public function getCache()
- {
- $this->_initCache();
- return $this->_cache;
- }
- /**
- * Returns the correct IDs object for use with this driver.
- *
- * @param mixed $ids Either self::ALL, self::SEARCH_RES, self::LARGEST,
- * Horde_Imap_Client_Ids object, array, or sequence
- * string.
- * @param boolean $sequence Are $ids message sequence numbers?
- *
- * @return Horde_Imap_Client_Ids The IDs object.
- */
- public function getIdsOb($ids = null, $sequence = false)
- {
- return new Horde_Imap_Client_Ids($ids, $sequence);
- }
- /**
- * Returns whether the IMAP server supports the given capability
- * (See RFC 3501 [6.1.1]).
- *
- * @deprecated Use $capability property instead.
- *
- * @param string $capability The capability string to query.
- *
- * @return mixed True if the server supports the queried capability,
- * false if it doesn't, or an array if the capability can
- * contain multiple values.
- */
- public function queryCapability($capability)
- {
- try {
- $c = $this->_capability();
- return ($out = $c->getParams($capability))
- ? $out
- : $c->query($capability);
- } catch (Horde_Imap_Client_Exception $e) {
- return false;
- }
- }
- /**
- * Get CAPABILITY information from the IMAP server.
- *
- * @deprecated Use $capability property instead.
- *
- * @return array The capability array.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function capability()
- {
- return $this->_capability()->toArray();
- }
- /**
- * Query server capability.
- *
- * Required because internal code can't call capability via magic method
- * directly - it may not exist yet, the creation code may call capability
- * recursively, and __get() doesn't allow recursive calls to the same
- * property (chicken/egg issue).
- *
- * @return mixed The capability object if no arguments provided. If
- * arguments are provided, they are passed to the query()
- * method and this value is returned.
- * @throws Horde_Imap_Client_Exception
- */
- protected function _capability()
- {
- if (!isset($this->_init['capability'])) {
- $this->_initCapability();
- }
- return ($args = func_num_args())
- ? $this->_init['capability']->query(func_get_arg(0), ($args > 1) ? func_get_arg(1) : null)
- : $this->_init['capability'];
- }
- /**
- * Retrieve capability information from the IMAP server.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _initCapability();
- /**
- * Send a NOOP command (RFC 3501 [6.1.2]).
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function noop()
- {
- if (!$this->_connection) {
- // NOOP can be called in the unauthenticated state.
- $this->_connect();
- }
- $this->_noop();
- }
- /**
- * Send a NOOP command.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _noop();
- /**
- * Get the NAMESPACE information from the IMAP server (RFC 2342).
- *
- * @param array $additional If the server supports namespaces, any
- * additional namespaces to add to the
- * namespace list that are not broadcast by
- * the server. The namespaces must be UTF-8
- * strings.
- * @param array $opts Additional options:
- * - ob_return: (boolean) If true, returns a
- * Horde_Imap_Client_Namespace_List object instead of an
- * array.
- *
- * @return mixed A Horde_Imap_Client_Namespace_List object if
- * 'ob_return', is true. Otherwise, an array of namespace
- * objects (@deprecated) with the name as the key (UTF-8)
- * and the following values:
- * <pre>
- * - delimiter: (string) The namespace delimiter.
- * - hidden: (boolean) Is this a hidden namespace?
- * - name: (string) The namespace name (UTF-8).
- * - translation: (string) Returns the translated name of the namespace
- * (UTF-8). Requires RFC 5255 and a previous call to
- * setLanguage().
- * - type: (integer) The namespace type. Either:
- * - Horde_Imap_Client::NS_PERSONAL
- * - Horde_Imap_Client::NS_OTHER
- * - Horde_Imap_Client::NS_SHARED
- * </pre>
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function getNamespaces(
- array $additional = array(), array $opts = array()
- )
- {
- $additional = array_map('strval', $additional);
- $sig = hash(
- 'md5',
- json_encode($additional) . intval(empty($opts['ob_return']))
- );
- if (isset($this->_init['namespace'][$sig])) {
- $ns = $this->_init['namespace'][$sig];
- } else {
- $this->login();
- $ns = $this->_getNamespaces();
- /* Skip namespaces if we have already auto-detected them. Also,
- * hidden namespaces cannot be empty. */
- $to_process = array_diff(array_filter($additional, 'strlen'), array_map('strlen', iterator_to_array($ns)));
- if (!empty($to_process)) {
- foreach ($this->listMailboxes($to_process, Horde_Imap_Client::MBOX_ALL, array('delimiter' => true)) as $key => $val) {
- $ob = new Horde_Imap_Client_Data_Namespace();
- $ob->delimiter = $val['delimiter'];
- $ob->hidden = true;
- $ob->name = $key;
- $ob->type = $ob::NS_SHARED;
- $ns[$val] = $ob;
- }
- }
- if (!count($ns)) {
- /* This accurately determines the namespace information of the
- * base namespace if the NAMESPACE command is not supported.
- * See: RFC 3501 [6.3.8] */
- $mbox = $this->listMailboxes('', Horde_Imap_Client::MBOX_ALL, array('delimiter' => true));
- $first = reset($mbox);
- $ob = new Horde_Imap_Client_Data_Namespace();
- $ob->delimiter = $first['delimiter'];
- $ns[''] = $ob;
- }
- $this->_init['namespace'][$sig] = $ns;
- $this->_setInit('namespace', $this->_init['namespace']);
- }
- if (!empty($opts['ob_return'])) {
- return $ns;
- }
- /* @todo Remove for 3.0 */
- $out = array();
- foreach ($ns as $key => $val) {
- $out[$key] = array(
- 'delimiter' => $val->delimiter,
- 'hidden' => $val->hidden,
- 'name' => $val->name,
- 'translation' => $val->translation,
- 'type' => $val->type
- );
- }
- return $out;
- }
- /**
- * Get the NAMESPACE information from the IMAP server.
- *
- * @return Horde_Imap_Client_Namespace_List Namespace list object.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getNamespaces();
- /**
- * Display if connection to the server has been secured via TLS or SSL.
- *
- * @return boolean True if the IMAP connection is secured.
- */
- public function isSecureConnection()
- {
- return ($this->_connection && $this->_connection->secure);
- }
- /**
- * Connect to the remote server.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _connect();
- /**
- * Return a list of alerts that MUST be presented to the user (RFC 3501
- * [7.1]).
- *
- * @deprecated Add an observer to the $alerts_ob property instead.
- *
- * @return array An array of alert messages.
- */
- public function alerts()
- {
- $alerts = isset($this->_temp['alerts'])
- ? $this->_temp['alerts']
- : array();
- unset($this->_temp['alerts']);
- return $alerts;
- }
- /**
- * Login to the IMAP server.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function login()
- {
- if (!$this->_isAuthenticated && $this->_login()) {
- if ($this->getParam('id')) {
- try {
- $this->sendID();
- /* ID is queued - force sending the queued command. */
- $this->_sendCmd($this->_pipeline());
- } catch (Horde_Imap_Client_Exception_NoSupportExtension $e) {
- // Ignore if server doesn't support ID extension.
- }
- }
- if ($this->getParam('comparator')) {
- try {
- $this->setComparator();
- } catch (Horde_Imap_Client_Exception_NoSupportExtension $e) {
- // Ignore if server doesn't support I18NLEVEL=2
- }
- }
- }
- $this->_isAuthenticated = true;
- }
- /**
- * Login to the IMAP server.
- *
- * @return boolean Return true if global login tasks should be run.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _login();
- /**
- * Logout from the IMAP server (see RFC 3501 [6.1.3]).
- */
- public function logout()
- {
- if ($this->_isAuthenticated && $this->_connection->connected) {
- $this->_logout();
- $this->_connection->close();
- }
- $this->_connection = $this->_selected = null;
- $this->_isAuthenticated = false;
- $this->_mode = 0;
- }
- /**
- * Logout from the IMAP server (see RFC 3501 [6.1.3]).
- */
- abstract protected function _logout();
- /**
- * Send ID information to the IMAP server (RFC 2971).
- *
- * @param array $info Overrides the value of the 'id' param and sends
- * this information instead.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function sendID($info = null)
- {
- if (!$this->_capability('ID')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('ID');
- }
- $this->_sendID(is_null($info) ? ($this->getParam('id') ?: array()) : $info);
- }
- /**
- * Send ID information to the IMAP server (RFC 2971).
- *
- * @param array $info The information to send to the server.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _sendID($info);
- /**
- * Return ID information from the IMAP server (RFC 2971).
- *
- * @return array An array of information returned, with the keys as the
- * 'field' and the values as the 'value'.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function getID()
- {
- if (!$this->_capability('ID')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('ID');
- }
- return $this->_getID();
- }
- /**
- * Return ID information from the IMAP server (RFC 2971).
- *
- * @return array An array of information returned, with the keys as the
- * 'field' and the values as the 'value'.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getID();
- /**
- * Sets the preferred language for server response messages (RFC 5255).
- *
- * @param array $langs Overrides the value of the 'lang' param and sends
- * this list of preferred languages instead. The
- * special string 'i-default' can be used to restore
- * the language to the server default.
- *
- * @return string The language accepted by the server, or null if the
- * default language is used.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function setLanguage($langs = null)
- {
- $lang = null;
- if ($this->_capability('LANGUAGE')) {
- $lang = is_null($langs)
- ? $this->getParam('lang')
- : $langs;
- }
- return is_null($lang)
- ? null
- : $this->_setLanguage($lang);
- }
- /**
- * Sets the preferred language for server response messages (RFC 5255).
- *
- * @param array $langs The preferred list of languages.
- *
- * @return string The language accepted by the server, or null if the
- * default language is used.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _setLanguage($langs);
- /**
- * Gets the preferred language for server response messages (RFC 5255).
- *
- * @param array $list If true, return the list of available languages.
- *
- * @return mixed If $list is true, the list of languages available on the
- * server (may be empty). If false, the language used by
- * the server, or null if the default language is used.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function getLanguage($list = false)
- {
- if (!$this->_capability('LANGUAGE')) {
- return $list ? array() : null;
- }
- return $this->_getLanguage($list);
- }
- /**
- * Gets the preferred language for server response messages (RFC 5255).
- *
- * @param array $list If true, return the list of available languages.
- *
- * @return mixed If $list is true, the list of languages available on the
- * server (may be empty). If false, the language used by
- * the server, or null if the default language is used.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getLanguage($list);
- /**
- * Open a mailbox.
- *
- * @param mixed $mailbox The mailbox to open. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param integer $mode The access mode. Either
- * - Horde_Imap_Client::OPEN_READONLY
- * - Horde_Imap_Client::OPEN_READWRITE
- * - Horde_Imap_Client::OPEN_AUTO
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function openMailbox($mailbox, $mode = Horde_Imap_Client::OPEN_AUTO)
- {
- $this->login();
- $change = false;
- $mailbox = Horde_Imap_Client_Mailbox::get($mailbox);
- if ($mode == Horde_Imap_Client::OPEN_AUTO) {
- if (is_null($this->_selected) ||
- !$mailbox->equals($this->_selected)) {
- $mode = Horde_Imap_Client::OPEN_READONLY;
- $change = true;
- }
- } else {
- $change = (is_null($this->_selected) ||
- !$mailbox->equals($this->_selected) ||
- ($mode != $this->_mode));
- }
- if ($change) {
- $this->_openMailbox($mailbox, $mode);
- $this->_mailboxOb()->open = true;
- if ($this->_initCache(true)) {
- $this->_condstoreSync();
- }
- }
- }
- /**
- * Open a mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to open.
- * @param integer $mode The access mode.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _openMailbox(Horde_Imap_Client_Mailbox $mailbox,
- $mode);
- /**
- * Called when the selected mailbox is changed.
- *
- * @param mixed $mailbox The selected mailbox or null.
- * @param integer $mode The access mode.
- */
- protected function _changeSelected($mailbox = null, $mode = null)
- {
- $this->_mode = $mode;
- if (is_null($mailbox)) {
- $this->_selected = null;
- } else {
- $this->_selected = clone $mailbox;
- $this->_mailboxOb()->reset();
- }
- }
- /**
- * Return the Horde_Imap_Client_Base_Mailbox object.
- *
- * @param string $mailbox The mailbox name. Defaults to currently
- * selected mailbox.
- *
- * @return Horde_Imap_Client_Base_Mailbox Mailbox object.
- */
- protected function _mailboxOb($mailbox = null)
- {
- $name = is_null($mailbox)
- ? strval($this->_selected)
- : strval($mailbox);
- if (!isset($this->_temp['mailbox_ob'][$name])) {
- $this->_temp['mailbox_ob'][$name] = new Horde_Imap_Client_Base_Mailbox();
- }
- return $this->_temp['mailbox_ob'][$name];
- }
- /**
- * Return the currently opened mailbox and access mode.
- *
- * @return mixed Null if no mailbox selected, or an array with two
- * elements:
- * - mailbox: (Horde_Imap_Client_Mailbox) The mailbox object.
- * - mode: (integer) Current mode.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function currentMailbox()
- {
- return is_null($this->_selected)
- ? null
- : array(
- 'mailbox' => clone $this->_selected,
- 'mode' => $this->_mode
- );
- }
- /**
- * Create a mailbox.
- *
- * @param mixed $mailbox The mailbox to create. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param array $opts Additional options:
- * - special_use: (array) An array of special-use flags to mark the
- * mailbox with. The server MUST support RFC 6154.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function createMailbox($mailbox, array $opts = array())
- {
- $this->login();
- if (!$this->_capability('CREATE-SPECIAL-USE')) {
- unset($opts['special_use']);
- }
- $this->_createMailbox(Horde_Imap_Client_Mailbox::get($mailbox), $opts);
- }
- /**
- * Create a mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to create.
- * @param array $opts Additional options. See
- * createMailbox().
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _createMailbox(Horde_Imap_Client_Mailbox $mailbox,
- $opts);
- /**
- * Delete a mailbox.
- *
- * @param mixed $mailbox The mailbox to delete. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function deleteMailbox($mailbox)
- {
- $this->login();
- $mailbox = Horde_Imap_Client_Mailbox::get($mailbox);
- $this->_deleteMailbox($mailbox);
- $this->_deleteMailboxPost($mailbox);
- }
- /**
- * Delete a mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to delete.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _deleteMailbox(Horde_Imap_Client_Mailbox $mailbox);
- /**
- * Actions to perform after a mailbox delete.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The deleted mailbox.
- */
- protected function _deleteMailboxPost(Horde_Imap_Client_Mailbox $mailbox)
- {
- /* Delete mailbox caches. */
- if ($this->_initCache()) {
- $this->_cache->deleteMailbox($mailbox);
- }
- unset($this->_temp['mailbox_ob'][strval($mailbox)]);
- /* Unsubscribe from mailbox. */
- try {
- $this->subscribeMailbox($mailbox, false);
- } catch (Horde_Imap_Client_Exception $e) {
- // Ignore failed unsubscribe request
- }
- }
- /**
- * Rename a mailbox.
- *
- * @param mixed $old The old mailbox name. Either a
- * Horde_Imap_Client_Mailbox object or a string (UTF-8).
- * @param mixed $new The new mailbox name. Either a
- * Horde_Imap_Client_Mailbox object or a string (UTF-8).
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function renameMailbox($old, $new)
- {
- // Login will be handled by first listMailboxes() call.
- $old = Horde_Imap_Client_Mailbox::get($old);
- $new = Horde_Imap_Client_Mailbox::get($new);
- /* Check if old mailbox(es) were subscribed to. */
- $base = $this->listMailboxes($old, Horde_Imap_Client::MBOX_SUBSCRIBED, array('delimiter' => true));
- if (empty($base)) {
- $base = $this->listMailboxes($old, Horde_Imap_Client::MBOX_ALL, array('delimiter' => true));
- $base = reset($base);
- $subscribed = array();
- } else {
- $base = reset($base);
- $subscribed = array($base['mailbox']);
- }
- $all_mboxes = array($base['mailbox']);
- if (strlen($base['delimiter'])) {
- $search = $old->list_escape . $base['delimiter'] . '*';
- $all_mboxes = array_merge($all_mboxes, $this->listMailboxes($search, Horde_Imap_Client::MBOX_ALL, array('flat' => true)));
- $subscribed = array_merge($subscribed, $this->listMailboxes($search, Horde_Imap_Client::MBOX_SUBSCRIBED, array('flat' => true)));
- }
- $this->_renameMailbox($old, $new);
- /* Delete mailbox actions. */
- foreach ($all_mboxes as $val) {
- $this->_deleteMailboxPost($val);
- }
- foreach ($subscribed as $val) {
- try {
- $this->subscribeMailbox(new Horde_Imap_Client_Mailbox(substr_replace($val, $new, 0, strlen($old))));
- } catch (Horde_Imap_Client_Exception $e) {
- // Ignore failed subscription requests
- }
- }
- }
- /**
- * Rename a mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $old The old mailbox name.
- * @param Horde_Imap_Client_Mailbox $new The new mailbox name.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _renameMailbox(Horde_Imap_Client_Mailbox $old,
- Horde_Imap_Client_Mailbox $new);
- /**
- * Manage subscription status for a mailbox.
- *
- * @param mixed $mailbox The mailbox to [un]subscribe to. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param boolean $subscribe True to subscribe, false to unsubscribe.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function subscribeMailbox($mailbox, $subscribe = true)
- {
- $this->login();
- $this->_subscribeMailbox(Horde_Imap_Client_Mailbox::get($mailbox), (bool)$subscribe);
- }
- /**
- * Manage subscription status for a mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to [un]subscribe
- * to.
- * @param boolean $subscribe True to subscribe, false to
- * unsubscribe.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _subscribeMailbox(Horde_Imap_Client_Mailbox $mailbox,
- $subscribe);
- /**
- * Obtain a list of mailboxes matching a pattern.
- *
- * @param mixed $pattern The mailbox search pattern(s) (see RFC 3501
- * [6.3.8] for the format). A UTF-8 string or an
- * array of strings. If a Horde_Imap_Client_Mailbox
- * object is given, it is escaped (i.e. wildcard
- * patterns are converted to return the miminal
- * number of matches possible).
- * @param integer $mode Which mailboxes to return. Either:
- * - Horde_Imap_Client::MBOX_SUBSCRIBED
- * Return subscribed mailboxes.
- * - Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS
- * Return subscribed mailboxes that exist on the server.
- * - Horde_Imap_Client::MBOX_UNSUBSCRIBED
- * Return unsubscribed mailboxes.
- * - Horde_Imap_Client::MBOX_ALL
- * Return all mailboxes regardless of subscription status.
- * - Horde_Imap_Client::MBOX_ALL_SUBSCRIBED (@since 2.23.0)
- * Return all mailboxes regardless of subscription status, and ensure
- * the '\subscribed' attribute is set if mailbox is subscribed
- * (implies 'attributes' option is true).
- * @param array $options Additional options:
- * <pre>
- * - attributes: (boolean) If true, return attribute information under
- * the 'attributes' key.
- * DEFAULT: Do not return this information.
- * - children: (boolean) Tell server to return children attribute
- * information (\HasChildren, \HasNoChildren). Requires the
- * LIST-EXTENDED extension to guarantee this information is
- * returned. Server MAY return this attribute without this
- * option, or if the CHILDREN extension is available, but it
- * is not guaranteed.
- * DEFAULT: false
- * - flat: (boolean) If true, return a flat list of mailbox names only.
- * Overrides the 'attributes' option.
- * DEFAULT: Do not return flat list.
- * - recursivematch: (boolean) Force the server to return information
- * about parent mailboxes that don't match other
- * selection options, but have some sub-mailboxes that
- * do. Information about children is returned in the
- * CHILDINFO extended data item ('extended'). Requires
- * the LIST-EXTENDED extension.
- * DEFAULT: false
- * - remote: (boolean) Tell server to return mailboxes that reside on
- * another server. Requires the LIST-EXTENDED extension.
- * DEFAULT: false
- * - special_use: (boolean) Tell server to return special-use attribute
- * information (see Horde_Imap_Client SPECIALUSE_*
- * constants). Server must support the SPECIAL-USE return
- * option for this setting to have any effect.
- * DEFAULT: false
- * - status: (integer) Tell server to return status information. The
- * value is a bitmask that may contain any of:
- * - Horde_Imap_Client::STATUS_MESSAGES
- * - Horde_Imap_Client::STATUS_RECENT
- * - Horde_Imap_Client::STATUS_UIDNEXT
- * - Horde_Imap_Client::STATUS_UIDVALIDITY
- * - Horde_Imap_Client::STATUS_UNSEEN
- * - Horde_Imap_Client::STATUS_HIGHESTMODSEQ
- * DEFAULT: 0
- * - sort: (boolean) If true, return a sorted list of mailboxes?
- * DEFAULT: Do not sort the list.
- * - sort_delimiter: (string) If 'sort' is true, this is the delimiter
- * used to sort the mailboxes.
- * DEFAULT: '.'
- * </pre>
- *
- * @return array If 'flat' option is true, the array values are a list
- * of Horde_Imap_Client_Mailbox objects. Otherwise, the
- * keys are UTF-8 mailbox names and the values are arrays
- * with these keys:
- * - attributes: (array) List of lower-cased attributes [only if
- * 'attributes' option is true].
- * - delimiter: (string) The delimiter for the mailbox.
- * - extended: (TODO) TODO [only if 'recursivematch' option is true and
- * LIST-EXTENDED extension is supported on the server].
- * - mailbox: (Horde_Imap_Client_Mailbox) The mailbox object.
- * - status: (array) See status() [only if 'status' option is true].
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function listMailboxes($pattern,
- $mode = Horde_Imap_Client::MBOX_ALL,
- array $options = array())
- {
- $this->login();
- $pattern = is_array($pattern)
- ? array_unique($pattern)
- : array($pattern);
- /* Prepare patterns. */
- $plist = array();
- foreach ($pattern as $val) {
- if ($val instanceof Horde_Imap_Client_Mailbox) {
- $val = $val->list_escape;
- }
- $plist[] = Horde_Imap_Client_Mailbox::get(preg_replace(
- array("/\*{2,}/", "/\%{2,}/"),
- array('*', '%'),
- Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($val)
- ), true);
- }
- if (isset($options['special_use']) &&
- !$this->_capability('SPECIAL-USE')) {
- unset($options['special_use']);
- }
- $ret = $this->_listMailboxes($plist, $mode, $options);
- if (!empty($options['status']) &&
- !$this->_capability('LIST-STATUS')) {
- foreach ($this->status(array_keys($ret), $options['status']) as $key => $val) {
- $ret[$key]['status'] = $val;
- }
- }
- if (empty($options['sort'])) {
- return $ret;
- }
- $list_ob = new Horde_Imap_Client_Mailbox_List(empty($options['flat']) ? array_keys($ret) : $ret);
- $sorted = $list_ob->sort(array(
- 'delimiter' => empty($options['sort_delimiter']) ? '.' : $options['sort_delimiter']
- ));
- if (!empty($options['flat'])) {
- return $sorted;
- }
- $out = array();
- foreach ($sorted as $val) {
- $out[$val] = $ret[$val];
- }
- return $out;
- }
- /**
- * Obtain a list of mailboxes matching a pattern.
- *
- * @param array $pattern The mailbox search patterns
- * (Horde_Imap_Client_Mailbox objects).
- * @param integer $mode Which mailboxes to return.
- * @param array $options Additional options.
- *
- * @return array See listMailboxes().
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _listMailboxes($pattern, $mode, $options);
- /**
- * Obtain status information for a mailbox.
- *
- * @param mixed $mailbox The mailbox(es) to query. Either a
- * Horde_Imap_Client_Mailbox object, a string
- * (UTF-8), or an array of objects/strings (since
- * 2.10.0).
- * @param integer $flags A bitmask of information requested from the
- * server. Allowed flags:
- * <pre>
- * - Horde_Imap_Client::STATUS_MESSAGES
- * Return key: messages
- * Return format: (integer) The number of messages in the mailbox.
- *
- * - Horde_Imap_Client::STATUS_RECENT
- * Return key: recent
- * Return format: (integer) The number of messages with the \Recent
- * flag set as currently reported in the mailbox
- *
- * - Horde_Imap_Client::STATUS_RECENT_TOTAL
- * Return key: recent_total
- * Return format: (integer) The number of messages with the \Recent
- * flag set. This returns the total number of messages
- * that have been marked as recent in this mailbox
- * since the PHP process began. (since 2.12.0)
- *
- * - Horde_Imap_Client::STATUS_UIDNEXT
- * Return key: uidnext
- * Return format: (integer) The next UID to be assigned in the
- * mailbox. Only returned if the server automatically
- * provides the data.
- *
- * - Horde_Imap_Client::STATUS_UIDNEXT_FORCE
- * Return key: uidnext
- * Return format: (integer) The next UID to be assigned in the
- * mailbox. This option will always determine this
- * value, even if the server does not automatically
- * provide this data.
- *
- * - Horde_Imap_Client::STATUS_UIDVALIDITY
- * Return key: uidvalidity
- * Return format: (integer) The unique identifier validity of the
- * mailbox.
- *
- * - Horde_Imap_Client::STATUS_UNSEEN
- * Return key: unseen
- * Return format: (integer) The number of messages which do not have
- * the \Seen flag set.
- *
- * - Horde_Imap_Client::STATUS_FIRSTUNSEEN
- * Return key: firstunseen
- * Return format: (integer) The sequence number of the first unseen
- * message in the mailbox.
- *
- * - Horde_Imap_Client::STATUS_FLAGS
- * Return key: flags
- * Return format: (array) The list of defined flags in the mailbox
- * (all flags are in lowercase).
- *
- * - Horde_Imap_Client::STATUS_PERMFLAGS
- * Return key: permflags
- * Return format: (array) The list of flags that a client can change
- * permanently (all flags are in lowercase).
- *
- * - Horde_Imap_Client::STATUS_HIGHESTMODSEQ
- * Return key: highestmodseq
- * Return format: (integer) If the server supports the CONDSTORE
- * IMAP extension, this will be the highest
- * mod-sequence value of all messages in the mailbox.
- * Else 0 if CONDSTORE not available or the mailbox
- * does not support mod-sequences.
- *
- * - Horde_Imap_Client::STATUS_SYNCMODSEQ
- * Return key: syncmodseq
- * Return format: (integer) If caching, and the server supports the
- * CONDSTORE IMAP extension, this is the cached
- * mod-sequence value of the mailbox when it was opened
- * for the first time in this access. Will be null if
- * not caching, CONDSTORE not available, or the mailbox
- * does not support mod-sequences.
- *
- * - Horde_Imap_Client::STATUS_SYNCFLAGUIDS
- * Return key: syncflaguids
- * Return format: (Horde_Imap_Client_Ids) If caching, the server
- * supports the CONDSTORE IMAP extension, and the
- * mailbox contained cached data when opened for the
- * first time in this access, this is the list of UIDs
- * in which flags have changed since STATUS_SYNCMODSEQ.
- *
- * - Horde_Imap_Client::STATUS_SYNCVANISHED
- * Return key: syncvanished
- * Return format: (Horde_Imap_Client_Ids) If caching, the server
- * supports the CONDSTORE IMAP extension, and the
- * mailbox contained cached data when opened for the
- * first time in this access, this is the list of UIDs
- * which have been deleted since STATUS_SYNCMODSEQ.
- *
- * - Horde_Imap_Client::STATUS_UIDNOTSTICKY
- * Return key: uidnotsticky
- * Return format: (boolean) If the server supports the UIDPLUS IMAP
- * extension, and the queried mailbox does not support
- * persistent UIDs, this value will be true. In all
- * other cases, this value will be false.
- *
- * - Horde_Imap_Client::STATUS_FORCE_REFRESH
- * Normally, the status information will be cached for a given
- * mailbox. Since most PHP requests are generally less than a second,
- * this is fine. However, if your script is long running, the status
- * information may not be up-to-date. Specifying this flag will ensure
- * that the server is always polled for the current mailbox status
- * before results are returned. (since 2.14.0)
- *
- * - Horde_Imap_Client::STATUS_ALL (DEFAULT)
- * Shortcut to return 'messages', 'recent', 'uidnext', 'uidvalidity',
- * and 'unseen' values.
- * </ul>
- * @param array $opts Additional options:
- * <pre>
- * - sort: (boolean) If true, sort the list of mailboxes? (since 2.10.0)
- * DEFAULT: Do not sort the list.
- * - sort_delimiter: (string) If 'sort' is true, this is the delimiter
- * used to sort the mailboxes. (since 2.10.0)
- * DEFAULT: '.'
- * </pre>
- *
- * @return array If $mailbox contains multiple mailboxes, an array with
- * keys being the UTF-8 mailbox name and values as arrays
- * containing the requested keys (see above).
- * Otherwise, an array with keys as the requested keys (see
- * above) and values as the key data.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function status($mailbox, $flags = Horde_Imap_Client::STATUS_ALL,
- array $opts = array())
- {
- $opts = array_merge(array(
- 'sort' => false,
- 'sort_delimiter' => '.'
- ), $opts);
- $this->login();
- if (is_array($mailbox)) {
- if (empty($mailbox)) {
- return array();
- }
- $ret_array = true;
- } else {
- $mailbox = array($mailbox);
- $ret_array = false;
- }
- $mlist = array_map(array('Horde_Imap_Client_Mailbox', 'get'), $mailbox);
- $unselected_flags = array(
- 'messages' => Horde_Imap_Client::STATUS_MESSAGES,
- 'recent' => Horde_Imap_Client::STATUS_RECENT,
- 'uidnext' => Horde_Imap_Client::STATUS_UIDNEXT,
- 'uidvalidity' => Horde_Imap_Client::STATUS_UIDVALIDITY,
- 'unseen' => Horde_Imap_Client::STATUS_UNSEEN
- );
- if (!$this->statuscache) {
- $flags |= Horde_Imap_Client::STATUS_FORCE_REFRESH;
- }
- if ($flags & Horde_Imap_Client::STATUS_ALL) {
- foreach ($unselected_flags as $val) {
- $flags |= $val;
- }
- }
- $master = $ret = array();
- /* Catch flags that are not supported. */
- if (($flags & Horde_Imap_Client::STATUS_HIGHESTMODSEQ) &&
- !$this->_capability()->isEnabled('CONDSTORE')) {
- $master['highestmodseq'] = 0;
- $flags &= ~Horde_Imap_Client::STATUS_HIGHESTMODSEQ;
- }
- if (($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY) &&
- !$this->_capability('UIDPLUS')) {
- $master['uidnotsticky'] = false;
- $flags &= ~Horde_Imap_Client::STATUS_UIDNOTSTICKY;
- }
- /* UIDNEXT return options. */
- if ($flags & Horde_Imap_Client::STATUS_UIDNEXT_FORCE) {
- $flags |= Horde_Imap_Client::STATUS_UIDNEXT;
- }
- foreach ($mlist as $val) {
- $name = strval($val);
- $tmp_flags = $flags;
- if ($val->equals($this->_selected)) {
- /* Check if already in mailbox. */
- $opened = true;
- if ($flags & Horde_Imap_Client::STATUS_FORCE_REFRESH) {
- $this->noop();
- }
- } else {
- /* A list of STATUS options (other than those handled directly
- * below) that require the mailbox to be explicitly opened. */
- $opened = ($flags & Horde_Imap_Client::STATUS_FIRSTUNSEEN) ||
- ($flags & Horde_Imap_Client::STATUS_FLAGS) ||
- ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) ||
- ($flags & Horde_Imap_Client::STATUS_UIDNOTSTICKY) ||
- /* Force mailboxes containing wildcards to be accessed via
- * STATUS so that wildcards do not return a bunch of
- * mailboxes in the LIST-STATUS response. */
- (strpbrk($name, '*%') !== false);
- }
- $ret[$name] = $master;
- $ptr = &$ret[$name];
- /* STATUS_PERMFLAGS requires a read/write mailbox. */
- if ($flags & Horde_Imap_Client::STATUS_PERMFLAGS) {
- $this->openMailbox($val, Horde_Imap_Client::OPEN_READWRITE);
- $opened = true;
- }
- /* Handle SYNC related return options. These require the mailbox
- * to be opened at least once. */
- if ($flags & Horde_Imap_Client::STATUS_SYNCMODSEQ) {
- $this->openMailbox($val);
- $ptr['syncmodseq'] = $this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCMODSEQ);
- $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCMODSEQ;
- $opened = true;
- }
- if ($flags & Horde_Imap_Client::STATUS_SYNCFLAGUIDS) {
- $this->openMailbox($val);
- $ptr['syncflaguids'] = $this->getIdsOb($this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCFLAGUIDS));
- $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCFLAGUIDS;
- $opened = true;
- }
- if ($flags & Horde_Imap_Client::STATUS_SYNCVANISHED) {
- $this->openMailbox($val);
- $ptr['syncvanished'] = $this->getIdsOb($this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_SYNCVANISHED));
- $tmp_flags &= ~Horde_Imap_Client::STATUS_SYNCVANISHED;
- $opened = true;
- }
- /* Handle RECENT_TOTAL option. */
- if ($flags & Horde_Imap_Client::STATUS_RECENT_TOTAL) {
- $this->openMailbox($val);
- $ptr['recent_total'] = $this->_mailboxOb($val)->getStatus(Horde_Imap_Client::STATUS_RECENT_TOTAL);
- $tmp_flags &= ~Horde_Imap_Client::STATUS_RECENT_TOTAL;
- $opened = true;
- }
- if ($opened) {
- if ($tmp_flags) {
- $tmp = $this->_status(array($val), $tmp_flags);
- $ptr += reset($tmp);
- }
- } else {
- $to_process[] = $val;
- }
- }
- if ($flags && !empty($to_process)) {
- if ((count($to_process) > 1) &&
- $this->_capability('LIST-STATUS')) {
- foreach ($this->listMailboxes($to_process, Horde_Imap_Client::MBOX_ALL, array('status' => $flags)) as $key => $val) {
- if (isset($val['status'])) {
- $ret[$key] += $val['status'];
- }
- }
- } else {
- foreach ($this->_status($to_process, $flags) as $key => $val) {
- $ret[$key] += $val;
- }
- }
- }
- if (!$opts['sort'] || (count($ret) === 1)) {
- return $ret_array
- ? $ret
- : reset($ret);
- }
- $list_ob = new Horde_Imap_Client_Mailbox_List(array_keys($ret));
- $sorted = $list_ob->sort(array(
- 'delimiter' => $opts['sort_delimiter']
- ));
- $out = array();
- foreach ($sorted as $val) {
- $out[$val] = $ret[$val];
- }
- return $out;
- }
- /**
- * Obtain status information for mailboxes.
- *
- * @param array $mboxes The list of mailbox objects to query.
- * @param integer $flags A bitmask of information requested from the
- * server.
- *
- * @return array See array return for status().
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _status($mboxes, $flags);
- /**
- * Perform a STATUS call on multiple mailboxes at the same time.
- *
- * This method leverages the LIST-EXTENDED and LIST-STATUS extensions on
- * the IMAP server to improve the efficiency of this operation.
- *
- * @deprecated Use status() instead.
- *
- * @param array $mailboxes The mailboxes to query. Either
- * Horde_Imap_Client_Mailbox objects, strings
- * (UTF-8), or a combination of the two.
- * @param integer $flags See status().
- * @param array $opts Additional options:
- * - sort: (boolean) If true, sort the list of mailboxes?
- * DEFAULT: Do not sort the list.
- * - sort_delimiter: (string) If 'sort' is true, this is the delimiter
- * used to sort the mailboxes.
- * DEFAULT: '.'
- *
- * @return array An array with the keys as the mailbox names (UTF-8) and
- * the values as arrays with the requested keys (from the
- * mask given in $flags).
- */
- public function statusMultiple($mailboxes,
- $flags = Horde_Imap_Client::STATUS_ALL,
- array $opts = array())
- {
- return $this->status($mailboxes, $flags, $opts);
- }
- /**
- * Append message(s) to a mailbox.
- *
- * @param mixed $mailbox The mailbox to append the message(s) to. Either
- * a Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param array $data The message data to append, along with
- * additional options. An array of arrays with
- * each embedded array having the following
- * entries:
- * <pre>
- * - data: (mixed) The data to append. If a string or a stream resource,
- * this will be used as the entire contents of a single message.
- * If an array, will catenate all given parts into a single
- * message. This array contains one or more arrays with
- * two keys:
- * - t: (string) Either 'url' or 'text'.
- * - v: (mixed) If 't' is 'url', this is the IMAP URL to the message
- * part to append. If 't' is 'text', this is either a string or
- * resource representation of the message part data.
- * DEFAULT: NONE (entry is MANDATORY)
- * - flags: (array) An array of flags/keywords to set on the appended
- * message.
- * DEFAULT: Only the \Recent flag is set.
- * - internaldate: (DateTime) The internaldate to set for the appended
- * message.
- * DEFAULT: internaldate will be the same date as when
- * the message was appended.
- * </pre>
- * @param array $options Additonal options:
- * <pre>
- * - create: (boolean) Try to create $mailbox if it does not exist?
- * DEFAULT: No.
- * </pre>
- *
- * @return Horde_Imap_Client_Ids The UIDs of the appended messages.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function append($mailbox, $data, array $options = array())
- {
- $this->login();
- $mailbox = Horde_Imap_Client_Mailbox::get($mailbox);
- $ret = $this->_append($mailbox, $data, $options);
- if ($ret instanceof Horde_Imap_Client_Ids) {
- return $ret;
- }
- $uids = $this->getIdsOb();
- foreach ($data as $val) {
- if (is_resource($val['data'])) {
- rewind($val['data']);
- }
- $uids->add($this->_getUidByMessageId(
- $mailbox,
- Horde_Mime_Headers::parseHeaders($val['data'])->getHeader('Message-ID')
- ));
- }
- return $uids;
- }
- /**
- * Append message(s) to a mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to append the
- * message(s) to.
- * @param array $data The message data.
- * @param array $options Additional options.
- *
- * @return mixed A Horde_Imap_Client_Ids object containing the UIDs of
- * the appended messages (if server supports UIDPLUS
- * extension) or true.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _append(Horde_Imap_Client_Mailbox $mailbox,
- $data, $options);
- /**
- * Request a checkpoint of the currently selected mailbox (RFC 3501
- * [6.4.1]).
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function check()
- {
- // CHECK only useful if we are already authenticated.
- if ($this->_isAuthenticated) {
- $this->_check();
- }
- }
- /**
- * Request a checkpoint of the currently selected mailbox.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _check();
- /**
- * Close the connection to the currently selected mailbox, optionally
- * expunging all deleted messages (RFC 3501 [6.4.2]).
- *
- * @param array $options Additional options:
- * - expunge: (boolean) Expunge all messages flagged as deleted?
- * DEFAULT: No
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function close(array $options = array())
- {
- // This check catches the non-logged in case.
- if (is_null($this->_selected)) {
- return;
- }
- /* If we are caching, search for deleted messages. */
- if (!empty($options['expunge']) && $this->_initCache(true)) {
- /* Make sure mailbox is read-write to expunge. */
- $this->openMailbox($this->_selected, Horde_Imap_Client::OPEN_READWRITE);
- if ($this->_mode == Horde_Imap_Client::OPEN_READONLY) {
- throw new Horde_Imap_Client_Exception(
- Horde_Imap_Client_Translation::r("Cannot expunge read-only mailbox."),
- Horde_Imap_Client_Exception::MAILBOX_READONLY
- );
- }
- $search_query = new Horde_Imap_Client_Search_Query();
- $search_query->flag(Horde_Imap_Client::FLAG_DELETED, true);
- $search_res = $this->search($this->_selected, $search_query);
- $mbox = $this->_selected;
- } else {
- $search_res = null;
- }
- $this->_close($options);
- $this->_selected = null;
- $this->_mode = 0;
- if (!is_null($search_res)) {
- $this->_deleteMsgs($mbox, $search_res['match']);
- }
- }
- /**
- * Close the connection to the currently selected mailbox, optionally
- * expunging all deleted messages (RFC 3501 [6.4.2]).
- *
- * @param array $options Additional options.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _close($options);
- /**
- * Expunge deleted messages from the given mailbox.
- *
- * @param mixed $mailbox The mailbox to expunge. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param array $options Additional options:
- * - delete: (boolean) If true, will flag all messages in 'ids' as
- * deleted (since 2.10.0).
- * DEFAULT: false
- * - ids: (Horde_Imap_Client_Ids) A list of messages to expunge. These
- * messages must already be flagged as deleted (unless 'delete'
- * is true).
- * DEFAULT: All messages marked as deleted will be expunged.
- * - list: (boolean) If true, returns the list of expunged messages
- * (UIDs only).
- * DEFAULT: false
- *
- * @return Horde_Imap_Client_Ids If 'list' option is true, returns the
- * UID list of expunged messages.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function expunge($mailbox, array $options = array())
- {
- // Open mailbox call will handle the login.
- $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_READWRITE);
- /* Don't expunge if the mailbox is readonly. */
- if ($this->_mode == Horde_Imap_Client::OPEN_READONLY) {
- throw new Horde_Imap_Client_Exception(
- Horde_Imap_Client_Translation::r("Cannot expunge read-only mailbox."),
- Horde_Imap_Client_Exception::MAILBOX_READONLY
- );
- }
- if (empty($options['ids'])) {
- $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
- } elseif ($options['ids']->isEmpty()) {
- return $this->getIdsOb();
- }
- return $this->_expunge($options);
- }
- /**
- * Expunge all deleted messages from the given mailbox.
- *
- * @param array $options Additional options.
- *
- * @return Horde_Imap_Client_Ids If 'list' option is true, returns the
- * list of expunged messages.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _expunge($options);
- /**
- * Search a mailbox.
- *
- * @param mixed $mailbox The mailbox to search.
- * Either a
- * Horde_Imap_Client_Mailbox
- * object or a string
- * (UTF-8).
- * @param Horde_Imap_Client_Search_Query $query The search query.
- * Defaults to an ALL
- * search.
- * @param array $options Additional options:
- * <pre>
- * - nocache: (boolean) Don't cache the results.
- * DEFAULT: false (results cached, if possible)
- * - partial: (mixed) The range of results to return (message sequence
- * numbers) Only a single range is supported (represented by
- * the minimum and maximum values contained in the range
- * given).
- * DEFAULT: All messages are returned.
- * - results: (array) The data to return. Consists of zero or more of
- * the following flags:
- * - Horde_Imap_Client::SEARCH_RESULTS_COUNT
- * - Horde_Imap_Client::SEARCH_RESULTS_MATCH (DEFAULT)
- * - Horde_Imap_Client::SEARCH_RESULTS_MAX
- * - Horde_Imap_Client::SEARCH_RESULTS_MIN
- * - Horde_Imap_Client::SEARCH_RESULTS_SAVE
- * - Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY
- * - sequence: (boolean) If true, returns an array of sequence numbers.
- * DEFAULT: Returns an array of UIDs
- * - sort: (array) Sort the returned list of messages. Multiple sort
- * criteria can be specified. Any sort criteria can be sorted in
- * reverse order (instead of the default ascending order) by
- * adding a Horde_Imap_Client::SORT_REVERSE element to the array
- * directly before adding the sort element. The following sort
- * criteria are available:
- * - Horde_Imap_Client::SORT_ARRIVAL
- * - Horde_Imap_Client::SORT_CC
- * - Horde_Imap_Client::SORT_DATE
- * - Horde_Imap_Client::SORT_DISPLAYFROM
- * On servers that don't support SORT=DISPLAY, this criteria will
- * fallback to doing client-side sorting.
- * - Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK
- * On servers that don't support SORT=DISPLAY, this criteria will
- * fallback to Horde_Imap_Client::SORT_FROM [since 2.4.0].
- * - Horde_Imap_Client::SORT_DISPLAYTO
- * On servers that don't support SORT=DISPLAY, this criteria will
- * fallback to doing client-side sorting.
- * - Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK
- * On servers that don't support SORT=DISPLAY, this criteria will
- * fallback to Horde_Imap_Client::SORT_TO [since 2.4.0].
- * - Horde_Imap_Client::SORT_FROM
- * - Horde_Imap_Client::SORT_SEQUENCE
- * - Horde_Imap_Client::SORT_SIZE
- * - Horde_Imap_Client::SORT_SUBJECT
- * - Horde_Imap_Client::SORT_TO
- *
- * [On servers that support SEARCH=FUZZY, this criteria is also
- * available:]
- * - Horde_Imap_Client::SORT_RELEVANCY
- * </pre>
- *
- * @return array An array with the following keys:
- * <pre>
- * - count: (integer) The number of messages that match the search
- * criteria. Always returned.
- * - match: (Horde_Imap_Client_Ids) The IDs that match $criteria, sorted
- * if the 'sort' modifier was set. Returned if
- * Horde_Imap_Client::SEARCH_RESULTS_MATCH is set.
- * - max: (integer) The UID (default) or message sequence number (if
- * 'sequence' is true) of the highest message that satisifies
- * $criteria. Returns null if no matches found. Returned if
- * Horde_Imap_Client::SEARCH_RESULTS_MAX is set.
- * - min: (integer) The UID (default) or message sequence number (if
- * 'sequence' is true) of the lowest message that satisifies
- * $criteria. Returns null if no matches found. Returned if
- * Horde_Imap_Client::SEARCH_RESULTS_MIN is set.
- * - modseq: (integer) The highest mod-sequence for all messages being
- * returned. Returned if 'sort' is false, the search query
- * includes a MODSEQ command, and the server supports the
- * CONDSTORE IMAP extension.
- * - relevancy: (array) The list of relevancy scores. Returned if
- * Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY is set and
- * the server supports FUZZY search matching.
- * - save: (boolean) Whether the search results were saved. Returned if
- * Horde_Imap_Client::SEARCH_RESULTS_SAVE is set.
- * </pre>
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function search($mailbox, $query = null, array $options = array())
- {
- $this->login();
- if (empty($options['results'])) {
- $options['results'] = array(
- Horde_Imap_Client::SEARCH_RESULTS_MATCH,
- Horde_Imap_Client::SEARCH_RESULTS_COUNT
- );
- } elseif (!in_array(Horde_Imap_Client::SEARCH_RESULTS_COUNT, $options['results'])) {
- $options['results'][] = Horde_Imap_Client::SEARCH_RESULTS_COUNT;
- }
- // Default to an ALL search.
- if (is_null($query)) {
- $query = new Horde_Imap_Client_Search_Query();
- }
- // Check for SEARCHRES support.
- if ((($pos = array_search(Horde_Imap_Client::SEARCH_RESULTS_SAVE, $options['results'])) !== false) &&
- !$this->_capability('SEARCHRES')) {
- unset($options['results'][$pos]);
- }
- // Check for SORT-related options.
- if (!empty($options['sort'])) {
- foreach ($options['sort'] as $key => $val) {
- switch ($val) {
- case Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK:
- $options['sort'][$key] = $this->_capability('SORT', 'DISPLAY')
- ? Horde_Imap_Client::SORT_DISPLAYFROM
- : Horde_Imap_Client::SORT_FROM;
- break;
- case Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK:
- $options['sort'][$key] = $this->_capability('SORT', 'DISPLAY')
- ? Horde_Imap_Client::SORT_DISPLAYTO
- : Horde_Imap_Client::SORT_TO;
- break;
- }
- }
- }
- /* Default search results. */
- $default_ret = array(
- 'count' => 0,
- 'match' => $this->getIdsOb(),
- 'max' => null,
- 'min' => null,
- 'relevancy' => array()
- );
- /* Build search query. */
- $squery = $query->build($this);
- /* Check for query contents. If empty, this means that the query
- * object has identified that this query can NEVER return any results.
- * Immediately return now. */
- if (!count($squery['query'])) {
- return $default_ret;
- }
- // Check for supported charset.
- if (!is_null($squery['charset']) &&
- ($this->search_charset->query($squery['charset'], true) === false)) {
- foreach ($this->search_charset->charsets as $val) {
- try {
- $new_query = clone $query;
- $new_query->charset($val);
- break;
- } catch (Horde_Imap_Client_Exception_SearchCharset $e) {
- unset($new_query);
- }
- }
- if (!isset($new_query)) {
- throw $e;
- }
- $query = $new_query;
- $squery = $query->build($this);
- }
- // Store query in $options array to pass to child method.
- $options['_query'] = $squery;
- /* RFC 6203: MUST NOT request relevancy results if we are not using
- * FUZZY searching. */
- if (in_array(Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY, $options['results']) &&
- !in_array('SEARCH=FUZZY', $squery['exts_used'])) {
- throw new InvalidArgumentException('Cannot specify RELEVANCY results if not doing a FUZZY search.');
- }
- /* Check for partial matching. */
- if (!empty($options['partial'])) {
- $pids = $this->getIdsOb($options['partial'], true)->range_string;
- if (!strlen($pids)) {
- throw new InvalidArgumentException('Cannot specify empty sequence range for a PARTIAL search.');
- }
- if (strpos($pids, ':') === false) {
- $pids .= ':' . $pids;
- }
- $options['partial'] = $pids;
- }
- /* Optimization - if query is just for a count of either RECENT or
- * ALL messages, we can send status information instead. Can't
- * optimize with unseen queries because we may cause an infinite loop
- * between here and the status() call. */
- if ((count($options['results']) === 1) &&
- (reset($options['results']) == Horde_Imap_Client::SEARCH_RESULTS_COUNT)) {
- switch ($squery['query']) {
- case 'ALL':
- $ret = $this->status($mailbox, Horde_Imap_Client::STATUS_MESSAGES);
- return array('count' => $ret['messages']);
- case 'RECENT':
- $ret = $this->status($mailbox, Horde_Imap_Client::STATUS_RECENT);
- return array('count' => $ret['recent']);
- }
- }
- $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
- /* Take advantage of search result caching. If CONDSTORE available,
- * we can cache all queries and invalidate the cache when the MODSEQ
- * changes. If CONDSTORE not available, we can only store queries
- * that don't involve flags. We store results by hashing the options
- * array. */
- $cache = null;
- if (empty($options['nocache']) &&
- $this->_initCache(true) &&
- ($this->_capability()->isEnabled('CONDSTORE') ||
- !$query->flagSearch())) {
- $cache = $this->_getSearchCache('search', $options);
- if (isset($cache['data'])) {
- if (isset($cache['data']['match'])) {
- $cache['data']['match'] = $this->getIdsOb($cache['data']['match']);
- }
- return $cache['data'];
- }
- }
- /* Optimization: Catch when there are no messages in a mailbox. */
- $status_res = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES | Horde_Imap_Client::STATUS_HIGHESTMODSEQ);
- if ($status_res['messages'] ||
- in_array(Horde_Imap_Client::SEARCH_RESULTS_SAVE, $options['results'])) {
- /* RFC 7162 [3.1.2.2] - trying to do a MODSEQ SEARCH on a mailbox
- * that doesn't support it will return BAD. */
- if (in_array('CONDSTORE', $squery['exts']) &&
- !$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
- throw new Horde_Imap_Client_Exception(
- Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."),
- Horde_Imap_Client_Exception::MBOXNOMODSEQ
- );
- }
- $ret = $this->_search($query, $options);
- } else {
- $ret = $default_ret;
- if (isset($status_res['highestmodseq'])) {
- $ret['modseq'] = $status_res['highestmodseq'];
- }
- }
- if ($cache) {
- $save = $ret;
- if (isset($save['match'])) {
- $save['match'] = strval($ret['match']);
- }
- $this->_setSearchCache($save, $cache);
- }
- return $ret;
- }
- /**
- * Search a mailbox.
- *
- * @param object $query The search query.
- * @param array $options Additional options. The '_query' key contains
- * the value of $query->build().
- *
- * @return Horde_Imap_Client_Ids An array of IDs.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _search($query, $options);
- /**
- * Set the comparator to use for searching/sorting (RFC 5255).
- *
- * @param string $comparator The comparator string (see RFC 4790 [3.1] -
- * "collation-id" - for format). The reserved
- * string 'default' can be used to select
- * the default comparator.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function setComparator($comparator = null)
- {
- $comp = is_null($comparator)
- ? $this->getParam('comparator')
- : $comparator;
- if (is_null($comp)) {
- return;
- }
- $this->login();
- if (!$this->_capability('I18NLEVEL', '2')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension(
- 'I18NLEVEL',
- 'The IMAP server does not support changing SEARCH/SORT comparators.'
- );
- }
- $this->_setComparator($comp);
- }
- /**
- * Set the comparator to use for searching/sorting (RFC 5255).
- *
- * @param string $comparator The comparator string (see RFC 4790 [3.1] -
- * "collation-id" - for format). The reserved
- * string 'default' can be used to select
- * the default comparator.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _setComparator($comparator);
- /**
- * Get the comparator used for searching/sorting (RFC 5255).
- *
- * @return mixed Null if the default comparator is being used, or an
- * array of comparator information (see RFC 5255 [4.8]).
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function getComparator()
- {
- $this->login();
- return $this->_capability('I18NLEVEL', '2')
- ? $this->_getComparator()
- : null;
- }
- /**
- * Get the comparator used for searching/sorting (RFC 5255).
- *
- * @return mixed Null if the default comparator is being used, or an
- * array of comparator information (see RFC 5255 [4.8]).
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getComparator();
- /**
- * Thread sort a given list of messages (RFC 5256).
- *
- * @param mixed $mailbox The mailbox to query. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param array $options Additional options:
- * <pre>
- * - criteria: (mixed) The following thread criteria are available:
- * - Horde_Imap_Client::THREAD_ORDEREDSUBJECT
- * - Horde_Imap_Client::THREAD_REFERENCES
- * - Horde_Imap_Client::THREAD_REFS
- * Other algorithms can be explicitly specified by passing the IMAP
- * thread algorithm in as a string value.
- * DEFAULT: Horde_Imap_Client::THREAD_ORDEREDSUBJECT
- * - search: (Horde_Imap_Client_Search_Query) The search query.
- * DEFAULT: All messages in mailbox included in thread sort.
- * - sequence: (boolean) If true, each message is stored and referred to
- * by its message sequence number.
- * DEFAULT: Stored/referred to by UID.
- * </pre>
- *
- * @return Horde_Imap_Client_Data_Thread A thread data object.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function thread($mailbox, array $options = array())
- {
- // Open mailbox call will handle the login.
- $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
- /* Take advantage of search result caching. If CONDSTORE available,
- * we can cache all queries and invalidate the cache when the MODSEQ
- * changes. If CONDSTORE not available, we can only store queries
- * that don't involve flags. See search() for similar caching. */
- $cache = null;
- if ($this->_initCache(true) &&
- ($this->_capability()->isEnabled('CONDSTORE') ||
- empty($options['search']) ||
- !$options['search']->flagSearch())) {
- $cache = $this->_getSearchCache('thread', $options);
- if (isset($cache['data']) &&
- ($cache['data'] instanceof Horde_Imap_Client_Data_Thread)) {
- return $cache['data'];
- }
- }
- $status_res = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES);
- $ob = $status_res['messages']
- ? $this->_thread($options)
- : new Horde_Imap_Client_Data_Thread(array(), empty($options['sequence']) ? 'uid' : 'sequence');
- if ($cache) {
- $this->_setSearchCache($ob, $cache);
- }
- return $ob;
- }
- /**
- * Thread sort a given list of messages (RFC 5256).
- *
- * @param array $options Additional options. See thread().
- *
- * @return Horde_Imap_Client_Data_Thread A thread data object.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _thread($options);
- /**
- * Fetch message data (see RFC 3501 [6.4.5]).
- *
- * @param mixed $mailbox The mailbox to search.
- * Either a
- * Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- * @param Horde_Imap_Client_Fetch_Query $query Fetch query object.
- * @param array $options Additional options:
- * - changedsince: (integer) Only return messages that have a
- * mod-sequence larger than this value. This option
- * requires the CONDSTORE IMAP extension (if not present,
- * this value is ignored). Additionally, the mailbox
- * must support mod-sequences or an exception will be
- * thrown. If valid, this option implicity adds the
- * mod-sequence fetch criteria to the fetch command.
- * DEFAULT: Mod-sequence values are ignored.
- * - exists: (boolean) Ensure that all ids returned exist on the server.
- * If false, the list of ids returned in the results object
- * is not guaranteed to reflect the current state of the
- * remote mailbox.
- * DEFAULT: false
- * - ids: (Horde_Imap_Client_Ids) A list of messages to fetch data from.
- * DEFAULT: All messages in $mailbox will be fetched.
- * - nocache: (boolean) If true, will not cache the results (previously
- * cached data will still be used to generate results) [since
- * 2.8.0].
- * DEFAULT: false
- *
- * @return Horde_Imap_Client_Fetch_Results A results object.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function fetch($mailbox, $query, array $options = array())
- {
- try {
- $ret = $this->_fetchWrapper($mailbox, $query, $options);
- unset($this->_temp['fetch_nocache']);
- return $ret;
- } catch (Exception $e) {
- unset($this->_temp['fetch_nocache']);
- throw $e;
- }
- }
- /**
- * Wrapper for fetch() to allow internal state to be reset on exception.
- *
- * @internal
- * @see fetch()
- */
- private function _fetchWrapper($mailbox, $query, $options)
- {
- $this->login();
- $query = clone $query;
- $cache_array = $header_cache = $new_query = array();
- if (empty($options['ids'])) {
- $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
- } elseif ($options['ids']->isEmpty()) {
- return new Horde_Imap_Client_Fetch_Results($this->_fetchDataClass);
- } elseif ($options['ids']->search_res &&
- !$this->_capability('SEARCHRES')) {
- /* SEARCHRES requires server support. */
- throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES');
- }
- $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
- $mbox_ob = $this->_mailboxOb();
- if (!empty($options['nocache'])) {
- $this->_temp['fetch_nocache'] = true;
- }
- $cf = $this->_initCache(true)
- ? $this->_cacheFields()
- : array();
- if (!empty($cf)) {
- /* If using cache, we store by UID so we need to return UIDs. */
- $query->uid();
- }
- $modseq_check = !empty($options['changedsince']);
- if ($query->contains(Horde_Imap_Client::FETCH_MODSEQ)) {
- if (!$this->_capability()->isEnabled('CONDSTORE')) {
- unset($query[Horde_Imap_Client::FETCH_MODSEQ]);
- } elseif (empty($options['changedsince'])) {
- $modseq_check = true;
- }
- }
- if ($modseq_check &&
- !$mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
- /* RFC 7162 [3.1.2.2] - trying to do a MODSEQ FETCH on a mailbox
- * that doesn't support it will return BAD. */
- throw new Horde_Imap_Client_Exception(
- Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."),
- Horde_Imap_Client_Exception::MBOXNOMODSEQ
- );
- }
- /* Determine if caching is available and if anything in $query is
- * cacheable. */
- foreach ($cf as $k => $v) {
- if (isset($query[$k])) {
- switch ($k) {
- case Horde_Imap_Client::FETCH_ENVELOPE:
- case Horde_Imap_Client::FETCH_FLAGS:
- case Horde_Imap_Client::FETCH_IMAPDATE:
- case Horde_Imap_Client::FETCH_SIZE:
- case Horde_Imap_Client::FETCH_STRUCTURE:
- $cache_array[$k] = $v;
- break;
- case Horde_Imap_Client::FETCH_HEADERS:
- $this->_temp['headers_caching'] = array();
- foreach ($query[$k] as $key => $val) {
- /* Only cache if directly requested. Iterate through
- * requests to ensure at least one can be cached. */
- if (!empty($val['cache']) && !empty($val['peek'])) {
- $cache_array[$k] = $v;
- ksort($val);
- $header_cache[$key] = hash('md5', serialize($val));
- }
- }
- break;
- }
- }
- }
- $ret = new Horde_Imap_Client_Fetch_Results(
- $this->_fetchDataClass,
- $options['ids']->sequence ? Horde_Imap_Client_Fetch_Results::SEQUENCE : Horde_Imap_Client_Fetch_Results::UID
- );
- /* If nothing is cacheable, we can do a straight search. */
- if (empty($cache_array)) {
- $options['_query'] = $query;
- $this->_fetch($ret, array($options));
- return $ret;
- }
- $cs_ret = empty($options['changedsince'])
- ? null
- : clone $ret;
- /* Convert special searches to UID lists and create mapping. */
- $ids = $this->resolveIds(
- $this->_selected,
- $options['ids'],
- empty($options['exists']) ? 1 : 2
- );
- /* Add non-user settable cache fields. */
- $cache_array[Horde_Imap_Client::FETCH_DOWNGRADED] = self::CACHE_DOWNGRADED;
- /* Get the cached values. */
- $data = $this->_cache->get(
- $this->_selected,
- $ids->ids,
- array_values($cache_array),
- $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY)
- );
- /* Build a list of what we still need. */
- $map = array_flip($mbox_ob->map->map);
- $sequence = $options['ids']->sequence;
- foreach ($ids as $uid) {
- $crit = clone $query;
- if ($sequence) {
- if (!isset($map[$uid])) {
- continue;
- }
- $entry_idx = $map[$uid];
- } else {
- $entry_idx = $uid;
- unset($crit[Horde_Imap_Client::FETCH_UID]);
- }
- $entry = $ret->get($entry_idx);
- if (isset($map[$uid])) {
- $entry->setSeq($map[$uid]);
- unset($crit[Horde_Imap_Client::FETCH_SEQ]);
- }
- $entry->setUid($uid);
- foreach ($cache_array as $key => $cid) {
- switch ($key) {
- case Horde_Imap_Client::FETCH_DOWNGRADED:
- if (!empty($data[$uid][$cid])) {
- $entry->setDowngraded(true);
- }
- break;
- case Horde_Imap_Client::FETCH_ENVELOPE:
- if (isset($data[$uid][$cid]) &&
- ($data[$uid][$cid] instanceof Horde_Imap_Client_Data_Envelope)) {
- $entry->setEnvelope($data[$uid][$cid]);
- unset($crit[$key]);
- }
- break;
- case Horde_Imap_Client::FETCH_FLAGS:
- if (isset($data[$uid][$cid]) &&
- is_array($data[$uid][$cid])) {
- $entry->setFlags($data[$uid][$cid]);
- unset($crit[$key]);
- }
- break;
- case Horde_Imap_Client::FETCH_HEADERS:
- foreach ($header_cache as $hkey => $hval) {
- if (isset($data[$uid][$cid][$hval])) {
- /* We have found a cached entry with the same
- * MD5 sum. */
- $entry->setHeaders($hkey, $data[$uid][$cid][$hval]);
- $crit->remove($key, $hkey);
- } else {
- $this->_temp['headers_caching'][$hkey] = $hval;
- }
- }
- break;
- case Horde_Imap_Client::FETCH_IMAPDATE:
- if (isset($data[$uid][$cid]) &&
- ($data[$uid][$cid] instanceof Horde_Imap_Client_DateTime)) {
- $entry->setImapDate($data[$uid][$cid]);
- unset($crit[$key]);
- }
- break;
- case Horde_Imap_Client::FETCH_SIZE:
- if (isset($data[$uid][$cid])) {
- $entry->setSize($data[$uid][$cid]);
- unset($crit[$key]);
- }
- break;
- case Horde_Imap_Client::FETCH_STRUCTURE:
- if (isset($data[$uid][$cid]) &&
- ($data[$uid][$cid] instanceof Horde_Mime_Part)) {
- $entry->setStructure($data[$uid][$cid]);
- unset($crit[$key]);
- }
- break;
- }
- }
- if (count($crit)) {
- $sig = $crit->hash();
- if (isset($new_query[$sig])) {
- $new_query[$sig]['i'][] = $entry_idx;
- } else {
- $new_query[$sig] = array(
- 'c' => $crit,
- 'i' => array($entry_idx)
- );
- }
- }
- }
- $to_fetch = array();
- foreach ($new_query as $val) {
- $ids_ob = $this->getIdsOb(null, $sequence);
- $ids_ob->duplicates = true;
- $ids_ob->add($val['i']);
- $to_fetch[] = array_merge($options, array(
- '_query' => $val['c'],
- 'ids' => $ids_ob
- ));
- }
- if (!empty($to_fetch)) {
- $this->_fetch(is_null($cs_ret) ? $ret : $cs_ret, $to_fetch);
- }
- if (is_null($cs_ret)) {
- return $ret;
- }
- /* If doing changedsince query, and all other data is cached, we still
- * need to hit IMAP server to determine proper results set. */
- if (empty($new_query)) {
- $squery = new Horde_Imap_Client_Search_Query();
- $squery->modseq($options['changedsince'] + 1);
- $squery->ids($options['ids']);
- $cs = $this->search($this->_selected, $squery, array(
- 'sequence' => $sequence
- ));
- foreach ($cs['match'] as $val) {
- $entry = $ret->get($val);
- if ($sequence) {
- $entry->setSeq($val);
- } else {
- $entry->setUid($val);
- }
- $cs_ret[$val] = $entry;
- }
- } else {
- foreach ($cs_ret as $key => $val) {
- $val->merge($ret->get($key));
- }
- }
- return $cs_ret;
- }
- /**
- * Fetch message data.
- *
- * Fetch queries should be grouped in the $queries argument. Each value
- * is an array of fetch options, with the fetch query stored in the
- * '_query' parameter. IMPORTANT: All queries must have the same ID
- * type (either sequence or UID).
- *
- * @param Horde_Imap_Client_Fetch_Results $results Fetch results.
- * @param array $queries The list of queries.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _fetch(Horde_Imap_Client_Fetch_Results $results,
- $queries);
- /**
- * Get the list of vanished messages (UIDs that have been expunged since a
- * given mod-sequence value).
- *
- * @param mixed $mailbox The mailbox to query. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param integer $modseq Search for expunged messages after this
- * mod-sequence value.
- * @param array $opts Additional options:
- * - ids: (Horde_Imap_Client_Ids) Restrict to these UIDs.
- * DEFAULT: Returns full list of UIDs vanished (QRESYNC only).
- * This option is REQUIRED for non-QRESYNC servers or
- * else an empty list will be returned.
- *
- * @return Horde_Imap_Client_Ids List of UIDs that have vanished.
- *
- * @throws Horde_Imap_Client_NoSupportExtension
- */
- public function vanished($mailbox, $modseq, array $opts = array())
- {
- $this->login();
- if (empty($opts['ids'])) {
- if (!$this->_capability()->isEnabled('QRESYNC')) {
- return $this->getIdsOb();
- }
- $opts['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
- } elseif ($opts['ids']->isEmpty()) {
- return $this->getIdsOb();
- } elseif ($opts['ids']->sequence) {
- throw new InvalidArgumentException('Vanished requires UIDs.');
- }
- $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_AUTO);
- if ($this->_capability()->isEnabled('QRESYNC')) {
- if (!$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
- throw new Horde_Imap_Client_Exception(
- Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."),
- Horde_Imap_Client_Exception::MBOXNOMODSEQ
- );
- }
- return $this->_vanished(max(1, $modseq), $opts['ids']);
- }
- $ids = $this->resolveIds($mailbox, $opts['ids']);
- $squery = new Horde_Imap_Client_Search_Query();
- $squery->ids($ids);
- $search = $this->search($mailbox, $squery, array(
- 'nocache' => true
- ));
- return $this->getIdsOb(array_diff($ids->ids, $search['match']->ids));
- }
- /**
- * Get the list of vanished messages.
- *
- * @param integer $modseq Mod-sequence value.
- * @param Horde_Imap_Client_Ids $ids UIDs.
- *
- * @return Horde_Imap_Client_Ids List of UIDs that have vanished.
- */
- abstract protected function _vanished($modseq, Horde_Imap_Client_Ids $ids);
- /**
- * Store message flag data (see RFC 3501 [6.4.6]).
- *
- * @param mixed $mailbox The mailbox containing the messages to modify.
- * Either a Horde_Imap_Client_Mailbox object or a
- * string (UTF-8).
- * @param array $options Additional options:
- * - add: (array) An array of flags to add.
- * DEFAULT: No flags added.
- * - ids: (Horde_Imap_Client_Ids) The list of messages to modify.
- * DEFAULT: All messages in $mailbox will be modified.
- * - remove: (array) An array of flags to remove.
- * DEFAULT: No flags removed.
- * - replace: (array) Replace the current flags with this set
- * of flags. Overrides both the 'add' and 'remove' options.
- * DEFAULT: No replace is performed.
- * - unchangedsince: (integer) Only changes flags if the mod-sequence ID
- * of the message is equal or less than this value.
- * Requires the CONDSTORE IMAP extension on the server.
- * Also requires the mailbox to support mod-sequences.
- * Will throw an exception if either condition is not
- * met.
- * DEFAULT: mod-sequence is ignored when applying
- * changes
- *
- * @return Horde_Imap_Client_Ids A Horde_Imap_Client_Ids object
- * containing the list of IDs that failed
- * the 'unchangedsince' test.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function store($mailbox, array $options = array())
- {
- // Open mailbox call will handle the login.
- $this->openMailbox($mailbox, Horde_Imap_Client::OPEN_READWRITE);
- /* SEARCHRES requires server support. */
- if (empty($options['ids'])) {
- $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
- } elseif ($options['ids']->isEmpty()) {
- return $this->getIdsOb();
- } elseif ($options['ids']->search_res &&
- !$this->_capability('SEARCHRES')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES');
- }
- if (!empty($options['unchangedsince'])) {
- if (!$this->_capability()->isEnabled('CONDSTORE')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('CONDSTORE');
- }
- /* RFC 7162 [3.1.2.2] - trying to do a UNCHANGEDSINCE STORE on a
- * mailbox that doesn't support it will return BAD. */
- if (!$this->_mailboxOb()->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) {
- throw new Horde_Imap_Client_Exception(
- Horde_Imap_Client_Translation::r("Mailbox does not support mod-sequences."),
- Horde_Imap_Client_Exception::MBOXNOMODSEQ
- );
- }
- }
- return $this->_store($options);
- }
- /**
- * Store message flag data.
- *
- * @param array $options Additional options.
- *
- * @return Horde_Imap_Client_Ids A Horde_Imap_Client_Ids object
- * containing the list of IDs that failed
- * the 'unchangedsince' test.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _store($options);
- /**
- * Copy messages to another mailbox.
- *
- * @param mixed $source The source mailbox. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param mixed $dest The destination mailbox. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param array $options Additional options:
- * - create: (boolean) Try to create $dest if it does not exist?
- * DEFAULT: No.
- * - force_map: (boolean) Forces the array mapping to always be
- * returned. [@since 2.19.0]
- * - ids: (Horde_Imap_Client_Ids) The list of messages to copy.
- * DEFAULT: All messages in $mailbox will be copied.
- * - move: (boolean) If true, delete the original messages.
- * DEFAULT: Original messages are not deleted.
- *
- * @return mixed An array mapping old UIDs (keys) to new UIDs (values) on
- * success (only guaranteed if 'force_map' is true) or
- * true.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function copy($source, $dest, array $options = array())
- {
- // Open mailbox call will handle the login.
- $this->openMailbox($source, empty($options['move']) ? Horde_Imap_Client::OPEN_AUTO : Horde_Imap_Client::OPEN_READWRITE);
- /* SEARCHRES requires server support. */
- if (empty($options['ids'])) {
- $options['ids'] = $this->getIdsOb(Horde_Imap_Client_Ids::ALL);
- } elseif ($options['ids']->isEmpty()) {
- return array();
- } elseif ($options['ids']->search_res &&
- !$this->_capability('SEARCHRES')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES');
- }
- $dest = Horde_Imap_Client_Mailbox::get($dest);
- $res = $this->_copy($dest, $options);
- if (($res === true) && !empty($options['force_map'])) {
- /* Need to manually create mapping from Message-ID data. */
- $query = new Horde_Imap_Client_Fetch_Query();
- $query->envelope();
- $fetch = $this->fetch($source, $query, array(
- 'ids' => $options['ids']
- ));
- $res = array();
- foreach ($fetch as $val) {
- if ($uid = $this->_getUidByMessageId($dest, $val->getEnvelope()->message_id)) {
- $res[$val->getUid()] = $uid;
- }
- }
- }
- return $res;
- }
- /**
- * Copy messages to another mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $dest The destination mailbox.
- * @param array $options Additional options.
- *
- * @return mixed An array mapping old UIDs (keys) to new UIDs (values) on
- * success (if the IMAP server and/or driver support the
- * UIDPLUS extension) or true.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _copy(Horde_Imap_Client_Mailbox $dest,
- $options);
- /**
- * Set quota limits. The server must support the IMAP QUOTA extension
- * (RFC 2087).
- *
- * @param mixed $root The quota root. Either a
- * Horde_Imap_Client_Mailbox object or a string
- * (UTF-8).
- * @param array $resources The resource values to set. Keys are the
- * resource atom name; value is the resource
- * value.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function setQuota($root, array $resources = array())
- {
- $this->login();
- if (!$this->_capability('QUOTA')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA');
- }
- if (!empty($resources)) {
- $this->_setQuota(Horde_Imap_Client_Mailbox::get($root), $resources);
- }
- }
- /**
- * Set quota limits.
- *
- * @param Horde_Imap_Client_Mailbox $root The quota root.
- * @param array $resources The resource values to set.
- *
- * @return boolean True on success.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _setQuota(Horde_Imap_Client_Mailbox $root,
- $resources);
- /**
- * Get quota limits. The server must support the IMAP QUOTA extension
- * (RFC 2087).
- *
- * @param mixed $root The quota root. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- *
- * @return mixed An array with resource keys. Each key holds an array
- * with 2 values: 'limit' and 'usage'.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function getQuota($root)
- {
- $this->login();
- if (!$this->_capability('QUOTA')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA');
- }
- return $this->_getQuota(Horde_Imap_Client_Mailbox::get($root));
- }
- /**
- * Get quota limits.
- *
- * @param Horde_Imap_Client_Mailbox $root The quota root.
- *
- * @return mixed An array with resource keys. Each key holds an array
- * with 2 values: 'limit' and 'usage'.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getQuota(Horde_Imap_Client_Mailbox $root);
- /**
- * Get quota limits for a mailbox. The server must support the IMAP QUOTA
- * extension (RFC 2087).
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- *
- * @return mixed An array with the keys being the quota roots. Each key
- * holds an array with resource keys: each of these keys
- * holds an array with 2 values: 'limit' and 'usage'.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function getQuotaRoot($mailbox)
- {
- $this->login();
- if (!$this->_capability('QUOTA')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('QUOTA');
- }
- return $this->_getQuotaRoot(Horde_Imap_Client_Mailbox::get($mailbox));
- }
- /**
- * Get quota limits for a mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- *
- * @return mixed An array with the keys being the quota roots. Each key
- * holds an array with resource keys: each of these keys
- * holds an array with 2 values: 'limit' and 'usage'.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getQuotaRoot(Horde_Imap_Client_Mailbox $mailbox);
- /**
- * Get the ACL rights for a given mailbox. The server must support the
- * IMAP ACL extension (RFC 2086/4314).
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- *
- * @return array An array with identifiers as the keys and
- * Horde_Imap_Client_Data_Acl objects as the values.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function getACL($mailbox)
- {
- $this->login();
- return $this->_getACL(Horde_Imap_Client_Mailbox::get($mailbox));
- }
- /**
- * Get ACL rights for a given mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- *
- * @return array An array with identifiers as the keys and
- * Horde_Imap_Client_Data_Acl objects as the values.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getACL(Horde_Imap_Client_Mailbox $mailbox);
- /**
- * Set ACL rights for a given mailbox/identifier.
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- * @param string $identifier The identifier to alter (UTF-8).
- * @param array $options Additional options:
- * - rights: (string) The rights to alter or set.
- * - action: (string, optional) If 'add' or 'remove', adds or removes the
- * specified rights. Sets the rights otherwise.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function setACL($mailbox, $identifier, $options)
- {
- $this->login();
- if (!$this->_capability('ACL')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
- }
- if (empty($options['rights'])) {
- if (!isset($options['action']) ||
- (($options['action'] != 'add') &&
- $options['action'] != 'remove')) {
- $this->_deleteACL(
- Horde_Imap_Client_Mailbox::get($mailbox),
- Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier)
- );
- }
- return;
- }
- $acl = ($options['rights'] instanceof Horde_Imap_Client_Data_Acl)
- ? $options['rights']
- : new Horde_Imap_Client_Data_Acl(strval($options['rights']));
- $options['rights'] = $acl->getString(
- $this->_capability('RIGHTS')
- ? Horde_Imap_Client_Data_AclCommon::RFC_4314
- : Horde_Imap_Client_Data_AclCommon::RFC_2086
- );
- if (isset($options['action'])) {
- switch ($options['action']) {
- case 'add':
- $options['rights'] = '+' . $options['rights'];
- break;
- case 'remove':
- $options['rights'] = '-' . $options['rights'];
- break;
- }
- }
- $this->_setACL(
- Horde_Imap_Client_Mailbox::get($mailbox),
- Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier),
- $options
- );
- }
- /**
- * Set ACL rights for a given mailbox/identifier.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- * @param string $identifier The identifier to alter
- * (UTF7-IMAP).
- * @param array $options Additional options. 'rights'
- * contains the string of
- * rights to set on the server.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _setACL(Horde_Imap_Client_Mailbox $mailbox,
- $identifier, $options);
- /**
- * Deletes ACL rights for a given mailbox/identifier.
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- * @param string $identifier The identifier to delete (UTF-8).
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function deleteACL($mailbox, $identifier)
- {
- $this->login();
- if (!$this->_capability('ACL')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
- }
- $this->_deleteACL(
- Horde_Imap_Client_Mailbox::get($mailbox),
- Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier)
- );
- }
- /**
- * Deletes ACL rights for a given mailbox/identifier.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- * @param string $identifier The identifier to delete
- * (UTF7-IMAP).
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _deleteACL(Horde_Imap_Client_Mailbox $mailbox,
- $identifier);
- /**
- * List the ACL rights for a given mailbox/identifier. The server must
- * support the IMAP ACL extension (RFC 2086/4314).
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- * @param string $identifier The identifier to query (UTF-8).
- *
- * @return Horde_Imap_Client_Data_AclRights An ACL data rights object.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function listACLRights($mailbox, $identifier)
- {
- $this->login();
- if (!$this->_capability('ACL')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
- }
- return $this->_listACLRights(
- Horde_Imap_Client_Mailbox::get($mailbox),
- Horde_Imap_Client_Utf7imap::Utf8ToUtf7Imap($identifier)
- );
- }
- /**
- * Get ACL rights for a given mailbox/identifier.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- * @param string $identifier The identifier to query
- * (UTF7-IMAP).
- *
- * @return Horde_Imap_Client_Data_AclRights An ACL data rights object.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _listACLRights(Horde_Imap_Client_Mailbox $mailbox,
- $identifier);
- /**
- * Get the ACL rights for the current user for a given mailbox. The
- * server must support the IMAP ACL extension (RFC 2086/4314).
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- *
- * @return Horde_Imap_Client_Data_Acl An ACL data object.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_NoSupportExtension
- */
- public function getMyACLRights($mailbox)
- {
- $this->login();
- if (!$this->_capability('ACL')) {
- throw new Horde_Imap_Client_Exception_NoSupportExtension('ACL');
- }
- return $this->_getMyACLRights(Horde_Imap_Client_Mailbox::get($mailbox));
- }
- /**
- * Get the ACL rights for the current user for a given mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- *
- * @return Horde_Imap_Client_Data_Acl An ACL data object.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getMyACLRights(Horde_Imap_Client_Mailbox $mailbox);
- /**
- * Return master list of ACL rights available on the server.
- *
- * @return array A list of ACL rights.
- */
- public function allAclRights()
- {
- $this->login();
- $rights = array(
- Horde_Imap_Client::ACL_LOOKUP,
- Horde_Imap_Client::ACL_READ,
- Horde_Imap_Client::ACL_SEEN,
- Horde_Imap_Client::ACL_WRITE,
- Horde_Imap_Client::ACL_INSERT,
- Horde_Imap_Client::ACL_POST,
- Horde_Imap_Client::ACL_ADMINISTER
- );
- if ($capability = $this->_capability()->getParams('RIGHTS')) {
- // Add rights defined in CAPABILITY string (RFC 4314).
- return array_merge($rights, str_split(reset($capability)));
- }
- // Add RFC 2086 rights (deprecated by RFC 4314, but need to keep for
- // compatibility with old servers).
- return array_merge($rights, array(
- Horde_Imap_Client::ACL_CREATE,
- Horde_Imap_Client::ACL_DELETE
- ));
- }
- /**
- * Get metadata for a given mailbox. The server must support either the
- * IMAP METADATA extension (RFC 5464) or the ANNOTATEMORE extension
- * (http://ietfreport.isoc.org/idref/draft-daboo-imap-annotatemore/).
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- * @param array $entries The entries to fetch (UTF-8 strings).
- * @param array $options Additional options:
- * - depth: (string) Either "0", "1" or "infinity". Returns only the
- * given value (0), only values one level below the specified
- * value (1) or all entries below the specified value
- * (infinity).
- * - maxsize: (integer) The maximal size the returned values may have.
- * DEFAULT: No maximal size.
- *
- * @return array An array with metadata names as the keys and metadata
- * values as the values. If 'maxsize' is set, and entries
- * exist on the server larger than this size, the size will
- * be returned in the key '*longentries'.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function getMetadata($mailbox, $entries, array $options = array())
- {
- $this->login();
- if (!is_array($entries)) {
- $entries = array($entries);
- }
- return $this->_getMetadata(Horde_Imap_Client_Mailbox::get($mailbox), array_map(array('Horde_Imap_Client_Utf7imap', 'Utf8ToUtf7Imap'), $entries), $options);
- }
- /**
- * Get metadata for a given mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- * @param array $entries The entries to fetch
- * (UTF7-IMAP strings).
- * @param array $options Additional options.
- *
- * @return array An array with metadata names as the keys and metadata
- * values as the values.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _getMetadata(Horde_Imap_Client_Mailbox $mailbox,
- $entries, $options);
- /**
- * Set metadata for a given mailbox/identifier.
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8). If empty, sets a
- * server annotation.
- * @param array $data A set of data values. The metadata values
- * corresponding to the keys of the array will
- * be set to the values in the array.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function setMetadata($mailbox, $data)
- {
- $this->login();
- $this->_setMetadata(Horde_Imap_Client_Mailbox::get($mailbox), $data);
- }
- /**
- * Set metadata for a given mailbox/identifier.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox A mailbox.
- * @param array $data A set of data values. See
- * setMetadata() for format.
- *
- * @throws Horde_Imap_Client_Exception
- */
- abstract protected function _setMetadata(Horde_Imap_Client_Mailbox $mailbox,
- $data);
- /* Public utility functions. */
- /**
- * Returns a unique identifier for the current mailbox status.
- *
- * @deprecated
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- * @param array $addl Additional cache info to add to the cache ID
- * string.
- *
- * @return string The cache ID string, which will change when the
- * composition of the mailbox changes. The uidvalidity
- * will always be the first element, and will be delimited
- * by the '|' character.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function getCacheId($mailbox, array $addl = array())
- {
- return Horde_Imap_Client_Base_Deprecated::getCacheId($this, $mailbox, $this->_capability()->isEnabled('CONDSTORE'), $addl);
- }
- /**
- * Parses a cacheID created by getCacheId().
- *
- * @deprecated
- *
- * @param string $id The cache ID.
- *
- * @return array An array with the following information:
- * - highestmodseq: (integer)
- * - messages: (integer)
- * - uidnext: (integer)
- * - uidvalidity: (integer) Always present
- */
- public function parseCacheId($id)
- {
- return Horde_Imap_Client_Base_Deprecated::parseCacheId($id);
- }
- /**
- * Resolves an IDs object into a list of IDs.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox.
- * @param Horde_Imap_Client_Ids $ids The Ids object.
- * @param integer $convert Convert to UIDs?
- * - 0: No
- * - 1: Only if $ids is not already a UIDs object
- * - 2: Always
- *
- * @return Horde_Imap_Client_Ids The list of IDs.
- */
- public function resolveIds(Horde_Imap_Client_Mailbox $mailbox,
- Horde_Imap_Client_Ids $ids, $convert = 0)
- {
- $map = $this->_mailboxOb($mailbox)->map;
- if ($ids->special) {
- /* Optimization for ALL sequence searches. */
- if (!$convert && $ids->all && $ids->sequence) {
- $res = $this->status($mailbox, Horde_Imap_Client::STATUS_MESSAGES);
- return $this->getIdsOb($res['messages'] ? ('1:' . $res['messages']) : array(), true);
- }
- $convert = 2;
- } elseif (!$convert ||
- (!$ids->sequence && ($convert == 1)) ||
- $ids->isEmpty()) {
- return clone $ids;
- } else {
- /* Do an all or nothing: either we have all the numbers/UIDs in
- * memory and can return, or just send the whole ID query to the
- * server. Any advantage we would get by a partial search are
- * outweighed by the complexities needed to make the search and
- * then merge back into the original results. */
- $lookup = $map->lookup($ids);
- if (count($lookup) === count($ids)) {
- return $this->getIdsOb(array_values($lookup));
- }
- }
- $query = new Horde_Imap_Client_Search_Query();
- $query->ids($ids);
- $res = $this->search($mailbox, $query, array(
- 'results' => array(
- Horde_Imap_Client::SEARCH_RESULTS_MATCH,
- Horde_Imap_Client::SEARCH_RESULTS_SAVE
- ),
- 'sequence' => (!$convert && $ids->sequence),
- 'sort' => array(Horde_Imap_Client::SORT_SEQUENCE)
- ));
- /* Update mapping. */
- if ($convert) {
- if ($ids->all) {
- $ids = $this->getIdsOb('1:' . count($res['match']));
- } elseif ($ids->special) {
- return $res['match'];
- }
- /* Sanity checking (Bug #12911). */
- $list1 = array_slice($ids->ids, 0, count($res['match']));
- $list2 = $res['match']->ids;
- if (!empty($list1) &&
- !empty($list2) &&
- (count($list1) === count($list2))) {
- $map->update(array_combine($list1, $list2));
- }
- }
- return $res['match'];
- }
- /**
- * Determines if the given charset is valid for search-related queries.
- * This check pertains just to the basic IMAP SEARCH command.
- *
- * @deprecated Use $search_charset property instead.
- *
- * @param string $charset The query charset.
- *
- * @return boolean True if server supports this charset.
- */
- public function validSearchCharset($charset)
- {
- return $this->search_charset->query($charset);
- }
- /* Mailbox syncing functions. */
- /**
- * Returns a unique token for the current mailbox synchronization status.
- *
- * @since 2.2.0
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- *
- * @return string The sync token.
- *
- * @throws Horde_Imap_Client_Exception
- */
- public function getSyncToken($mailbox)
- {
- $out = array();
- foreach ($this->_syncStatus($mailbox) as $key => $val) {
- $out[] = $key . $val;
- }
- return base64_encode(implode(',', $out));
- }
- /**
- * Synchronize a mailbox from a sync token.
- *
- * @since 2.2.0
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- * @param string $token A sync token generated by getSyncToken().
- * @param array $opts Additional options:
- * - criteria: (integer) Mask of Horde_Imap_Client::SYNC_* criteria to
- * return. Defaults to SYNC_ALL.
- * - ids: (Horde_Imap_Client_Ids) A cached list of UIDs. Unless QRESYNC
- * is available on the server, failure to specify this option
- * means SYNC_VANISHEDUIDS information cannot be returned.
- *
- * @return Horde_Imap_Client_Data_Sync A sync object.
- *
- * @throws Horde_Imap_Client_Exception
- * @throws Horde_Imap_Client_Exception_Sync
- */
- public function sync($mailbox, $token, array $opts = array())
- {
- if (($token = base64_decode($token, true)) === false) {
- throw new Horde_Imap_Client_Exception_Sync('Bad token.', Horde_Imap_Client_Exception_Sync::BAD_TOKEN);
- }
- $sync = array();
- foreach (explode(',', $token) as $val) {
- $sync[substr($val, 0, 1)] = substr($val, 1);
- }
- return new Horde_Imap_Client_Data_Sync(
- $this,
- $mailbox,
- $sync,
- $this->_syncStatus($mailbox),
- (isset($opts['criteria']) ? $opts['criteria'] : Horde_Imap_Client::SYNC_ALL),
- (isset($opts['ids']) ? $opts['ids'] : null)
- );
- }
- /* Private utility functions. */
- /**
- * Store FETCH data in cache.
- *
- * @param Horde_Imap_Client_Fetch_Results $data The fetch results.
- *
- * @throws Horde_Imap_Client_Exception
- */
- protected function _updateCache(Horde_Imap_Client_Fetch_Results $data)
- {
- if (!empty($this->_temp['fetch_nocache']) ||
- empty($this->_selected) ||
- !count($data) ||
- !$this->_initCache(true)) {
- return;
- }
- $c = $this->getParam('cache');
- if (in_array(strval($this->_selected), $c['fetch_ignore'])) {
- $this->_debug->info(sprintf(
- 'CACHE: Ignoring FETCH data [%s]',
- $this->_selected
- ));
- return;
- }
- /* Optimization: we can directly use getStatus() here since we know
- * these values are initialized. */
- $mbox_ob = $this->_mailboxOb();
- $highestmodseq = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ);
- $uidvalidity = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY);
- $mapping = $modseq = $tocache = array();
- if (count($data)) {
- $cf = $this->_cacheFields();
- }
- foreach ($data as $v) {
- /* It is possible that we received FETCH information that doesn't
- * contain UID data. This is uncacheable so don't process. */
- if (!($uid = $v->getUid())) {
- return;
- }
- $tmp = array();
- if ($v->isDowngraded()) {
- $tmp[self::CACHE_DOWNGRADED] = true;
- }
- foreach ($cf as $key => $val) {
- if ($v->exists($key)) {
- switch ($key) {
- case Horde_Imap_Client::FETCH_ENVELOPE:
- $tmp[$val] = $v->getEnvelope();
- break;
- case Horde_Imap_Client::FETCH_FLAGS:
- if ($highestmodseq) {
- $modseq[$uid] = $v->getModSeq();
- $tmp[$val] = $v->getFlags();
- }
- break;
- case Horde_Imap_Client::FETCH_HEADERS:
- foreach ($this->_temp['headers_caching'] as $label => $hash) {
- if ($hdr = $v->getHeaders($label)) {
- $tmp[$val][$hash] = $hdr;
- }
- }
- break;
- case Horde_Imap_Client::FETCH_IMAPDATE:
- $tmp[$val] = $v->getImapDate();
- break;
- case Horde_Imap_Client::FETCH_SIZE:
- $tmp[$val] = $v->getSize();
- break;
- case Horde_Imap_Client::FETCH_STRUCTURE:
- $tmp[$val] = clone $v->getStructure();
- break;
- }
- }
- }
- if (!empty($tmp)) {
- $tocache[$uid] = $tmp;
- }
- $mapping[$v->getSeq()] = $uid;
- }
- if (!empty($mapping)) {
- if (!empty($tocache)) {
- $this->_cache->set($this->_selected, $tocache, $uidvalidity);
- }
- $this->_mailboxOb()->map->update($mapping);
- }
- if (!empty($modseq)) {
- $this->_updateModSeq(max(array_merge($modseq, array($highestmodseq))));
- $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCFLAGUIDS, array_keys($modseq));
- }
- }
- /**
- * Moves cache entries from the current mailbox to another mailbox.
- *
- * @param Horde_Imap_Client_Mailbox $to The destination mailbox.
- * @param array $map Mapping of source UIDs (keys) to
- * destination UIDs (values).
- * @param string $uidvalid UIDVALIDITY of destination
- * mailbox.
- *
- * @throws Horde_Imap_Client_Exception
- */
- protected function _moveCache(Horde_Imap_Client_Mailbox $to, $map,
- $uidvalid)
- {
- if (!$this->_initCache()) {
- return;
- }
- $c = $this->getParam('cache');
- if (in_array(strval($to), $c['fetch_ignore'])) {
- $this->_debug->info(sprintf(
- 'CACHE: Ignoring moving FETCH data (%s => %s)',
- $this->_selected,
- $to
- ));
- return;
- }
- $old = $this->_cache->get($this->_selected, array_keys($map), null);
- $new = array();
- foreach ($map as $key => $val) {
- if (!empty($old[$key])) {
- $new[$val] = $old[$key];
- }
- }
- if (!empty($new)) {
- $this->_cache->set($to, $new, $uidvalid);
- }
- }
- /**
- * Delete messages in the cache.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox.
- * @param Horde_Imap_Client_Ids $ids The list of IDs to delete in
- * $mailbox.
- * @param array $opts Additional options (not used
- * in base class).
- *
- * @return Horde_Imap_Client_Ids UIDs that were deleted.
- * @throws Horde_Imap_Client_Exception
- */
- protected function _deleteMsgs(Horde_Imap_Client_Mailbox $mailbox,
- Horde_Imap_Client_Ids $ids,
- array $opts = array())
- {
- if (!$this->_initCache()) {
- return $ids;
- }
- $mbox_ob = $this->_mailboxOb();
- $ids_ob = $ids->sequence
- ? $this->getIdsOb($mbox_ob->map->lookup($ids))
- : $ids;
- $this->_cache->deleteMsgs($mailbox, $ids_ob->ids);
- $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCVANISHED, $ids_ob->ids);
- $mbox_ob->map->remove($ids);
- return $ids_ob;
- }
- /**
- * Retrieve data from the search cache.
- *
- * @param string $type The cache type ('search' or 'thread').
- * @param array $options The options array of the calling function.
- *
- * @return mixed Returns search cache metadata. If search was retrieved,
- * data is in key 'data'.
- * Returns null if caching is not available.
- */
- protected function _getSearchCache($type, $options)
- {
- $status = $this->status($this->_selected, Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDVALIDITY);
- /* Search caching requires MODSEQ, which may not be active for a
- * mailbox. */
- if (empty($status['highestmodseq'])) {
- return null;
- }
- ksort($options);
- $cache = hash('md5', $type . serialize($options));
- $cacheid = $this->getSyncToken($this->_selected);
- $ret = array();
- $md = $this->_cache->getMetaData(
- $this->_selected,
- $status['uidvalidity'],
- array(self::CACHE_SEARCH, self::CACHE_SEARCHID)
- );
- if (!isset($md[self::CACHE_SEARCHID]) ||
- ($md[self::CACHE_SEARCHID] != $cacheid)) {
- $md[self::CACHE_SEARCH] = array();
- $md[self::CACHE_SEARCHID] = $cacheid;
- if ($this->_debug->debug &&
- !isset($this->_temp['searchcacheexpire'][strval($this->_selected)])) {
- $this->_debug->info(sprintf(
- 'SEARCH: Expired from cache [%s]',
- $this->_selected
- ));
- $this->_temp['searchcacheexpire'][strval($this->_selected)] = true;
- }
- } elseif (isset($md[self::CACHE_SEARCH][$cache])) {
- $this->_debug->info(sprintf(
- 'SEARCH: Retrieved %s from cache (%s [%s])',
- $type,
- $cache,
- $this->_selected
- ));
- $ret['data'] = $md[self::CACHE_SEARCH][$cache];
- unset($md[self::CACHE_SEARCHID]);
- }
- return array_merge($ret, array(
- 'id' => $cache,
- 'metadata' => $md,
- 'type' => $type
- ));
- }
- /**
- * Set data in the search cache.
- *
- * @param mixed $data The cache data to store.
- * @param string $sdata The search data returned from _getSearchCache().
- */
- protected function _setSearchCache($data, $sdata)
- {
- $sdata['metadata'][self::CACHE_SEARCH][$sdata['id']] = $data;
- $this->_cache->setMetaData($this->_selected, null, $sdata['metadata']);
- if ($this->_debug->debug) {
- $this->_debug->info(sprintf(
- 'SEARCH: Saved %s to cache (%s [%s])',
- $sdata['type'],
- $sdata['id'],
- $this->_selected
- ));
- unset($this->_temp['searchcacheexpire'][strval($this->_selected)]);
- }
- }
- /**
- * Updates the cached MODSEQ value.
- *
- * @param integer $modseq MODSEQ value to store.
- *
- * @return mixed The MODSEQ of the old value if it was replaced (or false
- * if it didn't exist or is the same).
- */
- protected function _updateModSeq($modseq)
- {
- if (!$this->_initCache(true)) {
- return false;
- }
- $mbox_ob = $this->_mailboxOb();
- $uidvalid = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY);
- $md = $this->_cache->getMetaData($this->_selected, $uidvalid, array(self::CACHE_MODSEQ));
- if (isset($md[self::CACHE_MODSEQ])) {
- if ($md[self::CACHE_MODSEQ] < $modseq) {
- $set = true;
- $sync = $md[self::CACHE_MODSEQ];
- } else {
- $set = false;
- $sync = 0;
- }
- $mbox_ob->setStatus(Horde_Imap_Client::STATUS_SYNCMODSEQ, $md[self::CACHE_MODSEQ]);
- } else {
- $set = true;
- $sync = 0;
- }
- /* $modseq can be 0 - NOMODSEQ - so don't store in that case. */
- if ($set && $modseq) {
- $this->_cache->setMetaData($this->_selected, $uidvalid, array(
- self::CACHE_MODSEQ => $modseq
- ));
- }
- return $sync;
- }
- /**
- * Synchronizes the current mailbox cache with the server (using CONDSTORE
- * or QRESYNC).
- */
- protected function _condstoreSync()
- {
- $mbox_ob = $this->_mailboxOb();
- /* Check that modseqs are available in mailbox. */
- if (!($highestmodseq = $mbox_ob->getStatus(Horde_Imap_Client::STATUS_HIGHESTMODSEQ)) ||
- !($modseq = $this->_updateModSeq($highestmodseq))) {
- $mbox_ob->sync = true;
- }
- if ($mbox_ob->sync) {
- return;
- }
- $uids_ob = $this->getIdsOb($this->_cache->get(
- $this->_selected,
- array(),
- array(),
- $mbox_ob->getStatus(Horde_Imap_Client::STATUS_UIDVALIDITY)
- ));
- if (!count($uids_ob)) {
- $mbox_ob->sync = true;
- return;
- }
- /* Are we caching flags? */
- if (array_key_exists(Horde_Imap_Client::FETCH_FLAGS, $this->_cacheFields())) {
- $fquery = new Horde_Imap_Client_Fetch_Query();
- $fquery->flags();
- /* Update flags in cache. Cache will be updated in _fetch(). */
- $this->_fetch(new Horde_Imap_Client_Fetch_Results(), array(
- array(
- '_query' => $fquery,
- 'changedsince' => $modseq,
- 'ids' => $uids_ob
- )
- ));
- }
- /* Search for deleted messages, and remove from cache. */
- $vanished = $this->vanished($this->_selected, $modseq, array(
- 'ids' => $uids_ob
- ));
- $disappear = array_diff($uids_ob->ids, $vanished->ids);
- if (!empty($disappear)) {
- $this->_deleteMsgs($this->_selected, $this->getIdsOb($disappear));
- }
- $mbox_ob->sync = true;
- }
- /**
- * Provide the list of available caching fields.
- *
- * @return array The list of available caching fields (fields are in the
- * key).
- */
- protected function _cacheFields()
- {
- $c = $this->getParam('cache');
- $out = $c['fields'];
- if (!$this->_capability()->isEnabled('CONDSTORE')) {
- unset($out[Horde_Imap_Client::FETCH_FLAGS]);
- }
- return $out;
- }
- /**
- * Return the current mailbox synchronization status.
- *
- * @param mixed $mailbox A mailbox. Either a Horde_Imap_Client_Mailbox
- * object or a string (UTF-8).
- *
- * @return array An array with status data. (This data is not guaranteed
- * to have any specific format).
- */
- protected function _syncStatus($mailbox)
- {
- $status = $this->status(
- $mailbox,
- Horde_Imap_Client::STATUS_HIGHESTMODSEQ |
- Horde_Imap_Client::STATUS_MESSAGES |
- Horde_Imap_Client::STATUS_UIDNEXT_FORCE |
- Horde_Imap_Client::STATUS_UIDVALIDITY
- );
- $fields = array('uidnext', 'uidvalidity');
- if (empty($status['highestmodseq'])) {
- $fields[] = 'messages';
- } else {
- $fields[] = 'highestmodseq';
- }
- $out = array();
- $sync_map = array_flip(Horde_Imap_Client_Data_Sync::$map);
- foreach ($fields as $val) {
- $out[$sync_map[$val]] = $status[$val];
- }
- return array_filter($out);
- }
- /**
- * Get a message UID by the Message-ID. Returns the last message in a
- * mailbox that matches.
- *
- * @param Horde_Imap_Client_Mailbox $mailbox The mailbox to search
- * @param string $msgid Message-ID.
- *
- * @return string UID (null if not found).
- */
- protected function _getUidByMessageId($mailbox, $msgid)
- {
- if (!$msgid) {
- return null;
- }
- $query = new Horde_Imap_Client_Search_Query();
- $query->headerText('Message-ID', $msgid);
- $res = $this->search($mailbox, $query, array(
- 'results' => array(Horde_Imap_Client::SEARCH_RESULTS_MAX)
- ));
- return $res['max'];
- }
- }