Merged devel-threads branch (r3066:3364) back into trunk

release-0.6
thomascube 15 years ago
parent f4f1c442f7
commit f52c936f4d

@ -1,6 +1,10 @@
CHANGELOG RoundCube Webmail 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) - virtuser_query: support other identity data (#1486148)
- Options virtuser_* replaced with virtuser_* plugins - Options virtuser_* replaced with virtuser_* plugins
- Plugin API: Implemented 'email2user' and 'user2email' hooks - Plugin API: Implemented 'email2user' and 'user2email' hooks

@ -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

@ -89,8 +89,8 @@ function export_mailbox($mbox, $filename)
$from = current($IMAP->decode_address_list($headers->from, 1, false)); $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, 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_FetchPartHeader($IMAP->conn, $mbox, $i, null));
fwrite($out, iil_C_HandlePartBody($IMAP->conn, $IMAP->mailbox, $i, null, 1)); fwrite($out, iil_C_HandlePartBody($IMAP->conn, $mbox, $i, null, 1));
fwrite($out, "\n\n\n"); fwrite($out, "\n\n\n");
progress_update($i, $count); progress_update($i, $count);

@ -232,10 +232,11 @@ $rcmail_config['plugins'] = array();
// USER INTERFACE // USER INTERFACE
// ---------------------------------- // ----------------------------------
// default sort col // default messages sort column. Use empty value for default server's sorting,
$rcmail_config['message_sort_col'] = 'date'; // 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'; $rcmail_config['message_sort_order'] = 'DESC';
// These cols are shown in the message list. Available cols are: // These cols are shown in the message list. Available cols are:
@ -461,8 +462,10 @@ $rcmail_config['check_all_folders'] = false;
// If true, after message delete/move, the next message will be displayed // If true, after message delete/move, the next message will be displayed
$rcmail_config['display_next'] = false; $rcmail_config['display_next'] = false;
// If true, messages list will be sorted by message index instead of message date // 0 - Do not expand threads
$rcmail_config['index_sort'] = true; // 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) // When replying place cursor above original message (top posting)
$rcmail_config['top_posting'] = false; $rcmail_config['top_posting'] = false;

@ -226,6 +226,8 @@ $action_map = array(
'delete-folder' => 'manage_folders.inc', 'delete-folder' => 'manage_folders.inc',
'subscribe' => 'manage_folders.inc', 'subscribe' => 'manage_folders.inc',
'unsubscribe' => 'manage_folders.inc', 'unsubscribe' => 'manage_folders.inc',
'enable-threading' => 'manage_folders.inc',
'disable-threading' => 'manage_folders.inc',
'add-identity' => 'edit_identity.inc', 'add-identity' => 'edit_identity.inc',
) )
); );

