thomascube
2010-03-17 f52c936f4d451a5d3a87d2501aa5a1701cdafde5
Merged devel-threads branch (r3066:3364) back into trunk

37 files modified
3 files added
3454 ■■■■■ changed files
CHANGELOG 4 ●●●● patch | view | raw | blame | history
THREADS 43 ●●●●● patch | view | raw | blame | history
bin/msgexport.sh 4 ●●●● patch | view | raw | blame | history
config/main.inc.php.dist 13 ●●●●● patch | view | raw | blame | history
index.php 2 ●●●●● patch | view | raw | blame | history
program/include/html.php 5 ●●●●● patch | view | raw | blame | history
program/include/rcmail.php 1 ●●●● patch | view | raw | blame | history
program/include/rcube_imap.php 646 ●●●● patch | view | raw | blame | history
program/include/rcube_shared.inc 20 ●●●●● patch | view | raw | blame | history
program/include/rcube_user.php 7 ●●●● patch | view | raw | blame | history
program/js/app.js 1035 ●●●●● patch | view | raw | blame | history
program/js/common.js 2 ●●● patch | view | raw | blame | history
program/js/list.js 274 ●●●●● patch | view | raw | blame | history
program/lib/imap.inc 441 ●●●● patch | view | raw | blame | history
program/localization/de_CH/labels.inc 24 ●●●●● patch | view | raw | blame | history
program/localization/en_US/labels.inc 25 ●●●●● patch | view | raw | blame | history
program/localization/pl_PL/labels.inc 2 ●●● patch | view | raw | blame | history
program/steps/mail/check_recent.inc 33 ●●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 358 ●●●●● patch | view | raw | blame | history
program/steps/mail/list.inc 21 ●●●● patch | view | raw | blame | history
program/steps/mail/mark.inc 9 ●●●●● patch | view | raw | blame | history
program/steps/mail/move_del.inc 10 ●●●● patch | view | raw | blame | history
program/steps/mail/search.inc 4 ●●●● patch | view | raw | blame | history
program/steps/mail/show.inc 4 ●●●● patch | view | raw | blame | history
program/steps/mail/viewsource.inc 2 ●●● patch | view | raw | blame | history
program/steps/settings/func.inc 24 ●●●● patch | view | raw | blame | history
program/steps/settings/manage_folders.inc 84 ●●●●● patch | view | raw | blame | history
program/steps/settings/save_prefs.inc 2 ●●● patch | view | raw | blame | history
skins/default/common.css 10 ●●●● patch | view | raw | blame | history
skins/default/functions.js 87 ●●●●● patch | view | raw | blame | history
skins/default/ie6hacks.css 15 ●●●● patch | view | raw | blame | history
skins/default/iehacks.css 24 ●●●● patch | view | raw | blame | history
skins/default/images/icons/columnpicker.gif patch | view | raw | blame | history
skins/default/images/icons/unread_children.png patch | view | raw | blame | history
skins/default/images/mail_footer.png patch | view | raw | blame | history
skins/default/images/messageactions.gif patch | view | raw | blame | history
skins/default/images/messageactions.png patch | view | raw | blame | history
skins/default/includes/messagemenu.html 2 ●●● patch | view | raw | blame | history
skins/default/mail.css 158 ●●●● patch | view | raw | blame | history
skins/default/templates/mail.html 59 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,10 @@
CHANGELOG RoundCube Webmail
===========================
- Threaded message listing now available
- Added sorting by ARRIVAL and CC
- Message list columns configurable by the user
- Removed 'index_sort' option, now we're using empty 'message_sort_col' for this
- virtuser_query: support other identity data (#1486148)
- Options virtuser_* replaced with virtuser_* plugins
- Plugin API: Implemented 'email2user' and 'user2email' hooks
THREADS
New file
@@ -0,0 +1,43 @@
CHANGES IN RELATION TO ORIGINAL PATCH
    - don't add nested messages into selection on collapse if parent message
      is in selection
    - some changes in messages highlighting
    - re-written all changes in rcube_imap.php
    - temporary removed threads caching (see TODO)
    - use depth=0 for roots
    - thread expand state is not stored anywhere
    - removed imap_thread_algorithm option, we're using the best algorithm
      supported by server and implement REFS sorting in Roundcube
    - use underlined subject for root with unread children (icon is still supported)
    - on deleting messages the whole list isn't refreshed
    - added 'expand unread' button
TODO (must have):
    - threads caching
    - updating threaded message list on message delete
    - don't reload messages list on check_recent
TODO (other):
    - performance: fetching all messages for list in "expand all" state only,
      if "expand all" is disabled we should fetch only root messages and fetch
      children on-demand (on expand button click),
      Notice: this is not so simple, because we need to fetch children
              to set "unread_children", but we can fetch only flags instead of
              all headers for each child
    - button in #listcontrols to mark all messages in current thread (with selected
      root or child message),
    + thread tree icons
    + thread css: message row height, thread/status icon alignment
      (change size of all list icons to 14x14)
    - remove 'indexsort' label from localization files
TODO (by the way):
    - use jQuery.inArray instead of find_in_array() (common.js)
    + use only one function (js) to generate messages list
KNOWN ISSUES:
    - on new message (check_recent) the whole list is reloaded
    + table header replacement doesn't work on IE
    - css issues on IE6
    + css issues on IE7
bin/msgexport.sh
@@ -89,8 +89,8 @@
        $from = current($IMAP->decode_address_list($headers->from, 1, false));
        
        fwrite($out, sprintf("From %s %s UID %d\n", $from['mailto'], $headers->date, $headers->uid));
        fwrite($out, iil_C_FetchPartHeader($IMAP->conn, $IMAP->mailbox, $i, null));
        fwrite($out, iil_C_HandlePartBody($IMAP->conn, $IMAP->mailbox, $i, null, 1));
        fwrite($out, iil_C_FetchPartHeader($IMAP->conn, $mbox, $i, null));
        fwrite($out, iil_C_HandlePartBody($IMAP->conn, $mbox, $i, null, 1));
        fwrite($out, "\n\n\n");
        
        progress_update($i, $count);
config/main.inc.php.dist
@@ -232,10 +232,11 @@
// USER INTERFACE
// ----------------------------------
// default sort col
$rcmail_config['message_sort_col'] = 'date';
// default messages sort column. Use empty value for default server's sorting,
// or 'arrival', 'date', 'subject', 'from', 'to', 'size', 'cc'
$rcmail_config['message_sort_col'] = '';
// default sort order
// default messages sort order
$rcmail_config['message_sort_order'] = 'DESC';
// These cols are shown in the message list. Available cols are:
@@ -461,8 +462,10 @@
// If true, after message delete/move, the next message will be displayed
$rcmail_config['display_next'] = false;
// If true, messages list will be sorted by message index instead of message date
$rcmail_config['index_sort'] = true;
// 0 - Do not expand threads
// 1 - Expand all threads automatically
// 2 - Expand only threads with unread messages
$rcmail_config['autoexpand_threads'] = 0;
// When replying place cursor above original message (top posting)
$rcmail_config['top_posting'] = false;
index.php
@@ -226,6 +226,8 @@
    'delete-folder' => 'manage_folders.inc',
    'subscribe'     => 'manage_folders.inc',
    'unsubscribe'   => 'manage_folders.inc',
    'enable-threading'  => 'manage_folders.inc',
    'disable-threading' => 'manage_folders.inc',
    'add-identity'  => 'edit_identity.inc',
  )
);
program/include/html.php
@@ -5,7 +5,7 @@
 | program/include/html.php                                              |
 |                                                                       |
 | This file is part of the RoundCube Webmail client                     |
 | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 |
 | Copyright (C) 2005-2010, RoundCube Dev, - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 | PURPOSE:                                                              |
@@ -34,8 +34,7 @@
    public static $lc_tags = true;
    public static $common_attrib = array('id','class','style','title','align');
    public static $containers = array('iframe','div','span','p','h1','h2','h3',
    'form','textarea','table','tr','th','td','style','script');
    public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script');
    /**
     * Constructor
program/include/rcmail.php
@@ -410,7 +410,6 @@
    $this->imap = new rcube_imap($this->db);
    $this->imap->debug_level = $this->config->get('debug_level');
    $this->imap->skip_deleted = $this->config->get('skip_deleted');
    $this->imap->index_sort = $this->config->get('index_sort', true);
    // enable caching of imap data
    if ($this->config->get('enable_caching')) {
program/include/rcube_imap.php
@@ -5,7 +5,7 @@
 | program/include/rcube_imap.php                                        |
 |                                                                       |
 | This file is part of the RoundCube Webmail client                     |
 | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland                 |
 | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 | PURPOSE:                                                              |
@@ -41,35 +41,40 @@
 */
class rcube_imap
{
  var $db;
  var $conn;
  var $root_dir = '';
  var $mailbox = 'INBOX';
  var $list_page = 1;
  var $page_size = 10;
  var $sort_field = 'date';
  var $sort_order = 'DESC';
  var $index_sort = true;
  var $delimiter = NULL;
  var $caching_enabled = FALSE;
  var $default_charset = 'ISO-8859-1';
  var $struct_charset = NULL;
  var $default_folders = array('INBOX');
  var $fetch_add_headers = '';
  var $cache = array();
  var $cache_keys = array();
  var $cache_changes = array();
  var $uid_id_map = array();
  var $msg_headers = array();
  var $skip_deleted = FALSE;
  var $search_set = NULL;
  var $search_string = '';
  var $search_charset = '';
  var $search_sort_field = '';
  var $debug_level = 1;
  var $error_code = 0;
  var $options = array('auth_method' => 'check');
  public $debug_level = 1;
  public $error_code = 0;
  public $skip_deleted = false;
  public $root_dir = '';
  public $page_size = 10;
  public $list_page = 1;
  public $delimiter = NULL;
  public $threading = false;
  public $fetch_add_headers = '';
  public $conn;
  private $db;
  private $root_ns = '';
  private $mailbox = 'INBOX';
  private $sort_field = '';
  private $sort_order = 'DESC';
  private $caching_enabled = false;
  private $default_charset = 'ISO-8859-1';
  private $struct_charset = NULL;
  private $default_folders = array('INBOX');
  private $default_folders_lc = array('inbox');
  private $icache = array();
  private $cache = array();
  private $cache_keys = array();
  private $cache_changes = array();
  private $uid_id_map = array();
  private $msg_headers = array();
  public  $search_set = NULL;
  public  $search_string = '';
  private $search_charset = '';
  private $search_sort_field = '';
  private $search_threads = false;
  private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
  private $options = array('auth_method' => 'check');
  private $host, $user, $pass, $port, $ssl;
@@ -299,17 +304,18 @@
   * @param  string  Charset of search string
   * @param  string  Sorting field
   */
  function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null)
  function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false)
    {
    if (is_array($str) && $msgs == null)
      list($str, $msgs, $charset, $sort_field) = $str;
      list($str, $msgs, $charset, $sort_field, $threads) = $str;
    if ($msgs != null && !is_array($msgs))
      $msgs = explode(',', $msgs);
    $this->search_string = $str;
    $this->search_set = $msgs;
    $this->search_charset = $charset;
    $this->search_sort_field = $sort_field;
    $this->search_threads = $threads;
    }
@@ -319,7 +325,12 @@
   */
  function get_search_set()
    {
    return array($this->search_string, $this->search_set, $this->search_charset, $this->search_sort_field);
    return array($this->search_string,
    $this->search_set,
    $this->search_charset,
    $this->search_sort_field,
    $this->search_threads,
    );
    }
@@ -345,6 +356,30 @@
  function get_capability($cap)
    {
    return iil_C_GetCapability($this->conn, strtoupper($cap));
    }
  /**
   * Sets threading flag to the best supported THREAD algorithm
   *
   * @param  boolean  TRUE to enable and FALSE
   * @return string   Algorithm or false if THREAD is not supported
   * @access public
   */
  function set_threading($enable=false)
    {
    $this->threading = false;
    if ($enable) {
      if ($this->get_capability('THREAD=REFS'))
        $this->threading = 'REFS';
      else if ($this->get_capability('THREAD=REFERENCES'))
        $this->threading = 'REFERENCES';
      else if ($this->get_capability('THREAD=ORDEREDSUBJECT'))
        $this->threading = 'ORDEREDSUBJECT';
      }
    return $this->threading;
    }
@@ -480,11 +515,15 @@
    if (empty($mailbox))
      $mailbox = $this->mailbox;
    // count search set
    if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force)
      return count((array)$this->search_set);
    // count search set
    if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) {
      if ($this->search_threads)
        return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']);
      else
        return count((array)$this->search_set);
      }
    $a_mailbox_cache = $this->get_cache('messagecount');
    
    // return cached value
@@ -494,8 +533,11 @@
    if (!is_array($a_mailbox_cache[$mailbox]))
      $a_mailbox_cache[$mailbox] = array();
    if ($mode == 'THREADS')
      $count = $this->_threadcount($mailbox);
    // RECENT count is fetched a bit different
    if ($mode == 'RECENT')
    else if ($mode == 'RECENT')
       $count = iil_C_CheckForRecent($this->conn, $mailbox);
    // use SEARCH for message counting
@@ -534,6 +576,24 @@
  /**
   * Private method for getting nr of threads
   *
   * @access  private
   * @see     rcube_imap::messagecount()
   */
  private function _threadcount($mailbox)
    {
    if (!empty($this->icache['threads']))
      return count($this->icache['threads']['tree']);
    list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox);
//    $this->update_thread_cache($mailbox, $thread_tree, $msg_depth, $has_children);
    return count($thread_tree);
    }
  /**
   * Public method for listing headers
   * convert mailbox name with root dir first
   *
@@ -567,6 +627,9 @@
    if ($this->search_string && $mailbox == $this->mailbox)
      return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice);
    if ($this->threading)
      return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $recursive, $slice);
    $this->_set_sort_order($sort_field, $sort_order);
    $page = $page ? $page : $this->list_page;
@@ -593,8 +656,8 @@
    // retrieve headers from IMAP
    $a_msg_headers = array();
    // use message index sort for sorting by Date (for better performance)
    if ($this->index_sort && $this->sort_field == 'date')
    // use message index sort as default sorting (for better performance)
    if (!$this->sort_field)
      {
        if ($this->skip_deleted) {
          // @TODO: this could be cached
@@ -671,6 +734,140 @@
  /**
   * Private method for listing message headers using threads
   *
   * @access  private
   * @see     rcube_imap::list_headers
   */
  private function _list_thread_headers($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE, $slice=0)
    {
    $this->_set_sort_order($sort_field, $sort_order);
    $page = $page ? $page : $this->list_page;
//    $cache_key = $mailbox.'.msg';
//    $cache_status = $this->check_cache_status($mailbox, $cache_key);
    // get all threads (default sort order)
    list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox);
    if (empty($thread_tree))
      return array();
    $msg_index = $this->_sort_threads($mailbox, $thread_tree);
    return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children,
      $msg_index, $page, $slice);
    }
  /**
   * Private method for fetching threads data
   *
   * @param   string   Mailbox/folder name
   * @return  array    Array with thread data
   * @access  private
   */
  private function _fetch_threads($mailbox)
    {
    if (empty($this->icache['threads'])) {
      // get all threads
      list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn,
        $mailbox, $this->threading, $this->skip_deleted ? 'UNDELETED' : '');
      // add to internal (fast) cache
      $this->icache['threads'] = array();
      $this->icache['threads']['tree'] = $thread_tree;
      $this->icache['threads']['depth'] = $msg_depth;
      $this->icache['threads']['has_children'] = $has_children;
      }
    return array(
      $this->icache['threads']['tree'],
      $this->icache['threads']['depth'],
      $this->icache['threads']['has_children'],
      );
    }
  /**
   * Private method for fetching threaded messages headers
   *
   * @access  private
   */
  private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0)
    {
    $cache_key = $mailbox.'.msg';
    // now get IDs for current page
    list($begin, $end) = $this->_get_message_range(count($msg_index), $page);
    $msg_index = array_slice($msg_index, $begin, $end-$begin);
    if ($slice)
      $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice);
    if ($this->sort_order == 'DESC')
      $msg_index = array_reverse($msg_index);
    // flatten threads array
    // @TODO: fetch children only in expanded mode
    $all_ids = array();
    foreach($msg_index as $root) {
      $all_ids[] = $root;
      if (!empty($thread_tree[$root]))
        $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root]));
      }
    // fetch reqested headers from server
    $this->_fetch_headers($mailbox, $all_ids, $a_msg_headers, $cache_key);
    // return empty array if no messages found
    if (!is_array($a_msg_headers) || empty($a_msg_headers))
      return array();
    // use this class for message sorting
    $sorter = new rcube_header_sorter();
    $sorter->set_sequence_numbers($all_ids);
    $sorter->sort_headers($a_msg_headers);
    // Set depth, has_children and unread_children fields in headers
    $this->_set_thread_flags($a_msg_headers, $msg_depth, $has_children);
    return array_values($a_msg_headers);
    }
  /**
   * Private method for setting threaded messages flags:
   * depth, has_children and unread_children
   *
   * @param  array   Reference to headers array indexed by message ID
   * @param  array   Array of messages depth indexed by message ID
   * @param  array   Array of messages children flags indexed by message ID
   * @return array   Message headers array indexed by message ID
   * @access private
   */
  private function _set_thread_flags(&$headers, $msg_depth, $msg_children)
    {
    $parents = array();
    foreach ($headers as $idx => $header) {
      $id = $header->id;
      $depth = $msg_depth[$id];
      $parents = array_slice($parents, 0, $depth);
      if (!empty($parents)) {
        $headers[$idx]->parent_uid = end($parents);
        if (!$header->seen)
          $headers[$parents[0]]->unread_children++;
        }
      array_push($parents, $header->uid);
      $headers[$idx]->depth = $depth;
      $headers[$idx]->has_children = $msg_children[$id];
      }
    }
  /**
   * Private method for listing a set of message headers (search results)
   *
   * @param   string   Mailbox/folder name
@@ -687,6 +884,14 @@
    if (!strlen($mailbox) || empty($this->search_set))
      return array();
    // use saved messages from searching
    if ($this->threading)
      return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice);
    // search set is threaded, we need a new one
    if ($this->search_threads)
      $this->search('', $this->search_string, $this->search_charset, $sort_field);
    $msgs = $this->search_set;
    $a_msg_headers = array();
    $page = $page ? $page : $this->list_page;
@@ -694,8 +899,8 @@
    $this->_set_sort_order($sort_field, $sort_order);
    // quickest method
    if ($this->index_sort && $this->search_sort_field == 'date' && $this->sort_field == 'date')
    // quickest method (default sorting)
    if (!$this->search_sort_field && !$this->sort_field)
      {
      if ($sort_order == 'DESC')
        $msgs = array_reverse($msgs);
@@ -716,8 +921,9 @@
      return array_values($a_msg_headers);
      }
    // sorted messages, so we can first slice array and then fetch only wanted headers
    if ($this->get_capability('sort') && (!$this->index_sort || $this->sort_field != 'date')) // SORT searching result
    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)
@@ -745,10 +951,10 @@
      return array_values($a_msg_headers);
      }
    else { // SEARCH searching result, need sorting
    else { // SEARCH result, need sorting
      $cnt = count($msgs);
      // 300: experimantal value for best result
      if (($cnt > 300 && $cnt > $this->page_size) || ($this->index_sort && $this->sort_field == 'date')) {
      if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) {
        // use memory less expensive (and quick) method for big result set
        $a_index = $this->message_index('', $this->sort_field, $this->sort_order);
        // get messages uids for one page...
@@ -787,6 +993,40 @@
        return $a_msg_headers;
        }
      }
    }
  /**
   * Private method for listing a set of threaded message headers (search results)
   *
   * @param   string   Mailbox/folder name
   * @param   int      Current page to list
   * @param   string   Header field to sort by
   * @param   string   Sort order [ASC|DESC]
   * @param   boolean  Number of slice items to extract from result array
   * @return  array    Indexed array with message header objects
   * @access  private
   * @see     rcube_imap::list_header_set()
   */
  private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
    {
    // update search_set if previous data was fetched with disabled threading
    if (!$this->search_threads)
      $this->search('', $this->search_string, $this->search_charset, $sort_field);
    $thread_tree = $this->search_set['tree'];
    $msg_depth = $this->search_set['depth'];
    $has_children = $this->search_set['children'];
    $a_msg_headers = array();
    $page = $page ? $page : $this->list_page;
    $start_msg = ($page-1) * $this->page_size;
    $this->_set_sort_order($sort_field, $sort_order);
    $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth));
    return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0);
    }
