From cc97ea0559af1a92a54dbcdf738ee4d95e67d3ff Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Sun, 19 Apr 2009 13:44:29 -0400
Subject: [PATCH] Merged branch devel-api (from r2208 to r2387) back into trunk (omitting some sample plugins)

---
 plugins/http_authentication/http_authentication.php               |   41 
 program/js/common.js                                              |  232 +-
 program/include/main.inc                                          |   25 
 program/steps/mail/compose.inc                                    |   50 
 skins/default/mail.css                                            |    2 
 program/js/editor.js                                              |    4 
 plugins/userinfo/userinfo.php                                     |   53 
 program/steps/mail/show.inc                                       |    4 
 skins/default/templates/message.html                              |    1 
 plugins/userinfo/userinfo.js                                      |   16 
 plugins/markasjunk/localization/en_US.inc                         |    7 
 program/steps/settings/manage_folders.inc                         |    1 
 program/include/rcube_plugin.php                                  |  196 ++
 skins/default/splitter.js                                         |   40 
 index.php                                                         |   48 
 skins/default/templates/mail.html                                 |    1 
 program/steps/settings/func.inc                                   |   12 
 program/include/html.php                                          |   30 
 program/include/rcube_ldap.php                                    |    2 
 plugins/debug_logger/debug_logger.php                             |  146 +
 program/include/rcube_user.php                                    |   26 
 skins/default/templates/managefolders.html                        |    2 
 config/main.inc.php.dist                                          |    8 
 plugins/new_user_identity/new_user_identity.php                   |   49 
 program/include/rcube_config.php                                  |    1 
 program/steps/settings/save_prefs.inc                             |    3 
 program/include/rcube_addressbook.php                             |  169 ++
 program/steps/mail/attachments.inc                                |   72 
 plugins/database_attachments/database_attachments.php             |  152 +
 plugins/emoticons/emoticons.php                                   |   39 
 program/js/app.js                                                 |  997 ++++-------
 plugins/subscriptions_option/subscriptions_option.php             |   84 +
 program/include/rcube_contacts.php                                |   35 
 program/include/rcube_html_page.php                               |   11 
 skins/default/common.css                                          |    7 
 plugins/markasjunk/markasjunk.js                                  |   28 
 plugins/debug_logger/runlog/runlog.php                            |  227 ++
 plugins/password/localization/pl_PL.inc                           |   15 
 program/include/rcube_json_output.php                             |   28 
 plugins/userinfo/localization/en_US.inc                           |    9 
 program/lib/imap.inc                                              |   18 
 plugins/example_addressbook/example_addressbook.php               |   42 
 plugins/example_addressbook/example_addressbook_backend.php       |   72 
 program/include/iniset.php                                        |    2 
 skins/default/includes/settingstabs.html                          |    2 
 plugins/filesystem_attachments/filesystem_attachments.php         |  144 +
 plugins/vcard_attachments/vcard_attachments.php                   |  115 +
 skins/default/includes/taskbar.html                               |    1 
 plugins/subscriptions_option/localization/en_US.inc               |    6 
 plugins/markasjunk/markasjunk.php                                 |   47 
 program/steps/mail/func.inc                                       |  188 +-
 skins/default/templates/addressbook.html                          |    4 
 plugins/userinfo/localization/de_CH.inc                           |    9 
 program/js/list.js                                                |  172 -
 program/include/rcube_plugin_api.php                              |  312 +++
 plugins/password/localization/en_US.inc                           |   15 
 plugins/markasjunk/junk_act.png                                   |    0 
 program/steps/addressbook/func.inc                                |   36 
 plugins/vcard_attachments/vcardattach.js                          |   10 
 program/include/rcube_message.php                                 |    3 
 program/steps/mail/sendmail.inc                                   |   87 
 plugins/markasjunk/junk_pas.png                                   |    0 
 skins/default/templates/plugin.html                               |   24 
 plugins/password/password.js                                      |   44 
 program/include/rcube_imap.php                                    |   31 
 program/include/rcmail.php                                        |   44 
 program/include/rcube_vcard.php                                   |    4 
 program/include/rcube_template.php                                |  112 
 skins/default/functions.js                                        |   30 
 plugins/autologon/autologon.php                                   |   44 
 plugins/password/password.php                                     |  160 ++
 skins/default/templates/identities.html                           |    2 
 skins/default/templates/settings.html                             |    6 
 plugins/additional_message_headers/additional_message_headers.php |   42 
 plugins/show_additional_headers/show_additional_headers.php       |   49 
 75 files changed, 3,473 insertions(+), 1,277 deletions(-)

diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 850b2ba..9e3a25e 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -35,6 +35,12 @@
 // use this folder to store temp files (must be writeable for apache user)
 $rcmail_config['temp_dir'] = 'temp/';
 
+// use this folder to search for plugin sources
+$rcmail_config['plugins_dir'] = 'plugins/';
+
+// List of active plugins. Add the name of a directory found in 'plugins_dir'
+$rcmail_config['plugins'] = array();
+
 // enable caching of messages and mailbox data in the local database.
 // this is recommended if the IMAP server does not run on the same machine
 $rcmail_config['enable_caching'] = TRUE;
@@ -152,7 +158,7 @@
 $rcmail_config['date_today'] = 'H:i';
 
 // add this user-agent to message headers when sending
-$rcmail_config['useragent'] = 'RoundCube Webmail/0.2-beta';
+$rcmail_config['useragent'] = 'RoundCube Webmail/0.3-beta';
 
 // use this name to compose page titles
 $rcmail_config['product_name'] = 'RoundCube Webmail';