@ -5,7 +5,7 @@
| program/include/html.php | | program/include/html.php |
| | | |
| This file is part of the RoundCube Webmail client | | 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 | | Licensed under the GNU GPL |
| | | |
| PURPOSE: | | PURPOSE: |
@ -34,8 +34,7 @@ class html
public static $lc_tags = true; public static $lc_tags = true;
public static $common_attrib = array('id','class','style','title','align'); public static $common_attrib = array('id','class','style','title','align');
public static $containers = array('iframe','div','span','p','h1','h2','h3', public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script');
'form','textarea','table','tr','th','td','style','script');
/** /**
* Constructor * Constructor

@ -410,7 +410,6 @@ class rcmail
$this->imap = new rcube_imap($this->db); $this->imap = new rcube_imap($this->db);
$this->imap->debug_level = $this->config->get('debug_level'); $this->imap->debug_level = $this->config->get('debug_level');
$this->imap->skip_deleted = $this->config->get('skip_deleted'); $this->imap->skip_deleted = $this->config->get('skip_deleted');
$this->imap->index_sort = $this->config->get('index_sort', true);
// enable caching of imap data // enable caching of imap data
if ($this->config->get('enable_caching')) { if ($this->config->get('enable_caching')) {

@ -5,7 +5,7 @@
| program/include/rcube_imap.php | | program/include/rcube_imap.php |
| | | |
| This file is part of the RoundCube Webmail client | | 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 | | Licensed under the GNU GPL |
| | | |
| PURPOSE: | | PURPOSE: |
@ -41,35 +41,40 @@ require_once('lib/tnef_decoder.inc');
*/ */
class rcube_imap class rcube_imap
{ {
var $db; public $debug_level = 1;
var $conn; public $error_code = 0;
var $root_dir = ''; public $skip_deleted = false;
var $mailbox = 'INBOX'; public $root_dir = '';
var $list_page = 1; public $page_size = 10;
var $page_size = 10; public $list_page = 1;
var $sort_field = 'date'; public $delimiter = NULL;
var $sort_order = 'DESC'; public $threading = false;
var $index_sort = true; public $fetch_add_headers = '';
var $delimiter = NULL; public $conn;
var $caching_enabled = FALSE;
var $default_charset = 'ISO-8859-1'; private $db;
var $struct_charset = NULL; private $root_ns = '';
var $default_folders = array('INBOX'); private $mailbox = 'INBOX';
var $fetch_add_headers = ''; private $sort_field = '';
var $cache = array(); private $sort_order = 'DESC';
var $cache_keys = array(); private $caching_enabled = false;
var $cache_changes = array(); private $default_charset = 'ISO-8859-1';
var $uid_id_map = array(); private $struct_charset = NULL;
var $msg_headers = array(); private $default_folders = array('INBOX');
var $skip_deleted = FALSE; private $default_folders_lc = array('inbox');
var $search_set = NULL; private $icache = array();
var $search_string = ''; private $cache = array();
var $search_charset = ''; private $cache_keys = array();
var $search_sort_field = ''; private $cache_changes = array();
var $debug_level = 1; private $uid_id_map = array();
var $error_code = 0; private $msg_headers = array();
var $options = array('auth_method' => 'check'); 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; private $host, $user, $pass, $port, $ssl;
@ -299,10 +304,10 @@ class rcube_imap
* @param string Charset of search string * @param string Charset of search string
* @param string Sorting field * @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) 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)) if ($msgs != null && !is_array($msgs))
$msgs = explode(',', $msgs); $msgs = explode(',', $msgs);
@ -310,6 +315,7 @@ class rcube_imap
$this->search_set = $msgs; $this->search_set = $msgs;
$this->search_charset = $charset; $this->search_charset = $charset;
$this->search_sort_field = $sort_field; $this->search_sort_field = $sort_field;
$this->search_threads = $threads;
} }
@ -319,7 +325,12 @@ class rcube_imap
*/ */
function get_search_set() 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,
);
} }
@ -348,6 +359,30 @@ class rcube_imap
} }
/**
* 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;
}
/** /**
* Checks the PERMANENTFLAGS capability of the current mailbox * Checks the PERMANENTFLAGS capability of the current mailbox
* and returns true if the given flag is supported by the IMAP server * and returns true if the given flag is supported by the IMAP server
@ -482,8 +517,12 @@ class rcube_imap
$mailbox = $this->mailbox; $mailbox = $this->mailbox;
// count search set // count search set
if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force) 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); return count((array)$this->search_set);
}
$a_mailbox_cache = $this->get_cache('messagecount'); $a_mailbox_cache = $this->get_cache('messagecount');
@ -494,8 +533,11 @@ class rcube_imap
if (!is_array($a_mailbox_cache[$mailbox])) if (!is_array($a_mailbox_cache[$mailbox]))
$a_mailbox_cache[$mailbox] = array(); $a_mailbox_cache[$mailbox] = array();
if ($mode == 'THREADS')
$count = $this->_threadcount($mailbox);
// RECENT count is fetched a bit different // RECENT count is fetched a bit different
if ($mode == 'RECENT') else if ($mode == 'RECENT')
$count = iil_C_CheckForRecent($this->conn, $mailbox); $count = iil_C_CheckForRecent($this->conn, $mailbox);
// use SEARCH for message counting // use SEARCH for message counting
@ -533,6 +575,24 @@ class rcube_imap
} }
/**
* 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 * Public method for listing headers
* convert mailbox name with root dir first * convert mailbox name with root dir first
@ -567,6 +627,9 @@ class rcube_imap
if ($this->search_string && $mailbox == $this->mailbox) if ($this->search_string && $mailbox == $this->mailbox)
return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 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); $this->_set_sort_order($sort_field, $sort_order);
$page = $page ? $page : $this->list_page; $page = $page ? $page : $this->list_page;
@ -593,8 +656,8 @@ class rcube_imap
// retrieve headers from IMAP // retrieve headers from IMAP
$a_msg_headers = array(); $a_msg_headers = array();
// use message index sort for sorting by Date (for better performance) // use message index sort as default sorting (for better performance)
if ($this->index_sort && $this->sort_field == 'date') if (!$this->sort_field)
{ {
if ($this->skip_deleted) { if ($this->skip_deleted) {
// @TODO: this could be cached // @TODO: this could be cached
@ -670,6 +733,140 @@ class rcube_imap
} }
/**
* 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) * Private method for listing a set of message headers (search results)
* *
@ -687,6 +884,14 @@ class rcube_imap
if (!strlen($mailbox) || empty($this->search_set)) if (!strlen($mailbox) || empty($this->search_set))
return array(); 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; $msgs = $this->search_set;
$a_msg_headers = array(); $a_msg_headers = array();
$page = $page ? $page : $this->list_page; $page = $page ? $page : $this->list_page;
@ -694,8 +899,8 @@ class rcube_imap
$this->_set_sort_order($sort_field, $sort_order); $this->_set_sort_order($sort_field, $sort_order);
// quickest method // quickest method (default sorting)
if ($this->index_sort && $this->search_sort_field == 'date' && $this->sort_field == 'date') if (!$this->search_sort_field && !$this->sort_field)
{ {
if ($sort_order == 'DESC') if ($sort_order == 'DESC')
$msgs = array_reverse($msgs); $msgs = array_reverse($msgs);
@ -716,8 +921,9 @@ class rcube_imap
return array_values($a_msg_headers); return array_values($a_msg_headers);
} }
// sorted messages, so we can first slice array and then fetch only wanted 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 // reset search set if sorting field has been changed
if ($this->sort_field && $this->search_sort_field != $this->sort_field) if ($this->sort_field && $this->search_sort_field != $this->sort_field)
@ -745,10 +951,10 @@ class rcube_imap
return array_values($a_msg_headers); return array_values($a_msg_headers);
} }
else { // SEARCH searching result, need sorting else { // SEARCH result, need sorting
$cnt = count($msgs); $cnt = count($msgs);
// 300: experimantal value for best result // 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 // use memory less expensive (and quick) method for big result set
$a_index = $this->message_index('', $this->sort_field, $this->sort_order); $a_index = $this->message_index('', $this->sort_field, $this->sort_order);
// get messages uids for one page... // get messages uids for one page...
@ -790,6 +996,40 @@ class rcube_imap
} }
/**
* 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);
}
/** /**
* Helper function to get first and last index of the requested set * Helper function to get first and last index of the requested set
* *
@ -901,18 +1141,22 @@ class rcube_imap
*/ */
function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL) 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); $this->_set_sort_order($sort_field, $sort_order);
$mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
$key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi"; $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi";
// we have a saved search result, get index from there // 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(); $this->cache[$key] = array();
// use message index sort for sorting by Date // use message index sort as default sorting
if ($this->index_sort && $this->sort_field == 'date') if (!$this->sort_field)
{ {
$msgs = $this->search_set; $msgs = $this->search_set;
@ -937,7 +1181,8 @@ class rcube_imap
} }
else 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") if ($this->sort_order=="ASC")
asort($a_index); asort($a_index);
@ -963,8 +1208,8 @@ class rcube_imap
return array_keys($a_index); return array_keys($a_index);
} }
// use message index sort for sorting by Date // use message index sort as default sorting
if ($this->index_sort && $this->sort_field == 'date') if (!$this->sort_field)
{ {
if ($this->skip_deleted) { if ($this->skip_deleted) {
$a_index = $this->_search_index($mailbox, 'ALL'); $a_index = $this->_search_index($mailbox, 'ALL');
@ -1001,10 +1246,91 @@ class rcube_imap
} }
/**
* 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
*/
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 * @access private
*/ */
function sync_header_index($mailbox) private function sync_header_index($mailbox)
{ {
$cache_key = $mailbox.'.msg'; $cache_key = $mailbox.'.msg';
$cache_index = $this->get_message_cache_index($cache_key); $cache_index = $this->get_message_cache_index($cache_key);
@ -1102,7 +1428,7 @@ class rcube_imap
$results = $this->search($mbox_name, $res, NULL, $sort_field); $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; return $results;
} }
@ -1122,7 +1448,17 @@ class rcube_imap
if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria)) if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria))
$criteria = '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; $charset = $charset ? $charset : $this->default_charset;
$a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, FALSE, $charset); $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, FALSE, $charset);
} }
@ -1135,7 +1471,7 @@ class rcube_imap
$a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria); $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria);
// I didn't found that SEARCH always returns sorted IDs // 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); sort($a_messages);
} }
} }
@ -1148,6 +1484,90 @@ class rcube_imap
} }
/**
* 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 * Refresh saved search set
* *
@ -1156,7 +1576,8 @@ class rcube_imap
function refresh_search() function refresh_search()
{ {
if (!empty($this->search_string)) 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(); return $this->get_search_set();
} }
@ -1802,22 +2223,32 @@ class rcube_imap
} }
// moving failed // moving failed
else if ($config->get('delete_always', false) && $tbox == $config->get('trash_mbox')) { 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);
} }
if ($moved) {
// unset threads internal cache
unset($this->icache['threads']);
// remove message ids from search set // remove message ids from search set
if ($moved && $this->search_set && $from_mbox == $this->mailbox) { 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) foreach ($a_uids as $uid)
$a_mids[] = $this->_uid2id($uid, $from_mbox); $a_mids[] = $this->_uid2id($uid, $from_mbox);
$this->search_set = array_diff($this->search_set, $a_mids); $this->search_set = array_diff($this->search_set, $a_mids);
} }
}
// update cached message headers // update cached message headers
$cache_key = $from_mbox.'.msg'; $cache_key = $from_mbox.'.msg';
if ($moved && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { if ($start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) {
// clear cache from the lowest index on // clear cache from the lowest index on
$this->clear_message_cache($cache_key, $start_index); $this->clear_message_cache($cache_key, $start_index);
} }
}
return $moved; return $moved;
} }
@ -1843,28 +2274,35 @@ class rcube_imap
$deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_uids)); $deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_uids));
if ($deleted) {
// send expunge command in order to have the deleted message // send expunge command in order to have the deleted message
// really deleted from the mailbox // really deleted from the mailbox
if ($deleted)
{
$this->_expunge($mailbox, FALSE, $a_uids); $this->_expunge($mailbox, FALSE, $a_uids);
$this->_clear_messagecount($mailbox); $this->_clear_messagecount($mailbox);
unset($this->uid_id_map[$mailbox]); unset($this->uid_id_map[$mailbox]);
}
// unset threads internal cache
unset($this->icache['threads']);
// remove message ids from search set // remove message ids from search set
if ($deleted && $this->search_set && $mailbox == $this->mailbox) { 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) foreach ($a_uids as $uid)
$a_mids[] = $this->_uid2id($uid, $mailbox); $a_mids[] = $this->_uid2id($uid, $mailbox);
$this->search_set = array_diff($this->search_set, $a_mids); $this->search_set = array_diff($this->search_set, $a_mids);
} }
}
// remove deleted messages from cache // remove deleted messages from cache
$cache_key = $mailbox.'.msg'; $cache_key = $mailbox.'.msg';
if ($deleted && $start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) { if ($start_index = $this->get_message_cache_index_min($cache_key, $a_uids)) {
// clear cache from the lowest index on // clear cache from the lowest index on
$this->clear_message_cache($cache_key, $start_index); $this->clear_message_cache($cache_key, $start_index);
} }
}
return $deleted; return $deleted;
} }
@ -2422,13 +2860,11 @@ class rcube_imap
private function get_message_cache($key, $from, $to, $sort_field, $sort_order) private function get_message_cache($key, $from, $to, $sort_field, $sort_order)
{ {
$cache_key = "$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; $config = rcmail::get_instance()->config;
// use idx sort for sorting by Date with index_sort=true or for unknown field // use idx sort as default sorting
if (($sort_field == 'date' && $this->index_sort) if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) {
|| !in_array($sort_field, $db_header_fields)) {
$sort_field = 'idx'; $sort_field = 'idx';
} }
@ -2465,9 +2901,9 @@ class rcube_imap
*/ */
private function &get_cached_message($key, $uid) 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( $sql_result = $this->db->query(
"SELECT idx, headers, structure "SELECT idx, headers, structure
@ -2482,13 +2918,13 @@ class rcube_imap
if ($sql_arr = $this->db->fetch_assoc($sql_result)) if ($sql_arr = $this->db->fetch_assoc($sql_result))
{ {
$this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx']; $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx'];
$this->cache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); $this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers']));
if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure'])) if (is_object($this->icache[$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]->structure = $this->db->decode(unserialize($sql_arr['structure']));
} }
} }
return $this->cache[$internal_key][$uid]; return $this->icache[$internal_key][$uid];
} }
/** /**
@ -2505,8 +2941,8 @@ class rcube_imap
if (!empty($sa_message_index[$key]) && !$force) if (!empty($sa_message_index[$key]) && !$force)
return $sa_message_index[$key]; return $sa_message_index[$key];
// use idx sort for sorting by Date with index_sort=true // use idx sort as default
if ($sort_field == 'date' && $this->index_sort) if (!$sort_field || !in_array($sort_field, $this->db_header_fields))
$sort_field = 'idx'; $sort_field = 'idx';
$sa_message_index[$key] = array(); $sa_message_index[$key] = array();
@ -2534,8 +2970,8 @@ class rcube_imap
return; return;
// add to internal (fast) cache // add to internal (fast) cache
$this->cache['__single_msg'][$headers->uid] = clone $headers; $this->icache['message'][$headers->uid] = clone $headers;
$this->cache['__single_msg'][$headers->uid]->structure = $struct; $this->icache['message'][$headers->uid]->structure = $struct;
// no further caching // no further caching
if (!$this->caching_enabled) if (!$this->caching_enabled)

@ -607,6 +607,26 @@ function rcube_explode_quoted_string($delimiter, $string)
} }
/**
* 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 * mbstring replacement functions
*/ */