@@ -901,18 +1141,22 @@
   */
  function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
    {
    if ($this->threading)
      return $this->thread_index($mbox_name, $sort_field, $sort_order);
    $this->_set_sort_order($sort_field, $sort_order);
    $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
    $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi";
    // we have a saved search result, get index from there
    if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox)
    if (!isset($this->cache[$key]) && $this->search_string
      && !$this->search_threads && $mailbox == $this->mailbox)
    {
      $this->cache[$key] = array();
      
      // use message index sort for sorting by Date
      if ($this->index_sort && $this->sort_field == 'date')
      // use message index sort as default sorting
      if (!$this->sort_field)
      {
        $msgs = $this->search_set;
@@ -937,7 +1181,8 @@
      }
      else
      {
        $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field, $this->skip_deleted);
        $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox,
      join(',', $this->search_set), $this->sort_field, $this->skip_deleted);
        if ($this->sort_order=="ASC")
          asort($a_index);
@@ -963,8 +1208,8 @@
      return array_keys($a_index);
      }
    // use message index sort for sorting by Date
    if ($this->index_sort && $this->sort_field == 'date')
    // use message index sort as default sorting
    if (!$this->sort_field)
      {
      if ($this->skip_deleted) {
        $a_index = $this->_search_index($mailbox, 'ALL');
@@ -1002,9 +1247,90 @@
  /**
   * Return sorted array of threaded message IDs (not UIDs)
   *
   * @param string Mailbox to get index from
   * @param string Sort column
   * @param string Sort order [ASC, DESC]
   * @return array Indexed array with message IDs
   */
  function thread_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
    {
    $this->_set_sort_order($sort_field, $sort_order);
    $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
    $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.thi";
    // we have a saved search result, get index from there
    if (!isset($this->cache[$key]) && $this->search_string
      && $this->search_threads && $mailbox == $this->mailbox)
    {
      // use message IDs for better performance
      $ids = array_keys_recursive($this->search_set['tree']);
      $this->cache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids);
    }
    // have stored it in RAM
    if (isset($this->cache[$key]))
      return $this->cache[$key];
/*
    // check local cache
    $cache_key = $mailbox.'.msg';
    $cache_status = $this->check_cache_status($mailbox, $cache_key);
    // cache is OK
    if ($cache_status>0)
      {
      $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
      return array_keys($a_index);
      }
*/
    // get all threads (default sort order)
    list ($thread_tree) = $this->_fetch_threads($mailbox);
    $this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree);
    return $this->cache[$key];
    }
  /**
   * Return array of threaded messages (all, not only roots)
   *
   * @param string Mailbox to get index from
   * @param array  Threaded messages array (see _fetch_threads())
   * @param array  Message IDs if we know what we need (e.g. search result)
   *               for better performance
   * @return array Indexed array with message IDs
   *
   * @access private
   */
  function sync_header_index($mailbox)
  private function _flatten_threads($mailbox, $thread_tree, $ids=null)
    {
    if (empty($thread_tree))
      return array();
    $msg_index = $this->_sort_threads($mailbox, $thread_tree, $ids);
    if ($this->sort_order == 'DESC')
      $msg_index = array_reverse($msg_index);
    // flatten threads array
    $all_ids = array();
    foreach($msg_index as $root) {
      $all_ids[] = $root;
      if (!empty($thread_tree[$root]))
        $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root]));
      }
    return $all_ids;
    }
  /**
   * @access private
   */
  private function sync_header_index($mailbox)
    {
    $cache_key = $mailbox.'.msg';
    $cache_index = $this->get_message_cache_index($cache_key);
@@ -1102,7 +1428,7 @@
      $results = $this->search($mbox_name, $res, NULL, $sort_field);
    }
    $this->set_search_set($str, $results, $charset, $sort_field);
    $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading);
    return $results;
    }
@@ -1122,7 +1448,17 @@
    if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria))
      $criteria = 'UNDELETED '.$criteria;
    if ($sort_field && $this->get_capability('sort') && (!$this->index_sort || $sort_field != 'date')) {
    if ($this->threading) {
      list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn,
            $mailbox, $this->threading, $criteria, $charset);
      $a_messages = array(
        'tree'     => $thread_tree,
    'depth'    => $msg_depth,
    'children' => $has_children
        );
      }
    else 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);
      }
@@ -1135,7 +1471,7 @@
        $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria);
    
        // I didn't found that SEARCH always returns sorted IDs
        if ($this->index_sort && $this->sort_field == 'date')
        if (!$this->sort_field)
          sort($a_messages);
        }
      }
@@ -1149,6 +1485,90 @@
    
  
  /**
   * Sort thread
   *
   * @param string Mailbox name
   * @param  array Unsorted thread tree (iil_C_Thread() result)
   * @param  array Message IDs if we know what we need (e.g. search result)
   * @return array Sorted roots IDs
   * @access private
   */
  private function _sort_threads($mailbox, $thread_tree, $ids=NULL)
    {
    // THREAD=ORDEREDSUBJECT:     sorting by sent date of root message
    // THREAD=REFERENCES:     sorting by sent date of root message
    // THREAD=REFS:         sorting by the most recent date in each thread
    // default sorting
    if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) {
        return array_keys($thread_tree);
      }
    // here we'll implement REFS sorting, for performance reason
    else { // ($sort_field == 'date' && $this->threading != 'REFS')
      // use SORT command
      if ($this->get_capability('sort')) {
        $a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field,
        !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : ''));
        }
      else {
        // fetch specified headers for all messages and sort them
        $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, !empty($ids) ? $ids : "1:*",
        $this->sort_field, $this->skip_deleted);
        asort($a_index); // ASC
    $a_index = array_values($a_index);
        }
    return $this->_sort_thread_refs($thread_tree, $a_index);
      }
    }
  /**
   * THREAD=REFS sorting implementation
   *
   * @param  array   Thread tree array (message identifiers as keys)
   * @param  array   Array of sorted message identifiers
   * @return array   Array of sorted roots messages
   * @access private
   */
  private function _sort_thread_refs($tree, $index)
    {
    if (empty($tree))
      return array();
    $index = array_combine(array_values($index), $index);
    // assign roots
    foreach ($tree as $idx => $val) {
      $index[$idx] = $idx;
      if (!empty($val)) {
        $idx_arr = array_keys_recursive($tree[$idx]);
        foreach ($idx_arr as $subidx)
          $index[$subidx] = $idx;
        }
      }
    $index = array_values($index);
    // create sorted array of roots
    $msg_index = array();
    if ($this->sort_order != 'DESC') {
      foreach ($index as $idx)
        if (!isset($msg_index[$idx]))
          $msg_index[$idx] = $idx;
      $msg_index = array_values($msg_index);
      }
    else {
      for ($x=count($index)-1; $x>=0; $x--)
        if (!isset($msg_index[$index[$x]]))
          $msg_index[$index[$x]] = $index[$x];
      $msg_index = array_reverse($msg_index);
      }
    return $msg_index;
    }
  /**
   * Refresh saved search set
   *
   * @return array Current search set
@@ -1156,7 +1576,8 @@
  function refresh_search()
    {
    if (!empty($this->search_string))
      $this->search_set = $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
      $this->search_set = $this->search('', $this->search_string, $this->search_charset,
            $this->search_sort_field, $this->search_threads);
      
    return $this->get_search_set();
    }
@@ -1760,7 +2181,7 @@
   * @return boolean True on success, False on error
   */
  function move_message($uids, $to_mbox, $from_mbox='')
    {
  {
    $fbox = $from_mbox;
    $tbox = $to_mbox;
    $to_mbox = $this->mod_mailbox($to_mbox);
@@ -1802,25 +2223,35 @@
    }
    // moving failed
    else if ($config->get('delete_always', false) && $tbox == $config->get('trash_mbox')) {
      return $this->delete_message($a_uids, $fbox);
      $moved = $this->delete_message($a_uids, $fbox);
    }
    // remove message ids from search set
    if ($moved && $this->search_set && $from_mbox == $this->mailbox) {
      foreach ($a_uids as $uid)
        $a_mids[] = $this->_uid2id($uid, $from_mbox);
      $this->search_set = array_diff($this->search_set, $a_mids);
    }
    if ($moved) {
      // unset threads internal cache
      unset($this->icache['threads']);
    // update cached message headers
    $cache_key = $from_mbox.'.msg';
    if ($moved && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) {
      // clear cache from the lowest index on
      $this->clear_message_cache($cache_key, $start_index);
      }
      // remove message ids from search set
      if ($this->search_set && $from_mbox == $this->mailbox) {
        // threads are too complicated to just remove messages from set
        if ($this->search_threads)
          $this->refresh_search();
        else {
          foreach ($a_uids as $uid)
            $a_mids[] = $this->_uid2id($uid, $from_mbox);
          $this->search_set = array_diff($this->search_set, $a_mids);
          }
        }
      // update cached message headers
      $cache_key = $from_mbox.'.msg';
      if ($start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) {
        // clear cache from the lowest index on
        $this->clear_message_cache($cache_key, $start_index);
       }
    }
    return $moved;
    }
  }
  /**
@@ -1831,7 +2262,7 @@
   * @return boolean True on success, False on error
   */
  function delete_message($uids, $mbox_name='')
    {
  {
    $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
    // convert the list of uids to array
@@ -1843,31 +2274,38 @@
    $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_uids));
    // send expunge command in order to have the deleted message
    // really deleted from the mailbox
    if ($deleted)
      {
    if ($deleted) {
      // send expunge command in order to have the deleted message
      // really deleted from the mailbox
      $this->_expunge($mailbox, FALSE, $a_uids);
      $this->_clear_messagecount($mailbox);
      unset($this->uid_id_map[$mailbox]);
      }
    // remove message ids from search set
    if ($deleted && $this->search_set && $mailbox == $this->mailbox) {
      foreach ($a_uids as $uid)
        $a_mids[] = $this->_uid2id($uid, $mailbox);
      $this->search_set = array_diff($this->search_set, $a_mids);
      // unset threads internal cache
      unset($this->icache['threads']);
      // remove message ids from search set
      if ($this->search_set && $mailbox == $this->mailbox) {
        // threads are too complicated to just remove messages from set
        if ($this->search_threads)
          $this->refresh_search();
        else {
          foreach ($a_uids as $uid)
            $a_mids[] = $this->_uid2id($uid, $mailbox);
          $this->search_set = array_diff($this->search_set, $a_mids);
          }
        }
      // remove deleted messages from cache
      $cache_key = $mailbox.'.msg';
      if ($start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) {
        // clear cache from the lowest index on
        $this->clear_message_cache($cache_key, $start_index);
      }
    }
    // remove deleted messages from cache
    $cache_key = $mailbox.'.msg';
    if ($deleted && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) {
      // clear cache from the lowest index on
      $this->clear_message_cache($cache_key, $start_index);
      }
    return $deleted;
    }
  }
  /**
@@ -2422,13 +2860,11 @@
  private function get_message_cache($key, $from, $to, $sort_field, $sort_order)
    {
    $cache_key = "$key:$from:$to:$sort_field:$sort_order";
    $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
    
    $config = rcmail::get_instance()->config;
    // use idx sort for sorting by Date with index_sort=true or for unknown field
    if (($sort_field == 'date' && $this->index_sort)
      || !in_array($sort_field, $db_header_fields)) {
    // use idx sort as default sorting
    if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) {
      $sort_field = 'idx';
      }
    
@@ -2465,9 +2901,9 @@
   */
  private function &get_cached_message($key, $uid)
    {
    $internal_key = '__single_msg';
    $internal_key = 'message';
    
    if ($this->caching_enabled && !isset($this->cache[$internal_key][$uid]))
    if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid]))
      {
      $sql_result = $this->db->query(
        "SELECT idx, headers, structure
@@ -2482,13 +2918,13 @@
      if ($sql_arr = $this->db->fetch_assoc($sql_result))
        {
    $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx'];
        $this->cache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers']));
        if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure']))
          $this->cache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure']));
        $this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers']));
        if (is_object($this->icache[$internal_key][$uid]) && !empty($sql_arr['structure']))
          $this->icache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure']));
        }
      }
    return $this->cache[$internal_key][$uid];
    return $this->icache[$internal_key][$uid];
    }
  /**
@@ -2505,8 +2941,8 @@
    if (!empty($sa_message_index[$key]) && !$force)
      return $sa_message_index[$key];
    // use idx sort for sorting by Date with index_sort=true
    if ($sort_field == 'date' && $this->index_sort)
    // use idx sort as default
    if (!$sort_field || !in_array($sort_field, $this->db_header_fields))
      $sort_field = 'idx';
    
    $sa_message_index[$key] = array();
@@ -2534,8 +2970,8 @@
        return;
    // add to internal (fast) cache
    $this->cache['__single_msg'][$headers->uid] = clone $headers;
    $this->cache['__single_msg'][$headers->uid]->structure = $struct;
    $this->icache['message'][$headers->uid] = clone $headers;
    $this->icache['message'][$headers->uid]->structure = $struct;
    // no further caching
    if (!$this->caching_enabled)
program/include/rcube_shared.inc
@@ -608,6 +608,26 @@
/**
 * Get all keys from array (recursive)
 *
 * @param array Input array
 * @return array
 */
function array_keys_recursive($array)
{
  $keys = array();
  if (!empty($array))
    foreach ($array as $key => $child) {
      $keys[] = $key;
      if ($children = array_keys_recursive($child))
        $keys = array_merge($keys, $children);
    }
  return $keys;
}
/**
 * mbstring replacement functions
 */
program/include/rcube_user.php
@@ -111,19 +111,22 @@
      if (!isset($old_prefs[$key]) && ($value == $config->get($key)))
        unset($save_prefs[$key]);
    }
    $save_prefs = serialize($save_prefs);
    $this->db->query(
      "UPDATE ".get_table_name('users')."
       SET    preferences=?,
              language=?
       WHERE  user_id=?",
      serialize($save_prefs),
      $save_prefs,
      $_SESSION['language'],
      $this->ID);
    $this->language = $_SESSION['language'];
    if ($this->db->affected_rows()) {
      $config->set_user_prefs($a_user_prefs);
      $this->data['preferences'] = $save_prefs;
      return true;
    }
program/js/app.js
@@ -3,11 +3,12 @@
 | RoundCube Webmail Client Script                                       |
 |                                                                       |
 | This file is part of the RoundCube Webmail client                     |
 | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 |
 | Copyright (C) 2005-2010, RoundCube Dev, - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
 |          Aleksander 'A.L.E.C' Machniak <alec@alec.pl>                 |
 |          Charles McNulty <charles@charlesmcnulty.com>                 |
 +-----------------------------------------------------------------------+
 | Requires: jquery.js, common.js, list.js                               |
@@ -160,9 +161,13 @@
    switch (this.task)
      {
      case 'mail':
        // enable mail commands
        this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
        if (this.gui_objects.messagelist)
          {
          this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {multiselect:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time});
          this.message_list = new rcube_list_widget(this.gui_objects.messagelist,
            {multiselect:true, multiexpand:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time});
          this.message_list.row_init = function(o){ p.init_message_row(o); };
          this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
          this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
@@ -170,22 +175,22 @@
          this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
          this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
          this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); });
          this.message_list.addEventListener('expandcollapse', function(e){ p.msglist_expand(e); });
          document.onmouseup = function(e){ return p.doc_mouse_up(e); };
          this.set_message_coltypes(this.env.coltypes);
          this.message_list.init();
          this.enable_command('toggle_status', 'toggle_flag', true);
          this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', true);
          
          if (this.gui_objects.mailcontframe)
            this.gui_objects.mailcontframe.onmousedown = function(e){ return p.click_on_list(e); };
          else
            this.message_list.focus();
          }
          
        if (this.env.coltypes)
          this.set_message_coltypes(this.env.coltypes);
        // enable mail commands
        this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
          // load messages
          if (this.env.messagecount)
            this.command('list');
          }
        if (this.env.search_text != null && document.getElementById('quicksearchbox') != null)
          document.getElementById('quicksearchbox').value = this.env.search_text;
@@ -243,8 +248,10 @@
          this.init_messageform();
          }
        if (this.env.messagecount)
        if (this.env.messagecount) {
          this.enable_command('select-all', 'select-none', 'expunge', true);
          this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading);
        }
        if (this.purge_mailbox_test())
          this.enable_command('purge', true);
@@ -333,7 +340,7 @@
          this.enable_command('save', 'delete', 'edit', true);
        }
        else if (this.env.action=='folders')
          this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true);
          this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', 'enable-threading', 'disable-threading', true);
        if (this.gui_objects.identitieslist)
          {
@@ -351,6 +358,7 @@
          this.sections_list.addEventListener('select', function(o){ p.section_select(o); });
          this.sections_list.init();
          this.sections_list.focus();
          this.sections_list.select_first();
        }
        else if (this.gui_objects.subscriptionlist)
          this.init_subscription_list();
@@ -403,106 +411,6 @@
    // start keep-alive interval
    this.start_keepalive();
  };
  // start interval for keep-alive/recent_check signal
  this.start_keepalive = function()
    {
    if (this.env.keep_alive && !this.env.framed && this.task=='mail' && this.gui_objects.mailboxlist)
      this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000);
    else if (this.env.keep_alive && !this.env.framed && this.task!='login')
      this._int = setInterval(function(){ ref.send_keep_alive(); }, this.env.keep_alive * 1000);
    }
  this.init_message_row = function(row)
  {
    var uid = row.uid;
    if (uid && this.env.messages[uid])
      {
      row.deleted = this.env.messages[uid].deleted ? true : false;
      row.unread = this.env.messages[uid].unread ? true : false;
      row.replied = this.env.messages[uid].replied ? true : false;
      row.flagged = this.env.messages[uid].flagged ? true : false;
      row.forwarded = this.env.messages[uid].forwarded ? true : false;
      }
    // set eventhandler to message icon
    if (row.icon = row.obj.getElementsByTagName('td')[0].getElementsByTagName('img')[0])
      {
      var p = this;
      row.icon.id = 'msgicn_'+row.uid;
      row.icon._row = row.obj;
      row.icon.onmousedown = function(e) { p.command('toggle_status', this); };
      }
    // global variable 'flagged_col' may be not defined yet
    if (!this.env.flagged_col && this.env.coltypes)
      {
      var found;
      if((found = find_in_array('flag', this.env.coltypes)) >= 0)
        this.set_env('flagged_col', found+1);
      }
    // set eventhandler to flag icon, if icon found
    if (this.env.flagged_col && (row.flagged_icon = row.obj.getElementsByTagName('td')[this.env.flagged_col].getElementsByTagName('img')[0]))
      {
      var p = this;
      row.flagged_icon.id = 'flaggedicn_'+row.uid;
      row.flagged_icon._row = row.obj;
      row.flagged_icon.onmousedown = function(e) { p.command('toggle_flag', this); };
      }
    this.triggerEvent('insertrow', { uid:uid, row:row });
  };
  // init message compose form: set focus and eventhandlers
  this.init_messageform = function()
    {
    if (!this.gui_objects.messageform)
      return false;
    //this.messageform = this.gui_objects.messageform;
    var input_from = $("[name='_from']");
    var input_to = $("[name='_to']");
    var input_subject = $("input[name='_subject']");
    var input_message = $("[name='_message']").get(0);
    var html_mode = $("input[name='_is_html']").val() == '1';
    // init live search events
    this.init_address_input_events(input_to);
    this.init_address_input_events($("[name='_cc']"));
    this.init_address_input_events($("[name='_bcc']"));
    if (!html_mode)
      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
    // add signature according to selected identity
    if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == ''
        && !html_mode) {  // if we have HTML editor, signature is added in callback
      this.change_identity(input_from[0]);
    }
    else if (!html_mode)
      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
    if (input_to.val() == '')
      input_to.focus();
    else if (input_subject.val() == '')
      input_subject.focus();
    else if (input_message && !html_mode)
      input_message.focus();
    // get summary of all field values
    this.compose_field_hash(true);
    // start the auto-save timer
    this.auto_save_start();
    };
  this.init_address_input_events = function(obj)
    {
    var handler = function(e){ return ref.ksearch_keypress(e,this); };
    obj.bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), handler);
    obj.attr('autocomplete', 'off');
    };
  /*********************************************************/
