alecpl
2009-01-08 7a229b9e33f7955db3cd6725d357f01735293216
program/include/rcube_imap.php
@@ -61,11 +61,12 @@
  var $msg_headers = array();
  var $skip_deleted = FALSE;
  var $search_set = NULL;
  var $search_subject = '';
  var $search_string = '';
  var $search_charset = '';
  var $search_sort_field = '';
  var $debug_level = 1;
  var $error_code = 0;
  var $options = array('imap' => 'check');
  /**
@@ -90,7 +91,7 @@
   * @return boolean  TRUE on success, FALSE on failure
   * @access public
   */
  function connect($host, $user, $pass, $port=143, $use_ssl=null, $auth_type=null)
  function connect($host, $user, $pass, $port=143, $use_ssl=null)
    {
    global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
    
@@ -107,7 +108,7 @@
    $ICL_PORT = $port;
    $IMAP_USE_INTERNAL_DATE = false;
    $this->conn = iil_Connect($host, $user, $pass, array('imap' => $auth_type ? $auth_type : 'check'));
    $this->conn = iil_Connect($host, $user, $pass, $this->options);
    $this->host = $host;
    $this->user = $user;
    $this->pass = $pass;
@@ -172,6 +173,13 @@
      iil_C_Select($this->conn, $this->mailbox);
    }
  /**
   * Set options to be used in iil_Connect()
   */
  function set_options($opt)
  {
    $this->options = array_merge($this->options, (array)$opt);
  }
  /**
   * Set a root folder for the IMAP connection.
@@ -188,6 +196,7 @@
      $root = substr($root, 0, -1);
    $this->root_dir = $root;
    $this->options['rootdir'] = $root;
    
    if (empty($this->delimiter))
      $this->get_hierarchy_delimiter();
@@ -281,21 +290,22 @@
  /**
   * Save a set of message ids for future message listing methods
   *
   * @param  array  List of IMAP fields to search in
   * @param  string Search string
   * @param  array  List of message ids or NULL if empty
   * @param  string  IMAP Search query
   * @param  array   List of message ids or NULL if empty
   * @param  string  Charset of search string
   * @param  string  Sorting field
   */
  function set_search_set($subject, $str=null, $msgs=null, $charset=null, $sorted=null)
  function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null)
    {
    if (is_array($subject) && $str == null && $msgs == null)
      list($subject, $str, $msgs, $charset, $sorted) = $subject;
    if (is_array($str) && $msgs == null)
      list($str, $msgs, $charset, $sort_field) = $str;
    if ($msgs != null && !is_array($msgs))
      $msgs = split(',', $msgs);
      
    $this->search_subject = $subject;
    $this->search_string = $str;
    $this->search_set = (array)$msgs;
    $this->search_set = $msgs;
    $this->search_charset = $charset;
    $this->search_sort_field = $sort_field;
    }
@@ -305,7 +315,7 @@
   */
  function get_search_set()
    {
    return array($this->search_subject, $this->search_string, $this->search_set, $this->search_charset);
    return array($this->search_string, $this->search_set, $this->search_charset, $this->search_sort_field);
    }
@@ -544,7 +554,7 @@
    // use saved message set
    if ($this->search_string && $mailbox == $this->mailbox)
      return $this->_list_header_set($mailbox, $this->search_set, $page, $sort_field, $sort_order);
      return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order);
    $this->_set_sort_order($sort_field, $sort_order);
@@ -591,18 +601,13 @@
      // fetch reuested headers from server
      $a_msg_headers = array();
      $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
      if ($this->sort_order == 'DESC' && $headers_sorted) {
        //since the sort order is not used in the iil_c_sort function we have to do it here
        $a_msg_headers = array_reverse($a_msg_headers);
      }
      // delete cached messages with a higher index than $max+1
      // Changed $max to $max+1 to fix this bug : #1484295
      $this->clear_message_cache($cache_key, $max + 1);
      // kick child process to sync cache
      // ...
      }
    // return empty array if no messages found
