protected function _search($query, $options)
{
$sort_criteria = array(Horde_Imap_Client::SORT_ARRIVAL => 'ARRIVAL', Horde_Imap_Client::SORT_CC => 'CC', Horde_Imap_Client::SORT_DATE => 'DATE', Horde_Imap_Client::SORT_DISPLAYFROM => 'DISPLAYFROM', Horde_Imap_Client::SORT_DISPLAYTO => 'DISPLAYTO', Horde_Imap_Client::SORT_FROM => 'FROM', Horde_Imap_Client::SORT_REVERSE => 'REVERSE', Horde_Imap_Client::SORT_RELEVANCY => 'RELEVANCY', Horde_Imap_Client::SORT_SEQUENCE => 'SEQUENCE', Horde_Imap_Client::SORT_SIZE => 'SIZE', Horde_Imap_Client::SORT_SUBJECT => 'SUBJECT', Horde_Imap_Client::SORT_TO => 'TO');
$results_criteria = array(Horde_Imap_Client::SEARCH_RESULTS_COUNT => 'COUNT', Horde_Imap_Client::SEARCH_RESULTS_MATCH => 'ALL', Horde_Imap_Client::SEARCH_RESULTS_MAX => 'MAX', Horde_Imap_Client::SEARCH_RESULTS_MIN => 'MIN', Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY => 'RELEVANCY', Horde_Imap_Client::SEARCH_RESULTS_SAVE => 'SAVE');
// Check if the server supports sorting (RFC 5256).
$esearch = $return_sort = $server_seq_sort = $server_sort = false;
if (!empty($options['sort'])) {
/* Make sure sort options are correct. If not, default to no
* sort. */
if (count(array_intersect($options['sort'], array_keys($sort_criteria))) === 0) {
unset($options['sort']);
} else {
$return_sort = true;
if ($this->_capability('SORT')) {
/* Make sure server supports DISPLAYFROM & DISPLAYTO. */
$server_sort = !array_intersect($options['sort'], array(Horde_Imap_Client::SORT_DISPLAYFROM, Horde_Imap_Client::SORT_DISPLAYTO)) || $this->_capability('SORT', 'DISPLAY');
}
/* If doing a sequence sort, need to do this on the client
* side. */
if ($server_sort && in_array(Horde_Imap_Client::SORT_SEQUENCE, $options['sort'])) {
$server_sort = false;
/* Optimization: If doing only a sequence sort, just do a
* simple search and sort UIDs/sequences on client side. */
switch (count($options['sort'])) {
case 1:
$server_seq_sort = true;
break;
case 2:
$server_seq_sort = reset($options['sort']) == Horde_Imap_Client::SORT_REVERSE;
break;
}
}
}
}
$charset = is_null($options['_query']['charset']) ? 'US-ASCII' : $options['_query']['charset'];
$partial = false;
if ($server_sort) {
$cmd = $this->_command(empty($options['sequence']) ? 'UID SORT' : 'SORT');
$results = array();
// Use ESEARCH (RFC 4466) response if server supports.
$esearch = false;
// Check for ESORT capability (RFC 5267)
if ($this->_capability('ESORT')) {
foreach ($options['results'] as $val) {
if (isset($results_criteria[$val]) && $val != Horde_Imap_Client::SEARCH_RESULTS_SAVE) {
$results[] = $results_criteria[$val];
}
}
$esearch = true;
}
// Add PARTIAL limiting (RFC 5267 [4.4])
if ((!$esearch || !empty($options['partial'])) && $this->_capability('CONTEXT', 'SORT')) {
/* RFC 5267 indicates RFC 4466 ESEARCH-like support,
* notwithstanding "real" RFC 4731 support. */
$esearch = true;
if (!empty($options['partial'])) {
/* Can't have both ALL and PARTIAL returns. */
$results = array_diff($results, array('ALL'));
$results[] = 'PARTIAL';
$results[] = $options['partial'];
$partial = true;
}
}
if ($esearch && empty($this->_init['noesearch'])) {
$cmd->add(array('RETURN', new Horde_Imap_Client_Data_Format_List($results)));
}
$tmp = new Horde_Imap_Client_Data_Format_List();
foreach ($options['sort'] as $val) {
if (isset($sort_criteria[$val])) {
$tmp->add($sort_criteria[$val]);
}
}
$cmd->add($tmp);
/* Charset is mandatory for SORT (RFC 5256 [3]).
* However, if UTF-8 support is activated, a client MUST NOT
* send the charset specification (RFC 6855 [3]; Errata 4029). */
if (!$this->_capability()->isEnabled('UTF8=ACCEPT')) {
$cmd->add($charset);
}
} else {
$cmd = $this->_command(empty($options['sequence']) ? 'UID SEARCH' : 'SEARCH');
$esearch = false;
$results = array();
// Check if the server supports ESEARCH (RFC 4731).
if ($this->_capability('ESEARCH')) {
foreach ($options['results'] as $val) {
if (isset($results_criteria[$val])) {
$results[] = $results_criteria[$val];
}
}
$esearch = true;
}
// Add PARTIAL limiting (RFC 5267 [4.4]).
if ((!$esearch || !empty($options['partial'])) && $this->_capability('CONTEXT', 'SEARCH')) {
/* RFC 5267 indicates RFC 4466 ESEARCH-like support,
* notwithstanding "real" RFC 4731 support. */
$esearch = true;
if (!empty($options['partial'])) {
// Can't have both ALL and PARTIAL returns.
$results = array_diff($results, array('ALL'));
$results[] = 'PARTIAL';
$results[] = $options['partial'];
$partial = true;
}
}
if ($esearch && empty($this->_init['noesearch'])) {
// Always use ESEARCH if available because it returns results
// in a more compact sequence-set list
$cmd->add(array('RETURN', new Horde_Imap_Client_Data_Format_List($results)));
}
/* Charset is optional for SEARCH (RFC 3501 [6.4.4]).
* If UTF-8 support is activated, a client MUST NOT
* send the charset specification (RFC 6855 [3]; Errata 4029). */
if ($charset != 'US-ASCII' && !$this->_capability()->isEnabled('UTF8=ACCEPT')) {
$cmd->add(array('CHARSET', $options['_query']['charset']));
}
}
$cmd->add($options['_query']['query'], true);
$pipeline = $this->_pipeline($cmd);
$pipeline->data['esearchresp'] = array();
$er =& $pipeline->data['esearchresp'];
$pipeline->data['searchresp'] = $this->getIdsOb(array(), !empty($options['sequence']));
$sr =& $pipeline->data['searchresp'];
try {
$resp = $this->_sendCmd($pipeline);
} catch (Horde_Imap_Client_Exception $e) {
if ($e instanceof Horde_Imap_Client_Exception_ServerResponse && $e->status === Horde_Imap_Client_Interaction_Server::NO && $charset != 'US-ASCII') {
/* RFC 3501 [6.4.4]: BADCHARSET response code is only a
* SHOULD return. If it doesn't exist, need to check for
* command status of 'NO'. List of supported charsets in
* the BADCHARSET response has already been parsed and stored
* at this point. */
$this->search_charset->setValid($charset, false);
$e->setCode(Horde_Imap_Client_Exception::BADCHARSET);
}
if (empty($this->_temp['search_retry'])) {
$this->_temp['search_retry'] = true;
/* Bug #9842: Workaround broken Cyrus servers (as of
* 2.4.7). */
if ($esearch && $charset != 'US-ASCII') {
$this->_capability()->remove('ESEARCH');
$this->_setInit('noesearch', true);
try {
return $this->_search($query, $options);
} catch (Horde_Imap_Client_Exception $e) {
}
}
/* Try to convert charset. */
if ($e->getCode() === Horde_Imap_Client_Exception::BADCHARSET && $charset != 'US-ASCII') {
foreach ($this->search_charset->charsets as $val) {
$this->_temp['search_retry'] = 1;
$new_query = clone $query;
try {
$new_query->charset($val);
$options['_query'] = $new_query->build($this);
return $this->_search($new_query, $options);
} catch (Horde_Imap_Client_Exception $e) {
}
}
}
unset($this->_temp['search_retry']);
}
throw $e;
}
if ($return_sort && !$server_sort) {
if ($server_seq_sort) {
$sr->sort();
if (reset($options['sort']) == Horde_Imap_Client::SORT_REVERSE) {
$sr->reverse();
}
} else {
if (!isset($this->_temp['clientsort'])) {
$this->_temp['clientsort'] = new Horde_Imap_Client_Socket_ClientSort($this);
}
$sr = $this->getIdsOb($this->_temp['clientsort']->clientSort($sr, $options), !empty($options['sequence']));
}
}
if (!$partial && !empty($options['partial'])) {
$partial = $this->getIdsOb($options['partial'], true);
$min = $partial->min - 1;
$sr->sort();
$sr = $this->getIdsOb(array_slice($sr->ids, $min, $partial->max - $min), !empty($options['sequence']));
}
$ret = array();
foreach ($options['results'] as $val) {
switch ($val) {
case Horde_Imap_Client::SEARCH_RESULTS_COUNT:
$ret['count'] = $esearch && !$partial ? $er['count'] : count($sr);
break;
case Horde_Imap_Client::SEARCH_RESULTS_MATCH:
$ret['match'] = $sr;
break;
case Horde_Imap_Client::SEARCH_RESULTS_MAX:
$ret['max'] = $esearch ? !$partial && isset($er['max']) ? $er['max'] : null : (count($sr) ? max($sr->ids) : null);
break;
case Horde_Imap_Client::SEARCH_RESULTS_MIN:
$ret['min'] = $esearch ? !$partial && isset($er['min']) ? $er['min'] : null : (count($sr) ? min($sr->ids) : null);
break;
case Horde_Imap_Client::SEARCH_RESULTS_RELEVANCY:
$ret['relevancy'] = $esearch && isset($er['relevancy']) ? $er['relevancy'] : array();
break;
case Horde_Imap_Client::SEARCH_RESULTS_SAVE:
$this->_temp['search_save'] = $ret['save'] = $esearch ? empty($resp->data['searchnotsaved']) : false;
break;
}
}
// Add modseq data, if needed.
if (!empty($er['modseq'])) {
$ret['modseq'] = $er['modseq'];
}
unset($this->_temp['search_retry']);
/* Check for EXPUNGEISSUED (RFC 2180 [4.3]/RFC 5530 [3]). */
if (!empty($resp->data['expungeissued'])) {
$this->noop();
}
return $ret;
}