@@ -580,6 +488,12 @@
          parent.location.href = this.env.permaurl;
        break;
      case 'menu-open':
      case 'menu-save':
    this.triggerEvent(command, {props:props});
    return false;
        break;
      case 'open':
        var uid;
        if (uid = this.get_single_uid())
@@ -625,13 +539,8 @@
        else
          sort_order = 'ASC';
        // set table header class
        $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
        $('#rcm'+sort_col).addClass('sorted'+sort_order);
        // save new sort properties
        this.env.sort_col = sort_col;
        this.env.sort_order = sort_order;
        // set table header and update env
        this.set_list_sorting(sort_col, sort_order);
        // reload message list
        this.list_mailbox('', '', sort_col+'_'+sort_order);
@@ -756,7 +665,6 @@
          this.delete_identity();
        break;
      // mail task commands
      case 'move':
      case 'moveto':
@@ -849,6 +757,22 @@
      case 'select-none':
        this.message_list.clear_selection();
        break;
      case 'expand-all':
        this.env.autoexpand_threads = 1;
        this.message_list.expand_all();
        break;
      case 'expand-unread':
        this.env.autoexpand_threads = 2;
        this.message_list.collapse_all();
        this.expand_unread();
        break;
      case 'collapse-all':
        this.env.autoexpand_threads = 0;
        this.message_list.collapse_all();
        break;
      case 'nextmessage':
@@ -1116,7 +1040,15 @@
      case 'unsubscribe':
        this.unsubscribe_folder(props);
        break;
      case 'enable-threading':
        this.enable_threading(props);
        break;
      case 'disable-threading':
        this.disable_threading(props);
        break;
      case 'create-folder':
        this.create_folder(props);
        break;
@@ -1450,7 +1382,7 @@
    if (this.preview_timer)
      clearTimeout(this.preview_timer);
    var selected = list.selection.length==1;
    var selected = list.get_single_selection() != null;
    // Hide certain command buttons when Drafts folder is selected
    if (this.env.mailbox == this.env.drafts_mailbox)
@@ -1505,6 +1437,12 @@
      this.show_contentframe(false);
  };
  
  this.msglist_expand = function(row)
  {
    if (this.env.messages[row.uid])
      this.env.messages[row.uid].expanded = row.expanded;
  };
  this.check_droptarget = function(id)
  {
    if (this.task == 'mail')
@@ -1519,6 +1457,217 @@
  /*********************************************************/
  /*********     (message) list functionality      *********/
  /*********************************************************/
  this.init_message_row = function(row)
  {
    var self = this;
    var uid = row.uid;
    if (uid && this.env.messages[uid])
      $.extend(row, this.env.messages[uid]);
    // set eventhandler to message icon
    if (this.env.subject_col != null && (row.icon = document.getElementById('msgicn'+row.uid))) {
      row.icon._row = row.obj;
      row.icon.onmousedown = function(e) { self.command('toggle_status', this); };
    }
    // set eventhandler to flag icon, if icon found
    if (this.env.flagged_col != null && (row.flagged_icon = document.getElementById('flaggedicn'+row.uid))) {
      row.flagged_icon._row = row.obj;
      row.flagged_icon.onmousedown = function(e) { self.command('toggle_flag', this); };
    }
    var expando;
    if (!row.depth && row.has_children && (expando = document.getElementById('rcmexpando'+row.uid))) {
      expando.onmousedown = function(e) { return self.expand_message_row(e, uid); };
    }
    this.triggerEvent('insertrow', { uid:uid, row:row });
  };
  // create a table row in the message list
  this.add_message_row = function(uid, cols, flags, attop)
  {
    if (!this.gui_objects.messagelist || !this.message_list)
      return false;
    if (this.message_list.background)
      var tbody = this.message_list.background;
    else
      var tbody = this.gui_objects.messagelist.tBodies[0];
    var rows = this.message_list.rows;
    var rowcount = tbody.rows.length;
    var even = rowcount%2;
    if (!this.env.messages[uid])
      this.env.messages[uid] = {};
    // merge flags over local message object
    $.extend(this.env.messages[uid], {
      deleted: flags.deleted?1:0,
      replied: flags.replied?1:0,
      unread: flags.unread?1:0,
      forwarded: flags.forwarded?1:0,
      flagged: flags.flagged?1:0,
      has_children: flags.has_children?1:0,
      depth: flags.depth?flags.depth:0,
      unread_children: flags.unread_children,
      parent_uid: flags.parent_uid
    });
    var message = this.env.messages[uid];
    var css_class = 'message'
        + (even ? ' even' : ' odd')
        + (flags.unread ? ' unread' : '')
        + (flags.deleted ? ' deleted' : '')
        + (flags.flagged ? ' flagged' : '')
        + (flags.unread_children && !flags.unread ? ' unroot' : '')
        + (this.message_list.in_selection(uid) ? ' selected' : '');
    // for performance use DOM instead of jQuery here
    var row = document.createElement('tr');
    row.id = 'rcmrow'+uid;
    row.className = css_class;
    var icon = this.env.messageicon;
    if (!flags.unread && flags.unread_children > 0 && this.env.unreadchildrenicon)
      icon = this.env.unreadchildrenicon;
    else if (flags.deleted && this.env.deletedicon)
      icon = this.env.deletedicon;
    else if (flags.replied && this.env.repliedicon)
      {
      if (flags.forwarded && this.env.forwardedrepliedicon)
        icon = this.env.forwardedrepliedicon;
      else
        icon = this.env.repliedicon;
      }
    else if (flags.forwarded && this.env.forwardedicon)
      icon = this.env.forwardedicon;
    else if(flags.unread && this.env.unreadicon)
      icon = this.env.unreadicon;
    var tree = expando = '';
    if (this.env.threading)
      {
      // This assumes that div width is hardcoded to 15px,
      var width = message.depth * 15;
      if (message.depth) {
        if ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
            (!rows[message.parent_uid] || !rows[message.parent_uid].expanded)) {
          row.style.display = 'none';
          message.expanded = false;
        }
        else
          message.expanded = true;
      }
      else if (message.has_children) {
        if (typeof(message.expanded) == 'undefined' && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) {
          message.expanded = true;
        }
      }
      if (width)
        tree += '<span id="rcmtab' + uid + '" class="branch" style="width:' + width + 'px;">&nbsp;&nbsp;</span>';
      if (message.has_children && !message.depth)
        expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
      }
    tree += icon ? '<img id="msgicn'+uid+'" src="'+icon+'" alt="" class="msgicon" />' : '';
    // first col is always there
    var col = document.createElement('td');
    col.className = 'threads';
    col.innerHTML = expando;
    row.appendChild(col);
    // build subject link
    if (!bw.ie && cols.subject) {
      var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show';
      var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid';
      cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+uid+'"'+
        ' onclick="return rcube_event.cancel(event)">'+cols.subject+'</a>';
    }
    // add each submitted col
    for (var n = 0; n < this.env.coltypes.length; n++) {
      var c = this.env.coltypes[n];
      col = document.createElement('td');
      col.className = String(c).toLowerCase();
      var html;
      if (c=='flag') {
        if (flags.flagged && this.env.flaggedicon)
          html = '<img id="flaggedicn'+uid+'" src="'+this.env.flaggedicon+'" class="flagicon" alt="" />';
        else if(!flags.flagged && this.env.unflaggedicon)
          html = '<img id="flaggedicn'+uid+'" src="'+this.env.unflaggedicon+'" class="flagicon" alt="" />';
      }
      else if (c=='attachment')
        html = flags.attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;';
      else if (c=='subject')
        html = tree + cols[c];
      else
        html = cols[c];
      col.innerHTML = html;
      row.appendChild(col);
    }
    this.message_list.insert_row(row, attop);
    // remove 'old' row
    if (attop && this.env.pagesize && this.message_list.rowcount > this.env.pagesize) {
      var uid = this.message_list.get_last_row();
      this.message_list.remove_row(uid);
      this.message_list.clear_selection(uid);
    }
  };
  // messages list handling in background (for performance)
  this.offline_message_list = function(flag)
    {
      if (this.message_list)
          this.message_list.set_background_mode(flag);
    };
  this.set_list_sorting = function(sort_col, sort_order)
    {
    // set table header class
    $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
    if (sort_col)
      $('#rcm'+sort_col).addClass('sorted'+sort_order);
    this.env.sort_col = sort_col;
    this.env.sort_order = sort_order;
    }
  this.set_list_options = function(cols, sort_col, sort_order, threads)
    {
    var update, add_url = '';
    if (this.env.sort_col != sort_col || this.env.sort_order != sort_order) {
      update = 1;
      this.set_list_sorting(sort_col, sort_order);
      }
    if (this.env.threading != threads) {
      update = 1;
      add_url += '&_threads=' + threads;
      }
    if (cols.join() != this.env.coltypes.join()) {
      update = 1;
      add_url += '&_cols=' + cols.join(',');
      }
    if (update)
      this.list_mailbox('', '', sort_col+'_'+sort_order, add_url);
    }
  // when user doble-clicks on a row
  this.show_message = function(id, safe, preview)
@@ -1554,6 +1703,7 @@
      if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread)
        {
        this.set_message(id, 'unread', false);
        this.update_thread_root(id, 'read');
        if (this.env.unread_counts[this.env.mailbox])
          {
          this.env.unread_counts[this.env.mailbox] -= 1;
@@ -1621,23 +1771,25 @@
          + (this.env.mailbox ? '&_mbox='+urlencode(this.env.mailbox) : ''), true);
    }
  // list messages of a specific mailbox
  this.list_mailbox = function(mbox, page, sort)
  this.list_mailbox = function(mbox, page, sort, add_url)
    {
    var add_url = '';
    var url = '';
    var target = window;
    if (!mbox)
      mbox = this.env.mailbox;
    if (add_url)
      url += add_url;
    // add sort to url if set
    if (sort)
      add_url += '&_sort=' + sort;
      url += '&_sort=' + sort;
    // also send search request to get the right messages
    if (this.env.search_request)
      add_url += '&_search='+this.env.search_request;
      url += '&_search='+this.env.search_request;
    // set page=1 if changeing to another mailbox
    if (!page && this.env.mailbox != mbox)
@@ -1648,7 +1800,7 @@
      }
    if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
      add_url += '&_refresh=1';
      url += '&_refresh=1';
    // unselect selected messages
    this.last_selected = 0;
@@ -1661,21 +1813,21 @@
    // load message list remotely
    if (this.gui_objects.messagelist)
      {
      this.list_mailbox_remote(mbox, page, add_url);
      this.list_mailbox_remote(mbox, page, url);
      return;
      }
    
    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
      {
      target = window.frames[this.env.contentframe];
      add_url += '&_framed=1';
      url += '&_framed=1';
      }
    // load message list to target frame/window
    if (mbox)
      {
      this.set_busy(true, 'loading');
      target.location.href = this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+add_url;
      target.location.href = this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+url;
      }
    };
@@ -1691,53 +1843,177 @@
    this.http_request('list', url+add_url, true);
    };
  this.expunge_mailbox = function(mbox)
  // expand all threads with unread children
  this.expand_unread = function()
    {
    var lock = false;
    var add_url = '';
    var tbody = this.gui_objects.messagelist.tBodies[0];
    var new_row = tbody.firstChild;
    var r;
    
    // lock interface if it's the active mailbox
    if (mbox == this.env.mailbox)
       {
       lock = true;
       this.set_busy(true, 'loading');
       add_url = '&_reload=1';
       }
    // send request to server
    var url = '_mbox='+urlencode(mbox);
    this.http_post('expunge', url+add_url, lock);
    while (new_row) {
      if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid])
        && r.unread_children) {
    this.message_list.expand_all(r);
    var expando = document.getElementById('rcmexpando' + r.uid);
    if (expando)
      expando.className = 'expanded';
    this.set_unread_children(r.uid);
        }
      new_row = new_row.nextSibling;
      }
    return false;
    };
  this.purge_mailbox = function(mbox)
  // thread expanding/collapsing handler
  this.expand_message_row = function(e, uid)
    {
    var lock = false;
    var add_url = '';
    if (!confirm(this.get_label('purgefolderconfirm')))
      return false;
    // lock interface if it's the active mailbox
    if (mbox == this.env.mailbox)
       {
       lock = true;
       this.set_busy(true, 'loading');
       add_url = '&_reload=1';
       }
    var row = this.message_list.rows[uid];
    // send request to server
    var url = '_mbox='+urlencode(mbox);
    this.http_post('purge', url+add_url, lock);
    return true;
    // handle unread_children mark
    row.expanded = !row.expanded;
    this.set_unread_children(uid);
    row.expanded = !row.expanded;
    this.message_list.expand_row(e, uid);
    };
  // test if purge command is allowed
  this.purge_mailbox_test = function()
  // message list expanding
  this.expand_threads = function()
    {
    if (!this.env.threading || !this.env.autoexpand_threads || !this.message_list)
      return;
    switch (this.env.autoexpand_threads) {
      case 2: this.expand_unread(); break;
      case 1: this.message_list.expand_all(); break;
      }
    //  this.message_list.expand(null);
    }
  // update parent in a thread
  this.update_thread_root = function(uid, flag)
  {
    return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox
      || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))
      || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter))));
    if (!this.env.threading)
      return;
    var root = this.find_thread_root(uid);
    if (uid == root)
      return;
    var p = this.message_list.rows[root];
    if (flag == 'read' && p.unread_children) {
      p.unread_children--;
    } else if (flag == 'unread' && p.has_children) {
      // unread_children may be undefined
      p.unread_children = p.unread_children ? p.unread_children + 1 : 1;
    } else {
      return;
    }
    this.set_message_icon(root);
    this.set_unread_children(root);
  };
  // finds root message for specified thread
  this.find_thread_root = function(uid)
  {
    var r = this.message_list.rows[uid];
    if (r.parent_uid)
      return this.find_thread_root(r.parent_uid);
    else
      return uid;
  }
  // update thread indicators for all messages in a thread below the specified message
  // return number of removed/added root level messages
  this.update_thread = function (uid)
  {
    if (!this.env.threading)
      return 0;
    var rows = this.message_list.rows;
    var row = rows[uid]
    var depth = rows[uid].depth;
    var r, parent, count = 0;
    var roots = new Array();
    if (!row.depth) // root message: decrease roots count
      count--;
    else if (row.unread) {
      // update unread_children for thread root
      var parent = this.find_thread_root(uid);
      rows[parent].unread_children--;
      this.set_unread_children(parent);
      }
    parent = row.parent_uid;
    // childrens
    row = row.obj.nextSibling;
    while (row) {
      if (row.nodeType == 1 && (r = rows[row.uid])) {
    if (!r.depth || r.depth <= depth)
      break;
    r.depth--; // move left
    $('#rcmtab'+r.uid).width(r.depth * 15);
        if (!r.depth) { // a new root
      count++; // increase roots count
      r.parent_uid = 0;
      if (r.has_children) {
        // replace 'leaf' with 'collapsed'
        $('#rcmrow'+r.uid+' '+'.leaf:first')
              .attr('id', 'rcmexpando' + r.uid)
          .attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed'))
              .bind('mousedown', {uid:r.uid, p:this},
            function(e) { return e.data.p.expand_message_row(e, e.data.uid); });
        r.unread_children = 0;
        roots[roots.length] = r;
        }
      // show if it was hidden
      if (r.obj.style.display == 'none')
        $(r.obj).show();
      }
    else {
      if (r.depth == depth)
        r.parent_uid = parent;
      if (r.unread && roots.length) {
        roots[roots.length-1].unread_children++;
        }
      }
    }
    row = row.nextSibling;
      }
    // update unread_children for roots
    for (var i=0; i<roots.length; i++)
      this.set_unread_children(roots[i].uid);
    return count;
  };
  this.delete_excessive_thread_rows = function()
  {
    var rows = this.message_list.rows;
    var tbody = this.message_list.list.tBodies[0];
    var row = tbody.firstChild;
    var cnt = this.env.pagesize + 1;
    while (row) {
      if (row.nodeType == 1 && (r = rows[row.uid])) {
    if (!r.depth && cnt)
      cnt--;
        if (!cnt)
      this.message_list.remove_row(row.uid);
    }
    row = row.nextSibling;
      }
  }
  // set message icon
  this.set_message_icon = function(uid)
@@ -1747,8 +2023,10 @@
    if (!rows[uid])
      return false;
    if (rows[uid].deleted && this.env.deletedicon)
    if (!rows[uid].unread && rows[uid].unread_children && this.env.unreadchildrenicon) {
      icn_src = this.env.unreadchildrenicon;
    }
    else if (rows[uid].deleted && this.env.deletedicon)
      icn_src = this.env.deletedicon;
    else if (rows[uid].replied && this.env.repliedicon)
      {
@@ -1773,7 +2051,6 @@
      icn_src = this.env.flaggedicon;
    else if (!rows[uid].flagged && this.env.unflaggedicon)
      icn_src = this.env.unflaggedicon;
    if (rows[uid].flagged_icon && icn_src)
      rows[uid].flagged_icon.src = icn_src;
  }
@@ -1796,7 +2073,7 @@
    else if (flag == 'flagged')
      rows[uid].flagged = status;
    this.env.messages[uid] = rows[uid];
//    this.env.messages[uid] = rows[uid];
    }
  // set message row status, class and icon
@@ -1808,43 +2085,41 @@
    
    if (flag)
      this.set_message_status(uid, flag, status);
    var rowobj = $(rows[uid].obj);
    if (rows[uid].unread && rows[uid].classname.indexOf('unread')<0)
      {
      rows[uid].classname += ' unread';
    if (rows[uid].unread && !rowobj.hasClass('unread'))
      rowobj.addClass('unread');
      }
    else if (!rows[uid].unread && rows[uid].classname.indexOf('unread')>=0)
      {
      rows[uid].classname = rows[uid].classname.replace(/\s*unread/, '');
    else if (!rows[uid].unread && rowobj.hasClass('unread'))
      rowobj.removeClass('unread');
      }
    
    if (rows[uid].deleted && rows[uid].classname.indexOf('deleted')<0)
      {
      rows[uid].classname += ' deleted';
    if (rows[uid].deleted && !rowobj.hasClass('deleted'))
      rowobj.addClass('deleted');
      }
    else if (!rows[uid].deleted && rows[uid].classname.indexOf('deleted')>=0)
      {
      rows[uid].classname = rows[uid].classname.replace(/\s*deleted/, '');
    else if (!rows[uid].deleted && rowobj.hasClass('deleted'))
      rowobj.removeClass('deleted');
      }
    if (rows[uid].flagged && rows[uid].classname.indexOf('flagged')<0)
      {
      rows[uid].classname += ' flagged';
    if (rows[uid].flagged && !rowobj.hasClass('flagged'))
      rowobj.addClass('flagged');
      }
    else if (!rows[uid].flagged && rows[uid].classname.indexOf('flagged')>=0)
      {
      rows[uid].classname = rows[uid].classname.replace(/\s*flagged/, '');
    else if (!rows[uid].flagged && rowobj.hasClass('flagged'))
      rowobj.removeClass('flagged');
      }
    this.set_unread_children(uid);
    this.set_message_icon(uid);
    }
    };
  // sets unroot (unread_children) class of parent row
  this.set_unread_children = function(uid)
    {
    var row = this.message_list.rows[uid];
    if (row.parent_uid || !row.has_children)
      return;
    if (!row.unread && row.unread_children && !row.expanded)
      $(row.obj).addClass('unroot');
    else
      $(row.obj).removeClass('unroot');
    };
  // move selected messages to the specified mailbox
  this.move_messages = function(mbox)
