From 15a9d1ce671fcbc44ea3e4858d7aa6f5b22300c9 Mon Sep 17 00:00:00 2001 From: thomascube Date: Thu, 5 Jan 2006 00:37:10 +0000 Subject: Optimized loading time; added periodic mail check; added EXPUNGE command --- program/include/main.inc | 106 ++++++++++--- program/include/rcube_db.inc | 251 +++++++++++++++++++++++++------ program/include/rcube_imap.inc | 289 +++++++++++++++++++++++++++--------- program/js/app.js | 148 ++++++++++++------ program/js/common.js | 2 +- program/lib/imap.inc | 5 +- program/localization/de/labels.inc | 4 + program/localization/en/labels.inc | 4 + program/steps/mail/check_recent.inc | 47 ++++++ program/steps/mail/folders.inc | 61 ++++++++ program/steps/mail/func.inc | 29 +++- program/steps/mail/getunread.inc | 36 +++++ program/steps/mail/sendmail.inc | 9 +- program/steps/mail/upload.inc | 2 +- 14 files changed, 804 insertions(+), 189 deletions(-) create mode 100644 program/steps/mail/check_recent.inc create mode 100644 program/steps/mail/folders.inc create mode 100644 program/steps/mail/getunread.inc (limited to 'program') diff --git a/program/include/main.inc b/program/include/main.inc index 4cfb5b270..24110d343 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -146,6 +146,9 @@ function rcmail_imap_init($connect=FALSE) global $CONFIG, $DB, $IMAP; $IMAP = new rcube_imap($DB); + $IMAP->debug_level = $CONFIG['debug_level']; + $IMAP->skip_deleted = $CONFIG['skip_deleted']; + // connect with stored session data if ($connect) @@ -591,14 +594,25 @@ function decrypt_passwd($cypher) // send correct response on a remote request -function rcube_remote_response($js_code) +function rcube_remote_response($js_code, $flush=FALSE) { - send_nocacheing_headers(); - header('Content-Type: application/x-javascript'); + static $s_header_sent = FALSE; + + if (!$s_header_sent) + { + $s_header_sent = TRUE; + send_nocacheing_headers(); + header('Content-Type: application/x-javascript'); + print '/** remote response ['.date('d/M/Y h:i:s O')."] **/\n"; + } - print '/** remote response ['.date('d/M/Y h:i:s O')."] **/\n"; + // send response code print $js_code; - exit; + + if ($flush) // flush the output buffer + flush(); + else // terminate script + exit; } @@ -879,8 +893,13 @@ function rcube_xml_command($command, $str_attrib, $a_attrib=NULL) $object = strtolower($attrib['name']); $object_handlers = array( + // GENERAL + 'loginform' => 'rcmail_login_form', + 'username' => 'rcmail_current_username', + // MAIL 'mailboxlist' => 'rcmail_mailbox_list', + 'message' => 'rcmail_message_container', 'messages' => 'rcmail_message_list', 'messagecountdisplay' => 'rcmail_messagecount_display', 'messageheaders' => 'rcmail_message_headers', @@ -916,32 +935,28 @@ function rcube_xml_command($command, $str_attrib, $a_attrib=NULL) 'composebody' => 'rcmail_compose_body' ); - if ($object=='loginform') - return rcmail_login_form($attrib); - - else if ($object=='message') - return rcmail_message_container($attrib); // execute object handler function - else if ($object_handlers[$object] && function_exists($object_handlers[$object])) + if ($object_handlers[$object] && function_exists($object_handlers[$object])) return call_user_func($object_handlers[$object], $attrib); else if ($object=='pagetitle') { $task = $GLOBALS['_task']; + $title = !empty($CONFIG['product_name']) ? $CONFIG['product_name'].' :: ' : ''; + if ($task=='mail' && isset($GLOBALS['MESSAGE']['subject'])) - return rep_specialchars_output("RoundCube|Mail :: ".$GLOBALS['MESSAGE']['subject']); + $title .= $GLOBALS['MESSAGE']['subject']; else if (isset($GLOBALS['PAGE_TITLE'])) - return rep_specialchars_output("RoundCube|Mail :: ".$GLOBALS['PAGE_TITLE']); + $title .= $GLOBALS['PAGE_TITLE']; else if ($task=='mail' && ($mbox_name = $IMAP->get_mailbox_name())) - return "RoundCube|Mail :: ".rep_specialchars_output(UTF7DecodeString($mbox_name), 'html', 'all'); + $title .= UTF7DecodeString($mbox_name); else - return "RoundCube|Mail :: $task"; + $title .= $task; + + return rep_specialchars_output($title, 'html', 'all'); } - else if ($object=='about') - return ''; - break; } @@ -1266,6 +1281,38 @@ function rcmail_message_container($attrib) } +// return the IMAP username of the current session +function rcmail_current_username($attrib) + { + global $DB; + static $s_username; + + // alread fetched + if (!empty($s_username)) + return $s_username; + + // get e-mail address form default identity + $sql_result = $DB->query("SELECT email AS mailto + FROM ".get_table_name('identities')." + WHERE user_id=? + AND standard=1 + AND del<>1", + $_SESSION['user_id']); + + if ($DB->num_rows($sql_result)) + { + $sql_arr = $DB->fetch_assoc($sql_result); + $s_username = $sql_arr['mailto']; + } + else if (strstr($_SESSION['username'], '@')) + $s_username = $_SESSION['username']; + else + $s_username = $_SESSION['username'].'@'.$_SESSION['imap_host']; + + return $s_username; + } + + // return code for the webmail login form function rcmail_login_form($attrib) { @@ -1373,4 +1420,27 @@ function rcmail_charset_selector($attrib) } + +function rcube_timer() + { + list($usec, $sec) = explode(" ", microtime()); + return ((float)$usec + (float)$sec); + } + + +function rcube_print_time($timer, $label='Timer') + { + static $print_count = 0; + + $print_count++; + $now = rcube_timer(); + $diff = $now-$timer; + + if (empty($label)) + $label = 'Timer '.$print_count; + + console(sprintf("%s: %0.4f sec", $label, $diff)); + } + + ?> \ No newline at end of file diff --git a/program/include/rcube_db.inc b/program/include/rcube_db.inc index acb13ce37..e54dcc989 100755 --- a/program/include/rcube_db.inc +++ b/program/include/rcube_db.inc @@ -20,8 +20,24 @@ */ + +/** + * Obtain the PEAR::DB class that is used for abstraction + */ require_once('DB.php'); + +/** + * Database independent query interface + * + * This is a wrapper for the PEAR::DB class + * + * @package RoundCube Webmail + * @author David Saez Padros + * @author Thomas Bruederli + * @version 1.14 + * @link http://pear.php.net/package/DB + */ class rcube_db { var $db_dsnw; // DSN for write operations @@ -34,8 +50,13 @@ class rcube_db var $last_res_id = 0; - // PHP 5 constructor - function __construct($db_dsnw,$db_dsnr='') + /** + * Object constructor + * + * @param string DSN for read/write operations + * @param string Optional DSN for read only operations + */ + function __construct($db_dsnw, $db_dsnr='') { if ($db_dsnr=='') $db_dsnr=$db_dsnw; @@ -48,25 +69,44 @@ class rcube_db } - // PHP 4 compatibility + /** + * PHP 4 object constructor + * + * @see rcube_db::__construct + */ function rcube_db($db_dsnw,$db_dsnr='') { $this->__construct($db_dsnw,$db_dsnr); } - // Connect to specific database + /** + * Object destructor + */ + function __destruct() + { + // before closing the database connection, write session data + session_write_close(); + } + + + /** + * Connect to specific database + * + * @param string DSN for DB connections + * @return object PEAR database handle + * @access private + */ function dsn_connect($dsn) { // Use persistent connections if available $dbh = DB::connect($dsn, array('persistent' => TRUE)); if (DB::isError($dbh)) - raise_error(array('code' => 500, - 'type' => 'db', - 'line' => __LINE__, - 'file' => __FILE__, + { + raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $dbh->getMessage()), TRUE, FALSE); + } else if ($this->db_provider=='sqlite') { @@ -79,8 +119,14 @@ class rcube_db } - // Connect to appropiate databse - function db_connect ($mode) + /** + * Connect to appropiate databse + * depending on the operation + * + * @param string Connection mode (r|w) + * @access public + */ + function db_connect($mode) { $this->db_mode = $mode; @@ -99,7 +145,7 @@ class rcube_db if ($this->db_mode==$mode) return; } - + if ($mode=='r') $dsn = $this->db_dsnr; else @@ -110,7 +156,14 @@ class rcube_db } - // Query database + /** + * Execute a SQL query + * + * @param string SQL query to execute + * @param mixed Values to be inserted in query + * @return number Query handle identifier + * @access public + */ function query() { $params = func_get_args(); @@ -120,7 +173,16 @@ class rcube_db } - // Query with limits + /** + * Execute a SQL query with limits + * + * @param string SQL query to execute + * @param number Offset for LIMIT statement + * @param number Number of rows for LIMIT statement + * @param mixed Values to be inserted in query + * @return number Query handle identifier + * @access public + */ function limitquery() { $params = func_get_args(); @@ -132,6 +194,16 @@ class rcube_db } + /** + * Execute a SQL query with limits + * + * @param string SQL query to execute + * @param number Offset for LIMIT statement + * @param number Number of rows for LIMIT statement + * @param array Values to be inserted in query + * @return number Query handle identifier + * @access private + */ function _query($query, $offset, $numrows, $params) { // Read or write ? @@ -155,6 +227,14 @@ class rcube_db } + /** + * Get number of rows for a SQL query + * If no query handle is specified, the last query will be taken as reference + * + * @param number Optional query handle identifier + * @return mixed Number of rows or FALSE on failure + * @access public + */ function num_rows($res_id=NULL) { if (!$this->db_handle) @@ -167,7 +247,13 @@ class rcube_db } - function affected_rows($res_id=NULL) + /** + * Get number of affected rows fort he last query + * + * @return mixed Number of rows or FALSE on failure + * @access public + */ + function affected_rows() { if (!$this->db_handle) return FALSE; @@ -176,6 +262,14 @@ class rcube_db } + /** + * Get last inserted record ID + * For Postgres databases, a sequence name is required + * + * @param string Sequence name for increment + * @return mixed ID or FALSE on failure + * @access public + */ function insert_id($sequence = '') { if (!$this->db_handle || $this->db_mode=='r') @@ -185,10 +279,12 @@ class rcube_db { case 'pgsql': // PostgreSQL uses sequences - $result =& $this->db_handle->getOne("SELECT CURRVAL('$sequence')"); + $result = &$this->db_handle->getOne("SELECT CURRVAL('$sequence')"); if (DB::isError($result)) + { raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $result->getMessage()), TRUE, FALSE); + } return $result; @@ -207,6 +303,14 @@ class rcube_db } + /** + * Get an associative array for one row + * If no query handle is specified, the last query will be taken as reference + * + * @param number Optional query handle identifier + * @return mixed Array with col values or FALSE on failure + * @access public + */ function fetch_assoc($res_id=NULL) { $result = $this->_get_result($res_id); @@ -222,30 +326,66 @@ class rcube_db } - function quote($input, $type=null) + /** + * Formats input so it can be safely used in a query + * + * @param mixed Value to quote + * @return string Quoted/converted string for use in query + * @access public + */ + function quote($input) { + // create DB handle if not available if (!$this->db_handle) $this->db_connect('r'); - - return $this->db_handle->quote($input); + + // escape pear identifier chars + $rep_chars = array('?' => '\?', + '!' => '\!', + '&' => '\&'); + + return $this->db_handle->quoteSmart(strtr($input, $rep_chars)); } + /** + * Quotes a string so it can be safely used as a table or column name + * + * @param string Value to quote + * @return string Quoted string for use in query + * @deprecated Replaced by rcube_db::quote_identifier + * @see rcube_db::quote_identifier + * @access public + */ function quoteIdentifier($str) { - if (!$this->db_handle) - $this->db_connect('r'); - - return $this->db_handle->quoteIdentifier($str); + return $this->quote_identifier($str); } + /** + * Quotes a string so it can be safely used as a table or column name + * + * @param string Value to quote + * @return string Quoted string for use in query + * @access public + */ function quote_identifier($str) { - return $this->quoteIdentifier($str); + if (!$this->db_handle) + $this->db_connect('r'); + + return $this->db_handle->quoteIdentifier($str); } + /** + * Return SQL statement to convert a field value into a unix timestamp + * + * @param string Field name + * @return string SQL statement to use in query + * @access public + */ function unixtimestamp($field) { switch($this->db_provider) @@ -260,6 +400,13 @@ class rcube_db } + /** + * Return SQL statement to convert from a unix timestamp + * + * @param string Field name + * @return string SQL statement to use in query + * @access public + */ function fromunixtime($timestamp) { switch($this->db_provider) @@ -275,13 +422,20 @@ class rcube_db } + /** + * Adds a query result and returns a handle ID + * + * @param object Query handle + * @return mixed Handle ID or FALE on failure + * @access private + */ function _add_result($res) { // sql error occured if (DB::isError($res)) { raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, - 'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 1024)), TRUE, FALSE); + 'message' => $res->getMessage() . " Query: " . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)), TRUE, FALSE); return FALSE; } else @@ -294,7 +448,15 @@ class rcube_db } - function _get_result($res_id) + /** + * Resolves a given handle ID and returns the according query handle + * If no ID is specified, the last ressource handle will be returned + * + * @param number Handle ID + * @return mixed Ressource handle or FALE on failure + * @access private + */ + function _get_result($res_id=NULL) { if ($res_id==NULL) $res_id = $this->last_res_id; @@ -306,16 +468,22 @@ class rcube_db } - // create a sqlite database from a file - function _sqlite_create_database($dbh, $fileName) + /** + * Create a sqlite database from a file + * + * @param object SQLite database handle + * @param string File path to use for DB creation + * @access private + */ + function _sqlite_create_database($dbh, $file_name) { - if (empty($fileName) || !is_string($fileName)) - return ; + if (empty($file_name) || !is_string($file_name)) + return; $data = ''; - if ($fd = fopen($fileName, 'r')) + if ($fd = fopen($file_name, 'r')) { - $data = fread($fd, filesize($fileName)); + $data = fread($fd, filesize($file_name)); fclose($fd); } @@ -323,6 +491,13 @@ class rcube_db sqlite_exec($dbh->connection, $data); } + + /** + * Add some proprietary database functions to the current SQLite handle + * in order to make it MySQL compatible + * + * @access private + */ function _sqlite_prepare() { include_once('include/rcube_sqlite.inc'); @@ -334,21 +509,7 @@ class rcube_db sqlite_create_function($this->db_handle->connection, "md5", "rcube_sqlite_md5"); } -/* - // transform a query so that it is sqlite2 compliant - function _sqlite_prepare_query($query) - { - if (!is_string($query)) - return ($query); - - - $search = array('/NOW\(\)/i', '/`/'); - $replace = array("datetime('now')", '"'); - $query = preg_replace($search, $replace, $query); - return ($query); - } -*/ } // end class rcube_db ?> \ No newline at end of file diff --git a/program/include/rcube_imap.inc b/program/include/rcube_imap.inc index 593225da2..7b71dc09c 100644 --- a/program/include/rcube_imap.inc +++ b/program/include/rcube_imap.inc @@ -21,11 +21,24 @@ */ +/** + * Obtain classes from the Iloha IMAP library + */ require_once('lib/imap.inc'); require_once('lib/mime.inc'); require_once('lib/utf7.inc'); +/** + * Interface class for accessing an IMAP server + * + * This is a wrapper that implements the Iloha IMAP Library (IIL) + * + * @package RoundCube Webmail + * @author Thomas Bruederli + * @version 1.22 + * @link http://ilohamail.org + */ class rcube_imap { var $db; @@ -46,33 +59,53 @@ class rcube_imap var $uid_id_map = array(); var $msg_headers = array(); var $capabilities = array(); + var $skip_deleted = FALSE; + var $debug_level = 1; - // PHP 5 constructor + /** + * Object constructor + * + * @param object Database connection + */ function __construct($db_conn) { $this->db = $db_conn; } - // PHP 4 compatibility + + /** + * PHP 4 object constructor + * + * @see rcube_imap::__construct + */ function rcube_imap($db_conn) { $this->__construct($db_conn); } + /** + * Connect to an IMAP server + * + * @param string Host to connect + * @param string Username for IMAP account + * @param string Password for IMAP account + * @param number Port to connect to + * @param boolean Use SSL connection + * @return boolean TRUE on success, FALSE on failure + * @access public + */ function connect($host, $user, $pass, $port=143, $use_ssl=FALSE) { - global $ICL_SSL, $ICL_PORT, $CONFIG; + global $ICL_SSL, $ICL_PORT; // check for Open-SSL support in PHP build if ($use_ssl && in_array('openssl', get_loaded_extensions())) $ICL_SSL = TRUE; else if ($use_ssl) { - raise_error(array('code' => 403, - 'type' => 'imap', - 'file' => __FILE__, + raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'message' => 'Open SSL not available;'), TRUE, FALSE); $port = 143; } @@ -86,7 +119,7 @@ class rcube_imap $this->ssl = $use_ssl; // print trace mesages - if ($this->conn && ($CONFIG['debug_level'] & 8)) + if ($this->conn && ($this->debug_level & 8)) console($this->conn->message); // write error log @@ -116,6 +149,12 @@ class rcube_imap } + /** + * Close IMAP connection + * Usually done on script shutdown + * + * @access public + */ function close() { if ($this->conn) @@ -123,6 +162,12 @@ class rcube_imap } + /** + * Close IMAP connection and re-connect + * This is used to avoid some strange socket errors when talking to Courier IMAP + * + * @access public + */ function reconnect() { $this->close(); @@ -130,6 +175,15 @@ class rcube_imap } + /** + * Set a root folder for the IMAP connection. + * + * Only folders within this root folder will be displayed + * and all folder paths will be translated using this folder name + * + * @param string Root folder + * @access public + */ function set_rootdir($root) { if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/') @@ -142,6 +196,12 @@ class rcube_imap } + /** + * This list of folders will be listed above all other folders + * + * @param array Indexed list of folder names + * @access public + */ function set_default_mailboxes($arr) { if (is_array($arr)) @@ -159,6 +219,14 @@ class rcube_imap } + /** + * Set internal mailbox reference. + * + * All operations will be perfomed on this mailbox/folder + * + * @param string Mailbox/Folder name + * @access public + */ function set_mailbox($mbox) { $mailbox = $this->_mod_mailbox($mbox); @@ -173,24 +241,49 @@ class rcube_imap } + /** + * Set internal list page + * + * @param number Page number to list + * @access public + */ function set_page($page) { $this->list_page = (int)$page; } + /** + * Set internal page size + * + * @param number Number of messages to display on one page + * @access public + */ function set_pagesize($size) { $this->page_size = (int)$size; } + /** + * Returns the currently used mailbox name + * + * @return string Name of the mailbox/folder + * @access public + */ function get_mailbox_name() { return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : ''; } + /** + * Returns the IMAP server's capability + * + * @param string Capability name + * @return mixed Capability value or TRUE if supported, FALSE if not + * @access public + */ function get_capability($cap) { $cap = strtoupper($cap); @@ -198,6 +291,12 @@ class rcube_imap } + /** + * Returns the delimiter that is used by the IMAP server for folder separation + * + * @return string Delimiter string + * @access public + */ function get_hierarchy_delimiter() { if ($this->conn && empty($this->delimiter)) @@ -209,8 +308,17 @@ class rcube_imap return $this->delimiter; } - // public method for mailbox listing - // convert mailbox name with root dir first + + /** + * Public method for mailbox listing. + * + * Converts mailbox name with root dir first + * + * @param string Optional root folder + * @param string Optional filter for mailbox listing + * @return array List of mailboxes/folders + * @access public + */ function list_mailboxes($root='', $filter='*') { $a_out = array(); @@ -229,7 +337,14 @@ class rcube_imap return $a_out; } - // private method for mailbox listing + + /** + * Private method for mailbox listing + * + * @return array List of mailboxes/folders + * @access private + * @see rcube_imap::list_mailboxes + */ function _list_mailboxes($root='', $filter='*') { $a_defaults = $a_out = array(); @@ -261,7 +376,7 @@ class rcube_imap } - // get message count for a specific mailbox; acceptes modes are: ALL, UNSEEN + // get message count for a specific mailbox; acceptes modes are: ALL, UNSEEN, RECENT function messagecount($mbox='', $mode='ALL', $force=FALSE) { $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; @@ -274,7 +389,7 @@ class rcube_imap $a_mailbox_cache = FALSE; $mode = strtoupper($mode); - if (!$mailbox) + if (empty($mailbox)) $mailbox = $this->mailbox; $a_mailbox_cache = $this->get_cache('messagecount'); @@ -282,22 +397,37 @@ class rcube_imap // return cached value if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode])) return $a_mailbox_cache[$mailbox][$mode]; - - $search_str = "ALL UNDELETED"; - // get message count and store in cache - if ($mode == 'UNSEEN') - $search_str .= " UNSEEN"; + // RECENT count is fetched abit different + if ($mode == 'RECENT') + $count = iil_C_CheckForRecent($this->conn, $mailbox); - // get message count using SEARCH - // not very performant but more precise (using UNDELETED) - $count = 0; - $index = $this->_search_index($mailbox, $search_str); - if (is_array($index)) + // use SEARCH for message counting + else if ($this->skip_deleted) { - $str = implode(",", $index); - if (!empty($str)) - $count = count($index); + $search_str = "ALL UNDELETED"; + + // get message count and store in cache + if ($mode == 'UNSEEN') + $search_str .= " UNSEEN"; + + // get message count using SEARCH + // not very performant but more precise (using UNDELETED) + $count = 0; + $index = $this->_search_index($mailbox, $search_str); + if (is_array($index)) + { + $str = implode(",", $index); + if (!empty($str)) + $count = count($index); + } + } + else + { + if ($mode == 'UNSEEN') + $count = iil_C_CountUnseen($this->conn, $mailbox); + else + $count = iil_C_CountMessages($this->conn, $mailbox); } if (is_array($a_mailbox_cache[$mailbox])) @@ -348,7 +478,7 @@ class rcube_imap else { $begin = $start_msg; - $end = $start_msg + $this->page_size; + $end = $start_msg + $this->page_size; } if ($begin < 0) $begin = 0; @@ -372,16 +502,16 @@ class rcube_imap else { // retrieve headers from IMAP - if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field))) + if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''))) { -//console("$mailbox: ".count($msg_index)); +//console("$mailbox: ".join(',', $msg_index)); $msgs = $msg_index[$begin]; - for ($i=$begin; $i < $end; $i++) + for ($i=$begin+1; $i < $end; $i++) { - if ($this->sort_order == 'DESC') - $msgs = $msg_index[$i].','.$msgs; - else + //if ($this->sort_order == 'DESC') + // $msgs = $msg_index[$i].','.$msgs; + //else $msgs = $msgs.','.$msg_index[$i]; } @@ -401,44 +531,16 @@ class rcube_imap return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE); } - - // cache is incomplete - $cache_index = $this->get_message_cache_index($cache_key); // fetch reuested headers from server $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs); $a_msg_headers = array(); - $deleted_count = 0; - - if (!empty($a_header_index)) - { - foreach ($a_header_index as $i => $headers) - { - if ($headers->deleted) - { - // delete from cache - if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid) - $this->remove_message_cache($cache_key, $headers->id); - - $deleted_count++; - continue; - } - - // add message to cache - if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) - $this->add_message_cache($cache_key, $headers->id, $headers); - - $a_msg_headers[$headers->uid] = $headers; - } - } + $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key); // delete cached messages with a higher index than $max $this->clear_message_cache($cache_key, $max); - - // fetch more headers of there were any deleted messages - // ... - + // kick child process to sync cache // ... @@ -458,6 +560,53 @@ class rcube_imap } + /** + * Fetches message headers + * Used for loop + * + * @param string Mailbox name + * @param string Message indey to fetch + * @param array Reference to message headers array + * @param array Array with cache index + * @return number Number of deleted messages + * @access private + */ + function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) + { + // cache is incomplete + $cache_index = $this->get_message_cache_index($cache_key); + + // fetch reuested headers from server + $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs); + $deleted_count = 0; + + if (!empty($a_header_index)) + { + foreach ($a_header_index as $i => $headers) + { + if ($headers->deleted && $this->skip_deleted) + { + // delete from cache + if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid) + $this->remove_message_cache($cache_key, $headers->id); + + $deleted_count++; + continue; + } + + // add message to cache + if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) + $this->add_message_cache($cache_key, $headers->id, $headers); + + $a_msg_headers[$headers->uid] = $headers; + } + } + + return $deleted_count; + } + + + // return sorted array of message UIDs function message_index($mbox='', $sort_field=NULL, $sort_order=NULL) { @@ -584,7 +733,7 @@ class rcube_imap } - function get_headers($uid, $mbox=NULL) + function get_headers($id, $mbox=NULL, $is_uid=TRUE) { $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; @@ -592,7 +741,7 @@ class rcube_imap if ($headers = $this->get_cached_message($mailbox.'.msg', $uid)) return $headers; - $msg_id = $this->_uid2id($uid); + $msg_id = $is_uid ? $this->_uid2id($id) : $id; $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id); // write headers cache @@ -802,9 +951,9 @@ class rcube_imap // clear all messages in a specific mailbox - function clear_mailbox($mbox) + function clear_mailbox($mbox=NULL) { - $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; + $mailbox = !empty($mbox) ? $this->_mod_mailbox($mbox) : $this->mailbox; $msg_count = $this->_messagecount($mailbox, 'ALL'); if ($msg_count>0) @@ -1270,10 +1419,10 @@ class rcube_imap $key, $index, $headers->uid, - $this->decode_header($headers->subject, TRUE), - $this->decode_header($headers->from, TRUE), - $this->decode_header($headers->to, TRUE), - $this->decode_header($headers->cc, TRUE), + substr($this->decode_header($headers->subject, TRUE), 0, 128), + substr($this->decode_header($headers->from, TRUE), 0, 128), + substr($this->decode_header($headers->to, TRUE), 0, 128), + substr($this->decode_header($headers->cc, TRUE), 0, 128), (int)$headers->size, serialize($headers)); } diff --git a/program/js/app.js b/program/js/app.js index 213f62b21..dc0275c4b 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6,11 +6,11 @@ | Copyright (C) 2005, RoundCube Dev, - Switzerland | | Licensed under the GNU GPL | | | - | Modified: 2005/12/16 (roundcube) | - | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | +-----------------------------------------------------------------------+ + + $Id$ */ @@ -137,7 +137,7 @@ function rcube_webmail() this.enable_command('add-attachment', 'send-attachment', 'send', true); if (this.env.messagecount) - this.enable_command('select-all', 'select-none', 'sort', true); + this.enable_command('select-all', 'select-none', 'sort', 'expunge', true); this.set_page_buttons(); @@ -151,6 +151,10 @@ function rcube_webmail() // show printing dialog if (this.env.action=='print') window.print(); + + // get unread count for each mailbox + if (this.gui_objects.mailboxlist) + this.http_request('getunread', ''); break; @@ -219,9 +223,9 @@ function rcube_webmail() if (this.pending_message) this.display_message(this.pending_message[0], this.pending_message[1]); - // start interval for keep-alive siganl + // start interval for keep-alive/recent_check signal if (this.kepp_alive_interval) - this.kepp_alive_int = setInterval(this.ref+'.send_keep_alive()', this.kepp_alive_interval); + this.kepp_alive_int = setInterval(this.ref+'.'+(this.task=='mail'?'check_for_recent()':'send_keep_alive()'), this.kepp_alive_interval); }; @@ -436,6 +440,15 @@ function rcube_webmail() return false; } + + + // check input before leaving compose step + if (this.task=='mail' && this.env.action=='compose' && (command=='list' || command=='mail' || command=='addressbook' || command=='settings')) + { + if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning'))) + return false; + } + // process command switch (command) @@ -460,13 +473,7 @@ function rcube_webmail() // misc list commands case 'list': if (this.task=='mail') - { - // check input before leaving compose step - if (this.env.action=='compose' && this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning'))) - break; - this.list_mailbox(props); - } else if (this.task=='addressbook') this.list_contacts(); break; @@ -512,6 +519,16 @@ function rcube_webmail() this.list_page('prev'); break; + case 'expunge': + if (this.env.messagecount) + this.expunge_mailbox(this.env.mailbox); + break; + + case 'clear-mailbox': + //if (this.env.messagecount) + //this.clear_mailbox(this.env.mailbox); + break; + // common commands used in multiple tasks case 'show': @@ -1168,7 +1185,18 @@ function rcube_webmail() // send remote request to load message list this.list_mailbox_remote = function(mbox, page, add_url) { - // clear message list + // clear message list first + this.clear_message_list(); + + // send request to server + var url = '_mbox='+escape(mbox)+(page ? '&_page='+page : ''); + this.set_busy(true, 'loading'); + this.http_request('list', url+add_url, true); + }; + + + this.clear_message_list = function() + { var table = this.gui_objects.messagelist; var tbody = document.createElement('TBODY'); table.insertBefore(tbody, table.tBodies[0]); @@ -1176,11 +1204,26 @@ function rcube_webmail() this.message_rows = new Array(); this.list_rows = this.message_rows; + + }; + + + this.expunge_mailbox = function(mbox) + { + var lock = false; + var add_url = ''; + + // lock interface if it's the active mailbox + if (mbox == this.env.mailbox) + { + lock = true; + this.set_busy(true, 'loading'); + add_url = '&_reload=1'; + } // send request to server - var url = '_mbox='+escape(mbox)+(page ? '&_page='+page : ''); - this.set_busy(true, 'loading'); - this.http_request('list', url+add_url, true); + var url = '_mbox='+escape(mbox); + this.http_request('expunge', url+add_url, lock); }; @@ -2263,7 +2306,7 @@ function rcube_webmail() // create a table row in the message list - this.add_message_row = function(uid, cols, flags, attachment) + this.add_message_row = function(uid, cols, flags, attachment, attop) { if (!this.gui_objects.messagelist || !this.gui_objects.messagelist.tBodies[0]) return false; @@ -2277,8 +2320,8 @@ function rcube_webmail() var row = document.createElement('TR'); row.id = 'rcmrow'+uid; - row.className = 'message '+(even ? 'even' : 'odd')+(flags.unread ? ' unread' : ''); - + row.className = 'message '+(even ? 'even' : 'odd')+(flags.unread ? ' unread' : '')+(flags.deleted ? ' deleted' : ''); + if (this.in_selection(uid)) row.className += ' selected'; @@ -2304,7 +2347,11 @@ function rcube_webmail() col.innerHTML = attachment && this.env.attachmenticon ? '' : ''; row.appendChild(col); - tbody.appendChild(row); + if (attop && tbody.rows.length) + tbody.insertBefore(row, tbody.firstChild); + else + tbody.appendChild(row); + this.init_message_row(row); }; @@ -2321,35 +2368,44 @@ function rcube_webmail() // update the mailboxlist - this.set_unread_count = function(mbox, count) + this.set_unread_count = function(mbox, count, set_title) { if (!this.gui_objects.mailboxlist) return false; - mbox = String(mbox).toLowerCase().replace(this.mbox_expression, ''); - var item, reg, text_obj; - for (var n=0; n=0) { - item = this.gui_objects.mailboxlist.childNodes[n]; + // set new text + text_obj = item.firstChild; + reg = /\s+\([0-9]+\)$/i; - if (item.className && item.className.indexOf('mailbox '+mbox)>=0) - { - // set new text - text_obj = item.firstChild; - reg = /\s+\([0-9]+\)$/i; - - if (count && text_obj.innerHTML.match(reg)) - text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+count+')'); - else if (count) - text_obj.innerHTML += ' ('+count+')'; - else - text_obj.innerHTML = text_obj.innerHTML.replace(reg, ''); + if (count && text_obj.innerHTML.match(reg)) + text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+count+')'); + else if (count) + text_obj.innerHTML += ' ('+count+')'; + else + text_obj.innerHTML = text_obj.innerHTML.replace(reg, ''); - // set the right classes - this.set_classname(item, 'unread', count>0 ? true : false); - break; - } + // set the right classes + this.set_classname(item, 'unread', count>0 ? true : false); + } + + // set unread count to window title + if (set_title && document.title) + { + var doc_title = String(document.title); + reg = /^\([0-9]+\)\s+/i; + + if (count && doc_title.match(reg)) + document.title = doc_title.replace(reg, '('+count+') '); + else if (count) + document.title = '('+count+') '+doc_title; + else + document.title = doc_title.replace(reg, ''); } }; @@ -2527,9 +2583,10 @@ function rcube_webmail() if (this.env.action=='show') this.command('list'); break; - + case 'list': - this.enable_command('select-all', 'select-none', this.env.messagecount ? true : false); + case 'expunge': + this.enable_command('select-all', 'select-none', 'expunge', this.env.messagecount ? true : false); break; } @@ -2556,7 +2613,14 @@ function rcube_webmail() var d = new Date(); this.http_request('keep-alive', '_t='+d.getTime()); }; + + // send periodic request to check for recent messages + this.check_for_recent = function() + { + var d = new Date(); + this.http_request('check-recent', '_t='+d.getTime()); + }; /********************************************************/ diff --git a/program/js/common.js b/program/js/common.js index 0c9917ad9..02f698b90 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -269,7 +269,7 @@ function rcube_check_email(input, inline) { if (input && window.RegExp) { - var reg_str = '([a-z0-9][-a-z0-9\.\+_]*)\@([a-z0-9]([-a-z0-9][\.]?)*[a-z0-9]\.[a-z]{2,9})'; + var reg_str = '([a-z0-9][-a-z0-9\.\+_]*)\@(([-a-z0-9][\.]?)*[a-z0-9]\.[a-z]{2,9})'; var reg1 = inline ? new RegExp(reg_str, 'i') : new RegExp('^'+reg_str+'$', 'i'); var reg2 = /[\._\-\@]{2}/; return reg1.test(input) && !reg2.test(input) ? true : false; diff --git a/program/lib/imap.inc b/program/lib/imap.inc index a1dbd7b98..daacb03b4 100644 --- a/program/lib/imap.inc +++ b/program/lib/imap.inc @@ -607,7 +607,7 @@ function iil_StrToTime($str){ return $time2; } -function iil_C_Sort(&$conn, $mailbox, $field){ +function iil_C_Sort(&$conn, $mailbox, $field, $add=''){ /* Do "SELECT" command */ if (!iil_C_Select($conn, $mailbox)) return false; @@ -618,7 +618,8 @@ function iil_C_Sort(&$conn, $mailbox, $field){ if (!$fields[$field]) return false; $fp = $conn->fp; - $command = 's SORT ('.$field.') US-ASCII ALL UNDELETED'."\r\n"; + $command = 's SORT ('.$field.') US-ASCII ALL '."$add\r\n"; + //$command = 's SORT ('.$field.') US-ASCII ALL UNDELETED'."\r\n"; $line = $data = ''; if (!fputs($fp, $command)) return false; diff --git a/program/localization/de/labels.inc b/program/localization/de/labels.inc index 12ec06e8c..ae0cfa3d2 100644 --- a/program/localization/de/labels.inc +++ b/program/localization/de/labels.inc @@ -105,6 +105,9 @@ $labels['all'] = 'Alle'; $labels['none'] = 'Keine'; $labels['unread'] = 'Ungelesene'; +$labels['compact'] = 'Packen'; + + // message compose // Nachrichten erstellen $labels['compose'] = 'Neue Nachricht verfassen'; $labels['sendmessage'] = 'Nachricht jetzt senden'; @@ -167,6 +170,7 @@ $labels['timezone'] = 'Zeitzone'; $labels['pagesize'] = 'Einträge pro Seite'; $labels['signature'] = 'Signatur'; +$labels['folder'] = 'Ordner'; $labels['folders'] = 'Ordner'; $labels['foldername'] = 'Ordnername'; $labels['subscribed'] = 'Abonniert'; diff --git a/program/localization/en/labels.inc b/program/localization/en/labels.inc index b37b48140..086c3080f 100644 --- a/program/localization/en/labels.inc +++ b/program/localization/en/labels.inc @@ -105,6 +105,9 @@ $labels['all'] = 'All'; $labels['none'] = 'None'; $labels['unread'] = 'Unread'; +$labels['compact'] = 'Compact'; + + // message compose $labels['compose'] = 'Compose a message'; $labels['sendmessage'] = 'Send the message now'; @@ -167,6 +170,7 @@ $labels['timezone'] = 'Time zone'; $labels['pagesize'] = 'Rows per page'; $labels['signature'] = 'Signature'; +$labels['folder'] = 'Folder'; $labels['folders'] = 'Folders'; $labels['foldername'] = 'Folder name'; $labels['subscribed'] = 'Subscribed'; diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc new file mode 100644 index 000000000..fbf8871f9 --- /dev/null +++ b/program/steps/mail/check_recent.inc @@ -0,0 +1,47 @@ + | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +$REMOTE_REQUEST = TRUE; +$mbox = $IMAP->get_mailbox_name(); + +if ($recent_count = $IMAP->messagecount(NULL, 'RECENT')) + { + $count = $IMAP->messagecount(); + $unread_count = $IMAP->messagecount(NULL, 'UNSEEN'); + + $commands = sprintf("this.set_unread_count('%s', %d, true);\n", addslashes($mbox), $unread_count); + $commands .= sprintf("this.set_env('messagecount', %d);\n", $count); + $commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text()); + + // add new message headers to list + $a_headers = array(); + for ($i=$recent_count, $id=$count-$recent_count+1; $i>0; $i--, $id++) + $a_headers[] = $IMAP->get_headers($id, NULL, FALSE); + + $commands .= rcmail_js_message_list($a_headers, TRUE); + } + +if (strtoupper($mbox)!='INBOX' && $IMAP->messagecount('INBOX', 'RECENT')) + $commands = sprintf("this.set_unread_count('INBOX', %d);\n", $IMAP->messagecount('INBOX', 'UNSEEN')); + + +rcube_remote_response($commands); +?> \ No newline at end of file diff --git a/program/steps/mail/folders.inc b/program/steps/mail/folders.inc new file mode 100644 index 000000000..e1730ef93 --- /dev/null +++ b/program/steps/mail/folders.inc @@ -0,0 +1,61 @@ + | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +$REMOTE_REQUEST = TRUE; +$mbox = $IMAP->get_mailbox_name(); + + +// send EXPUNGE command +if ($_action=='expunge') + { + $success = $IMAP->expunge(); + + // reload message list if current mailbox + if ($success && $_GET['_reload']) + { + rcube_remote_response('this.clear_message_list();', TRUE); + $_action = 'list'; + return; + } + else + $commands = "// expunged: $success\n"; + } + +// clear mailbox +else if ($_action=='purge') + { + $success = $IMAP->clear_mailbox(); + + if ($success && $_GET['_reload']) + { + $commands = "this.set_env('messagecount', 0);\n"; + $commands .= "this.set_env('pagecount', 0);\n"; + $commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text()); + $commands .= sprintf("this.set_unread_count('%s', 0);\n", addslashes($mbox)); + } + else + $commands = "// purged: $success"; + } + + + +rcube_remote_response($commands); +?> \ No newline at end of file diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index c430467d1..8ebd1c59c 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -69,6 +69,8 @@ function rcmail_mailbox_list($attrib) static $s_added_script = FALSE; static $a_mailboxes; +// $mboxlist_start = rcube_timer(); + $type = $attrib['type'] ? $attrib['type'] : 'ul'; $add_attrib = $type=='select' ? array('style', 'class', 'id', 'name', 'onchange') : array('style', 'class', 'id'); @@ -100,7 +102,9 @@ function rcmail_mailbox_list($attrib) $a_folders = $IMAP->list_mailboxes(); $delimiter = $IMAP->get_hierarchy_delimiter(); $a_mailboxes = array(); - + +// rcube_print_time($mboxlist_start, 'list_mailboxes()'); + foreach ($a_folders as $folder) rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter); } @@ -112,6 +116,8 @@ function rcmail_mailbox_list($attrib) else $out .= rcmail_render_folder_tree_html($a_mailboxes, $special_mailboxes, $mbox, $attrib['maxlength']); +// rcube_print_time($mboxlist_start, 'render_folder_tree()'); + if ($type=='ul') $OUTPUT->add_script(sprintf("%s.gui_object('mailboxlist', '%s');", $JS_OBJECT_NAME, $attrib['id'])); @@ -181,7 +187,7 @@ function rcmail_render_folder_tree_html(&$arrFolders, &$special, &$mbox, $maxlen } // add unread message count display - if ($unread_count = $IMAP->messagecount($folder['id'], 'UNSEEN', ($folder['id']==$mbox))) + if ($unread_count = $IMAP->messagecount($folder['id'], 'RECENT', ($folder['id']==$mbox))) $foldername .= sprintf(' (%d)', $unread_count); // make folder name safe for ids and class names @@ -397,7 +403,12 @@ function rcmail_message_list($attrib) if ($attrib['attachmenticon'] && preg_match("/multipart\/m/i", $header->ctype)) $attach_icon = $attrib['attachmenticon']; - $out .= sprintf(''."\n", $header->uid); + $out .= sprintf(''."\n", + $header->uid, + $header->seen ? '' : ' unread', + $header->deleted ? ' deleted' : '', + $zebra_class); + $out .= sprintf("%s\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : ''); // format each col @@ -495,12 +506,16 @@ function rcmail_js_message_list($a_headers, $insert_top=FALSE) $a_msg_flags['unread'] = $header->seen ? 0 : 1; $a_msg_flags['replied'] = $header->answered ? 1 : 0; + + if ($header->deleted) + $a_msg_flags['deleted'] = 1; - $commands .= sprintf("this.add_message_row(%s, %s, %s, %b);\n", + $commands .= sprintf("this.add_message_row(%s, %s, %s, %b, %b);\n", $header->uid, array2js($a_msg_cols), array2js($a_msg_flags), - preg_match("/multipart\/m/i", $header->ctype)); + preg_match("/multipart\/m/i", $header->ctype), + $insert_top); } return $commands; @@ -1377,11 +1392,11 @@ function rcmail_compose_cleanup() // remove attachment files from temp dir if (is_array($_SESSION['compose']['attachments'])) foreach ($_SESSION['compose']['attachments'] as $attachment) - unlink($attachment['path']); + @unlink($attachment['path']); // kill temp dir if ($_SESSION['compose']['temp_dir']) - rmdir($_SESSION['compose']['temp_dir']); + @rmdir($_SESSION['compose']['temp_dir']); unset($_SESSION['compose']); } diff --git a/program/steps/mail/getunread.inc b/program/steps/mail/getunread.inc new file mode 100644 index 000000000..d35dcf9f1 --- /dev/null +++ b/program/steps/mail/getunread.inc @@ -0,0 +1,36 @@ + | + +-----------------------------------------------------------------------+ + + $Id$ + +*/ + +$REMOTE_REQUEST = TRUE; + +$a_folders = $IMAP->list_mailboxes(); + +if (!empty($a_folders)) + { + foreach ($a_folders as $mbox) + { + $commands = sprintf("this.set_unread_count('%s', %d);\n", $mbox, $IMAP->messagecount($mbox, 'UNSEEN')); + rcube_remote_response($commands, TRUE); + } + } + +exit; +?> \ No newline at end of file diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index 3c9f60378..c29fcf1d3 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -224,14 +224,17 @@ else { // unset some headers because they will be added by the mail() function $headers_php = $headers; + $headers_enc = $MAIL_MIME->headers($headers); unset($headers_php['To'], $headers_php['Subject']); + // reset stored headers and overwrite + $MAIL_MIME->_headers = array(); $header_str = $MAIL_MIME->txtHeaders($headers_php); if(ini_get('safe_mode')) - $sent = mail($mailto, $msg_subject, $msg_body, $header_str); - else - $sent = mail($mailto, $msg_subject, $msg_body, $header_str, "-f$from"); + $sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str); + else + $sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str, "-f$from"); } diff --git a/program/steps/mail/upload.inc b/program/steps/mail/upload.inc index 308ec796e..4cd929d0d 100644 --- a/program/steps/mail/upload.inc +++ b/program/steps/mail/upload.inc @@ -39,7 +39,7 @@ $response = ''; foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) { $tmpfname = tempnam($temp_dir, 'rcmAttmnt'); - if (copy($filepath, $tmpfname)) + if (move_uploaded_file($filepath, $tmpfname)) { $_SESSION['compose']['attachments'][] = array('name' => $_FILES['_attachments']['name'][$i], 'mimetype' => $_FILES['_attachments']['type'][$i], -- cgit v1.2.3