Source for file imap_asearch.php
Documentation is available at imap_asearch.php
* Subfolder search idea from Patch #806075 by Thomas Pohl xraven at users.sourceforge.net. Thanks Thomas!
* @author Alex Lemaresquier - Brainstorm <alex at brainstorm.fr>
* @copyright 1999-2020 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id: imap_asearch.php 14845 2020-01-07 08:09:34Z pdontthink $
* @link http://www.ietf.org/rfc/rfc3501.txt
/** This functionality requires the IMAP and date functions
//require_once(SM_PATH . 'functions/imap_general.php');
//require_once(SM_PATH . 'functions/date.php');
/** Set to TRUE to dump the IMAP dialogue
* @global bool $imap_asearch_debug_dump
$imap_asearch_debug_dump =
FALSE;
* @global array $imap_asearch_opcodes
global $imap_asearch_opcodes;
$imap_asearch_opcodes =
array(
/* <sequence-set> => 'asequence', */ // Special handling, @see sqimap_asearch_build_criteria()
/*'ALL' is binary operator */
'HEADER' =>
'afield', // Special syntax for this one, @see sqimap_asearch_build_criteria()
/*'NOT' is unary operator */
/*'OR' is binary operator */
'UNKEYWORD' =>
'akeyword',
/** IMAP SEARCH month names encoding
* @global array $imap_asearch_months
$imap_asearch_months =
array(
* Function to display an error related to an IMAP query.
* We need to do our own error management since we may receive NO responses on purpose (even BAD with SORT or THREAD)
* so we call sqimap_error_box() if the function exists (sm >= 1.5) or use our own embedded code
* @global array imap_error_titles
* @param string $response the imap server response code
* @param string $query the failed query
* @param string $message an optional error message
* @param string $link an optional link to try again
//@global array color sm colors array
// Error message titles according to IMAP server returned code
$imap_error_titles =
array(
'NO' =>
_("ERROR: Could not complete request."),
'BAD' =>
_("ERROR: Bad or malformed request."),
'BYE' =>
_("ERROR: IMAP server closed the connection."),
'' =>
_("ERROR: Connection dropped by IMAP server.")
$title =
_("ERROR: Unknown IMAP response.");
$title =
$imap_error_titles[$response];
$message_title =
_("Reason Given:");
$message_title =
_("Possible reason:");
* This is a convenient way to avoid spreading if (isset(... all over the code
* @param mixed $var any variable (reference)
* @param mixed $def default value to return if unset (default is zls (''), pass 0 or array() when appropriate)
* @return mixed $def if $var is unset, otherwise $var
* This should give the same results as PHP 4 >= 4.3.0's html_entity_decode(),
* except it doesn't handle hex constructs
* @param string $string string to unhtmlentity()
* @return string decoded string
for ($i=
127; $i<
255; $i++
) /* Add &#<dec>; entities */
$trans_tbl['&#' .
$i .
';'] =
chr($i);
return strtr($string, $trans_tbl);
/* I think the one above is quicker, though it should be benchmarked
$string = strtr($string, array_flip(get_html_translation_table(HTML_ENTITIES)));
return preg_replace("/&#([0-9]+);/E", "chr('\\1')", $string);
/** Encode a string to quoted or literal as defined in rfc 3501
* A quoted string is a sequence of zero or more 7-bit characters,
* excluding CR and LF, with double quote (<">) characters at each end.
* quoted-specials = DQUOTE / "\"
* @param string $what string to encode
* @param string $charset search charset used
* @return string encoded string
if (strtoupper($charset) ==
'ISO-2022-JP') // This should be now handled in imap_utf7_local?
return '{' .
strlen($what) .
"}\r\n" .
$what; // 4.3 literal form
return '"' .
$what .
'"'; // 4.3 quoted string form
* Parses a user date string into an rfc 3501 date string
* Handles space, slash, backslash, dot and comma as separators (and dash of course ;=)
* @global array imap_asearch_months
* @param string user date
* @return array a preg_match-style array:
* - [0] = fully formatted rfc 3501 date string (<day number>-<US month TLA>-<4 digit year>)
preg_match('/^([0-9]+)-+([^\-]+)-+([0-9]+)$/', $what, $what_parts);
if (count($what_parts) ==
4) {
/* if (!in_array($what_month, $imap_asearch_months)) {*/
foreach ($imap_asearch_months as $month_number =>
$month_code) {
if (($what_month ==
$month_number)
||
($what_month ==
$month_code)
$what_parts[2] =
$month_number;
$what_parts[0] =
$what_parts[1] .
'-' .
$month_code .
'-' .
$what_parts[3];
* Build one criteria sequence
* @global array imap_asearch_opcodes
* @param string $opcode search opcode
* @param string $what opcode argument
* @param string $charset search charset
* @return string one full criteria sequence
switch ($imap_asearch_opcodes[$opcode]) {
$what =
substr($what, 0, -
1) <<
30;
$what =
substr($what, 0, -
1) <<
20;
$what =
substr($what, 0, -
1) <<
10;
$criteria =
$opcode .
' ' .
$what .
' ';
$criteria =
$opcode .
' ';
case 'afield':
/* HEADER field-name: field-body */
preg_match('/^([^:]+):(.*)$/', $what, $what_parts);
if (count($what_parts) ==
3)
$criteria =
$opcode .
' ' .
if (isset
($what_parts[0]))
$criteria =
$opcode .
' ' .
$what_parts[0] .
' ';
$criteria =
$opcode .
' ' .
$what .
' ';
* Another way to do array_values(array_unique(array_merge($to, $from)));
* @param array $to to array (reference)
* @param array $from from array
* @return array uniquely merged array
for ($i =
0; $i <
$count; $i++
) {
* Run the IMAP SEARCH command as defined in rfc 3501
* @link http://www.ietf.org/rfc/rfc3501.txt
* @param resource $imapConnection the current imap stream
* @param string $search_string the full search expression eg "ALL RECENT"
* @param string $search_charset charset to use or zls ('')
* @return array an IDs or UIDs array of matching messages or an empty array
//For some reason, this seems to happen and forbids searching servers not allowing OPTIONAL [CHARSET]
/* 6.4.4 try OPTIONAL [CHARSET] specification first */
if ($search_charset !=
'')
$query =
'SEARCH CHARSET "' .
strtoupper($search_charset) .
'" ' .
$search_string;
$query =
'SEARCH ' .
$search_string;
/* 6.4.4 try US-ASCII charset if we tried an OPTIONAL [CHARSET] and received a tagged NO response (SHOULD be [BADCHARSET]) */
if (($search_charset !=
'') &&
(strtoupper($response) ==
'NO')) {
$query =
'SEARCH CHARSET US-ASCII ' .
$search_string;
if (empty($messagelist)) //Empty search response, ie '* SEARCH'
$cnt =
count($messagelist);
for ($q =
0; $q <
$cnt; $q++
)
$id[$q] =
trim($messagelist[$q]);
* @global bool allow_charset_search user setting
* @global array languages sm languages array
* @global string squirrelmail_language user language setting
* @return string the user defined charset if $allow_charset_search is TRUE else zls ('')
if ($allow_charset_search)
return $languages[$squirrelmail_language]['CHARSET'];
* Convert SquirrelMail internal sort to IMAP sort taking care of:
* - user defined date sorting (ARRIVAL vs DATE)
* - if the searched mailbox is the sent folder then TO is being used instead of FROM
* - reverse order by using REVERSE
* @param string $mailbox mailbox name to sort
* @param integer $sort_by sm sort criteria index
* @global bool internal_date_sort sort by arrival date instead of message date
* @global string sent_folder sent folder name
* @return string imap sort criteria
global $internal_date_sort, $sent_folder;
$sort_opcodes =
array ('DATE', 'FROM', 'SUBJECT', 'SIZE');
if ($internal_date_sort ==
true)
$sort_opcodes[0] =
'ARRIVAL';
// FIXME: Why are these commented out? I have no idea what this code does, but both of these functions sound more robust than the simple string check that's being used now. Someone who understands this code should either fix this or remove these lines completely or document why they are here commented out
// if (handleAsSent($mailbox))
// if (isSentFolder($mailbox))
if ($mailbox ==
$sent_folder)
return (($sort_by %
2) ?
'' :
'REVERSE ') .
$sort_opcodes[($sort_by >>
1) & 3];
* @param string $cur_mailbox unformatted mailbox name
* @param array $boxes_unformatted selectable mailbox unformatted names array (reference)
* @return array sub mailboxes unformatted names
$sub_mboxes_array =
array();
$boxcount =
count($mboxes_array);
for ($boxnum=
0; $boxnum <
$boxcount; $boxnum++
) {
if (isBoxBelow($mboxes_array[$boxnum], $cur_mailbox))
$sub_mboxes_array[] =
$mboxes_array[$boxnum];
return $sub_mboxes_array;
* Create the search query strings for all given criteria and merge results for every mailbox
* @param resource $imapConnection
* @param array $mailbox_array (reference)
* @param array $biop_array (reference)
* @param array $unop_array (reference)
* @param array $where_array (reference)
* @param array $what_array (reference)
* @param array $exclude_array (reference)
* @param array $sub_array (reference)
* @param array $mboxes_array selectable unformatted mailboxes names (reference)
* @return array array(mailbox => array(UIDs))
function sqimap_asearch($imapConnection, &$mailbox_array, &$biop_array, &$unop_array, &$where_array, &$what_array, &$exclude_array, &$sub_array, &$mboxes_array)
$cur_mailbox =
$mailbox_array[0];
$cur_biop =
''; /* Start with ALL */
/* We loop one more time than the real array count, so the last search gets fired */
for ($cur_crit=
0,$iCnt=
count($where_array); $cur_crit <=
$iCnt; ++
$cur_crit) {
if (empty($exclude_array[$cur_crit])) {
$next_mailbox =
(isset
($mailbox_array[$cur_crit])) ?
$mailbox_array[$cur_crit] :
false;
if ($next_mailbox !=
$cur_mailbox) {
$search_string =
trim($search_string); /* Trim out last space */
if ($cur_mailbox ==
'All Folders')
$search_mboxes =
$mboxes_array;
else if ((!empty($sub_array[$cur_crit -
1])) ||
(!in_array($cur_mailbox, $mboxes_array)))
$search_mboxes =
array($cur_mailbox);
foreach ($search_mboxes as $cur_mailbox) {
if (isset
($mbox_search[$cur_mailbox])) {
$mbox_search[$cur_mailbox]['search'] .=
' ' .
$search_string;
$mbox_search[$cur_mailbox]['search'] =
$search_string;
$mbox_search[$cur_mailbox]['charset'] =
$search_charset;
$cur_mailbox =
$next_mailbox;
if (isset
($where_array[$cur_crit]) &&
empty($exclude_array[$cur_crit])) {
for ($crit =
$cur_crit; $crit <
count($where_array); $crit++
) {
if (!empty($criteria) &&
empty($exclude_array[$crit])) {
if (asearch_nz($mailbox_array[$crit]) ==
$cur_mailbox) {
$unop =
$unop_array[$crit];
$criteria =
$unop .
' ' .
$criteria;
$aCriteria[] =
array($biop_array[$crit], $criteria);
$exclude_array[$crit] =
true;
for($i=
0,$iCnt=
count($aCriteria);$i<
$iCnt;++
$i) {
$cur_biop =
$aCriteria[$i][0];
$next_biop =
(isset
($aCriteria[$i+
1][0])) ?
$aCriteria[$i+
1][0] :
false;
if ($next_biop !=
$cur_biop &&
$next_biop ==
'OR') {
$aSearch[] =
'OR '.
$aCriteria[$i][1];
} else if ($cur_biop !=
'OR') {
$aSearch[] =
'ALL '.
$aCriteria[$i][1];
} else { // OR only supports 2 search keys so we need to create a parenthesized list
$prev_biop =
(isset
($aCriteria[$i-
1][0])) ?
$aCriteria[$i-
1][0] :
false;
if ($prev_biop ==
$cur_biop) {
if (!substr($last,-
1) ==
')') {
$aSearch[$i-
1] =
"(OR $last";
$aSearch[] =
$aCriteria[$i][1].
')';
while ($last &&
substr($last,-
1) ==
')') {
$aSearch[$i-
1] =
"(OR $last";
$aSearch[] =
$aCriteria[$i][1].
$sEnd.
')';
$aSearch[] =
$aCriteria[$i][1];
$search_string .=
implode(' ',$aSearch);
Documentation generated on Mon, 13 Jan 2020 04:22:43 +0100 by phpDocumentor 1.4.3