@@ -1881,8 +2156,10 @@
      return;
    // if config is set to flag for deletion
    if (this.env.flag_for_deletion)
    if (this.env.flag_for_deletion) {
      this.mark_message('delete');
      return false;
      }
    // if there isn't a defined trash mailbox or we are in it
    else if (!this.env.trash_mailbox || this.env.mailbox == this.env.trash_mailbox) 
      this.permanently_remove_messages();
@@ -1897,6 +2174,8 @@
      else
        this.move_messages(this.env.trash_mailbox);
      }
    return true;
  };
  // delete the selected messages permanently
@@ -1910,22 +2189,23 @@
    this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : ''));
    };
  // Send a specifc request with UIDs of all selected messages
  // Send a specifc moveto/delete request with UIDs of all selected messages
  // @private
  this._with_selected_messages = function(action, lock, add_url, remove)
  this._with_selected_messages = function(action, lock, add_url)
  {
    var a_uids = new Array();
    var count = 0;
    if (this.env.uid)
      a_uids[0] = this.env.uid;
    else
    {
      var selection = this.message_list.get_selection();
      var rows = this.message_list.rows;
      var id;
      for (var n=0; n<selection.length; n++) {
        id = selection[n];
        a_uids[a_uids.length] = id;
    count += this.update_thread(id);
        this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
      }
      // make sure there are no selected rows
@@ -1939,6 +2219,12 @@
    if (this.env.display_next && this.env.next_uid)
      add_url += '&_next_uid='+this.env.next_uid;
    if (count < 0)
      add_url += '&_count='+(count*-1);
    else if (count > 0)
      // remove threads from the end of the list
      this.delete_excessive_thread_rows();
    // send request to server
    this.http_post(action, '_uid='+a_uids.join(',')+'&_mbox='+urlencode(this.env.mailbox)+add_url, lock);
@@ -2009,6 +2295,9 @@
      this.set_message(a_uids[i], 'unread', (flag=='unread' ? true : false));
    this.http_post('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
    for (var i=0; i<a_uids.length; i++)
      this.update_thread_root(a_uids[i], flag);
  };
  // set image to flagged or unflagged
@@ -2071,7 +2360,8 @@
    var add_url = '';
    var r_uids = new Array();
    var rows = this.message_list ? this.message_list.rows : new Array();
    var count = 0;
    for (var i=0; i<a_uids.length; i++)
      {
      uid = a_uids[i];
@@ -2080,16 +2370,25 @@
        if (rows[uid].unread)
          r_uids[r_uids.length] = uid;
    if (this.env.skip_deleted)
    if (this.env.skip_deleted) {
      count += this.update_thread(uid);
          this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
      }
    else
      this.set_message(uid, 'deleted', true);
        }
      }
    // make sure there are no selected rows
    if (this.env.skip_deleted && !this.env.display_next && this.message_list)
    if (this.env.skip_deleted && this.message_list) {
      if(!this.env.display_next)
      this.message_list.clear_selection();
      if (count < 0)
        add_url += '&_count='+(count*-1);
      else if (count > 0)
        // remove threads from the end of the list
        this.delete_excessive_thread_rows();
      }
    add_url = '&_from='+(this.env.action ? this.env.action : '');
    
@@ -2126,7 +2425,60 @@
        this.set_message(uid, 'unread', false);
      }
  };
  /*********************************************************/
  /*********       mailbox folders methods         *********/
  /*********************************************************/
  this.expunge_mailbox = function(mbox)
    {
    var lock = false;
    var add_url = '';
    // lock interface if it's the active mailbox
    if (mbox == this.env.mailbox)
       {
       lock = true;
       this.set_busy(true, 'loading');
       add_url = '&_reload=1';
       }
    // send request to server
    var url = '_mbox='+urlencode(mbox);
    this.http_post('expunge', url+add_url, lock);
    };
  this.purge_mailbox = function(mbox)
    {
    var lock = false;
    var add_url = '';
    if (!confirm(this.get_label('purgefolderconfirm')))
      return false;
    // lock interface if it's the active mailbox
    if (mbox == this.env.mailbox)
       {
       lock = true;
       this.set_busy(true, 'loading');
       add_url = '&_reload=1';
       }
    // send request to server
    var url = '_mbox='+urlencode(mbox);
    this.http_post('purge', url+add_url, lock);
    return true;
    };
  // test if purge command is allowed
  this.purge_mailbox_test = function()
  {
    return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox
      || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))
      || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter))));
  };
  
  /*********************************************************/
  /*********           login form methods          *********/
@@ -2152,9 +2504,59 @@
  /*********        message compose methods        *********/
  /*********************************************************/
  
  // init message compose form: set focus and eventhandlers
  this.init_messageform = function()
  {
    if (!this.gui_objects.messageform)
      return false;
    //this.messageform = this.gui_objects.messageform;
    var input_from = $("[name='_from']");
    var input_to = $("[name='_to']");
    var input_subject = $("input[name='_subject']");
    var input_message = $("[name='_message']").get(0);
    var html_mode = $("input[name='_is_html']").val() == '1';
    // init live search events
    this.init_address_input_events(input_to);
    this.init_address_input_events($("[name='_cc']"));
    this.init_address_input_events($("[name='_bcc']"));
    if (!html_mode)
      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
    // add signature according to selected identity
    if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == ''
        && !html_mode) {  // if we have HTML editor, signature is added in callback
      this.change_identity(input_from[0]);
    }
    else if (!html_mode)
      this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
    if (input_to.val() == '')
      input_to.focus();
    else if (input_subject.val() == '')
      input_subject.focus();
    else if (input_message && !html_mode)
      input_message.focus();
    // get summary of all field values
    this.compose_field_hash(true);
    // start the auto-save timer
    this.auto_save_start();
  };
  this.init_address_input_events = function(obj)
  {
    var handler = function(e){ return ref.ksearch_keypress(e,this); };
    obj.bind((bw.safari || bw.ie ? 'keydown' : 'keypress'), handler);
    obj.attr('autocomplete', 'off');
  };
  // checks the input fields before sending a message
  this.check_compose_input = function()
    {
  {
    // check input fields
    var input_to = $("[name='_to']");
    var input_cc = $("[name='_cc']");
@@ -2189,36 +2591,30 @@
    }
    
    // display localized warning for missing subject
    if (input_subject.val() == '')
      {
    if (input_subject.val() == '') {
      var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject'));
      // user hit cancel, so don't send
      if (!subject && subject !== '')
        {
      if (!subject && subject !== '') {
        input_subject.focus();
        return false;
        }
      else
        {
        input_subject.val((subject ? subject : this.get_label('nosubject')));
        }
      }
      else
        input_subject.val((subject ? subject : this.get_label('nosubject')));
    }
    // check for empty body
    if ((!window.tinyMCE || !tinyMCE.get(this.env.composebody))
    && input_message.val() == '' && !confirm(this.get_label('nobodywarning')))
      {
        && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) {
      input_message.focus();
      return false;
      }
    }
    else if (window.tinyMCE && tinyMCE.get(this.env.composebody)
    && !tinyMCE.get(this.env.composebody).getContent()
    && !confirm(this.get_label('nobodywarning')))
      {
        && !tinyMCE.get(this.env.composebody).getContent()
        && !confirm(this.get_label('nobodywarning'))) {
      tinyMCE.get(this.env.composebody).focus();
      return false;
      }
    }
    // Apply spellcheck changes if spell checker is active
    this.stop_spellchecking();
@@ -2228,26 +2624,26 @@
      tinyMCE.triggerSave();
    return true;
    };
  };
  this.stop_spellchecking = function()
    {
  {
    if (this.env.spellcheck && !this.spellcheck_ready) {
      $(this.env.spellcheck.spell_span).trigger('click');
      this.set_spellcheck_state('ready');
      }
    };
    }
  };
  this.display_spellcheck_controls = function(vis)
    {
  {
    if (this.env.spellcheck) {
      // stop spellchecking process
      if (!vis)
    this.stop_spellchecking();
        this.stop_spellchecking();
      $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden');
      }
    };
  };
  this.set_spellcheck_state = function(s)
    {
@@ -3511,7 +3907,20 @@
    if (folder)
      this.http_post('unsubscribe', '_mbox='+urlencode(folder));
    };
  this.enable_threading = function(folder)
    {
    if (folder)
      this.http_post('enable-threading', '_mbox='+urlencode(folder));
    };
  this.disable_threading = function(folder)
    {
    if (folder)
      this.http_post('disable-threading', '_mbox='+urlencode(folder));
    };
    
  // helper method to find a specific mailbox row ID
  this.get_folder_row_id = function(folder)
    {
@@ -3816,135 +4225,65 @@
    return null;
  };
  // for reordering column array, Konqueror workaround
  this.set_message_coltypes = function(coltypes)
  // for reordering column array (Konqueror workaround)
  // and for setting some message list global variables
  this.set_message_coltypes = function(coltypes, repl)
  { 
    this.coltypes = coltypes;
    this.env.coltypes = coltypes;
    
    // set correct list titles
    var cell, col;
    var thead = this.gui_objects.messagelist ? this.gui_objects.messagelist.tHead : null;
    for (var n=0; thead && n<this.coltypes.length; n++)
    // replace old column headers
    if (thead && repl) {
      for (var cell, c=0; c < repl.length; c++) {
        cell = thead.rows[0].cells[c];
        if (!cell) {
          cell = document.createElement('td');
          thead.rows[0].appendChild(cell);
        }
        cell.innerHTML = repl[c].html;
        if (repl[c].id) cell.id = repl[c].id;
        if (repl[c].className) cell.className = repl[c].className;
      }
    }
    var cell, col, n;
    for (n=0; thead && n<this.env.coltypes.length; n++)
      {
      col = this.coltypes[n];
      col = this.env.coltypes[n];
      if ((cell = thead.rows[0].cells[n+1]) && (col=='from' || col=='to'))
        {
        // if we have links for sorting, it's a bit more complicated...
        if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a')
          {
          cell.firstChild.innerHTML = this.get_label(this.coltypes[n]);
          cell.firstChild.innerHTML = this.get_label(this.env.coltypes[n]);
          cell.firstChild.onclick = function(){ return rcmail.command('sort', this.__col, this); };
          cell.firstChild.__col = col;
          }
        else
          cell.innerHTML = this.get_label(this.coltypes[n]);
          cell.innerHTML = this.get_label(this.env.coltypes[n]);
        cell.id = 'rcm'+col;
        }
      else if (col == 'subject' && this.message_list)
        this.message_list.subject_col = n+1;
      }
  };
  // create a table row in the message list
  this.add_message_row = function(uid, cols, flags, attachment, attop)
    {
    if (!this.gui_objects.messagelist || !this.message_list)
      return false;
    if (this.message_list.background)
      var tbody = this.message_list.background;
    else
      var tbody = this.gui_objects.messagelist.tBodies[0];
    var rowcount = tbody.rows.length;
    var even = rowcount%2;
    this.env.messages[uid] = {
      deleted: flags.deleted?1:0,
      replied: flags.replied?1:0,
      unread: flags.unread?1:0,
      forwarded: flags.forwarded?1:0,
      flagged:flags.flagged?1:0
    };
    var css_class = 'message'
        + (even ? ' even' : ' odd')
        + (flags.unread ? ' unread' : '')
        + (flags.deleted ? ' deleted' : '')
        + (flags.flagged ? ' flagged' : '')
        + (this.message_list.in_selection(uid) ? ' selected' : '');
    // for performance use DOM instead of jQuery here
    var row = document.createElement('tr');
    row.id = 'rcmrow'+uid;
    row.className = css_class;
    var icon = this.env.messageicon;
    if (flags.deleted && this.env.deletedicon)
      icon = this.env.deletedicon;
    else if (flags.replied && this.env.repliedicon)
      {
      if (flags.forwarded && this.env.forwardedrepliedicon)
        icon = this.env.forwardedrepliedicon;
      else
        icon = this.env.repliedicon;
      }
    else if (flags.forwarded && this.env.forwardedicon)
      icon = this.env.forwardedicon;
    else if(flags.unread && this.env.unreadicon)
      icon = this.env.unreadicon;
    // add icon col
    var col = document.createElement('td');
    col.className = 'icon';
    col.innerHTML = icon ? '<img src="'+icon+'" alt="" />' : '';
    row.appendChild(col);
    // build subject link
    if (!bw.ie && cols.subject) {
      var action = cols.mbox == this.env.drafts_mailbox ? 'compose' : 'show';
      var uid_param = cols.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid';
      cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(cols.mbox)+'&'+uid_param+'='+uid+'"'+
         ' onclick="return rcube_event.cancel(event)">'+cols.subject+'</a>';
    }
    // add each submitted col
    for (var n = 0; n < this.coltypes.length; n++) {
      var c = this.coltypes[n];
      col = document.createElement('td');
      col.className = String(c).toLowerCase();
      if (c=='flag') {
        if (flags.flagged && this.env.flaggedicon)
          col.innerHTML = '<img src="'+this.env.flaggedicon+'" alt="" />';
        else if(!flags.flagged && this.env.unflaggedicon)
          col.innerHTML = '<img src="'+this.env.unflaggedicon+'" alt="" />';
        }
      else if (c=='attachment')
        col.innerHTML = (attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;');
      else
        col.innerHTML = cols[c];
      row.appendChild(col);
      }
    this.message_list.insert_row(row, attop);
    // remove excessive columns
    for (var i=n+1; thead && i<thead.rows[0].cells.length; i++)
      thead.rows[0].removeChild(thead.rows[0].cells[i]);
    // remove 'old' row
    if (attop && this.env.pagesize && this.message_list.rowcount > this.env.pagesize) {
      var uid = this.message_list.get_last_row();
      this.message_list.remove_row(uid);
      this.message_list.clear_selection(uid);
      }
    };
    this.env.subject_col = null;
    this.env.flagged_col = null;
  // messages list handling in background (for performance)
  this.offline_message_list = function(flag)
    {
    var found;
    if((found = find_in_array('subject', this.env.coltypes)) >= 0) {
      this.set_env('subject_col', found);
      if (this.message_list)
          this.message_list.set_background_mode(flag);
    };
        this.message_list.subject_col = found+1;
      }
    if((found = find_in_array('flag', this.env.coltypes)) >= 0)
      this.set_env('flagged_col', found);
  };
  // replace content of row count display
  this.set_rowcount = function(text)
@@ -4260,7 +4599,7 @@
      for (var i=0; i < response.callbacks.length; i++)
        this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
    }
    // process the response data according to the sent action
    switch (response.action) {
      case 'delete':
@@ -4288,19 +4627,25 @@
          // disable commands useless when mailbox is empty
          this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 
            'mark', 'viewsource', 'open', 'edit', 'download', 'print', 'load-attachment', 
            'purge', 'expunge', 'select-all', 'select-none', 'sort', false);
            'purge', 'expunge', 'select-all', 'select-none', 'sort',
            'expand-all', 'expand-unread', 'collapse-all', false);
        }
        break;
      case 'check-recent':
      case 'getunread':
      case 'search':
      case 'list':
        if (this.task == 'mail') {
          if (this.message_list && response.action == 'list')
          if (this.message_list && (response.action == 'list' || response.action == 'search')) {
            this.msglist_select(this.message_list);
            this.expand_threads();
          }
          this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
          this.enable_command('purge', this.purge_mailbox_test());
          this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount);
          if (response.action == 'list')
            this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
        }
@@ -4332,6 +4677,15 @@
    var d = new Date();
    this.http_request('keep-alive', '_t='+d.getTime());
    };
  // start interval for keep-alive/recent_check signal
  this.start_keepalive = function()
    {
    if (this.env.keep_alive && !this.env.framed && this.task=='mail' && this.gui_objects.mailboxlist)
      this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000);
    else if (this.env.keep_alive && !this.env.framed && this.task!='login')
      this._int = setInterval(function(){ ref.send_keep_alive(); }, this.env.keep_alive * 1000);
    }
  // send periodic request to check for recent messages
  this.check_for_recent = function(refresh)