@ -112,18 +112,21 @@ class rcube_user
unset($save_prefs[$key]); unset($save_prefs[$key]);
} }
$save_prefs = serialize($save_prefs);
$this->db->query( $this->db->query(
"UPDATE ".get_table_name('users')." "UPDATE ".get_table_name('users')."
SET preferences=?, SET preferences=?,
language=? language=?
WHERE user_id=?", WHERE user_id=?",
serialize($save_prefs), $save_prefs,
$_SESSION['language'], $_SESSION['language'],
$this->ID); $this->ID);
$this->language = $_SESSION['language']; $this->language = $_SESSION['language'];
if ($this->db->affected_rows()) { if ($this->db->affected_rows()) {
$config->set_user_prefs($a_user_prefs); $config->set_user_prefs($a_user_prefs);
$this->data['preferences'] = $save_prefs;
return true; return true;
} }

File diff suppressed because it is too large Load Diff

@ -37,6 +37,7 @@ function rcube_list_widget(list, p)
this.subject_col = -1; this.subject_col = -1;
this.shiftkey = false; this.shiftkey = false;
this.multiselect = false; this.multiselect = false;
this.multiexpand = false;
this.multi_selecting = false; this.multi_selecting = false;
this.draggable = false; this.draggable = false;
this.keyboard = false; this.keyboard = false;
@ -76,7 +77,7 @@ init: function()
for(var r=0; r<this.list.tBodies[0].childNodes.length; r++) for(var r=0; r<this.list.tBodies[0].childNodes.length; r++)
{ {
row = this.list.tBodies[0].childNodes[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; row = row.nextSibling;
r++; r++;
@ -108,7 +109,7 @@ init_row: function(row)
var p = this; var p = this;
var uid = RegExp.$1; var uid = RegExp.$1;
row.uid = uid; 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 // set eventhandlers to table row
row.onmousedown = function(e){ return p.drag_row(e, this.uid); }; row.onmousedown = function(e){ return p.drag_row(e, this.uid); };
@ -319,6 +320,188 @@ click_row: function(e, id)
}, },
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 * get first/next/previous/last rows that are not hidden
*/ */
@ -495,15 +678,17 @@ shift_select: function(id, control)
{ {
if ((this.rows[n].obj.rowIndex >= i) && (this.rows[n].obj.rowIndex <= j)) 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); this.highlight_row(n, true);
} }
}
else else
{ {
if (this.in_selection(n) && !control) if (this.in_selection(n) && !control) {
this.highlight_row(n, true); this.highlight_row(n, true);
} }
} }
}
}, },
@ -685,6 +870,16 @@ key_press: function(e)
// Stop propagation so that the browser doesn't scroll // Stop propagation so that the browser doesn't scroll
rcube_event.cancel(e); rcube_event.cancel(e);
return this.use_arrow_key(keyCode, mod_key); 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: default:
this.shiftkey = e.shiftKey; this.shiftkey = e.shiftKey;
this.key_pressed = keyCode; this.key_pressed = keyCode;
@ -712,6 +907,10 @@ key_down: function(e)
case 38: case 38:
case 63233: case 63233:
case 63232: case 63232:
case 61:
case 107:
case 109:
case 32:
if (!rcube_event.get_modifier(e) && this.focused) if (!rcube_event.get_modifier(e) && this.focused)
return rcube_event.cancel(e); return rcube_event.cancel(e);
@ -745,6 +944,36 @@ use_arrow_key: function(keyCode, mod_key)
}, },
/**
* 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;
},
/** /**
* Try to scroll the list to make the specified row visible * Try to scroll the list to make the specified row visible
*/ */
@ -779,9 +1008,9 @@ drag_mouse_move: function(e)
if (!this.draglayer) if (!this.draglayer)
this.draglayer = $('<div>').attr('id', 'rcmdraglayer').css({ position:'absolute', display:'none', 'z-index':2000 }).appendTo(document.body); 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 names = '';
var c, i, node, subject, obj; var c, i, subject, obj;
for(var n=0; n<this.selection.length; n++) for(var n=0; n<this.selection.length; n++)
{ {
if (n>12) // only show 12 lines if (n>12) // only show 12 lines
@ -790,24 +1019,29 @@ drag_mouse_move: function(e)
break; break;
} }
if (this.rows[this.selection[n]].obj) if (obj = this.rows[this.selection[n]].obj)
{ {
obj = this.rows[this.selection[n]].obj;
subject = ''; 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')) && if (n == 0)
(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(); this.drag_start_pos = $(obj.childNodes[i]).offset();
else
this.drag_start_pos = $(node).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; subject = node.nodeType==3 ? node.data : node.innerHTML;
// remove leading spaces // remove leading spaces
subject = subject.replace(/^\s+/i, ''); subject = subject.replace(/^\s+/i, '');

@ -173,20 +173,12 @@ class iilBasicHeader
var $forwarded = false; var $forwarded = false;
var $junk = false; var $junk = false;
var $flagged = false; var $flagged = false;
var $has_children = false;
var $depth = 0;
var $unread_children = 0;
var $others = array(); 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) { function iil_xor($string, $string2) {
$result = ''; $result = '';
$size = strlen($string); $size = strlen($string);
@ -885,9 +877,12 @@ function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
$is_uid = $is_uid ? 'UID ' : ''; $is_uid = $is_uid ? 'UID ' : '';
if (!empty($add)) { // message IDs
if (is_array($add))
$add = iil_CompressMessageSet(join(',', $add));
if (!empty($add))
$add = " $add"; $add = " $add";
}
$command = 's ' . $is_uid . 'SORT (' . $field . ') '; $command = 's ' . $is_uid . 'SORT (' . $field . ') ';
$command .= $encoding . ' ALL' . $add; $command .= $encoding . ' ALL' . $add;
@ -917,20 +912,27 @@ function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false) { function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=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); list($from_idx, $to_idx) = explode(':', $message_set);
if (empty($message_set) || if (empty($message_set) ||
(isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) {
return false; return false;
} }
}
$index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field);
$fields_a['DATE'] = 1; $fields_a['DATE'] = 1;
$fields_a['INTERNALDATE'] = 4; $fields_a['INTERNALDATE'] = 4;
$fields_a['ARRIVAL'] = 4;
$fields_a['FROM'] = 1; $fields_a['FROM'] = 1;
$fields_a['REPLY-TO'] = 1; $fields_a['REPLY-TO'] = 1;
$fields_a['SENDER'] = 1; $fields_a['SENDER'] = 1;
$fields_a['TO'] = 1; $fields_a['TO'] = 1;
$fields_a['CC'] = 1;
$fields_a['SUBJECT'] = 1; $fields_a['SUBJECT'] = 1;
$fields_a['UID'] = 2; $fields_a['UID'] = 2;
$fields_a['SIZE'] = 2; $fields_a['SIZE'] = 2;
@ -1031,22 +1033,6 @@ function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field='',
} }
} while (!iil_StartsWith($line, $key, true)); } 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; return $result;
} }
@ -1122,307 +1108,6 @@ function iil_C_FetchUIDs(&$conn,$mailbox) {
return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID'); 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='') function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='')
{ {
global $IMAP_USE_INTERNAL_DATE; global $IMAP_USE_INTERNAL_DATE;
@ -1436,6 +1121,9 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bo
return false; return false;
} }
if (is_array($message_set))
$message_set = join(',', $message_set);
$message_set = iil_CompressMessageSet($message_set); $message_set = iil_CompressMessageSet($message_set);
if ($add) if ($add)
@ -1878,6 +1566,87 @@ function iil_C_ID2UID(&$conn, $folder, $id) {
return $result; 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) { function iil_C_Search(&$conn, $folder, $criteria) {
if (iil_C_Select($conn, $folder)) { if (iil_C_Select($conn, $folder)) {

@ -127,6 +127,30 @@ $labels['unanswered'] = 'Unbeantwortete';
$labels['deleted'] = 'Gelöschte'; $labels['deleted'] = 'Gelöschte';
$labels['invert'] = 'Umkehren'; $labels['invert'] = 'Umkehren';
$labels['filter'] = 'Filter'; $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['compact'] = 'Packen';
$labels['empty'] = 'Leeren'; $labels['empty'] = 'Leeren';
$labels['purge'] = 'Aufräumen'; $labels['purge'] = 'Aufräumen';

@ -56,6 +56,7 @@ $labels['reply-to'] = $labels['replyto'];
$labels['mailboxlist'] = 'Folders'; $labels['mailboxlist'] = 'Folders';
$labels['messagesfromto'] = 'Messages $from to $to of $count'; $labels['messagesfromto'] = 'Messages $from to $to of $count';
$labels['threadsfromto'] = 'Threads $from to $to of $count';
$labels['messagenrof'] = 'Message $nr of $count'; $labels['messagenrof'] = 'Message $nr of $count';
$labels['moveto'] = 'Move to...'; $labels['moveto'] = 'Move to...';
@ -150,6 +151,29 @@ $labels['deleted'] = 'Deleted';
$labels['invert'] = 'Invert'; $labels['invert'] = 'Invert';
$labels['filter'] = 'Filter'; $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['compact'] = 'Compact';
$labels['empty'] = 'Empty'; $labels['empty'] = 'Empty';
$labels['purge'] = 'Purge'; $labels['purge'] = 'Purge';
@ -308,7 +332,6 @@ $labels['advancedoptions'] = 'Advanced options';
$labels['focusonnewmessage'] = 'Focus browser window on new message'; $labels['focusonnewmessage'] = 'Focus browser window on new message';
$labels['checkallfolders'] = 'Check all folders for new messages'; $labels['checkallfolders'] = 'Check all folders for new messages';
$labels['displaynext'] = 'After message delete/move display the next message'; $labels['displaynext'] = 'After message delete/move display the next message';
$labels['indexsort'] = 'Use message index for sorting by date';
$labels['mainoptions'] = 'Main Options'; $labels['mainoptions'] = 'Main Options';
$labels['section'] = 'Section'; $labels['section'] = 'Section';
$labels['maintenance'] = 'Maintenance'; $labels['maintenance'] = 'Maintenance';

@ -130,7 +130,7 @@ $labels['markunflagged'] = 'Jako nieoflagowane';
$labels['messageactions'] = 'Więcej akcji...'; $labels['messageactions'] = 'Więcej akcji...';
$labels['select'] = 'Zaznacz'; $labels['select'] = 'Zaznacz';
$labels['all'] = 'Wszystkie'; $labels['all'] = 'Wszystkie';
$labels['none'] = 'Anuluj'; $labels['none'] = 'Brak';
$labels['unread'] = 'Nieprzeczytane'; $labels['unread'] = 'Nieprzeczytane';
$labels['flagged'] = 'Oznaczone'; $labels['flagged'] = 'Oznaczone';
$labels['unanswered'] = 'Bez odpowiedzi'; $labels['unanswered'] = 'Bez odpowiedzi';

@ -32,13 +32,12 @@ foreach ($a_mailboxes as $mbox_name) {
} }
// get overall message count; allow caching because rcube_imap::recent_uids() did a refresh // 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); $unread_count = $IMAP->messagecount(NULL, 'UNSEEN', TRUE);
$_SESSION['unseen_count'][$mbox_name] = $unread_count; $_SESSION['unseen_count'][$mbox_name] = $unread_count;
$OUTPUT->set_env('messagecount', $all_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->set_env('pagecount', ceil($all_count/$IMAP->page_size));
$OUTPUT->command('set_unread_count', $mbox_name, $unread_count, ($mbox_name == 'INBOX')); $OUTPUT->command('set_unread_count', $mbox_name, $unread_count, ($mbox_name == 'INBOX'));
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($all_count)); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($all_count));
@ -56,6 +55,15 @@ foreach ($a_mailboxes as $mbox_name) {
if (empty($_GET['_list'])) if (empty($_GET['_list']))
continue; continue;
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 // use SEARCH/SORT to find recent messages
$search_str = 'UID '.min($recents).':'.max($recents); $search_str = 'UID '.min($recents).':'.max($recents);
if ($search_request) if ($search_request)
@ -69,6 +77,7 @@ foreach ($a_mailboxes as $mbox_name) {
rcmail_js_message_list($result_h, true, false); rcmail_js_message_list($result_h, true, false);
} }
} }
}
else { else {
rcmail_send_unread_count($mbox_name, true); rcmail_send_unread_count($mbox_name, true);
} }

