Source for file imap_messages.php

Documentation is available at imap_messages.php

  1. <?php
  2.  
  3. /**
  4.  * imap_messages.php
  5.  *
  6.  * This implements functions that manipulate messages
  7.  * NOTE: Quite a few functions in this file are obsolete
  8.  *
  9.  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  10.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  11.  * @version $Id: imap_messages.php,v 1.208 2006/09/30 09:49:08 tokul Exp $
  12.  * @package squirrelmail
  13.  * @subpackage imap
  14.  */
  15.  
  16.  
  17. /**
  18.  * Copy a set of messages ($id) to another mailbox ($mailbox)
  19.  * @param int $imap_stream The resource ID for the IMAP socket
  20.  * @param string $id The list of messages to copy
  21.  * @param string $mailbox The destination to copy to
  22.  * @param bool $handle_errors Show error messages in case of a NO, BAD or BYE response
  23.  * @return bool If the copy completed without errors
  24.  */
  25. function sqimap_msgs_list_copy($imap_stream$id$mailbox$handle_errors true{
  26.     $msgs_id sqimap_message_list_squisher($id);
  27.     $read sqimap_run_command ($imap_stream"COPY $msgs_id sqimap_encode_mailbox_name($mailbox)$handle_errors$response$messageTRUE);
  28.     if ($response == 'OK'{
  29.         return true;
  30.     else {
  31.         return false;
  32.     }
  33. }
  34.  
  35.  
  36. /**
  37.  * Move a set of messages ($id) to another mailbox. Deletes the originals.
  38.  * @param int $imap_stream The resource ID for the IMAP socket
  39.  * @param string $id The list of messages to move
  40.  * @param string $mailbox The destination to move to
  41.  * @param bool $handle_errors Show error messages in case of a NO, BAD or BYE response
  42.  * @param string $source_mailbox (since 1.5.1) name of source mailbox. It is used to
  43.  *   validate that target mailbox != source mailbox.
  44.  * @return bool If the move completed without errors
  45.  */
  46. function sqimap_msgs_list_move($imap_stream$id$mailbox$handle_errors true$source_mailbox false{
  47.     if ($source_mailbox!==false && $source_mailbox==$mailbox{
  48.         return false;
  49.     }
  50.     $msgs_id sqimap_message_list_squisher($id);
  51.     if (sqimap_msgs_list_copy ($imap_stream$id$mailbox$handle_errors)) {
  52.         return sqimap_toggle_flag($imap_stream$id'\\Deleted'truetrue);
  53.     else {
  54.         return false;
  55.     }
  56. }
  57.  
  58.  
  59. /**
  60.  * Deletes a message and move it to trash or expunge the mailbox
  61.  * @param  resource imap connection
  62.  * @param  string $mailbox mailbox, used for checking if it concerns the trash_folder
  63.  * @param  array $id list with uid's
  64.  * @param  bool   $bypass_trash (since 1.5.0) skip copy to trash
  65.  * @return array  $aMessageList array with messages containing the new flags and UID @see parseFetch
  66.  * @since 1.4.0
  67.  */
  68. function sqimap_msgs_list_delete($imap_stream$mailbox$id$bypass_trash=false{
  69.     // FIX ME, remove globals by introducing an associative array with properties
  70.     // as 4th argument as replacement for the bypass_trash var
  71.     global $move_to_trash$trash_folder;
  72.     if (($move_to_trash == true&& ($bypass_trash != true&&
  73.         (sqimap_mailbox_exists($imap_stream$trash_folder&&  ($mailbox != $trash_folder)) ) {
  74.         /**
  75.          * turn off internal error handling (fourth argument = false) and
  76.          * ignore copy to trash errors (allows to delete messages when overquota)
  77.          */
  78.         sqimap_msgs_list_copy ($imap_stream$id$trash_folderfalse);
  79.     }
  80.     return sqimap_toggle_flag($imap_stream$id'\\Deleted'truetrue);
  81. }
  82.  
  83.  
  84. /**
  85.  * Set a flag on the provided uid list
  86.  * @param  resource imap connection
  87.  * @param  array $id list with uid's
  88.  * @param  string $flag Flags to set/unset flags can be i.e.'\Seen', '\Answered', '\Seen \Answered'
  89.  * @param  bool   $set  add (true) or remove (false) the provided flag
  90.  * @param  bool   $handle_errors Show error messages in case of a NO, BAD or BYE response
  91.  * @return array  $aMessageList array with messages containing the new flags and UID @see parseFetch
  92.  */
  93. function sqimap_toggle_flag($imap_stream$id$flag$set$handle_errors{
  94.     $msgs_id sqimap_message_list_squisher($id);
  95.     $set_string ($set '+' '-');
  96.  
  97.     for ($i=0$i<sizeof($id)$i++{
  98.         $aMessageList["$id[$i]"array();
  99.     }
  100.  
  101.     $aResponse sqimap_run_command_list($imap_stream"STORE $msgs_id ".$set_string."FLAGS ($flag)"$handle_errors$response$messageTRUE);
  102.  
  103.     // parse the fetch response
  104.     $parseFetchResults=parseFetch($aResponse,$aMessageList);
  105.  
  106.     // some broken IMAP servers do not return UID elements on UID STORE
  107.     // if this is the case, then we need to do a UID FETCH
  108.     $testkey=$id[0];
  109.     if (!isset($parseFetchResults[$testkey]['UID'])) {
  110.         $aResponse sqimap_run_command_list($imap_stream"FETCH $msgs_id (FLAGS)"$handle_errors$response$messageTRUE);
  111.         $parseFetchResults parseFetch($aResponse,$aMessageList);
  112.     }
  113.  
  114.     return ($parseFetchResults);
  115. }
  116.  
  117.  
  118. /**
  119.  * Sort the message list and crunch to be as small as possible
  120.  * (overflow could happen, so make it small if possible)
  121.  * @param array $aUid array with uid's
  122.  * @return string $s message set string
  123.  */
  124. function sqimap_message_list_squisher($aUid{
  125.     if!is_array$aUid ) ) {
  126.         return $aUid;
  127.     }
  128.     sort($aUidSORT_NUMERIC);
  129.  
  130.     if (count($aUid)) {
  131.         $s '';
  132.         for ($i=0,$iCnt=count($aUid);$i<$iCnt;++$i{
  133.             $iStart $aUid[$i];
  134.             $iEnd $iStart;
  135.             while ($i<($iCnt-1&& $aUid[$i+1== $iEnd +1{
  136.                 $iEnd $aUid[$i+1];
  137.                 ++$i;
  138.             }
  139.             if ($s{
  140.                 $s .= ',';
  141.             }
  142.             $s .= $iStart;
  143.             if ($iStart != $iEnd{
  144.                 $s .= ':' $iEnd;
  145.             }
  146.         }
  147.     }
  148.     return $s;
  149. }
  150.  
  151.  
  152. /**
  153.  * Retrieves an array with a sorted uid list. Sorting is done on the imap server
  154.  * @link http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-17.txt
  155.  * @param resource $imap_stream IMAP socket connection
  156.  * @param string $sSortField Field to sort on
  157.  * @param bool $reverse Reverse order search
  158.  * @return array $id sorted uid list
  159.  */
  160. function sqimap_get_sort_order($imap_stream$sSortField$reverse$search='ALL'{
  161.     global  $default_charset;
  162.  
  163.     if ($sSortField{
  164.         if ($reverse{
  165.             $sSortField 'REVERSE '.$sSortField;
  166.         }
  167.         $query "SORT ($sSortField".strtoupper($default_charset)." $search";
  168.         // FIX ME sqimap_run_command should return the parsed data accessible by $aDATA['SORT']
  169.         // use sqimap_run_command_list in case of unsollicited responses. If we don't we could loose the SORT response
  170.         $aData sqimap_run_command_list ($imap_stream$queryfalse$response$messageTRUE);
  171.         /* fallback to default charset */
  172.         if ($response == 'NO'{
  173.             if (strpos($message,'BADCHARSET'!== false ||
  174.                 strpos($message,'character'!== false{
  175.                 sqm_trigger_imap_error('SQM_IMAP_BADCHARSET',$query$response$message);
  176.                 $query "SORT ($sSortFieldUS-ASCII $search";
  177.                 $aData sqimap_run_command_list ($imap_stream$querytrue$response$messageTRUE);
  178.             else {
  179.                 sqm_trigger_imap_error('SQM_IMAP_ERROR',$query$response$message);
  180.             }
  181.         else if ($response == 'BAD'{
  182.             sqm_trigger_imap_error('SQM_IMAP_NO_SORT',$query$response$message);
  183.         }
  184.     }
  185.  
  186.     if ($response == 'OK'{
  187.         return parseUidList($aData,'SORT');
  188.     else {
  189.         return false;
  190.     }
  191. }
  192.  
  193.  
  194. /**
  195.  * Parses a UID list returned on a SORT or SEARCH request
  196.  * @param array $aData imap response (retrieved from sqimap_run_command_list)
  197.  * @param string $sCommand issued imap command (SEARCH or SORT)
  198.  * @return array $aUid uid list
  199.  */
  200. function parseUidList($aData,$sCommand{
  201.     $aUid array();
  202.     if (isset($aData&& count($aData)) {
  203.         for ($i=0,$iCnt=count($aData);$i<$iCnt;++$i{
  204.             for ($j=0,$jCnt=count($aData[$i]);$j<$jCnt;++$j{
  205.                 if (preg_match("/^\* $sCommand (.+)$/"$aData[$i][$j]$aMatch)) {
  206.                     $aUid += preg_split("/ /"trim($aMatch[1]));
  207.                 }
  208.             }
  209.         }
  210.     }
  211.     return array_unique($aUid);
  212. }
  213.  
  214. /**
  215.  * Retrieves an array with a sorted uid list. Sorting is done by SquirrelMail
  216.  *
  217.  * @param resource $imap_stream IMAP socket connection
  218.  * @param string $sSortField Field to sort on
  219.  * @param bool $reverse Reverse order search
  220.  * @param array $aUid limit the search to the provided array with uid's default sqimap_get_small_headers uses 1:*
  221.  * @return array $aUid sorted uid list
  222.  */
  223. function get_squirrel_sort($imap_stream$sSortField$reverse false$aUid NULL{
  224.     if ($sSortField != 'RFC822.SIZE' && $sSortField != 'INTERNALDATE'{
  225.         $msgs sqimap_get_small_header_list($imap_stream$aUid,
  226.                                       array($sSortField)array());
  227.     else {
  228.         $msgs sqimap_get_small_header_list($imap_stream$aUid,
  229.                                       array()array($sSortField));
  230.     }
  231.     $aUid array();
  232.     $walk false;
  233.     switch ($sSortField{
  234.       // natcasesort section
  235.       case 'FROM':
  236.       case 'TO':
  237.       case 'CC':
  238.         if(!$walk{
  239.             array_walk($msgscreate_function('&$v,&$k,$f',
  240.                 '$v[$f] = (isset($v[$f])) ? $v[$f] : "";
  241.                  $addr = reset(parseRFC822Address($v[$f],1));
  242.                  $sPersonal = (isset($addr[SQM_ADDR_PERSONAL]) && $addr[SQM_ADDR_PERSONAL]) ?
  243.                    $addr[SQM_ADDR_PERSONAL] : "";
  244.                  $sEmail = ($addr[SQM_ADDR_HOST]) ?
  245.                       $addr[SQM_ADDR_MAILBOX] . "@".$addr[SQM_ADDR_HOST] :
  246.                       $addr[SQM_ADDR_HOST];
  247.                  $v[$f] = ($sPersonal) ? decodeHeader($sPersonal):$sEmail;'),$sSortField);
  248.             $walk true;
  249.         }
  250.         // nobreak
  251.       case 'SUBJECT':
  252.         if(!$walk{
  253.             array_walk($msgscreate_function('&$v,&$k,$f',
  254.                 '$v[$f] = (isset($v[$f])) ? $v[$f] : "";
  255.                  $v[$f] = strtolower(decodeHeader(trim($v[$f])));
  256.                  $v[$f] = (preg_match("/^(vedr|sv|re|aw|\[\w\]):\s*(.*)$/si", $v[$f], $matches)) ?
  257.                                     $matches[2] : $v[$f];'),$sSortField);
  258.             $walk true;
  259.         }
  260.         foreach ($msgs as $item{
  261.             $aUid[$item['UID']] $item[$sSortField];
  262.         }
  263.         natcasesort($aUid);
  264.         $aUid array_keys($aUid);
  265.         if ($reverse{
  266.              $aUid array_reverse($aUid);
  267.         }
  268.         break;
  269.         //  \natcasesort section
  270.       // sort_numeric section
  271.       case 'DATE':
  272.       case 'INTERNALDATE':
  273.         if(!$walk{
  274.             array_walk($msgscreate_function('&$v,$k,$f',
  275.                 '$v[$f] = (isset($v[$f])) ? $v[$f] : "";
  276.                  $v[$f] = getTimeStamp(explode(" ",$v[$f]));'),$sSortField);
  277.             $walk true;
  278.         }
  279.         // nobreak;
  280.       case 'RFC822.SIZE':
  281.         if(!$walk{
  282.             // redefine $sSortField to maintain the same namespace between
  283.             // server-side sorting and SquirrelMail sorting
  284.             $sSortField 'SIZE';
  285.         }
  286.         foreach ($msgs as $item{
  287.             $aUid[$item['UID']] (isset($item[$sSortField])) $item[$sSortField0;
  288.         }
  289.         if ($reverse{
  290.             arsort($aUid,SORT_NUMERIC);
  291.         else {
  292.             asort($aUidSORT_NUMERIC);
  293.         }
  294.         $aUid array_keys($aUid);
  295.         break;
  296.         // \sort_numeric section
  297.       case 'UID':
  298.         $aUid array_reverse($msgs);
  299.         break;
  300.     }
  301.     return $aUid;
  302. }
  303.  
  304. /**
  305.  * Returns an array with each element as a string representing one
  306.  * message-thread as returned by the IMAP server.
  307.  * @param resource $imap_stream IMAP socket connection
  308.  * @param string $search optional search string
  309.  * @return array 
  310.  * @link http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-13.txt
  311.  */
  312. function get_thread_sort($imap_stream$search='ALL'{
  313.     global $sort_by_ref$default_charset;
  314.  
  315.     if ($sort_by_ref == 1{
  316.         $sort_type 'REFERENCES';
  317.     else {
  318.         $sort_type 'ORDEREDSUBJECT';
  319.     }
  320.     $query "THREAD $sort_type ".strtoupper($default_charset)." $search";
  321.  
  322.     // TODO use sqimap_run_command_list as we do in get_server_sort()
  323.     $sRead sqimap_run_command ($imap_stream$queryfalse$response$messageTRUE);
  324.  
  325.     /* fallback to default charset */
  326.     if ($response == 'NO'{
  327.         if (strpos($message,'BADCHARSET'!== false ||
  328.             strpos($message,'character'!== false{
  329.             sqm_trigger_imap_error('SQM_IMAP_BADCHARSET',$query$response$message);
  330.             $query "THREAD $sort_type US-ASCII $search";
  331.             $sRead sqimap_run_command ($imap_stream$querytrue$response$messageTRUE);
  332.         else {
  333.             sqm_trigger_imap_error('SQM_IMAP_ERROR',$query$response$message);
  334.         }
  335.     elseif ($response == 'BAD'{
  336.         sqm_trigger_imap_error('SQM_IMAP_NO_THREAD',$query$response$message);
  337.     }
  338.     if (isset($sRead[0])) {
  339.         for ($i=0,$iCnt=count($sRead);$i<$iCnt;++$i{
  340.             if (preg_match("/^\* THREAD (.+)$/"$sRead[$i]$aMatch)) {
  341.                 $sThreadResponse trim($aMatch[1]);
  342.                 break;
  343.             }
  344.         }
  345.     else {
  346.         $sThreadResponse "";
  347.     }
  348.     unset($sRead);
  349.  
  350.     if ($response !== 'OK'{
  351.         return false;
  352.     }
  353.  
  354.     /* Example response
  355.      *  S: * THREAD (2)(3 6 (4 23)(44 7 96))
  356.      * -- 2
  357.      *
  358.      * -- 3
  359.      *    \-- 6
  360.      *        |-- 4
  361.      *        |   \-- 23
  362.      *        |
  363.      *        \-- 44
  364.      *             \-- 7
  365.      *                 \-- 96
  366.      */
  367. /*
  368.  * Notes for future work:
  369.  * indent_array should contain: indent_level, parent and flags,
  370.  * sibling nodes ..
  371.  * To achieve that we  need to define the following flags:
  372.  * 0: hasnochildren
  373.  * 1: haschildren
  374.  * 2: is first
  375.  * 4: is last
  376.  * a node has sibling nodes if it's not the last node
  377.  * a node has no sibling nodes if it's the last node
  378.  * By using binary comparations we can store the flag in one var
  379.  *
  380.  * example:
  381.  * -1      par = 0, level = 0, flag = 1 + 2 + 4 = 7 (haschildren,   isfirst, islast)
  382.  *  \-2    par = 1, level = 1, flag = 0 + 2     = 2 (hasnochildren, isfirst)
  383.  *  |-3    par = 1, level = 1, flag = 1 + 4     = 5 (haschildren,   islast)
  384.  *   \-4   par = 3, level = 2, flag = 1 + 2 + 4 = 7 (haschildren,   isfirst, islast)
  385.  *     \-5 par = 4, level = 3, flag = 0 + 2 + 4 = 6 (hasnochildren, isfirst, islast)
  386.  */
  387.  
  388.     $j 0;
  389.     $k 0;
  390.     $l 0;
  391.     $aUidThread array();
  392.     $aIndent array();
  393.     $aUidSubThread array();
  394.     $aDepthStack array();
  395.     $sUid '';
  396.  
  397.     if ($sThreadResponse{
  398.         for ($i=0,$iCnt strlen($sThreadResponse);$i<$iCnt;++$i{
  399.             $cChar $sThreadResponse{$i};
  400.             switch ($cChar{
  401.                 case '('// new sub thread
  402.                     // correction for a subthread of a thread with no parents in thread
  403.                     if (!count($aUidSubThread&& $j 0{
  404.                        --$l;
  405.                     }
  406.                     $aDepthStack[$j$l;
  407.                     ++$j;
  408.                     break;
  409.                 case ')'// close sub thread
  410.                     if($sUid !== ''{
  411.                         $aUidSubThread[$sUid;
  412.                         $aIndent[$sUid$j $l 1;
  413.                         ++$l;
  414.                         $sUid '';
  415.                     }
  416.                     --$j;
  417.                     if ($j === 0{
  418.                         // show message that starts the thread first.
  419.                         $aUidSubThread array_reverse($aUidSubThread);
  420.                         // do not use array_merge because it's extremely slow and is causing timeouts
  421.                         foreach ($aUidSubThread as $iUid{
  422.                             $aUidThread[$iUid;
  423.                         }
  424.                         $aUidSubThread array();
  425.                         $l 0;
  426.                         $aDepthStack array();
  427.                     else {
  428.                         $l $aDepthStack[$j];
  429.                     }
  430.                     break;
  431.                 case ' '// new child
  432.                     if ($sUid !== ''{
  433.                         $aUidSubThread[$sUid;
  434.                         $aIndent[$sUid$j $l 1;
  435.                         ++$l;
  436.                         $sUid '';
  437.                     }
  438.                     break;
  439.                 default// part of UID
  440.                     $sUid .= $cChar;
  441.                     break;
  442.             }
  443.         }
  444.     }
  445.     unset($sThreadResponse);
  446.     // show newest threads first
  447.     $aUidThread array_reverse($aUidThread);
  448.     return array($aUidThread,$aIndent);
  449. }
  450.  
  451.  
  452. function elapsedTime($start{
  453.     $stop gettimeofday();
  454.     $timepassed =  1000000 ($stop['sec'$start['sec']$stop['usec'$start['usec'];
  455.     return $timepassed;
  456. }
  457.  
  458.  
  459. /**
  460.  * Normalise the different Priority headers into a uniform value,
  461.  * namely that of the X-Priority header (1, 3, 5). Supports:
  462.  * Prioirty, X-Priority, Importance.
  463.  * X-MS-Mail-Priority is not parsed because it always coincides
  464.  * with one of the other headers.
  465.  *
  466.  * FIXME: DUPLICATE CODE ALERT:
  467.  * NOTE: this is actually a duplicate from the function in
  468.  * class/mime/Rfc822Header.php.
  469.  * @todo obsolate function or use it instead of code block in parseFetch()
  470.  */
  471. function parsePriority($sValue{
  472.     $aValue split('/\w/',trim($sValue));
  473.     $value strtolower(array_shift($aValue));
  474.     if is_numeric($value) ) {
  475.         return $value;
  476.     }
  477.     if $value == 'urgent' || $value == 'high' {
  478.         return 1;
  479.     elseif $value == 'non-urgent' || $value == 'low' {
  480.         return 5;
  481.     }
  482.     return 3;
  483. }
  484.  
  485. /**
  486.  * Parses a string in an imap response. String starts with " or { which means it
  487.  * can handle double quoted strings and literal strings