* - 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. *
* - 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 *
* - 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: '.' *
* - 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. * * @param array $opts Additional options: * * - 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: '.' * * * @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: * * - 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. * * @param array $options Additonal options: * * - create: (boolean) Try to create $mailbox if it does not exist? * DEFAULT: No. * * * @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: * * - 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 * * * @return array An array with the following keys: * * - 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. * * * @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: * * - 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. * * * @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']; } }
* - 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: '.' *
* - 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. *
* - create: (boolean) Try to create $mailbox if it does not exist? * DEFAULT: No. *
* - 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 *
* - 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. *
* - 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. *