thomascube
2006-01-04 15a9d1ce671fcbc44ea3e4858d7aa6f5b22300c9
Optimized loading time; added periodic mail check; added EXPUNGE command


3 files added
21 files modified
1149 ■■■■ changed files
CHANGELOG 14 ●●●●● patch | view | raw | blame | history
INSTALL 1 ●●●● patch | view | raw | blame | history
SQL/mysql.initial.sql 2 ●●● patch | view | raw | blame | history
SQL/mysql.update.sql 4 ●●●● patch | view | raw | blame | history
SQL/postgres.initial.sql 74 ●●●● patch | view | raw | blame | history
UPGRADING 7 ●●●● patch | view | raw | blame | history
config/main.inc.php.dist 3 ●●●●● patch | view | raw | blame | history
index.php 14 ●●●●● patch | view | raw | blame | history
program/include/main.inc 106 ●●●● patch | view | raw | blame | history
program/include/rcube_db.inc 253 ●●●● patch | view | raw | blame | history
program/include/rcube_imap.inc 289 ●●●● patch | view | raw | blame | history
program/js/app.js 148 ●●●● patch | view | raw | blame | history
program/js/common.js 2 ●●● patch | view | raw | blame | history
program/lib/imap.inc 5 ●●●●● patch | view | raw | blame | history
program/localization/de/labels.inc 4 ●●●● patch | view | raw | blame | history
program/localization/en/labels.inc 4 ●●●● patch | view | raw | blame | history
program/steps/mail/check_recent.inc 47 ●●●●● patch | view | raw | blame | history
program/steps/mail/folders.inc 61 ●●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 29 ●●●● patch | view | raw | blame | history
program/steps/mail/getunread.inc 36 ●●●●● patch | view | raw | blame | history
program/steps/mail/sendmail.inc 9 ●●●●● patch | view | raw | blame | history
program/steps/mail/upload.inc 2 ●●● patch | view | raw | blame | history
skins/default/mail.css 30 ●●●●● patch | view | raw | blame | history
skins/default/templates/mail.html 5 ●●●●● patch | view | raw | blame | history
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
----------
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)
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;
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;
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);
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';
  $rcmail_config['db_sequence_message_ids'] = 'message_ids';
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'] = '';
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');
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));
  }
?>
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
?>
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));
    }
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());
    };
  /********************************************************/
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;
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;
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';
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';
program/steps/mail/check_recent.inc
New file
@@ -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);
?>
program/steps/mail/folders.inc
New file
@@ -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);
?>
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']);
  }
program/steps/mail/getunread.inc
New file
@@ -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;
?>
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");
  }
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],
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 */
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"