diff --git a/index.php b/index.php
index 172d57c..7c2d230 100644
--- a/index.php
+++ b/index.php
@@ -2,7 +2,7 @@
 /*
  +-------------------------------------------------------------------------+
  | RoundCube Webmail IMAP Client                                           |
- | Version 0.2-20080829                                                    |
+ | Version 0.3-20090419                                                    |
  |                                                                         |
  | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland                   |
  |                                                                         |
@@ -35,6 +35,9 @@
 
 // init output class
 $OUTPUT = !empty($_REQUEST['_remote']) ? $RCMAIL->init_json() : $RCMAIL->load_gui(!empty($_REQUEST['_framed']));
+
+// init plugin API
+$RCMAIL->plugins->init();
 
 // set output buffering
 if ($RCMAIL->action != 'get' && $RCMAIL->action != 'viewsource') {
@@ -70,21 +73,29 @@
   raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
 }
 
+
+// trigger startup plugin hook
+$startup = $RCMAIL->plugins->exec_hook('startup', array('task' => $RCMAIL->task, 'action' => $RCMAIL->action));
+$RCMAIL->set_task($startup['task']);
+$RCMAIL->action = $startup['action'];
+
+
 // try to log in
 if ($RCMAIL->action=='login' && $RCMAIL->task=='mail') {
   // purge the session in case of new login when a session already exists 
-  $RCMAIL->kill_session(); 
+  $RCMAIL->kill_session();
   
-  // set IMAP host
-  $host = $RCMAIL->autoselect_host();
-  
+  $auth = $RCMAIL->plugins->exec_hook('authenticate', array(
+    'host' => $RCMAIL->autoselect_host(),
+    'user' => trim(get_input_value('_user', RCUBE_INPUT_POST)),
+  )) + array('pass' => get_input_value('_pass', RCUBE_INPUT_POST, true, 'ISO-8859-1'));
+
   // check if client supports cookies
   if (empty($_COOKIE)) {
     $OUTPUT->show_message("cookiesdisabled", 'warning');
   }
-  else if ($_SESSION['temp'] && !empty($_POST['_user']) && !empty($_POST['_pass']) &&
-           $RCMAIL->login(trim(get_input_value('_user', RCUBE_INPUT_POST), ' '),
-              get_input_value('_pass', RCUBE_INPUT_POST, true, 'ISO-8859-1'), $host)) {
+  else if ($_SESSION['temp'] && !empty($auth['user']) && !empty($auth['host']) && isset($auth['pass']) && 
+            $RCMAIL->login($auth['user'], $auth['pass'], $auth['host'])) {
     // create new session ID
     unset($_SESSION['temp']);
     rcube_sess_regenerate_id();
@@ -99,12 +110,22 @@
         $RCMAIL->user->ID,
         $_SERVER['REMOTE_ADDR']));
     }
+    
+    // restore original request parameters
+    $query = array();
+    if ($url = get_input_value('_url', RCUBE_INPUT_POST))
+      parse_str($url, $query);
+
+    // allow plugins to control the redirect url after login success
+    $redir = $RCMAIL->plugins->exec_hook('login_after', $query + array('task' => $RCMAIL->task));
+    unset($redir['abort']);
 
     // send redirect
-    $OUTPUT->redirect();
+    $OUTPUT->redirect($redir);
   }
   else {
     $OUTPUT->show_message($IMAP->error_code < -1 ? 'imaperror' : 'loginfailed', 'warning');
+    $RCMAIL->plugins->exec_hook('login_failed', array('code' => $IMAP->error_code, 'host' => $auth['host'], 'user' => $auth['user']));
     $RCMAIL->kill_session();
   }
 }
@@ -208,9 +229,14 @@
 while ($redirects < 5) {
   $stepfile = !empty($action_map[$RCMAIL->task][$RCMAIL->action]) ?
     $action_map[$RCMAIL->task][$RCMAIL->action] : strtr($RCMAIL->action, '-', '_') . '.inc';
-    
+
+  // execute a plugin action
+  if (eregi('^plugin.', $RCMAIL->action)) {
+    $RCMAIL->plugins->exec_action($RCMAIL->action);
+    break;
+  }
   // try to include the step file
-  if (is_file(($incfile = 'program/steps/'.$RCMAIL->task.'/'.$stepfile))) {
+  else if (is_file(($incfile = 'program/steps/'.$RCMAIL->task.'/'.$stepfile))) {
     include($incfile);
     $redirects++;
   }
diff --git a/plugins/additional_message_headers/additional_message_headers.php b/plugins/additional_message_headers/additional_message_headers.php
new file mode 100644
index 0000000..9247138
--- /dev/null
+++ b/plugins/additional_message_headers/additional_message_headers.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Additional Message Headers
+ *
+ * Very simple plugin which will read additional headers for outgoing messages from the config file.
+ *
+ * Enable the plugin in config/main.inc.php and add your desired headers.
+ *
+ * @version 1.0
+ * @author Ziba Scott
+ * @website http://roundcube.net
+ * 
+ * Example:
+ *
+ * $rcmail_config['additional_message_headers']['X-Remote-Browser'] = $_SERVER['HTTP_USER_AGENT'];
+ * $rcmail_config['additional_message_headers']['X-Originating-IP'] = $_SERVER['REMOTE_ADDR'];
+ * $rcmail_config['additional_message_headers']['X-RoundCube-Server'] = $_SERVER['SERVER_ADDR'];
+ * if( isset( $_SERVER['MACHINE_NAME'] )) {
+ *     $rcmail_config['additional_message_headers']['X-RoundCube-Server'] .= ' (' . $_SERVER['MACHINE_NAME'] . ')';
+ * }
+ */
+class additional_message_headers extends rcube_plugin
+{
+    public $task = 'mail';
+    
+    function init()
+    {
+        $this->add_hook('outgoing_message_headers', array($this, 'message_headers'));
+    }
+
+    function message_headers($args){
+
+        // additional email headers
+        $additional_headers = rcmail::get_instance()->config->get('additional_message_headers',array());
+        foreach($additional_headers as $header=>$value){
+            $args['headers'][$header] = $value;
+        }
+
+        return $args;
+    }
+}
diff --git a/plugins/autologon/autologon.php b/plugins/autologon/autologon.php
new file mode 100644
index 0000000..c40f2d4
--- /dev/null
+++ b/plugins/autologon/autologon.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Sample plugin to try out some hooks.
+ * This performs an automatic login if accessed from localhost
+ */
+class autologon extends rcube_plugin
+{
+
+  function init()
+  {
+    $this->add_hook('startup', array($this, 'startup'));
+    $this->add_hook('authenticate', array($this, 'authenticate'));
+  }
+
+  function startup($args)
+  {
+    $rcmail = rcmail::get_instance();
+
+    // change action to login
+    if ($args['task'] == 'mail' && empty($args['action']) && empty($_SESSION['user_id']) && !empty($_GET['_autologin']) && $this->is_localhost())
+      $args['action'] = 'login';
+
+    return $args;
+  }
+
+  function authenticate($args)
+  {
+    if (!empty($_GET['_autologin']) && $this->is_localhost()) {
+      $args['user'] = 'me';
+      $args['pass'] = '******';
+      $args['host'] = 'localhost';
+    }
+  
+    return $args;
+  }
+
+  function is_localhost()
+  {
+    return $_SERVER['REMOTE_ADDR'] == '::1' || $_SERVER['REMOTE_ADDR'] == '127.0.0.1';
+  }
+
+}
+
diff --git a/plugins/database_attachments/database_attachments.php b/plugins/database_attachments/database_attachments.php
new file mode 100644
index 0000000..28ccde4
--- /dev/null
+++ b/plugins/database_attachments/database_attachments.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Filesystem Attachments
+ * 
+ * This plugin which provides database backed storage for temporary
+ * attachment file handling.  The primary advantage of this plugin
+ * is its compatibility with round-robin dns multi-server roundcube
+ * installations.
+ *
+ * This plugin relies on the core filesystem_attachments plugin
+ *
+ * @author Ziba Scott <ziba@umich.edu>
+ * 
+ */
+require_once('plugins/filesystem_attachments/filesystem_attachments.php');
+class database_attachments extends filesystem_attachments
+{
+
+    // A prefix for the cache key used in the session and in the key field of the cache table
+    private $cache_prefix = "db_attach";
+
+    /**
+     * Helper method to generate a unique key for the given attachment file
+     */
+    private function _key($filepath)
+    {
+        return  $this->cache_prefix.md5(mktime().$filepath.$_SESSION['user_id']); 
+    }
+
+    /**
+     * Save a newly uploaded attachment
+     */
+    function upload($args)
+    {
+        $args['status'] = false;
+        $rcmail = rcmail::get_instance();
+        $key = $this->_key($args['path']);
+        $data = base64_encode(file_get_contents($args['path']));
+
+        $status = $rcmail->db->query(
+            "INSERT INTO ".get_table_name('cache')."
+             (created, user_id, cache_key, data)
+             VALUES (".$rcmail->db->now().", ?, ?, ?)",
+            $_SESSION['user_id'],
+            $key,
+            $data);
+            
+        if ($status) {
+            $args['id'] = $key;
+            $args['status'] = true;
+            unset($args['path']);
+        }
+        
+        return $args;
+    }
+
+    /**
+     * Save an attachment from a non-upload source (draft or forward)
+     */
+    function save($args)
+    {
+        $args['status'] = false;
+        $rcmail = rcmail::get_instance();
+
+        $key = $this->_key($args['name']);
+        $data = base64_encode($args['data']);
+
+        $status = $rcmail->db->query(
+            "INSERT INTO ".get_table_name('cache')."
+             (created, user_id, cache_key, data)
+             VALUES (".$rcmail->db->now().", ?, ?, ?)",
+            $_SESSION['user_id'],
+            $key,
+            $data);
+        
+        if ($status) {
+            $args['id'] = $key;
+            $args['status'] = true;
+        }
+
+        return $args;
+    }
+
+    /**
+     * Remove an attachment from storage
+     * This is triggered by the remove attachment button on the compose screen
+     */
+    function remove($args)
+    {
+        $args['status'] = false;
+        $rcmail = rcmail::get_instance();
+        $status = $rcmail->db->query(
+            "DELETE FROM ".get_table_name('cache')."
+             WHERE  user_id=?
+             AND    cache_key=?",
+            $_SESSION['user_id'],
+            $args['id']);
+    
+        if ($status) {
+            $args['status'] = true;
+        }
+        
+        return $args;
+    }
+
+    /**
+     * When composing an html message, image attachments may be shown
+     * For this plugin, $this->get_attachment will check the file and
+     * return it's contents
+     */
+    function display($args)
+    {
+        return $this->get_attachment($args);
+    }
+
+    /**
+     * When displaying or sending the attachment the file contents are fetched
+     * using this method. This is also called by the display_attachment hook.
+     */
+    function get_attachment($args)
+    {
+        $rcmail = rcmail::get_instance();
+        
+        $sql_result = $rcmail->db->query(
+            "SELECT cache_id, data
+             FROM ".get_table_name('cache')."
+             WHERE  user_id=?
+             AND    cache_key=?",
+            $_SESSION['user_id'],
+            $args['id']);
+
+        if ($sql_arr = $rcmail->db->fetch_assoc($sql_result)) {
+            $args['data'] = base64_decode($sql_arr['data']);
+            $args['status'] = true;
+        }
+        
+        return $args;
+    }
+    
+    /**
+     * Delete all temp files associated with this user
+     */
+    function cleanup($args)
+    {
+        $rcmail = rcmail::get_instance();
+        $rcmail->db->query(
+            "DELETE FROM ".get_table_name('cache')."
+             WHERE  user_id=?
+             AND cache_key like '{$this->cache_prefix}%'",
+            $_SESSION['user_id']);
+    }
+}
diff --git a/plugins/debug_logger/debug_logger.php b/plugins/debug_logger/debug_logger.php
new file mode 100644
index 0000000..8cd3351
--- /dev/null
+++ b/plugins/debug_logger/debug_logger.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * Debug Logger
+ *
+ * Enhanced logging for debugging purposes.  It is not recommened
+ * to be enabled on production systems without testing because of
+ * the somewhat increased memory, cpu and disk i/o overhead.
+ *
+ * Debug Logger listens for existing console("message") calls and
+ * introduces start and end tags as well as free form tagging
+ * which can redirect messages to files.  The resulting log files
+ * provide timing and tag quantity results.
+ *
+ * Enable the plugin in config/main.inc.php and add your desired
+ * log types and files.
+ *
+ * @version 1.0
+ * @author Ziba Scott
+ * @website http://roundcube.net
+ * 
+ * Example:
+ *
+ * config/main.inc.php:
+ *
+ *   // $rcmail_config['debug_logger'][type of logging] = name of file in log_dir
+ *   // The 'master' log includes timing information
+ *   $rcmail_config['debug_logger']['master'] = 'master';
+ *   // If you want sql messages to also go into a separate file 
+ *   $rcmail_config['debug_logger']['sql'] = 'sql';
+ *
+ * index.php (just after $RCMAIL->plugins->init()):
+ *
+ *   console("my test","start");
+ *   console("my message");
+ *   console("my sql calls","start");
+ *   console("cp -r * /dev/null","shell exec");
+ *   console("select * from example","sql");
+ *   console("select * from example","sql");
+ *   console("select * from example","sql");
+ *   console("end");
+ *   console("end");
+ *
+ *
+ * logs/master (after reloading the main page):
+ *
+ *   [17-Feb-2009 16:51:37 -0500] start: Task: mail. 
+ *   [17-Feb-2009 16:51:37 -0500]   start: my test
+ *   [17-Feb-2009 16:51:37 -0500]     my message
+ *   [17-Feb-2009 16:51:37 -0500]     shell exec: cp -r * /dev/null
+ *   [17-Feb-2009 16:51:37 -0500]     start: my sql calls
+ *   [17-Feb-2009 16:51:37 -0500]       sql: select * from example
+ *   [17-Feb-2009 16:51:37 -0500]       sql: select * from example
+ *   [17-Feb-2009 16:51:37 -0500]       sql: select * from example
+ *   [17-Feb-2009 16:51:37 -0500]     end: my sql calls - 0.0018 seconds shell exec: 1, sql: 3, 
+ *   [17-Feb-2009 16:51:37 -0500]   end: my test - 0.0055 seconds shell exec: 1, sql: 3, 
+ *   [17-Feb-2009 16:51:38 -0500] end: Task: mail.  - 0.8854 seconds shell exec: 1, sql: 3, 
+ * 
+ * logs/sql (after reloading the main page):
+ *
+ *   [17-Feb-2009 16:51:37 -0500]       sql: select * from example
+ *   [17-Feb-2009 16:51:37 -0500]       sql: select * from example
+ *   [17-Feb-2009 16:51:37 -0500]       sql: select * from example
+ */
+class debug_logger extends rcube_plugin
+{
+    function init()
+    {
+        require_once(dirname(__FILE__).'/runlog/runlog.php');
+        $this->runlog = new runlog(); 
+
+        if(!rcmail::get_instance()->config->get('log_dir')){
+            rcmail::get_instance()->config->set('log_dir',INSTALL_PATH.'logs');
+        }
+
+        $log_config = rcmail::get_instance()->config->get('debug_logger',array());
+
+        foreach($log_config as $type=>$file){
+            $this->runlog->set_file(rcmail::get_instance()->config->get('log_dir').'/'.$file, $type);
+        }
+
+        $start_string = "";
+        $action = rcmail::get_instance()->action;
+        $task = rcmail::get_instance()->task;
+        if($action){
+               $start_string .= "Action: ".$action.". "; 
+        }
+        if($task){
+               $start_string .= "Task: ".$task.". "; 
+        }
+        $this->runlog->start($start_string);
+
+        $this->add_hook('console', array($this, 'console'));
+        $this->add_hook('authenticate', array($this, 'authenticate'));
+    }
+
+    function authenticate($args){
+        $this->runlog->note('Authenticating '.$args['user'].'@'.$args['host']);
+        return $args;
+    }
+
+    function console($args){
+        $note = $args[0];
+        $type = $args[1];
+
+
+        if(!isset($args[1])){
+            // This could be extended to detect types based on the 
+            // file which called console.  For now only rcube_imap.inc is supported
+            $bt = debug_backtrace(true);
+            $file  = $bt[3]['file'];
+            switch(basename($file)){
+                case 'rcube_imap.php':
+                    $type = 'imap';
+                    break;
+                default:
+                    $type = FALSE; 
+                    break; 
+            }
+        }
+        switch($note){
+            case 'end':
+                $type = 'end';
+                break;
+        }
+
+
+        switch($type){
+            case 'start':
+                $this->runlog->start($note);
+                break;
+            case 'end':
+                $this->runlog->end();
+                break;
+            default:
+                $this->runlog->note($note, $type);
+                break;
+        }
+        return $args;
+    }
+
+    function __destruct(){
+                $this->runlog->end();
+    }
+}
+?>
diff --git a/plugins/debug_logger/runlog/runlog.php b/plugins/debug_logger/runlog/runlog.php
new file mode 100644
index 0000000..c9f6726
--- /dev/null
+++ b/plugins/debug_logger/runlog/runlog.php
@@ -0,0 +1,227 @@
+<?php
+
+/**
+ * runlog 
+ * 
+ * @author Ziba Scott <ziba@umich.edu> 
+ */
+class runlog {
+
+    private $start_time = FALSE;
+
+    private $parent_stack = array();
+
+    public $print_to_console = FALSE;
+
+    private $file_handles = array();
+
+    private $indent = 0;
+
+    public $threshold = 0;
+
+    public $tag_count = array();
+
+    public $timestamp = "d-M-Y H:i:s O";
+
+    public $max_line_size = 150;
+
+    private $run_log = array();
+
+    function runlog()
+    {
+        $this->start_time = microtime( TRUE );
+    }
+
+    public function start( $name, $tag = FALSE  )
+    {
+        $this->run_log[] = array( 'type' => 'start',
+                                  'tag' => $tag,
+                                  'index' => count($this->run_log),
+                                  'value' => $name,
+                                  'time' => microtime( TRUE ),
+                                  'parents' => $this->parent_stack,
+                                  'ended' => false,
+                                   );
+        $this->parent_stack[] = $name;
+
+        $this->print_to_console("start: ".$name, $tag, 'start');
+        $this->print_to_file("start: ".$name, $tag, 'start');
+        $this->indent++;
+    }
+
+    public function end()
+    {
+        $name = array_pop( $this->parent_stack );
+        foreach ( $this->run_log as $k => $entry ) {
+            if ( $entry['value'] == $name && $entry['type'] == 'start'  && $entry['ended'] == false) {
+                $lastk = $k;
+            }
+        }
+        $start = $this->run_log[$lastk]['time'];
+        $this->run_log[$lastk]['duration'] = microtime( TRUE ) - $start;
+        $this->run_log[$lastk]['ended'] = true;
+
+        $this->run_log[] = array( 'type' => 'end',
+                                  'tag' =>  $this->run_log[$lastk]['tag'],
+                                  'index' => $lastk,
+                                  'value' => $name,
+                                  'time' => microtime( TRUE ),
+                                  'duration' => microtime( TRUE ) - $start,
+                                  'parents' => $this->parent_stack,
+                                   );
+        $this->indent--;
+        if($this->run_log[$lastk]['duration'] >= $this->threshold){ 
+            $tag_report = "";
+            foreach($this->tag_count as $tag=>$count){
+                $tag_report .= "$tag: $count, ";
+            }
+            if(!empty($tag_report)){
+//                $tag_report = "\n$tag_report\n";
+            }
+            $end_txt = sprintf("end: $name - %0.4f seconds $tag_report", $this->run_log[$lastk]['duration'] ); 
+            $this->print_to_console($end_txt, $this->run_log[$lastk]['tag'] , 'end');
+            $this->print_to_file($end_txt,  $this->run_log[$lastk]['tag'], 'end');
+        }
+    }
+
+    public function increase_tag_count($tag){
+            if(!isset($this->tag_count[$tag])){
+                $this->tag_count[$tag] = 0;
+            }
+            $this->tag_count[$tag]++;
+    }
+
+    public function get_text(){
+        $text = "";
+        foreach($this->run_log as $entry){
+           $text .= str_repeat("   ",count($entry['parents']));
+           if($entry['tag'] != 'text'){
+            $text .= $entry['tag'].': ';
+           }
+           $text .= $entry['value'];
+
+           if($entry['tag'] == 'end'){
+            $text .= sprintf(" - %0.4f seconds", $entry['duration'] ); 
+           }
+
+           $text .= "\n"; 
+        }
+        return $text;
+    }
+
+    public function set_file($filename, $tag = 'master'){
+        if(!isset($this->file_handle[$tag])){
+            $this->file_handles[$tag] = fopen($filename, 'a');
+            if(!$this->file_handles[$tag]){
+                trigger_error('Could not open file for writing: '.$filename);
+            }
+        }
+    }
+
+    public function note( $msg, $tag = FALSE )
+    {
+        if($tag){
+            $this->increase_tag_count($tag);
+        }
+        if ( is_array( $msg )) {
+            $msg = '<pre>' . print_r( $msg, TRUE ) . '</pre>';
+        }
+        $this->debug_messages[] = $msg;
+        $this->run_log[] = array( 'type' => 'note',
+                                  'tag' => $tag ? $tag:"text",
+                                  'value' => htmlentities($msg),
+                                  'time' => microtime( TRUE ),
+                                  'parents' => $this->parent_stack,
+             );
+
+       $this->print_to_file($msg, $tag);
+       $this->print_to_console($msg, $tag);
+
+    }
+
+    public function print_to_file($msg, $tag = FALSE, $type = FALSE){
+       if(!$tag){
+        $file_handle_tag = 'master';
+       }
+       else{
+            $file_handle_tag = $tag;
+       }
+       if($file_handle_tag != 'master' && isset($this->file_handles[$file_handle_tag])){
+           $buffer = $this->get_indent();
+           $buffer .= "$msg\n";
+           if(!empty($this->timestamp)){
+                $buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer);
+           }
+           fwrite($this->file_handles[$file_handle_tag], wordwrap($buffer, $this->max_line_size, "\n     "));
+        }
+       if(isset($this->file_handles['master']) && $this->file_handles['master']){
+           $buffer = $this->get_indent();
+           if($tag){
+            $buffer .= "$tag: ";
+           }
+           $msg = str_replace("\n","",$msg);
+           $buffer .= "$msg";
+           if(!empty($this->timestamp)){
+                $buffer = sprintf("[%s] %s",date($this->timestamp, mktime()), $buffer);
+           }
+           if(strlen($buffer) > $this->max_line_size){
+                $buffer = substr($buffer,0,$this->max_line_size - 3)."...";
+           }
+           fwrite($this->file_handles['master'], $buffer."\n");
+       }
+    }
+
+    public function print_to_console($msg, $tag=FALSE){
+        if($this->print_to_console){
+            if(is_array($this->print_to_console)){
+                if(in_array($tag, $this->print_to_console)){
+                    echo $this->get_indent();
+                    if($tag){
+                        echo "$tag: ";
+                    }
+                    echo "$msg\n";
+                }
+            }
+            else{
+                echo $this->get_indent();
+                if($tag){
+                    echo "$tag: ";
+                }
+                echo "$msg\n";
+            }
+        }
+    }
+
+    public function print_totals(){
+        $totals = array();
+        foreach ( $this->run_log as $k => $entry ) {
+            if ( $entry['type'] == 'start'  && $entry['ended'] == true) {
+                $totals[$entry['value']]['duration'] += $entry['duration'];
+                $totals[$entry['value']]['count'] += 1;
+            }
+        }
+       if($this->file_handle){
+           foreach($totals as $name=>$details){
+            fwrite($this->file_handle,$name.": ".number_format($details['duration'],4)."sec,  ".$details['count']." calls \n");
+           }
+        }
+    }
+
+    private function get_indent(){
+           $buf = "";
+           for($i = 0; $i < $this->indent; $i++){
+               $buf .= "  "; 
+           }
+           return $buf;
+    }
+
+
+   function  __destruct(){
+       foreach($this->file_handles as $handle){
+            fclose($handle);
+        }
+    }
+
+}
+
+?>
diff --git a/plugins/emoticons/emoticons.php b/plugins/emoticons/emoticons.php
new file mode 100644
index 0000000..be736b6
--- /dev/null
+++ b/plugins/emoticons/emoticons.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Display Emoticons
+ *
+ * Sample plugin to replace emoticons in plain text message body with real icons
+ *
+ * @version 1.0.1
+ * @author Thomas Bruederli
+ * @website http://roundcube.net
+ */
+class emoticons extends rcube_plugin
+{
+  public $task = 'mail';
+  private $map;
+
+  function init()
+  {
+    $this->task = 'mail';
+    $this->add_hook('message_part_after', array($this, 'replace'));
+  
+    $this->map = array(
+      ':)'  => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif', 'alt' => ':)')),
+      ':-)' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-smile.gif', 'alt' => ':-)')),
+      ':('  => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif', 'alt' => ':(')),
+      ':-(' => html::img(array('src' => './program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif', 'alt' => ':-(')),
+    );
+  }
+
+  function replace($args)
+  {
+    if ($args['type'] == 'plain')
+      return array('body' => strtr($args['body'], $this->map));
+  
+    return null;
+  }
+
+}
+
diff --git a/plugins/example_addressbook/example_addressbook.php b/plugins/example_addressbook/example_addressbook.php
new file mode 100644
index 0000000..081efcb
--- /dev/null
+++ b/plugins/example_addressbook/example_addressbook.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Sample plugin to add a new address book
+ * with just a static list of contacts
+ */
+class example_addressbook extends rcube_plugin
+{
+  private $abook_id = 'static';
+  
+  public function init()
+  {
+    $this->add_hook('address_sources', array($this, 'address_sources'));
+    $this->add_hook('get_address_book', array($this, 'get_address_book'));
+    
+    // use this address book for autocompletion queries
+    // (maybe this should be configurable by the user?)
+    $config = rcmail::get_instance()->config;
+    $sources = $config->get('autocomplete_addressbooks', array('sql'));
+    if (!in_array($this->abook_id, $sources)) {
+      $sources[] = $this->abook_id;
+      $config->set('autocomplete_addressbooks', $sources);
+    }
+  }
+  
+  public function address_sources($p)
+  {
+    $p['sources'][$this->abook_id] = array('id' => $this->abook_id, 'name' => 'Static List', 'readonly' => true);
+    return $p;
+  }
+  
+  public function get_address_book($p)
+  {
+    if ($p['id'] == $this->abook_id) {
+      require_once(dirname(__FILE__) . '/example_addressbook_backend.php');
+      $p['instance'] = new example_addressbook_backend;
+    }
+    
+    return $p;
+  }
+  
+}
diff --git a/plugins/example_addressbook/example_addressbook_backend.php b/plugins/example_addressbook/example_addressbook_backend.php
new file mode 100644
index 0000000..ad6b89d
--- /dev/null
+++ b/plugins/example_addressbook/example_addressbook_backend.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Example backend class for a custom address book
+ *
+ * This one just holds a static list of address records
+ *
+ * @author Thomas Bruederli
+ */
+class example_addressbook_backend extends rcube_addressbook
+{
+  public $primary_key = 'ID';
+  public $readonly = true;
+  
+  private $filter;
+  private $result;
+  
+  public function __construct()
+  {
+    $this->ready = true;
+  }
+  
+  public function set_search_set($filter)
+  {
+    $this->filter = $filter;
+  }
+  
+  public function get_search_set()
+  {
+    return $this->filter;
+  }
+
+  public function reset()
+  {
+    $this->result = null;
+    $this->filter = null;
+  }
+
+  public function list_records($cols=null, $subset=0)
+  {
+    $this->result = $this->count();
+    $this->result->add(array('ID' => '111', 'name' => "Example Contact", 'firstname' => "Example", 'surname' => "Contact", 'email' => "example@roundcube.net"));
+    
+    return $this->result;
+  }
+
+  public function search($fields, $value, $strict=false, $select=true)
+  {
+    // no search implemented, just list all records
+    return $this->list_records();
+  }
+
+  public function count()
+  {
+    return new rcube_result_set(1, ($this->list_page-1) * $this->page_size);
+  }
+
+  public function get_result()
+  {
+    return $this->result;
+  }
+
+  public function get_record($id, $assoc=false)
+  {
+    $this->list_records();
+    $first = $this->result->first();
+    $sql_arr = $first['ID'] == $id ? $first : null;
+    
+    return $assoc && $sql_arr ? $sql_arr : $this->result;
+  }
+  
+}
diff --git a/plugins/filesystem_attachments/filesystem_attachments.php b/plugins/filesystem_attachments/filesystem_attachments.php
new file mode 100644
index 0000000..9a6c0a8
--- /dev/null
+++ b/plugins/filesystem_attachments/filesystem_attachments.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Filesystem Attachments
+ * 
+ * This is a core plugin which provides basic, filesystem based
+ * attachment temporary file handling.  This includes storing
+ * attachments of messages currently being composed, writing attachments
+ * to disk when drafts with attachments are re-opened and writing
+ * attachments to disk for inline display in current html compositions.
+ *
+ * Developers may wish to extend this class when creating attachment
+ * handler plugins:
+ *   require_once('plugins/filesystem_attachments/filesystem_attachments.php');
+ *   class myCustom_attachments extends filesystem_attachments
+ *
+ * @author Ziba Scott <ziba@umich.edu>
+ * @author Thomas Bruederli <roundcube@gmail.com>
+ * 
+ */
+class filesystem_attachments extends rcube_plugin
+{
+    public $task = 'mail';
+    
+    function init()
+    {
+        // Save a newly uploaded attachment
+        $this->add_hook('upload_attachment', array($this, 'upload'));
+
+        // Save an attachment from a non-upload source (draft or forward)
+        $this->add_hook('save_attachment', array($this, 'save'));
+
+        // Remove an attachment from storage
+        $this->add_hook('remove_attachment', array($this, 'remove'));
+
+        // When composing an html message, image attachments may be shown
+        $this->add_hook('display_attachment', array($this, 'display'));
+
+        // Get the attachment from storage and place it on disk to be sent
+        $this->add_hook('get_attachment', array($this, 'get_attachment'));
+
+        // Delete all temp files associated with this user
+        $this->add_hook('cleanup_attachments', array($this, 'cleanup'));
+    }
+
+    /**
+     * Save a newly uploaded attachment
+     */
+    function upload($args)
+    {
+        $args['status'] = false;
+        $rcmail = rcmail::get_instance();
+
+        // use common temp dir for file uploads
+        // #1484529: we need absolute path on Windows for move_uploaded_file()
+        $temp_dir = realpath($rcmail->config->get('temp_dir'));
+        $tmpfname = tempnam($temp_dir, 'rcmAttmnt');
+
+        if (move_uploaded_file($args['path'], $tmpfname) && file_exists($tmpfname)) {
+            $args['id'] = count($_SESSION['plugins']['filesystem_attachments']['tmp_files'])+1;
+            $args['path'] = $tmpfname;
+            $args['status'] = true;
+
+            // Note the file for later cleanup
+            $_SESSION['plugins']['filesystem_attachments']['tmp_files'][] = $tmpfname;
+        }
+
+        return $args;
+    }
+
+    /**
+     * Save an attachment from a non-upload source (draft or forward)
+     */
+    function save($args)
+    {
+        $args['status'] = false;
+        $rcmail = rcmail::get_instance();
+        $temp_dir = unslashify($rcmail->config->get('temp_dir'));
+        $tmp_path = tempnam($temp_dir, 'rcmAttmnt');
+
+        if ($fp = fopen($tmp_path, 'w')) {
+            fwrite($fp, $args['data']);
+            fclose($fp);
+            
+            $args['id'] = count($_SESSION['plugins']['filesystem_attachments']['tmp_files'])+1;
+            $args['path'] = $tmp_path;
+            $args['status'] = true;
+            
+            // Note the file for later cleanup
+            $_SESSION['plugins']['filesystem_attachments']['tmp_files'][] = $tmp_path;
+        }
+
+        return $args;
+    }
+
+    /**
+     * Remove an attachment from storage
+     * This is triggered by the remove attachment button on the compose screen
+     */
+    function remove($args)
+    {
+        $args['status'] = @unlink($args['path']);
+        return $args;
+    }
+
+    /**
+     * When composing an html message, image attachments may be shown
+     * For this plugin, the file is already in place, just check for
+     * the existance of the proper metadata
+     */
+    function display($args)
+    {
+        $args['status'] = file_exists($args['path']);
+        return $args;
+    }
+
+    /**
+     * This attachment plugin doesn't require any steps to put the file
+     * on disk for use.  This stub function is kept here to make this 
+     * class handy as a parent class for other plugins which may need it.
+     */
+    function get_attachment($args)
+    {
+        return $args;
+    }
+    
+    /**
+     * Delete all temp files associated with this user
+     */
+    function cleanup($args)
+    {
+        // $_SESSION['compose']['attachments'] is not a complete record of
+        // temporary files because loading a draft or starting a forward copies
+        // the file to disk, but does not make an entry in that array
+        if (is_array($_SESSION['plugins']['filesystem_attachments']['tmp_files'])){
+            foreach ($_SESSION['plugins']['filesystem_attachments']['tmp_files'] as $filename){
+                if(file_exists($filename)){
+                    unlink($filename);
+                }
+            }
+            unset($_SESSION['plugins']['filesystem_attachments']['tmp_files']);
+        }
+        return $args;
+    }
+}
diff --git a/plugins/http_authentication/http_authentication.php b/plugins/http_authentication/http_authentication.php
new file mode 100644
index 0000000..57422a7
--- /dev/null
+++ b/plugins/http_authentication/http_authentication.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * HTTP Basic Authentication
+ *
+ * Make use of an existing HTTP authentication and perform login with the existing user credentials
+ *
+ * @version 1.0
+ * @author Thomas Bruederli
+ */
+class http_authentication extends rcube_plugin
+{
+
+  function init()
+  {
+    $this->add_hook('startup', array($this, 'startup'));
+    $this->add_hook('authenticate', array($this, 'authenticate'));
+  }
+
+  function startup($args)
+  {
+    // change action to login
+    if ($args['task'] == 'mail' && empty($args['action']) && empty($_SESSION['user_id'])
+        && !empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW']))
+      $args['action'] = 'login';
+
+    return $args;
+  }
+
+  function authenticate($args)
+  {
+    if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
+      $args['user'] = $_SERVER['PHP_AUTH_USER'];
+      $args['pass'] = $_SERVER['PHP_AUTH_PW'];
+    }
+  
+    return $args;
+  }
+
+}
+
diff --git a/plugins/markasjunk/junk_act.png b/plugins/markasjunk/junk_act.png
new file mode 100644
index 0000000..b5a84f6
--- /dev/null
+++ b/plugins/markasjunk/junk_act.png
Binary files differ
diff --git a/plugins/markasjunk/junk_pas.png b/plugins/markasjunk/junk_pas.png
new file mode 100644
index 0000000..b88a561
--- /dev/null
+++ b/plugins/markasjunk/junk_pas.png
Binary files differ
diff --git a/plugins/markasjunk/localization/en_US.inc b/plugins/markasjunk/localization/en_US.inc
new file mode 100644
index 0000000..6f63e16
--- /dev/null
+++ b/plugins/markasjunk/localization/en_US.inc
@@ -0,0 +1,7 @@
+<?php
+
+$labels = array();
+$labels['buttontitle'] = 'Mark as Junk';
+$labels['reportedasjunk'] = 'Successfully reported as Junk';
+
+?>
\ No newline at end of file
diff --git a/plugins/markasjunk/markasjunk.js b/plugins/markasjunk/markasjunk.js
new file mode 100644
index 0000000..8b02d74
--- /dev/null
+++ b/plugins/markasjunk/markasjunk.js
@@ -0,0 +1,28 @@
+/* Mark-as-Junk plugin script */
+
+function rcmail_markasjunk(prop)
+{
+  if (!rcmail.env.uid && (!rcmail.message_list || !rcmail.message_list.get_selection().length))
+    return;
+  
+    var uids = rcmail.env.uid ? rcmail.env.uid : rcmail.message_list.get_selection().join(',');
+    
+    rcmail.set_busy(true, 'loading');
+    rcmail.http_post('plugin.markasjunk', '_uid='+uids+'&_mbox='+urlencode(rcmail.env.mailbox), true);
+}
+
+// callback for app-onload event
+if (window.rcmail) {
+  rcmail.addEventListener('init', function(evt) {
+    
+    // register command (directly enable in message view mode)
+    rcmail.register_command('plugin.markasjunk', rcmail_markasjunk, rcmail.env.uid);
+    
+    // add event-listener to message list
+    if (rcmail.message_list)
+      rcmail.message_list.addEventListener('select', function(list){
+        rcmail.enable_command('plugin.markasjunk', list.get_selection().length > 0);
+      });
+  })
+}
+
diff --git a/plugins/markasjunk/markasjunk.php b/plugins/markasjunk/markasjunk.php
new file mode 100644
index 0000000..959111d
--- /dev/null
+++ b/plugins/markasjunk/markasjunk.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Mark as Junk
+ *
+ * Sample plugin that adds a new button to the mailbox toolbar
+ * to mark the selected messages as Junk and move them to the Junk folder
+ *
+ * @version 1.0
+ * @author Thomas Bruederli
+ */
+class markasjunk extends rcube_plugin
+{
+  public $task = 'mail';
+
+  function init()
+  {
+    $this->register_action('plugin.markasjunk', array($this, 'request_action'));
+    $GLOBALS['IMAP_FLAGS']['JUNK'] = 'Junk';
+    
+    $rcmail = rcmail::get_instance();
+    if ($rcmail->action == '' || $rcmail->action == 'show') {
+      $this->include_script('markasjunk.js');
+      $this->add_texts('localization', true);
+      $this->add_button(array('command' => 'plugin.markasjunk', 'imagepas' => 'junk_pas.png', 'imageact' => 'junk_act.png'), 'toolbar');
+    }
+  }
+
+  function request_action()
+  {
+    $this->add_texts('localization');
+    
+    $uids = get_input_value('_uid', RCUBE_INPUT_POST);
+    $mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
+    
+    $rcmail = rcmail::get_instance();
+    $rcmail->imap->set_flag($uids, 'JUNK');
+    
+    if (($junk_mbox = $rcmail->config->get('junk_mbox')) && $mbox != $junk_mbox) {
+      $rcmail->output->command('move_messages', $junk_mbox);
+    }
+    
+    $rcmail->output->command('display_message', $this->gettext('reportedasjunk'), 'confirmation');
+    $rcmail->output->send();
+  }
+
+}
\ No newline at end of file
diff --git a/plugins/new_user_identity/new_user_identity.php b/plugins/new_user_identity/new_user_identity.php
new file mode 100644
index 0000000..7559569
--- /dev/null
+++ b/plugins/new_user_identity/new_user_identity.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * New user identity
+ *
+ * Populates a new user's default identity from LDAP on their first visit.
+ *
+ * This plugin requires that a working public_ldap directory be configured.
+ *
+ * @version 1.0
+ * @author Kris Steinhoff
+ *
+ * Example configuration:
+ *
+ *  // The id of the address book to use to automatically set a new
+ *  // user's full name in their new identity. (This should be an
+ *  // string, which refers to the $rcmail_config['ldap_public'] array.)
+ *  $rcmail_config['new_user_identity_addressbook'] = 'People';
+ *  
+ *  // When automatically setting a new users's full name in their
+ *  // new identity, match the user's login name against this field.
+ *  $rcmail_config['new_user_identity_match'] = 'uid';
+ *  
+ *  // Use the value in this field to automatically set a new users's
+ *  // full name in their new identity.
+ *  $rcmail_config['new_user_identity_field'] = 'name';
+ */
+class new_user_identity extends rcube_plugin
+{
+    function init()
+    {
+        $this->add_hook('create_user', array($this, 'lookup_user_name'));
+    }
+
+    function lookup_user_name($args)
+    {
+        $rcmail = rcmail::get_instance();
+        if ($addressbook = $rcmail->config->get('new_user_identity_addressbook')) {
+            $match = $rcmail->config->get('new_user_identity_match');
+            $ldap = $rcmail->get_address_book($addressbook);
+            $ldap->prop['search_fields'] = array($match);
+            $results = $ldap->search($match, $args['user'], TRUE);
+            if (count($results->records) == 1) {
+                $args['user_name'] = $results->records[0][$rcmail->config->get('new_user_identity_field')];
+            }
+        }
+        return $args;
+    }
+}
+?>
diff --git a/plugins/password/localization/en_US.inc b/plugins/password/localization/en_US.inc
new file mode 100644
index 0000000..b54bcd4
--- /dev/null
+++ b/plugins/password/localization/en_US.inc
@@ -0,0 +1,15 @@
+<?php
+
+$labels = array();
+$labels['changepasswd']  = 'Change Password';
+$labels['curpasswd']  = 'Current Password:';
+$labels['newpasswd']  = 'New Password:';
+$labels['confpasswd']  = 'Confirm New Password:';
+
+$messages = array();
+$messages['nopassword'] = "Please input new password.";
+$messages['nocurpassword'] = "Please input current password.";
+$messages['passwordincorrectly'] = "Current password incorrectly.";
+$messages['passwordinconsistency'] = "Inconsistency of password, please try again.";
+
+?>
\ No newline at end of file
diff --git a/plugins/password/localization/pl_PL.inc b/plugins/password/localization/pl_PL.inc
new file mode 100644
index 0000000..1979995
--- /dev/null
+++ b/plugins/password/localization/pl_PL.inc
@@ -0,0 +1,15 @@
+<?php
+
+$labels = array();
+$labels['changepasswd']  = 'Zmiana hasła';
+$labels['curpasswd']  = 'Aktualne hasło:';
+$labels['newpasswd']  = 'Nowe hasło:';
+$labels['confpasswd']  = 'Potwierdź hasło:';
+
+$messages = array();
+$messages['nopassword'] = 'Wprowadź nowe hasło.';
+$messages['nocurpassword'] = 'Wprowadź aktualne hasło.';
+$messages['passwordincorrect'] = 'Błędne aktualne hasło, spróbuj ponownie.';
+$messages['passwordinconsistency'] = 'Hasła nie pasują, spróbuj ponownie.';
+
+?>
diff --git a/plugins/password/password.js b/plugins/password/password.js
new file mode 100644
index 0000000..3d05b62
--- /dev/null
+++ b/plugins/password/password.js
@@ -0,0 +1,44 @@
+/* Password change interface (tab) */
+
+if (window.rcmail) {
+  rcmail.addEventListener('init', function(evt) {
+    // <span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span>
+    var tab = $('<span>').attr('id', 'settingstabpluginpassword').addClass('tablink');
+    
+    var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.password').html(rcmail.gettext('password')).appendTo(tab);
+    button.bind('click', function(e){ return rcmail.command('plugin.password', this) });
+
+    // add button and register commands
+    rcmail.add_element(tab, 'tabs');
+    rcmail.register_command('plugin.password', function() { rcmail.goto_url('plugin.password') }, true);
+    rcmail.register_command('plugin.password-save', function() { 
+	var input_curpasswd = rcube_find_object('_curpasswd');
+	var input_newpasswd = rcube_find_object('_newpasswd');
+        var input_confpasswd = rcube_find_object('_confpasswd');
+	
+	if (input_curpasswd && input_curpasswd.value=='') {
+	    alert(rcmail.gettext('nocurpassword', 'password'));
+	    input_curpasswd.focus();
+	} else if (input_newpasswd && input_newpasswd.value=='') {
+	    alert(rcmail.gettext('nopassword', 'password'));
+	    input_newpasswd.focus();
+	} else if (input_confpasswd && input_confpasswd.value=='') {
+	    alert(rcmail.gettext('nopassword', 'password'));
+	    input_confpasswd.focus();
+	} else if ((input_newpasswd && input_confpasswd) && (input_newpasswd.value != input_confpasswd.value)) {
+	    alert(rcmail.gettext('passwordinconsistency', 'password'));
+	    input_newpasswd.focus();
+	} else {
+	    rcmail.gui_objects.passform.submit();
+	}
+    }, true);
+  })
+
+    // set page title
+    if (rcmail.env.action == 'plugin.password' && rcmail.env.task == 'settings') {
+      var title = rcmail.gettext('changepasswd','password')
+      if (rcmail.env.product_name)
+        title = rcmail.env.product_name + ' :: ' + title;
+      rcmail.set_pagetitle(title);
+    }
+}
diff --git a/plugins/password/password.php b/plugins/password/password.php
new file mode 100644
index 0000000..4a35da1
--- /dev/null
+++ b/plugins/password/password.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * Change Password
+ *
+ * Sample plugin that adds a possibility to change password
+ * (Settings -> Password tab)
+ *
+ * @version 1.0
+ * @author Aleksander 'A.L.E.C' Machniak
+ */
+class password extends rcube_plugin
+{
+  public $task = 'settings';
+
+  function init()
+  {
+    $rcmail = rcmail::get_instance();
+    // add Tab label
+    $rcmail->output->add_label('password');
+    $this->register_action('plugin.password', array($this, 'password_init'));
+    $this->register_action('plugin.password-save', array($this, 'password_save'));
+    $this->register_handler('plugin.body', array($this, 'password_form'));
+    $this->include_script('password.js');
+  }
+
+  function password_init()
+  {
+    $this->add_texts('localization/');
+    rcmail::get_instance()->output->send('plugin');
+  }
+  
+  function password_save()
+  {
+    $rcmail = rcmail::get_instance();
+
+    $this->add_texts('localization/');
+
+    if (!isset($_POST['_curpasswd']) || !isset($_POST['_newpasswd']))
+      $rcmail->output->command('display_message', $this->gettext('nopassword'), 'error');
+    else {
+      $curpwd = get_input_value('_curpasswd', RCUBE_INPUT_POST);
+      $newpwd = get_input_value('_newpasswd', RCUBE_INPUT_POST);
+
+      if ($_SESSION['password'] != $rcmail->encrypt_passwd($curpwd))
+        $rcmail->output->command('display_message', $this->gettext('passwordincorrect'), 'error');
+      else if ($res = $this->_save($newpwd)) {
+        $rcmail->output->command('display_message', $this->gettext('successfullysaved'), 'confirmation');
+        $_SESSION['password'] = $rcmail->encrypt_passwd($newpwd);
+      } else
+        $rcmail->output->command('display_message', $this->gettext('errorsaving'), 'error');
+    }
+
+    rcmail_overwrite_action('plugin.password');
+    rcmail::get_instance()->output->send('plugin');
+  }
+
+  function password_form()
+  {
+    $rcmail = rcmail::get_instance();
+
+    // add some labels to client
+    $rcmail->output->add_label(
+	'password.nopassword',
+	'password.nocurpassword',
+        'password.passwordinconsistency',
+	'password.changepasswd'
+    );
+//    $rcmail->output->set_pagetitle($this->gettext('changepasswd'));
+    $rcmail->output->set_env('product_name', $rcmail->config->get('product_name'));
+
+    // allow the following attributes to be added to the <table> tag
+    $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+
+    // return the complete edit form as table
+    $out = '<table' . $attrib_str . ">\n\n";
+
+    $a_show_cols = array('curpasswd'   => array('type' => 'text'),
+                'newpasswd'   => array('type' => 'text'),
+                'confpasswd'   => array('type' => 'text'));
+
+    // show current password selection
+    $field_id = 'curpasswd';
+    $input_newpasswd = new html_passwordfield(array('name' => '_curpasswd', 'id' => $field_id, 'size' => 20));
+
+    $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                $field_id,
+                rep_specialchars_output($this->gettext('curpasswd')),
+                $input_newpasswd->show($rcmail->config->get('curpasswd')));
+
+    // show new password selection
+    $field_id = 'newpasswd';
+    $input_newpasswd = new html_passwordfield(array('name' => '_newpasswd', 'id' => $field_id, 'size' => 20));
+
+    $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                $field_id,
+                rep_specialchars_output($this->gettext('newpasswd')),
+                $input_newpasswd->show($rcmail->config->get('newpasswd')));
+
+    // show confirm password selection
+    $field_id = 'confpasswd';
+    $input_confpasswd = new html_passwordfield(array('name' => '_confpasswd', 'id' => $field_id, 'size' => 20));
+
+    $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
+                $field_id,
+                rep_specialchars_output($this->gettext('confpasswd')),
+                $input_confpasswd->show($rcmail->config->get('confpasswd')));
+
+    $out .= "\n</table>";
+
+    $out .= '<br />';
+    
+    $out .= $rcmail->output->button(array(
+	    'command' => 'plugin.password-save',
+	    'type' => 'input',
+	    'class' => 'button mainaction',
+	    'label' => 'save'
+    ));
+
+    $rcmail->output->add_gui_object('passform', 'password-form');
+
+    return $rcmail->output->form_tag(array(
+	'id' => 'password-form',
+	'name' => 'password-form',
+	'method' => 'post',
+	'action' => './?_task=settings&_action=plugin.password-save',
+	), $out);
+  }
+
+
+  private function _save($passwd)
+  {
+    $cfg = rcmail::get_instance()->config;
+
+    if (!($sql = $cfg->get('password_query')))
+      $sql = "SELECT update_passwd('%p', '%u')";
+        
+    $sql = str_replace('%u', $_SESSION['username'], $sql);
+    $sql = str_replace('%p', crypt($passwd), $sql);
+
+    if ($dsn = $cfg->get('db_passwd_dsn')) {
+      $db = new rcube_mdb2($dsn, '', FALSE);
+      $db->set_debug((bool)$cfg->get('sql_debug'));
+      $db->db_connect('w');
+    } else {
+      $db = rcmail::get_instance()->get_dbh();
+    }
+    
+    if (!$db->db_connected)
+      return false;
+    
+    $res = $db->query($sql);
+    $res = $db->fetch_array($res);
+
+    return $res;
+  }
+
+}
+
+?>
diff --git a/plugins/show_additional_headers/show_additional_headers.php b/plugins/show_additional_headers/show_additional_headers.php
new file mode 100644
index 0000000..c31c9df
--- /dev/null
+++ b/plugins/show_additional_headers/show_additional_headers.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Show additional message headers
+ *
+ * Proof-of-concept plugin which will fetch additional headers
+ * and display them in the message view.
+ *
+ * Enable the plugin in config/main.inc.php and add your desired headers:
+ *   $rcmail_config['show_additional_headers'] = array('User-Agent');
+ *
+ * @version 1.0
+ * @author Thomas Bruederli
+ * @website http://roundcube.net
+ */
+class show_additional_headers extends rcube_plugin
+{
+  public $task = 'mail';
+  
+  function init()
+  {
+    $rcmail = rcmail::get_instance();
+    if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
+      $this->add_hook('imap_init', array($this, 'imap_init'));
+      $this->add_hook('message_headers_output', array($this, 'message_headers'));
+    }
+  }
+  
+  function imap_init($p)
+  {
+    $rcmail = rcmail::get_instance();
+    if ($add_headers = $rcmail->config->get('show_additional_headers', array()))
+      $p['fetch_headers'] = trim($p['fetch_headers'].' ' . strtoupper(join(' ', $add_headers)));
+
+    return $p;
+  }
+
+  function message_headers($p)
+  {
+    $rcmail = rcmail::get_instance();
+    foreach ($rcmail->config->get('show_additional_headers', array()) as $header) {
+      $key = strtolower($header);
+      if ($value = $p['headers']->others[$key])
+        $p['output'][$key] = array('title' => $header, 'value' => $value);
+    }
+
+    return $p;
+  }
+}
diff --git a/plugins/subscriptions_option/localization/en_US.inc b/plugins/subscriptions_option/localization/en_US.inc
new file mode 100644
index 0000000..5a348e0
--- /dev/null
+++ b/plugins/subscriptions_option/localization/en_US.inc
@@ -0,0 +1,6 @@
+<?php
+
+$labels = array();
+$labels['useimapsubscriptions']  = 'Use IMAP Subscriptions';
+
+?>
diff --git a/plugins/subscriptions_option/subscriptions_option.php b/plugins/subscriptions_option/subscriptions_option.php
new file mode 100644
index 0000000..ba7236c
--- /dev/null
+++ b/plugins/subscriptions_option/subscriptions_option.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * Subscription Options
+ *
+ * A plugin which can enable or disable the use of imap subscriptions.
+ * It includes a toggle on the settings page under "Server Settings".
+ * The preference can also be locked
+ *
+ * Add it to the plugins list in config/main.inc.php to enable the user option
+ * The user option can be hidden and set globally by adding 'use_subscriptions'
+ * to the the 'dont_override' configure line:
+ * $rcmail_config['dont_override'] = array('use_subscriptions');
+ * and then set the global preference"
+ * $rcmail_config['use_subscriptions'] = true; // or false
+ *
+ * Roundcube caches folder lists.  When a user changes this option or visits
+ * their folder list, this cache is refreshed.  If the option is on the
+ * 'dont_override' list and the global option has changed, don't expect
+ * to see the change until the folder list cache is refreshed.
+ *
+ * @version 1.0
+ * @author Ziba Scott
+ */
+class subscriptions_option extends rcube_plugin
+{
+
+    function init()
+    {
+        $this->add_texts('localization/', false);
+        $dont_override = rcmail::get_instance()->config->get('dont_override', array());
+        if (!in_array('use_subscriptions', $dont_override)){
+            $this->add_hook('user_preferences', array($this, 'settings_table'));
+            $this->add_hook('save_preferences', array($this, 'save_prefs'));
+        }
+        $this->add_hook('list_mailboxes', array($this, 'list_mailboxes'));
+        $this->add_hook('manage_folders', array($this, 'manage_folders'));
+    }
+
+    function settings_table($args)
+    {
+        if ($args['section'] == 'server') {
+            $use_subscriptions = rcmail::get_instance()->config->get('use_subscriptions');
+            $field_id = 'rcmfd_use_subscriptions';
+            $use_subscriptions = new html_checkbox(array('name' => '_use_subscriptions', 'id' => $field_id, 'value' => 1));
+
+            $args['table']->add('title', html::label($field_id, Q($this->gettext('useimapsubscriptions'))));
+            $args['table']->add(null, $use_subscriptions->show($use_subscriptions?1:0));
+        }
+
+        return $args;
+    }
+
+    function save_prefs($args){
+        $rcmail = rcmail::get_instance();
+        $use_subscriptions = $rcmail->config->get('use_subscriptions');
+
+        $args['prefs']['use_subscriptions'] = isset($_POST['_use_subscriptions']) ? true : false;
+        // if the use_subscriptions preference changes, flush the folder cache
+        if (($use_subscriptions && !isset($_POST['_use_subscriptions'])) ||
+            (!$use_subscriptions && isset($_POST['_use_subscriptions']))) {
+                $rcmail->imap_init(true);
+                $rcmail->imap->clear_cache('mailboxes');
+            }
+
+        return $args;
+    }
+
+    function list_mailboxes($args){
+        $rcmail = rcmail::get_instance();
+        if (!$rcmail->config->get('use_subscriptions', true)) {
+            $args['folders'] = iil_C_ListMailboxes($rcmail->imap->conn, $rcmail->imap->_mod_mailbox($args['root']), $args['filter']);
+        }
+        return $args;
+    }
+
+    function manage_folders($args){
+        $rcmail = rcmail::get_instance();
+        if (!$rcmail->config->get('use_subscriptions', true)) {
+            $args['table']->remove_column('subscribed');
+        }
+        return $args;
+    }
+}
diff --git a/plugins/userinfo/localization/de_CH.inc b/plugins/userinfo/localization/de_CH.inc
new file mode 100644
index 0000000..5f236b6
--- /dev/null
+++ b/plugins/userinfo/localization/de_CH.inc
@@ -0,0 +1,9 @@
+<?php
+
+$labels = array();
+$labels['userinfo'] = 'Benutzerinfo';
+$labels['created'] = 'Erstellt';
+$labels['lastlogin'] = 'Letztes Login';
+$labels['defaultidentity'] = 'Standard-Absender';
+
+?>
\ No newline at end of file
diff --git a/plugins/userinfo/localization/en_US.inc b/plugins/userinfo/localization/en_US.inc
new file mode 100644
index 0000000..1a2fd90
--- /dev/null
+++ b/plugins/userinfo/localization/en_US.inc
@@ -0,0 +1,9 @@
+<?php
+
+$labels = array();
+$labels['userinfo'] = 'User info';
+$labels['created'] = 'Created';
+$labels['lastlogin'] = 'Last Login';
+$labels['defaultidentity'] = 'Default Identity';
+
+?>
\ No newline at end of file
diff --git a/plugins/userinfo/userinfo.js b/plugins/userinfo/userinfo.js
new file mode 100644
index 0000000..70a5085
--- /dev/null
+++ b/plugins/userinfo/userinfo.js
@@ -0,0 +1,16 @@
+/* Show user-info plugin script */
+
+if (window.rcmail) {
+  rcmail.addEventListener('init', function(evt) {
+    // <span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span>
+    var tab = $('<span>').attr('id', 'settingstabpluginuserinfo').addClass('tablink');
+    
+    var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.userinfo').html(rcmail.gettext('userinfo', 'userinfo')).appendTo(tab);
+    button.bind('click', function(e){ return rcmail.command('plugin.userinfo', this) });
+    
+    // add button and register command
+    rcmail.add_element(tab, 'tabs');
+    rcmail.register_command('plugin.userinfo', function(){ rcmail.goto_url('plugin.userinfo') }, true);
+  })
+}
+
diff --git a/plugins/userinfo/userinfo.php b/plugins/userinfo/userinfo.php
new file mode 100644
index 0000000..0f1b18c
--- /dev/null
+++ b/plugins/userinfo/userinfo.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Sample plugin that adds a new tab to the settings section
+ * to display some information about the current user
+ */
+class userinfo extends rcube_plugin
+{
+  public $task = 'settings';
+
+  function init()
+  {
+    $this->add_texts('localization/', array('userinfo'));
+    $this->register_action('plugin.userinfo', array($this, 'infostep'));
+    $this->include_script('userinfo.js');
+  }
+
+  function infostep()
+  {
+    $this->register_handler('plugin.body', array($this, 'infohtml'));
+    rcmail::get_instance()->output->send('plugin');
+  }
+  
+  function infohtml()
+  {
+    $rcmail = rcmail::get_instance();
+    $user = $rcmail->user;
+    
+    $table = new html_table(array('cols' => 2, 'cellpadding' => 3));
+
+    $table->add('title', 'ID');
+    $table->add('', Q($user->ID));
+    
+    $table->add('title', Q($this->gettext('username')));
+    $table->add('', Q($user->data['username']));
+    
+    $table->add('title', Q($this->gettext('server')));
+    $table->add('', Q($user->data['mail_host']));
+
+    $table->add('title', Q($this->gettext('created')));
+    $table->add('', Q($user->data['created']));
+
+    $table->add('title', Q($this->gettext('lastlogin')));
+    $table->add('', Q($user->data['last_login']));
+    
+    $identity = $user->get_identity();
+    $table->add('title', Q($this->gettext('defaultidentity')));
+    $table->add('', Q($identity['name'] . ' <' . $identity['email'] . '>'));
+    
+    return html::tag('h4', null, Q('Infos for ' . $user->get_username())) . $table->show();
+  }
+
+}
\ No newline at end of file
diff --git a/plugins/vcard_attachments/vcard_attachments.php b/plugins/vcard_attachments/vcard_attachments.php
new file mode 100644
index 0000000..da8ea1c
--- /dev/null
+++ b/plugins/vcard_attachments/vcard_attachments.php
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * Detect VCard attachments and show a button to add them to address book
+ *
+ * @version 1.0
+ * @author Thomas Bruederli
+ */
+class vcard_attachments extends rcube_plugin
+{
+  public $task = 'mail';
+  
+  private $message;
+  private $vcard_part;
+
+  function init()
+  {
+    $rcmail = rcmail::get_instance();
+    if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
+      $this->add_hook('message_load', array($this, 'message_load'));
+      $this->add_hook('template_object_messagebody', array($this, 'html_output'));
+    }
+    
+    $this->register_action('plugin.savevcard', array($this, 'save_vcard'));
+  }
+  
+  /**
+   * Check message attachments for vcards
+   */
+  function message_load($p)
+  {
+    $this->message = $p['object'];
+    
+    foreach ((array)$this->message->attachments as $attachment) {
+      if (in_array($attachment->mimetype, array('text/vcard', 'text/x-vcard')))
+        $this->vcard_part = $attachment->mime_id;
+    }
+  }
+  
+  /**
+   * This callback function adds a box below the message content
+   * if there is a vcard attachment available
+   */
+  function html_output($p)
+  {
+    if ($this->vcard_part) {
+      $vcard = new rcube_vcard($this->message->get_part_content($this->vcard_part));
+      
+      // successfully parsed vcard
+      if ($vcard->displayname) {
+        $display = $vcard->displayname;
+        if ($vcard->email[0])
+          $display .= ' <'.$vcard->email[0].'>';
+        
+        // add box below messsage body
+        $p['content'] .= html::p(array('style' => "margin:1em; padding:0.5em; border:1px solid #999; width: auto;"),
+          html::a(array(
+              'href' => "#",
+              'onclick' => "return plugin_vcard_save_contact('".JQ($this->vcard_part)."')",
+              'title' => "Save contact in local address book"),  // TODO: localize this title
+            html::img(array('src' => '/images/buttons/add_contact_act.png', 'align' => "middle")))
+            . ' ' . html::span(null, Q($display)));
+        
+        $this->include_script('vcardattach.js');
+      }
+    }
+    
+    return $p;
+  }
+  
+  /**
+   * Handler for request action
+   */
+  function save_vcard()
+  {
+    $uid = get_input_value('_uid', RCUBE_INPUT_POST);
+    $mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
+    $mime_id = get_input_value('_part', RCUBE_INPUT_POST);
+    
+    $rcmail = rcmail::get_instance();
+    $part = $uid && $mime_id ? $rcmail->imap->get_message_part($uid, $mime_id) : null;
+    
+    $error_msg = 'Failed to saved vcard'; // TODO: localize this text
+    
+    if ($part && ($vcard = new rcube_vcard($part)) && $vcard->displayname && $vcard->email) {
+      $contacts = $rcmail->get_address_book(null, true);
+      
+      // check for existing contacts
+      $existing = $contacts->search('email', $vcard->email[0], true, false);
+      if ($done = $existing->count) {
+        $rcmail->output->command('display_message', $this->gettext('contactexists'), 'warning');
+      }
+      else {
+        // add contact
+        $success = $contacts->insert(array(
+          'name' => $vcard->displayname,
+          'firstname' => $vcard->firstname,
+          'surname' => $vcard->surname,
+          'email' => $vcard->email[0],
+          'vcard' => $vcard->export(),
+        ));
+        
+        if ($success)
+          $rcmail->output->command('display_message', $this->gettext('addedsuccessfully'), 'confirmation');
+        else
+          $rcmail->output->command('display_message', $error_msg, 'error');
+      }
+    }
+    else
+      $rcmail->output->command('display_message', $error_msg, 'error');
+    
+    $rcmail->output->send();
+  }
+  
+}
\ No newline at end of file
diff --git a/plugins/vcard_attachments/vcardattach.js b/plugins/vcard_attachments/vcardattach.js
new file mode 100644
index 0000000..e03e508
--- /dev/null
+++ b/plugins/vcard_attachments/vcardattach.js
@@ -0,0 +1,10 @@
+
+function plugin_vcard_save_contact(mime_id)
+{
+  rcmail.set_busy(true, 'loading');
+  rcmail.http_post('plugin.savevcard', '_uid='+rcmail.env.uid+'&_mbox='+urlencode(rcmail.env.mailbox)+'&_part='+urlencode(mime_id), true);
+  
+  return false;
+}
+
+
diff --git a/program/include/html.php b/program/include/html.php
index 01ad415..78e696c 100644
--- a/program/include/html.php
+++ b/program/include/html.php
@@ -33,7 +33,7 @@
     protected $content;
 
     public static $common_attrib = array('id','class','style','title','align');
