Do not trust case of get_class() return string
[squirrelmail.git] / functions / imap_messages.php
index c6eb5846d96d9da9dc473a6cd4226d1e7b60b888..74a2448542e48d22739432351bd13cccfae71f4a 100755 (executable)
@@ -6,7 +6,7 @@
  * This implements functions that manipulate messages
  * NOTE: Quite a few functions in this file are obsolete
  *
- * @copyright © 1999-2006 The SquirrelMail Project Team
+ * @copyright © 1999-2007 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
@@ -66,19 +66,17 @@ function sqimap_msgs_list_move($imap_stream, $id, $mailbox, $handle_errors = tru
  * @since 1.4.0
  */
 function sqimap_msgs_list_delete($imap_stream, $mailbox, $id, $bypass_trash=false) {
-    // FIX ME, remove globals by introducing an associative array with properties
-    // as 4th argument as replacement for the bypass_trash var
+    // FIXME: Remove globals by introducing an associative array with properties as 4th argument as replacement for the $bypass_trash variable.
     global $move_to_trash, $trash_folder;
-    $bRes = true;
     if (($move_to_trash == true) && ($bypass_trash != true) &&
         (sqimap_mailbox_exists($imap_stream, $trash_folder) &&  ($mailbox != $trash_folder)) ) {
-        $bRes = sqimap_msgs_list_copy ($imap_stream, $id, $trash_folder);
-    }
-    if ($bRes) {
-        return sqimap_toggle_flag($imap_stream, $id, '\\Deleted', true, true);
-    } else {
-        return false;
+        /**
+         * turn off internal error handling (fourth argument = false) and
+         * ignore copy to trash errors (allows to delete messages when overquota)
+         */
+        sqimap_msgs_list_copy ($imap_stream, $id, $trash_folder, false);
     }
+    return sqimap_toggle_flag($imap_stream, $id, '\\Deleted', true, true);
 }
 
 
@@ -119,29 +117,34 @@ function sqimap_toggle_flag($imap_stream, $id, $flag, $set, $handle_errors) {
 /**
  * Sort the message list and crunch to be as small as possible
  * (overflow could happen, so make it small if possible)
+ * @param array $aUid array with uid's
+ * @return string $s message set string
  */
-function sqimap_message_list_squisher($messages_array) {
-    if( !is_array( $messages_array ) ) {
-        return $messages_array;
-    }
-
-    sort($messages_array, SORT_NUMERIC);
-    $msgs_str = '';
-    while ($messages_array) {
-        $start = array_shift($messages_array);
-        $end = $start;
-        while (isset($messages_array[0]) && $messages_array[0] == $end + 1) {
-            $end = array_shift($messages_array);
-        }
-        if ($msgs_str != '') {
-            $msgs_str .= ',';
-        }
-        $msgs_str .= $start;
-        if ($start != $end) {
-            $msgs_str .= ':' . $end;
+function sqimap_message_list_squisher($aUid) {
+    if( !is_array( $aUid ) ) {
+        return $aUid;
+    }
+    sort($aUid, SORT_NUMERIC);
+
+    if (count($aUid)) {
+        $s = '';
+        for ($i=0,$iCnt=count($aUid);$i<$iCnt;++$i) {
+            $iStart = $aUid[$i];
+            $iEnd = $iStart;
+            while ($i<($iCnt-1) && $aUid[$i+1] == $iEnd +1) {
+                $iEnd = $aUid[$i+1];
+                ++$i;
+            }
+            if ($s) {
+                $s .= ',';
+            }
+            $s .= $iStart;
+            if ($iStart != $iEnd) {
+                $s .= ':' . $iEnd;
+            }
         }
     }
-    return $msgs_str;
+    return $s;
 }
 
 
@@ -161,8 +164,8 @@ function sqimap_get_sort_order($imap_stream, $sSortField, $reverse, $search='ALL
             $sSortField = 'REVERSE '.$sSortField;
         }
         $query = "SORT ($sSortField) ".strtoupper($default_charset)." $search";
-        // FIX ME sqimap_run_command should return the parsed data accessible by $aDATA['SORT']
-        // use sqimap_run_command_list in case of unsollicited responses. If we don't we could loose the SORT response
+        // FIXME: sqimap_run_command() should return the parsed data accessible by $aDATA['SORT']
+        // use sqimap_run_command_list() in case of unsolicited responses. If we don't we could loose the SORT response.
         $aData = sqimap_run_command_list ($imap_stream, $query, false, $response, $message, TRUE);
         /* fallback to default charset */
         if ($response == 'NO') {
@@ -199,7 +202,7 @@ function parseUidList($aData,$sCommand) {
         for ($i=0,$iCnt=count($aData);$i<$iCnt;++$i) {
             for ($j=0,$jCnt=count($aData[$i]);$j<$jCnt;++$j) {
                 if (preg_match("/^\* $sCommand (.+)$/", $aData[$i][$j], $aMatch)) {
-                    $aUid += preg_split("/ /", trim($aMatch[1]));
+                    $aUid += explode(' ', trim($aMatch[1]));
                 }
             }
         }
@@ -224,6 +227,13 @@ function get_squirrel_sort($imap_stream, $sSortField, $reverse = false, $aUid =
         $msgs = sqimap_get_small_header_list($imap_stream, $aUid,
                                       array(), array($sSortField));
     }
+
+    // sqimap_get_small_header (see above) returns fields in lower case,
+    // but the code below uses all upper case
+    foreach ($msgs as $k => $v) 
+        if (isset($msgs[$k][strtolower($sSortField)])) 
+            $msgs[$k][strtoupper($sSortField)] = $msgs[$k][strtolower($sSortField)];
+
     $aUid = array();
     $walk = false;
     switch ($sSortField) {
@@ -240,7 +250,7 @@ function get_squirrel_sort($imap_stream, $sSortField, $reverse = false, $aUid =
                  $sEmail = ($addr[SQM_ADDR_HOST]) ?
                       $addr[SQM_ADDR_MAILBOX] . "@".$addr[SQM_ADDR_HOST] :
                       $addr[SQM_ADDR_HOST];
-                 $v[$f] = ($sPersonal) ? decodeHeader($sPersonal):$sEmail;'),$sSortField);
+                 $v[$f] = ($sPersonal) ? decodeHeader($sPersonal, true, false):$sEmail;'),$sSortField);
             $walk = true;
         }
         // nobreak
@@ -248,9 +258,9 @@ function get_squirrel_sort($imap_stream, $sSortField, $reverse = false, $aUid =
         if(!$walk) {
             array_walk($msgs, create_function('&$v,&$k,$f',
                 '$v[$f] = (isset($v[$f])) ? $v[$f] : "";
-                 $v[$f] = strtolower(decodeHeader(trim($v[$f])));
-                 $v[$f] = (preg_match("/^(vedr|sv|re|aw|\[\w\]):\s*(.*)$/si", $v[$f], $matches)) ?
-                                    $matches[2] : $v[$f];'),$sSortField);
+                 $v[$f] = strtolower(decodeHeader(trim($v[$f]), true, false));
+                 $v[$f] = (preg_match("/^(?:(?:vedr|sv|re|aw|fw|fwd|\[\w\]):\s*)*\s*(.*)$/si", $v[$f], $matches)) ?
+                                    $matches[1] : $v[$f];'),$sSortField);
             $walk = true;
         }
         foreach ($msgs as $item) {
@@ -331,6 +341,7 @@ function get_thread_sort($imap_stream, $search='ALL') {
     } elseif ($response == 'BAD') {
         sqm_trigger_imap_error('SQM_IMAP_NO_THREAD',$query, $response, $message);
     }
+    $sThreadResponse = '';
     if (isset($sRead[0])) {
         for ($i=0,$iCnt=count($sRead);$i<$iCnt;++$i) {
             if (preg_match("/^\* THREAD (.+)$/", $sRead[$i], $aMatch)) {
@@ -338,8 +349,6 @@ function get_thread_sort($imap_stream, $search='ALL') {
                 break;
             }
         }
-    } else {
-        $sThreadResponse = "";
     }
     unset($sRead);
 
@@ -395,6 +404,10 @@ function get_thread_sort($imap_stream, $search='ALL') {
             $cChar = $sThreadResponse{$i};
             switch ($cChar) {
                 case '(': // new sub thread
+                    // correction for a subthread of a thread with no parents in thread
+                    if (!count($aUidSubThread) && $j > 0) {
+                       --$l;
+                    }
                     $aDepthStack[$j] = $l;
                     ++$j;
                     break;
@@ -447,33 +460,6 @@ function elapsedTime($start) {
     return $timepassed;
 }
 
-
-/**
- * Normalise the different Priority headers into a uniform value,
- * namely that of the X-Priority header (1, 3, 5). Supports:
- * Prioirty, X-Priority, Importance.
- * X-MS-Mail-Priority is not parsed because it always coincides
- * with one of the other headers.
- *
- * FIXME: DUPLICATE CODE ALERT:
- * NOTE: this is actually a duplicate from the function in
- * class/mime/Rfc822Header.php.
- * @todo obsolate function or use it instead of code block in parseFetch()
- */
-function parsePriority($sValue) {
-    $aValue = split('/\w/',trim($sValue));
-    $value = strtolower(array_shift($aValue));
-    if ( is_numeric($value) ) {
-        return $value;
-    }
-    if ( $value == 'urgent' || $value == 'high' ) {
-        return 1;
-    } elseif ( $value == 'non-urgent' || $value == 'low' ) {
-        return 5;
-    }
-    return 3;
-}
-
 /**
  * Parses a string in an imap response. String starts with " or { which means it
  * can handle double quoted strings and literal strings
@@ -620,7 +606,7 @@ function sqimap_get_small_header_list($imap_stream, $msg_list,
  * @return array   $aMessageList associative array with messages. Key is the UID, value is an associative array
  * @author Marc Groot Koerkamp
  */
-function parseFetch($aResponse,$aMessageList = array()) {
+function parseFetch(&$aResponse,$aMessageList = array()) {
     for ($j=0,$iCnt=count($aResponse);$j<$iCnt;++$j) {
         $aMsg = array();
 
@@ -722,13 +708,14 @@ function parseFetch($aResponse,$aMessageList = array()) {
                                 case 'x-priority': $aMsg['x-priority'] = ($value) ? (int) $value{0} : 3; break;
                                 case 'priority':
                                 case 'importance':
+                                    // duplicate code with Rfc822Header.cls:parsePriority()
                                     if (!isset($aMsg['x-priority'])) {
-                                        $aPrio = split('/\w/',trim($value));
+                                        $aPrio = preg_split('/\s/',trim($value));
                                         $sPrio = strtolower(array_shift($aPrio));
                                         if  (is_numeric($sPrio)) {
                                             $iPrio = (int) $sPrio;
                                         } elseif ( $sPrio == 'non-urgent' || $sPrio == 'low' ) {
-                                            $iPrio = 3;
+                                            $iPrio = 5;
                                         } elseif ( $sPrio == 'urgent' || $sPrio == 'high' ) {
                                             $iPrio = 1;
                                         } else {
@@ -773,6 +760,7 @@ function parseFetch($aResponse,$aMessageList = array()) {
             $msgi = '';
        }
        $aMessageList[$msgi] = $aMsg;
+       $aResponse[$j] = NULL;
     }
     return $aMessageList;
 }
@@ -903,9 +891,10 @@ function sqimap_parse_address($read, &$i) {
  * @param  resource $imap_stream imap connection
  * @param  integer  $id uid of the message
  * @param  string   $mailbox used for error handling, can be removed because we should return an error code and generate the message elsewhere
- * @return Message  Message object
+ * @param  int      $hide Indicates whether or not to hide any errors: 0 = don't hide, 1 = hide (just exit), 2 = hide (return FALSE), 3 = hide (return error string) (OPTIONAL; default don't hide)
+ * @return mixed  Message object or FALSE/error string if error occurred and $hide is set to 2/3
  */
-function sqimap_get_message($imap_stream, $id, $mailbox) {
+function sqimap_get_message($imap_stream, $id, $mailbox, $hide=0) {
     // typecast to int to prohibit 1:* msgs sets
     $id = (int) $id;
     $flags = array();
@@ -913,17 +902,25 @@ function sqimap_get_message($imap_stream, $id, $mailbox) {
     if ($read) {
         if (preg_match('/.+FLAGS\s\((.*)\)\s/AUi',$read[0],$regs)) {
             if (trim($regs[1])) {
-                $flags = preg_split('/ /', $regs[1],-1,'PREG_SPLIT_NI_EMPTY');
+                $flags = preg_split('/ /', $regs[1],-1,PREG_SPLIT_NO_EMPTY);
             }
         }
     } else {
+
+        if ($hide == 1) exit;
+        if ($hide == 2) return FALSE;
+
         /* the message was not found, maybe the mailbox was modified? */
-        global $sort, $startMessage, $color;
+        global $sort, $startMessage;
+
+        $errmessage = _("The server couldn't find the message you requested.");
+
+        if ($hide == 3) return $errmessage;
+
+        $errmessage .= '<p>'._("Most probably your message list was out of date and the message has been moved away or deleted (perhaps by another program accessing the same mailbox).");
 
-        $errmessage = _("The server couldn't find the message you requested.") .
-            '<p>'._("Most probably your message list was out of date and the message has been moved away or deleted (perhaps by another program accessing the same mailbox).");
         /* this will include a link back to the message list */
-        error_message($errmessage, $mailbox, $sort, (int) $startMessage, $color);
+        error_message($errmessage, $mailbox, $sort, (int) $startMessage);
         exit;
     }
     $bodystructure = implode('',$read);
@@ -932,7 +929,39 @@ function sqimap_get_message($imap_stream, $id, $mailbox) {
     $rfc822_header = new Rfc822Header();
     $rfc822_header->parseHeader($read);
     $msg->rfc822_header = $rfc822_header;
+
+    parse_message_entities($msg, $id, $imap_stream);
     return $msg;
-}
+ }
 
-?>
+
+/**
+ * Recursively parse embedded messages (if any) in the given
+ * message, building correct rfc822 headers for each one
+ *
+ * @param object $msg The message object to scan for attached messages
+ *                    NOTE: this is passed by reference!  Changes made
+ *                    within will affect the caller's copy of $msg!
+ * @param int $id The top-level message UID on the IMAP server, even
+ *                if the $msg being passed in is only an attached entity
+ *                thereof.
+ * @param resource $imap_stream A live connection to the IMAP server.
+ *
+ * @return void
+ *
+ * @since 1.5.2
+ *
+ */
+function parse_message_entities(&$msg, $id, $imap_stream) {
+    if (!empty($msg->entities)) foreach ($msg->entities as $i => $entity) {
+        if (is_object($entity) && strtolower(get_class($entity)) == 'message') {
+            if (!empty($entity->rfc822_header)) {
+                $read = sqimap_run_command($imap_stream, "FETCH $id BODY[". $entity->entity_id .".HEADER]", true, $response, $message, TRUE);
+                $rfc822_header = new Rfc822Header();
+                $rfc822_header->parseHeader($read);
+                $msg->entities[$i]->rfc822_header = $rfc822_header;
+            }
+            parse_message_entities($msg->entities[$i], $id, $imap_stream);
+        }
+    }
+}