@@ -626,62 +631,91 @@
    }
  /**
   * Public method for listing a specific set of headers
   * convert mailbox name with root dir first
   * Private method for listing a set of message headers (search results)
   *
   * @param   string   Mailbox/folder name
   * @param   array    List of message ids to list
   * @param   int      Current page to list
   * @param   string   Header field to sort by
   * @param   string   Sort order [ASC|DESC]
   * @return  array    Indexed array with message header objects
   * @access  public
   */
  function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
    {
    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
    return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);
    }
  /**
   * Private method for listing a set of message headers
   *
   * @access  private
   * @see     rcube_imap::list_header_set()
   */
  function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
  function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL)
    {
    if (!strlen($mailbox) || empty($msgs))
    if (!strlen($mailbox) || empty($this->search_set))
      return array();
    // also accept a comma-separated list of message ids
    if (is_array ($msgs)) {
      $max = count ($msgs);
      $msgs = join (',', $msgs);
    } else {
      $max = count(split(',', $msgs));
    }
    $msgs = $this->search_set;
    $a_msg_headers = array();
    $start_msg = ($this->list_page-1) * $this->page_size;
    $this->_set_sort_order($sort_field, $sort_order);
    $start_msg = ($this->list_page-1) * $this->page_size;
    // sorted messages, so we can first slice array and then fetch only wanted headers
    if ($this->get_capability('sort')) // SORT searching result
      {
      // reset search set if sorting field has been changed
      if ($this->sort_field && $this->search_sort_field != $this->sort_field)
        {
        $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
        }
    // fetch reuested headers from server
    $a_msg_headers = array();
    $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, NULL);
      // return empty array if no messages found
      if (empty($msgs))
        return array();
    // return empty array if no messages found
    if (!is_array($a_msg_headers) || empty($a_msg_headers))
      return array();
      if ($sort_order == 'DESC')
        $msgs = array_reverse($msgs);
    // if not already sorted
    $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
      // get messages uids for one page
      $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size));
    // only return the requested part of the set
    return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
      // fetch headers
      $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL);
      $sorter = new rcube_header_sorter();
      $sorter->set_sequence_numbers($msgs);
      $sorter->sort_headers($a_msg_headers);
      return array_values($a_msg_headers);
      }
    else { // SEARCH searching result, need sorting
      $cnt = count($msgs);
      if ($cnt > 300 && $cnt > $this->page_size) { // experimantal value for best result
        // use memory less expensive (and quick) method for big result set
   $a_index = $this->message_index($mailbox, $this->sort_field, $this->sort_order);
        // get messages uids for one page...
        $msgs = array_slice(array_keys($a_index), $start_msg, min($cnt-$start_msg, $this->page_size));
   // ...and fetch headers
        $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
        // return empty array if no messages found
        if (!is_array($a_msg_headers) || empty($a_msg_headers))
          return array();
        $sorter = new rcube_header_sorter();
        $sorter->set_sequence_numbers($msgs);
        $sorter->sort_headers($a_msg_headers);
        return array_values($a_msg_headers);
        }
      else {
        // for small result set we can fetch all messages headers
        $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
        // return empty array if no messages found
        if (!is_array($a_msg_headers) || empty($a_msg_headers))
          return array();
        // if not already sorted
        $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
        // only return the requested part of the set
        return array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size));
        }
      }
    }