@ -59,6 +59,17 @@ if (!isset($_SESSION['sort_col']))
if (!isset($_SESSION['sort_order'])) if (!isset($_SESSION['sort_order']))
$_SESSION['sort_order'] = $CONFIG['message_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 // set message set for search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']])) if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
{ {
@ -88,13 +99,20 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list')
$OUTPUT->set_env('search_mods', $search_mods); $OUTPUT->set_env('search_mods', $search_mods);
// make sure the message count is refreshed (for default view) // 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('mailbox', $mbox_name);
$OUTPUT->set_env('pagesize', $IMAP->page_size);
$OUTPUT->set_env('quota', $IMAP->get_capability('quota')); $OUTPUT->set_env('quota', $IMAP->get_capability('quota'));
$OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter()); $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']) if ($CONFIG['flag_for_deletion'])
$OUTPUT->set_env('flag_for_deletion', true); $OUTPUT->set_env('flag_for_deletion', true);
@ -123,42 +141,25 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list')
* return the message list as HTML table * return the message list as HTML table
*/ */
function rcmail_message_list($attrib) 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 // add some labels to client
$OUTPUT->add_label('from', 'to'); $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 // add id to message list table if not specified
if (!strlen($attrib['id'])) if (!strlen($attrib['id']))
$attrib['id'] = 'rcubemessagelist'; $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 // define list of cols to be displayed based on parameter or config
if (empty($attrib['columns'])) if (empty($attrib['columns']))
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject'); $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
else else
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns'])); $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_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(); $mbox = $IMAP->get_mailbox_name();
$delim = $IMAP->get_hierarchy_delimiter(); $delim = $IMAP->get_hierarchy_delimiter();
@ -168,197 +169,18 @@ function rcmail_message_list($attrib)
&& (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false) && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
$a_show_cols[$f] = 'to'; $a_show_cols[$f] = 'to';
// add col definition $skin_path = $_SESSION['skin_path'] = $CONFIG['skin_path'];
$out .= '<colgroup>'; $message_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
$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();
// set client env // set client env
$OUTPUT->add_gui_object('mailcontframe', 'mailcontframe'); $OUTPUT->add_gui_object('mailcontframe', 'mailcontframe');
$OUTPUT->add_gui_object('messagelist', $attrib['id']); $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('messagecount', $message_count);
$OUTPUT->set_env('current_page', $IMAP->list_page); $OUTPUT->set_env('current_page', $IMAP->list_page);
$OUTPUT->set_env('pagecount', ceil($message_count/$IMAP->page_size)); $OUTPUT->set_env('pagecount', ceil($message_count/$IMAP->page_size));
$OUTPUT->set_env('sort_col', $sort_col); $OUTPUT->set_env('sort_col', $_SESSION['sort_col']);
$OUTPUT->set_env('sort_order', $sort_order); $OUTPUT->set_env('sort_order', $_SESSION['sort_order']);
if ($attrib['messageicon']) if ($attrib['messageicon'])
$OUTPUT->set_env('messageicon', $skin_path . $attrib['messageicon']); $OUTPUT->set_env('messageicon', $skin_path . $attrib['messageicon']);
@ -378,22 +200,35 @@ function rcmail_message_list($attrib)
$OUTPUT->set_env('flaggedicon', $skin_path . $attrib['flaggedicon']); $OUTPUT->set_env('flaggedicon', $skin_path . $attrib['flaggedicon']);
if ($attrib['unflaggedicon']) if ($attrib['unflaggedicon'])
$OUTPUT->set_env('unflaggedicon', $skin_path . $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); $OUTPUT->set_env('coltypes', $a_show_cols);
if (!$message_count)
$OUTPUT->show_message('nomessagesfound', 'notice');
$OUTPUT->include_script('list.js'); $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 * return javascript commands to add rows to the message list
* or to replace the whole list (IE only) * 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; global $CONFIG, $IMAP, $OUTPUT;
if (empty($_SESSION['list_columns'])) if (empty($_SESSION['list_columns']))
@ -409,9 +244,12 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE)
&& (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false) && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
$a_show_cols[$f] = 'to'; $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 // remove 'attachment' and 'flag' columns, we don't need them here
if(($key = array_search('attachment', $a_show_cols)) !== FALSE) if(($key = array_search('attachment', $a_show_cols)) !== FALSE)
@ -419,7 +257,7 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE)
if(($key = array_search('flag', $a_show_cols)) !== FALSE) if(($key = array_search('flag', $a_show_cols)) !== FALSE)
unset($a_show_cols[$key]); unset($a_show_cols[$key]);
if ($browser->ie && $replace) if ($OUTPUT->browser->ie && $replace)
$OUTPUT->command('offline_message_list', true); $OUTPUT->command('offline_message_list', true);
// loop through message headers // loop through message headers
@ -443,7 +281,6 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE)
$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'); if (!$cont) $cont = rcube_label('nosubject');
$cont = Q($cont); $cont = Q($cont);
$a_msg_cols['mbox'] = $mbox;
} }
else if ($col=='size') else if ($col=='size')
$cont = show_bytes($header->$col); $cont = show_bytes($header->$col);
@ -455,6 +292,14 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE)
$a_msg_cols[$col] = $cont; $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) if ($header->deleted)
$a_msg_flags['deleted'] = 1; $a_msg_flags['deleted'] = 1;
if (!$header->seen) if (!$header->seen)
@ -465,12 +310,14 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE)
$a_msg_flags['forwarded'] = 1; $a_msg_flags['forwarded'] = 1;
if ($header->flagged) if ($header->flagged)
$a_msg_flags['flagged'] = 1; $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', $OUTPUT->command('add_message_row',
$header->uid, $header->uid,
$a_msg_cols, $a_msg_cols,
$a_msg_flags, $a_msg_flags,
preg_match("/multipart\/m/i", $header->ctype),
$insert_top); $insert_top);
} }
@ -479,6 +326,61 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE, $replace=TRUE)
} }
/*
* 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;
}
/** /**
* return an HTML iframe for loading mail content * return an HTML iframe for loading mail content
*/ */
@ -513,7 +415,7 @@ function rcmail_messagecount_display($attrib)
function rcmail_quota_display($attrib) function rcmail_quota_display($attrib)
{ {
global $OUTPUT, $COMM_PATH; global $OUTPUT;
if (!$attrib['id']) if (!$attrib['id'])
$attrib['id'] = 'rcmquotadisplay'; $attrib['id'] = 'rcmquotadisplay';
@ -583,19 +485,19 @@ function rcmail_get_messagecount_text($count=NULL, $page=NULL)
{ {
return rcube_label(array('name' => 'messagenrof', return rcube_label(array('name' => 'messagenrof',
'vars' => array('nr' => $MESSAGE->index+1, 'vars' => array('nr' => $MESSAGE->index+1,
'count' => $count!==NULL ? $count : $IMAP->messagecount()))); 'count' => $count!==NULL ? $count : $IMAP->messagecount(NULL, 'ALL')))); // Only messages, no threads here
} }
if ($page===NULL) if ($page===NULL)
$page = $IMAP->list_page; $page = $IMAP->list_page;
$start_msg = ($page-1) * $IMAP->page_size + 1; $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) if ($max==0)
$out = rcube_label('mailboxempty'); $out = rcube_label('mailboxempty');
else else
$out = rcube_label(array('name' => 'messagesfromto', $out = rcube_label(array('name' => $IMAP->threading ? 'threadsfromto' : 'messagesfromto',
'vars' => array('from' => $start_msg, 'vars' => array('from' => $start_msg,
'to' => min($max, $start_msg + $IMAP->page_size - 1), 'to' => min($max, $start_msg + $IMAP->page_size - 1),
'count' => $max))); 'count' => $max)));

@ -33,8 +33,6 @@ if ($sort = get_input_value('_sort', RCUBE_INPUT_GET))
$save_arr = array(); $save_arr = array();
$_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col; $_SESSION['sort_col'] = $save_arr['message_sort_col'] = $sort_col;
$_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order; $_SESSION['sort_order'] = $save_arr['message_sort_order'] = $sort_order;
$RCMAIL->user->save_prefs($save_arr);
} }
else else
{ {
@ -43,6 +41,16 @@ else
$sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order']; $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(); $mbox_name = $IMAP->get_mailbox_name();
// initialize searching result if search_filter is used // initialize searching result if search_filter is used
@ -55,9 +63,13 @@ if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL')
} }
// fetch message headers // 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); $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 // update mailboxlist
rcmail_send_unread_count($mbox_name, !empty($_REQUEST['_refresh'])); rcmail_send_unread_count($mbox_name, !empty($_REQUEST['_refresh']));
@ -65,13 +77,14 @@ rcmail_send_unread_count($mbox_name, !empty($_REQUEST['_refresh']));
$pages = ceil($count/$IMAP->page_size); $pages = ceil($count/$IMAP->page_size);
$OUTPUT->set_env('messagecount', $count); $OUTPUT->set_env('messagecount', $count);
$OUTPUT->set_env('pagecount', $pages); $OUTPUT->set_env('pagecount', $pages);
$OUTPUT->set_env('threading', (bool) $IMAP->threading);
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count)); $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count));
$OUTPUT->command('set_mailboxname', rcmail_get_mailbox_name_text()); $OUTPUT->command('set_mailboxname', rcmail_get_mailbox_name_text());
// add message rows // add message rows
rcmail_js_message_list($a_headers, FALSE, TRUE, (bool) $cols);
if (isset($a_headers) && count($a_headers)) if (isset($a_headers) && count($a_headers))
{ {
rcmail_js_message_list($a_headers);
if ($search_request) if ($search_request)
$OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count)); $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count));
} }