@@ -4432,7 +4786,6 @@
    };
    
}  // end object rcube_webmail
// copy event engine prototype
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
program/js/common.js
@@ -292,7 +292,7 @@
    e = this;
  else if (typeof e == 'object')
    e.event = evt;
  if (this._events && this._events[evt] && !this._event_exec) {
    this._event_exec = true;
    for (var i=0; i < this._events[evt].length; i++) {
program/js/list.js
@@ -37,6 +37,7 @@
  this.subject_col = -1;
  this.shiftkey = false;
  this.multiselect = false;
  this.multiexpand = false;
  this.multi_selecting = false;
  this.draggable = false;
  this.keyboard = false;
@@ -76,7 +77,7 @@
    for(var r=0; r<this.list.tBodies[0].childNodes.length; r++)
    {
      row = this.list.tBodies[0].childNodes[r];
      while (row && (row.nodeType != 1 || row.style.display == 'none'))
      while (row && row.nodeType != 1)
      {
        row = row.nextSibling;
        r++;
@@ -108,7 +109,7 @@
    var p = this;
    var uid = RegExp.$1;
    row.uid = uid;
    this.rows[uid] = {uid:uid, id:row.id, obj:row, classname:row.className};
    this.rows[uid] = {uid:uid, id:row.id, obj:row};
    // set eventhandlers to table row
    row.onmousedown = function(e){ return p.drag_row(e, this.uid); };
@@ -319,6 +320,188 @@
},
expand_row: function(e, id)
{
  var row = this.rows[id];
  var evtarget = rcube_event.get_target(e);
  var mod_key = rcube_event.get_modifier(e);
  // Don't select this message
  this.dont_select = true;
  // Don't treat double click on the expando as double click on the message.
  row.clicked = 0;
  if (row.expanded) {
    evtarget.className = "collapsed";
    if (mod_key == CONTROL_KEY || this.multiexpand)
      this.collapse_all(row);
    else
      this.collapse(row);
  }
  else {
    evtarget.className = "expanded";
    if (mod_key == CONTROL_KEY || this.multiexpand)
      this.expand_all(row);
    else
     this.expand(row);
  }
},
collapse: function(row)
{
  row.expanded = false;
  this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
  var depth = row.depth;
  var new_row = row ? row.obj.nextSibling : null;
  var r;
  while (new_row) {
    if (new_row.nodeType == 1) {
      var r = this.rows[new_row.uid];
      if (r && r.depth <= depth)
        break;
      $(new_row).hide();
      r.expanded = false;
      this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
    }
    new_row = new_row.nextSibling;
  }
  return false;
},
expand: function(row)
{
  var depth, new_row;
  var last_expanded_parent_depth;
  if (row) {
    row.expanded = true;
    depth = row.depth;
    new_row = row.obj.nextSibling;
    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
  }
  else {
    var tbody = this.list.tBodies[0];
    new_row = tbody.firstChild;
    depth = 0;
    last_expanded_parent_depth = 0;
  }
  while (new_row) {
    if (new_row.nodeType == 1) {
      var r = this.rows[new_row.uid];
      if (r) {
        if (row && (!r.depth || r.depth <= depth))
          break;
        if (r.parent_uid) {
          var p = this.rows[r.parent_uid];
          if (p && p.expanded) {
            if ((row && p == row) || last_expanded_parent_depth >= p.depth - 1) {
              last_expanded_parent_depth = p.depth;
              $(new_row).show();
              r.expanded = true;
              this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
            }
          }
          else
            if (row && (! p || p.depth <= depth))
              break;
        }
      }
    }
    new_row = new_row.nextSibling;
  }
  return false;
},
collapse_all: function(row)
{
  var depth, new_row;
  var r;
  if (row) {
    row.expanded = false;
    depth = row.depth;
    new_row = row.obj.nextSibling;
    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
    // don't collapse sub-root tree in multiexpand mode
    if (depth && this.multiexpand)
      return false;
  }
  else {
    var tbody = this.list.tBodies[0];
    new_row = tbody.firstChild;
    depth = 0;
  }
  while (new_row) {
    if (new_row.nodeType == 1) {
      var r = this.rows[new_row.uid];
      if (r) {
        if (row && (!r.depth || r.depth <= depth))
          break;
        if (row || r.depth)
          $(new_row).hide();
        if (r.has_children) {
          r.expanded = false;
          var expando = document.getElementById('rcmexpando' + r.uid);
          if (expando)
            expando.className = 'collapsed';
          this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
        }
      }
    }
    new_row = new_row.nextSibling;
  }
  return false;
},
expand_all: function(row)
{
  var depth, new_row;
  var r;
  if (row) {
    row.expanded = true;
    depth = row.depth;
    new_row = row.obj.nextSibling;
    this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded });
  }
  else {
    var tbody = this.list.tBodies[0];
    new_row = tbody.firstChild;
    depth = 0;
  }
  while (new_row) {
    if (new_row.nodeType == 1) {
      var r = this.rows[new_row.uid];
      if (r) {
        if (row && r.depth <= depth)
          break;
        $(new_row).show();
        if (r.has_children) {
          r.expanded = true;
          var expando = document.getElementById('rcmexpando' + r.uid);
          if (expando)
            expando.className = 'expanded';
          this.triggerEvent('expandcollapse', { uid:r.uid, expanded:r.expanded });
        }
      }
    }
    new_row = new_row.nextSibling;
  }
  return false;
},
/**
 * get first/next/previous/last rows that are not hidden
 */
@@ -495,13 +678,15 @@
  {
    if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j))
    {
      if (!this.in_selection(n))
      if (!this.in_selection(n)) {
        this.highlight_row(n, true);
      }
    }
    else
    {
      if  (this.in_selection(n) && !control)
      if  (this.in_selection(n) && !control) {
        this.highlight_row(n, true);
      }
    }
  }
},
@@ -516,7 +701,7 @@
    if (this.selection[n]==id)
      return true;
  return false;
  return false;
},
@@ -567,7 +752,7 @@
  var select_before = this.selection.join(',');
  
  for (var n in this.rows)
    this.highlight_row(n, true);
    this.highlight_row(n, true);
  // trigger event if selection changed
  if (this.selection.join(',') != select_before)
@@ -685,6 +870,16 @@
      // Stop propagation so that the browser doesn't scroll
      rcube_event.cancel(e);
      return this.use_arrow_key(keyCode, mod_key);
    case 61:
    case 107: // Plus sign on a numeric keypad (fc11 + firefox 3.5.2)
    case 109:
    case 32:
      // Stop propagation
      rcube_event.cancel(e);
      var ret = this.use_plusminus_key(keyCode, mod_key);
      this.key_pressed = keyCode;
      this.triggerEvent('keypress');
      return ret;
    default:
      this.shiftkey = e.shiftKey;
      this.key_pressed = keyCode;
@@ -712,6 +907,10 @@
    case 38: 
    case 63233:
    case 63232:
    case 61:
    case 107:
    case 109:
    case 32:
      if (!rcube_event.get_modifier(e) && this.focused)
        return rcube_event.cancel(e);
        
@@ -740,6 +939,36 @@
    this.select_row(new_row.uid, mod_key, true);
    this.scrollto(new_row.uid);
  }
  return false;
},
/**
 * Special handling method for +/- keys
 */
use_plusminus_key: function(keyCode, mod_key)
{
  var selected_row = this.rows[this.last_selected];
  if (!selected_row)
    return;
  if (keyCode == 32)
    keyCode = selected_row.expanded ? 109 : 61;
  if (keyCode == 61 || keyCode == 107)
    if (mod_key == CONTROL_KEY || this.multiexpand)
      this.expand_all(selected_row);
    else
     this.expand(selected_row);
  else
    if (mod_key == CONTROL_KEY || this.multiexpand)
      this.collapse_all(selected_row);
    else
      this.collapse(selected_row);
  var expando = document.getElementById('rcmexpando' + selected_row.uid);
  if (expando)
    expando.className = selected_row.expanded?'expanded':'collapsed';
  return false;
},
@@ -779,9 +1008,9 @@
    if (!this.draglayer)
      this.draglayer = $('<div>').attr('id', 'rcmdraglayer').css({ position:'absolute', display:'none', 'z-index':2000 }).appendTo(document.body);
    // get subjects of selectedd messages
    // get subjects of selected messages
    var names = '';
    var c, i, node, subject, obj;
    var c, i, subject, obj;
    for(var n=0; n<this.selection.length; n++)
    {
      if (n>12)  // only show 12 lines
@@ -790,24 +1019,29 @@
        break;
      }
      if (this.rows[this.selection[n]].obj)
      if (obj = this.rows[this.selection[n]].obj)
      {
        obj = this.rows[this.selection[n]].obj;
        subject = '';
        for(c=0, i=0; i<obj.childNodes.length; i++)
        for (c=0, i=0; i<obj.childNodes.length; i++)
        {
          if (obj.childNodes[i].nodeName == 'TD')
      if (obj.childNodes[i].nodeName == 'TD')
          {
            if (((node = obj.childNodes[i].firstChild) && (node.nodeType==3 || node.nodeName=='A')) &&
              (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c)))
            {
          if (n == 0) {
            if (node.nodeType == 3)
          this.drag_start_pos = $(obj.childNodes[i]).offset();
        else
                  this.drag_start_pos = $(node).offset();
            if (n == 0)
          this.drag_start_pos = $(obj.childNodes[i]).offset();
        if (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c))
        {
          var node, tmp_node, nodes = obj.childNodes[i].childNodes;
          // find text node
          for (m=0; m<nodes.length; m++) {
            if ((tmp_node = obj.childNodes[i].childNodes[m]) && (tmp_node.nodeType==3 || tmp_node.nodeName=='A'))
              node = tmp_node;
          }
          if (!node)
            break;
              subject = node.nodeType==3 ? node.data : node.innerHTML;
          // remove leading spaces
          subject = subject.replace(/^\s+/i, '');
program/lib/imap.inc
@@ -173,18 +173,10 @@
    var $forwarded = false;
    var $junk = false;
    var $flagged = false;
    var $has_children = false;
    var $depth = 0;
    var $unread_children = 0;
    var $others = array();
}
/**
 * @todo Change class vars to public/private
 */
class iilThreadHeader
{
    var $id;
    var $sbj;
    var $irt;
    var $mid;
}
function iil_xor($string, $string2) {
@@ -873,7 +865,7 @@
    
    $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
        'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
    if (!$fields[$field]) {
        return false;
    }
@@ -885,9 +877,12 @@
    
    $is_uid = $is_uid ? 'UID ' : '';
    
    if (!empty($add)) {
    // message IDs
    if (is_array($add))
        $add = iil_CompressMessageSet(join(',', $add));
    if (!empty($add))
        $add = " $add";
    }
    $command  = 's ' . $is_uid . 'SORT (' . $field . ') ';
    $command .= $encoding . ' ALL' . $add;
@@ -917,20 +912,27 @@
function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false) {
    list($from_idx, $to_idx) = explode(':', $message_set);
    if (empty($message_set) ||
        (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) {
        return false;
    if (is_array($message_set)) {
        if (!($message_set = iil_CompressMessageSet(join(',', $message_set))))
            return false;
    } else {
        list($from_idx, $to_idx) = explode(':', $message_set);
        if (empty($message_set) ||
            (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) {
            return false;
        }
    }
    $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field);
    
    $fields_a['DATE']         = 1;
    $fields_a['INTERNALDATE'] = 4;
    $fields_a['ARRIVAL']       = 4;
    $fields_a['FROM']         = 1;
    $fields_a['REPLY-TO']     = 1;
    $fields_a['SENDER']       = 1;
    $fields_a['TO']           = 1;
    $fields_a['CC']           = 1;
    $fields_a['SUBJECT']      = 1;
    $fields_a['UID']          = 2;
    $fields_a['SIZE']         = 2;
@@ -1031,22 +1033,6 @@
        }
    } while (!iil_StartsWith($line, $key, true));
/*
    //check number of elements...
    if (is_numeric($from_idx) && is_numeric($to_idx)) {
        //count how many we should have
        $should_have = $to_idx - $from_idx + 1;
        //if we have less, try and fill in the "gaps"
        if (count($result) < $should_have) {
            for ($i=$from_idx; $i<=$to_idx; $i++) {
                if (!isset($result[$i])) {
                        $result[$i] = '';
                        }
                }
        }
    }
*/
    return $result;    
}
@@ -1122,307 +1108,6 @@
    return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
}
function iil_SortThreadHeaders($headers, $index_a, $uids) {
    asort($index_a);
    $result = array();
    foreach ($index_a as $mid=>$foobar) {
        $uid = $uids[$mid];
        $result[$uid] = $headers[$uid];
    }
    return $result;
}
function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) {
    global $clock;
    global $index_a;
    list($from_idx, $to_idx) = explode(':', $message_set);
    if (empty($message_set) || (isset($to_idx)
        && (int)$from_idx > (int)$to_idx)) {
        return false;
    }
    $result = array();
    $uids   = iil_C_FetchUIDs($conn, $mailbox);
    $debug  = false;
    $message_set = iil_CompressMessageSet($message_set);
    /* if we're missing any, get them */
    if ($message_set) {
        /* FETCH date,from,subject headers */
        $key        = 'fh';
        $fp         = $conn->fp;
        $request    = $key . " FETCH $message_set ";
            $request   .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])";
        $mid_to_id  = array();
        if (!iil_PutLine($fp, $request)) {
            return false;
            }
        do {
            $line = chop(iil_ReadLine($fp, 1024));
            if ($debug) {
                echo $line . "\n";
                }
            if (preg_match('/\{[0-9]+\}$/', $line)) {
                $a      = explode(' ', $line);
                $new = array();
                $new_thhd = new iilThreadHeader;
                $new_thhd->id = $a[1];
                do {
                    $line = chop(iil_ReadLine($fp, 1024), "\r\n");
                    if (iil_StartsWithI($line, 'Message-ID:')
                                    || (iil_StartsWithI($line,'In-Reply-To:'))
                                    || (iil_StartsWithI($line,'SUBJECT:'))) {
                        $pos        = strpos($line, ':');
                        $field_name = substr($line, 0, $pos);
                        $field_val  = substr($line, $pos+1);
                        $new[strtoupper($field_name)] = trim($field_val);
                    } else if (preg_match('/^\s+/', $line)) {
                        $new[strtoupper($field_name)] .= trim($line);
                    }
                } while ($line[0] != ')');
                $new_thhd->sbj = $new['SUBJECT'];
                $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);
                $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);
                $result[$uids[$new_thhd->id]] = $new_thhd;
            }
        } while (!iil_StartsWith($line, 'fh'));
    }
    /* sort headers */
    if (is_array($index_a)) {
        $result = iil_SortThreadHeaders($result, $index_a, $uids);
    }
    //echo 'iil_FetchThreadHeaders:'."\n";
    //print_r($result);
    return $result;
}
function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) {
    global $index_a;
    list($from_idx, $to_idx) = explode(':', $message_set);
    if (empty($message_set) || (isset($to_idx)
            && (int)$from_idx > (int)$to_idx)) {
        return false;
    }
    $result    = array();
    $roots     = array();
    $root_mids = array();
    $sub_mids  = array();
    $strays    = array();
    $messages  = array();
    $fp        = $conn->fp;
    $debug     = false;
    $sbj_filter_pat = '/[a-z]{2,3}(\[[0-9]*\])?:(\s*)/i';
    /*  Do "SELECT" command */
    if (!iil_C_Select($conn, $mailbox)) {
        return false;
    }
    /* FETCH date,from,subject headers */
    $mid_to_id = array();
    $messages  = array();
    $headers   = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);
    if ($clock) {
        $clock->register('fetched headers');
    }
    if ($debug) {
        print_r($headers);
    }
    /* go through header records */
    foreach ($headers as $header) {
        //$id = $header['i'];
        //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'],
        //            'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);
        $id  = $header->id;
        $new = array('id' => $id, 'MESSAGE-ID' => $header->mid,
                'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj);
        /* add to message-id -> mid lookup table */
        $mid_to_id[$new['MESSAGE-ID']] = $id;
        /* if no subject, use message-id */
        if (empty($new['SUBJECT'])) {
            $new['SUBJECT'] = $new['MESSAGE-ID'];
        }
        /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
        $sbj_pre = '';
        $has_re = false;
        if (preg_match($sbj_filter_pat, $new['SUBJECT'])) {
            $has_re = true;
        }
            if ($has_re || $new['IN-REPLY-TO']) {
                $sbj_pre = 'RE:';
        }
        /* strip out 're:', 'fw:' etc */
        if ($has_re) {
            $sbj = preg_replace($sbj_filter_pat, '', $new['SUBJECT']);
        } else {
            $sbj = $new['SUBJECT'];
        }
            $new['SUBJECT'] = $sbj_pre.$sbj;
        /* if subject not a known thread-root, add to list */
        if ($debug) {
            echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n";
        }
            $root_id = $roots[$sbj];
        if ($root_id && ($has_re || !$root_in_root[$root_id])) {
            if ($debug) {
                echo "\tfound root: $root_id\n";
            }
                $sub_mids[$new['MESSAGE-ID']] = $root_id;
            $result[$root_id][]           = $id;
        } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) {
            /* try to use In-Reply-To header to find root
                unless subject contains 'Re:' */
            if ($has_re&&$new['IN-REPLY-TO']) {
                if ($debug) {
                    echo "\tlooking: ".$new['IN-REPLY-TO']."\n";
                }
                //reply to known message?
                $temp = $sub_mids[$new['IN-REPLY-TO']];
                if ($temp) {
                    //found it, root:=parent's root
                    if ($debug) {
                        echo "\tfound parent: ".$new['SUBJECT']."\n";
                    }
                            $result[$temp][]              = $id;
                    $sub_mids[$new['MESSAGE-ID']] = $temp;
                    $sbj                          = '';
                } else {
                    //if we can't find referenced parent, it's a "stray"
                    $strays[$id] = $new['IN-REPLY-TO'];
                }
            }
            //add subject as root
            if ($sbj) {
                if ($debug) {
                    echo "\t added to root\n";
                }
                        $roots[$sbj]                  = $id;
                $root_in_root[$id]            = !$has_re;
                $sub_mids[$new['MESSAGE-ID']] = $id;
                $result[$id]                  = array($id);
            }
            if ($debug) {
                echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n";
                }
        }
    }
    //now that we've gone through all the messages,
    //go back and try and link up the stray threads
    if (count($strays) > 0) {
        foreach ($strays as $id=>$irt) {
            $root_id = $sub_mids[$irt];
            if (!$root_id || $root_id==$id) {
                continue;
            }
                $result[$root_id] = array_merge($result[$root_id],$result[$id]);
            unset($result[$id]);
        }
    }
    if ($clock) {
        $clock->register('data prepped');
    }
    if ($debug) {
        print_r($roots);
    }
    return $result;
}
function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') {
    if (!is_array($tree) || !is_array($index)) {
        return false;
    }
    //create an id to position lookup table
    $i = 0;
    foreach ($index as $id=>$val) {
        $i++;
        $index[$id] = $i;
    }
    $max = $i+1;
    //for each tree, set array key to position
    $itree = array();
    foreach ($tree as $id=>$node) {
        if (count($tree[$id])<=1) {
            //for "threads" with only one message, key is position of that message
            $n         = $index[$id];
            $itree[$n] = array($n=>$id);
        } else {
            //for "threads" with multiple messages,
            $min   = $max;
            $new_a = array();
            foreach ($tree[$id] as $mid) {
                $new_a[$index[$mid]] = $mid;        //create new sub-array mapping position to id
                $pos                 = $index[$mid];
                if ($pos&&$pos<$min) {
                    $min = $index[$mid];    //find smallest position
                }
            }
            $n = $min;    //smallest position of child is thread position
            //assign smallest position to root level key
            //set children array to one created above
            ksort($new_a);
            $itree[$n] = $new_a;
        }
    }
    //sort by key, this basically sorts all threads
    ksort($itree);
    $i   = 0;
    $out = array();
    foreach ($itree as $k=>$node) {
        $out[$i] = $itree[$k];
        $i++;
    }
    return $out;
}
function iil_IndexThreads(&$tree) {
    /* creates array mapping mid to thread id */
    if (!is_array($tree)) {
        return false;
    }
    $t_index = array();
    foreach ($tree as $pos=>$kids) {
        foreach ($kids as $kid) $t_index[$kid] = $pos;
    }
    return $t_index;
}
function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='')
{
    global $IMAP_USE_INTERNAL_DATE;
@@ -1435,6 +1120,9 @@
        $conn->error = "Couldn't select $mailbox";
        return false;
    }
    if (is_array($message_set))
        $message_set = join(',', $message_set);
    $message_set = iil_CompressMessageSet($message_set);