-    public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','tr','th','td','style');
+    public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','tr','th','td','style','script');
     public static $lc_tags = true;
 
     /**
@@ -599,6 +599,34 @@
         $this->header[] = $cell;
     }
 
+     /**
+     * Remove a column from a table
+     * Useful for plugins making alterations
+     * 
+     * @param string $class 
+     */
+    public function remove_column($class)
+    {
+        // Remove the header
+        foreach($this->header as $index=>$header){
+            if($header->attrib['class'] == $class){
+                unset($this->header[$index]);
+                break;
+            }
+        }
+
+        // Remove cells from rows
+        foreach($this->rows as $i=>$row){
+            foreach($row->cells as $j=>$cell){
+                if($cell->attrib['class'] == $class){
+                    unset($this->rows[$i]->cells[$j]);
+                    break;
+                }
+            }
+        }
+    }
+
+
     /**
      * Jump to next row
      *
diff --git a/program/include/iniset.php b/program/include/iniset.php
index 234f9eb..46f3750 100755
--- a/program/include/iniset.php
+++ b/program/include/iniset.php
@@ -22,7 +22,7 @@
 
 
 // application constants
-define('RCMAIL_VERSION', '0.2-trunk');
+define('RCMAIL_VERSION', '0.3-trunk');
 define('RCMAIL_CHARSET', 'UTF-8');
 define('JS_OBJECT_NAME', 'rcmail');
 
diff --git a/program/include/main.inc b/program/include/main.inc
index b22be1a..b3d0dab 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -88,9 +88,9 @@
  * @return string Localized text
  * @see rcmail::gettext()
  */
-function rcube_label($p)
+function rcube_label($p, $domain=null)
 {
-  return rcmail::get_instance()->gettext($p);
+  return rcmail::get_instance()->gettext($p, $domain);
 }
 
 
@@ -302,12 +302,11 @@
  */
 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
   {
-  global $OUTPUT;
   static $html_encode_arr = false;
   static $js_rep_table = false;
   static $xml_rep_table = false;
 
-  $charset = $OUTPUT->get_charset();
+  $charset = rcmail::get_instance()->config->get('charset', RCMAIL_CHARSET);
   $is_iso_8859_1 = false;
   if ($charset == 'ISO-8859-1') {
     $is_iso_8859_1 = true;
@@ -692,11 +691,11 @@
   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
 
   // convert attributes to an associative array (name => value)
-  if ($regs)
-    foreach ($regs as $attr)
-      {
-      $attrib[strtolower($attr[1])] = $attr[3] . $attr[4];
-      }
+  if ($regs) {
+    foreach ($regs as $attr) {
+      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
+    }
+  }
 
   return $attrib;
   }
@@ -829,9 +828,13 @@
  */
 function console()
   {
+  $args = func_get_args();
+  if (class_exists('rcmail', false))
+      rcmail::get_instance()->plugins->exec_hook('console', $args);
+
   $msg = array();
-  foreach (func_get_args() as $arg)
-    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
+  foreach ($args as $arg)
+     $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
 
   if (!($GLOBALS['CONFIG']['debug_level'] & 4))
     write_log('console', join(";\n", $msg));
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 9aad25b..56fc2f5 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -37,6 +37,7 @@
   public $db;
   public $imap;
   public $output;
+  public $plugins;
   public $task = 'mail';
   public $action = '';
   public $comm_path = './';
@@ -88,7 +89,7 @@
       $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
       openlog($syslog_id, LOG_ODELAY, $syslog_facility);
     }
-    				
+
     // set task and action properties
     $this->set_task(strip_quotes(get_input_value('_task', RCUBE_INPUT_GPC)));
     $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
@@ -131,6 +132,9 @@
     // create IMAP object
     if ($this->task == 'mail')
       $this->imap_init();
+      
+    // create plugin API and load plugins
+    $this->plugins = rcube_plugin_api::get_instance();
   }
   
   
@@ -255,9 +259,18 @@
     $contacts = null;
     $ldap_config = (array)$this->config->get('ldap_public');
     $abook_type = strtolower($this->config->get('address_book_type'));
+
+    $plugin = $this->plugins->exec_hook('get_address_book', array('id' => $id, 'writeable' => $writeable));
     
-    if ($id && $ldap_config[$id]) {
+    // plugin returned instance of a rcube_addressbook
+    if ($plugin['instance'] instanceof rcube_addressbook) {
+      $contacts = $plugin['instance'];
+    }
+    else if ($id && $ldap_config[$id]) {
       $contacts = new rcube_ldap($ldap_config[$id]);
+    }
+    else if ($id === '0') {
+      $contacts = new rcube_contacts($this->db, $this->user->ID);
     }
     else if ($abook_type == 'ldap') {
       // Use the first writable LDAP address book.
@@ -598,7 +611,7 @@
    * @param mixed Named parameters array or label name
    * @return string Localized text
    */
-  public function gettext($attrib)
+  public function gettext($attrib, $domain=null)
   {
     // load localization files if not done yet
     if (empty($this->texts))
@@ -613,9 +626,12 @@
 
     $command_name = !empty($attrib['command']) ? $attrib['command'] : NULL;
     $alias = $attrib['name'] ? $attrib['name'] : ($command_name && $command_label_map[$command_name] ? $command_label_map[$command_name] : '');
-
+    
+    // check for text with domain
+    if ($domain && ($text_item = $this->texts[$domain.'.'.$alias]))
+      ;
     // text does not exist
-    if (!($text_item = $this->texts[$alias])) {
+    else if (!($text_item = $this->texts[$alias])) {
       /*
       raise_error(array(
         'code' => 500,
@@ -677,7 +693,7 @@
    *
    * @param string Language ID
    */
-  public function load_language($lang = null)
+  public function load_language($lang = null, $add = array())
   {
     $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
     
@@ -707,6 +723,10 @@
       
       $_SESSION['language'] = $lang;
     }
+
+    // append additional texts (from plugin)
+    if (is_array($add) && !empty($add))
+      $this->texts += $add;
   }
 
 
@@ -920,18 +940,20 @@
   {
     if (!is_array($p))
       $p = array('_action' => @func_get_arg(0));
+      
+    $task = $p['_task'] ? $p['_task'] : $p['task'];
+    if (!$task || !in_array($task, rcmail::$main_tasks))
+      $task = $this->task;
 
-    if (!$p['task'] || !in_array($p['task'], rcmail::$main_tasks))
-      $p['task'] = $this->task;
-
-    $p['_task'] = $p['task'];
+    $p['_task'] = $task;
     unset($p['task']);
 
     $url = './';
     $delm = '?';
-    foreach (array_reverse($p) as $par => $val)
+    foreach (array_reverse($p) as $key => $val)
     {
       if (!empty($val)) {
+        $par = $key[0] == '_' ? $key : '_'.$key;
         $url .= $delm.urlencode($par).'='.urlencode($val);
         $delm = '&';
       }
diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php
new file mode 100644
index 0000000..9e970f2
--- /dev/null
+++ b/program/include/rcube_addressbook.php
@@ -0,0 +1,169 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_addressbook.php                                 |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2006-2009, RoundCube Dev. - Switzerland                 |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Interface to the local address book database                        |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id:  $
+
+*/
+
+
+/**
+ * Abstract skeleton of an address book/repository
+ *
+ * @package Addressbook
+ */
+abstract class rcube_addressbook
+{
+    /** public properties */
+    var $primary_key;
+    var $readonly = true;
+    var $ready = false;
+    var $list_page = 1;
+    var $page_size = 10;
+
+    /**
+     * Save a search string for future listings
+     *
+     * @param mixed Search params to use in listing method, obtained by get_search_set()
+     */
+    abstract function set_search_set($filter);
+
+    /**
+     * Getter for saved search properties
+     *
+     * @return mixed Search properties used by this class
+     */
+    abstract function get_search_set();
+
+    /**
+     * Reset saved results and search parameters
+     */
+    abstract function reset();
+
+    /**
+     * List the current set of contact records
+     *
+     * @param  array  List of cols to show
+     * @param  int    Only return this number of records, use negative values for tail
+     * @return array  Indexed list of contact records, each a hash array
+     */
+    abstract function list_records($cols=null, $subset=0);
+
+    /**
+     * Search records
+     *
+     * @param array   List of fields to search in
+     * @param string  Search value
+     * @param boolean True if results are requested, False if count only
+     * @return Indexed list of contact records and 'count' value
+     */
+    abstract function search($fields, $value, $strict=false, $select=true);
+
+    /**
+     * Count number of available contacts in database
+     *
+     * @return object rcube_result_set Result set with values for 'count' and 'first'
+     */
+    abstract function count();
+
+    /**
+     * Return the last result set
+     *
+     * @return object rcube_result_set Current result set or NULL if nothing selected yet
+     */
+    abstract function get_result();
+
+    /**
+     * Get a specific contact record
+     *
+     * @param mixed record identifier(s)
+     * @param boolean True to return record as associative array, otherwise a result set is returned
+     * @return mixed Result object with all record fields or False if not found
+     */
+    abstract function get_record($id, $assoc=false);
+
+    /**
+     * Close connection to source
+     * Called on script shutdown
+     */
+    function close() { }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Create a new contact record
+     *
+     * @param array Assoziative array with save data
+     * @param boolean True to check for duplicates first
+     * @return The created record ID on success, False on error
+     */
+    function insert($save_data, $check=false)
+    {
+      /* empty for read-only address books */
+    }
+
+    /**
+     * Update a specific contact record
+     *
+     * @param mixed Record identifier
+     * @param array Assoziative array with save data
+     * @return True on success, False on error
+     */
+    function update($id, $save_cols)
+    {
+      /* empty for read-only address books */
+    }
+
+    /**
+     * Mark one or more contact records as deleted
+     *
+     * @param array  Record identifiers
+     */
+    function delete($ids)
+    {
+      /* empty for read-only address books */
+    }
+
+    /**
+     * Remove all records from the database
+     */
+    function delete_all()
+    {
+      /* empty for read-only address books */
+    }
+
+}
+ 
\ No newline at end of file
diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php
index 7be2b0d..1312a73 100644
--- a/program/include/rcube_config.php
+++ b/program/include/rcube_config.php
@@ -74,6 +74,7 @@
     // fix paths
     $this->prop['log_dir'] = $this->prop['log_dir'] ? unslashify($this->prop['log_dir']) : INSTALL_PATH . 'logs';
     $this->prop['temp_dir'] = $this->prop['temp_dir'] ? unslashify($this->prop['temp_dir']) : INSTALL_PATH . 'temp';
+    $this->prop['plugins_dir'] = $this->prop['plugins_dir'] ? unslashify($this->prop['plugins_dir']) : INSTALL_PATH . 'plugins';
 
     // fix default imap folders encoding
     foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder)
diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php
index 65d89ca..f440e5f 100644
--- a/program/include/rcube_contacts.php
+++ b/program/include/rcube_contacts.php
@@ -25,7 +25,7 @@
  *
  * @package Addressbook
  */
-class rcube_contacts
+class rcube_contacts extends rcube_addressbook
 {
   var $db = null;
   var $db_name = '';
@@ -56,30 +56,6 @@
     $this->db_name = get_table_name('contacts');
     $this->user_id = $user;
     $this->ready = $this->db && !$this->db->is_error();
-  }
-
-
-  /**
-   * 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;
   }
 
 
@@ -115,13 +91,6 @@
     $this->search_fields = null;
     $this->search_string = null;
   }
-  
-  
-  /**
-   * Close connection to source
-   * Called on script shutdown
-   */
-  function close(){}
   
   
   /**
@@ -233,7 +202,7 @@
    *
    * @return Result array or NULL if nothing selected yet
    */
-  function get_result($as_res=true)
+  function get_result()
   {
     return $this->result;
   }
diff --git a/program/include/rcube_html_page.php b/program/include/rcube_html_page.php
index 78f6176..c83c6ae 100644
--- a/program/include/rcube_html_page.php
+++ b/program/include/rcube_html_page.php
@@ -31,8 +31,8 @@
     protected $scripts = array();
     protected $charset = 'UTF-8';
 
-    protected $script_tag_file = "<script type=\"text/javascript\" src=\"%s%s\"></script>\n";
-    protected $script_tag      = "<script type=\"text/javascript\">\n<!--\n%s\n\n//-->\n</script>\n";
+    protected $script_tag_file = "<script type=\"text/javascript\" src=\"%s\"></script>\n";
+    protected $script_tag  =  "<script type=\"text/javascript\">\n/* <![CDATA[ */\n%s\n/* ]]> */\n</script>";
     protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
 
     protected $title = '';
@@ -53,6 +53,9 @@
     public function include_script($file, $position='head')
     {
         static $sa_files = array();
+        
+        if (!ereg('^https?://', $file) && $file[0] != '/')
+          $file = $this->scripts_path . $file;
 
         if (in_array($file, $sa_files)) {
             return;
@@ -165,7 +168,7 @@
         // definition of the code to be placed in the document header and footer
         if (is_array($this->script_files['head'])) {
             foreach ($this->script_files['head'] as $file) {
-                $__page_header .= sprintf($this->script_tag_file, $this->scripts_path, $file);
+                $__page_header .= sprintf($this->script_tag_file, $file);
             }
         }
 
@@ -180,7 +183,7 @@
 
         if (is_array($this->script_files['foot'])) {
             foreach ($this->script_files['foot'] as $file) {
-                $__page_footer .= sprintf($this->script_tag_file, $this->scripts_path, $file);
+                $__page_footer .= sprintf($this->script_tag_file, $file);
             }
         }
 
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index da7c5bf..e2b6c0d 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -55,6 +55,7 @@
   var $default_charset = 'ISO-8859-1';
   var $default_folders = array('INBOX');
   var $default_folders_lc = array('inbox');
+  var $fetch_add_headers = '';
   var $cache = array();
   var $cache_keys = array();  
   var $cache_changes = array();
@@ -428,8 +429,16 @@
     if (is_array($a_mboxes))
       return $a_mboxes;
 
-    // retrieve list of folders from IMAP server
-    $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
+    // Give plugins a chance to provide a list of mailboxes
+    $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes',array('root'=>$root,'filter'=>$filter));
+    if (isset($data['folders'])) {
+        $a_folders = $data['folders'];
+    }
+    else{
+        // retrieve list of folders from IMAP server
+        $a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
+    }
+
     
     if (!is_array($a_folders) || !sizeof($a_folders))
       $a_folders = array();
@@ -775,7 +784,7 @@
     $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_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs, false, $this->fetch_add_headers);
     $deleted_count = 0;
     
     if (!empty($a_header_index))
@@ -829,14 +838,14 @@
         if ($this->sort_field && $this->search_sort_field != $this->sort_field)
           $this->search('', $this->search_string, $this->search_charset, $this->sort_field);
 
-	if ($this->sort_order == 'DESC')
+        if ($this->sort_order == 'DESC')
           $this->cache[$key] = array_reverse($this->search_set);
-	else
-	  $this->cache[$key] = $this->search_set;
+        else
+          $this->cache[$key] = $this->search_set;
         }
       else
         {
-	$a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field);
+        $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, join(',', $this->search_set), $this->sort_field, false);
 
         if ($this->sort_order=="ASC")
           asort($a_index);
@@ -923,7 +932,7 @@
         
 
       // fetch complete headers and add to cache
-      $headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
+      $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, false, $this->fetch_add_headers);
       $this->add_message_cache($cache_key, $headers->id, $headers);
       }
 
@@ -1062,7 +1071,7 @@
     if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
       return $headers;
 
-    $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr);
+    $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers);
 
     // write headers cache
     if ($headers)
@@ -2227,7 +2236,7 @@
     if ($cache_count==$msg_count)
       {
       // get highest index
-      $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
+      $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count", false, $this->fetch_add_headers);
       $cache_uid = array_pop($cache_index);
       
       // uids of highest message matches -> cache seems OK
@@ -2277,7 +2286,7 @@
         
         // featch headers if unserialize failed
         if (empty($this->cache[$cache_key][$uid]))
-          $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true);
+          $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers);
         }
       }
       
diff --git a/program/include/rcube_json_output.php b/program/include/rcube_json_output.php
index 0bd3a2b..a14f4ae 100644
--- a/program/include/rcube_json_output.php
+++ b/program/include/rcube_json_output.php
@@ -196,26 +196,33 @@
      * @return void
      * @deprecated
      */
-    public function remote_response($add='', $flush=false)
+    public function remote_response($add='')
     {
         static $s_header_sent = false;
 
         if (!$s_header_sent) {
             $s_header_sent = true;
             send_nocacheing_headers();
-            header('Content-Type: application/x-javascript; charset=' . $this->get_charset());
+            header('Content-Type: text/plain; charset=' . $this->get_charset());
             print '/** ajax response ['.date('d/M/Y h:i:s O')."] **/\n";
         }
 
         // unset default env vars
         unset($this->env['task'], $this->env['action'], $this->env['comm_path']);
 
-        // send response code
-        echo $this->get_js_commands() . $add;
+        $rcmail = rcmail::get_instance();
+        $response = array('action' => $rcmail->action, 'unlock' => (bool)$_REQUEST['_unlock']);
+        
+        if (!empty($this->env))
+          $response['env'] = $this->env;
+          
+        if (!empty($this->texts))
+          $response['texts'] = $this->texts;
 
-        // flush the output buffer
-        if ($flush)
-            flush();
+        // send response code
+        $response['exec'] = $this->get_js_commands() . $add;
+
+        echo json_serialize($response);
     }
     
     