@ -36,7 +36,7 @@ if (($uids = get_input_value('_uid', RCUBE_INPUT_POST)) && ($flag = get_input_va
if ($flag == 'DELETED' && $CONFIG['skip_deleted'] && $_POST['_from'] != 'show') { if ($flag == 'DELETED' && $CONFIG['skip_deleted'] && $_POST['_from'] != 'show') {
// count messages before changing anything // 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); $old_pages = ceil($old_count / $IMAP->page_size);
$count = sizeof(explode(',', $uids)); $count = sizeof(explode(',', $uids));
} }
@ -75,7 +75,7 @@ if (($uids = get_input_value('_uid', RCUBE_INPUT_POST)) && ($flag = get_input_va
$_SESSION['search'][$search_request] = $IMAP->refresh_search(); $_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); $pages = ceil($msg_count / $IMAP->page_size);
$nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page; $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page;
$remaining = $msg_count - $IMAP->page_size * ($IMAP->list_page - 1); $remaining = $msg_count - $IMAP->page_size * ($IMAP->list_page - 1);
@ -103,8 +103,11 @@ if (($uids = get_input_value('_uid', RCUBE_INPUT_POST)) && ($flag = get_input_va
} }
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count)); $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) // 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_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']; $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];

@ -24,7 +24,7 @@ if (!$OUTPUT->ajax_call)
return; return;
// count messages before changing anything // 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); $old_pages = ceil($old_count / $IMAP->page_size);
// move messages // move messages
@ -50,6 +50,7 @@ if ($RCMAIL->action=='moveto' && !empty($_POST['_uid']) && !empty($_POST['_targe
else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) { else if ($RCMAIL->action=='delete' && !empty($_POST['_uid'])) {
$count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST)))); $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_POST))));
$mbox = get_input_value('_mbox', RCUBE_INPUT_POST); $mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
$del = $IMAP->delete_message($uids, $mbox); $del = $IMAP->delete_message($uids, $mbox);
if (!$del) { if (!$del) {
@ -82,7 +83,7 @@ if ($_POST['_from'] == 'show')
} }
else else
{ {
$msg_count = $IMAP->messagecount(); $msg_count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL');
$pages = ceil($msg_count / $IMAP->page_size); $pages = ceil($msg_count / $IMAP->page_size);
$nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page; $nextpage_count = $old_count - $IMAP->page_size * $IMAP->list_page;
$remaining = $msg_count - $IMAP->page_size * ($IMAP->list_page - 1); $remaining = $msg_count - $IMAP->page_size * ($IMAP->list_page - 1);
@ -116,8 +117,11 @@ else
$OUTPUT->command('set_quota', rcmail_quota_content()); $OUTPUT->command('set_quota', rcmail_quota_content());
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count)); $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) // 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_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']; $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];