@@ -769,7 +803,7 @@
    
  
  /**
   * Return sorted array of message UIDs
   * Return sorted array of message IDs (not UIDs)
   *
   * @param string Mailbox to get index from
   * @param string Sort column
@@ -786,11 +820,29 @@
    // we have a saved search result. get index from there
    if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox)
    {
      $this->cache[$key] = $a_msg_headers = array();
      $this->_fetch_headers($mailbox, join(',', $this->search_set), $a_msg_headers, NULL);
      $this->cache[$key] = array();
      if ($this->get_capability('sort'))
        {
        if ($this->sort_field && $this->search_sort_field != $this->sort_field)
          $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
      foreach (iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order) as $i => $msg)
        $this->cache[$key][] = $msg->uid;
   if ($this->sort_order == 'DESC')
          $this->cache[$key] = array_reverse($this->search_set);
   else
     $this->cache[$key] = $this->search_set;
        }
      else
        {
   $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field);
        if ($this->sort_order=="ASC")
          asort($a_index);
        else if ($this->sort_order=="DESC")
          arsort($a_index);
        $this->cache[$key] = array_keys($a_index);
   }
    }
    // have stored it in RAM
@@ -805,34 +857,28 @@
    if ($cache_status>0)
      {
      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
      return array_values($a_index);
      return array_keys($a_index);
      }
    // fetch complete message index
    $msg_count = $this->_messagecount($mailbox);
    if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
    if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '')))
      {
      if ($this->sort_order == 'DESC')
        $a_index = array_reverse($a_index);
      $this->cache[$key] = $a_index;
      }
    else
      {
      $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
      $a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
      if ($this->sort_order=="ASC")
        asort($a_index);
      else if ($this->sort_order=="DESC")
        arsort($a_index);
        
      $i = 0;
      $this->cache[$key] = array();
      foreach ($a_index as $index => $value)
        $this->cache[$key][$i++] = $a_uids[$index];
      $this->cache[$key] = array_keys($a_index);
      }
    return $this->cache[$key];
@@ -892,36 +938,50 @@
   * Invoke search request to IMAP server
   *
   * @param  string  mailbox name to search in
   * @param  string  search criteria (ALL, TO, FROM, SUBJECT, etc)
   * @param  string  search string
   * @param  string  search string charset
   * @param  string  header field to sort by
   * @return array   search results as list of message ids
   * @access public
   */
  function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
  function search($mbox_name='', $str=NULL, $charset=NULL, $sort_field=NULL)
    {
    if (!$str)
      return false;
    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
    $search = '';
    // have an array of criterias => create search string
    if (is_array($criteria))
      $search .= 'OR';
    $criteria = (array) $criteria;
    foreach($criteria as $idx => $crit)
      if ($str)
        $search .= sprintf(" (%s {%d}\r\n%s)", $crit, strlen($str), $str);
      else
        $search .= '('. $crit .')';
    $results = $this->_search_index($mailbox, (!empty($charset) ? "CHARSET $charset " : '') . $search);
    $results = $this->_search_index($mailbox, $str, $charset, $sort_field);
    // try search with ISO charset (should be supported by server)
    if (empty($results) && !empty($charset) && $charset!='ISO-8859-1')
      $results = $this->search($mbox_name, $criteria, rcube_charset_convert($str, $charset, 'ISO-8859-1'), 'ISO-8859-1');
    // only if UTF-8 search is not supported
    if (empty($results) && !is_array($results) && !empty($charset) && $charset!='ISO-8859-1')
      {
   // convert strings to ISO-8859-1
        if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE))
     {
     $last = 0; $res = '';
     foreach($matches[1] as $m)
       {
       $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
       $string = substr($str, $string_offset - 1, $m[0]);
       $string = rcube_charset_convert($string, $charset, 'ISO-8859-1');
       $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string);
       $last = $m[0] + $string_offset - 1;
       }
       if ($last < strlen($str))
         $res .= substr($str, $last, strlen($str)-$last);
     }
   else // strings for conversion not found
     $res = $str;
   $results = $this->search($mbox_name, $res, 'ISO-8859-1', $sort_field);
      }
    $this->set_search_set($criteria, $str, $results, $charset);
    $this->set_search_set($str, $results, $charset, $sort_field);
    return $results;
    }
    }
  /**
@@ -931,9 +991,15 @@
   * @access private
   * @see rcube_imap::search()
   */
  function _search_index($mailbox, $criteria='ALL')
  function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL)
    {
    $a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
    if ($sort_field && $this->get_capability('sort'))
      {
      $charset = $charset ? $charset : $this->default_charset;
      $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, FALSE, $charset);
      }
    else
      $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria);
    // clean message list (there might be some empty entries)
    if (is_array($a_messages))
@@ -954,8 +1020,8 @@
   */
  function refresh_search()
    {
    if (!empty($this->search_subject) && !empty($this->search_string))
      $this->search_set = $this->search('', $this->search_subject, $this->search_string, $this->search_charset);
    if (!empty($this->search_string))
      $this->search_set = $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
      
    return $this->get_search_set();
    }
@@ -981,9 +1047,10 @@
   * @param int     Message ID
   * @param string  Mailbox to read from 
   * @param boolean True if $id is the message UID
   * @param boolean True if we need also BODYSTRUCTURE in headers
   * @return object Message headers representation
   */
  function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
  function get_headers($id, $mbox_name=NULL, $is_uid=TRUE, $bodystr=FALSE)
    {
    $mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
    $uid = $is_uid ? $id : $this->_id2uid($id);
@@ -992,7 +1059,7 @@
    if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
      return $headers;
    $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid);
    $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr);
    // write headers cache
    if ($headers)
@@ -1012,9 +1079,10 @@
   * an object structure similar to the one generated by PEAR::Mail_mimeDecode
   *
   * @param int Message UID to fetch
   * @param string Message BODYSTRUCTURE string (optional)
   * @return object rcube_message_part Message part tree or False on failure
   */
  function &get_structure($uid)
  function &get_structure($uid, $structure_str='')
    {
    $cache_key = $this->mailbox.'.msg';
    $headers = &$this->get_cached_message($cache_key, $uid, true);
@@ -1029,7 +1097,8 @@
      return FALSE;
    }
    $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
    if (!$structure_str)
      $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
    $structure = iml_GetRawStructureArray($structure_str);
    $struct = false;