@@ -227,14 +234,7 @@
     private function get_js_commands()
     {
         $out = '';
-	
-	if (sizeof($this->env))
-	    $out .= 'this.set_env('.json_serialize($this->env).");\n";
         
-        foreach($this->texts as $name => $text) {
-            $out .= sprintf("this.add_label('%s', '%s');\n", $name, JQ($text));
-        }
-
         foreach ($this->commands as $i => $args) {
             $method = array_shift($args);
             foreach ($args as $i => $arg) {
diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php
index 016fe94..544c7f7 100644
--- a/program/include/rcube_ldap.php
+++ b/program/include/rcube_ldap.php
@@ -24,7 +24,7 @@
  *
  * @package Addressbook
  */
-class rcube_ldap
+class rcube_ldap extends rcube_addressbook
 {
   var $conn;
   var $prop = array();
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index ec3be4b..5c03b21 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -84,6 +84,9 @@
     else {
       $this->body = $this->imap->get_body($uid);
     }
+    
+    // notify plugins and let them analyze this structured message object
+    $this->app->plugins->exec_hook('message_load', array('object' => $this));
   }
   
   
diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php
new file mode 100644
index 0000000..62f65a9
--- /dev/null
+++ b/program/include/rcube_plugin.php
@@ -0,0 +1,196 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_plugin.php                                      |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland                 |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |  Abstract plugins interface/class                                     |
+ |  All plugins need to extend this class                                |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: $
+
+*/
+
+/**
+ * Plugin interface class
+ *
+ * @package Core
+ */
+abstract class rcube_plugin
+{
+  public $ID;
+  public $api;
+  public $task;
+  protected $home;
+  protected $urlbase;
+
+  /**
+   * Default constructor.
+   */
+  public function __construct($api)
+  {
+    $this->ID = get_class($this);
+    $this->api = $api;
+    $this->home = $api->dir . DIRECTORY_SEPARATOR . $this->ID;
+    $this->urlbase = $api->url . $this->ID . '/';
+  }
+  
+  /**
+   * Initialization method, needs to be implemented by the plugin itself
+   */
+  abstract function init();
+
+  /**
+   * Register a callback function for a specific (server-side) hook
+   *
+   * @param string Hook name
+   * @param mixed Callback function as string or array with object reference and method name
+   */
+  public function add_hook($hook, $callback)
+  {
+    $this->api->register_hook($hook, $callback);
+  }
+  
+  /**
+   * Load localized texts from the plugins dir
+   *
+   * @param string Directory to search in
+   * @param mixed Make texts also available on the client (array with list or true for all)
+   */
+  public function add_texts($dir, $add2client = false)
+  {
+    $domain = $this->ID;
+    
+    $lang = $_SESSION['language'];
+    $locdir = slashify(realpath(slashify($this->home) . $dir));
+    $texts = array();
+    
+    foreach (array('en_US', $lang) as $lng) {
+      @include($locdir . $lng . '.inc');
+      $texts = (array)$labels + (array)$messages + (array)$texts;
+    }
+
+    // prepend domain to text keys and add to the application texts repository
+    if (!empty($texts)) {
+      $add = array();
+      foreach ($texts as $key => $value)
+        $add[$domain.'.'.$key] = $value;
+
+      $rcmail = rcmail::get_instance();
+      $rcmail->load_language($lang, $add);
+      
+      // add labels to client
+      if ($add2client) {
+        $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add);
+        $rcmail->output->add_label($js_labels);
+      }
+    }
+  }
+  
+  /**
+   * Wrapper for rcmail::gettext() adding the plugin ID as domain
+   *
+   * @return string Localized text
+   * @see rcmail::gettext()
+   */
+  function gettext($p)
+  {
+    return rcmail::get_instance()->gettext($p, $this->ID);
+  }
+  
+  /**
+    * Register a handler for a specific client-request action
+    *
+    * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
+    *
+    * @param string Action name (should be unique)
+    * @param mixed Callback function as string or array with object reference and method name
+   */
+  public function register_action($action, $callback)
+  {
+    $this->api->register_action($action, $this->ID, $callback);
+  }
+
+  /**
+   * Register a handler function for a template object
+   *
+   * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
+   * will be replaced by the return value if the registered callback function.
+   *
+   * @param string Object name (should be unique and start with 'plugin.')
+   * @param mixed Callback function as string or array with object reference and method name
+   */
+  public function register_handler($name, $callback)
+  {
+    $this->api->register_handler($name, $this->ID, $callback);
+  }
+
+  /**
+   * Make this javascipt file available on the client
+   *
+   * @param string File path; absolute or relative to the plugin directory
+   */
+  public function include_script($fn)
+  {
+    $this->api->include_script($this->ressource_url($fn));
+  }
+
+  /**
+   * Make this stylesheet available on the client
+   *
+   * @param string File path; absolute or relative to the plugin directory
+   */
+  public function include_stylesheet($fn)
+  {
+    $this->api->include_stylesheet($this->ressource_url($fn));
+  }
+  
+  /**
+   * Append a button to a certain container
+   *
+   * @param array Hash array with named parameters (as used in skin templates)
+   * @param string Container name where the buttons should be added to
+   * @see rcube_remplate::button()
+   */
+  public function add_button($p, $container)
+  {
+    if ($this->api->output->type == 'html') {
+      // fix relative paths
+      foreach (array('imagepas', 'imageact', 'imagesel') as $key)
+        if ($p[$key])
+          $p[$key] = $this->api->url . $this->ressource_url($p[$key]);
+      
+      $this->api->add_content($this->api->output->button($p), $container);
+    }
+  }
+
+  /**
+   * Make the given file name link into the plugin directory
+   */
+  private function ressource_url($fn)
+  {
+    if ($fn[0] != '/' && !eregi('^https?://', $fn))
+      return $this->ID . '/' . $fn;
+    else
+      return $fn;
+  }
+
+  /**
+   * Callback function for array_map
+   */
+  private function label_map_callback($key)
+  {
+    return $this->ID.'.'.$key;
+  }
+
+
+}
+
diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php
new file mode 100644
index 0000000..4780f2e
--- /dev/null
+++ b/program/include/rcube_plugin_api.php
@@ -0,0 +1,312 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_plugin_api.php                                  |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2008-2009, RoundCube Dev. - Switzerland                 |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Plugins repository                                                  |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: $
+
+*/
+
+/**
+ * The plugin loader and global API
+ *
+ * @package Core
+ */
+class rcube_plugin_api
+{
+  static private $instance;
+  
+  public $dir;
+  public $url = 'plugins/';
+  public $output;
+  
+  public $handlers = array();
+  private $plugins = array();
+  private $actions = array();
+  private $actionmap = array();
+  private $objectsmap = array();
+  private $template_contents = array();
+  
+  private  $required_plugins = array('filesystem_attachments');
+
+  /**
+   * This implements the 'singleton' design pattern
+   *
+   * @return object rcube_plugin_api The one and only instance if this class
+   */
+  static function get_instance()
+  {
+    if (!self::$instance) {
+      self::$instance = new rcube_plugin_api();
+    }
+
+    return self::$instance;
+  }
+  
+  
+  /**
+   * Private constructor
+   */
+  private function __construct()
+  {
+    $rcmail = rcmail::get_instance();
+    $this->dir = realpath($rcmail->config->get('plugins_dir'));
+  }
+  
+  
+  /**
+   * Load and init all enabled plugins
+   *
+   * This has to be done after rcmail::load_gui() or rcmail::init_json()
+   * was called because plugins need to have access to rcmail->output
+   */
+  public function init()
+  {
+    $rcmail = rcmail::get_instance();
+    $this->output = $rcmail->output;
+    
+    $plugins_dir = dir($this->dir);
+    $plugins_enabled = (array)$rcmail->config->get('plugins', array());
+    
+    foreach ($plugins_enabled as $plugin_name) {
+      $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
+      
+      if (file_exists($fn)) {
+        include($fn);
+        
+        // instantiate class if exists
+        if (class_exists($plugin_name, false)) {
+          $plugin = new $plugin_name($this);
+          // check inheritance and task specification
+          if (is_subclass_of($plugin, 'rcube_plugin') && (!$plugin->task || $plugin->task == $rcmail->task)) {
+            $plugin->init();
+            $this->plugins[] = $plugin;
+          }
+        }
+        else {
+          raise_error(array('code' => 520, 'type' => 'php', 'message' => "No plugin class $plugin_name found in $fn"), true, false);
+        }
+      }
+      else {
+        raise_error(array('code' => 520, 'type' => 'php', 'message' => "Failed to load plugin file $fn"), true, false);
+      }
+    }
+    
+    // check existance of all required core plugins
+    foreach ($this->required_plugins as $plugin_name) {
+      $loaded = false;
+      foreach ($this->plugins as $plugin) {
+        if ($plugin instanceof $plugin_name) {
+          $loaded = true;
+          break;
+        }
+      }
+      
+      // load required core plugin if no derivate was found
+      if (!$loaded) {
+        $fn = $plugins_dir->path . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
+        if (file_exists($fn)) {
+          include($fn);
+          
+          if (class_exists($plugin_name, false)) {
+            $plugin = new $plugin_name($this);
+            // check inheritance
+            if (is_subclass_of($plugin, 'rcube_plugin')) {
+              $plugin->init();
+              $this->plugins[] = $plugin;
+              $loaded = true;
+            }
+          }
+        }
+      }
+      
+      // trigger fatal error if still not loaded
+      if (!$loaded) {
+        raise_error(array('code' => 520, 'type' => 'php', 'message' => "Requried plugin $plugin_name was not loaded"), true, true);
+      }
+    }
+
+    // register an internal hook
+    $this->register_hook('template_container', array($this, 'template_container_hook'));
+    
+    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
+    
+    
+    // call imap_init right now
+    // (should actually be done in rcmail::imap_init() but plugins are not initialized then)
+    if ($rcmail->imap) {
+      $hook = $this->exec_hook('imap_init', array('fetch_headers' => $rcmail->imap->fetch_add_headers));
+      if ($hook['fetch_headers'])
+        $rcmail->imap->fetch_add_headers = $hook['fetch_headers'];
+    }
+  }
+  
+  
+  /**
+   * Allows a plugin object to register a callback for a certain hook
+   *
+   * @param string Hook name
+   * @param mixed String with global function name or array($obj, 'methodname')
+   */
+  public function register_hook($hook, $callback)
+  {
+    if (is_callable($callback))
+      $this->handlers[$hook][] = $callback;
+    else
+      raise_error(array('code' => 521, 'type' => 'php', 'message' => "Invalid callback function for $hook"), true, false);
+  }
+  
+  
+  /**
+   * Triggers a plugin hook.
+   * This is called from the application and executes all registered handlers
+   *
+   * @param string Hook name
+   * @param array Named arguments (key->value pairs)
+   * @return array The (probably) altered hook arguments
+   */
+  public function exec_hook($hook, $args = array())
+  {
+    $args += array('abort' => false);
+    
+    foreach ((array)$this->handlers[$hook] as $callback) {
+      $ret = call_user_func($callback, $args);
+      if ($ret && is_array($ret))
+        $args = $ret + $args;
+      
+      if ($args['abort'])
+        break;
+    }
+    
+    return $args;
+  }
+
+
+  /**
+   * Let a plugin register a handler for a specific request
+   *
+   * @param string Action name (_task=mail&_action=plugin.foo)
+   * @param string Plugin name that registers this action
+   * @param mixed Callback: string with global function name or array($obj, 'methodname')
+   */
+  public function register_action($action, $owner, $callback)
+  {
+    // check action name
+    if (strpos($action, 'plugin.') !== 0)
+      $action = 'plugin.'.$action;
+    
+    // can register action only if it's not taken or registered by myself
+    if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
+      $this->actions[$action] = $callback;
+      $this->actionmap[$action] = $owner;
+    }
+    else {
+      raise_error(array('code' => 523, 'type' => 'php', 'message' => "Cannot register action $action; already taken by another plugin"), true, false);
+    }
+  }
+
+
+  /**
+   * This method handles requests like _task=mail&_action=plugin.foo
+   * It executes the callback function that was registered with the given action.
+   *
+   * @param string Action name
+   */
+  public function exec_action($action)
+  {
+    if (isset($this->actions[$action])) {
+      call_user_func($this->actions[$action]);
+    }
+    else {
+      raise_error(array('code' => 524, 'type' => 'php', 'message' => "No handler found for action $action"), true, true);
+    }
+  }
+
+
+  /**
+   * Register a handler function for template objects
+   *
+   * @param string Object name
+   * @param string Plugin name that registers this action
+   * @param mixed Callback: string with global function name or array($obj, 'methodname')
+   */
+  public function register_handler($name, $owner, $callback)
+  {
+    // check name
+    if (strpos($name, 'plugin.') !== 0)
+      $name = 'plugin.'.$name;
+    
+    // can register handler only if it's not taken or registered by myself
+    if (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner) {
+      $this->output->add_handler($name, $callback);
+      $this->objectsmap[$name] = $owner;
+    }
+    else {
+      raise_error(array('code' => 525, 'type' => 'php', 'message' => "Cannot register template handler $name; already taken by another plugin"), true, false);
+    }
+  }
+  
+  /**
+   * Include a plugin script file in the current HTML page
+   */
+  public function include_script($fn)
+  {
+    if ($this->output->type == 'html') {
+      $src = $this->ressource_url($fn);
+      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
+    }
+  }
+
+  /**
+   * Include a plugin stylesheet in the current HTML page
+   */
+  public function include_stylesheet($fn)
+  {
+    if ($this->output->type == 'html') {
+      $src = $this->ressource_url($fn);
+      $this->output->add_header(html::tag('link', array('rel' => "stylesheet", 'type' => "text/css", 'href' => $src)));
+    }
+  }
+  
+  /**
+   * Save the given HTML content to be added to a template container
+   */
+  public function add_content($html, $container)
+  {
+    $this->template_contents[$container] .= $html . "\n";
+  }
+  
+  /**
+   * Callback for template_container hooks
+   */
+  private function template_container_hook($attrib)
+  {
+    $container = $attrib['name'];
+    return array('content' => $this->template_contents[$container]);
+  }
+  
+  /**
+   * Make the given file name link into the plugins directory
+   */
+  private function ressource_url($fn)
+  {
+    if ($fn[0] != '/' && !eregi('^https?://', $fn))
+      return $this->url . $fn;
+    else
+      return $fn;
+  }
+
+}
+
diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php
index 1e732ca..557509a 100755
--- a/program/include/rcube_template.php
+++ b/program/include/rcube_template.php
@@ -66,11 +66,12 @@
         $javascript = 'var '.JS_OBJECT_NAME.' = new rcube_webmail();';
 
         // don't wait for page onload. Call init at the bottom of the page (delayed)
-        $javascript_foot = "if (window.call_init)\n call_init('".JS_OBJECT_NAME."');";
+        $javascript_foot = '$(document).ready(function(){ '.JS_OBJECT_NAME.'.init(); });';
 
         $this->add_script($javascript, 'head_top');
         $this->add_script($javascript_foot, 'foot');
         $this->scripts_path = 'program/js/';
+        $this->include_script('http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js');
         $this->include_script('common.js');
         $this->include_script('app.js');
 
@@ -208,8 +209,11 @@
      */
     public function add_label()
     {
-        $arg_list = func_get_args();
-        foreach ($arg_list as $i => $name) {
+        $args = func_get_args();
+        if (count($args) == 1 && is_array($args[0]))
+          $args = $args[0];
+        
+        foreach ($args as $name) {
             $this->command('add_label', $name, rcube_label($name));
         }
     }
@@ -375,9 +379,9 @@
             $parent = $this->framed || preg_match('/^parent\./', $method);
             $out .= sprintf(
                 "%s.%s(%s);\n",
-            ($parent ? 'parent.' : '') . JS_OBJECT_NAME,
-            preg_replace('/^parent\./', '', $method),
-            implode(',', $args)
+                ($parent ? 'if(window.parent && parent.'.JS_OBJECT_NAME.') parent.' : '') . JS_OBJECT_NAME,
+                preg_replace('/^parent\./', '', $method),
+                implode(',', $args)
             );
         }
         
@@ -511,37 +515,21 @@
      */
     private function parse_xml($input)
     {
-        return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command_callback'), $input);
+        return preg_replace_callback('/<roundcube:([-_a-z]+)\s+([^>]+)>/Ui', array($this, 'xml_command'), $input);
     }
 
 
     /**
-     * This is a callback function for preg_replace_callback (see #1485286)
-     * It's only purpose is to reconfigure parameters for xml_command, so that the signature isn't disturbed
-     */
-    private function xml_command_callback($matches)
-    {
-        $str_attrib = isset($matches[2]) ? $matches[2] : '';
-        $add_attrib = isset($matches[3]) ? $matches[3] : array();
-
-        $command = $matches[1];
-        //matches[0] is the entire matched portion of the string
-
-        return $this->xml_command($command, $str_attrib, $add_attrib);
-    }
-
-
-    /**
-     * Convert a xml command tag into real content
+     * Callback function for parsing an xml command tag
+     * and turn it into real html content
      *
-     * @param  string Tag command: object,button,label, etc.
-     * @param  string Attribute string
+     * @param  array Matches array of preg_replace_callback
      * @return string Tag/Object content
      */
-    private function xml_command($command, $str_attrib, $add_attrib = array())
+    private function xml_command($matches)
     {
-        $command = strtolower($command);
-        $attrib  = parse_attrib_string($str_attrib) + $add_attrib;
+        $command = strtolower($matches[1]);
+        $attrib  = parse_attrib_string($matches[2]);
 
         // empty output if required condition is not met
         if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
@@ -572,67 +560,70 @@
                         $incl = $this->include_php($path);
                     }
                     else {
-		        $incl = file_get_contents($path);
-		    }
+                      $incl = file_get_contents($path);
+                    }
                     return $this->parse_xml($incl);
                 }
                 break;
 
             case 'plugin.include':
-                //rcube::tfk_debug(var_export($this->config['skin_path'], true));
-                $path = realpath($this->config['skin_path'].$attrib['file']);
-                if (!$path) {
-                    //rcube::tfk_debug("Does not exist:");
-                    //rcube::tfk_debug($this->config['skin_path']);
-                    //rcube::tfk_debug($attrib['file']);
-                    //rcube::tfk_debug($path);
-                }
-                $incl = file_get_contents($path);
-                if ($incl) {
-                    return $this->parse_xml($incl);
+                $hook = $this->app->plugins->exec_hook("template_plugin_include", $attrib);
+                return $hook['content'];
+                break;
+            
+            // define a container block
+            case 'container':
+                if ($attrib['name'] && $attrib['id']) {
+                    $this->command('gui_container', $attrib['name'], $attrib['id']);
+                    // let plugins insert some content here
+                    $hook = $this->app->plugins->exec_hook("template_container", $attrib);
+                    return $hook['content'];
                 }
                 break;
 
             // return code for a specific application object
             case 'object':
                 $object = strtolower($attrib['name']);
+                $content = '';
 
                 // we are calling a class/method
                 if (($handler = $this->object_handlers[$object]) && is_array($handler)) {
                     if ((is_object($handler[0]) && method_exists($handler[0], $handler[1])) ||
                     (is_string($handler[0]) && class_exists($handler[0])))
-                    return call_user_func($handler, $attrib);
+                    $content = call_user_func($handler, $attrib);
                 }
+                // execute object handler function
                 else if (function_exists($handler)) {
-                    // execute object handler function
-                    return call_user_func($handler, $attrib);
+                    $content = call_user_func($handler, $attrib);
                 }
-
-                if ($object=='productname') {
+                else if ($object == 'productname') {
                     $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'RoundCube Webmail';
-                    return Q($name);
+                    $content = Q($name);
                 }
-                if ($object=='version') {
+                else if ($object == 'version') {
                     $ver = (string)RCMAIL_VERSION;
                     if (is_file(INSTALL_PATH . '.svn/entries')) {
                         if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
                           $ver .= ' [SVN r'.$regs[1].']';
                     }
-                    return $ver;
+                    $content = Q($ver);
                 }
-                if ($object=='steptitle') {
-                  return Q($this->get_pagetitle());
+                else if ($object == 'steptitle') {
+                  $content = Q($this->get_pagetitle());
                 }
-                if ($object=='pagetitle') {
+                else if ($object == 'pagetitle') {
                     $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : '';
                     $title .= $this->get_pagetitle();
-                    return Q($title);
+                    $content = Q($title);
                 }
-                break;
+                
+                // exec plugin hooks for this template object
+                $hook = $this->app->plugins->exec_hook("template_object_$object", $attrib + array('content' => $content));
+                return $hook['content'];
 
             // return code for a specified eval expression
             case 'exp':
-        	$value = $this->parse_expression($attrib['expression']);
+                $value = $this->parse_expression($attrib['expression']);
                 return eval("return Q($value);");
             
             // return variable
@@ -702,7 +693,7 @@
         static $s_button_count = 100;
 
         // these commands can be called directly via url
-        $a_static_commands = array('compose', 'list');
+        $a_static_commands = array('compose', 'list', 'preferences', 'folders', 'identities');
 
         if (!($attrib['command'] || $attrib['name'])) {
             return '';
@@ -941,11 +932,17 @@
         $default_host = $this->config['default_host'];
 
         $_SESSION['temp'] = true;
+        
+        // save original url
+        $url = get_input_value('_url', RCUBE_INPUT_POST);
+        if (empty($url) && !preg_match('/_action=logout/', $_SERVER['QUERY_STRING']))
+            $url = $_SERVER['QUERY_STRING'];
 
         $input_user   = new html_inputfield(array('name' => '_user', 'id' => 'rcmloginuser', 'size' => 30) + $attrib);
         $input_pass   = new html_passwordfield(array('name' => '_pass', 'id' => 'rcmloginpwd', 'size' => 30) + $attrib);
         $input_action = new html_hiddenfield(array('name' => '_action', 'value' => 'login'));
         $input_tzone  = new html_hiddenfield(array('name' => '_timezone', 'id' => 'rcmlogintz', 'value' => '_default_'));
+        $input_url    = new html_hiddenfield(array('name' => '_url', 'id' => 'rcmloginurl', 'value' => $url));
         $input_host   = null;
 
         if (is_array($default_host)) {
@@ -985,6 +982,7 @@
 
         $out = $input_action->show();
         $out .= $input_tzone->show();
+        $out .= $input_url->show();
         $out .= $table->show();
 
         // surround html output with a form tag
diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php
index 17debeb..b68c56c 100644
--- a/program/include/rcube_user.php
+++ b/program/include/rcube_user.php
@@ -346,16 +346,22 @@
    */
   static function create($user, $host)
   {
+    $user_name  = '';
     $user_email = '';
     $rcmail = rcmail::get_instance();
+
+    $data = $rcmail->plugins->exec_hook('create_user', array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email));
+    $user_name = $data['user_name'];
+    $user_email = $data['user_email'];
+
     $dbh = $rcmail->get_dbh();
 
     // try to resolve user in virtuser table and file
-    if (!strpos($user, '@')) {
+    if ($user_email != '' && !strpos($user, '@')) {
       if ($email_list = self::user2email($user, false))
         $user_email = $email_list[0];
     }
-    
+
     $dbh->query(
       "INSERT INTO ".get_table_name('users')."
         (created, last_login, username, mail_host, alias, language)
@@ -372,7 +378,9 @@
       if ($user_email=='')
         $user_email = strpos($user, '@') ? $user : sprintf('%s@%s', $user, $mail_domain);
 
-      $user_name = $user != $user_email ? $user : '';
+      if ($user_name == '') {
+        $user_name = $user != $user_email ? $user : '';
+      }
 
       if (empty($email_list))
         $email_list[] = strip_newlines($user_email); 
@@ -385,10 +393,10 @@
               (user_id, del, standard, name, email)
              VALUES (?, 0, ?, ?, ?)",
             $user_id,
-	    $standard,
+            $standard,
             strip_newlines($user_name),
             preg_replace('/^@/', $user . '@', $email));
-	$standard = 0;
+        $standard = 0;
       }
     }
     else
@@ -446,9 +454,9 @@
       while ($sql_arr = $dbh->fetch_array($sql_result))
         if (strpos($sql_arr[0], '@')) {
           $result[] = $sql_arr[0];
-	  if ($first)
-	    return $result[0];
-	}
+          if ($first)
+            return $result[0];
+        }
     }
     // File lookup
     $r = self::findinvirtual('[[:space:]]' . quotemeta($user) . '[[:space:]]*$');
@@ -460,7 +468,7 @@
       {
         $result[] = trim(str_replace('\\@', '@', $arr[0]));
 
-	if ($first)
+        if ($first)
           return $result[0];
       }
     }
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index ce5087a..444900c 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -263,9 +263,9 @@
           $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
       }
 
-      if (!preg_match('/^(BEGIN|END)$/', $line[1]) && preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
+      if (!preg_match('/^(BEGIN|END)$/i', $line[1]) && preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
         $entry = array('');
-        $field = $regs2[1][0];
+        $field = strtoupper($regs2[1][0]);
 
         foreach($regs2[1] as $attrid => $attr) {
           if ((list($key, $value) = explode('=', $attr)) && $value) {
diff --git a/program/js/app.js b/program/js/app.js
index 205bb2d..0e0c8bf 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -10,27 +10,26 @@
  | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
  |          Charles McNulty <charles@charlesmcnulty.com>                 |
  +-----------------------------------------------------------------------+
- | Requires: common.js, list.js                                          |
+ | Requires: jquery.js, common.js, list.js                               |
  +-----------------------------------------------------------------------+
 
   $Id$
 */
 
 
-var rcube_webmail_client;
-
 function rcube_webmail()
-  {
+{
   this.env = new Object();
   this.labels = new Object();
   this.buttons = new Object();
   this.gui_objects = new Object();
+  this.gui_containers = new Object();
   this.commands = new Object();
+  this.command_handlers = new Object();
   this.onloads = new Array();
 
   // create protected reference to myself
-  rcube_webmail_client = this;
-  this.ref = 'rcube_webmail_client';
+  this.ref = 'rcmail';
   var ref = this;
  
   // webmail client settings
@@ -52,6 +51,12 @@
   this.env.comm_path = './';
   this.env.bin_path = './bin/';
   this.env.blankpage = 'program/blank.gif';
+
+  // set jQuery ajax options
+  jQuery.ajaxSetup({ cache:false,
+    error:function(request, status, err){ ref.http_error(request, status, err); },
+    beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-RoundCube-Referer', bw.get_cookie('roundcube_sessid')); }
+  });
 
   // set environment variable(s)
   this.set_env = function(p, value)
@@ -89,11 +94,33 @@
     this.gui_objects[name] = id;
     };
   
+  // register a container object
+  this.gui_container = function(name, id)
+  {
+    this.gui_containers[name] = id;
+  };
+  
+  // add a GUI element (html node) to a specified container
+  this.add_element = function(elm, container)
+  {
+    if (this.gui_containers[container] && this.gui_containers[container].jquery)
+      this.gui_containers[container].append(elm);
+  };
+
+  // register an external handler for a certain command
+  this.register_command = function(command, callback, enable)
+  {
+    this.command_handlers[command] = callback;
+    
+    if (enable)
+      this.enable_command(command, true);
+  };
+  
   // execute the given script on load
   this.add_onload = function(f)
-    {
-      this.onloads[this.onloads.length] = f;
-    };
+  {
+    this.onloads[this.onloads.length] = f;
+  };
 
   // initialize webmail client
   this.init = function()
@@ -108,6 +135,10 @@
       return;
       }
     
+    // find all registered gui containers
+    for (var n in this.gui_containers)
+      this.gui_containers[n] = $('#'+this.gui_containers[n]);
+
     // find all registered gui objects
     for (var n in this.gui_objects)
       this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
@@ -135,15 +166,13 @@
           this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
           this.message_list.addEventListener('dragmove', function(o, e){ p.drag_move(e); });
           this.message_list.addEventListener('dragend', function(o){ p.drag_active = false; });
+          document.onmouseup = function(e){ return p.doc_mouse_up(e); };
 
           this.message_list.init();
           this.enable_command('toggle_status', 'toggle_flag', true);
           
           if (this.gui_objects.mailcontframe)
-            {
             this.gui_objects.mailcontframe.onmousedown = function(e){ return p.click_on_list(e); };
-            document.onmouseup = function(e){ return p.doc_mouse_up(e); };
-            }
           else
             this.message_list.focus();
           }
@@ -196,7 +225,7 @@
             {
             this.env.spellcheck.spelling_state_observer = function(s){ ref.set_spellcheck_state(s); };
             this.set_spellcheck_state('ready');
-            if (rcube_find_object('_is_html').value == '1')
+            if ($("input[name='_is_html']").val() == '1')
               this.display_spellcheck_controls(false);
             }
           if (this.env.drafts_mailbox)
@@ -263,6 +292,8 @@
             }
           else
             this.contact_list.focus();
+            
+          this.gui_objects.folderlist = this.gui_objects.contactslist;
           }
 
         this.set_page_buttons();
@@ -316,20 +347,16 @@
         break;
 
       case 'login':
-        var input_user = rcube_find_object('rcmloginuser');
-        var input_pass = rcube_find_object('rcmloginpwd');
-        var input_tz = rcube_find_object('rcmlogintz');
-
-        if (input_user)
-          input_user.onkeyup = function(e){ return rcmail.login_user_keyup(e); };
-        if (input_user && input_user.value=='')
+        var input_user = $('#rcmloginuser');
+        input_user.bind('keypress', function(e){ return rcmail.login_user_keyup(e); });
+        
+        if (input_user.val() == '')
           input_user.focus();
-        else if (input_pass)
-          input_pass.focus();
+        else
+          $('#rcmloginpwd').focus();
 
         // detect client timezone
-        if (input_tz)
-          input_tz.value = new Date().getTimezoneOffset() / -60;
+        $('#rcmlogintz').val(new Date().getTimezoneOffset() / -60);
 
         this.enable_command('login', true);
         break;
@@ -347,11 +374,16 @@
     // show message
     if (this.pending_message)
       this.display_message(this.pending_message[0], this.pending_message[1]);
+      
+    // map implicit containers
+    if (this.gui_objects.folderlist)
+      this.gui_containers.foldertray = $(this.gui_objects.folderlist);
 
-    // start keep-alive interval
-    this.start_keepalive();
+    // trigger init event hook
+    this.triggerEvent('init', { task:this.task, action:this.env.action });
     
     // execute all foreign onload scripts
+    // @deprecated
     for (var i=0; i<this.onloads.length; i++)
       {
       if (typeof(this.onloads[i]) == 'string')
@@ -359,7 +391,10 @@
       else if (typeof(this.onloads[i]) == 'function')
         this.onloads[i]();
       }
-    };
+
+    // start keep-alive interval
+    this.start_keepalive();
+  };
 
   // start interval for keep-alive/recent_check signal
   this.start_keepalive = function()
@@ -417,34 +452,25 @@
       return false;
     
     //this.messageform = this.gui_objects.messageform;
-    var input_from = rcube_find_object('_from');
-    var input_to = rcube_find_object('_to');
-    var input_cc = rcube_find_object('_cc');
-    var input_bcc = rcube_find_object('_bcc');
-    var input_replyto = rcube_find_object('_replyto');
-    var input_subject = rcube_find_object('_subject');
-    var input_message = rcube_find_object('_message');
-    var draftid = rcube_find_object('_draft_saveid');
+    var input_from = $("[name='_from']");
+    var input_to = $("[name='_to']");
+    var input_subject = $("input[name='_subject']");
+    var input_message = $("[name='_message']").get(0);
 
     // init live search events
-    if (input_to)
-      this.init_address_input_events(input_to);
-    if (input_cc)
-      this.init_address_input_events(input_cc);
-    if (input_bcc)
-      this.init_address_input_events(input_bcc);
+    this.init_address_input_events(input_to);
+    this.init_address_input_events($("[name='_cc']"));
+    this.init_address_input_events($("[name='_bcc']"));
 
     // add signature according to selected identity
-    if (input_from && input_from.type=='select-one' && (!draftid || draftid.value=='')
-	// if we have HTML editor, signature is added in callback
-	&& rcube_find_object('_is_html').value != '1')
-      {
-      this.change_identity(input_from);
-      }
+    if (input_from.attr('type') == 'select-one' && $("input[name='_draft_saveid']").val() == ''
+        && $("input[name='_is_html']").val() != '1') {  // if we have HTML editor, signature is added in callback
+      this.change_identity(input_from[0]);
+    }
 
-    if (input_to && input_to.value=='')
+    if (input_to.val() == '')
       input_to.focus();
-    else if (input_subject && input_subject.value=='')
+    else if (input_subject.val() == '')
       input_subject.focus();
     else if (input_message)
       this.set_caret2start(input_message);
@@ -459,13 +485,8 @@
   this.init_address_input_events = function(obj)
     {
     var handler = function(e){ return ref.ksearch_keypress(e,this); };
-    
-    if (obj.addEventListener)
-      obj.addEventListener(bw.safari ? 'keydown' : 'keypress', handler, false);
-    else
-      obj.onkeydown = handler;
-
-    obj.setAttribute('autocomplete', 'off');
+    obj.bind((bw.safari ? 'keydown' : 'keypress'), handler);
+    obj.attr('autocomplete', 'off');
     };
 
 
@@ -499,7 +520,29 @@
         return false;
      }
 