@ -104,7 +104,7 @@ if ($search_str)
// Get the headers // Get the headers
$result_h = $IMAP->list_headers($mbox, 1, $_SESSION['sort_col'], $_SESSION['sort_order']); $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 // save search results in session
if (!is_array($_SESSION['search'])) if (!is_array($_SESSION['search']))
@ -120,7 +120,7 @@ if (!empty($result_h))
{ {
rcmail_js_message_list($result_h); rcmail_js_message_list($result_h);
if ($search_str) if ($search_str)
$OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count)); $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $IMAP->messagecount(NULL, 'ALL')));
} }
else else
{ {

@ -104,7 +104,7 @@ if ($_GET['_uid']) {
$next = $prev = $first = $last = -1; $next = $prev = $first = $last = -1;
if ($_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] != 'DESC' 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 // this assumes that we are sorted by date_DESC
$cnt = $IMAP->messagecount(); $cnt = $IMAP->messagecount();
@ -142,7 +142,7 @@ if ($_GET['_uid']) {
if (!$MESSAGE->headers->seen) if (!$MESSAGE->headers->seen)
$RCMAIL->plugins->exec_hook('message_read', array('uid' => $MESSAGE->uid, $RCMAIL->plugins->exec_hook('message_read', array('uid' => $MESSAGE->uid,
'mailbox' => $IMAP->mailbox, 'message' => $MESSAGE)); 'mailbox' => $mbox_name, 'message' => $MESSAGE));
} }

@ -25,7 +25,7 @@ ob_end_clean();
if ($uid = get_input_value('_uid', RCUBE_INPUT_GET)) if ($uid = get_input_value('_uid', RCUBE_INPUT_GET))
{ {
$headers = $IMAP->get_headers($uid); $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}"); header("Content-Type: text/plain; charset={$charset}");
if (!empty($_GET['_save'])) { if (!empty($_GET['_save'])) {

@ -248,17 +248,6 @@ function rcmail_user_prefs($current=null)
); );
} }
// 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 // show drop-down for available skins
if (!isset($no_override['skin'])) { if (!isset($no_override['skin'])) {
$skins = rcmail_get_skins(); $skins = rcmail_get_skins();
@ -311,6 +300,19 @@ function rcmail_user_prefs($current=null)
); );
} }
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'])) { if (!isset($no_override['focus_on_new_message'])) {
$field_id = 'rcmfd_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)); $input_focus_on_new_message = new html_checkbox(array('name' => '_focus_on_new_message', 'id' => $field_id, 'value' => 1));

