Builds an IMAP4rev1 compliant search string.
public build ( Horde_Imap_Client_Base $exts = [] ) : array | ||
$exts | Horde_Imap_Client_Base | The server object this query will be run on (@since 2.24.0), a Horde_Imap_Client_Data_Capability object (@since 2.24.0), or the list of extensions present on the server (@deprecated). If null, all extensions are assumed to be available. |
return | array | An array with these elements: - charset: (string) The charset of the search string. If null, no text strings appear in query. - exts: (array) The list of IMAP extensions used to create the string. - query: (Horde_Imap_Client_Data_Format_List) The IMAP search command. |
public function testHeaderTextUtf8Query() { $ob = new Horde_Imap_Client_Search_Query(); $ob->headerText('Foo', 'EëE'); try { $ob->build(); $this->fail(); } catch (Horde_Imap_Client_Data_Format_Exception $e) { // Expected } $ob->charset('UTF-8', false); $this->assertNotEmpty($ob->build()); }
/** * 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 (empty($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 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: * <ul> * <li> * nocache: (boolean) Don't cache the results. * DEFAULT: false (results cached, if possible) * </li> * <li> * partial: (mixed) The range of results to return (message sequence * numbers). * DEFAULT: All messages are returned. * </li> * <li> * results: (array) The data to return. Consists of zero or more of * the following flags: * <ul> * <li>Horde_Imap_Client::SEARCH_RESULTS_COUNT</li> * <li>Horde_Imap_Client::SEARCH_RESULTS_MATCH (DEFAULT)</li> * <li>Horde_Imap_Client::SEARCH_RESULTS_MAX</li> * <li>Horde_Imap_Client::SEARCH_RESULTS_MIN</li> * <li> * Horde_Imap_Client::SEARCH_RESULTS_SAVE (This option is currently * meant for internal use only) * </li> * <li>Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY</li> * </ul> * </li> * <li> * sequence: (boolean) If true, returns an array of sequence numbers. * DEFAULT: Returns an array of UIDs * </li> * <li> * 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: * <ul> * <li>Horde_Imap_Client::SORT_ARRIVAL</li> * <li>Horde_Imap_Client::SORT_CC</li> * <li>Horde_Imap_Client::SORT_DATE</li> * <li>Horde_Imap_Client::SORT_FROM</li> * <li>Horde_Imap_Client::SORT_SEQUENCE</li> * <li>Horde_Imap_Client::SORT_SIZE</li> * <li>Horde_Imap_Client::SORT_SUBJECT</li> * <li>Horde_Imap_Client::SORT_TO</li> * <li> * [On servers that support SORT=DISPLAY, these criteria are also * available:] * <ul> * <li>Horde_Imap_Client::SORT_DISPLAYFROM</li> * <li>Horde_Imap_Client::SORT_DISPLAYTO</li> * </ul> * </li> * <li> * [On servers that support SEARCH=FUZZY, this criteria is also * available:] * <ul> * <li>Horde_Imap_Client::SORT_RELEVANCY</li> * </ul> * </li> * </ul> * </li> * </ul> * * @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); } // Default to an ALL search. if (is_null($query)) { $query = new Horde_Imap_Client_Search_Query(); } // Check for supported charset. $options['_query'] = $query->build($this->capability()); if (!is_null($options['_query']['charset']) && array_key_exists($options['_query']['charset'], $this->_init['s_charset']) && !$this->_init['s_charset'][$options['_query']['charset']]) { foreach (array_merge(array_keys(array_filter($this->_init['s_charset'])), array('US-ASCII')) 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; $options['_query'] = $query->build($this->capability()); } /* 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', $options['_query']['exts_used'])) { throw new InvalidArgumentException('Cannot specify RELEVANCY results if not doing a FUZZY search.'); } /* 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 ($options['_query']['query']) { case 'ALL': $ret = $this->status($this->_selected, Horde_Imap_Client::STATUS_MESSAGES); return array('count' => $ret['messages']); case 'RECENT': $ret = $this->status($this->_selected, 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 - the generated query is already added to '_query' key * above. */ $cache = null; if (empty($options['nocache']) && $this->_initCache(true) && (isset($this->_init['enabled']['CONDSTORE']) || !$query->flagSearch())) { $cache = $this->_getSearchCache('search', $this->_selected, $options); if (is_array($cache)) { 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'])) { $ret = $this->_search($query, $options); } else { $ret = array('count' => 0); foreach ($options['results'] as $val) { switch ($val) { case Horde_Imap_Client::SEARCH_RESULTS_MATCH: $ret['match'] = $this->getIdsOb(); break; case Horde_Imap_Client::SEARCH_RESULTS_MAX: $ret['max'] = null; break; case Horde_Imap_Client::SEARCH_RESULTS_MIN: $ret['min'] = null; break; case Horde_Imap_Client::SEARCH_RESULTS_MIN: if (isset($status_res['highestmodseq'])) { $ret['modseq'] = $status_res['highestmodseq']; } break; case Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY: $ret['relevancy'] = array(); break; } } } if ($cache) { $save = $ret; if (isset($save['match'])) { $save['match'] = strval($ret['match']); } $this->_setSearchCache($save, $cache); } return $ret; }