-    // process command
+    // process external commands
+    if (typeof this.command_handlers[command] == 'function')
+    {
+      var ret = this.command_handlers[command](props, obj);
+      return ret !== null ? ret : (obj ? false : true);
+    }
+    else if (typeof this.command_handlers[command] == 'string')
+    {
+      var ret = window[this.command_handlers[command]](props, obj);
+      return ret !== null ? ret : (obj ? false : true);
+    }
+    
+    // trigger plugin hook
+    var event_ret = this.triggerEvent('before'+command, props);
+    if (typeof event_ret != 'undefined') {
+      // abort if one the handlers returned false
+      if (event_ret === false)
+        return false;
+      else
+        props = event_ret;
+    }
+
+    // process internal command
     switch (command)
       {
       case 'login':
@@ -509,7 +552,7 @@
 
       case 'logout':
         this.goto_url('logout', '', true);
-        break;      
+        break;
 
       // commands to switch task
       case 'mail':
@@ -558,7 +601,6 @@
         var a_sort = props.split('_');
         var sort_col = a_sort[0];
         var sort_order = a_sort[1] ? a_sort[1].toUpperCase() : null;
-        var header;
 
         // no sort order specified: toggle
         if (sort_order==null)
@@ -573,10 +615,8 @@
           break;
 
         // set table header class
-        if (header = document.getElementById('rcm'+this.env.sort_col))
-          this.set_classname(header, 'sorted'+(this.env.sort_order.toUpperCase()), false);
-        if (header = document.getElementById('rcm'+sort_col))
-          this.set_classname(header, 'sorted'+sort_order, true);
+        $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
+        $('#rcm'+sort_col).addClass('sorted'+sort_order);
 
         // save new sort properties
         this.env.sort_col = sort_col;
@@ -657,12 +697,12 @@
       case 'save':
         if (this.gui_objects.editform)
           {
-          var input_pagesize = rcube_find_object('_pagesize');
-          var input_name  = rcube_find_object('_name');
-          var input_email = rcube_find_object('_email');
+          var input_pagesize = $("input[name='_pagesize']");
+          var input_name  = $("input[name='_name']");
+          var input_email = $("input[name='_email']");
 
           // user prefs
-          if (input_pagesize && isNaN(parseInt(input_pagesize.value)))
+          if (input_pagesize.length && isNaN(parseInt(input_pagesize.val())))
             {
             alert(this.get_label('nopagesizewarning'));
             input_pagesize.focus();
@@ -671,13 +711,13 @@
           // contacts/identities
           else
             {
-            if (input_name && input_name.value == '')
+            if (input_name.length && input_name.val() == '')
               {
               alert(this.get_label('nonamewarning'));
               input_name.focus();
               break;
               }
-            else if (input_email && !rcube_check_email(input_email.value))
+            else if (input_email.length && !rcube_check_email(input_email.val()))
               {
               alert(this.get_label('noemailwarning'));
               input_email.focus();
@@ -1062,6 +1102,8 @@
         break;
 
       }
+      
+    this.triggerEvent('after'+command, props);
 
     return obj ? false : true;
     };
@@ -1114,13 +1156,18 @@
     };
 
   // return a localized string
-  this.get_label = function(name)
+  this.get_label = function(name, domain)
     {
-    if (this.labels[name])
+    if (domain && this.labels[domain+'.'+name])
+      return this.labels[domain+'.'+name];
+    else if (this.labels[name])
       return this.labels[name];
     else
       return name;
     };
+  
+  // alias for convenience reasons
+  this.gettext = this.get_label;
 
   // switch to another application task
   this.switch_task = function(task)
@@ -1157,16 +1204,18 @@
 
   this.doc_mouse_up = function(e)
   {
-    var model, li;
+    var model, list, li;
 
     if (this.message_list) {
       if (!rcube_mouse_is_over(e, this.message_list.list))
         this.message_list.blur();
+      list = this.message_list;
       model = this.env.mailboxes;
     }
     else if (this.contact_list) {
       if (!rcube_mouse_is_over(e, this.contact_list.list))
         this.contact_list.blur();
+      list = this.contact_list;
       model = this.env.address_sources;
     }
     else if (this.ksearch_value) {
@@ -1175,9 +1224,10 @@
 
     // handle mouse release when dragging
     if (this.drag_active && model && this.env.last_folder_target) {
-      this.set_classname(this.get_folder_li(this.env.last_folder_target), 'droptarget', false);
+      $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
       this.command('moveto', model[this.env.last_folder_target].id);
       this.env.last_folder_target = null;
+      list.draglayer.hide();
     }
   };
 
@@ -1196,27 +1246,25 @@
     if (this.gui_objects.folderlist && model)
       {
       var li, pos, list, height;
-      list = rcube_find_object(this.task == 'mail' ? 'mailboxlist' : 'directorylist');
-      pos = rcube_get_object_pos(list);
-      this.env.folderlist_coords = {x1:pos.x, y1:pos.y, x2:pos.x + list.offsetWidth, y2:pos.y + list.offsetHeight};
+      list = $(this.gui_objects.folderlist);
+      pos = list.offset();
+      this.env.folderlist_coords = { x1:pos.left, y1:pos.top, x2:pos.left + list.width(), y2:pos.top + list.height() };
 
       this.env.folder_coords = new Array();
       for (var k in model) {
-        if (li = this.get_folder_li(k))
-	  {
-	  pos = rcube_get_object_pos(li.firstChild);
-	  // only visible folders
-	  if (height = li.firstChild.offsetHeight)
-	    this.env.folder_coords[k] = {x1:pos.x, y1:pos.y, x2:pos.x + li.firstChild.offsetWidth, y2:pos.y + height};
-          }
+        if (li = this.get_folder_li(k)) {
+          pos = $(li.firstChild).offset();
+          // only visible folders
+          if (height = li.firstChild.offsetHeight)
+            this.env.folder_coords[k] = { x1:pos.left, y1:pos.top, x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height };
         }
       }
+    }
   };
 
   this.drag_move = function(e)
-    {
-    if (this.gui_objects.folderlist && this.env.folder_coords)
-      {
+  {
+    if (this.gui_objects.folderlist && this.env.folder_coords) {
       // offsets to compensate for scrolling while dragging a message
       var boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop;
       var moffset = this.initialMailBoxScrollTop-document.getElementById('mailboxlist-container').scrollTop;
@@ -1229,53 +1277,50 @@
       mouse.y += toffset;
 
       // if mouse pointer is outside of folderlist
-      if (mouse.x < pos.x1 || mouse.x >= pos.x2 
-	    || mouse.y < pos.y1 || mouse.y >= pos.y2)
-	{
-	if (this.env.last_folder_target) {
-	  this.set_classname(this.get_folder_li(this.env.last_folder_target), 'droptarget', false);
+      if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) {
+        if (this.env.last_folder_target) {
+          $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
           this.env.last_folder_target = null;
-	  }
-	return;
         }
+        return;
+      }
 
       // over the folders
-      for (var k in this.env.folder_coords)
-        {
-	pos = this.env.folder_coords[k];
-	if (this.check_droptarget(k) && ((mouse.x >= pos.x1) && (mouse.x < pos.x2) 
-	    && (mouse.y >= pos.y1) && (mouse.y < pos.y2)))
-	  {
-          this.set_classname(this.get_folder_li(k), 'droptarget', true);
-	  this.env.last_folder_target = k;
-	  }
-	else
-	  this.set_classname(this.get_folder_li(k), 'droptarget', false);
+      for (var k in this.env.folder_coords) {
+        pos = this.env.folder_coords[k];
+        if (this.check_droptarget(k) && ((mouse.x >= pos.x1) && (mouse.x < pos.x2) 
+            && (mouse.y >= pos.y1) && (mouse.y < pos.y2))) {
+          $(this.get_folder_li(k)).addClass('droptarget');
+          this.env.last_folder_target = k;
+        }
+        else {
+          $(this.get_folder_li(k)).removeClass('droptarget');
+          if (k == this.env.last_folder_target)
+            this.env.last_folder_target = null;
         }
       }
-    };
+    }
+  };
   
   this.collapse_folder = function(id)
     {
     var div;
     if ((li = this.get_folder_li(id)) &&
-        (div = li.getElementsByTagName("div")[0]) &&
-        (div.className.match(/collapsed/) || div.className.match(/expanded/)))
+        (div = $(li.getElementsByTagName("div")[0])) &&
+        (div.hasClass('collapsed') || div.hasClass('expanded')))
       {
-      var ul = li.getElementsByTagName("ul")[0];
-      if (div.className.match(/collapsed/))
+      var ul = $(li.getElementsByTagName("ul")[0]);
+      if (div.hasClass('collapsed'))
         {
-        ul.style.display = '';
-        this.set_classname(div, 'collapsed', false);
-        this.set_classname(div, 'expanded', true);
+        ul.show();
+        div.removeClass('collapsed').addClass('expanded');
         var reg = new RegExp('&'+urlencode(id)+'&');
         this.set_env('collapsed_folders', this.env.collapsed_folders.replace(reg, ''));
         }
       else
         {
-        ul.style.display = 'none';
-        this.set_classname(div, 'expanded', false);
-        this.set_classname(div, 'collapsed', true);
+        ul.hide();
+        div.removeClass('expanded').addClass('collapsed');
         this.set_env('collapsed_folders', this.env.collapsed_folders+'&'+urlencode(id)+'&');
 
         // select parent folder if one of its childs is currently selected
@@ -1308,10 +1353,6 @@
       this.message_list.focus();
     else if (this.contact_list)
       this.contact_list.focus();
-
-    var mbox_li;
-    if (mbox_li = this.get_folder_li())
-      this.set_classname(mbox_li, 'unfocused', true);
 
     return rcube_event.get_button(e) == 2 ? true : rcube_event.cancel(e);
     };
@@ -1412,6 +1453,7 @@
     // also send search request to get the right messages
     if (this.env.search_request)
       add_url += '&_search='+this.env.search_request;
+
     var url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox)+add_url;
     if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
       this.show_contentframe(true);
@@ -1424,19 +1466,19 @@
       if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread)
         {
         this.set_message(id, 'unread', false);
-	if (this.env.unread_counts[this.env.mailbox])
-	  {
-	  this.env.unread_counts[this.env.mailbox] -= 1;
-	  this.set_unread_count(this.env.mailbox, this.env.unread_counts[this.env.mailbox], this.env.mailbox == 'INBOX');
-	  }
-	}
+        if (this.env.unread_counts[this.env.mailbox])
+          {
+          this.env.unread_counts[this.env.mailbox] -= 1;
+          this.set_unread_count(this.env.mailbox, this.env.unread_counts[this.env.mailbox], this.env.mailbox == 'INBOX');
+          }
+        }
       }
     };
 
   this.show_contentframe = function(show)
     {
     var frm;
-    if (this.env.contentframe && (frm = rcube_find_object(this.env.contentframe)))
+    if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length)
       {
       if (!show && window.frames[this.env.contentframe])
         {
@@ -1444,7 +1486,7 @@
           window.frames[this.env.contentframe].location.href = this.env.blankpage;
         }
       else if (!bw.safari && !bw.konq)
-        frm.style.display = show ? 'block' : 'none';
+        frm[show ? 'show' : 'hide']();
       }
 
     if (!show && this.busy)
@@ -1677,37 +1719,38 @@
     if (flag)
       this.set_message_status(uid, flag, status);
     
+    var rowobj = $(rows[uid].obj);
     if (rows[uid].unread && rows[uid].classname.indexOf('unread')<0)
       {
       rows[uid].classname += ' unread';
-      this.set_classname(rows[uid].obj, 'unread', true);
+      rowobj.addClass('unread');
       }
     else if (!rows[uid].unread && rows[uid].classname.indexOf('unread')>=0)
       {
       rows[uid].classname = rows[uid].classname.replace(/\s*unread/, '');
-      this.set_classname(rows[uid].obj, 'unread', false);
+      rowobj.removeClass('unread');
       }
     
     if (rows[uid].deleted && rows[uid].classname.indexOf('deleted')<0)
       {
       rows[uid].classname += ' deleted';
-      this.set_classname(rows[uid].obj, 'deleted', true);
+      rowobj.addClass('deleted');
       }
     else if (!rows[uid].deleted && rows[uid].classname.indexOf('deleted')>=0)
       {
       rows[uid].classname = rows[uid].classname.replace(/\s*deleted/, '');
-      this.set_classname(rows[uid].obj, 'deleted', false);
+      rowobj.removeClass('deleted');
       }
 
     if (rows[uid].flagged && rows[uid].classname.indexOf('flagged')<0)
       {
       rows[uid].classname += ' flagged';
-      this.set_classname(rows[uid].obj, 'flagged', true);
+      rowobj.addClass('flagged');
       }
     else if (!rows[uid].flagged && rows[uid].classname.indexOf('flagged')>=0)
       {
       rows[uid].classname = rows[uid].classname.replace(/\s*flagged/, '');
-      this.set_classname(rows[uid].obj, 'flagged', false);
+      rowobj.removeClass('flagged');
       }
 
     this.set_message_icon(uid);
@@ -1811,8 +1854,8 @@
         {
           this.set_message_status(id, 'deleted', true);
           if (this.env.read_when_deleted)
-    	    this.set_message_status(id, 'unread', false);
-	  this.set_message(id);
+            this.set_message_status(id, 'unread', false);
+          this.set_message(id);
         }
       }
     }
@@ -1999,14 +2042,15 @@
   this.login_user_keyup = function(e)
   {
     var key = rcube_event.get_keycode(e);
-    var elm;
+    var passwd = $('#rcmloginpwd');
 
     // enter
-    if ((key==13) && (elm = rcube_find_object('_pass')))
-    {
-      elm.focus();
-      return false;
+    if (key == 13 && passwd.length && !passwd.val()) {
+      passwd.focus();
+      return rcube_event.cancel(e);
     }
+    
+    return true;
   };
 
 
@@ -2018,15 +2062,15 @@
   this.check_compose_input = function()
     {
     // check input fields
-    var input_to = rcube_find_object('_to');
-    var input_cc = rcube_find_object('_cc');
-    var input_bcc = rcube_find_object('_bcc');
-    var input_from = rcube_find_object('_from');
-    var input_subject = rcube_find_object('_subject');
-    var input_message = rcube_find_object('_message');
+    var input_to = $("[name='_to']");
+    var input_cc = $("[name='_cc']");
+    var input_bcc = $("[name='_bcc']");
+    var input_from = $("[name='_from']");
+    var input_subject = $("[name='_subject']");
+    var input_message = $("[name='_message']");
 
     // check sender (if have no identities)
-    if (input_from.type == 'text' && !rcube_check_email(input_from.value, true))
+    if (input_from.attr('type') == 'text' && !rcube_check_email(input_from.val(), true))
       {
       alert(this.get_label('nosenderwarning'));
       input_from.focus();
@@ -2034,7 +2078,7 @@
       }
 
     // check for empty recipient
-    var recipients = input_to.value ? input_to.value : (input_cc.value ? input_cc.value : input_bcc.value);
+    var recipients = input_to.val() ? input_to.val() : (input_cc.val() ? input_cc.val() : input_bcc.val());
     if (!rcube_check_email(recipients.replace(/^\s+/, '').replace(/[\s,;]+$/, ''), true))
       {
       alert(this.get_label('norecipientwarning'));
@@ -2043,7 +2087,7 @@
       }
 
     // display localized warning for missing subject
-    if (input_subject && input_subject.value == '')
+    if (input_subject.val() == '')
       {
       var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject'));
 
@@ -2055,12 +2099,12 @@
         }
       else
         {
-        input_subject.value = subject ? subject : this.get_label('nosubject');            
+        input_subject.val((subject ? subject : this.get_label('nosubject')));
         }
       }
 
     // check for empty body
-    if ((!window.tinyMCE || !tinyMCE.get('compose-body')) && input_message.value == '' && !confirm(this.get_label('nobodywarning')))
+    if ((!window.tinyMCE || !tinyMCE.get('compose-body')) && input_message.val() == '' && !confirm(this.get_label('nobodywarning')))
       {
       input_message.focus();
       return false;
@@ -2105,9 +2149,7 @@
 
   this.set_draft_id = function(id)
     {
-    var f;
-    if (f = rcube_find_object('_draft_saveid'))
-      f.value = id;
+    $("input[name='_draft_saveid']").val(id);
     };
 
   this.auto_save_start = function()
@@ -2122,29 +2164,26 @@
   this.compose_field_hash = function(save)
     {
     // check input fields
-    var input_to = rcube_find_object('_to');
-    var input_cc = rcube_find_object('_cc');
-    var input_bcc = rcube_find_object('_bcc');
-    var input_subject = rcube_find_object('_subject');
-    var editor, input_message;
+    var value_to = $("[name='_to']").val();
+    var value_cc = $("[name='_cc']").val();
+    var value_bcc = $("[name='_bcc']").val();
+    var value_subject = $("[name='_subject']").val();
     var str = '';
     
-    if (input_to && input_to.value)
-      str += input_to.value+':';
-    if (input_cc && input_cc.value)
-      str += input_cc.value+':';
-    if (input_bcc && input_bcc.value)
-      str += input_bcc.value+':';
-    if (input_subject && input_subject.value)
-      str += input_subject.value+':';
+    if (value_to)
+      str += value_to+':';
+    if (value_cc)
+      str += value_cc+':';
+    if (value_bcc)
+      str += value_bcc+':';
+    if (value_subject)
+      str += value_subject+':';
     
-    if (editor = tinyMCE.get('compose-body'))
+    var editor = tinyMCE.get('compose-body');
+    if (editor)
       str += editor.getContent();
     else
-      {
-      input_message = rcube_find_object('_message');
-      str += input_message.value;
-      }
+      str += $("[name='_message']").val();
     
     if (save)
       this.cmp_hash = str;
@@ -2158,9 +2197,9 @@
       return false;
 
     var id = obj.options[obj.selectedIndex].value;
-    var input_message = rcube_find_object('_message');
-    var message = input_message ? input_message.value : '';
-    var is_html = (rcube_find_object('_is_html').value == '1');
+    var input_message = $("[name='_message']");
+    var message = input_message.val();
+    var is_html = ($("input[name='_is_html']").val() == '1');
     var sig, p;
 
     if (!this.env.identity)
@@ -2174,9 +2213,9 @@
         if (this.env.signatures[this.env.identity]['is_html'])
           sig = this.env.signatures[this.env.identity]['plain_text'];
         else
-	  sig = this.env.signatures[this.env.identity]['text'];
+          sig = this.env.signatures[this.env.identity]['text'];
         
-	if (sig.indexOf('-- ')!=0)
+        if (sig.indexOf('-- ')!=0)
           sig = '-- \n'+sig;
 
         p = message.lastIndexOf(sig);
@@ -2207,32 +2246,32 @@
         {
         // Append the signature as a div within the body
         var sigElem = editor.dom.get('_rc_sig');
-	var newsig = '';
-	var htmlsig = true;
+        var newsig = '';
+        var htmlsig = true;
 
         if (!sigElem)
           {
-	  // add empty line before signature on IE
-	  if (bw.ie)
+          // add empty line before signature on IE
+          if (bw.ie)
             editor.getBody().appendChild(editor.getDoc().createElement('br'));
 
-	  sigElem = editor.getDoc().createElement('div');
+          sigElem = editor.getDoc().createElement('div');
           sigElem.setAttribute('id', '_rc_sig');
           editor.getBody().appendChild(sigElem);
           }
 
-	if (this.env.signatures[id])
-	  {
-	  newsig = this.env.signatures[id]['text'];
-	  htmlsig = this.env.signatures[id]['is_html'];
-        
-	  if (newsig) {
-	    if (htmlsig && this.env.signatures[id]['plain_text'].indexOf('-- ')!=0)
+        if (this.env.signatures[id])
+        {
+          newsig = this.env.signatures[id]['text'];
+          htmlsig = this.env.signatures[id]['is_html'];
+
+          if (newsig) {
+            if (htmlsig && this.env.signatures[id]['plain_text'].indexOf('-- ')!=0)
               newsig = '<p>-- </p>' + newsig;
-	    else if (!htmlsig && newsig.indexOf('-- ')!=0)
+            else if (!htmlsig && newsig.indexOf('-- ')!=0)
               newsig = '-- \n' + newsig;
-	    }
-	  }
+          }
+        }
 
         if (htmlsig)
           sigElem.innerHTML = newsig;
@@ -2241,8 +2280,7 @@
         }
       }
 
-    if (input_message)
-      input_message.value = message;
+    input_message.val(message);
 
     this.env.identity = id;
     return true;
@@ -2256,27 +2294,24 @@
     var elm, list;
     if (elm = this.gui_objects.uploadbox)
       {
-      if (a &&  (list = this.gui_objects.attachmentlist))
+      if (a && (list = this.gui_objects.attachmentlist))
         {
-        var pos = rcube_get_object_pos(list);
-        var left = pos.x;
-        var top = pos.y + list.offsetHeight + 10;
-      
-        elm.style.top = top+'px';
-        elm.style.left = left+'px';
+        var pos = $(list).offset();
+        elm.style.top = (pos.top + list.offsetHeight + 10) + 'px';
+        elm.style.left = pos.left + 'px';
         }
       
       elm.style.visibility = a ? 'visible' : 'hidden';
       }
       
     // clear upload form
-	try {
+    try {
       if (!a && this.gui_objects.attachmentform != this.gui_objects.messageform)
-      	this.gui_objects.attachmentform.reset();
-	}
-	catch(e){}  // ignore errors
+        this.gui_objects.attachmentform.reset();
+    }
+    catch(e){}  // ignore errors
     
-    return true;  
+    return true;
     };
 
   // upload attachment file
@@ -2336,10 +2371,7 @@
     if (!this.gui_objects.attachmentlist)
       return false;
       
-    var li = document.createElement('LI');
-    li.id = name;
-    li.innerHTML = content;
-    this.gui_objects.attachmentlist.appendChild(li);
+    $('<li>').attr('id', name).html(content).appendTo(this.gui_objects.attachmentlist);
     return true;
     };
 
@@ -2439,7 +2471,7 @@
         
         highlight = document.getElementById('rcmksearchSelected');
         if (!highlight)
-          highlight = this.ksearch_pane.ul.firstChild;
+          highlight = this.ksearch_pane.__ul.firstChild;
         
         if (highlight)
           this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
@@ -2479,15 +2511,13 @@
   
   this.ksearch_select = function(node)
   {
-    var current = document.getElementById('rcmksearchSelected');
-    if (current && node) {
-      current.removeAttribute('id');
-      this.set_classname(current, 'selected', false);
+    var current = $('#rcmksearchSelected');
+    if (current[0] && node) {
+      current.removeAttr('id').removeClass('selected');
     }
 
     if (node) {
-      node.setAttribute('id', 'rcmksearchSelected');
-      this.set_classname(node, 'selected', true);
+      $(node).attr('id', 'rcmksearchSelected').addClass('selected');
       this.ksearch_selected = node._rcm_id;
     }
   };
@@ -2521,8 +2551,8 @@
     if (inp_value === null)
       return;
       
-    if (this.ksearch_pane && this.ksearch_pane.visible)
-      this.ksearch_pane.show(0);
+    if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
+      this.ksearch_pane.hide();
 
     // get string from current cursor pos to last comma
     var cpos = this.get_caret_pos(this.ksearch_input);
@@ -2561,15 +2591,13 @@
       
       // create results pane if not present
       if (!this.ksearch_pane) {
-        ul = document.createElement('UL');
-        this.ksearch_pane = new rcube_layer('rcmKSearchpane', {vis:0, zindex:30000});
-        this.ksearch_pane.elm.appendChild(ul);
-        this.ksearch_pane.ul = ul;
+        ul = $('<ul>');
+        this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
+        this.ksearch_pane.__ul = ul[0];
       }
-      else
-        ul = this.ksearch_pane.ul;
 
       // remove all search results
+      ul = this.ksearch_pane.__ul;
       ul.innerHTML = '';
             
       // add each result line to list
@@ -2583,14 +2611,12 @@
       }
 
       // select the first
-      ul.firstChild.setAttribute('id', 'rcmksearchSelected');
-      this.set_classname(ul.firstChild, 'selected', true);
+      $(ul.firstChild).attr('id', 'rcmksearchSelected').addClass('selected');
       this.ksearch_selected = 0;
 
       // move the results pane right under the input box and make it visible
-      var pos = rcube_get_object_pos(this.ksearch_input);
-      this.ksearch_pane.move(pos.x, pos.y+this.ksearch_input.offsetHeight);
-      this.ksearch_pane.show(1);
+      var pos = $(this.ksearch_input).offset();
+      this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px' }).show();
     }
     // hide results pane
     else
@@ -2623,7 +2649,7 @@
     this.ksearch_selected = null;
     
     if (this.ksearch_pane)
-      this.ksearch_pane.show(0);
+      this.ksearch_pane.hide();
     };
 
 
@@ -2788,19 +2814,18 @@
 
   // update a contact record in the list
   this.update_contact_row = function(cid, cols_arr)
-    {
+  {
     var row;
-    if (this.contact_list.rows[cid] && (row = this.contact_list.rows[cid].obj))
-      {
+    if (this.contact_list.rows[cid] && (row = this.contact_list.rows[cid].obj)) {
       for (var c=0; c<cols_arr.length; c++)
         if (row.cells[c])
-          row.cells[c].innerHTML = cols_arr[c];
+          $(row.cells[c]).html(cols_arr[c]);
 
       return true;
-      }
+    }
 
     return false;
-    };
+  };
 
 
   /*********************************************************/
@@ -2882,30 +2907,29 @@
           (folder = this.env.subscriptionrows[id][0]))
         {
         if (this.check_droptarget(folder) &&
-    	    !this.env.subscriptionrows[this.get_folder_row_id(this.env.folder)][2] &&    
-	    (folder != this.env.folder.replace(reg, '')) &&
+            !this.env.subscriptionrows[this.get_folder_row_id(this.env.folder)][2] &&
+            (folder != this.env.folder.replace(reg, '')) &&
             (!folder.match(new RegExp('^'+RegExp.escape(this.env.folder+this.env.delimiter)))))
           {
           this.set_env('dstfolder', folder);
-          this.set_classname(row, 'droptarget', true);
+          $(row).addClass('droptarget');
           }
         }
       else if (this.env.folder.match(new RegExp(RegExp.escape(this.env.delimiter))))
         {
         this.set_env('dstfolder', this.env.delimiter);
-        this.set_classname(this.subscription_list.frame, 'droptarget', true);
+        $(this.subscription_list.frame).addClass('droptarget');
         }
     }
 
   this.unfocus_subscription = function(id)
     {
-      var row;
+      var row = $('#'+id);
       this.set_env('dstfolder', null);
-      if (this.env.subscriptionrows[id] &&
-          (row = document.getElementById(id)))
-        this.set_classname(row, 'droptarget', false);
+      if (this.env.subscriptionrows[id] && row[0])
+        row.removeClass('droptarget');
       else
-        this.set_classname(this.subscription_list.frame, 'droptarget', false);
+        $(this.subscription_list.frame).removeClass('droptarget');
     }
 
   this.subscription_select = function(list)
@@ -2919,7 +2943,7 @@
       this.set_env('folder', null);
       
     if (this.gui_objects.createfolderhint)
-      this.gui_objects.createfolderhint.innerHTML = this.env.folder ? this.get_label('addsubfolderhint') : '';
+      $(this.gui_objects.createfolderhint).html(this.env.folder ? this.get_label('addsubfolderhint') : '');
     };
 
   this.subscription_move_folder = function(list)
@@ -3006,7 +3030,7 @@
     var cell = this.name_input ? this.name_input.parentNode : null;
 
     if (cell && this.edit_folder && this.env.subscriptionrows[this.edit_folder])
-      cell.innerHTML = this.env.subscriptionrows[this.edit_folder][1];
+      $(cell).html(this.env.subscriptionrows[this.edit_folder][1]);
       
     this.edit_folder = null;
     };
@@ -3054,8 +3078,7 @@
       this.http_post('delete-folder', '_mboxes='+urlencode(folder), true);
       this.set_env('folder', null);
 
-      if (this.gui_objects.createfolderhint)
-        this.gui_objects.createfolderhint.innerHTML = '';
+      $(this.gui_objects.createfolderhint).html('');
       }
     };
 
@@ -3378,16 +3401,6 @@
       }
     };
 
-  // set/unset a specific class name
-  this.set_classname = function(obj, classname, set)
-    {
-    var reg = new RegExp('\s*'+classname, 'i');
-    if (!set && obj.className.match(reg))
-      obj.className = obj.className.replace(reg, '');
-    else if (set && !obj.className.match(reg))
-      obj.className += ' '+classname;
-    };
-
   // write to the document/window title
   this.set_pagetitle = function(title)
   {
@@ -3418,25 +3431,20 @@
     if (type)
       cont = '<div class="'+type+'">'+cont+'</div>';
 
-    var _rcube = this;
-    this.gui_objects.message.innerHTML = cont;
-    this.gui_objects.message.style.display = 'block';
+    var obj = $(this.gui_objects.message).html(cont).show();
     
     if (type!='loading')
-      this.gui_objects.message.onmousedown = function(){ _rcube.hide_message(); return true; };
+      obj.bind('mousedown', function(){ ref.hide_message(); return true; });
     
     if (!hold)
-      this.message_timer = window.setTimeout(function(){ ref.hide_message(); }, this.message_time);
+      this.message_timer = window.setTimeout(function(){ ref.hide_message(true); }, this.message_time);
     };
 
   // make a message row disapear
-  this.hide_message = function()
+  this.hide_message = function(fade)
     {
     if (this.gui_objects.message)
-      {
-      this.gui_objects.message.style.display = 'none';
-      this.gui_objects.message.onmousedown = null;
-      }
+      $(this.gui_objects.message).unbind()[(fade?'fadeOut':'hide')]();
     };
 
   // mark a mailbox as selected and set environment variable