@ -38,6 +38,20 @@ else if ($RCMAIL->action=='unsubscribe')
$IMAP->unsubscribe(array($mbox)); $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 // create a new mailbox
else if ($RCMAIL->action=='create-folder') else if ($RCMAIL->action=='create-folder')
{ {
@ -79,6 +93,24 @@ else if ($RCMAIL->action=='rename-folder')
$rename = $IMAP->rename_mailbox($oldname, $name); $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) if ($rename && $OUTPUT->ajax_call)
{ {
$folderlist = $IMAP->list_unsubscribed(); $folderlist = $IMAP->list_unsubscribed();
@ -159,7 +191,11 @@ if ($OUTPUT->ajax_call)
// build table with all folders listed by server // build table with all folders listed by server
function rcube_subscription_form($attrib) 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'); list($form_start, $form_end) = get_form_tags($attrib, 'folders');
unset($attrib['form']); unset($attrib['form']);
@ -173,15 +209,17 @@ function rcube_subscription_form($attrib)
$table->add_header('name', rcube_label('foldername')); $table->add_header('name', rcube_label('foldername'));
$table->add_header('msgcount', rcube_label('messagecount')); $table->add_header('msgcount', rcube_label('messagecount'));
$table->add_header('subscribed', rcube_label('subscribed')); $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('rename', '&nbsp;');
$table->add_header('delete', '&nbsp;'); $table->add_header('delete', '&nbsp;');
// get folders from server // get folders from server
$IMAP->clear_cache('mailboxes'); $IMAP->clear_cache('mailboxes');
$a_unsubscribed = $IMAP->list_unsubscribed(); $a_unsubscribed = $IMAP->list_unsubscribed();
$a_subscribed = $IMAP->list_mailboxes(); $a_subscribed = $IMAP->list_mailboxes();
$a_threaded = $a_threaded_copy = $RCMAIL->config->get('message_threading', array());
$delimiter = $IMAP->get_hierarchy_delimiter(); $delimiter = $IMAP->get_hierarchy_delimiter();
$a_js_folders = $seen_folders = $list_folders = array(); $a_js_folders = $seen_folders = $list_folders = array();
@ -203,14 +241,28 @@ function rcube_subscription_form($attrib)
} }
} }
unset($a_threaded_copy[$folder]);
$list_folders[] = array('id' => $folder, 'name' => $name, 'level' => $level); $list_folders[] = array('id' => $folder, 'name' => $name, 'level' => $level);
$seen[$folder]++; $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( $checkbox_subscribe = new html_checkbox(array(
'name' => '_subscribed[]', 'name' => '_subscribed[]',
'onclick' => JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)", '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'])) if (!empty($attrib['deleteicon']))
$del_button = html::img(array('src' => $CONFIG['skin_path'] . $attrib['deleteicon'], 'alt' => rcube_label('delete'))); $del_button = html::img(array('src' => $CONFIG['skin_path'] . $attrib['deleteicon'], 'alt' => rcube_label('delete')));
@ -226,6 +278,7 @@ function rcube_subscription_form($attrib)
foreach ($list_folders as $i => $folder) { foreach ($list_folders as $i => $folder) {
$idx = $i + 1; $idx = $i + 1;
$subscribed = in_array($folder['id'], $a_subscribed); $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'])); $protected = ($CONFIG['protect_default_folders'] == true && in_array($folder['id'], $CONFIG['default_imap_folders']));
$classes = array($i%2 ? 'even' : 'odd'); $classes = array($i%2 ? 'even' : 'odd');
$folder_js = JQ($folder['id']); $folder_js = JQ($folder['id']);
@ -238,9 +291,13 @@ function rcube_subscription_form($attrib)
$table->add_row(array('id' => 'rcmrow'.$idx, 'class' => join(' ', $classes))); $table->add_row(array('id' => 'rcmrow'.$idx, 'class' => join(' ', $classes)));
$table->add('name', Q($display_folder)); $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;') : $table->add('subscribed', ($protected || $folder['virtual']) ? ($subscribed ? '&nbsp;&#x2022;' : '&nbsp;') :
$checkbox_subscribe->show(($subscribed ? $folder_utf8 : ''), array('value' => $folder_utf8))); $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 // add rename and delete buttons
if (!$protected && !$folder['virtual']) { if (!$protected && !$folder['virtual']) {
@ -335,6 +392,27 @@ function rcube_rename_folder_form($attrib)
return $out; 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->set_pagetitle(rcube_label('folders'));
$OUTPUT->include_script('list.js'); $OUTPUT->include_script('list.js');

@ -32,7 +32,6 @@ switch ($CURR_SECTION)
'timezone' => isset($_POST['_timezone']) ? (is_numeric($_POST['_timezone']) ? floatval($_POST['_timezone']) : get_input_value('_timezone', RCUBE_INPUT_POST)) : $CONFIG['timezone'], '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, 'dst_active' => isset($_POST['_dst_active']) ? TRUE : FALSE,
'pagesize' => is_numeric($_POST['_pagesize']) ? max(2, intval($_POST['_pagesize'])) : $CONFIG['pagesize'], '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, 'prettydate' => isset($_POST['_pretty_date']) ? TRUE : FALSE,
'skin' => isset($_POST['_skin']) ? get_input_value('_skin', RCUBE_INPUT_POST) : $CONFIG['skin'], 'skin' => isset($_POST['_skin']) ? get_input_value('_skin', RCUBE_INPUT_POST) : $CONFIG['skin'],
); );
@ -42,6 +41,7 @@ switch ($CURR_SECTION)
$a_user_prefs = array( $a_user_prefs = array(
'focus_on_new_message' => isset($_POST['_focus_on_new_message']) ? TRUE : FALSE, 'focus_on_new_message' => isset($_POST['_focus_on_new_message']) ? TRUE : FALSE,
'preview_pane' => isset($_POST['_preview_pane']) ? 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, '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'], 'keep_alive' => isset($_POST['_keep_alive']) ? intval($_POST['_keep_alive'])*60 : $CONFIG['keep_alive'],
'check_all_folders' => isset($_POST['_check_all_folders']) ? TRUE : FALSE, 'check_all_folders' => isset($_POST['_check_all_folders']) ? TRUE : FALSE,

@ -537,7 +537,7 @@ a.rcmContactAddress:hover
ul.toolbarmenu ul.toolbarmenu
{ {
margin: 0; margin: -4px 0 -4px 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
@ -547,13 +547,14 @@ ul.toolbarmenu li
font-size: 11px; font-size: 11px;
white-space: nowrap; white-space: nowrap;
min-width: 130px; min-width: 130px;
margin: 3px -4px;
} }
ul.toolbarmenu li a ul.toolbarmenu li a
{ {
display: block; display: block;
color: #a0a0a0; color: #a0a0a0;
padding: 2px 8px 3px 22px; padding: 2px 12px 3px 28px;
text-decoration: none; text-decoration: none;
min-height: 14px; min-height: 14px;
} }
@ -597,3 +598,8 @@ ul.toolbarmenu li.separator_above
margin-top: 2px; margin-top: 2px;
padding-top: 2px; padding-top: 2px;
} }
.disabled
{
color: #999;
}

@ -124,6 +124,7 @@ function rcube_mail_ui()
this.markmenu = $('#markmessagemenu'); this.markmenu = $('#markmessagemenu');
this.searchmenu = $('#searchmenu'); this.searchmenu = $('#searchmenu');
this.messagemenu = $('#messagemenu'); this.messagemenu = $('#messagemenu');
this.listmenu = $('#listmenu');
} }
rcube_mail_ui.prototype = { rcube_mail_ui.prototype = {
@ -186,15 +187,89 @@ set_searchmod: function(elem)
rcmail.env.search_mods[rcmail.env.mailbox][elem.value] = elem.value; 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) 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); 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); 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 menu = rcube_find_object('searchmenu');
var target = rcube_event.get_target(evt);
while (target.parentNode) { while (target.parentNode) {
if (target.parentNode == menu) if (target.parentNode == menu)
return; return;
@ -213,6 +288,8 @@ body_keypress: function(evt, p)
this.show_searchmenu(false); this.show_searchmenu(false);
if (this.messagemenu && this.messagemenu.is(':visible')) if (this.messagemenu && this.messagemenu.is(':visible'))
this.show_messagemenu(false); this.show_messagemenu(false);
if (this.listmenu && this.listmenu.is(':visible'))
this.show_listmenu(false);
} }
} }
@ -225,4 +302,6 @@ function rcube_init_mail_ui()
rcmail_ui = new rcube_mail_ui(); 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_mouseup', event:'mouseup' });
rcube_event.add_listener({ object:rcmail_ui, method:'body_keypress', event:'keypress' }); 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);
} }