@@ -1878,6 +1566,87 @@
    return $result;
}
// Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about
// 7 times instead :-) See comments on http://uk2.php.net/references and this article:
// http://derickrethans.nl/files/phparch-php-variables-article.pdf
function iil_ParseThread($str, $begin, $end, $root, $parent, $depth, &$depthmap, &$haschildren) {
    $node = array();
    if ($str[$begin] != '(') {
        $stop = $begin + strspn($str, "1234567890", $begin, $end - $begin);
        $msg = substr($str, $begin, $stop - $begin);
        if ($msg == 0)
            return $node;
        if (is_null($root))
            $root = $msg;
        $depthmap[$msg] = $depth;
        $haschildren[$msg] = false;
        if (!is_null($parent))
            $haschildren[$parent] = true;
        if ($stop + 1 < $end)
            $node[$msg] = iil_ParseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren);
        else
            $node[$msg] = array();
    } else {
        $off = $begin;
        while ($off < $end) {
            $start = $off;
            $off++;
            $n = 1;
            while ($n > 0) {
                $p = strpos($str, ')', $off);
                if ($p === false) {
                    error_log('Mismatched brackets parsing IMAP THREAD response:');
                    error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20));
                    error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10))));
                    return $node;
                }
                $p1 = strpos($str, '(', $off);
                if ($p1 !== false && $p1 < $p) {
                    $off = $p1 + 1;
                    $n++;
                } else {
                    $off = $p + 1;
                    $n--;
                }
            }
            $node += iil_ParseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren);
        }
    }
    return $node;
}
function iil_C_Thread(&$conn, $folder, $algorithm='REFERENCES', $criteria='',
    $encoding='US-ASCII') {
    if (iil_C_Select($conn, $folder)) {
        $encoding = $encoding ? trim($encoding) : 'US-ASCII';
        $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';
        $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL';
        iil_PutLineC($conn->fp, "thrd1 THREAD $algorithm $encoding $criteria");
        do {
            $line = trim(iil_ReadLine($conn->fp, 10000));
            if (preg_match('/^\* THREAD/', $line)) {
                $str = trim(substr($line, 8));
                $depthmap = array();
                $haschildren = array();
                $tree = iil_ParseThread($str, 0, strlen($str), null, null, 0, $depthmap, $haschildren);
            }
        } while (!iil_StartsWith($line, 'thrd1', true));
        $result_code = iil_ParseResult($line);
        if ($result_code == 0) {
            return array($tree, $depthmap, $haschildren);
        }
        $conn->error = 'iil_C_Thread: ' . $line . "\n";
        return false;
    }
    $conn->error = "iil_C_Thread: Couldn't select \"$folder\"\n";
    return false;
}
function iil_C_Search(&$conn, $folder, $criteria) {
    if (iil_C_Select($conn, $folder)) {
program/localization/de_CH/labels.inc
@@ -127,6 +127,30 @@
$labels['deleted'] = 'Gelöschte';
$labels['invert'] = 'Umkehren';
$labels['filter'] = 'Filter';
$labels['list'] = 'Liste';
$labels['threads'] = 'Konversationen';
$labels['expand-all'] = 'All aufklappen';
$labels['expand-unread'] = 'Ungelesene aufklappen';
$labels['collapse-all'] = 'Alle zuklappen';
$labels['threaded'] = 'Gruppiert';
$labels['autoexpand_threads'] = 'Konversationen aufklappen';
$labels['do_expand'] = 'alle';
$labels['expand_only_unread'] = 'nur ungelesene';
$labels['fromto'] = 'Sender/Empfänger';
$labels['flag'] = 'Markierung';
$labels['attachment'] = 'Anhang';
$labels['nonesort'] = 'Keine';
$labels['sentdate'] = 'Sendedatum';
$labels['arrival'] = 'Empfangsdatum';
$labels['asc'] = 'aufsteigend';
$labels['desc'] = 'absteigend';
$labels['listcolumns'] = 'Spalten';
$labels['listsorting'] = 'Sortierung';
$labels['listorder'] = 'Ordnung';
$labels['listmode'] = 'Anzeigemodus';
$labels['compact'] = 'Packen';
$labels['empty'] = 'Leeren';
$labels['purge'] = 'Aufräumen';
program/localization/en_US/labels.inc
@@ -56,6 +56,7 @@
$labels['mailboxlist'] = 'Folders';
$labels['messagesfromto'] = 'Messages $from to $to of $count';
$labels['threadsfromto'] = 'Threads $from to $to of $count';
$labels['messagenrof'] = 'Message $nr of $count';
$labels['moveto']   = 'Move to...';
@@ -149,6 +150,29 @@
$labels['deleted'] = 'Deleted';
$labels['invert'] = 'Invert';
$labels['filter'] = 'Filter';
$labels['list'] = 'List';
$labels['threads'] = 'Threads';
$labels['expand-all'] = 'Expand All';
$labels['expand-unread'] = 'Expand Unread';
$labels['collapse-all'] = 'Collapse All';
$labels['threaded'] = 'Threaded';
$labels['autoexpand_threads'] = 'Expand message threads';
$labels['do_expand'] = 'all threads';
$labels['expand_only_unread'] = 'only with unread messages';
$labels['fromto'] = 'Sender/Recipient';
$labels['flag'] = 'Flag';
$labels['attachment'] = 'Attachment';
$labels['nonesort'] = 'None';
$labels['sentdate'] = 'Sent date';
$labels['arrival'] = 'Arrival date';
$labels['asc'] = 'ascending';
$labels['desc'] = 'descending';
$labels['listcolumns'] = 'List columns';
$labels['listsorting'] = 'Sorting column';
$labels['listorder'] = 'Sorting order';
$labels['listmode'] = 'List view mode';
$labels['compact'] = 'Compact';
$labels['empty'] = 'Empty';
@@ -308,7 +332,6 @@
$labels['focusonnewmessage'] = 'Focus browser window on new message';
$labels['checkallfolders'] = 'Check all folders for new messages';
$labels['displaynext'] = 'After message delete/move display the next message';
$labels['indexsort'] = 'Use message index for sorting by date';
$labels['mainoptions'] = 'Main Options';
$labels['section'] = 'Section';
$labels['maintenance'] = 'Maintenance';
program/localization/pl_PL/labels.inc
@@ -130,7 +130,7 @@
$labels['messageactions'] = 'WiÄ™cej akcji...';
$labels['select'] = 'Zaznacz';
$labels['all'] = 'Wszystkie';
$labels['none'] = 'Anuluj';
$labels['none'] = 'Brak';
$labels['unread'] = 'Nieprzeczytane';
$labels['flagged'] = 'Oznaczone';
$labels['unanswered'] = 'Bez odpowiedzi';
program/steps/mail/check_recent.inc
@@ -32,13 +32,12 @@
      }
      
      // get overall message count; allow caching because rcube_imap::recent_uids() did a refresh
      $all_count = $IMAP->messagecount();
      $all_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
      
      $unread_count = $IMAP->messagecount(NULL, 'UNSEEN', TRUE);
      $_SESSION['unseen_count'][$mbox_name] = $unread_count;
      $OUTPUT->set_env('messagecount', $all_count);
      $OUTPUT->set_env('pagesize', $IMAP->page_size);
      $OUTPUT->set_env('pagecount', ceil($all_count/$IMAP->page_size));
      $OUTPUT->command('set_unread_count', $mbox_name, $unread_count, ($mbox_name == 'INBOX'));
      $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($all_count));
@@ -56,17 +55,27 @@
      if (empty($_GET['_list']))
        continue;
      // use SEARCH/SORT to find recent messages
      $search_str = 'UID '.min($recents).':'.max($recents);
      if ($search_request)
        $search_str .= ' '.$IMAP->search_string;
      if ($IMAP->threading) {
        $OUTPUT->command('message_list.clear');
        $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col'];
        $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];
        $result_h = $IMAP->list_headers($mbox_name, NULL, $sort_col, $sort_order);
        // add to the list
        rcmail_js_message_list($result_h);
      }
      else {
        // use SEARCH/SORT to find recent messages
        $search_str = 'UID '.min($recents).':'.max($recents);
        if ($search_request)
          $search_str .= ' '.$IMAP->search_string;
      if ($IMAP->search($mbox_name, $search_str, NULL, 'date')) {
        // revert sort order
        $order = $_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] == 'DESC' ? 'ASC' : 'DESC';
        // get the headers and add them to the list
        $result_h = $IMAP->list_headers($mbox_name, 1, 'date', $order);
        rcmail_js_message_list($result_h, true, false);
        if ($IMAP->search($mbox_name, $search_str, NULL, 'date')) {
          // revert sort order
          $order = $_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] == 'DESC' ? 'ASC' : 'DESC';
          // get the headers and add them to the list
          $result_h = $IMAP->list_headers($mbox_name, 1, 'date', $order);
          rcmail_js_message_list($result_h, true, false);
        }
      }
    }
    else {
program/steps/mail/func.inc
@@ -59,6 +59,17 @@
if (!isset($_SESSION['sort_order']))
  $_SESSION['sort_order'] = $CONFIG['message_sort_order'];
// set threads mode
$a_threading = $RCMAIL->config->get('message_threading', array());
if (isset($_GET['_threads'])) {
  if ($_GET['_threads'])
    $a_threading[$_SESSION['mbox']] = true;
  else
    unset($a_threading[$_SESSION['mbox']]);
  $RCMAIL->user->save_prefs(array('message_threading' => $a_threading));
}
$IMAP->set_threading($a_threading[$_SESSION['mbox']]);
// set message set for search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
  {
@@ -88,13 +99,20 @@
      $OUTPUT->set_env('search_mods', $search_mods);
      
      // make sure the message count is refreshed (for default view)
      $IMAP->messagecount($mbox_name, 'ALL', true);
      $IMAP->messagecount($mbox_name, $IMAP->threading ? 'THREADS' : 'ALL', true);
    }
    
  // set current mailbox in client environment
  // set current mailbox and some other vars in client environment
  $OUTPUT->set_env('mailbox', $mbox_name);
  $OUTPUT->set_env('pagesize', $IMAP->page_size);
  $OUTPUT->set_env('quota', $IMAP->get_capability('quota'));
  $OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter());
  $OUTPUT->set_env('threading', (bool) $IMAP->threading);
  $OUTPUT->set_env('threads', $IMAP->threading
    || $IMAP->get_capability('thread=references')
        || $IMAP->get_capability('thread=orderedsubject')
        || $IMAP->get_capability('thread=refs')
  );
  if ($CONFIG['flag_for_deletion'])
    $OUTPUT->set_env('flag_for_deletion', true);
@@ -123,30 +141,15 @@
 * return the message list as HTML table
 */
function rcmail_message_list($attrib)
  {
  global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT;
{
  global $IMAP, $CONFIG, $OUTPUT;
  $skin_path = $CONFIG['skin_path'];
  $image_tag = '<img src="%s%s" alt="%s" />';
  // check to see if we have some settings for sorting
  $sort_col   = $_SESSION['sort_col'];
  $sort_order = $_SESSION['sort_order'];
  // add some labels to client
  $OUTPUT->add_label('from', 'to');
  // get message headers
  $a_headers = $IMAP->list_headers('', '', $sort_col, $sort_order);
  // add id to message list table if not specified
  if (!strlen($attrib['id']))
    $attrib['id'] = 'rcubemessagelist';
  // allow the following attributes to be added to the <table> tag
  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
  $out = '<table' . $attrib_str . ">\n";
  // define list of cols to be displayed based on parameter or config
  if (empty($attrib['columns']))
@@ -154,12 +157,10 @@
  else
      $a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns']));
  // store column list in a session-variable
  // save some variables for use in ajax list
  $_SESSION['list_columns'] = $a_show_cols;
  $_SESSION['list_attrib'] = $attrib;
  
  // define sortable columns
  $a_sort_cols = array('subject', 'date', 'from', 'to', 'size');
  $mbox = $IMAP->get_mailbox_name();
  $delim = $IMAP->get_hierarchy_delimiter();
@@ -167,198 +168,19 @@
  if ((strpos($mbox.$delim, $CONFIG['sent_mbox'].$delim)===0 || strpos($mbox.$delim, $CONFIG['drafts_mbox'].$delim)===0)
      && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
    $a_show_cols[$f] = 'to';
  // add col definition
  $out .= '<colgroup>';
  $out .= '<col class="icon" />';
  foreach ($a_show_cols as $col)
    $out .= ($col!='attachment') ? sprintf('<col class="%s" />', $col) : '<col class="icon" />';
  $out .= "</colgroup>\n";
  // add table title
  $out .= "<thead><tr>\n<td class=\"icon\">&nbsp;</td>\n";
  $javascript = '';
  foreach ($a_show_cols as $col)
    {
    // get column name
    switch ($col)
      {
      case 'flag':
        $col_name = sprintf($image_tag, $skin_path, $attrib['unflaggedicon'], '');
        break;
      case 'attachment':
        $col_name = sprintf($image_tag, $skin_path, $attrib['attachmenticon'], '');
        break;
      default:
        $col_name = Q(rcube_label($col));
    }
    // make sort links
    $sort = '';
    if (in_array($col, $a_sort_cols))
      {
      // have buttons configured
      if (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton']))
        {
        $sort = '&nbsp;&nbsp;';
        // asc link
        if (!empty($attrib['sortascbutton']))
          {
          $sort .= $OUTPUT->button(array(
            'command' => 'sort',
            'prop' => $col.'_ASC',
            'image' => $attrib['sortascbutton'],
            'align' => 'absmiddle',
            'title' => 'sortasc'));
          }
        // desc link
        if (!empty($attrib['sortdescbutton']))
          {
          $sort .= $OUTPUT->button(array(
            'command' => 'sort',
            'prop' => $col.'_DESC',
            'image' => $attrib['sortdescbutton'],
            'align' => 'absmiddle',
            'title' => 'sortdesc'));
          }
        }
      // just add a link tag to the header
      else
        {
        $col_name = sprintf(
          '<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>',
          JS_OBJECT_NAME,
          $col,
          rcube_label('sortby'),
          $col_name);
        }
      }
    $sort_class = $col==$sort_col ? " sorted$sort_order" : '';
    // put it all together
    if ($col!='attachment')
      $out .= '<td class="'.$col.$sort_class.'" id="rcm'.$col.'">' . "$col_name$sort</td>\n";
    else
      $out .= '<td class="icon" id="rcm'.$col.'">' . "$col_name$sort</td>\n";
    }
  $out .= "</tr></thead>\n<tbody>\n";
  // no messages in this mailbox
  if (!sizeof($a_headers))
    $OUTPUT->show_message('nomessagesfound', 'notice');
  $a_js_message_arr = array();
  // create row for each message
  foreach ($a_headers as $i => $header)  //while (list($i, $header) = each($a_headers))
    {
    $message_icon = $attach_icon = $flagged_icon = '';
    $js_row_arr = array();
    $zebra_class = $i%2 ? ' even' : ' odd';
    // set messag attributes to javascript array
    if ($header->deleted)
      $js_row_arr['deleted'] = true;
    if (!$header->seen)
      $js_row_arr['unread'] = true;
    if ($header->answered)
      $js_row_arr['replied'] = true;
    if ($header->forwarded)
      $js_row_arr['forwarded'] = true;
    if ($header->flagged)
      $js_row_arr['flagged'] = true;
    // set message icon
    if ($attrib['deletedicon'] && $header->deleted)
      $message_icon = $attrib['deletedicon'];
    else if ($attrib['repliedicon'] && $header->answered)
      {
      if ($attrib['forwardedrepliedicon'] && $header->forwarded)
        $message_icon = $attrib['forwardedrepliedicon'];
      else
        $message_icon = $attrib['repliedicon'];
      }
    else if ($attrib['forwardedicon'] && $header->forwarded)
      $message_icon = $attrib['forwardedicon'];
    else if ($attrib['unreadicon'] && !$header->seen)
      $message_icon = $attrib['unreadicon'];
    else if ($attrib['messageicon'])
      $message_icon = $attrib['messageicon'];
    if ($attrib['flaggedicon'] && $header->flagged)
      $flagged_icon = $attrib['flaggedicon'];
    else if ($attrib['unflaggedicon'] && !$header->flagged)
      $flagged_icon = $attrib['unflaggedicon'];
    // set attachment icon
    if ($attrib['attachmenticon'] && preg_match("/multipart\/m/i", $header->ctype))
      $attach_icon = $attrib['attachmenticon'];
    $out .= sprintf('<tr id="rcmrow%d" class="message%s%s%s%s">'."\n",
                    $header->uid,
                    $header->seen ? '' : ' unread',
                    $header->deleted ? ' deleted' : '',
                    $header->flagged ? ' flagged' : '',
                    $zebra_class);
    $out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : '');
    $IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']);
    // format each col
    foreach ($a_show_cols as $col)
      {
      if (in_array($col, array('from', 'to', 'cc', 'replyto')))
        $cont = Q(rcmail_address_string($header->$col, 3, false, $attrib['addicon']), 'show');
      else if ($col=='subject')
        {
        $action = $mbox==$CONFIG['drafts_mbox'] ? 'compose' : 'show';
        $uid_param = $mbox==$CONFIG['drafts_mbox'] ? '_draft_uid' : '_uid';
        $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160);
        if (empty($cont)) $cont = rcube_label('nosubject');
        $cont = $OUTPUT->browser->ie ? Q($cont) : sprintf('<a href="%s" onclick="return rcube_event.cancel(event)">%s</a>', Q(rcmail_url($action, array($uid_param=>$header->uid, '_mbox'=>$mbox))), Q($cont));
        }
      else if ($col=='flag')
        $cont = $flagged_icon ? sprintf($image_tag, $skin_path, $flagged_icon, '') : '';
      else if ($col=='size')
        $cont = show_bytes($header->$col);
      else if ($col=='date')
        $cont = format_date($header->date);
      else
        $cont = Q($header->$col);
      if ($col!='attachment')
        $out .= '<td class="'.$col.'">' . $cont . "</td>\n";
      else
        $out .= sprintf("<td class=\"icon\">%s</td>\n", $attach_icon ? sprintf($image_tag, $skin_path, $attach_icon, '') : '&nbsp;');
      }
    $out .= "</tr>\n";
    if (sizeof($js_row_arr))
      $a_js_message_arr[$header->uid] = $js_row_arr;
    }
  // complete message table
  $out .= "</tbody></table>\n";
  $message_count = $IMAP->messagecount();
  $skin_path = $_SESSION['skin_path'] = $CONFIG['skin_path'];
  $message_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
  
  // set client env
  $OUTPUT->add_gui_object('mailcontframe', 'mailcontframe');
  $OUTPUT->add_gui_object('messagelist', $attrib['id']);
  $OUTPUT->set_env('autoexpand_threads', intval($CONFIG['autoexpand_threads']));
  $OUTPUT->set_env('messagecount', $message_count);
  $OUTPUT->set_env('current_page', $IMAP->list_page);
  $OUTPUT->set_env('pagecount', ceil($message_count/$IMAP->page_size));
  $OUTPUT->set_env('sort_col', $sort_col);
  $OUTPUT->set_env('sort_order', $sort_order);
  $OUTPUT->set_env('sort_col', $_SESSION['sort_col']);
  $OUTPUT->set_env('sort_order', $_SESSION['sort_order']);
  
  if ($attrib['messageicon'])
    $OUTPUT->set_env('messageicon', $skin_path . $attrib['messageicon']);
@@ -378,22 +200,35 @@
    $OUTPUT->set_env('flaggedicon', $skin_path . $attrib['flaggedicon']);
  if ($attrib['unflaggedicon'])
    $OUTPUT->set_env('unflaggedicon', $skin_path . $attrib['unflaggedicon']);
  if ($attrib['unreadchildrenicon'])
    $OUTPUT->set_env('unreadchildrenicon', $skin_path . $attrib['unreadchildrenicon']);
  
  $OUTPUT->set_env('messages', $a_js_message_arr);
  $OUTPUT->set_env('messages', array());
  $OUTPUT->set_env('coltypes', $a_show_cols);
  if (!$message_count)
    $OUTPUT->show_message('nomessagesfound', 'notice');
  
  $OUTPUT->include_script('list.js');
  
  return $out;
  }
  $thead = '';
  foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell)
    $thead .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
  return html::tag('table',
    $attrib,
    html::tag('thead', null, html::tag('tr', null, $thead)) .
      html::tag('tbody', null, ''),
    array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
}
/**
 * return javascript commands to add rows to the message list
 * or to replace the whole list (IE only)
 */
function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE)
  {
function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE, $head_replace=FALSE)
{
  global $CONFIG, $IMAP, $OUTPUT;
  if (empty($_SESSION['list_columns']))
@@ -409,9 +244,12 @@
      && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
    $a_show_cols[$f] = 'to';
  $browser = new rcube_browser;
  $thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : NULL;
  $OUTPUT->command('set_message_coltypes', $a_show_cols, $thead);
  $OUTPUT->command('set_message_coltypes', $a_show_cols);
  if (empty($a_headers))
    return;
  // remove 'attachment' and 'flag' columns, we don't need them here
  if(($key = array_search('attachment', $a_show_cols)) !== FALSE)
@@ -419,7 +257,7 @@
  if(($key = array_search('flag', $a_show_cols)) !== FALSE)
    unset($a_show_cols[$key]);
  if ($browser->ie && $replace)
  if ($OUTPUT->browser->ie && $replace)
    $OUTPUT->command('offline_message_list', true);
  // loop through message headers
@@ -440,10 +278,9 @@
        $cont = Q(rcmail_address_string($header->$col, 3), 'show');
      else if ($col=='subject')
        {
    $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160);
        $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160);
        if (!$cont) $cont = rcube_label('nosubject');
    $cont = Q($cont);
    $a_msg_cols['mbox'] = $mbox;
        $cont = Q($cont);
        }
      else if ($col=='size')
        $cont = show_bytes($header->$col);