@@ -3446,16 +3454,12 @@
     {
       var current_li, target_li;
       
-      if ((current_li = this.get_folder_li(old)))
-      {
-        this.set_classname(current_li, 'selected', false);
-        this.set_classname(current_li, 'unfocused', false);
+      if ((current_li = this.get_folder_li(old))) {
+        $(current_li).removeClass('selected').removeClass('unfocused');
       }
 
-      if ((target_li = this.get_folder_li(name)))
-      {
-        this.set_classname(target_li, 'unfocused', false);
-        this.set_classname(target_li, 'selected', true);
+      if ((target_li = this.get_folder_li(name))) {
+        $(target_li).removeClass('unfocused').addClass('selected');
       }
     }
   };
@@ -3512,22 +3516,24 @@
     var rowcount = tbody.rows.length;
     var even = rowcount%2;
     
-    this.env.messages[uid] = {deleted:flags.deleted?1:0,
-                              replied:flags.replied?1:0,
-                              unread:flags.unread?1:0,
-			      forwarded:flags.forwarded?1:0,
-                              flagged:flags.flagged?1:0};
-    
-    var row = document.createElement('TR');
-    row.id = 'rcmrow'+uid;
-    row.className = 'message'
-	+ (even ? ' even' : ' odd')
+    this.env.messages[uid] = {
+      deleted: flags.deleted?1:0,
+      replied: flags.replied?1:0,
+      unread: flags.unread?1:0,
+      forwarded: flags.forwarded?1:0,
+      flagged:flags.flagged?1:0
+    };
+
+    var css_class = 'message'
+        + (even ? ' even' : ' odd')
         + (flags.unread ? ' unread' : '')
-	+ (flags.deleted ? ' deleted' : '')
-	+ (flags.flagged ? ' flagged' : '');		    
+        + (flags.deleted ? ' deleted' : '')
+        + (flags.flagged ? ' flagged' : '');
+
+    var row = $('<tr>').attr('id', 'rcmrow'+uid).attr('class', css_class);
 
     if (this.message_list.in_selection(uid))
-      row.className += ' selected';
+      row.addClass('selected');
 
     var icon = this.env.messageicon;
     if (flags.deleted && this.env.deletedicon)
@@ -3544,49 +3550,42 @@
     else if(flags.unread && this.env.unreadicon)
       icon = this.env.unreadicon;
     
-    var col = document.createElement('TD');
-    col.className = 'icon';
-    col.innerHTML = icon ? '<img src="'+icon+'" alt="" />' : '';
-    row.appendChild(col);
+    // add icon col
+    $('<td>').addClass('icon').html(icon ? '<img src="'+icon+'" alt="" />' : '').appendTo(row);
 
     // add each submitted col
-    for (var n = 0; n < this.coltypes.length; n++) 
-      { 
+    for (var n = 0; n < this.coltypes.length; n++) {
       var c = this.coltypes[n];
-      col = document.createElement('TD');
-      col.className = String(c).toLowerCase();
+      col = $('<td>').addClass(String(c).toLowerCase());
       
-      if (c=='flag')
-        {
+      if (c=='flag') {
         if (flags.flagged && this.env.flaggedicon)
-          col.innerHTML = '<img src="'+this.env.flaggedicon+'" alt="" />';
+          col.html('<img src="'+this.env.flaggedicon+'" alt="" />');
         else if(!flags.flagged && this.env.unflaggedicon)
-          col.innerHTML = '<img src="'+this.env.unflaggedicon+'" alt="" />';
+          col.html('<img src="'+this.env.unflaggedicon+'" alt="" />');
       }
       else if (c=='attachment')
-        col.innerHTML = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;';
+        col.html(attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" />' : '&nbsp;');
       else
-        col.innerHTML = cols[c];
+        col.html(cols[c]);
 
-      row.appendChild(col);
+      col.appendTo(row);
       }
 
     this.message_list.insert_row(row, attop);
 
     // remove 'old' row
-    if (attop && this.env.pagesize && this.message_list.rowcount > this.env.pagesize)
-      {
-	var uid = this.message_list.get_last_row();
-        this.message_list.remove_row(uid);
-	this.message_list.clear_selection(uid);
-      }
-    };
+    if (attop && this.env.pagesize && this.message_list.rowcount > this.env.pagesize) {
+      var uid = this.message_list.get_last_row();
+      this.message_list.remove_row(uid);
+      this.message_list.clear_selection(uid);
+    }
+  };
 
   // replace content of row count display
   this.set_rowcount = function(text)
     {
-    if (this.gui_objects.countdisplay)
-      this.gui_objects.countdisplay.innerHTML = text;
+    $(this.gui_objects.countdisplay).html(text);
 
     // update page navigation buttons
     this.set_page_buttons();
@@ -3602,8 +3601,8 @@
   // replace content of quota display
   this.set_quota = function(content)
     {
-    if (this.gui_objects.quotadisplay && content)
-      this.gui_objects.quotadisplay.innerHTML = content;
+    if (content && this.gui_objects.quotadisplay)
+      $(this.gui_objects.quotadisplay).html(content);
     };
 
   // update the mailboxlist
@@ -3632,9 +3631,8 @@
         {
         // add children's counters
         for (var k in this.env.unread_counts) 
-          if (k.indexOf(mbox + this.env.delimiter) == 0) {
+          if (k.indexOf(mbox + this.env.delimiter) == 0)
             childcount += this.env.unread_counts[k];
-	  }
         }
 
       if (mycount && text_obj.innerHTML.match(reg))
@@ -3650,7 +3648,10 @@
         this.set_unread_count_display(mbox.replace(reg, ''), false);
 
       // set the right classes
-      this.set_classname(item, 'unread', (mycount+childcount)>0 ? true : false);
+      if ((mycount+childcount)>0)
+        $(item).addClass('unread');
+      else
+        $(item).removeClass('unread');
       }
 
     // set unread count to window title
@@ -3691,21 +3692,15 @@
     var rowcount = tbody.rows.length;
     var even = rowcount%2;
     
-    var row = document.createElement('TR');
-    row.id = 'rcmrow'+cid;
-    row.className = 'contact '+(even ? 'even' : 'odd');
+    var row = $('<tr>').attr('id', 'rcmrow'+cid).addClass('class').addClass(even ? 'even' : 'odd');
     
     if (this.contact_list.in_selection(cid))
-      row.className += ' selected';
+      row.addClass('selected');
 
     // add each submitted col
-    for (var c in cols)
-      {
-      col = document.createElement('TD');
-      col.className = String(c).toLowerCase();
-      col.innerHTML = cols[c];
-      row.appendChild(col);
-      }
+    for (var c in cols) {
+      col = $('<td>').addClass(String(c).toLowerCase()).html(cols[c]).appendTo(row);
+    }
     
     this.contact_list.insert_row(row);
     this.enable_command('export', (this.contact_list.rowcount > 0));
@@ -3720,19 +3715,16 @@
 
   // display fetched raw headers
   this.set_headers = function(content)
-    {
-    if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content)
-      {
-      var box = this.gui_objects.all_headers_box;
-      box.innerHTML = content;
-      box.style.display = 'block';
+  {
+    if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content) {
+      $(this.gui_objects.all_headers_box).html(content).show();
 
       if (this.env.framed && parent.rcmail)
-	parent.rcmail.set_busy(false);
+        parent.rcmail.set_busy(false);
       else
         this.set_busy(false);
-      }
-    };
+    }
+  };
 
   // display all-headers row and fetch raw message headers
   this.load_headers = function(elem)
@@ -3740,15 +3732,14 @@
     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
       return;
     
-    this.set_classname(elem, 'show-headers', false);
-    this.set_classname(elem, 'hide-headers', true);
-    this.gui_objects.all_headers_row.style.display = bw.ie ? 'block' : 'table-row';
+    $(elem).removeClass('show-headers').addClass('hide-headers');
+    $(this.gui_objects.all_headers_row).show();
     elem.onclick = function() { rcmail.hide_headers(elem); }
 
     // fetch headers only once
     if (!this.gui_objects.all_headers_box.innerHTML)
       {
-      this.display_message(this.get_label('loading'), 'loading', true); 
+      this.display_message(this.get_label('loading'), 'loading', true);
       this.http_post('headers', '_uid='+this.env.uid);
       }
     }
@@ -3759,9 +3750,8 @@
     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
       return;
 
-    this.set_classname(elem, 'hide-headers', false);
-    this.set_classname(elem, 'show-headers', true);
-    this.gui_objects.all_headers_row.style.display = 'none';
+    $(elem).removeClass('hide-headers').addClass('show-headers');
+    $(this.gui_objects.all_headers_row).hide();
     elem.onclick = function() { rcmail.load_headers(elem); }
     }
 
@@ -3772,23 +3762,16 @@
 
   this.html2plain = function(htmlText, id)
     {
-    var http_request = new rcube_http_request();
     var url = this.env.bin_path+'html2text.php';
     var rcmail = this;
 
     this.set_busy(true, 'converting');
     console.log('HTTP POST: '+url);
 
-    http_request.onerror = function(o) { rcmail.http_error(o); };
-    http_request.oncomplete = function(o) { rcmail.set_text_value(o, id); };
-    http_request.POST(url, htmlText, 'application/octet-stream');
-    }
-
-  this.set_text_value = function(httpRequest, id)
-    {
-    this.set_busy(false);
-    document.getElementById(id).value = httpRequest.get_text();
-    console.log(httpRequest.get_text());
+    $.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream',
+      error: function(o) { rcmail.http_error(o); },
+      success: function(data) { rcmail.set_busy(false); $(document.getElementById(id)).val(data); console.log(data); }
+      });
     }
 
 
@@ -3813,99 +3796,61 @@
     this.redirect(this.env.comm_path+'&_action='+action+querystring, lock);
     };
 
-  this.http_sockets = new Array();
-  
-  // find a non-busy socket or create a new one
-  this.get_request_obj = function()
-    {
-    for (var n=0; n<this.http_sockets.length; n++)
-      {
-      if (!this.http_sockets[n].busy)
-        return this.http_sockets[n];
-      }
-    
-    // create a new XMLHTTP object
-    var i = this.http_sockets.length;
-    this.http_sockets[i] = new rcube_http_request();
-
-    return this.http_sockets[i];
-    };
-  
   // send a http request to the server
   this.http_request = function(action, querystring, lock)
-    {
-    var request_obj = this.get_request_obj();
+  {
     querystring += (querystring ? '&' : '') + '_remote=1';
+    var url = this.env.comm_path + '&_action=' + action + '&' + querystring
     
-    // add timestamp to request url to avoid cacheing problems in Safari
-    if (bw.safari)
-      querystring += '&_ts='+(new Date().getTime());
+    // send request
+    console.log('HTTP POST: ' + url);
+    jQuery.get(url, { _unlock:(lock?1:0) }, function(data){ ref.http_response(data); }, 'json');
+  };
+
+  // send a http POST request to the server
+  this.http_post = function(action, postdata, lock)
+  {
+    var url = this.env.comm_path+'&_action=' + action;
+    
+    if (postdata && typeof(postdata) == 'object') {
+      postdata._remote = 1;
+      postdata._unlock = (lock ? 1 : 0);
+    }
+    else
+      postdata += (postdata ? '&' : '') + '_remote=1' + (lock ? '&_unlock=1' : '');
 
     // send request
-    if (request_obj)
-      {
-      console.log('HTTP request: '+this.env.comm_path+'&_action='+action+'&'+querystring);
-
-      if (lock)
-        this.set_busy(true);
-
-      var rcm = this;
-      request_obj.__lock = lock ? true : false;
-      request_obj.__action = action;
-      request_obj.onerror = function(o){ ref.http_error(o); };
-      request_obj.oncomplete = function(o){ ref.http_response(o); };
-      request_obj.GET(this.env.comm_path+'&_action='+action+'&'+querystring);
-      }
-    };
-
-    // send a http POST request to the server
-    this.http_post = function(action, postdata, lock)
-      {
-      var request_obj;
-      if (postdata && typeof(postdata) == 'object')
-        postdata._remote = 1;
-      else
-        postdata += (postdata ? '&' : '') + '_remote=1';
-
-      // send request
-      if (request_obj = this.get_request_obj())
-        {
-        console.log('HTTP POST: '+this.env.comm_path+'&_action='+action);
-
-        if (lock)
-          this.set_busy(true);
-
-        var rcm = this;
-        request_obj.__lock = lock ? true : false;
-        request_obj.__action = action;
-        request_obj.onerror = function(o){ rcm.http_error(o); };
-        request_obj.oncomplete = function(o){ rcm.http_response(o); };
-        request_obj.POST(this.env.comm_path+'&_action='+action, postdata);
-        }
-      };
+    console.log('HTTP POST: ' + url);
+    jQuery.post(url, postdata, function(data){ ref.http_response(data); }, 'json');
+  };
 
   // handle HTTP response
-  this.http_response = function(request_obj)
-    {
-    var ctype = request_obj.get_header('Content-Type');
-    if (ctype)
-      {
-      ctype = String(ctype).toLowerCase();
-      var ctype_array=ctype.split(";");
-      ctype = ctype_array[0];
-      }
-
-    if (request_obj.__lock)
+  this.http_response = function(response)
+  {
+    var console_msg = '';
+    
+    if (response.unlock)
       this.set_busy(false);
 
-    console.log(request_obj.get_text());
+    // set env vars
+    if (response.env)
+      this.set_env(response.env);
+
+    // we have labels to add
+    if (typeof response.texts == 'object') {
+      for (var name in response.texts)
+        if (typeof response.texts[name] == 'string')
+          this.add_label(name, response.texts[name]);
+    }
 
     // if we get javascript code from server -> execute it
-    if (request_obj.get_text() && (ctype=='text/javascript' || ctype=='application/x-javascript'))
-      eval(request_obj.get_text());
-
+    if (response.exec) {
+      console.log(response.exec);
+      eval(response.exec);
+    }
+    
     // process the response data according to the sent action
-    switch (request_obj.__action) {
+    switch (response.action) {
       case 'delete':
         if (this.task == 'addressbook') {
           var uid = this.contact_list.get_selection();
@@ -3922,7 +3867,7 @@
         break;
         
       case 'purge':
-      case 'expunge':      
+      case 'expunge':
         if (!this.env.messagecount && this.task == 'mail') {
           // clear preview pane content
           if (this.env.contentframe)
@@ -3937,23 +3882,22 @@
       case 'getunread':
       case 'list':
         if (this.task == 'mail') {
-          if (this.message_list && request_obj.__action == 'list')
+          if (this.message_list && response.action == 'list')
             this.msglist_select(this.message_list);
           this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
           this.enable_command('purge', this.purge_mailbox_test());
         }
         else if (this.task == 'addressbook')
           this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
-
         break;
-      }
-
-    request_obj.reset();
-    };
+    }
+  };
 
   // handle HTTP request errors
-  this.http_error = function(request_obj)
+  this.http_error = function(request, status, err)
     {
+      alert(status+":"+err);
+/*
     //alert('Error sending request: '+request_obj.url+' => HTTP '+request_obj.xmlhttp.status);
     if (request_obj.__lock)
       this.set_busy(false);
@@ -3961,6 +3905,7 @@
     request_obj.reset();
     request_obj.__lock = false;
     this.display_message('Unknown Server Error!', 'error');
+*/
     };
 
   // use an image to send a keep-alive siganl to the server
@@ -4066,161 +4011,11 @@
       }
     };
     
-  }  // end object rcube_webmail
+}  // end object rcube_webmail
 
 
-/**
- * Class for sending HTTP requests
- * @constructor
- */
-function rcube_http_request()
-  {
-  this.url = '';
-  this.busy = false;
-  this.xmlhttp = null;
-
-  // reset object properties
-  this.reset = function()
-    {
-    // set unassigned event handlers
-    this.onloading = function(){ };
-    this.onloaded = function(){ };
-    this.oninteractive = function(){ };
-    this.oncomplete = function(){ };
-    this.onabort = function(){ };
-    this.onerror = function(){ };
-    
-    this.url = '';
-    this.busy = false;
-    this.xmlhttp = null;
-    }
-
-  // create HTMLHTTP object
-  this.build = function()
-    {
-    if (window.XMLHttpRequest)
-      this.xmlhttp = new XMLHttpRequest();
-    else if (window.ActiveXObject)
-      {
-      try { this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
-      catch(e) { this.xmlhttp = null; }
-      }
-    else
-      {
-      
-      }
-    }
-
-  // send GET request
-  this.GET = function(url)
-    {
-    this.build();
-
-    if (!this.xmlhttp)
-      {
-      this.onerror(this);
-      return false;
-      }
-
-    var _ref = this;
-    this.url = url;
-    this.busy = true;
-
-    this.xmlhttp.onreadystatechange = function(){ _ref.xmlhttp_onreadystatechange(); };
-    this.xmlhttp.open('GET', url, true);
-    this.xmlhttp.setRequestHeader('X-RoundCube-Referer', bw.get_cookie('roundcube_sessid'));
-    this.xmlhttp.send(null);
-    };
-
-  this.POST = function(url, body, contentType)
-    {
-    // default value for contentType if not provided
-    if (typeof(contentType) == 'undefined')
-      contentType = 'application/x-www-form-urlencoded';
-
-    this.build();
-    
-    if (!this.xmlhttp)
-    {
-       this.onerror(this);
-       return false;
-    }
-    
-    var req_body = body;
-    if (typeof(body) == 'object')
-    {
-      req_body = '';
-      for (var p in body)
-        req_body += (req_body ? '&' : '') + p+'='+urlencode(body[p]);
-    }
-
-    var ref = this;
-    this.url = url;
-    this.busy = true;
-    
-    this.xmlhttp.onreadystatechange = function() { ref.xmlhttp_onreadystatechange(); };
-    this.xmlhttp.open('POST', url, true);
-    this.xmlhttp.setRequestHeader('Content-Type', contentType);
-    this.xmlhttp.setRequestHeader('X-RoundCube-Referer', bw.get_cookie('roundcube_sessid'));
-    this.xmlhttp.send(req_body);
-    };
-
-  // handle onreadystatechange event
-  this.xmlhttp_onreadystatechange = function()
-    {
-    if(this.xmlhttp.readyState == 1)
-      this.onloading(this);
-
-    else if(this.xmlhttp.readyState == 2)
-      this.onloaded(this);
-
-    else if(this.xmlhttp.readyState == 3)
-      this.oninteractive(this);
-
-    else if(this.xmlhttp.readyState == 4)
-      {
-      try {
-        if (this.xmlhttp.status == 0)
-          this.onabort(this);
-        else if(this.xmlhttp.status == 200)
-          this.oncomplete(this);
-        else
-          this.onerror(this);
-
-        this.busy = false;
-        }
-      catch(err)
-        {
-        this.onerror(this);
-        this.busy = false;
-        }
-      }
-    }
-
-  // getter method for HTTP headers
-  this.get_header = function(name)
-    {
-    return this.xmlhttp.getResponseHeader(name);
-    };
-
-  this.get_text = function()
-    {
-    return this.xmlhttp.responseText;
-    };
-
-  this.get_xml = function()
-    {
-    return this.xmlhttp.responseXML;
-    };
-
-  this.reset();
-  
-  }  // end class rcube_http_request
-
-// helper function to call the init method with a delay
-function call_init(o)
-  {
-    window.setTimeout('if (window[\''+o+'\'] && window[\''+o+'\'].init) { '+o+'.init(); }',
-        bw.win ? 500 : 200);
-  }
+// copy event engine prototype
+rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
+rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
+rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
 
diff --git a/program/js/common.js b/program/js/common.js
index bd699d9..407da41 100644
--- a/program/js/common.js
+++ b/program/js/common.js
@@ -93,7 +93,7 @@
   }
 
 
-// static functions for event handling
+// static functions for DOM event handling
 var rcube_event = {
 
 /**
@@ -159,8 +159,8 @@
   }
 
   if (e._offset) {
-    mX += e._offset.x;
-    mY += e._offset.y;
+    mX += e._offset.left;
+    mY += e._offset.top;
   }
 
   return { x:mX, y:mY };
@@ -234,7 +234,86 @@
 };
 
 
-var rcube_layer_objects = new Array();
+/**
+ * rcmail objects event interface
+ */
+function rcube_event_engine()
+{
+  this._events = {};
+}
+
+rcube_event_engine.prototype = {
+
+/**
+ * Setter for object event handlers
+ *
+ * @param {String}   Event name
+ * @param {Function} Handler function
+ * @return Listener ID (used to remove this handler later on)
+ */
+addEventListener: function(evt, func, obj)
+{
+  if (!this._events)
+    this._events = {};
+  if (!this._events[evt])
+    this._events[evt] = [];
+    
+  var e = {func:func, obj:obj ? obj : window};
+  this._events[evt][this._events[evt].length] = e;
+},
+
+/**
+ * Removes a specific event listener
+ *
+ * @param {String} Event name
+ * @param {Int}    Listener ID to remove
+ */
+removeEventListener: function(evt, func, obj)
+{
+  if (typeof obj == 'undefined')
+    obj = window;
+    
+  for (var h,i=0; this._events && this._events[evt] && i < this._events[evt].length; i++)
+    if ((h = this._events[evt][i]) && h.func == func && h.obj == obj)
+      this._events[evt][i] = null;
+},
+
+/**
+ * This will execute all registered event handlers
+ *
+ * @param {String} Event to trigger
+ * @param {Object} Event object/arguments
+ */
+triggerEvent: function(evt, e)
+{
+  var ret, h;
+  if (typeof e == 'undefined')
+    e = {};
+  if (typeof e == 'object')
+    e.event = evt;
+  
+  if (this._events && this._events[evt] && !this._event_exec) {
+    this._event_exec = true;
+    for (var i=0; i < this._events[evt].length; i++) {
+      if ((h = this._events[evt][i])) {
+        if (typeof h.func == 'function')
+          ret = h.func.call ? h.func.call(h.obj, this, e) : h.func(this, e);
+        else if (typeof h.obj[h.func] == 'function')
+          ret = h.obj[h.func](this, e);
+              
+        // cancel event execution
+        if (typeof ret != 'undefined' && !ret)
+          break;
+      }
+    }
+  }
+
+  this._event_exec = false;
+  return ret;
+}
+
+}  // end rcube_event_engine.prototype
+
 
 
 /**
@@ -243,7 +322,7 @@
  * @constructor
  */
 function rcube_layer(id, attributes)
-  {
+{
   this.name = id;
   
   // create a new layer in the current document
@@ -310,10 +389,6 @@
   this.y = parseInt(this.elm.offsetTop);
   this.visible = (this.css.visibility=='visible' || this.css.visibility=='show' || this.css.visibility=='inherit') ? true : false;
 
-  this.id = rcube_layer_objects.length;
-  this.obj = 'rcube_layer_objects['+this.id+']';
-  rcube_layer_objects[this.id] = this;
-
 
   // ********* layer object methods *********
 
@@ -327,16 +402,6 @@
     this.css.top = Math.round(this.y)+'px';
     }
 
-
-  // move the layer for a specific step
-  this.shift = function(x,y)
-    {
-    x = Math.round(x*100)/100;
-    y = Math.round(y*100)/100;
-    this.move(this.x+x, this.y+y);
-    }
-
-
   // change the layers width and height
   this.resize = function(w,h)
     {
@@ -344,15 +409,6 @@
     this.css.height = h+'px';
     this.width = w;
     this.height = h;
-    }
-
-
-  // cut the layer (top,width,height,left)
-  this.clip = function(t,w,h,l)
-    {
-    this.css.clip='rect('+t+' '+w+' '+h+' '+l+')';
-    this.clip_height = h;
-    this.clip_width = w;
     }
 
 
@@ -383,36 +439,7 @@
     this.elm.innerHTML = cont;
     }
 
-
-  // set the given color to the layer background
-  this.set_bgcolor = function(c)
-    {
-    if(!c || c=='#')
-      c = 'transparent';
-
-    this.css.backgroundColor = c;
-    }
-
-
-  // set the opacity of a layer to the given ammount (in %)
-  this.set_opacity = function(v)
-    {
-    if(!bw.opacity)
-      return;
-
-    var op = v<=1 ? Math.round(v*100) : parseInt(v);
-
-    if(bw.ie)
-      this.css.filter = 'alpha(opacity:'+op+')';
-    else if(bw.safari)
-      {
-      this.css.opacity = op/100;
-      this.css.KhtmlOpacity = op/100;
-      }
-    else if(bw.mz)
-      this.css.MozOpacity = op/100;
-    }
-  }
+}
 
 
 // check if input is a valid email address
@@ -472,7 +499,7 @@
 
 // get any type of html objects by id/name
 function rcube_find_object(id, d)
-  {
+{
   var n, f, obj, e;
   if(!d) d = document;
 
@@ -486,88 +513,34 @@
   if(!obj && d.images.length)
     obj = d.images[id];
 
-  if(!obj && d.forms.length)
-    for(f=0; f<d.forms.length; f++)
-      {
+  if (!obj && d.forms.length) {
+    for (f=0; f<d.forms.length; f++) {
       if(d.forms[f].name == id)
         obj = d.forms[f];
       else if(d.forms[f].elements[id])
         obj = d.forms[f].elements[id];
-      }
-
-  if(!obj && d.layers)
-    {
-    if(d.layers[id]) obj = d.layers[id];
-    for(n=0; !obj && n<d.layers.length; n++)
-      obj = rcube_find_object(id, d.layers[n].document);
     }
+  }
+
+  if (!obj && d.layers) {
+    if (d.layers[id]) obj = d.layers[id];
+    for (n=0; !obj && n<d.layers.length; n++)
+      obj = rcube_find_object(id, d.layers[n].document);
+  }
 
   return obj;
-  }
-
-
-// return the absolute position of an object within the document
-function rcube_get_object_pos(obj, relative)
-  {
-  if(typeof(obj)=='string')
-    obj = rcube_find_object(obj);
-
-  if(!obj) return {x:0, y:0};
-
-  var iX = (bw.layers) ? obj.x : obj.offsetLeft;
-  var iY = (bw.layers) ? obj.y : obj.offsetTop;
-
-  if(!relative && (bw.ie || bw.mz))
-    {
-    var elm = obj.offsetParent;
-    while(elm && elm!=null)
-      {
-      iX += elm.offsetLeft - (elm.parentNode && elm.parentNode.scrollLeft ? elm.parentNode.scrollLeft : 0);
-      iY += elm.offsetTop - (elm.parentNode && elm.parentNode.scrollTop ? elm.parentNode.scrollTop : 0);
-      elm = elm.offsetParent;
-      }
-    }
-
-  return {x:iX, y:iY};
-  }
+}
 
 // determine whether the mouse is over the given object or not
 function rcube_mouse_is_over(ev, obj)
 {
   var mouse = rcube_event.get_mouse_pos(ev);
-  var pos = rcube_get_object_pos(obj);
-  
-  return ((mouse.x >= pos.x) && (mouse.x < (pos.x + obj.offsetWidth)) &&
-    (mouse.y >= pos.y) && (mouse.y < (pos.y + obj.offsetHeight)));
+  var pos = $(obj).offset();
+
+  return ((mouse.x >= pos.left) && (mouse.x < (pos.left + obj.offsetWidth)) &&
+    (mouse.y >= pos.top) && (mouse.y < (pos.top + obj.offsetHeight)));
 }
 
-
-/**
- * Return the currently applied value of a css property
- *
- * @param {Object} html_element  Node reference
- * @param {String} css_property  Property name to read in Javascript notation (eg. 'textAlign')
- * @param {String} mozilla_equivalent  Equivalent property name in CSS notation (eg. 'text-align')
- * @return CSS property value
- * @type String
- */
-function get_elements_computed_style(html_element, css_property, mozilla_equivalent)
-  {
-  if (arguments.length==2)
-    mozilla_equivalent = css_property;
-
-  var el = html_element;
-  if (typeof(html_element)=='string')
-    el = rcube_find_object(html_element);
-
-  if (el && el.currentStyle)
-    return el.currentStyle[css_property];
-  else if (el && document.defaultView && document.defaultView.getComputedStyle)
-    return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozilla_equivalent);
-  else
-    return false;
-  }
-  
 
 // cookie functions by GoogieSpell
 function setCookie(name, value, expires, path, domain, secure)
@@ -611,7 +584,7 @@
 
     if (box) {
       if (msg.charAt(msg.length-1)=='\n')
-	msg += '--------------------------------------\n';
+        msg += '--------------------------------------\n';
       else
         msg += '\n--------------------------------------\n';
 
@@ -633,7 +606,8 @@
 }
 
 var bw = new roundcube_browser();
-var console = new rcube_console();
+if (!window.console) 
+  console = new rcube_console();
 
 
 // Add escape() method to RegExp object
diff --git a/program/js/editor.js b/program/js/editor.js
index 6b847ba..7f937b2 100644
--- a/program/js/editor.js
+++ b/program/js/editor.js
@@ -52,8 +52,8 @@
       spellchecker_languages : (rcmail.env.spellcheck_langs ? rcmail.env.spellcheck_langs : 'Dansk=da,Deutsch=de,+English=en,Espanol=es,Francais=fr,Italiano=it,Nederlands=nl,Polski=pl,Portugues=pt,Suomi=fi,Svenska=sv'),
       gecko_spellcheck : true,
       relative_urls : false,
-      remove_script_host : false ,
-      rc_client: rcube_webmail_client,
+      remove_script_host : false,
+      rc_client: rcmail,
       oninit : 'rcmail_editor_callback'
     });
 }
diff --git a/program/js/list.js b/program/js/list.js
index 522af59..dabcecb 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -51,7 +51,6 @@
   this.drag_mouse_start = null;
   this.dblclick_time = 600;
   this.row_init = function(){};
-  this.events = { click:[], dblclick:[], select:[], keypress:[], dragstart:[], dragmove:[], dragend:[] };
   
   // overwrite default paramaters
   if (p && typeof(p)=='object')
@@ -160,13 +159,15 @@
 insert_row: function(row, attop)
 {
   var tbody = this.list.tBodies[0];
+  if (!row.jquery)
+    row = $(row);
 
   if (attop && tbody.rows.length)
-    tbody.insertBefore(row, tbody.firstChild);
+    row.prependTo(tbody)
   else
-    tbody.appendChild(row);
+    row.appendTo(tbody);
 
-  this.init_row(row);
+  this.init_row(row[0]);
   this.rowcount++;
 },
 