@ -14,7 +14,9 @@ img
background-image: url('images/display/icons.gif'); 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'); background-image: url('images/messageactions.gif');
} }
@ -47,13 +49,12 @@ img
background-image: url('images/abook_toolbar.gif'); background-image: url('images/abook_toolbar.gif');
} }
ul.toolbarmenu li ul.toolbarmenu li a
{ {
width: auto; clear: left;
border: 1px solid #f6f6f6;
} }
ul.toolbarmenu li a ul.toolbarmenu li.separator_below
{ {
clear: left; padding-bottom: 8px;
} }

@ -24,13 +24,9 @@ input, textarea
filter: alpha(opacity=85); filter: alpha(opacity=85);
} }
#markmessagemenu, .popupmenu
#searchmenu,
#messagemenu
{ {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)"; background-color: #ffffff;
filter: alpha(opacity=90);
} }
#tabsbar #tabsbar
@ -160,6 +156,11 @@ input, textarea
border-collapse: collapse; border-collapse: collapse;
} }
#messagelist tbody tr.unroot td.subject
{
text-decoration: underline;
}
#messageframe #messageframe
{ {
width: expression((parseInt(this.parentNode.offsetWidth)-180)+'px'); width: expression((parseInt(this.parentNode.offsetWidth)-180)+'px');
@ -243,6 +244,11 @@ div.message-part div.pre
filter: alpha(opacity=70); filter: alpha(opacity=70);
} }
ul.toolbarmenu
{
margin: 0 0 -4px 0;
}
ul.toolbarmenu li ul.toolbarmenu li
{ {
min-width: auto; min-width: auto;
@ -263,3 +269,9 @@ table.records-table thead tr td
{ {
height: 19px; height: 19px;
} }
#listmenu fieldset
{
margin: 0 4px;
padding: 0.8em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -1,4 +1,4 @@
<div id="messagemenu"> <div id="messagemenu" class="popupmenu">
<ul class="toolbarmenu"> <ul class="toolbarmenu">
<li><roundcube:button class="printlink" command="print" label="printmessage" classAct="printlink active" /></li> <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> <li><roundcube:button class="downloadlink" command="download" label="emlsave" classAct="downloadlink active" /></li>

@ -164,25 +164,38 @@
padding-left: 2px; padding-left: 2px;
} }
#markmessagemenu, .popupmenu
#searchmenu,
#messagemenu
{ {
position: absolute; position: absolute;
top: 32px; top: 32px;
left: 90px; left: 90px;
width: auto; width: auto;
display: none; display: none;
background-color: #F9F9F9; background-color: #fff;
border: 1px solid #CCC; background-color: rgba(255, 255, 255, 0.95);
padding: 1px; border: 1px solid #999;
opacity: 0.9; padding: 4px;
z-index: 240; 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 #searchmenu
{ {
width: 172px; width: 160px;
}
#searchmenu ul.toolbarmenu
{
margin: 0;
}
#searchmenu ul.toolbarmenu li
{
margin: 1px 4px 1px;
} }
#messagemenu li a.active:hover, #messagemenu li a.active:hover,
@ -194,53 +207,53 @@
#messagemenu li a #messagemenu li a
{ {
background: url('images/messageactions.png') no-repeat 1px 0; background: url('images/messageactions.png') no-repeat 7px 0;
background-position: 0px 20px; background-position: 7px 20px;
} }
#messagemenu li a.printlink #messagemenu li a.printlink
{ {
background-position: 1px 1px; background-position: 7px 1px;
} }
#messagemenu li a.downloadlink #messagemenu li a.downloadlink
{ {
background-position: 1px -17px; background-position: 7px -17px;
} }
#messagemenu li a.sourcelink #messagemenu li a.sourcelink
{ {
background-position: 1px -35px; background-position: 7px -35px;
} }
#messagemenu li a.openlink #messagemenu li a.openlink
{ {
background-position: 1px -53px; background-position: 7px -53px;
} }
#messagemenu li a.editlink #messagemenu li a.editlink
{ {
background-position: 1px -71px; background-position: 7px -71px;
} }
#markmessagemenu a.readlink #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 #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 #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 #markmessagemenu a.unflaggedlink
{ {
background: url('images/icons/unflagged.png') no-repeat 2px; background: url('images/icons/unflagged.png') no-repeat 7px 2px;
} }
#searchfilter #searchfilter
@ -613,6 +626,30 @@ td.formlinks a:visited
background-position: -75px -15px; 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 #countcontrols
{ {
height: 15px; height: 15px;
@ -720,15 +757,25 @@ body.messagelist
vertical-align: middle; vertical-align: middle;
} }
#messagelist thead tr td.subject
{
padding-left: 22px;
}
#messagelist thead tr td.icon, #messagelist thead tr td.icon,
#messagelist thead tr td.flag #messagelist thead tr td.flag,
#messagelist thead tr td.threads
{ {
width: 22px; width: 22px;
padding: 0; padding: 0;
text-align: center; text-align: center;
} }
#messagelist tbody tr td.icon, #messagelist thead tr td.threads
{
width: 18px;
}
#messagelist tbody tr td.flag #messagelist tbody tr td.flag
{ {
padding: 2px 3px 2px 3px; padding: 2px 3px 2px 3px;
@ -736,6 +783,45 @@ body.messagelist
cursor: pointer; 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, #messagelist tbody tr td.flag img:hover,
#messagelist thead tr td.flag img #messagelist thead tr td.flag img
{ {
@ -749,6 +835,12 @@ body.messagelist
width: 99%; width: 99%;
} }
/* thread parent message with unread children */
#messagelist tbody tr.unroot td.subject a
{
text-decoration: underline;
}
#messagelist tr td.size #messagelist tr td.size
{ {
width: 70px; width: 70px;
@ -1267,3 +1359,27 @@ font.bold
{ {
font-weight: bold; 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;
}

@ -59,7 +59,9 @@
forwardedrepliedIcon="/images/icons/forwarded_replied.png" forwardedrepliedIcon="/images/icons/forwarded_replied.png"
attachmentIcon="/images/icons/attachment.png" attachmentIcon="/images/icons/attachment.png"
flaggedIcon="/images/icons/flagged.png" flaggedIcon="/images/icons/flagged.png"
unflaggedIcon="/images/icons/blank.gif" /> unflaggedIcon="/images/icons/blank.gif"
unreadchildrenIcon=""
optionsmenuIcon="/images/icons/columnpicker.gif" />
</div> </div>
<roundcube:if condition="config:preview_pane == true" /> <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="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-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=" " /> <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:container name="listcontrols" id="listcontrols" />
<roundcube:if condition="env:quota" /> <roundcube:if condition="env:quota" />
<span style="margin-left: 20px; margin-right: 5px"><roundcube:label name="quota" />:</span> <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="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=" " /> <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"> <ul class="toolbarmenu">
<li><roundcube:button command="mark" prop="read" label="markread" classAct="readlink active" class="readlink" /></li> <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> <li><roundcube:button command="mark" prop="unread" label="markunread" classAct="unreadlink active" class="unreadlink" /></li>
@ -125,7 +131,7 @@
</div> </div>
<div id="searchmenu"> <div id="searchmenu" class="popupmenu">
<ul class="toolbarmenu"> <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="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> <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" /> <roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
</div> </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> </body>
</html> </html>

Loading…
Cancel
Save