@@ -455,6 +292,14 @@
      $a_msg_cols[$col] = $cont;
      }
    if ($header->depth)
      $a_msg_flags['depth'] = $header->depth;
    if ($header->parent_uid)
      $a_msg_flags['parent_uid'] = $header->parent_uid;
    if ($header->has_children)
      $a_msg_flags['has_children'] = $header->has_children;
    if ($header->unread_children)
      $a_msg_flags['unread_children'] = $header->unread_children;
    if ($header->deleted)
      $a_msg_flags['deleted'] = 1;
    if (!$header->seen)
@@ -465,18 +310,75 @@
      $a_msg_flags['forwarded'] = 1;
    if ($header->flagged)
      $a_msg_flags['flagged'] = 1;
    if(preg_match("/multipart\/m/i", $header->ctype))
      $a_msg_flags['attachment'] = 1;
    $a_msg_flags['mbox'] = $mbox;
    $OUTPUT->command('add_message_row',
      $header->uid,
      $a_msg_cols,
      $a_msg_flags,
      preg_match("/multipart\/m/i", $header->ctype),
      $insert_top);
    }
  if ($browser->ie && $replace)
    $OUTPUT->command('offline_message_list', false);
  }
/*
 * Creates <THEAD> for message list table
 */
function rcmail_message_list_head($attrib, $a_show_cols)
{
  global $CONFIG;
  $skin_path = $_SESSION['skin_path'];
  $image_tag = html::img(array('src' => "%s%s", 'alt' => "%s"));
  // check to see if we have some settings for sorting
  $sort_col   = $_SESSION['sort_col'];
  $sort_order = $_SESSION['sort_order'];
  // define sortable columns
  $a_sort_cols = array('subject', 'date', 'from', 'to', 'size', 'cc');
  if (!empty($attrib['optionsmenuicon']))
    $list_menu = html::a(
      array('href' => '#', 'onclick' => 'return '.JS_OBJECT_NAME.".command('menu-open', 'messagelistmenu')"),
      html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], 'id' => 'listmenulink', 'title' => rcube_label('listoptions')))
    );
  else
    $list_menu = '';
  $cells = array(array('className' => 'threads', 'html' => $list_menu));
  foreach ($a_show_cols as $col) {
    // get column name
    switch ($col) {
      case 'flag':
        $col_name = sprintf($image_tag, $skin_path, $attrib['unflaggedicon'], '');
        break;
      case 'attachment':
        $col_name = sprintf($image_tag, $skin_path, $attrib['attachmenticon'], '');
        break;
      default:
        $col_name = Q(rcube_label($col));
    }
    // make sort links
    if (in_array($col, $a_sort_cols))
      $col_name = html::a(array('href'=>"./#sort", 'onclick' => 'return '.JS_OBJECT_NAME.".command('sort','".$col."',this)", 'title' => rcube_label('sortby')), $col_name);
    $sort_class = $col == $sort_col ? " sorted$sort_order" : '';
    $class_name = $col == 'attachment' ? 'icon' : $col.$sort_class;
    // put it all together
    $cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name);
  }
  return $cells;
}
/**
@@ -513,7 +415,7 @@
function rcmail_quota_display($attrib)
  {
  global $OUTPUT, $COMM_PATH;
  global $OUTPUT;
  if (!$attrib['id'])
    $attrib['id'] = 'rcmquotadisplay';
@@ -582,23 +484,23 @@
  if (isset($MESSAGE->index))
    {
    return rcube_label(array('name' => 'messagenrof',
                             'vars' => array('nr'  => $MESSAGE->index+1,
                                             'count' => $count!==NULL ? $count : $IMAP->messagecount())));
        'vars' => array('nr'  => $MESSAGE->index+1,
        'count' => $count!==NULL ? $count : $IMAP->messagecount(NULL, 'ALL')))); // Only messages, no threads here
    }
  if ($page===NULL)
    $page = $IMAP->list_page;
    
  $start_msg = ($page-1) * $IMAP->page_size + 1;
  $max = $count!==NULL ? $count : $IMAP->messagecount();
  $max = $count!==NULL ? $count : $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
  if ($max==0)
    $out = rcube_label('mailboxempty');
  else
    $out = rcube_label(array('name' => 'messagesfromto',
                              'vars' => array('from'  => $start_msg,
                                              'to'    => min($max, $start_msg + $IMAP->page_size - 1),
                                              'count' => $max)));
    $out = rcube_label(array('name' => $IMAP->threading ? 'threadsfromto' : 'messagesfromto',
            'vars' => array('from'  => $start_msg,
            'to'    => min($max, $start_msg + $IMAP->page_size - 1),
            'count' => $max)));
  return Q($out);
  }
program/steps/mail/list.inc
@@ -33,8 +33,6 @@
  $save_arr = array();
  $_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col;
  $_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order;
  $RCMAIL->user->save_prefs($save_arr);
}
else
{
@@ -42,6 +40,16 @@
  $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col'];
  $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];
}
// is there a set of columns for this request?
if ($cols = get_input_value('_cols', RCUBE_INPUT_GET))
{
  $save_arr = array();
  $_SESSION['list_columns'] = $save_arr['list_cols'] = explode(',', $cols);
}
if ($save_arr)
  $RCMAIL->user->save_prefs($save_arr);
$mbox_name = $IMAP->get_mailbox_name();
@@ -55,8 +63,12 @@
}
// fetch message headers
if ($count = $IMAP->messagecount($mbox_name, 'ALL', !empty($_REQUEST['_refresh'])))
if ($count = $IMAP->messagecount($mbox_name, $IMAP->threading ? 'THREADS' : 'ALL', !empty($_REQUEST['_refresh'])))
  $a_headers = $IMAP->list_headers($mbox_name, NULL, $sort_col, $sort_order);
// update search set (possible change of threading mode)
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
  $_SESSION['search'][$_REQUEST['_search']] = $IMAP->get_search_set();
// update mailboxlist
rcmail_send_unread_count($mbox_name, !empty($_REQUEST['_refresh']));
@@ -65,13 +77,14 @@
$pages = ceil($count/$IMAP->page_size);
$OUTPUT->set_env('messagecount', $count);
$OUTPUT->set_env('pagecount', $pages);
$OUTPUT->set_env('threading', (bool) $IMAP->threading);
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count));
$OUTPUT->command('set_mailboxname', rcmail_get_mailbox_name_text());
// add message rows
rcmail_js_message_list($a_headers, FALSE, TRUE, (bool) $cols);
if (isset($a_headers) && count($a_headers))
{
  rcmail_js_message_list($a_headers);
  if ($search_request)
    $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count));
}
program/steps/mail/mark.inc
@@ -36,7 +36,7 @@
  if ($flag == 'DELETED' && $CONFIG['skip_deleted'] && $_POST['_from'] != 'show') {
    // count messages before changing anything
    $old_count = $IMAP->messagecount();
    $old_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
    $old_pages = ceil($old_count / $IMAP->page_size);
    $count = sizeof(explode(',', $uids));
  }
@@ -75,7 +75,7 @@
        $_SESSION['search'][$search_request] = $IMAP->refresh_search();
      }
      $msg_count      = $IMAP->messagecount();
      $msg_count      = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
      $pages          = ceil($msg_count / $IMAP->page_size);
      $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page;
      $remaining      = $msg_count - $IMAP->page_size * ($IMAP->list_page - 1);
@@ -103,8 +103,11 @@
      }
      $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count));
      if ($IMAP->threading)
    $count = get_input_value('_count', RCUBE_INPUT_POST);
      // add new rows from next page (if any)
      if (($jump_back || $nextpage_count > 0)) {
      if ($count && ($jump_back || $nextpage_count > 0)) {
        $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col'];
        $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];
  
program/steps/mail/move_del.inc
@@ -24,7 +24,7 @@
  return;
// count messages before changing anything
$old_count = $IMAP->messagecount();
$old_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
$old_pages = ceil($old_count / $IMAP->page_size);
// move messages
@@ -50,6 +50,7 @@
else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) {
    $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST))));
    $mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
    $del = $IMAP->delete_message($uids, $mbox);
  
    if (!$del) {
@@ -82,7 +83,7 @@
}
else
{
  $msg_count      = $IMAP->messagecount();
  $msg_count      = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
  $pages          = ceil($msg_count / $IMAP->page_size);
  $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page;
  $remaining      = $msg_count - $IMAP->page_size * ($IMAP->list_page - 1);
@@ -116,8 +117,11 @@
  $OUTPUT->command('set_quota', rcmail_quota_content());
  $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count));
  if ($IMAP->threading)
    $count = get_input_value('_count', RCUBE_INPUT_POST);
  // add new rows from next page (if any)
  if ($addrows && ($jump_back || $nextpage_count > 0)) {
  if ($addrows && $count && ($jump_back || $nextpage_count > 0)) {
    $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col'];
    $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];
program/steps/mail/search.inc
@@ -104,7 +104,7 @@
// Get the headers
$result_h = $IMAP->list_headers($mbox, 1, $_SESSION['sort_col'], $_SESSION['sort_order']);
$count = $IMAP->messagecount();
$count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
// save search results in session
if (!is_array($_SESSION['search']))
@@ -120,7 +120,7 @@
{
  rcmail_js_message_list($result_h);
  if ($search_str)
    $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count));
    $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $IMAP->messagecount(NULL, 'ALL')));
}
else
{
program/steps/mail/show.inc
@@ -104,7 +104,7 @@
    $next = $prev = $first = $last = -1;
    if ($_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] != 'DESC'
        && empty($_REQUEST['_search']) && !$IMAP->skip_deleted)
        && empty($_REQUEST['_search']) && !$CONFIG['skip_deleted'] && !$IMAP->threading)
      {
      // this assumes that we are sorted by date_DESC
      $cnt = $IMAP->messagecount();
@@ -142,7 +142,7 @@
  if (!$MESSAGE->headers->seen)
    $RCMAIL->plugins->exec_hook('message_read', array('uid' => $MESSAGE->uid,
      'mailbox' => $IMAP->mailbox, 'message' => $MESSAGE));
      'mailbox' => $mbox_name, 'message' => $MESSAGE));
}
program/steps/mail/viewsource.inc
@@ -25,7 +25,7 @@
if ($uid = get_input_value('_uid', RCUBE_INPUT_GET))
{
  $headers = $IMAP->get_headers($uid);
  $charset = $headers->charset ? $headers->charset : $IMAP->default_charset;
  $charset = $headers->charset ? $headers->charset : $CONFIG['default_charset'];
  header("Content-Type: text/plain; charset={$charset}");
  if (!empty($_GET['_save'])) {
program/steps/settings/func.inc
@@ -248,17 +248,6 @@
      );
    }
    // Show checkbox for toggling 'index_sort'
    if (!isset($no_override['index_sort'])) {
      $field_id = 'rcmfd_indexsort';
      $input_indexsort = new html_checkbox(array('name' => '_index_sort', 'id' => $field_id, 'value' => 1));
      $blocks['list']['options']['index_sort'] = array(
        'title' => html::label($field_id, Q(rcube_label('indexsort'))),
        'content' => $input_indexsort->show($config['index_sort']?1:0),
      );
    }
    // show drop-down for available skins
    if (!isset($no_override['skin'])) {
      $skins = rcmail_get_skins();
@@ -311,6 +300,19 @@
      );
    }
    if (!isset($no_override['autoexpand_threads'])) {
      $field_id = 'rcmfd_autoexpand_threads';
      $select_autoexpand_threads = new html_select(array('name' => '_autoexpand_threads', 'id' => $field_id));
      $select_autoexpand_threads->add(rcube_label('never'), 0);
      $select_autoexpand_threads->add(rcube_label('do_expand'), 1);
      $select_autoexpand_threads->add(rcube_label('expand_only_unread'), 2);
      $blocks['main']['options']['autoexpand_threads'] = array(
    'title' => html::label($field_id, Q(rcube_label('autoexpand_threads'))),
    'content' => $select_autoexpand_threads->show($config['autoexpand_threads']),
      );
    }
    if (!isset($no_override['focus_on_new_message'])) {
      $field_id = 'rcmfd_focus_on_new_message';
      $input_focus_on_new_message = new html_checkbox(array('name' => '_focus_on_new_message', 'id' => $field_id, 'value' => 1));
program/steps/settings/manage_folders.inc
@@ -38,6 +38,20 @@
    $IMAP->unsubscribe(array($mbox));
  }
// enable threading for one or more mailboxes
else if ($RCMAIL->action=='enable-threading')
  {
  if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF7-IMAP'))
    rcube_set_threading($mbox, true);
  }
// enable threading for one or more mailboxes
else if ($RCMAIL->action=='disable-threading')
  {
  if ($mbox = get_input_value('_mbox', RCUBE_INPUT_POST, false, 'UTF7-IMAP'))
    rcube_set_threading($mbox, false);
  }
// create a new mailbox
else if ($RCMAIL->action=='create-folder')
  {
@@ -77,6 +91,24 @@
    $oldname = rcube_charset_convert($oldname_utf8, RCMAIL_CHARSET, 'UTF7-IMAP');
    $rename = $IMAP->rename_mailbox($oldname, $name);
    }
  // update per-folder options for modified folder and its subfolders
  if ($rename) {
    $a_threaded = $RCMAIL->config->get('message_threading', array());
    $delimiter = $IMAP->get_hierarchy_delimiter();
    $oldprefix = '/^' . preg_quote($oldname . $delimiter, '/') . '/';
    foreach ($a_threaded as $key => $val)
      if ($key == $oldname) {
        unset($a_threaded[$key]);
    $a_threaded[$name] = true;
        }
      else if (preg_match($oldprefix, $key)) {
        unset($a_threaded[$key]);
    $a_threaded[preg_replace($oldprefix, $name.$delimiter, $key)] = true;
      }
    $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded));
    }
  if ($rename && $OUTPUT->ajax_call)
@@ -159,7 +191,11 @@
// build table with all folders listed by server
function rcube_subscription_form($attrib)
  {
  global $IMAP, $CONFIG, $OUTPUT;
  global $RCMAIL, $IMAP, $CONFIG, $OUTPUT;
  $threading_supported = $IMAP->get_capability('thread=references')
    || $IMAP->get_capability('thread=orderedsubject')
    || $IMAP->get_capability('thread=refs');
  list($form_start, $form_end) = get_form_tags($attrib, 'folders');
  unset($attrib['form']);
@@ -173,15 +209,17 @@
  $table->add_header('name', rcube_label('foldername'));
  $table->add_header('msgcount', rcube_label('messagecount'));
  $table->add_header('subscribed', rcube_label('subscribed'));
  if ($threading_supported)
    $table->add_header('threaded', rcube_label('threaded'));
  $table->add_header('rename', '&nbsp;');
  $table->add_header('delete', '&nbsp;');
  // get folders from server
  $IMAP->clear_cache('mailboxes');
  $a_unsubscribed = $IMAP->list_unsubscribed();
  $a_subscribed = $IMAP->list_mailboxes();
  $a_threaded = $a_threaded_copy = $RCMAIL->config->get('message_threading', array());
  $delimiter = $IMAP->get_hierarchy_delimiter();
  $a_js_folders = $seen_folders = $list_folders = array();
@@ -203,13 +241,27 @@
      }
    }
    
    unset($a_threaded_copy[$folder]);
    $list_folders[] = array('id' => $folder, 'name' => $name, 'level' => $level);
    $seen[$folder]++;
  }
  // remove 'message_threading' option for not existing folders
  if ($a_threaded_copy) {
    foreach ($a_threaded_copy as $key => $val)
      unset($a_threaded[$key]);
    unset($a_threaded_copy);
    $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded));
  }
  $checkbox_subscribe = new html_checkbox(array(
    'name' => '_subscribed[]',
    'onclick' => JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)",
  ));
  $checkbox_threaded = new html_checkbox(array(
    'name' => '_threaded[]',
    'onclick' => JS_OBJECT_NAME.".command(this.checked?'enable-threading':'disable-threading',this.value)",
  ));
  
  if (!empty($attrib['deleteicon']))
@@ -226,6 +278,7 @@
  foreach ($list_folders as $i => $folder) {
    $idx = $i + 1;
    $subscribed = in_array($folder['id'], $a_subscribed);
    $threaded = $a_threaded[$folder['id']];
    $protected = ($CONFIG['protect_default_folders'] == true && in_array($folder['id'], $CONFIG['default_imap_folders']));
    $classes = array($i%2 ? 'even' : 'odd');
    $folder_js = JQ($folder['id']);
@@ -238,9 +291,13 @@
    $table->add_row(array('id' => 'rcmrow'.$idx, 'class' => join(' ', $classes)));
    
    $table->add('name', Q($display_folder));
    $table->add('msgcount', ($folder['virtual'] ? '' : $IMAP->messagecount($folder['id'])));
    $table->add('msgcount', ($folder['virtual'] ? '' : $IMAP->messagecount($folder['id']))); // XXX: Use THREADS or ALL?
    $table->add('subscribed', ($protected || $folder['virtual']) ? ($subscribed ? '&nbsp;&#x2022;' : '&nbsp;') :
        $checkbox_subscribe->show(($subscribed ? $folder_utf8 : ''), array('value' => $folder_utf8)));
    if ($IMAP->get_capability('thread=references')) {
      $table->add('threaded',
                  $checkbox_threaded->show(($threaded ? $folder_utf8 : ''), array('value' => $folder_utf8)));
    }
    
    // add rename and delete buttons
    if (!$protected && !$folder['virtual']) {
@@ -335,6 +392,27 @@
  return $out;
  }
// (un)set 'threading' for selected folder
function rcube_set_threading($mbox, $state=true)
  {
  global $RCMAIL;
  $mbox = (array)$mbox;
  $a_prefs = (array)$RCMAIL->config->get('message_threading');
  if ($state) {
    foreach ($mbox as $box)
      $a_prefs[$box] = true;
    }
  else {
    foreach ($mbox as $box)
      unset($a_prefs[$box]);
    }
  $RCMAIL->user->save_prefs(array('message_threading' => $a_prefs));
  }
$OUTPUT->set_pagetitle(rcube_label('folders'));
$OUTPUT->include_script('list.js');
program/steps/settings/save_prefs.inc
@@ -32,7 +32,6 @@
      'timezone'     => isset($_POST['_timezone']) ? (is_numeric($_POST['_timezone']) ? floatval($_POST['_timezone']) : get_input_value('_timezone', RCUBE_INPUT_POST)) : $CONFIG['timezone'],
      'dst_active'   => isset($_POST['_dst_active']) ? TRUE : FALSE,
      'pagesize'     => is_numeric($_POST['_pagesize']) ? max(2, intval($_POST['_pagesize'])) : $CONFIG['pagesize'],
      'index_sort'   => isset($_POST['_index_sort']) ? TRUE : FALSE,
      'prettydate'   => isset($_POST['_pretty_date']) ? TRUE : FALSE,
      'skin'          => isset($_POST['_skin']) ? get_input_value('_skin', RCUBE_INPUT_POST) : $CONFIG['skin'],
    );
@@ -42,6 +41,7 @@
    $a_user_prefs = array(
      'focus_on_new_message' => isset($_POST['_focus_on_new_message']) ? TRUE : FALSE,
      'preview_pane'          => isset($_POST['_preview_pane']) ? TRUE : FALSE,
      'autoexpand_threads'   => isset($_POST['_autoexpand_threads']) ? intval($_POST['_autoexpand_threads']) : 0,
      'mdn_requests'         => isset($_POST['_mdn_requests']) ? intval($_POST['_mdn_requests']) : 0,
      'keep_alive'           => isset($_POST['_keep_alive']) ? intval($_POST['_keep_alive'])*60 : $CONFIG['keep_alive'],
      'check_all_folders'    => isset($_POST['_check_all_folders']) ? TRUE : FALSE,
skins/default/common.css
@@ -537,7 +537,7 @@
ul.toolbarmenu
{
  margin: 0;
  margin: -4px 0 -4px 0;
  padding: 0;
  list-style: none;
}
@@ -547,13 +547,14 @@
  font-size: 11px;
  white-space: nowrap;
  min-width: 130px;
  margin: 3px -4px;
}
ul.toolbarmenu li a
{
  display: block;
  color: #a0a0a0;
  padding: 2px 8px 3px 22px;
  padding: 2px 12px 3px 28px;
  text-decoration: none;
  min-height: 14px;
}
@@ -597,3 +598,8 @@
  margin-top: 2px;
  padding-top: 2px;
}
.disabled
{
  color: #999;
}
skins/default/functions.js
@@ -124,6 +124,7 @@
  this.markmenu = $('#markmessagemenu');
  this.searchmenu = $('#searchmenu');
  this.messagemenu = $('#messagemenu');
  this.listmenu = $('#listmenu');
}
rcube_mail_ui.prototype = {
@@ -186,15 +187,89 @@
    rcmail.env.search_mods[rcmail.env.mailbox][elem.value] = elem.value;
},
show_listmenu: function(show)
{
  if (typeof show == 'undefined')
    show = this.listmenu.is(':visible') ? false : true;
  var ref = rcube_find_object('listmenulink');
  if (show && ref) {
    var pos = $(ref).offset();
    this.listmenu.css({ left:pos.left, top:(pos.top + ref.offsetHeight + 2)});
    // set form values
    $('input[name="sort_col"][value="'+rcmail.env.sort_col+'"]').attr('checked', 1);
    $('input[name="sort_ord"][value="DESC"]').attr('checked', rcmail.env.sort_order=='DESC' ? 1 : 0);
    $('input[name="sort_ord"][value="ASC"]').attr('checked', rcmail.env.sort_order=='DESC' ? 0 : 1);
    $('input[name="view"][value="thread"]').attr('checked', rcmail.env.threading ? 1 : 0);
    $('input[name="view"][value="list"]').attr('checked', rcmail.env.threading ? 0 : 1);
    // list columns
    var cols = $('input[name="list_col[]"]');
    for (var i=0; i<cols.length; i++) {
      var found = 0;
      if (cols[i].value != 'from')
        found = jQuery.inArray(cols[i].value, rcmail.env.coltypes) != -1;
      else
        found = (jQuery.inArray('from', rcmail.env.coltypes) != -1
        || jQuery.inArray('to', rcmail.env.coltypes) != -1);
      $(cols[i]).attr('checked',found ? 1 : 0);
    }
  }
  this.listmenu[show?'show':'hide']();
  if (show) {
    var maxheight=0;
    $('#listmenu fieldset').each(function() {
      var height = $(this).height();
      if (height > maxheight) {
        maxheight = height;
      }
    });
    $('#listmenu fieldset').css("min-height", maxheight+"px")
    // IE6 complains if you set this attribute using either method:
    //$('#listmenu fieldset').css({'height':'auto !important'});
    //$('#listmenu fieldset').css("height","auto !important");
      .height(maxheight);
  };
},
open_listmenu: function(e)
{
  this.show_listmenu();
},
save_listmenu: function()
{
  this.show_listmenu();
  var sort = $('input[name="sort_col"]:checked').val();
  var ord = $('input[name="sort_ord"]:checked').val();
  var thread = $('input[name="view"]:checked').val();
  var cols = $('input[name="list_col[]"]:checked')
    .map(function(){ return this.value; }).get();
  rcmail.set_list_options(cols, sort, ord, thread == 'thread' ? 1 : 0);
},
body_mouseup: function(evt, p)
{
  if (this.markmenu && this.markmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('markreadbutton'))
  var target = rcube_event.get_target(evt);
  if (this.markmenu && this.markmenu.is(':visible') && target != rcube_find_object('markreadbutton'))
    this.show_markmenu(false);
  else if (this.messagemenu && this.messagemenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('messagemenulink'))
  else if (this.messagemenu && this.messagemenu.is(':visible') && target != rcube_find_object('messagemenulink'))
    this.show_messagemenu(false);
  else if (this.searchmenu && this.searchmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('searchmod')) {
  else if (this.listmenu && this.listmenu.is(':visible') && target != rcube_find_object('listmenulink')) {
    var menu = rcube_find_object('listmenu');
    while (target.parentNode) {
      if (target.parentNode == menu)
        return;
      target = target.parentNode;
    }
    this.show_listmenu(false);
  }
  else if (this.searchmenu && this.searchmenu.is(':visible') && target != rcube_find_object('searchmod')) {
    var menu = rcube_find_object('searchmenu');
    var target = rcube_event.get_target(evt);
    while (target.parentNode) {
      if (target.parentNode == menu)
        return;
@@ -213,6 +288,8 @@
      this.show_searchmenu(false);
    if (this.messagemenu && this.messagemenu.is(':visible'))
      this.show_messagemenu(false);
    if (this.listmenu && this.listmenu.is(':visible'))
      this.show_listmenu(false);
  }
}
@@ -225,4 +302,6 @@
  rcmail_ui = new rcube_mail_ui();
  rcube_event.add_listener({ object:rcmail_ui, method:'body_mouseup', event:'mouseup' });
  rcube_event.add_listener({ object:rcmail_ui, method:'body_keypress', event:'keypress' });
  rcmail.addEventListener('menu-open', 'open_listmenu', rcmail_ui);
  rcmail.addEventListener('menu-save', 'save_listmenu', rcmail_ui);
}
skins/default/ie6hacks.css
@@ -14,7 +14,9 @@
  background-image: url('images/display/icons.gif');
}
#messagemenu li a
#messagemenu li a,
#messagelist tr td div.expanded,
#messagelist tr td div.collapsed
{
  background-image: url('images/messageactions.gif');
}
@@ -47,13 +49,12 @@
  background-image: url('images/abook_toolbar.gif');
}
ul.toolbarmenu li
{
  width: auto;
  border: 1px solid #f6f6f6;
}
ul.toolbarmenu li a
{
  clear: left;
}
ul.toolbarmenu li.separator_below
{
  padding-bottom: 8px;
}
skins/default/iehacks.css
@@ -24,13 +24,9 @@
  filter: alpha(opacity=85);
}
#markmessagemenu,
#searchmenu,
#messagemenu
.popupmenu
{
  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)";
  filter: alpha(opacity=90);
    background-color: #ffffff;
}
#tabsbar
@@ -160,6 +156,11 @@
  border-collapse: collapse;
}
#messagelist tbody tr.unroot td.subject
{
  text-decoration: underline;
}
#messageframe
{
  width: expression((parseInt(this.parentNode.offsetWidth)-180)+'px');
@@ -243,6 +244,11 @@
  filter: alpha(opacity=70);
}
ul.toolbarmenu
{
  margin: 0 0 -4px 0;
}
ul.toolbarmenu li
{
  min-width: auto;
@@ -263,3 +269,9 @@
{
  height: 19px;
}
#listmenu fieldset
{
  margin: 0 4px;
  padding: 0.8em;
}
skins/default/images/icons/columnpicker.gif
skins/default/images/icons/unread_children.png
skins/default/images/mail_footer.png

skins/default/images/messageactions.gif

skins/default/images/messageactions.png

skins/default/includes/messagemenu.html
@@ -1,4 +1,4 @@
<div id="messagemenu">
<div id="messagemenu" class="popupmenu">
  <ul class="toolbarmenu">
    <li><roundcube:button class="printlink" command="print" label="printmessage" classAct="printlink active" /></li>
    <li><roundcube:button class="downloadlink" command="download" label="emlsave" classAct="downloadlink active" /></li>
skins/default/mail.css
@@ -164,25 +164,38 @@
  padding-left: 2px;
}
#markmessagemenu,
#searchmenu,
#messagemenu
.popupmenu
{
  position: absolute;
  top: 32px;
  left: 90px;
  width: auto;
  display: none;
  background-color: #F9F9F9;
  border: 1px solid #CCC;
  padding: 1px;
  opacity: 0.9;
  background-color: #fff;
  background-color: rgba(255, 255, 255, 0.95);
  border: 1px solid #999;
  padding: 4px;
  z-index: 240;
  border-radius: 3px;
  -moz-border-radius: 3px;
  -webkit-border-radius: 3px;
  -moz-box-shadow: 1px 1px 12px #999;
  -webkit-box-shadow: #999 1px 1px 12px;
}
#searchmenu
{
  width: 172px;
  width: 160px;
}
#searchmenu ul.toolbarmenu
{
  margin: 0;
}
#searchmenu ul.toolbarmenu li
{
  margin: 1px 4px 1px;
}
#messagemenu li a.active:hover,
@@ -194,53 +207,53 @@
#messagemenu li a
{
  background: url('images/messageactions.png') no-repeat 1px 0;
  background-position: 0px 20px;
  background: url('images/messageactions.png') no-repeat 7px 0;
  background-position: 7px 20px;
}
#messagemenu li a.printlink
{
  background-position: 1px 1px;
  background-position: 7px 1px;
}
#messagemenu li a.downloadlink
{
  background-position: 1px -17px;
  background-position: 7px -17px;
}
#messagemenu li a.sourcelink
{
  background-position: 1px -35px;
  background-position: 7px -35px;
}
#messagemenu li a.openlink
{
  background-position: 1px -53px;
  background-position: 7px -53px;
}
#messagemenu li a.editlink
{
  background-position: 1px -71px;
  background-position: 7px -71px;
}
#markmessagemenu a.readlink
{
  background: url('images/icons/dot.png') no-repeat 2px;
  background: url('images/icons/dot.png') no-repeat 7px 2px;
}
#markmessagemenu a.unreadlink
{
  background: url('images/icons/unread.png') no-repeat 2px;
  background: url('images/icons/unread.png') no-repeat 7px 2px;
}
#markmessagemenu a.flaggedlink
{
  background: url('images/icons/flagged.png') no-repeat 2px;
  background: url('images/icons/flagged.png') no-repeat 7px 2px;
}
#markmessagemenu a.unflaggedlink
{
  background: url('images/icons/unflagged.png') no-repeat 2px;
  background: url('images/icons/unflagged.png') no-repeat 7px 2px;
}
#searchfilter
@@ -613,6 +626,30 @@
  background-position: -75px -15px;
}
#listcontrols a.expand-all {
  background-position: -90px 0;
}
#listcontrols a.expand-allsel {
  background-position: -90px -15px;
}
#listcontrols a.collapse-all {
  background-position: -105px 0;
}
#listcontrols a.collapse-allsel {
  background-position: -105px -15px;
}
#listcontrols a.expand-unread {
  background-position: -120px 0;
}
#listcontrols a.expand-unreadsel {
  background-position: -120px -15px;
}
#countcontrols
{
  height: 15px;
@@ -720,20 +757,69 @@
  vertical-align: middle;
}
#messagelist thead tr td.subject
{
  padding-left: 22px;
}
#messagelist thead tr td.icon,
#messagelist thead tr td.flag
#messagelist thead tr td.flag,
#messagelist thead tr td.threads
{
  width: 22px;
  padding: 0;
  text-align: center;
}
#messagelist tbody tr td.icon,
#messagelist thead tr td.threads
{
  width: 18px;
}
#messagelist tbody tr td.flag
{
  padding: 2px 3px 2px 3px;
  vertical-align: middle;
  cursor: pointer;
}
#messagelist tr td span.branch
{
  display: inline-block;
  width: 15px;
  height: 15px;
}
#messagelist tr td.subject img.msgicon
{
  vertical-align: middle;
}
#messagelist tbody td img.msgicon
{
  position: relative;
  top: 0px;
  margin-right: 5px;
}
#messagelist tr td div.collapsed,
#messagelist tr td div.expanded,
#messagelist tr td img.flagicon,
#messagelist tr td img.msgicon
{
  cursor: pointer;
}
#messagelist tr td div.collapsed
{
  display: block;
  background: url('images/messageactions.png') center -91px no-repeat;
}
#messagelist tr td div.expanded
{
  display: block;
  background: url('images/messageactions.png') center -109px no-repeat;
}
#messagelist tbody tr td.flag img:hover,
@@ -747,6 +833,12 @@
  overflow: hidden;
  vertical-align: middle;
  width: 99%;
}
/* thread parent message with unread children */
#messagelist tbody tr.unroot td.subject a
{
  text-decoration: underline;
}
#messagelist tr td.size
@@ -1267,3 +1359,27 @@
{
  font-weight: bold;
}
#listmenu
{
  padding: 6px;
}
#listmenu legend
{
  color: #999999;
}
#listmenu fieldset
{
  border: 1px solid #999999;
  margin: 0 5px;
  float: left;
}
#listmenu div
{
  padding: 8px 0 3px 0;
  text-align: center;
  clear: both;
}
skins/default/templates/mail.html
@@ -59,7 +59,9 @@
  forwardedrepliedIcon="/images/icons/forwarded_replied.png"
  attachmentIcon="/images/icons/attachment.png"
  flaggedIcon="/images/icons/flagged.png"
  unflaggedIcon="/images/icons/blank.gif" />
  unflaggedIcon="/images/icons/blank.gif"
  unreadchildrenIcon=""
  optionsmenuIcon="/images/icons/columnpicker.gif" />
