From 15a9d1ce671fcbc44ea3e4858d7aa6f5b22300c9 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Wed, 04 Jan 2006 19:37:10 -0500
Subject: [PATCH] Optimized loading time; added periodic mail check; added EXPUNGE command

---
 CHANGELOG                           |   14 
 INSTALL                             |    1 
 program/js/common.js                |    2 
 program/include/main.inc            |  106 ++++
 skins/default/mail.css              |   30 +
 program/lib/imap.inc                |    5 
 SQL/mysql.update.sql                |    4 
 UPGRADING                           |    7 
 config/main.inc.php.dist            |    3 
 program/localization/en/labels.inc  |    4 
 program/steps/mail/sendmail.inc     |    9 
 index.php                           |   14 
 program/include/rcube_db.inc        |  253 ++++++++++--
 skins/default/templates/mail.html   |    5 
 program/steps/mail/upload.inc       |    2 
 program/steps/mail/folders.inc      |   61 +++
 program/localization/de/labels.inc  |    4 
 program/steps/mail/check_recent.inc |   47 ++
 program/steps/mail/func.inc         |   29 +
 program/steps/mail/getunread.inc    |   36 +
 SQL/postgres.initial.sql            |   74 ---
 SQL/mysql.initial.sql               |    2 
 program/js/app.js                   |  148 +++++--
 program/include/rcube_imap.inc      |  289 ++++++++++---
 24 files changed, 888 insertions(+), 261 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 5f6e380..facd5b9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,20 @@
 CHANGELOG RoundCube Webmail
 ---------------------------
 
+2006/01/04
+----------
+- Fixed bug when inserting signatures with !?&
+- Chopping message headers before inserting into the message cache table (to avoid bugs in Postgres)
+- Allow one-char domains in e-mail addresses
+- Make product name in page title configurable
+- Make username available as skin object
+- Added session_write_close() in rcube_db class destructor to avoid problems in PHP 5.0.5
+- Use move_uploaded_file() instead of copy() for a more secure handling of uploaded attachments
+- Additional config parameter to show/hide deleted messages
+- Added periodic request for checking new mails (Request #1307821)
+- Added EXPUNGE command
+- Optimized loading time for mail interface
+
 
 2005/12/16
 ----------
diff --git a/INSTALL b/INSTALL
index f343c82..92b8bee 100644
--- a/INSTALL
+++ b/INSTALL
@@ -76,6 +76,7 @@
 * The Apache Webserver
 * .htaccess support allowing overrides for DirectoryIndex
 * PHP Version 4.3.1 or greater
+* PCRE (perl compatible regular expression) installed with PHP
 * php.ini options:
    - error_reporting E_ALL & ~E_NOTICE (or lower)
    - file_uploads on (for attachment upload features)
diff --git a/SQL/mysql.initial.sql b/SQL/mysql.initial.sql
index 21444ed..eabc75e 100644
--- a/SQL/mysql.initial.sql
+++ b/SQL/mysql.initial.sql
@@ -91,7 +91,7 @@
   `created` datetime NOT NULL default '0000-00-00 00:00:00',
   `last_login` datetime NOT NULL default '0000-00-00 00:00:00',
   `language` varchar(5) NOT NULL default 'en',
-  `preferences` text NOT NULL,
+  `preferences` text NOT NULL default '',
   PRIMARY KEY  (`user_id`)
 ) TYPE=MyISAM;
 
diff --git a/SQL/mysql.update.sql b/SQL/mysql.update.sql
index 778919a..e93fc98 100644
--- a/SQL/mysql.update.sql
+++ b/SQL/mysql.update.sql
@@ -2,8 +2,8 @@
 -- Version 0.1-20051007
 
 
-ALTER TABLE session ADD ip VARCHAR(15) NOT NULL AFTER changed;
-ALTER TABLE users ADD alias VARCHAR(128) NOT NULL AFTER mail_host;
+ALTER TABLE `session` ADD `ip` VARCHAR(15) NOT NULL AFTER changed;
+ALTER TABLE `users` ADD `alias` VARCHAR(128) NOT NULL AFTER mail_host;
 
 
 