@@ -181,10 +182,8 @@
   for (var n=0; n<this.selection.length; n++)
   {
     id = this.selection[n];
-    if (this.rows[id] && this.rows[id].obj)
-    {
-      this.set_classname(this.rows[id].obj, 'selected', true);
-      this.set_classname(this.rows[id].obj, 'unfocused', false);
+    if (this.rows[id] && this.rows[id].obj) {
+      $(this.rows[id].obj).addClass('selected').removeClass('unfocused');
     }
   }
 
@@ -203,10 +202,8 @@
   for (var n=0; n<this.selection.length; n++)
   {
     id = this.selection[n];
-    if (this.rows[id] && this.rows[id].obj)
-    {
-      this.set_classname(this.rows[id].obj, 'selected', false);
-      this.set_classname(this.rows[id].obj, 'unfocused', true);
+    if (this.rows[id] && this.rows[id].obj) {
+      $(this.rows[id].obj).removeClass('selected').addClass('unfocused');
     }
   }
 },
@@ -251,26 +248,26 @@
       if (iframes[n].contentDocument)
         iframedoc = iframes[n].contentDocument;
       else if (iframes[n].contentWindow)
-	iframedoc = iframes[n].contentWindow.document;
+        iframedoc = iframes[n].contentWindow.document;
       else if (iframes[n].document)
         iframedoc = iframes[n].document;
 
       if (iframedoc)
       {
-	var list = this;
-	var pos = rcube_get_object_pos(document.getElementById(iframes[n].id));
-	this.iframe_events[n] = function(e) { e._offset = pos; return list.drag_mouse_move(e); }
-	
-	if (iframedoc.addEventListener)
-	  iframedoc.addEventListener('mousemove', this.iframe_events[n], false);
-	else if (iframes[n].attachEvent)
-	  iframedoc.attachEvent('onmousemove', this.iframe_events[n]);
-	else
-	  iframedoc['onmousemove'] = this.iframe_events[n];
+        var list = this;
+        var pos = $('#'+iframes[n].id).offset();
+        this.iframe_events[n] = function(e) { e._offset = pos; return list.drag_mouse_move(e); }
+
+        if (iframedoc.addEventListener)
+          iframedoc.addEventListener('mousemove', this.iframe_events[n], false);
+        else if (iframes[n].attachEvent)
+          iframedoc.attachEvent('onmousemove', this.iframe_events[n]);
+        else
+          iframedoc['onmousemove'] = this.iframe_events[n];
 
         rcube_event.add_listener({element:iframedoc, event:'mouseup', object:this, method:'drag_mouse_up'});
       }
-    }								  
+    }
   }
 
   return false;
@@ -307,9 +304,9 @@
 
   // row was double clicked
   if (this.rows && dblclicked && this.in_selection(id))
-    this.trigger_event('dblclick');
+    this.triggerEvent('dblclick');
   else
-    this.trigger_event('click');
+    this.triggerEvent('click');
 
   if (!this.drag_active)
     rcube_event.cancel(e);
@@ -407,10 +404,10 @@
 
   // trigger event if selection changed
   if (this.selection.join(',') != select_before)
-    this.trigger_event('select');
+    this.triggerEvent('select');
 
   if (this.last_selected != 0 && this.rows[this.last_selected])
-    this.set_classname(this.rows[this.last_selected].obj, 'focused', false);
+    $(this.rows[this.last_selected].obj).removeClass('focused');
 
   // unselect if toggleselect is active and the same row was clicked again
   if (this.toggleselect && this.last_selected == id)
@@ -419,7 +416,7 @@
     id = null;
   }
   else
-    this.set_classname(this.rows[id].obj, 'focused', true);
+    $(this.rows[id].obj).addClass('focused');
 
   if (!this.selection.length)
     this.shift_start = null;
@@ -524,7 +521,7 @@
 
   // trigger event if selection changed
   if (this.selection.join(',') != select_before)
-    this.trigger_event('select');
+    this.triggerEvent('select');
 
   this.focus();
 
@@ -543,27 +540,24 @@
   if (id)
     {
     for (var n=0; n<this.selection.length; n++)
-      if (this.selection[n] == id)
-        {
-	this.selection.splice(n,1);
-    	break;
-	}
+      if (this.selection[n] == id) {
+        this.selection.splice(n,1);
+        break;
+      }
     }
   // all rows
   else
     {
     for (var n=0; n<this.selection.length; n++)
-      if (this.rows[this.selection[n]])
-        {
-        this.set_classname(this.rows[this.selection[n]].obj, 'selected', false);
-        this.set_classname(this.rows[this.selection[n]].obj, 'unfocused', false);
+      if (this.rows[this.selection[n]]) {
+        $(this.rows[this.selection[n]].obj).removeClass('selected').removeClass('unfocused');
         }
     
     this.selection = new Array();
     }
 
   if (num_select && !this.selection.length)
-    this.trigger_event('select');
+    this.triggerEvent('select');
 },
 
 
@@ -599,7 +593,7 @@
     {
       this.clear_selection();
       this.selection[0] = id;
-      this.set_classname(this.rows[id].obj, 'selected', true);
+      $(this.rows[id].obj).addClass('selected');
     }
   }
   else if (this.rows[id])
@@ -607,7 +601,7 @@
     if (!this.in_selection(id))  // select row
     {
       this.selection[this.selection.length] = id;
-      this.set_classname(this.rows[id].obj, 'selected', true);
+      $(this.rows[id].obj).addClass('selected');
     }
     else  // unselect row
     {
@@ -615,8 +609,7 @@
       var a_pre = this.selection.slice(0, p);
       var a_post = this.selection.slice(p+1, this.selection.length);
       this.selection = a_pre.concat(a_post);
-      this.set_classname(this.rows[id].obj, 'selected', false);
-      this.set_classname(this.rows[id].obj, 'unfocused', false);
+      $(this.rows[id].obj).removeClass('selected').removeClass('unfocused');
     }
   }
 },
@@ -644,7 +637,7 @@
     default:
       this.shiftkey = e.shiftKey;
       this.key_pressed = keyCode;
-      this.trigger_event('keypress');
+      this.triggerEvent('keypress');
       
       if (this.key_pressed == this.BACKSPACE_KEY)
         return rcube_event.cancel(e);
@@ -729,7 +722,7 @@
       return false;
   
     if (!this.draglayer)
-      this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, vis:0, zindex:2000});
+      this.draglayer = $('<div>').attr('id', 'rcmdraglayer').css({ position:'absolute', display:'none', 'z-index':2000 }).appendTo(document.body);
   
     // get subjects of selectedd messages
     var names = '';
@@ -754,6 +747,9 @@
             if (((node = obj.childNodes[i].firstChild) && (node.nodeType==3 || node.nodeName=='A')) &&
               (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c)))
             {
+              if (n == 0)
+                this.drag_start_pos = $(node).offset();
+              
               subject = node.nodeType==3 ? node.data : node.innerHTML;
 	      // remove leading spaces
 	      subject = subject.replace(/^\s+/i, '');
@@ -767,18 +763,18 @@
       }
     }
 
-    this.draglayer.write(names);
-    this.draglayer.show(1);
+    this.draglayer.html(names);
+    this.draglayer.show();
 
     this.drag_active = true;
-    this.trigger_event('dragstart');
+    this.triggerEvent('dragstart');
   }
 
   if (this.drag_active && this.draglayer)
   {
     var pos = rcube_event.get_mouse_pos(e);
-    this.draglayer.move(pos.x+20, bw.ie ? pos.y-5+document.documentElement.scrollTop : pos.y-5);
-    this.trigger_event('dragmove', e);
+    this.draglayer.css({ left:(pos.x+20)+'px', top:(pos.y-5 + (bw.ie ? document.documentElement.scrollTop : 0))+'px' });
+    this.triggerEvent('dragmove', e);
   }
 
   this.drag_start = false;
@@ -794,11 +790,15 @@
 {
   document.onmousemove = null;
 
-  if (this.draglayer && this.draglayer.visible)
-    this.draglayer.show(0);
+  if (this.draglayer && this.draglayer.is(':visible')) {
+    if (this.drag_start_pos)
+      this.draglayer.animate(this.drag_start_pos, 300, 'swing').hide(20);
+    else
+      this.draglayer.hide();
+  }
 
   this.drag_active = false;
-  this.trigger_event('dragend');
+  this.triggerEvent('dragend');
 
   rcube_event.remove_listener({element:document, event:'mousemove', object:this, method:'drag_mouse_move'});
   rcube_event.remove_listener({element:document, event:'mouseup', object:this, method:'drag_mouse_up'});
@@ -828,68 +828,10 @@
     }
 
   return rcube_event.cancel(e);
-},
-
-
-
-/**
- * set/unset a specific class name
- */
-set_classname: function(obj, classname, set)
-{
-  var reg = new RegExp('\s*'+classname, 'i');
-  if (!set && obj.className.match(reg))
-    obj.className = obj.className.replace(reg, '');
-  else if (set && !obj.className.match(reg))
-    obj.className += ' '+classname;
-},
-
-
-/**
- * Setter for object event handlers
- *
- * @param {String}   Event name
- * @param {Function} Handler function
- * @return Listener ID (used to remove this handler later on)
- */
-addEventListener: function(evt, handler)
-{
-  if (this.events[evt]) {
-    var handle = this.events[evt].length;
-    this.events[evt][handle] = handler;
-    return handle;
-  }
-  else
-    return false;
-},
-
-
-/**
- * Removes a specific event listener
- *
- * @param {String} Event name
- * @param {Int}    Listener ID to remove
- */
-removeEventListener: function(evt, handle)
-{
-  if (this.events[evt] && this.events[evt][handle])
-    this.events[evt][handle] = null;
-},
-
-
-/**
- * This will execute all registered event handlers
- * @private
- */
-trigger_event: function(evt, p)
-{
-  if (this.events[evt] && this.events[evt].length) {
-    for (var i=0; i<this.events[evt].length; i++)
-      if (typeof(this.events[evt][i]) == 'function')
-        this.events[evt][i](this, p);
-  }
 }
-
 
 };
 
+rcube_list_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
+rcube_list_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
+rcube_list_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
diff --git a/program/lib/imap.inc b/program/lib/imap.inc
index 995d82f..967b3f1 100644
--- a/program/lib/imap.inc
+++ b/program/lib/imap.inc
@@ -182,6 +182,7 @@
 	var $forwarded = false;
 	var $junk = false;
 	var $flagged = false;
+	var $others = array();
 }
 
 /**
@@ -1661,7 +1662,7 @@
 	return $t_index;
 }
 
-function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false)
+function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='')
 {
 	global $IMAP_USE_INTERNAL_DATE;
 	
@@ -1701,6 +1702,9 @@
 			}
 		}
 	}
+	
+	if ($add)
+	  $add = ' '.strtoupper(trim($add));
 
 	/* FETCH uid, size, flags and headers */
 	$key  	  = 'FH12';
@@ -1711,7 +1715,7 @@
 	$request .= "BODY.PEEK[HEADER.FIELDS ";
 	$request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC ";
 	$request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID ";
-	$request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY)])";
+	$request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY".$add.")])";
 
 	if (!iil_PutLine($fp, $request)) {
 		return false;
@@ -1859,7 +1863,7 @@
 					list($field, $string) = iil_SplitHeaderLine($str);
 					
 					$field  = strtolower($field);
-                                        $string = ereg_replace("\n[[:space:]]*"," ",$string); 
+					$string = ereg_replace("\n[[:space:]]*"," ",$string);
 					
 					switch ($field) {
 					case 'date';
@@ -1917,6 +1921,10 @@
 						if (preg_match('/^(\d+)/', $string, $matches))
 							$result[$id]->priority = intval($matches[1]);
 						break;
+					default:
+						if (strlen($field) > 2)
+							$result[$id]->others[$field] = $string;
+						break;
 					} // end switch ()
 				} // end while ()
 		
@@ -1964,9 +1972,9 @@
 	return $result;
 }
 
-function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false, $bodystr=false) {
+function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false, $bodystr=false, $add='') {
 
-	$a  = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch, $bodystr);
+	$a  = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch, $bodystr, $add);
 	if (is_array($a)) {
 		return array_shift($a);
 	}
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index 33dfad6..e3d7606 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -42,18 +42,20 @@
 $js_list = array();
 if (strtolower($CONFIG['address_book_type']) != 'ldap') {
   // We are using the DB address book, add it.
-  $js_list = array("0" => array('id' => 0, 'readonly' => false));
+  $js_list['0'] = array('id' => 0, 'name' => rcube_label('personaladrbook'), 'readonly' => false);
 }
 if (is_array($CONFIG['ldap_public'])) {
   foreach ($CONFIG['ldap_public'] as $id => $prop)
-    $js_list[$id] = array('id' => $id, 'readonly' => !$prop['writable']);
+    $js_list[$id] = array('id' => $id, 'name' => $prop['name'], 'readonly' => !$prop['writable']);
 }
-$OUTPUT->set_env('address_sources', $js_list);
+
+$plugin = $RCMAIL->plugins->exec_hook('address_sources', array('sources' => $js_list));
+$OUTPUT->set_env('address_sources', $plugin['sources']);
 
 
 function rcmail_directory_list($attrib)
 {
-  global $CONFIG, $OUTPUT;
+  global $RCMAIL, $OUTPUT;
   
   if (!$attrib['id'])
     $attrib['id'] = 'rcmdirectorylist';
@@ -63,26 +65,24 @@
   $current = get_input_value('_source', RCUBE_INPUT_GPC);
   $line_templ = html::tag('li', array('id' => 'rcmli%s', 'class' => '%s'),
     html::a(array('href' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
-    
-  if (strtolower($CONFIG['address_book_type']) != 'ldap') {
-    $out .= sprintf($line_templ, $local_id, (!$current ? 'selected' : ''),
-      Q(rcmail_url(null, array('_source' => $local_id))), $local_id, rcube_label('personaladrbook'));
-  } // end if
-  else {
+
+  if (!$current && strtolower($RCMAIL->config->get('address_book_type', 'sql')) != 'ldap') {
+    $current = '0';
+  }
+  else if (!$current) {
     // DB address book not used, see if a source is set, if not use the
     // first LDAP directory.
-    if (!$current) {
-      $current = key((array)$CONFIG['ldap_public']);
-    } // end if
-  } // end else
-  
-  foreach ((array)$CONFIG['ldap_public'] as $id => $prop) {
+    $current = key((array)$RCMAIL->config->get('ldap_public', array()));
+  }
+
+  foreach ((array)$OUTPUT->env['address_sources'] as $j => $source) {
+    $id = $source['id'] ? $source['id'] : $j;
     $js_id = JQ($id);
     $dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id);
     $out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''),
-      Q(rcmail_url(null, array('_source' => $id))), $js_id, (!empty($prop['name']) ? Q($prop['name']) : Q($id)));
+      Q(rcmail_url(null, array('_source' => $id))), $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id)));
   }
-  
+
   $OUTPUT->add_gui_object('folderlist', $attrib['id']);
   
   return html::tag('ul', $attrib, $out, html::$common_attrib);
diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc
index f6e29f9..6d58edc 100644
--- a/program/steps/mail/attachments.inc
+++ b/program/steps/mail/attachments.inc
@@ -28,45 +28,44 @@
 // remove an attachment
 if ($RCMAIL->action=='remove-attachment')
 {
-  if (preg_match('/^rcmfile([0-9]+)$/', $_POST['_file'], $regs))
-  {
+  $id = 'undefined';
+  if (preg_match('/^rcmfile(\w+)$/', $_POST['_file'], $regs))
     $id = $regs[1];
-    if (is_array($_SESSION['compose']['attachments'][$id]))
-    {
-      @unlink($_SESSION['compose']['attachments'][$id]['path']);
+  if ($attachment = $_SESSION['compose']['attachments'][$id])
+    $attachment = $RCMAIL->plugins->exec_hook('remove_attachment', $attachment);
+  if ($attachment['status']) {
+    if (is_array($_SESSION['compose']['attachments'][$id])) {
       unset($_SESSION['compose']['attachments'][$id]);
       $OUTPUT->command('remove_from_attachment_list', "rcmfile$id");
-      $OUTPUT->send();
     }
   }
+  
+  $OUTPUT->send();
   exit;
 }
 
 if ($RCMAIL->action=='display-attachment')
 {
-  if (preg_match('/^rcmfile([0-9]+)$/', $_GET['_file'], $regs))
-  {
+  $id = 'undefined';
+  if (preg_match('/^rcmfile(\w+)$/', $_GET['_file'], $regs))
     $id = $regs[1];
-    if (is_array($_SESSION['compose']['attachments'][$id]))
-    {
-      $apath = $_SESSION['compose']['attachments'][$id]['path'];
-      header('Content-Type: ' . $_SESSION['compose']['attachments'][$id]['mimetype']);
-      header('Content-Length: ' . filesize($apath));
-      readfile($apath);
-    }
+  if ($attachment = $_SESSION['compose']['attachments'][$id])
+    $attachment = $RCMAIL->plugins->exec_hook('display_attachment', $attachment);
+    
+  if ($attachment['status']) {
+    $size = $attachment['data'] ? strlen($attachment['data']) : @filesize($attachment['path']);
+    header('Content-Type: ' . $attachment['mimetype']);
+    header('Content-Length: ' . $size);
+    
+    if ($attachment['data'])
+      echo $attachment['data'];
+    else if ($attachment['path'])
+      readfile($attachment['path']);
   }
   exit;
 }
 
 // attachment upload action
-
-// use common temp dir for file uploads
-$temp_dir = unslashify($CONFIG['temp_dir']);
-
-// #1484529: we need absolute path on Windows for move_uploaded_file()
-if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
-  $temp_dir = realpath($temp_dir);
-}
 
 if (!is_array($_SESSION['compose']['attachments'])) {
   $_SESSION['compose']['attachments'] = array();
@@ -77,15 +76,20 @@
 
 if (is_array($_FILES['_attachments']['tmp_name'])) {
   foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
-    $tmpfname = tempnam($temp_dir, 'rcmAttmnt');
-    if (move_uploaded_file($filepath, $tmpfname) && file_exists($tmpfname)) {
-      $id = count($_SESSION['compose']['attachments']);
-      $_SESSION['compose']['attachments'][] = array(
-        'name' => $_FILES['_attachments']['name'][$i],
-        'mimetype' => rc_mime_content_type($tmpfname, $_FILES['_attachments']['name'][$i], $_FILES['_attachments']['type'][$i]),
-        'path' => $tmpfname,
-      );
+    $attachment = array(
+      'path' => $filepath,
+      'name' => $_FILES['_attachments']['name'][$i],
+      'mimetype' => rc_mime_content_type($tmpfname, $_FILES['_attachments']['type'][$i])
+    );
 
+    $attachment = $RCMAIL->plugins->exec_hook('upload_attachment', $attachment);
+    if ($attachment['status']) {
+      $id = $attachment['id'];
+      
+      // store new attachment in session
+      unset($attachment['status']);
+      $_SESSION['compose']['attachments'][$id] = $attachment;
+      
       if (is_file($icon = $CONFIG['skin_path'] . '/images/icons/remove-attachment.png')) {
         $button = html::img(array(
           'src' => $icon,
@@ -99,11 +103,11 @@
 
       $content = html::a(array(
         'href' => "#delete",
-        'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%d', this)", JS_OBJECT_NAME, $id),
+        'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id),
         'title' => rcube_label('delete'),
       ), $button);
-      
-      $content .= Q($_FILES['_attachments']['name'][$i]);
+
+      $content .= Q($attachment['name']);
       
       $OUTPUT->command('add2attachment_list', "rcmfile$id", $content);
     }
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 49c4c30..c93fa9b 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -594,8 +594,6 @@
   global $OUTPUT;
   
   $cid_map = array();
-  $id = 0;
-  
   foreach ((array)$message->mime_parts as $pid => $part)
   {
     if (($part->ctype_primary != 'message' || !$bodyIsHtml) &&
@@ -603,16 +601,14 @@
          || (empty($part->disposition) && $part->filename)))
     {
       if ($attachment = rcmail_save_attachment($message, $pid)) {
-        $_SESSION['compose']['attachments'][$id] = $attachment;
-	if ($bodyIsHtml && $part->filename && $part->content_id) {
-	  $cid_map['cid:'.$part->content_id] = 
-	    $OUTPUT->app->comm_path.'&_action=display-attachment&_file=rcmfile'.$id;
+        $_SESSION['compose']['attachments'][$attachment['id']] = $attachment;
+        if ($bodyIsHtml && $part->filename && $part->content_id) {
+          $cid_map['cid:'.$part->content_id] = $OUTPUT->app->comm_path.'&_action=display-attachment&_file=rcmfile'.$attachment['id'];
         }
-	$id++;
       }
     }
   }
-	
+
   $_SESSION['compose']['forward_attachments'] = true;
 
   return $cid_map;
@@ -624,15 +620,11 @@
   global $OUTPUT;
 
   $cid_map = array();
-  $id = 0;
-  
   foreach ((array)$message->mime_parts as $pid => $part) {
     if ($part->content_id && $part->filename) {
       if ($attachment = rcmail_save_attachment($message, $pid)) {
-        $_SESSION['compose']['attachments'][$id] = $attachment;
-        $cid_map['cid:'.$part->content_id] = 
-	  $OUTPUT->app->comm_path.'&_action=display-attachment&_file=rcmfile'.$id;
-        $id++;
+        $_SESSION['compose']['attachments'][$attachment['id']] = $attachment;
+        $cid_map['cid:'.$part->content_id] = $OUTPUT->app->comm_path.'&_action=display-attachment&_file=rcmfile'.$attachment['id'];
       }
     }
   }
@@ -642,24 +634,22 @@
 
 function rcmail_save_attachment(&$message, $pid)
 {
-  global $RCMAIL;
-
-  $temp_dir = unslashify($RCMAIL->config->get('temp_dir'));
-  $tmp_path = tempnam($temp_dir, 'rcmAttmnt');
   $part = $message->mime_parts[$pid];
   
-  if ($fp = fopen($tmp_path, 'w'))
-  {
-    $message->get_part_content($pid, $fp);
-    fclose($fp);
-
-    return array(
-        'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
-        'name' => $part->filename,
-        'path' => $tmp_path,
-	'content_id' => $part->content_id
-    );
+  $attachment = array(
+    'name' => $part->filename,
+    'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+    'content_id' => $part->content_id,
+    'data' => $message->get_part_content($pid),
+  );
+  
+  $attachment = rcmail::get_instance()->plugins->exec_hook('save_attachment', $attachment);
+  if ($attachment['status']) {
+    unset($attachment['data'], $attachment['status']);
+    return $attachment;
   }
+
+  return false;
 }
 
 
@@ -739,7 +729,7 @@
         html::a(array(
             'href' => "#delete",
             'title' => rcube_label('delete'),
-            'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%d', this)", JS_OBJECT_NAME, $id)),
+            'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id)),
           $button) . Q($a_prop['name']));
     }
   }
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 8931cfa..28ae70c 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -730,71 +730,86 @@
  */
 function rcmail_print_body($part, $p = array())
 {
-  $p += array('safe' => false, 'plain' => false, 'inline_html' => true);
+  global $RCMAIL;
+  
+  // trigger plugin hook
+  $data = $RCMAIL->plugins->exec_hook('message_part_before',
+    array('type' => $part->ctype_secondary, 'body' => $part->body) + $p + array('safe' => false, 'plain' => false, 'inline_html' => true));
 
   // convert html to text/plain
-  if ($part->ctype_secondary == 'html' && $p['plain']) {
-    $txt = new html2text($part->body, false, true);
+  if ($data['type'] == 'html' && $data['plain']) {
+    $txt = new html2text($data['body'], false, true);
     $body = $txt->get_text();
     $part->ctype_secondary = 'plain';
   }
   // text/html
-  else if ($part->ctype_secondary == 'html') {
-    return rcmail_wash_html($part->body, $p, $part->replaces);
+  else if ($data['type'] == 'html') {
+    $body = rcmail_wash_html($data['body'], $data, $part->replaces);
+    $part->ctype_secondary = $data['type'];
   }
   // text/enriched
-  else if ($part->ctype_secondary=='enriched') {
+  else if ($data['type'] == 'enriched') {
     $part->ctype_secondary = 'html';
     require_once('lib/enriched.inc');
-    return Q(enriched_to_html($part->body), 'show');
+    $body = Q(enriched_to_html($data['body']), 'show');
   }
-  else
+  else {
+    // assert plaintext
     $body = $part->body;
-
-
-  /**** assert plaintext ****/
-
-  // make links and email-addresses clickable
-  $replacements = new rcube_string_replacer;
-  
-  $url_chars = 'a-z0-9_\-\+\*\$\/&%=@#:;';
-  $url_chars_within = '\?\.~,!';
-  
-  // search for patterns like links and e-mail addresses
-  $body = preg_replace_callback("/([\w]+):\/\/([a-z0-9\-\.]+[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
-  $body = preg_replace_callback("/([^\/:]|\s)(www\.)([a-z0-9\-]{2,}[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
-  $body = preg_replace_callback('/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i', array($replacements, 'mailto_callback'), $body);
-
-  // split body into single lines
-  $a_lines = preg_split('/\r?\n/', $body);
-  $quote_level = 0;
-
-  // colorize quoted parts
-  for ($n=0; $n < sizeof($a_lines); $n++) {
-    $line = $a_lines[$n];
-    $quotation = '';
-    $q = 0;
-    
-    if (preg_match('/^(>+\s*)+/', $line, $regs)) {
-      $q    = strlen(preg_replace('/\s/', '', $regs[0]));
-      $line = substr($line, strlen($regs[0]));
-
-      if ($q > $quote_level)
-        $quotation = str_repeat('<blockquote>', $q - $quote_level);
-      else if ($q < $quote_level)
-        $quotation = str_repeat("</blockquote>", $quote_level - $q);
-    }
-    else if ($quote_level > 0)
-      $quotation = str_repeat("</blockquote>", $quote_level);
-
-    $quote_level = $q;
-    $a_lines[$n] = $quotation . Q($line, 'replace', false);  // htmlquote plaintext
+    $part->ctype_secondary = $data['type'] = 'plain';
   }
+  
+  // free some memory (hopefully)
+  unset($data['body']);
 
-  // insert the links for urls and mailtos
-  $body = $replacements->resolve(join("\n", $a_lines));
 
-  return html::tag('pre', array(), $body);
+  // plaintext postprocessing
+  if ($part->ctype_secondary == 'plain') {
+    // make links and email-addresses clickable
+    $replacements = new rcube_string_replacer;
+    
+    $url_chars = 'a-z0-9_\-\+\*\$\/&%=@#:;';
+    $url_chars_within = '\?\.~,!';
+
+    // search for patterns like links and e-mail addresses
+    $body = preg_replace_callback("/([\w]+):\/\/([a-z0-9\-\.]+[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
+    $body = preg_replace_callback("/([^\/:]|\s)(www\.)([a-z0-9\-]{2,}[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
+    $body = preg_replace_callback('/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i', array($replacements, 'mailto_callback'), $body);
+
+    // split body into single lines
+    $a_lines = preg_split('/\r?\n/', $body);
+    $quote_level = 0;
+
+    // colorize quoted parts
+    for ($n=0; $n < count($a_lines); $n++) {
+      $line = $a_lines[$n];
+      $quotation = '';
+      $q = 0;
+    
+      if (preg_match('/^(>+\s*)+/', $line, $regs)) {
+        $q    = strlen(preg_replace('/\s/', '', $regs[0]));
+        $line = substr($line, strlen($regs[0]));
+
+        if ($q > $quote_level)
+          $quotation = str_repeat('<blockquote>', $q - $quote_level);
+        else if ($q < $quote_level)
+          $quotation = str_repeat("</blockquote>", $quote_level - $q);
+      }
+      else if ($quote_level > 0)
+        $quotation = str_repeat("</blockquote>", $quote_level);
+
+      $quote_level = $q;
+      $a_lines[$n] = $quotation . Q($line, 'replace', false);  // htmlquote plaintext
+    }
+
+    // insert the links for urls and mailtos
+    $body = $replacements->resolve(join("\n", $a_lines));
+  }
+  
+  // allow post-processing of the message body
+  $data = $RCMAIL->plugins->exec_hook('message_part_after', array('type' => $part->ctype_secondary, 'body' => $body) + $data);
+
+  return $data['type'] == 'html' ? $data['body'] : html::tag('pre', array(), $data['body']);
 }
 
 
@@ -842,7 +857,7 @@
  */
 function rcmail_message_headers($attrib, $headers=NULL)
   {
-  global $IMAP, $OUTPUT, $MESSAGE, $PRINT_MODE, $CONFIG;
+  global $IMAP, $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL;
   static $sa_attrib;
   
   // keep header table attrib
@@ -851,7 +866,6 @@
   else if (!is_array($attrib) && is_array($sa_attrib))
     $attrib = $sa_attrib;
   
-  
   if (!isset($MESSAGE))
     return FALSE;
 
@@ -859,58 +873,55 @@
   if (!$headers)
     $headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers;
     
-  $header_count = 0;
-  
-  // allow the following attributes to be added to the <table> tag
-  $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
-  $out = '<table' . $attrib_str . ">\n";
-
   // show these headers
   $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', 'date');
+  $output_headers = array();
 
-  foreach ($standard_headers as $hkey)
-    {
+  foreach ($standard_headers as $hkey) {
     if (!$headers[$hkey])
       continue;
 
-    if ($hkey == 'date')
-      {
+    if ($hkey == 'date') {
       if ($PRINT_MODE)
-        $header_value = format_date($headers[$hkey], $CONFIG['date_long'] ? $CONFIG['date_long'] : 'x');
+        $header_value = format_date($headers[$hkey], $RCMAIL->config->get('date_long', 'x'));
       else
         $header_value = format_date($headers[$hkey]);
-      }
-    else if ($hkey == 'replyto')
-      {
+    }
+    else if ($hkey == 'replyto') {
       if ($headers['replyto'] != $headers['from'])
-        $header_value = Q(rcmail_address_string($headers['replyto'], null, true, $attrib['addicon']), 'show');
+        $header_value = rcmail_address_string($headers['replyto'], null, true, $attrib['addicon']);
       else
         continue;
-      }
-    else if (in_array($hkey, array('from', 'to', 'cc', 'bcc')))
-      $header_value = Q(rcmail_address_string($headers[$hkey], null, true, $attrib['addicon']), 'show');
-    else if ($hkey == 'subject' && empty($headers[$hkey]))
-      $header_value = Q(rcube_label('nosubject'));
-    else
-      $header_value = Q(trim($IMAP->decode_header($headers[$hkey])));
-
-    $out .= "\n<tr>\n";
-    $out .= '<td class="header-title">'.Q(rcube_label($hkey)).":&nbsp;</td>\n";
-    $out .= '<td class="'.$hkey.'" width="90%">'.$header_value."</td>\n</tr>";
-    $header_count++;
     }
+    else if (in_array($hkey, array('from', 'to', 'cc', 'bcc')))
+      $header_value = rcmail_address_string($headers[$hkey], null, true, $attrib['addicon']);
+    else if ($hkey == 'subject' && empty($headers[$hkey]))
+      $header_value = rcube_label('nosubject');
+    else
+      $header_value = trim($IMAP->decode_header($headers[$hkey]));
+      
+    $output_headers[$hkey] = array('title' => rcube_label($hkey), 'value' => $header_value, 'raw' => $headers[$hkey]);
+  }
+    
+  $plugin = $RCMAIL->plugins->exec_hook('message_headers_output', array('output' => $output_headers, 'headers' => $MESSAGE->headers));
+  
+  // compose html table
+  $table = new html_table(array('cols' => 2));
+  
+  foreach ($plugin['output'] as $hkey => $row) {
+    $table->add(array('class' => 'header-title'), Q($row['title']));
+    $table->add(array('class' => $hkey, 'width' => "90%"), Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show')));
+  }
 
   // all headers division
-  $out .= "\n".'<tr><td colspan="2" class="more-headers show-headers"
-	onclick="return '.JS_OBJECT_NAME.'.command(\'load-headers\', \'\', this)"></td></tr>';
-  $out .= "\n".'<tr id="all-headers"><td colspan="2" class="all"><div id="headers-source"></div></td></tr>';
-
+  $table->add(array('colspan' => 2, 'class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('load-headers','',this)"), '');
+  $table->add_row(array('id' => "all-headers"));
+  $table->add(array('colspan' => 2, 'class' => "all"), html::div(array('id' => 'headers-source'), ''));
+  
   $OUTPUT->add_gui_object('all_headers_row', 'all-headers');
   $OUTPUT->add_gui_object('all_headers_box', 'headers-source');
 
-  $out .= "\n</table>\n\n";
-
-  return $header_count ? $out : '';  
+  return $table->show($attrib);
   }
 
 
@@ -1251,10 +1262,7 @@
   if (!isset($_SESSION['compose']))
     return;
 
-  // remove attachment files from temp dir
-  if (is_array($_SESSION['compose']['attachments']))
-    foreach ($_SESSION['compose']['attachments'] as $attachment)
-      @unlink($attachment['path']);
+  rcmail::get_instance()->plugins->exec_hook('cleanup_attachments',array());
   
   unset($_SESSION['compose']);
   }
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 9607619..34e2c09 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -297,86 +297,92 @@
 // For HTML-formatted messages, construct the MIME message with both
 // the HTML part and the plain-text part
 
-if ($isHtml)
-  {
-  $MAIL_MIME->setHTMLBody($message_body . ($footer ? "\r\n<pre>".$footer.'</pre>' : ''));
+if ($isHtml) {
+  $plugin = $RCMAIL->plugins->exec_hook('outgoing_message_body', array('body' => $message_body, 'type' => 'html', 'message' => $MAIL_MIME));
+  $MAIL_MIME->setHTMLBody($plugin['body'] . ($footer ? "\r\n<pre>".$footer.'</pre>' : ''));
 
   // add a plain text version of the e-mail as an alternative part.
-  $h2t = new html2text($message_body, false, true, 0);
-  $plainTextPart = rc_wordwrap($h2t->get_text(), 75, "\r\n"). ($footer ? "\r\n".$footer : '');
+  $h2t = new html2text($plugin['body'], false, true, 0);
+  $plainTextPart = rc_wordwrap($h2t->get_text(), 75, "\r\n") . ($footer ? "\r\n".$footer : '');
   $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
-  if (!strlen($plainTextPart))
-    { 
+  if (!strlen($plainTextPart)) {
     // empty message body breaks attachment handling in drafts 
     $plainTextPart = "\r\n"; 
-    }
-  $MAIL_MIME->setTXTBody($plainTextPart);
+  }
+  $plugin = $RCMAIL->plugins->exec_hook('outgoing_message_body', array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
+  $MAIL_MIME->setTXTBody($plugin['body']);
 
   // look for "emoticon" images from TinyMCE and copy into message as attachments
   $message_body = rcmail_attach_emoticons($MAIL_MIME);
-  }
+}
 else
   {
   $message_body = rc_wordwrap($message_body, 75, "\r\n");
   if ($footer)
     $message_body .= "\r\n" . $footer;
   $message_body = wordwrap($message_body, 998, "\r\n", true);
-  if (!strlen($message_body))  
-    { 
+  if (!strlen($message_body)) { 
     // empty message body breaks attachment handling in drafts 
     $message_body = "\r\n"; 
-    } 
-  $MAIL_MIME->setTXTBody($message_body, FALSE, TRUE);
   }
+  $plugin = $RCMAIL->plugins->exec_hook('outgoing_message_body', array('body' => $message_body, 'type' => 'plain', 'message' => $MAIL_MIME));
+  $MAIL_MIME->setTXTBody($plugin['body'], false, true);
+}
 
 // chose transfer encoding
 $charset_7bit = array('ASCII', 'ISO-2022-JP', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-15');
 $transfer_encoding = in_array(strtoupper($message_charset), $charset_7bit) ? '7bit' : '8bit';
 
 // add stored attachments, if any
-if (is_array($_SESSION['compose']['attachments']))
-  foreach ($_SESSION['compose']['attachments'] as $id => $attachment)
-  {
-    $dispurl = '/\ssrc\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile' . $id . '[\s\'"]\s*/';
-    $match = preg_match($dispurl, $message_body, $matches);
-    if ($isHtml && ($match > 0))
-    {
+if (is_array($_SESSION['compose']['attachments'])) {
+  foreach ($_SESSION['compose']['attachments'] as $id => $attachment) {
+    // This hook retrieves the attachment contents from the file storage backend
+    $attachment = $RCMAIL->plugins->exec_hook('get_attachment', $attachment);
+
+    $dispurl = '/\ssrc\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\s\'"]\s*/';
+    $message_body = $MAIL_MIME->getHTMLBody();
+    if ($isHtml && (preg_match($dispurl, $message_body) > 0)) {
       $message_body = preg_replace($dispurl, ' src="'.$attachment['name'].'" ', $message_body);
       $MAIL_MIME->setHTMLBody($message_body);
-      $MAIL_MIME->addHTMLImage($attachment['path'], $attachment['mimetype'], $attachment['name']);
+      
+      if ($attachment['data'])
+        $MAIL_MIME->addHTMLImage($attachment['data'], $attachment['mimetype'], $attachment['name'], false);
+      else
+        $MAIL_MIME->addHTMLImage($attachment['path'], $attachment['mimetype'], $attachment['name'], true);
     }
-    else
-    {
+    else {
       $ctype = str_replace('image/pjpeg', 'image/jpeg', $attachment['mimetype']); // #1484914
+      $file = $attachment['data'] ? $attachment['data'] : $attachment['path'];
 
       // .eml attachments send inline
-      $MAIL_MIME->addAttachment($attachment['path'],
+      $MAIL_MIME->addAttachment($file,
         $ctype, 
-        $attachment['name'], true, 
+        $attachment['name'],
+        ($attachment['data'] ? false : true),
         ($ctype == 'message/rfc822' ? $transfer_encoding : 'base64'),
         ($ctype == 'message/rfc822' ? 'inline' : 'attachment'),
         $message_charset, '', '', 
-	$CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL,
-	$CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL
-	);
+        $CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL,
+        $CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL
+      );
     }
   }
+}
 
 // add submitted attachments
-if (is_array($_FILES['_attachments']['tmp_name']))
-  foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath)
-    {
+if (is_array($_FILES['_attachments']['tmp_name'])) {
+  foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
     $ctype = $files['type'][$i];
     $ctype = str_replace('image/pjpeg', 'image/jpeg', $ctype); // #1484914
     
     $MAIL_MIME->addAttachment($filepath, $ctype, $files['name'][$i], true,
-	$ctype == 'message/rfc822' ? $transfer_encoding : 'base64',
-	'attachment', $message_charset, '', '', 
-	$CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL,
-	$CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL
-	);
-    }
-
+      $ctype == 'message/rfc822' ? $transfer_encoding : 'base64',
+      'attachment', $message_charset, '', '', 
+      $CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL,
+      $CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL
+    );
+  }
+}
 
 // encoding settings for mail composing
 $MAIL_MIME->setParam(array(
@@ -388,6 +394,9 @@
   'text_charset'  => $message_charset,
 ));
 
+$data = $RCMAIL->plugins->exec_hook('outgoing_message_headers', array('headers' => $headers));
+$headers = $data['headers'];
+
 // encoding subject header with mb_encode provides better results with asian characters
 if (function_exists("mb_encode_mimeheader"))
 {
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index fd31fa9..9beb425 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -135,9 +135,11 @@
     }
 
   // mark message as read
-  if (!$MESSAGE->headers->seen)
+  if (!$MESSAGE->headers->seen) {
     $IMAP->set_flag($MESSAGE->uid, 'SEEN');
+    $RCMAIL->plugins->exec_hook('message_read', array('uid' => $MESSAGE->uid, 'mailbox' => $IMAP->mailbox, 'message' => $MESSAGE));
   }
+}
 
 
 
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index 3f03577..6eda4db 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -159,6 +159,8 @@
       }
     }
     
+    $RCMAIL->plugins->exec_hook('user_preferences', array('section' => $part, 'table' => $table));
+    
     if ($table->size())
       $out = html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('uisettings'))) . $table->show($attrib));
     break;
@@ -216,6 +218,8 @@
       $table->add(null, $input_check_all->show($config['check_all_folders']?1:0));
     }
 
+    $RCMAIL->plugins->exec_hook('user_preferences', array('section' => $part, 'table' => $table));
+
     if ($table->size())
       $out = html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('mailboxview'))) . $table->show($attrib));
     break;
@@ -253,6 +257,8 @@
       $table->add('title', html::label($field_id, Q(rcube_label('showinlineimages'))));
       $table->add(null, $input_inline_images->show($config['inline_images']?1:0));
     }
+
+    $RCMAIL->plugins->exec_hook('user_preferences', array('section' => $part, 'table' => $table));
 
     if ($table->size())
       $out = html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('messagesdisplaying'))) . $table->show($attrib));
@@ -295,6 +301,8 @@
       $table->add(null, $select_param_folding->show($config['mime_param_folding']));
     }
 
+    $RCMAIL->plugins->exec_hook('user_preferences', array('section' => $part, 'table' => $table));
+
     if ($table->size())
       $out = html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('messagescomposition'))) . $table->show($attrib));
     break;