</div>
<roundcube:if condition="config:preview_pane == true" />
@@ -82,6 +84,10 @@
      <roundcube:button command="select-all" type="link" prop="unread" title="unread" class="buttonPas unread" classAct="button unread" classSel="button unreadsel" content=" " />
      <roundcube:button command="select-all" type="link" prop="invert" title="invert" class="buttonPas invert" classAct="button invert" classSel="button invertsel" content=" " />
      <roundcube:button command="select-none" type="link" title="none" class="buttonPas none" classAct="button none" classSel="button nonesel" content=" " />
      <span style="margin-left: 20px"><roundcube:label name="threads" />:&nbsp;</span>
      <roundcube:button command="expand-all" type="link" title="expand-all" class="buttonPas expand-all" classAct="button expand-all" classSel="button expand-allsel" content=" "  />
      <roundcube:button command="expand-unread" type="link" title="expand-unread" class="buttonPas expand-unread" classAct="button expand-unread" classSel="button expand-unreadsel" content=" " />
      <roundcube:button command="collapse-all" type="link" title="collapse-all" class="buttonPas collapse-all" classAct="button collapse-all" classSel="button collapse-allsel" content=" " />
      <roundcube:container name="listcontrols" id="listcontrols" />
  <roundcube:if condition="env:quota" />
  <span style="margin-left: 20px; margin-right: 5px"><roundcube:label name="quota" />:</span>
@@ -111,7 +117,7 @@
<roundcube:button name="markreadbutton" id="markreadbutton" type="link" class="button markmessage" title="markmessages" onclick="rcmail_ui.show_markmenu();return false" content=" " />
<roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button messagemenu" title="messageactions" onclick="rcmail_ui.show_messagemenu();return false" content=" " />
<div id="markmessagemenu">
<div id="markmessagemenu" class="popupmenu">
  <ul class="toolbarmenu">
    <li><roundcube:button command="mark" prop="read" label="markread" classAct="readlink active" class="readlink" /></li>
    <li><roundcube:button command="mark" prop="unread" label="markunread" classAct="unreadlink active" class="unreadlink" /></li>
@@ -125,7 +131,7 @@
</div>
<div id="searchmenu">
<div id="searchmenu" class="popupmenu">
  <ul class="toolbarmenu">
    <li><input type="checkbox" name="s_mods[]" value="subject" id="s_mod_subject" onclick="rcmail_ui.set_searchmod(this)" /><label for="s_mod_subject"><roundcube:label name="subject" /></label></li>
    <li><input type="checkbox" name="s_mods[]" value="from" id="s_mod_from" onclick="rcmail_ui.set_searchmod(this)" /><label for="s_mod_from"><roundcube:label name="from" /></label></li>
@@ -146,5 +152,52 @@
<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
</div>
<div id="listmenu" class="popupmenu">
<fieldset class="thinbordered"><legend><roundcube:label name="listmode" /></legend>
  <ul class="toolbarmenu">
    <li><input type="radio" name="view" value="list" id="view_default" /><label for="view_default"><roundcube:label name="list" /></label></li>
    <roundcube:if condition="env:threads" />
      <li><input type="radio" name="view" value="thread" id="view_thread" /><label for="view_thread"><roundcube:label name="threads" /></label></li>
    <roundcube:else />
      <li><input type="radio" name="view" value="thread" id="view_thread" disabled="disabled" /><label for="view_thread" class="disabled"><roundcube:label name="threads" /></label></li>
    <roundcube:endif />
  </ul>
</fieldset>
<fieldset class="thinbordered"><legend><roundcube:label name="listcolumns" /></legend>
  <ul class="toolbarmenu">
    <li><input type="checkbox" name="list_col[]" value="flag" id="cols_flag" /><label for="cols_flag"><roundcube:label name="flag" /></label></li>
    <li><input type="checkbox" name="list_col[]" value="subject" id="cols_subject" checked="checked" disabled="disabled" /><label for="cols_subject" class="disabled"><roundcube:label name="subject" /></label></li>
    <li><input type="checkbox" name="list_col[]" value="from" id="cols_fromto" /><label for="cols_fromto"><roundcube:label name="fromto" /></label></li>
    <li><input type="checkbox" name="list_col[]" value="replyto" id="cols_replyto" /><label for="cols_replyto"><roundcube:label name="replyto" /></label></li>
    <li><input type="checkbox" name="list_col[]" value="cc" id="cols_cc" /><label for="cols_cc"><roundcube:label name="cc" /></label></li>
    <li><input type="checkbox" name="list_col[]" value="date" id="cols_date" /><label for="cols_date"><roundcube:label name="date" /></label></li>
    <li><input type="checkbox" name="list_col[]" value="size" id="cols_size" /><label for="cols_size"><roundcube:label name="size" /></label></li>
    <li><input type="checkbox" name="list_col[]" value="attachment" id="cols_attachment" /><label for="cols_attachment"><roundcube:label name="attachment" /></label></li>
  </ul>
</fieldset>
<fieldset class="thinbordered"><legend><roundcube:label name="listsorting" /></legend>
  <ul class="toolbarmenu">
    <li><input type="radio" name="sort_col" value="" id="sort_default" /><label for="sort_default"><roundcube:label name="nonesort" /></label></li>
    <li><input type="radio" name="sort_col" value="arrival" id="sort_arrival" /><label for="sort_arrival"><roundcube:label name="arrival" /></label></li>
    <li><input type="radio" name="sort_col" value="date" id="sort_date" /><label for="sort_date"><roundcube:label name="sentdate" /></label></li>
    <li><input type="radio" name="sort_col" value="subject" id="sort_subject" /><label for="sort_subject"><roundcube:label name="subject" /></label></li>
    <li><input type="radio" name="sort_col" value="from" id="sort_fromto" /><label for="sort_fromto"><roundcube:label name="fromto" /></label></li>
    <li><input type="radio" name="sort_col" value="to" id="sort_replyto" /><label for="sort_replyto"><roundcube:label name="replyto" /></label></li>
    <li><input type="radio" name="sort_col" value="cc" id="sort_cc" /><label for="sort_cc"><roundcube:label name="cc" /></label></li>
    <li><input type="radio" name="sort_col" value="size" id="sort_size" /><label for="sort_size"><roundcube:label name="size" /></label></li>
  </ul>
</fieldset>
<fieldset><legend><roundcube:label name="listorder" /></legend>
      <ul class="toolbarmenu">
        <li><input type="radio" name="sort_ord" value="ASC" id="sort_asc" /><label for="sort_asc"><roundcube:label name="asc" /></label></li>
        <li><input type="radio" name="sort_ord" value="DESC" id="sort_desc" /><label for="sort_desc"><roundcube:label name="desc" /></label></li>
      </ul>
</fieldset>
<div>
  <roundcube:button command="menu-open" id="listmenucancel" type="input" class="button" label="cancel" />
  <roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" />
</div>
</div>
</body>
</html>