diff --git a/SQL/postgres.initial.sql b/SQL/postgres.initial.sql
index 554614e..b251755 100755
--- a/SQL/postgres.initial.sql
+++ b/SQL/postgres.initial.sql
@@ -68,7 +68,7 @@
 --
 
 CREATE TABLE users (
-    user_id integer DEFAULT nextval('user_ids'::text) NOT NULL,
+    user_id integer DEFAULT nextval('user_ids'::text) PRIMARY KEY,
     username character varying(128) DEFAULT ''::character varying NOT NULL,
     mail_host character varying(128) DEFAULT ''::character varying NOT NULL,
     alias character varying(128) DEFAULT ''::character varying NOT NULL,
@@ -86,7 +86,7 @@
 --
 
 CREATE TABLE "session" (
-    sess_id character varying(40) DEFAULT ''::character varying NOT NULL,
+    sess_id character varying(40) DEFAULT ''::character varying PRIMARY KEY,
     created timestamp with time zone DEFAULT now() NOT NULL,
     changed timestamp with time zone DEFAULT now() NOT NULL,
     ip character varying(16) NOT NULL,
@@ -101,8 +101,8 @@
 --
 
 CREATE TABLE identities (
-    identity_id integer DEFAULT nextval('identity_ids'::text) NOT NULL,
-    user_id integer DEFAULT 0 NOT NULL,
+    identity_id integer DEFAULT nextval('identity_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
     del integer DEFAULT 0 NOT NULL,
     standard integer DEFAULT 0 NOT NULL,
     name character varying(128) NOT NULL,
@@ -120,8 +120,8 @@
 --
 
 CREATE TABLE contacts (
-    contact_id integer DEFAULT nextval('contact_ids'::text) NOT NULL,
-    user_id integer DEFAULT 0 NOT NULL,
+    contact_id integer DEFAULT nextval('contact_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
     changed timestamp with time zone DEFAULT now() NOT NULL,
     del integer DEFAULT 0 NOT NULL,
     name character varying(128) DEFAULT ''::character varying NOT NULL,
@@ -139,9 +139,9 @@
 --
 
 CREATE TABLE "cache" (
-    cache_id integer DEFAULT nextval('cache_ids'::text) NOT NULL,
-    user_id integer DEFAULT 0 NOT NULL,
-    session_id character varying(40),
+    cache_id integer DEFAULT nextval('cache_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
+    session_id character varying(40) REFERENCES "session" (session_id),
     cache_key character varying(128) DEFAULT ''::character varying NOT NULL,
     created timestamp with time zone DEFAULT now() NOT NULL,
     data text NOT NULL
@@ -155,8 +155,8 @@
 --
 
 CREATE TABLE "messages" (
-    message_id integer DEFAULT nextval('message_ids'::text) NOT NULL,
-    user_id integer DEFAULT 0 NOT NULL,
+    message_id integer DEFAULT nextval('message_ids'::text) PRIMARY KEY,
+    user_id integer NOT NULL REFERENCES users (user_id),
     del integer DEFAULT 0 NOT NULL,
     cache_key character varying(128) DEFAULT ''::character varying NOT NULL,
     idx integer DEFAULT 0 NOT NULL,
@@ -170,56 +170,4 @@
     headers text NOT NULL,
     body text
 );
-
-
-
---
--- Add primary keys
---
-
-ALTER TABLE ONLY "cache"
-    ADD CONSTRAINT cache_pkey PRIMARY KEY (cache_id);
-
-
-ALTER TABLE ONLY "contacts"
-    ADD CONSTRAINT contacts_pkey PRIMARY KEY (contact_id);
-
-
-ALTER TABLE ONLY identities
-    ADD CONSTRAINT identities_pkey PRIMARY KEY (identity_id);
-
-
-ALTER TABLE ONLY "session"
-    ADD CONSTRAINT session_pkey PRIMARY KEY (sess_id);
-
-
-ALTER TABLE ONLY "users"
-    ADD CONSTRAINT users_pkey PRIMARY KEY (user_id);
-
-
-ALTER TABLE ONLY "messages"
-    ADD CONSTRAINT messages_pkey PRIMARY KEY (message_id);
-
-
---
--- Reference keys
---
-
-ALTER TABLE ONLY "cache"
-    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
-
-ALTER TABLE ONLY "cache"
-    ADD CONSTRAINT "$2" FOREIGN KEY (session_id) REFERENCES "session"(sess_id);
-
-
-ALTER TABLE ONLY "contacts"
-    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
-
-
-ALTER TABLE ONLY "identities"
-    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
-
-
-ALTER TABLE ONLY "messages"
-    ADD CONSTRAINT "$1" FOREIGN KEY (user_id) REFERENCES users(user_id);
 
diff --git a/UPGRADING b/UPGRADING
index fbfd47b..21512c1 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -19,6 +19,7 @@
   $rcmail_config['smtp_port'] = 25;
   $rcmail_config['default_port'] = 143;
   $rcmail_config['session_lifetime'] = 20;
+  $rcmail_config['skip_deleted'] = FALSE;
   $rcmail_config['message_sort_col'] = 'date';
   $rcmail_config['message_sort_order'] = 'DESC';
   $rcmail_config['log_dir'] = 'logs/';
@@ -41,6 +42,7 @@
   $rcmail_config['smtp_port'] = 25;
   $rcmail_config['default_port'] = 143;
   $rcmail_config['session_lifetime'] = 20;
+  $rcmail_config['skip_deleted'] = FALSE;
   $rcmail_config['message_sort_col'] = 'date';
   $rcmail_config['message_sort_order'] = 'DESC';
   $rcmail_config['log_dir'] = 'logs/';
@@ -61,6 +63,7 @@
 - add these lines to /config/main.inc.php
   $rcmail_config['smtp_auth_type'] = '';  // if you need to specify an auth method for SMTP
   $rcmail_config['session_lifetime'] = 20;  // to specify the session lifetime in minutes
+  $rcmail_config['skip_deleted'] = FALSE;
   $rcmail_config['message_sort_col'] = 'date';
   $rcmail_config['message_sort_order'] = 'DESC';
   $rcmail_config['log_dir'] = 'logs/';
@@ -81,6 +84,7 @@
 - replace all files in folder /skins/default/
 - run all commands in SQL/*.update.sql or re-initalize database with *.initial.sql
 - add these lines to /config/main.inc.php
+  $rcmail_config['skip_deleted'] = FALSE;
   $rcmail_config['message_sort_col'] = 'date';
   $rcmail_config['message_sort_order'] = 'DESC';
   $rcmail_config['log_dir'] = 'logs/';
@@ -91,4 +95,5 @@
   $rcmail_config['db_sequence_identity_ids'] = 'identity_ids';
   $rcmail_config['db_sequence_contact_ids'] = 'contact_ids';
   $rcmail_config['db_sequence_cache_ids'] = 'cache_ids';
-  $rcmail_config['db_sequence_message_ids'] = 'message_ids';  
\ No newline at end of file
+  $rcmail_config['db_sequence_message_ids'] = 'message_ids';
+  
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index dc14776..0ea3864 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -97,6 +97,9 @@
 // add this user-agent to message headers when sending
 $rcmail_config['useragent'] = 'RoundCube Webmail/0.1b';
 
+// use this name to compose page titles
+$rcmail_config['product_name'] = 'RoundCube Webmail';
+
 // only list folders within this path
 $rcmail_config['imap_root'] = '';
 
diff --git a/index.php b/index.php
index ee65f8a..f864aca 100644
--- a/index.php
+++ b/index.php
@@ -3,7 +3,7 @@
 /*
  +-----------------------------------------------------------------------+
  | RoundCube Webmail IMAP Client                                         |
- | Version 0.1-20051214                                                  |
+ | Version 0.1-20060104                                                  |
  |                                                                       |
  | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
  | Licensed under the GNU GPL                                            |
@@ -40,6 +40,9 @@
  $Id$
 
 */
+
+define('RCMAIL_VERSION', '0.1-20060104');
+
 
 // define global vars
 $INSTALL_PATH = dirname($_SERVER['SCRIPT_FILENAME']);
@@ -240,6 +243,15 @@
 
   if ($_action=='addcontact')
     include('program/steps/mail/addcontact.inc');
+
+  if ($_action=='expunge')
+    include('program/steps/mail/folders.inc');
+
+  if ($_action=='check-recent')
+    include('program/steps/mail/check_recent.inc');
+
+  if ($_action=='getunread')
+    include('program/steps/mail/getunread.inc');
     
   if ($_action=='list' && $_GET['_remote'])
     include('program/steps/mail/list.inc');
diff --git a/program/include/main.inc b/program/include/main.inc
index 4cfb5b2..24110d3 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -146,6 +146,9 @@
   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 @@
 
 
 // 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 @@
       $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,31 +935,27 @@
         '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 @@
   }
 
 
+// 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 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 acb13ce..e54dcc9 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 <david@ols.es>
+ * @author     Thomas Bruederli <roundcube@gmail.com>
+ * @version    1.14
+ * @link       http://pear.php.net/package/DB
+ */
 class rcube_db
   {
   var $db_dsnw;               // DSN for write operations
@@ -34,8 +50,13 @@
   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 @@
     }
 
 
-  // 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 @@
     }
 
 
-  // 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 @@
       if ($this->db_mode==$mode)
         return;
       }
-            
+     
     if ($mode=='r')
       $dsn = $this->db_dsnr;
     else
@@ -110,7 +156,14 @@
     }
 
 
-  // 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 @@
     }
 
 
-  // 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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
-  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 @@
     }
 
 
+  /**
+   * 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 @@
       {
       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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
-  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)
 	{
+    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)
+    {
     if (!$this->db_handle)
       $this->db_connect('r');
 			
     return $this->db_handle->quoteIdentifier($str);
-	}
-
-
-  function quote_identifier($str)
-    {
-    return $this->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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
-  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 @@
     }
 
 
-  // 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 @@
       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 @@
     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 593225d..7b71dc0 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 <roundcube@gmail.com>
+ * @version    1.22
+ * @link       http://ilohamail.org
+ */
 class rcube_imap
   {
   var $db;
@@ -46,33 +59,53 @@
   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 @@
     $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 @@
     }
 
 
+  /**
+   * Close IMAP connection
+   * Usually done on script shutdown
+   *
+   * @access public
+   */
   function close()
     {    
     if ($this->conn)
@@ -123,6 +162,12 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
+  /**
+   * 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 @@
     }
 
 
+  /**
+   * 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 @@
     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 @@
     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 @@
     }
 
 
-  // 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 @@
     $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 @@
     // 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 @@
     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 @@
     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 @@
         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
       // ...
       
@@ -456,6 +558,53 @@
 
     return array_values($a_msg_headers);
     }
+
+
+  /**
+   * 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
@@ -584,7 +733,7 @@
     }
 
 
-  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 @@
     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 @@
 
 
   // 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 @@
       $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 213f62b..dc0275c 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 <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
+ 
+  $Id$
 */
 
 
@@ -137,7 +137,7 @@
           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 @@
         // 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 @@
     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 @@
 
       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 @@
       // 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;
@@ -510,6 +517,16 @@
 
       case 'previouspage':
         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;
 
 
@@ -1168,7 +1185,18 @@
   // 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 @@
     
     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 @@
 
 
   // 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 @@
     
     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 @@
     col.innerHTML = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" border="0" />' : '';
     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 @@
 
 
   // 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<this.gui_objects.mailboxlist.childNodes.length; n++)
+    mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
+    item = document.getElementById('rcmbx'+mbox);
+
+    if (item && item.className && item.className.indexOf('mailbox '+mbox)>=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 @@
         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 @@
     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 0c9917a..02f698b 100644
--- a/program/js/common.js
+++ b/program/js/common.js
@@ -269,7 +269,7 @@
   {
   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 a1dbd7b..daacb03 100644
--- a/program/lib/imap.inc
+++ b/program/lib/imap.inc
@@ -607,7 +607,7 @@
 	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 @@
 	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 12ec06e..ae0cfa3 100644
--- a/program/localization/de/labels.inc
+++ b/program/localization/de/labels.inc
@@ -105,6 +105,9 @@
 $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['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 b37b481..086c308 100644
--- a/program/localization/en/labels.inc
+++ b/program/localization/en/labels.inc
@@ -105,6 +105,9 @@
 $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['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 0000000..fbf8871
--- /dev/null
+++ b/program/steps/mail/check_recent.inc
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/check_recent.inc                                   |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Check for recent messages                                           |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $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 0000000..e1730ef
--- /dev/null
+++ b/program/steps/mail/folders.inc
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/folders.inc                                        |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Implement folder operations line EXPUNGE and Clear                  |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $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 c430467..8ebd1c5 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -69,6 +69,8 @@
   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 @@
     $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);
     }
@@ -111,6 +115,8 @@
     $out .= rcmail_render_folder_tree_select($a_mailboxes, $special_mailboxes, $mbox, $attrib['maxlength']);
    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')
@@ -181,7 +187,7 @@
       }
 
     // 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 @@
     if ($attrib['attachmenticon'] && preg_match("/multipart\/m/i", $header->ctype))
       $attach_icon = $attrib['attachmenticon'];
         
-    $out .= sprintf('<tr id="rcmrow%d" class="message'.($header->seen ? '' : ' unread').' '.$zebra_class.'">'."\n", $header->uid);
+    $out .= sprintf('<tr id="rcmrow%d" class="message%s%s %s">'."\n",
+                    $header->uid,
+                    $header->seen ? '' : ' unread',
+                    $header->deleted ? ' deleted' : '',
+                    $zebra_class);    
+    
     $out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : '');
         
     // format each col
@@ -495,12 +506,16 @@
 
     $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 @@
   // 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 0000000..d35dcf9
--- /dev/null
+++ b/program/steps/mail/getunread.inc
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/mail/getunread.inc                                      |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Check all mailboxes for unread messages and update GUI              |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $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 3c9f603..c29fcf1 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -224,14 +224,17 @@
   {
   // 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 308ec79..4cd929d 100644
--- a/program/steps/mail/upload.inc
+++ b/program/steps/mail/upload.inc
@@ -39,7 +39,7 @@
 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],
diff --git a/skins/default/mail.css b/skins/default/mail.css
index 9d507fe..28fe2ee 100644
--- a/skins/default/mail.css
+++ b/skins/default/mail.css
@@ -51,7 +51,10 @@
 
 #listcontrols a,
 #listcontrols a:active,
-#listcontrols a:visited
+#listcontrols a:visited,
+#mailboxcontrols a,
+#mailboxcontrols a:active,
+#mailboxcontrols a:visited
 {
   color: #999999;
   font-size: 11px;
@@ -60,12 +63,16 @@
 
 #listcontrols a.active,
 #listcontrols a.active:active,
-#listcontrols a.active:visited
+#listcontrols a.active:visited,
+#mailboxcontrols a.active,
+#mailboxcontrols a.active:active,
+#mailboxcontrols a.active:visited
 {
   color: #CC0000;
 }
 
-#listcontrols a.active:hover
+#listcontrols a.active:hover,
+#mailboxcontrols a.active:hover
 {
   text-decoration: underline;
 }
@@ -294,6 +301,17 @@
 }
 
 
+#mailboxcontrols
+{
+  position: absolute;
+  left: 20px;
+  width: 160px;
+  bottom: 20px;
+  height: 16px;
+  overflow: hidden;
+}
+
+
 /** message list styles */
 
 body.messagelist
@@ -430,6 +448,12 @@
   color: #FFFFFF;
 }
 
+#messagelist tr.deleted td,
+#messagelist tr.deleted td a
+{
+  color: #CCCCCC;
+}
+
 
 /** message view styles */
 
diff --git a/skins/default/templates/mail.html b/skins/default/templates/mail.html
index 0f87c63..1af25ed 100644
--- a/skins/default/templates/mail.html
+++ b/skins/default/templates/mail.html
@@ -28,6 +28,11 @@
 <div id="mailboxlist-header"><roundcube:label name="mailboxlist" /></div>
 <div id="mailboxlist-container"><roundcube:object name="mailboxlist" id="mailboxlist" maxlength="16" /></div>
 
+<div id="mailboxcontrols">
+<roundcube:label name="folder" />:&nbsp;
+<roundcube:button command="expunge" label="compact" classAct="active" />&nbsp;
+</div>
+
 
 <div id="mailcontframe">
 <roundcube:object name="messages"

--
Gitblit v1.9.1