@@ -328,6 +336,8 @@
         $table->add('title', Q(rcube_label('trash')));
         $table->add(null, $select->show($config['trash_mbox'], array('name' => "_trash_mbox")));
       }
+
+      $RCMAIL->plugins->exec_hook('user_preferences', array('section' => $part, 'table' => $table));
 
       $out = html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('specialfolders'))) . $table->show($attrib));
     }
@@ -381,6 +391,8 @@
       $table->add(null, $input_expunge->show($config['logout_expunge']?1:0));
     }
 
+    $RCMAIL->plugins->exec_hook('user_preferences', array('section' => $part, 'table' => $table));
+
     if ($table->size())
       $out = html::tag('fieldset', null, html::tag('legend', null, Q(rcube_label('serversettings'))) . $table->show($attrib));
     break;
diff --git a/program/steps/settings/manage_folders.inc b/program/steps/settings/manage_folders.inc
index 9affded..79d313c 100644
--- a/program/steps/settings/manage_folders.inc
+++ b/program/steps/settings/manage_folders.inc
@@ -256,6 +256,7 @@
     $a_js_folders['rcmrow'.$idx] = array($folder_utf8, $display_folder, $protected || $folder['virtual']);
   }
 
+  rcmail::get_instance()->plugins->exec_hook('manage_folders', array('table'=>$table));
 
   $OUTPUT->add_gui_object('subscriptionlist', $attrib['id']);
   $OUTPUT->set_env('subscriptionrows', $a_js_folders);
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
index 09cf63d..c5afd5b 100644
--- a/program/steps/settings/save_prefs.inc
+++ b/program/steps/settings/save_prefs.inc
@@ -48,6 +48,9 @@
   'trash_mbox' => get_input_value('_trash_mbox', RCUBE_INPUT_POST),
   );
 
+$data  =  rcmail::get_instance()->plugins->exec_hook('save_preferences', array('prefs' => $a_user_prefs));
+$a_user_prefs = $data['prefs'];
+
 // don't override these parameters
 foreach ((array)$CONFIG['dont_override'] as $p)
   $a_user_prefs[$p] = $CONFIG[$p];
diff --git a/skins/default/common.css b/skins/default/common.css
index 34ea1d2..631321c 100644
--- a/skins/default/common.css
+++ b/skins/default/common.css
@@ -246,6 +246,13 @@
   border: 1px solid #CCCCCC;
 }
 
+#pagecontent
+{
+  position: absolute;
+  top: 95px;
+  left: 20px;
+}
+
 .splitter
 {
   user-select: none;
diff --git a/skins/default/functions.js b/skins/default/functions.js
index 9e71f6f..fd6e612 100644
--- a/skins/default/functions.js
+++ b/skins/default/functions.js
@@ -8,24 +8,16 @@
 
 function rcube_init_settings_tabs()
 {
+  var tab = '#settingstabdefault';
   if (window.rcmail && rcmail.env.action)
-    {
-    var action = rcmail.env.action=='preferences' ? 'default' : (rcmail.env.action.indexOf('identity')>0 ? 'identities' : rcmail.env.action);
-    var tab = document.getElementById('settingstab'+action);
-    }
-  else 
-    var tab = document.getElementById('settingstabdefault');
-  
-  if (tab)
-    tab.className = 'tablink-selected';
+    tab = '#settingstab' + (rcmail.env.action=='preferences' ? 'default' : (rcmail.env.action.indexOf('identity')>0 ? 'identities' : rcmail.env.action.replace(/\./g, '')));
+
+  $(tab).addClass('tablink-selected');
 }
 
 function rcube_show_advanced(visible)
 {
-  var rows = document.getElementsByTagName('TR');
-  for(var i=0; i<rows.length; i++)
-    if(rows[i].className && rows[i].className.match(/advanced/))
-      rows[i].style.display = visible ? (bw.ie ? 'block' : 'table-row') : 'none';
+  $('tr.advanced').css('display', (visible ? (bw.ie ? 'block' : 'table-row') : 'none'));
 }
 
 /**
@@ -128,7 +120,7 @@
 
 function rcube_mail_ui()
 {
-  this.markmenu = new rcube_layer('markmessagemenu');
+  this.markmenu = $('#markmessagemenu');
 }
 
 rcube_mail_ui.prototype = {
@@ -136,24 +128,24 @@
 show_markmenu: function(show)
 {
   if (typeof show == 'undefined')
-    show = this.markmenu.visible ? false : true;
+    show = this.markmenu.is(':visible') ? false : true;
   
   var ref = rcube_find_object('markreadbutton');
   if (show && ref)
-    this.markmenu.move(ref.offsetLeft, ref.offsetTop + ref.offsetHeight);
+    this.markmenu.css({ left:ref.offsetLeft, top:(ref.offsetTop + ref.offsetHeight) });
   
-  this.markmenu.show(show);
+  this.markmenu[show?'show':'hide']();
 },
 
 body_mouseup: function(evt, p)
 {
-  if (this.markmenu && this.markmenu.visible && rcube_event.get_target(evt) != rcube_find_object('markreadbutton'))
+  if (this.markmenu && this.markmenu.is(':visible') && rcube_event.get_target(evt) != rcube_find_object('markreadbutton'))
     this.show_markmenu(false);
 },
 
 body_keypress: function(evt, p)
 {
-  if (rcube_event.get_keycode(evt) == 27 && this.markmenu && this.markmenu.visible)
+  if (rcube_event.get_keycode(evt) == 27 && this.markmenu && this.markmenu.is(':visible'))
     this.show_markmenu(false);
 }
 
diff --git a/skins/default/includes/settingstabs.html b/skins/default/includes/settingstabs.html
index 5121ba1..ce6d234 100644
--- a/skins/default/includes/settingstabs.html
+++ b/skins/default/includes/settingstabs.html
@@ -2,4 +2,6 @@
 <span id="settingstabdefault" class="tablink"><roundcube:button command="preferences" type="link" label="preferences" title="editpreferences" /></span>
 <span id="settingstabfolders" class="tablink"><roundcube:button command="folders" type="link" label="folders" title="managefolders" class="tablink" /></span>
 <span id="settingstabidentities" class="tablink"><roundcube:button command="identities" type="link" label="identities" title="manageidentities" class="tablink" /></span>
+<roundcube:container name="tabs" id="tabsbar" />
+<script type="text/javascript"> if (window.rcmail) rcmail.add_onload(rcube_init_settings_tabs); </script>
 </div>
diff --git a/skins/default/includes/taskbar.html b/skins/default/includes/taskbar.html
index ef1aa82..c2841a6 100644
--- a/skins/default/includes/taskbar.html
+++ b/skins/default/includes/taskbar.html
@@ -1,4 +1,5 @@
 <div id="taskbar">
+<roundcube:container name="taskbar" id="taskbar" />
 <roundcube:button command="mail" label="mail" class="button-mail" />
 <roundcube:button command="addressbook" label="addressbook" class="button-addressbook" />
 <roundcube:button command="settings" label="settings" class="button-settings" />
diff --git a/skins/default/mail.css b/skins/default/mail.css
index 5a4e57b..ab4579e 100644
--- a/skins/default/mail.css
+++ b/skins/default/mail.css
@@ -49,7 +49,7 @@
   top: 32px;
   left: 90px;
   width: auto;
-  visibility: hidden;
+  display: none;
   background-color: #F9F9F9;
   border: 1px solid #CCC;
   padding: 1px;
diff --git a/skins/default/splitter.js b/skins/default/splitter.js
index 3ed0eb6..fae3ca5 100644
--- a/skins/default/splitter.js
+++ b/skins/default/splitter.js
@@ -22,18 +22,18 @@
     this.p2 = document.getElementById(this.p2id);
 
     // create and position the handle for this splitter
-    this.p1pos = rcube_get_object_pos(this.p1, this.relative);
-    this.p2pos = rcube_get_object_pos(this.p2, this.relative);
+    this.p1pos = this.relative ? $(this.p1).position() : $(this.p1).offset();
+    this.p2pos = this.relative ? $(this.p2).position() : $(this.p2).offset();
     
     if (this.horizontal)
       {
-      var top = this.p1pos.y + this.p1.offsetHeight;
+      var top = this.p1pos.top + this.p1.offsetHeight;
       this.layer = new rcube_layer(this.id, {x: 0, y: top, height: 10, 
     	    width: '100%', vis: 1, parent: this.p1.parentNode});
       }
     else
       {
-      var left = this.p1pos.x + this.p1.offsetWidth;
+      var left = this.p1pos.left + this.p1.offsetWidth;
       this.layer = new rcube_layer(this.id, {x: left, y: 0, width: 10, 
     	    height: '100%', vis: 1,  parent: this.p1.parentNode});
       }
@@ -70,18 +70,18 @@
     if (this.horizontal)
       {
       var lh = this.layer.height - this.offset * 2;
-      this.p1.style.height = Math.floor(this.pos - this.p1pos.y - lh / 2) + 'px';
+      this.p1.style.height = Math.floor(this.pos - this.p1pos.top - lh / 2) + 'px';
       this.p2.style.top = Math.ceil(this.pos + lh / 2) + 'px';
-      this.layer.move(this.layer.x, Math.round(this.pos - lh / 2 + 1));	     
+      this.layer.move(this.layer.x, Math.round(this.pos - lh / 2 + 1));
       if (bw.ie)
-	{
+        {
         var new_height = (parseInt(this.p2.parentNode.offsetHeight) - parseInt(this.p2.style.top));
         this.p2.style.height = (new_height > 0 ? new_height : 0) +'px';
         }
       }
     else
       {
-      this.p1.style.width = Math.floor(this.pos - this.p1pos.x - this.layer.width / 2) + 'px';
+      this.p1.style.width = Math.floor(this.pos - this.p1pos.left - this.layer.width / 2) + 'px';
       this.p2.style.left = Math.ceil(this.pos + this.layer.width / 2) + 'px';
       this.layer.move(Math.round(this.pos - this.layer.width / 2 + 1), this.layer.y);
       if (bw.ie)
@@ -94,8 +94,8 @@
    */
   this.onDragStart = function(e)
     {
-    this.p1pos = rcube_get_object_pos(this.p1, this.relative);
-    this.p2pos = rcube_get_object_pos(this.p2, this.relative);
+    this.p1pos = this.relative ? $(this.p1).position() : $(this.p1).offset();
+    this.p2pos = this.relative ? $(this.p2).position() : $(this.p2).offset();
     this.drag_active = true;
     
     // start listening to mousemove events
@@ -119,8 +119,8 @@
         // I don't use the add_listener function for this one because I need to create closures to fetch
         // the position of each iframe when the event is received
         var s = this;
-        var id = iframes[n].id;
-        this.iframe_events[n] = function(e){ e._offset = rcube_get_object_pos(document.getElementById(id)); return s.onDrag(e); }
+        var id = '#'+iframes[n].id;
+        this.iframe_events[n] = function(e){ e._offset = $(id).offset(); return s.onDrag(e); }
 
         if (iframedoc.addEventListener)
           iframedoc.addEventListener('mousemove', this.iframe_events[n], false);
@@ -145,14 +145,14 @@
 
     if (this.relative)
       {
-      var parent = rcube_get_object_pos(this.p1.parentNode);
-      pos.x -= parent.x;
-      pos.y -= parent.y;
+      var parent = $(this.p1.parentNode).offset();
+      pos.x -= parent.left;
+      pos.y -= parent.top;
       }
 
     if (this.horizontal)
       {
-      if (((pos.y - this.layer.height * 1.5) > this.p1pos.y) && ((pos.y + this.layer.height * 1.5) < (this.p2pos.y + this.p2.offsetHeight)))
+      if (((pos.y - this.layer.height * 1.5) > this.p1pos.top) && ((pos.y + this.layer.height * 1.5) < (this.p2pos.top + this.p2.offsetHeight)))
         {
         this.pos = pos.y;
         this.resize();
@@ -160,15 +160,15 @@
       }
     else
       {
-      if (((pos.x - this.layer.width * 1.5) > this.p1pos.x) && ((pos.x + this.layer.width * 1.5) < (this.p2pos.x + this.p2.offsetWidth)))
+      if (((pos.x - this.layer.width * 1.5) > this.p1pos.left) && ((pos.x + this.layer.width * 1.5) < (this.p2pos.left + this.p2.offsetWidth)))
         {
         this.pos = pos.x;
         this.resize();
         }
       }
 
-    this.p1pos = rcube_get_object_pos(this.p1, this.relative);
-    this.p2pos = rcube_get_object_pos(this.p2, this.relative);
+    this.p1pos = this.relative ? $(this.p1).position() : $(this.p1).offset();
+    this.p2pos = this.relative ? $(this.p2).position() : $(this.p2).offset();
     return false;
     };
 
@@ -198,7 +198,7 @@
         if (this.iframe_events[n]) {
           if (iframedoc.removeEventListener)
             iframedoc.removeEventListener('mousemove', this.iframe_events[n], false);
-	  else if (iframedoc.detachEvent)
+          else if (iframedoc.detachEvent)
             iframedoc.detachEvent('onmousemove', this.iframe_events[n]);
           else
             iframedoc['onmousemove'] = null;
diff --git a/skins/default/templates/addressbook.html b/skins/default/templates/addressbook.html
index ce29556..431c058 100644
--- a/skins/default/templates/addressbook.html
+++ b/skins/default/templates/addressbook.html
@@ -7,7 +7,7 @@
 <script type="text/javascript" src="/splitter.js"></script>
 
 <style type="text/css">
-<roundcube:if condition="config:ldap_public == false" />
+<roundcube:if condition="count(env:address_sources) &lt;= 1" />
 #abookcountbar { left: 20px;}
 #mainscreen { left:20px; /* IE hack */ width:expression((parseInt(document.documentElement.clientWidth)-40)+'px') }
 #addresslist { width: <roundcube:exp expression="!empty(cookie:addressviewsplitter) ? cookie:addressviewsplitter-5 : 245" />px; }
@@ -44,7 +44,7 @@
 <roundcube:object name="searchform" id="quicksearchbox" /><roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
 </div>
 
-<roundcube:if condition="config:ldap_public" />
+<roundcube:if condition="count(env:address_sources) &gt; 1" />
 <div id="directorylist">
 <div id="groups-title"><roundcube:label name="groups" /></div>
 <roundcube:object name="directorylist" id="directories-list" />
diff --git a/skins/default/templates/identities.html b/skins/default/templates/identities.html
index 9799bc5..30d33a3 100644
--- a/skins/default/templates/identities.html
+++ b/skins/default/templates/identities.html
@@ -6,7 +6,7 @@
 <link rel="stylesheet" type="text/css" href="/settings.css" />
 <script type="text/javascript" src="/functions.js"></script>
 </head>
-<body onload="rcube_init_settings_tabs()">
+<body>
 
 <roundcube:include file="/includes/taskbar.html" />
 <roundcube:include file="/includes/header.html" />
diff --git a/skins/default/templates/mail.html b/skins/default/templates/mail.html
index 96be0f6..4e1d7ce 100644
--- a/skins/default/templates/mail.html
+++ b/skins/default/templates/mail.html
@@ -119,6 +119,7 @@
 <roundcube:button command="forward" imageSel="/images/buttons/forward_sel.png" imageAct="/images/buttons/forward_act.png" imagePas="/images/buttons/forward_pas.png" width="32" height="32" title="forwardmessage" />
 <roundcube:button command="delete" imageSel="/images/buttons/delete_sel.png" imageAct="/images/buttons/delete_act.png" imagePas="/images/buttons/delete_pas.png" width="32" height="32" title="deletemessage" />
 <roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="printmessage" />
+<roundcube:container name="toolbar" id="messagetoolbar" />
 
 <div id="markmessagemenu">
   <ul class="toolbarmenu">
diff --git a/skins/default/templates/managefolders.html b/skins/default/templates/managefolders.html
index 5da5c22..925bc2c 100644
--- a/skins/default/templates/managefolders.html
+++ b/skins/default/templates/managefolders.html
@@ -6,7 +6,7 @@
 <link rel="stylesheet" type="text/css" href="/settings.css" />
 <script type="text/javascript" src="/functions.js"></script>
 </head>
-<body onload="rcube_init_settings_tabs()">
+<body>
 
 <roundcube:include file="/includes/taskbar.html" />
 <roundcube:include file="/includes/header.html" />
diff --git a/skins/default/templates/message.html b/skins/default/templates/message.html
index b8d66c1..7d42ef7 100644
--- a/skins/default/templates/message.html
+++ b/skins/default/templates/message.html
@@ -36,6 +36,7 @@
 <roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="printmessage" />
 <roundcube:button command="viewsource" imageSel="/images/buttons/source_sel.png" imageAct="/images/buttons/source_act.png" imagePas="/images/buttons/source_pas.png" width="32" height="32" title="viewsource" />
 <roundcube:object name="mailboxlist" type="select" noSelection="moveto" maxlength="25" onchange="rcmail.command('moveto', this.options[this.selectedIndex].value)" class="mboxlist" />
+<roundcube:container name="toolbar" id="messagetoolbar" />
 </div>
 
 <div id="mainscreen">
diff --git a/skins/default/templates/plugin.html b/skins/default/templates/plugin.html
new file mode 100644
index 0000000..9725fe4
--- /dev/null
+++ b/skins/default/templates/plugin.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/<roundcube:var name='env:task'/>.css" />
+<script type="text/javascript" src="/functions.js"></script>
+</head>
+<body>
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:if condition="env:task == 'settings'" />
+  <roundcube:include file="/includes/settingstabs.html" />
+<roundcube:endif />
+
+<div id="pagecontent">
+<roundcube:object name="plugin.body" />
+</div>
+  
+<roundcube:object name="plugin.footer" />
+
+</body>
+</html>
diff --git a/skins/default/templates/settings.html b/skins/default/templates/settings.html
index a3f5298..0abe7fa 100644
--- a/skins/default/templates/settings.html
+++ b/skins/default/templates/settings.html
@@ -6,7 +6,7 @@
 <link rel="stylesheet" type="text/css" href="/settings.css" />
 <script type="text/javascript" src="/functions.js"></script>
 </head>
-<body onload="rcube_init_settings_tabs()">
+<body>
 
 <roundcube:include file="/includes/taskbar.html" />
 <roundcube:include file="/includes/header.html" />
@@ -17,7 +17,7 @@
 <div id="userprefs-box">
 <div id="userprefs-title"><roundcube:label name="userpreferences" /></div>
 
-<div style="padding:15px 0 15px 15px">
+<div id="userprefscontainer" style="padding:15px 0 15px 15px">
 <div class="userprefs-block">
   <roundcube:object name="userprefs" form="form" parts="general,mailbox,mailview" />
 </div>
@@ -25,6 +25,8 @@
   <roundcube:object name="userprefs" form="form" parts="compose,folders,server" />
 </div>
 <div style="clear:left"></div>
+
+<roundcube:container name="userprefs" id="userprefscontainer" />
 </div>
 </div>
 

--
Gitblit v1.9.1