@@ -1064,7 +1133,7 @@
   *
   * @access private
   */
  function &_structure_part($part, $count=0, $parent='')
  function &_structure_part($part, $count=0, $parent='', $raw_headers=null)
    {
    $struct = new rcube_message_part;
    $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
@@ -1084,11 +1153,25 @@
          
      $struct->mimetype = 'multipart/'.$struct->ctype_secondary;
      // build parts list for headers pre-fetching
      for ($i=0, $count=0; $i<count($part); $i++)
        if (is_array($part[$i]) && count($part[$i]) > 3)
     // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
     if (strtolower($part[$i][0]) == 'message' ||
       (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL'))) {
       $part_headers[] = $struct->mime_id ? $struct->mime_id.'.'.$i+1 : $i+1;
       }
      // pre-fetch headers of all parts (in one command for better performance)
      if ($part_headers)
        $part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, $part_headers);
      $struct->parts = array();
      for ($i=0, $count=0; $i<count($part); $i++)
        if (is_array($part[$i]) && count($part[$i]) > 3)
          $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id);
          $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
      $part_headers[$struct->mime_id ? $struck->mime_id.'.'.$i+1 : $i+1]);
      return $struct;
      }
    
@@ -1153,8 +1236,9 @@
    
    // fetch message headers if message/rfc822 or named part (could contain Content-Location header)
    if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
      $part_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id);
      $struct->headers = $this->_parse_headers($part_headers) + $struct->headers;
      if (empty($raw_headers))
        $raw_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id);
      $struct->headers = $this->_parse_headers($raw_headers) + $struct->headers;
    }
    if ($struct->ctype_primary=='message') {
@@ -1307,6 +1391,8 @@
      
    // TODO: Add caching for message parts
    if (!$part) $part = 'TEXT';
    if ($print)
      {
      $mode = $o_part->encoding == 'base64' ? 3 : ($o_part->encoding == 'quoted-printable' ? 1 : 2);
@@ -1378,10 +1464,7 @@
    if (!($msg_id = $this->_uid2id($uid)))
      return FALSE;
    $body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
    $body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
    return $body;
    return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id);
    }
@@ -1412,8 +1495,6 @@
    if (!($msg_id = $this->_uid2id($uid)))
      return FALSE;
    print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
    flush();
    iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
    }
@@ -2091,8 +2172,7 @@
      {
      $this->db->query(
        "UPDATE ".get_table_name('cache')."
         SET    created=".$this->db->now().",
                data=?
         SET    created=". $this->db->now().", data=?
         WHERE  user_id=?
         AND    cache_key=?",
        $data,
@@ -2245,7 +2325,7 @@
  /**
   * @access private
   */  
  function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
  function get_message_cache_index($key, $force=FALSE, $sort_field='idx', $sort_order='ASC')
    {
    static $sa_message_index = array();
    
@@ -2262,7 +2342,7 @@
       FROM ".get_table_name('messages')."
       WHERE  user_id=?
       AND    cache_key=?
       ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
       ORDER BY ".$this->db->quote_identifier($sort_field)." ".$sort_order,
      $_SESSION['user_id'],
      $key);
@@ -2279,7 +2359,7 @@
    {
    if (empty($key) || !is_object($headers) || empty($headers->uid))
        return;
    // add to internal (fast) cache
    $this->cache['__single_msg'][$headers->uid] = $headers;
    $this->cache['__single_msg'][$headers->uid]->structure = $struct;
@@ -2442,12 +2522,13 @@
  /**
   * Decode a mime-encoded string to internal charset
   *
   * @param string  Header value
   * @param string  Fallback charset if none specified
   * @param string $input    Header value
   * @param string $fallback Fallback charset if none specified
   *
   * @return string Decoded string
   * @static
   */
  function decode_mime_string($input, $fallback=null)
  public static function decode_mime_string($input, $fallback=null)
    {
    // Initialize variable
    $out = '';
@@ -2656,32 +2737,43 @@
        $folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7'));
      }
    // sort folders and place defaults on the top
    asort($folders, SORT_LOCALE_STRING);
    ksort($a_defaults);
    $folders = array_merge($a_defaults, array_keys($folders));
    // finally we must rebuild the list to move 
    // subfolders of default folders to their place...
    // ...also do this for the rest of folders because
    // asort() is not properly sorting case sensitive names
    // set the type of folder name variable (#1485527)
    while (list($key, $folder) = each($folders)) {
      // set the type of folder name variable (#1485527)
      $a_out[] = (string) $folder;
      unset($folders[$key]);
      foreach ($folders as $idx => $f) {
   if (strpos($f, $folder.$delimiter) === 0) {
         $a_out[] = (string) $f;
     unset($folders[$idx]);
     }
        }
      reset($folders);
      $this->_rsort($folder, $delimiter, $folders, $a_out);
      }
    return $a_out;
    }
  /**
   * @access private
   */
  function _rsort($folder, $delimiter, &$list, &$out)
    {
      while (list($key, $name) = each($list)) {
   if (strpos($name, $folder.$delimiter) === 0) {
     // set the type of folder name variable (#1485527)
         $out[] = (string) $name;
     unset($list[$key]);
     $this->_rsort($name, $delimiter, $list, $out);
     }
        }
      reset($list);
    }
  /**
   * @access private
   */