From 0c259682f65eaaf23ea4ccb56a706d6baf3007e4 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Fri, 13 Apr 2012 04:52:02 -0400
Subject: [PATCH] - Merge devel-framework branch, resolved conflicts

---
 program/include/main.inc                  | 2114 ------------
 program/steps/mail/compose.inc            |   16 
 program/include/rcube_ui.php              | 1468 +++++++++
 program/include/rcube_session.php         |   21 
 program/steps/mail/show.inc               |   28 
 program/include/iniset.php                |   60 
 program/include/rcube_base_replacer.php   |  110 
 program/include/rcube_plugin.php          |   16 
 index.php                                 |   27 
 program/include/rcube_shared.inc          |  654 ++--
 program/include/rcube_imap_cache.php      |   69 
 program/steps/mail/func.inc               |   25 
 program/include/rcube_storage.php         |   92 
 program/include/rcube_output_json.php     |   99 
 program/include/rcube_smtp.php            |    6 
 CHANGELOG                                 |   11 
 program/include/html.php                  |   66 
 program/include/rcube_message_part.php    |  103 
 program/include/rcube_plugin_api.php      |   83 
 installer/utils.php                       |   16 
 program/include/rcube_ldap.php            |   32 
 program/include/rcube_string_replacer.php |    6 
 program/include/rcube_message_header.php  |  238 +
 program/steps/addressbook/func.inc        |    2 
 program/include/rcube_user.php            |   36 
 program/include/rcube_message.php         |   18 
 program/include/rcube_spellchecker.php    |   20 
 program/include/rcube_config.php          |   14 
 program/include/rcube_output.php          |  259 +
 program/include/rcube.php                 | 1226 +++++++
 /dev/null                                 |   79 
 program/include/rcube_mdb2.php            |  345 +
 program/include/rcube_addressbook.php     |    7 
 program/include/rcube_cache.php           |   14 
 program/include/rcube_output_html.php     |  646 ++-
 program/steps/utils/error.inc             |   15 
 program/include/rcube_imap.php            |   53 
 program/include/clisetup.php              |    2 
 program/include/rcmail.php                | 1247 ++-----
 program/include/rcube_vcard.php           |    6 
 program/steps/mail/get.inc                |   25 
 program/include/rcube_imap_generic.php    |   49 
 bin/msgexport.sh                          |    2 
 installer/index.php                       |    2 
 program/include/rcube_contacts.php        |   42 
 45 files changed, 5,358 insertions(+), 4,111 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index a074cab..58612d3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,17 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Roundcube Framework:
+    Add possibility to replace IMAP driver with custom class
+    Add IMAP auto-connection feature, improving performance with caching enabled
+    Replace imap_init hook with storage_init (with additional 'driver' argument)
+    Improved performance by caching IMAP server's capabilities in session
+    Unified global functions naming (rcube_ prefix)
+    Move global functions from main.inc and rcube_shared.inc into classes
+    Better classes separation
+
+RELEASE 0.8-rc
+----------------
 - Set flexible width to login form fields (#1488418)
 - Fix re-draw bug on list columns change in IE8 (#1487822)
 - Allow mass-removal of addresses from a group (#1487748)
diff --git a/bin/msgexport.sh b/bin/msgexport.sh
index c876f5f..e6c1801 100755
--- a/bin/msgexport.sh
+++ b/bin/msgexport.sh
@@ -34,7 +34,7 @@
 	$IMAP->set_folder($mbox);
 
     $index = $IMAP->index($mbox, null, 'ASC');
-    $count = $index->countMessages();
+    $count = $index->count();
     $index = $index->get();
 
 	vputs("Getting message list of {$mbox}...");
diff --git a/index.php b/index.php
index beed388..8a7a79f 100644
--- a/index.php
+++ b/index.php
@@ -2,7 +2,7 @@
 /*
  +-------------------------------------------------------------------------+
  | Roundcube Webmail IMAP Client                                           |
- | Version 0.8-svn                                                         |
+ | Version 0.9-svn                                                         |
  |                                                                         |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                         |
  |                                                                         |
@@ -45,7 +45,7 @@
 $RCMAIL = rcmail::get_instance();
 
 // Make the whole PHP output non-cacheable (#1487797)
-send_nocacheing_headers();
+$RCMAIL->output->nocacheing_headers();
 
 // turn on output buffering
 ob_start();
@@ -67,14 +67,14 @@
 }
 
 // error steps
-if ($RCMAIL->action=='error' && !empty($_GET['_code'])) {
+if ($RCMAIL->action == 'error' && !empty($_GET['_code'])) {
   raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
 }
 
 // check if https is required (for login) and redirect if necessary
 if (empty($_SESSION['user_id']) && ($force_https = $RCMAIL->config->get('force_https', false))) {
   $https_port = is_bool($force_https) ? 443 : $force_https;
-  if (!rcube_https_check($https_port)) {
+  if (!rcube_ui::https_check($https_port)) {
     $host  = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
     $host .= ($https_port != 443 ? ':' . $https_port : '');
     header('Location: https://' . $host . $_SERVER['REQUEST_URI']);
@@ -89,15 +89,15 @@
 
 // try to log in
 if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
-  $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(RCUBE_INPUT_POST, 'login');
+  $request_valid = $_SESSION['temp'] && $RCMAIL->check_request(rcube_ui::INPUT_POST, 'login');
 
   // purge the session in case of new login when a session already exists 
   $RCMAIL->kill_session();
 
   $auth = $RCMAIL->plugins->exec_hook('authenticate', array(
     'host' => $RCMAIL->autoselect_host(),
-    'user' => trim(get_input_value('_user', RCUBE_INPUT_POST)),
-    'pass' => get_input_value('_pass', RCUBE_INPUT_POST, true,
+    'user' => trim(rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST)),
+    'pass' => rcube_ui::get_input_value('_pass', rcube_ui::INPUT_POST, true,
        $RCMAIL->config->get('password_charset', 'ISO-8859-1')),
     'cookiecheck' => true,
     'valid' => $request_valid,
@@ -119,11 +119,11 @@
     $RCMAIL->session->set_auth_cookie();
 
     // log successful login
-    rcmail_log_login();
+    $RCMAIL->log_login();
 
     // restore original request parameters
     $query = array();
-    if ($url = get_input_value('_url', RCUBE_INPUT_POST)) {
+    if ($url = rcube_ui::get_input_value('_url', rcube_ui::INPUT_POST)) {
       parse_str($url, $query);
 
       // prevent endless looping on login page
@@ -149,7 +149,7 @@
 }
 
 // end session (after optional referer check)
-else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcube_check_referer())) {
+else if ($RCMAIL->task == 'logout' && isset($_SESSION['user_id']) && (!$RCMAIL->config->get('referer_check') || rcmail::check_referer())) {
   $userdata = array(
     'user' => $_SESSION['username'],
     'host' => $_SESSION['storage_host'],
@@ -172,7 +172,8 @@
 // not logged in -> show login page
 if (empty($RCMAIL->user->ID)) {
   // log session failures
-  if (($task = get_input_value('_task', RCUBE_INPUT_GPC)) && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) {
+  $task = rcube_ui::get_input_value('_task', rcube_ui::INPUT_GPC);
+  if ($task && !in_array($task, array('login','logout')) && !$session_error && ($sess_id = $_COOKIE[ini_get('session.name')])) {
     $RCMAIL->session->log("Aborted session " . $sess_id . "; no valid session data found");
     $session_error = true;
   }
@@ -208,7 +209,7 @@
 
   // check client X-header to verify request origin
   if ($OUTPUT->ajax_call) {
-    if (rc_request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) {
+    if (rcube_request_header('X-Roundcube-Request') != $RCMAIL->get_request_token() && !$RCMAIL->config->get('devel_mode')) {
       header('HTTP/1.1 403 Forbidden');
       die("Invalid Request");
     }
@@ -220,7 +221,7 @@
   }
 
   // check referer if configured
-  if (!$request_check_whitelist[$RCMAIL->action] && $RCMAIL->config->get('referer_check') && !rcube_check_referer()) {
+  if (!$request_check_whitelist[$RCMAIL->action] && $RCMAIL->config->get('referer_check') && !rcmail::check_referer()) {
     raise_error(array(
       'code' => 403,
       'type' => 'php',
diff --git a/installer/index.php b/installer/index.php
index 65e84a3..842b900 100644
--- a/installer/index.php
+++ b/installer/index.php
@@ -53,6 +53,8 @@
 set_include_path($include_path);
 
 require_once 'utils.php';
+require_once 'rcube_shared.inc';
+// deprecated aliases (to be removed)
 require_once 'main.inc';
 
 session_start();
diff --git a/installer/utils.php b/installer/utils.php
index d559df1..ca2577c 100644
--- a/installer/utils.php
+++ b/installer/utils.php
@@ -45,26 +45,16 @@
     include_once $filename. '.php';
 }
 
-
-/**
- * Fake internal error handler to catch errors
- */
-function raise_error($p)
-{
-    $rci = rcube_install::get_instance();
-    $rci->raise_error($p);
-}
-
 /**
  * Local callback function for PEAR errors
  */
-function rcube_pear_error($err)
+function __pear_error($err)
 {
-    raise_error(array(
+    rcmail::raise_error(array(
         'code' => $err->getCode(),
         'message' => $err->getMessage(),
     ));
 }
 
 // set PEAR error handling (will also load the PEAR main class)
-PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error');
+PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, '__pear_error');
diff --git a/program/include/clisetup.php b/program/include/clisetup.php
index c5f8dd1..22ffbbc 100644
--- a/program/include/clisetup.php
+++ b/program/include/clisetup.php
@@ -55,7 +55,7 @@
 			continue;
 
 		$args[$key] = preg_replace(array('/^["\']/', '/["\']$/'), '', $value);
-		
+
 		if ($alias = $aliases[$key])
 			$args[$alias] = $args[$key];
 	}
diff --git a/program/include/html.php b/program/include/html.php
index 0e89d77..305a397 100644
--- a/program/include/html.php
+++ b/program/include/html.php
@@ -277,7 +277,7 @@
         $attrib_arr = array();
         foreach ($attrib as $key => $value) {
             // skip size if not numeric
-            if (($key=='size' && !is_numeric($value))) {
+            if ($key == 'size' && !is_numeric($value)) {
                 continue;
             }
 
@@ -297,16 +297,56 @@
                     $attrib_arr[] = $key . '="' . $key . '"';
                 }
             }
-            else if ($key=='value') {
-                $attrib_arr[] = $key . '="' . Q($value, 'strict', false) . '"';
-            }
             else {
-                $attrib_arr[] = $key . '="' . Q($value) . '"';
+                $attrib_arr[] = $key . '="' . self::quote($value) . '"';
             }
         }
+
         return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
     }
+
+    /**
+     * Convert a HTML attribute string attributes to an associative array (name => value)
+     *
+     * @param string Input string
+     * @return array Key-value pairs of parsed attributes
+     */
+    public static function parse_attrib_string($str)
+    {
+        $attrib = array();
+        $regexp = '/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui';
+
+        preg_match_all($regexp, stripslashes($str), $regs, PREG_SET_ORDER);
+
+        // convert attributes to an associative array (name => value)
+        if ($regs) {
+            foreach ($regs as $attr) {
+                $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
+            }
+        }
+
+        return $attrib;
+    }
+
+    /**
+     * Replacing specials characters in html attribute value
+     *
+     * @param  string  $str  Input string
+     *
+     * @return string  The quoted string
+     */
+    public static function quote($str)
+    {
+        $str = htmlspecialchars($str, ENT_COMPAT, RCMAIL_CHARSET);
+
+        // avoid douple quotation of &
+        // @TODO: get rid of it?
+        $str = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $str);
+
+        return $str;
+    }
 }
+
 
 /**
  * Class to create an HTML input field
@@ -317,9 +357,11 @@
 {
     protected $tagname = 'input';
     protected $type = 'text';
-    protected $allowed = array('type','name','value','size','tabindex',
+    protected $allowed = array(
+        'type','name','value','size','tabindex',
         'autocomplete','checked','onchange','onclick','disabled','readonly',
-        'spellcheck','results','maxlength','src','multiple','placeholder');
+        'spellcheck','results','maxlength','src','multiple','placeholder',
+    );
 
     /**
      * Object constructor
@@ -517,11 +559,11 @@
         }
 
         if (!empty($value) && !preg_match('/mce_editor/', $this->attrib['class'])) {
-            $value = Q($value, 'strict', false);
+            $value = self::quote($value);
         }
 
         return self::tag($this->tagname, $this->attrib, $value,
-	    array_merge(self::$common_attrib, $this->allowed));
+	        array_merge(self::$common_attrib, $this->allowed));
     }
 }
 
@@ -550,7 +592,7 @@
     protected $options = array();
     protected $allowed = array('name','size','tabindex','autocomplete',
 	'multiple','onchange','disabled','rel');
-    
+
     /**
      * Add a new option to this drop-down
      *
@@ -591,8 +633,9 @@
                 'selected' => (in_array($option['value'], $select, true) ||
                   in_array($option['text'], $select, true)) ? 1 : null);
 
-            $this->content .= self::tag('option', $attr, Q($option['text']));
+            $this->content .= self::tag('option', $attr, self::quote($option['text']));
         }
+
         return parent::show();
     }
 }
@@ -803,4 +846,3 @@
     }
 
 }
-
diff --git a/program/include/iniset.php b/program/include/iniset.php
index 5feca7d..3715c21 100644
--- a/program/include/iniset.php
+++ b/program/include/iniset.php
@@ -40,7 +40,7 @@
 }
 
 // application constants
-define('RCMAIL_VERSION', '0.8-svn');
+define('RCMAIL_VERSION', '0.9-svn');
 define('RCMAIL_CHARSET', 'UTF-8');
 define('JS_OBJECT_NAME', 'rcmail');
 define('RCMAIL_START', microtime(true));
@@ -51,11 +51,6 @@
 
 if (!defined('RCMAIL_CONFIG_DIR')) {
     define('RCMAIL_CONFIG_DIR', INSTALL_PATH . 'config');
-}
-
-// make sure path_separator is defined
-if (!defined('PATH_SEPARATOR')) {
-    define('PATH_SEPARATOR', (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') ? ';' : ':');
 }
 
 // RC include folders MUST be included FIRST to avoid other
@@ -80,59 +75,14 @@
     @mb_regex_encoding(RCMAIL_CHARSET);
 }
 
-/**
- * Use PHP5 autoload for dynamic class loading
- * 
- * @todo Make Zend, PEAR etc play with this
- * @todo Make our classes conform to a more straight forward CS.
- */
-function rcube_autoload($classname)
-{
-    $filename = preg_replace(
-        array(
-            '/MDB2_(.+)/',
-            '/Mail_(.+)/',
-            '/Net_(.+)/',
-            '/Auth_(.+)/',
-            '/^html_.+/',
-            '/^utf8$/',
-        ),
-        array(
-            'MDB2/\\1',
-            'Mail/\\1',
-            'Net/\\1',
-            'Auth/\\1',
-            'html',
-            'utf8.class',
-        ),
-        $classname
-    );
+// include global functions
+require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
 
-    if ($fp = @fopen("$filename.php", 'r', true)) {
-        fclose($fp);
-        include_once("$filename.php");
-        return true;
-    }
-
-    return false;
-}
-
+// Register autoloader
 spl_autoload_register('rcube_autoload');
-
-/**
- * Local callback function for PEAR errors
- */
-function rcube_pear_error($err)
-{
-    error_log(sprintf("%s (%s): %s",
-        $err->getMessage(),
-        $err->getCode(),
-        $err->getUserinfo()), 0);
-}
 
 // set PEAR error handling (will also load the PEAR main class)
 PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'rcube_pear_error');
 
-// include global functions
+// backward compatybility (to be removed)
 require_once INSTALL_PATH . 'program/include/main.inc';
-require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
diff --git a/program/include/main.inc b/program/include/main.inc
index 3f50275..791e657 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -5,14 +5,14 @@
  | program/include/main.inc                                              |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
  | See the README file for a full license statement.                     |
  |                                                                       |
  | PURPOSE:                                                              |
- |   Provide basic functions for the webmail package                     |
+ |   Provide deprecated functions aliases for backward compatibility     |
  |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
@@ -23,2224 +23,324 @@
 */
 
 /**
- * Roundcube Webmail common functions
+ * Roundcube Webmail deprecated functions
  *
  * @package Core
  * @author Thomas Bruederli <roundcube@gmail.com>
  */
 
-require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
-
-// define constannts for input reading
-define('RCUBE_INPUT_GET', 0x0101);
-define('RCUBE_INPUT_POST', 0x0102);
-define('RCUBE_INPUT_GPC', 0x0103);
+// constants for input reading
+define('RCUBE_INPUT_GET',  rcube_ui::INPUT_GET);
+define('RCUBE_INPUT_POST', rcube_ui::INPUT_POST);
+define('RCUBE_INPUT_GPC',  rcube_ui::INPUT_GPC);
 
 
-
-/**
- * Return correct name for a specific database table
- *
- * @param string Table name
- * @return string Translated table name
- */
 function get_table_name($table)
-  {
-  global $CONFIG;
+{
+    return rcmail::get_instance()->db->table_name($table);
+}
 
-  // return table name if configured
-  $config_key = 'db_table_'.$table;
-
-  if (strlen($CONFIG[$config_key]))
-    return $CONFIG[$config_key];
-
-  return $table;
-  }
-
-
-/**
- * Return correct name for a specific database sequence
- * (used for Postgres only)
- *
- * @param string Secuence name
- * @return string Translated sequence name
- */
 function get_sequence_name($sequence)
-  {
-  // return sequence name if configured
-  $config_key = 'db_sequence_'.$sequence;
-  $opt = rcmail::get_instance()->config->get($config_key);
+{
+    return rcmail::get_instance()->db->sequence_name($sequence);
+}
 
-  if (!empty($opt))
-    return $opt;
-    
-  return $sequence;
-  }
-
-
-/**
- * Get localized text in the desired language
- * It's a global wrapper for rcmail::gettext()
- *
- * @param mixed Named parameters array or label name
- * @param string Domain to search in (e.g. plugin name)
- * @return string Localized text
- * @see rcmail::gettext()
- */
 function rcube_label($p, $domain=null)
 {
-  return rcmail::get_instance()->gettext($p, $domain);
+    return rcmail::get_instance()->gettext($p, $domain);
 }
 
-
-/**
- * Global wrapper of rcmail::text_exists()
- * to check whether a text label is defined
- *
- * @see rcmail::text_exists()
- */
 function rcube_label_exists($name, $domain=null, &$ref_domain = null)
 {
-  return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
+    return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
 }
 
-
-/**
- * Overwrite action variable
- *
- * @param string New action value
- */
 function rcmail_overwrite_action($action)
-  {
-  $app = rcmail::get_instance();
-  $app->action = $action;
-  $app->output->set_env('action', $action);
-  }
+{
+    rcmail::get_instance()->overwrite_action($action);
+}
 
-
-/**
- * Compose an URL for a specific action
- *
- * @param string  Request action
- * @param array   More URL parameters
- * @param string  Request task (omit if the same)
- * @return The application URL
- */
 function rcmail_url($action, $p=array(), $task=null)
 {
-  $app = rcmail::get_instance();
-  return $app->url((array)$p + array('_action' => $action, 'task' => $task));
+    return rcube_ui::url($action, $p, $task);
 }
 
-
-/**
- * Garbage collector function for temp files.
- * Remove temp files older than two days
- */
 function rcmail_temp_gc()
 {
-  $rcmail = rcmail::get_instance();
-
-  $tmp = unslashify($rcmail->config->get('temp_dir'));
-  $expire = mktime() - 172800;  // expire in 48 hours
-
-  if ($dir = opendir($tmp)) {
-    while (($fname = readdir($dir)) !== false) {
-      if ($fname{0} == '.')
-        continue;
-
-      if (filemtime($tmp.'/'.$fname) < $expire)
-        @unlink($tmp.'/'.$fname);
-    }
-
-    closedir($dir);
-  }
+  $rcmail = rcmail::get_instance()->temp_gc();
 }
 
-
-// Deprecated
 function rcube_charset_convert($str, $from, $to=NULL)
 {
     return rcube_charset::convert($str, $from, $to);
 }
 
-
-// Deprecated
 function rc_detect_encoding($string, $failover='')
 {
     return rcube_charset::detect($string, $failover);
 }
 
-
-// Deprecated
 function rc_utf8_clean($input)
 {
     return rcube_charset::clean($input);
 }
 
-
-/**
- * Convert a variable into a javascript object notation
- *
- * @param mixed Input value
- * @return string Serialized JSON string
- */
 function json_serialize($input)
 {
-    $input = rcube_charset::clean($input);
-
-    // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
-    // that's why we have @ here
-    return @json_encode($input);
+    return rcube_output::json_serialize($input);
 }
 
-
-/**
- * Replacing specials characters to a specific encoding type
- *
- * @param  string  Input string
- * @param  string  Encoding type: text|html|xml|js|url
- * @param  string  Replace mode for tags: show|replace|remove
- * @param  boolean Convert newlines
- * @return string  The quoted string
- */
 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
-  {
-  static $html_encode_arr = false;
-  static $js_rep_table = false;
-  static $xml_rep_table = false;
+{
+    return rcube_ui::rep_specialchars_output($str, $enctype, $mode, $newlines);
+}
 
-  if (!$enctype)
-    $enctype = $OUTPUT->type;
-
-  // encode for HTML output
-  if ($enctype=='html')
-    {
-    if (!$html_encode_arr)
-      {
-      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
-      unset($html_encode_arr['?']);
-      }
-
-    $ltpos = strpos($str, '<');
-    $encode_arr = $html_encode_arr;
-
-    // don't replace quotes and html tags
-    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
-      {
-      unset($encode_arr['"']);
-      unset($encode_arr['<']);
-      unset($encode_arr['>']);
-      unset($encode_arr['&']);
-      }
-    else if ($mode=='remove')
-      $str = strip_tags($str);
-
-    $out = strtr($str, $encode_arr);
-
-    // avoid douple quotation of &
-    $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
-
-    return $newlines ? nl2br($out) : $out;
-    }
-
-  // if the replace tables for XML and JS are not yet defined
-  if ($js_rep_table===false)
-    {
-    $js_rep_table = $xml_rep_table = array();
-    $xml_rep_table['&'] = '&amp;';
-
-    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
-      $xml_rep_table[chr($c)] = "&#$c;";
-
-    $xml_rep_table['"'] = '&quot;';
-    $js_rep_table['"'] = '\\"';
-    $js_rep_table["'"] = "\\'";
-    $js_rep_table["\\"] = "\\\\";
-    // Unicode line and paragraph separators (#1486310)
-    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
-    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
-    }
-
-  // encode for javascript use
-  if ($enctype=='js')
-    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
-
-  // encode for plaintext
-  if ($enctype=='text')
-    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
-
-  if ($enctype=='url')
-    return rawurlencode($str);
-
-  // encode for XML
-  if ($enctype=='xml')
-    return strtr($str, $xml_rep_table);
-
-  // no encoding given -> return original string
-  return $str;
-  }
-  
-/**
- * Quote a given string.
- * Shortcut function for rep_specialchars_output
- *
- * @return string HTML-quoted string
- * @see rep_specialchars_output()
- */
 function Q($str, $mode='strict', $newlines=TRUE)
-  {
-  return rep_specialchars_output($str, 'html', $mode, $newlines);
-  }
+{
+    return rcube_ui::Q($str, $mode, $newlines);
+}
 
-/**
- * Quote a given string for javascript output.
- * Shortcut function for rep_specialchars_output
- * 
- * @return string JS-quoted string
- * @see rep_specialchars_output()
- */
 function JQ($str)
-  {
-  return rep_specialchars_output($str, 'js');
-  }
+{
+    return rcube_ui::JQ($str);
+}
 
-
-/**
- * Read input value and convert it for internal use
- * Performs stripslashes() and charset conversion if necessary
- * 
- * @param  string   Field name to read
- * @param  int      Source to get value from (GPC)
- * @param  boolean  Allow HTML tags in field value
- * @param  string   Charset to convert into
- * @return string   Field value or NULL if not available
- */
 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
 {
-  $value = NULL;
-
-  if ($source == RCUBE_INPUT_GET) {
-    if (isset($_GET[$fname]))
-      $value = $_GET[$fname];
-  }
-  else if ($source == RCUBE_INPUT_POST) {
-    if (isset($_POST[$fname]))
-      $value = $_POST[$fname];
-  }
-  else if ($source == RCUBE_INPUT_GPC) {
-    if (isset($_POST[$fname]))
-      $value = $_POST[$fname];
-    else if (isset($_GET[$fname]))
-      $value = $_GET[$fname];
-    else if (isset($_COOKIE[$fname]))
-      $value = $_COOKIE[$fname];
-  }
-
-  return parse_input_value($value, $allow_html, $charset);
+    return rcube_ui::get_input_value($fname, $source, $allow_html, $charset);
 }
 
-/**
- * Parse/validate input value. See get_input_value()
- * Performs stripslashes() and charset conversion if necessary
- *
- * @param  string   Input value
- * @param  boolean  Allow HTML tags in field value
- * @param  string   Charset to convert into
- * @return string   Parsed value
- */
 function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
 {
-  global $OUTPUT;
-
-  if (empty($value))
-    return $value;
-
-  if (is_array($value)) {
-    foreach ($value as $idx => $val)
-      $value[$idx] = parse_input_value($val, $allow_html, $charset);
-    return $value;
-  }
-
-  // strip single quotes if magic_quotes_sybase is enabled
-  if (ini_get('magic_quotes_sybase'))
-    $value = str_replace("''", "'", $value);
-  // strip slashes if magic_quotes enabled
-  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
-    $value = stripslashes($value);
-
-  // remove HTML tags if not allowed
-  if (!$allow_html)
-    $value = strip_tags($value);
-
-  $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
-
-  // remove invalid characters (#1488124)
-  if ($output_charset == 'UTF-8')
-    $value = rc_utf8_clean($value);
-
-  // convert to internal charset
-  if ($charset && $output_charset)
-    $value = rcube_charset_convert($value, $output_charset, $charset);
-
-  return $value;
+    return rcube_ui::parse_input_value($value, $allow_html, $charset);
 }
 
-/**
- * Convert array of request parameters (prefixed with _)
- * to a regular array with non-prefixed keys.
- *
- * @param  int   Source to get value from (GPC)
- * @return array Hash array with all request parameters
- */
 function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action')
 {
-  $out = array();
-  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
-  foreach ($src as $key => $value) {
-    $fname = $key[0] == '_' ? substr($key, 1) : $key;
-    if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname))
-      $out[$fname] = get_input_value($key, $mode);
-  }
-
-  return $out;
+    return rcube_ui::request2param($mode, $ignore);
 }
 
-/**
- * Remove all non-ascii and non-word chars
- * except ., -, _
- */
-function asciiwords($str, $css_id = false, $replace_with = '')
-{
-  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
-  return preg_replace("/[^$allowed]/i", $replace_with, $str);
-}
-
-/**
- * Convert the given string into a valid HTML identifier
- * Same functionality as done in app.js with rcube_webmail.html_identifier()
- */
 function html_identifier($str, $encode=false)
 {
-  if ($encode)
-    return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
-  else
-    return asciiwords($str, true, '_');
+    return rcube_ui::html_identifier($str, $encode);
 }
 
-/**
- * Remove single and double quotes from given string
- *
- * @param string Input value
- * @return string Dequoted string
- */
-function strip_quotes($str)
-{
-  return str_replace(array("'", '"'), '', $str);
-}
-
-
-/**
- * Remove new lines characters from given string
- *
- * @param string Input value
- * @return string Stripped string
- */
-function strip_newlines($str)
-{
-  return preg_replace('/[\r\n]/', '', $str);
-}
-
-
-/**
- * Create a HTML table based on the given data
- *
- * @param  array  Named table attributes
- * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
- * @param  array  List of cols to show
- * @param  string Name of the identifier col
- * @return string HTML table code
- */
 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
 {
-  global $RCMAIL;
-
-  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
-
-  // add table header
-  if (!$attrib['noheader'])
-    foreach ($a_show_cols as $col)
-      $table->add_header($col, Q(rcube_label($col)));
-
-  $c = 0;
-  if (!is_array($table_data))
-  {
-    $db = $RCMAIL->get_dbh();
-    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
-    {
-      $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col])));
-
-      // format each col
-      foreach ($a_show_cols as $col)
-        $table->add($col, Q($sql_arr[$col]));
-
-      $c++;
-    }
-  }
-  else {
-    foreach ($table_data as $row_data)
-    {
-      $class = !empty($row_data['class']) ? $row_data['class'] : '';
-
-      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class));
-
-      // format each col
-      foreach ($a_show_cols as $col)
-        $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
-
-      $c++;
-    }
-  }
-
-  return $table->show($attrib);
+    return rcube_ui::table_output($attrib, $table_data, $a_show_cols, $id_col);
 }
 
-
-/**
- * Create an edit field for inclusion on a form
- * 
- * @param string col field name
- * @param string value field value
- * @param array attrib HTML element attributes for field
- * @param string type HTML element type (default 'text')
- * @return string HTML field definition
- */
 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
 {
-  static $colcounts = array();
-
-  $fname = '_'.$col;
-  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
-  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
-
-  if ($type == 'checkbox') {
-    $attrib['value'] = '1';
-    $input = new html_checkbox($attrib);
-  }
-  else if ($type == 'textarea') {
-    $attrib['cols'] = $attrib['size'];
-    $input = new html_textarea($attrib);
-  }
-  else if ($type == 'select') {
-    $input = new html_select($attrib);
-    $input->add('---', '');
-    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
-  }
-  else if ($attrib['type'] == 'password') {
-    $input = new html_passwordfield($attrib);
-  }
-  else {
-    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
-        $attrib['type'] = 'text';
-    $input = new html_inputfield($attrib);
-  }
-
-  // use value from post
-  if (isset($_POST[$fname])) {
-    $postvalue = get_input_value($fname, RCUBE_INPUT_POST, true);
-    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
-  }
-
-  $out = $input->show($value);
-
-  return $out;
+  return rcube_ui::get_edit_field($col, $value, $attrib, $type);
 }
 
-
-/**
- * Replace all css definitions with #container [def]
- * and remove css-inlined scripting
- *
- * @param string CSS source code
- * @param string Container ID to use as prefix
- * @return string Modified CSS source
- */
 function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
-  {
-  $last_pos = 0;
-  $replacements = new rcube_string_replacer;
+{
+    return rcube_ui::mod_css_styles($source, $container_id, $allow_remote);
+}
 
-  // ignore the whole block if evil styles are detected
-  $source = rcmail_xss_entity_decode($source);
-  $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
-  $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
-  if (preg_match("/$evilexpr/i", $stripped))
-    return '/* evil! */';
-
-  // cut out all contents between { and }
-  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
-    $styles = substr($source, $pos+1, $pos2-($pos+1));
-
-    // check every line of a style block...
-    if ($allow_remote) {
-      $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
-      foreach ($a_styles as $line) {
-        $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
-        // ... and only allow strict url() values
-        if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) {
-          $a_styles = array('/* evil! */');
-          break;
-        }
-      }
-      $styles = join(";\n", $a_styles);
-    }
-
-    $key = $replacements->add($styles);
-    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
-    $last_pos = $pos+2;
-  }
-
-  // remove html comments and add #container to each tag selector.
-  // also replace body definition because we also stripped off the <body> tag
-  $styles = preg_replace(
-    array(
-      '/(^\s*<!--)|(-->\s*$)/',
-      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
-      '/'.preg_quote($container_id, '/').'\s+body/i',
-    ),
-    array(
-      '',
-      "\\1#$container_id \\2",
-      $container_id,
-    ),
-    $source);
-
-  // put block contents back in
-  $styles = $replacements->resolve($styles);
-
-  return $styles;
-  }
-
-
-/**
- * Decode escaped entities used by known XSS exploits.
- * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
- *
- * @param string CSS content to decode
- * @return string Decoded string
- */
 function rcmail_xss_entity_decode($content)
 {
-  $out = html_entity_decode(html_entity_decode($content));
-  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
-  $out = preg_replace('#/\*.*\*/#Ums', '', $out);
-  return $out;
+    return rcube_ui::xss_entity_decode($content);
 }
 
-
-/**
- * preg_replace_callback callback for rcmail_xss_entity_decode_callback
- *
- * @param array matches result from preg_replace_callback
- * @return string decoded entity
- */ 
-function rcmail_xss_entity_decode_callback($matches)
-{ 
-  return chr(hexdec($matches[1]));
-}
-
-/**
- * Compose a valid attribute string for HTML tags
- *
- * @param array Named tag attributes
- * @param array List of allowed attributes
- * @return string HTML formatted attribute string
- */
 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
-  {
-  // allow the following attributes to be added to the <iframe> tag
-  $attrib_str = '';
-  foreach ($allowed_attribs as $a)
-    if (isset($attrib[$a]))
-      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
-
-  return $attrib_str;
-  }
-
-
-/**
- * Convert a HTML attribute string attributes to an associative array (name => value)
- *
- * @param string Input string
- * @return array Key-value pairs of parsed attributes
- */
-function parse_attrib_string($str)
-  {
-  $attrib = array();
-  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])] = html_entity_decode($attr[3] . $attr[4]);
-    }
-  }
-
-  return $attrib;
-  }
-
-
-/**
- * Improved equivalent to strtotime()
- *
- * @param string Date string
- * @return int 
- */
-function rcube_strtotime($date)
 {
-  // check for MS Outlook vCard date format YYYYMMDD
-  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
-    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
-  }
-  else if (is_numeric($date))
-    return $date;
-
-  // support non-standard "GMTXXXX" literal
-  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
-
-  // if date parsing fails, we have a date in non-rfc format.
-  // remove token from the end and try again
-  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
-    $d = explode(' ', $date);
-    array_pop($d);
-    if (!$d) break;
-    $date = implode(' ', $d);
-  }
-
-  return $ts;
+    return html::attrib_string($attrib, $allowed_attribs);
 }
 
+function parse_attrib_string($str)
+{
+    return html::parse_attrib_string($str);
+}
 
-/**
- * Convert the given date to a human readable form
- * This uses the date formatting properties from config
- *
- * @param mixed  Date representation (string, timestamp or DateTime object)
- * @param string Date format to use
- * @param bool   Enables date convertion according to user timezone
- *
- * @return string Formatted date string
- */
 function format_date($date, $format=NULL, $convert=true)
 {
-  global $RCMAIL, $CONFIG;
-
-  if (is_object($date) && is_a($date, 'DateTime')) {
-    $timestamp = $date->format('U');
-  }
-  else {
-    if (!empty($date))
-      $timestamp = rcube_strtotime($date);
-
-    if (empty($timestamp))
-      return '';
-
-    try {
-      $date = new DateTime("@".$timestamp);
-    }
-    catch (Exception $e) {
-      return '';
-    }
-  }
-
-  if ($convert) {
-    try {
-      // convert to the right timezone
-      $stz = date_default_timezone_get();
-      $tz = new DateTimeZone($RCMAIL->config->get('timezone'));
-      $date->setTimezone($tz);
-      date_default_timezone_set($tz->getName());
-
-      $timestamp = $date->format('U');
-    }
-    catch (Exception $e) {
-    }
-  }
-
-  // define date format depending on current time
-  if (!$format) {
-    $now         = time();
-    $now_date    = getdate($now);
-    $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
-    $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
-
-    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
-      $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
-      $today  = true;
-    }
-    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
-      $format = $RCMAIL->config->get('date_short', 'D H:i');
-    else
-      $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
-  }
-
-  // strftime() format
-  if (preg_match('/%[a-z]+/i', $format)) {
-    $format = strftime($format, $timestamp);
-
-    if ($convert && $stz) {
-      date_default_timezone_set($stz);
-    }
-
-    return $today ? (rcube_label('today') . ' ' . $format) : $format;
-  }
-
-  // parse format string manually in order to provide localized weekday and month names
-  // an alternative would be to convert the date() format string to fit with strftime()
-  $out = '';
-  for ($i=0; $i<strlen($format); $i++) {
-    if ($format[$i]=='\\')  // skip escape chars
-      continue;
-
-    // write char "as-is"
-    if ($format[$i]==' ' || $format{$i-1}=='\\')
-      $out .= $format[$i];
-    // weekday (short)
-    else if ($format[$i]=='D')
-      $out .= rcube_label(strtolower(date('D', $timestamp)));
-    // weekday long
-    else if ($format[$i]=='l')
-      $out .= rcube_label(strtolower(date('l', $timestamp)));
-    // month name (short)
-    else if ($format[$i]=='M')
-      $out .= rcube_label(strtolower(date('M', $timestamp)));
-    // month name (long)
-    else if ($format[$i]=='F')
-      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
-    else if ($format[$i]=='x')
-      $out .= strftime('%x %X', $timestamp);
-    else
-      $out .= date($format[$i], $timestamp);
-  }
-
-  if ($today) {
-    $label = rcube_label('today');
-    // replcae $ character with "Today" label (#1486120)
-    if (strpos($out, '$') !== false) {
-      $out = preg_replace('/\$/', $label, $out, 1);
-    }
-    else {
-      $out = $label . ' ' . $out;
-    }
-  }
-
-  if ($convert && $stz) {
-    date_default_timezone_set($stz);
-  }
-
-  return $out;
+    return rcube_ui::format_date($date, $format, $convert);
 }
 
-
-/**
- * Compose a valid representation of name and e-mail address
- *
- * @param string E-mail address
- * @param string Person name
- * @return string Formatted string
- */
-function format_email_recipient($email, $name='')
-{
-  if ($name && $name != $email) {
-    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
-    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
-  }
-
-  return trim($email);
-}
-
-
-/**
- * Return the mailboxlist in HTML
- *
- * @param array Named parameters
- * @return string HTML code for the gui object
- */
 function rcmail_mailbox_list($attrib)
 {
-  global $RCMAIL;
-  static $a_mailboxes;
-
-  $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
-
-  // add some labels to client
-  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
-
-  $type = $attrib['type'] ? $attrib['type'] : 'ul';
-  unset($attrib['type']);
-
-  if ($type=='ul' && !$attrib['id'])
-    $attrib['id'] = 'rcmboxlist';
-
-  if (empty($attrib['folder_name']))
-    $attrib['folder_name'] = '*';
-
-  // get mailbox list
-  $mbox_name = $RCMAIL->storage->get_folder();
-
-  // build the folders tree
-  if (empty($a_mailboxes)) {
-    // get mailbox list
-    $a_folders = $RCMAIL->storage->list_folders_subscribed('', $attrib['folder_name'], $attrib['folder_filter']);
-    $delimiter = $RCMAIL->storage->get_hierarchy_delimiter();
-    $a_mailboxes = array();
-
-    foreach ($a_folders as $folder)
-      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
-  }
-
-  // allow plugins to alter the folder tree or to localize folder names
-  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array(
-    'list'      => $a_mailboxes,
-    'delimiter' => $delimiter,
-    'type'      => $type,
-    'attribs'   => $attrib,
-  ));
-
-  $a_mailboxes = $hook['list'];
-  $attrib      = $hook['attribs'];
-
-  if ($type == 'select') {
-    $select = new html_select($attrib);
-
-    // add no-selection option
-    if ($attrib['noselection'])
-      $select->add(rcube_label($attrib['noselection']), '');
-
-    rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
-    $out = $select->show($attrib['default']);
-  }
-  else {
-    $js_mailboxlist = array();
-    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
-
-    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
-    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
-    $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']);
-    $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
-  }
-
-  return $out;
+    return rcube_ui::folder_list($attrib);
 }
 
-
-/**
- * Return the mailboxlist as html_select object
- *
- * @param array Named parameters
- * @return html_select HTML drop-down object
- */
-function rcmail_mailbox_select($p = array())
+function rcmail_mailbox_select($attrib = array())
 {
-  global $RCMAIL;
-
-  $p += array('maxlength' => 100, 'realnames' => false);
-  $a_mailboxes = array();
-  $storage = $RCMAIL->get_storage();
-
-  if (empty($p['folder_name'])) {
-    $p['folder_name'] = '*';
-  }
-
-  if ($p['unsubscribed'])
-    $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
-  else
-    $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
-
-  $delimiter = $storage->get_hierarchy_delimiter();
-
-  foreach ($list as $folder) {
-    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
-      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
-  }
-
-  $select = new html_select($p);
-
-  if ($p['noselection'])
-    $select->add($p['noselection'], '');
-
-  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
-
-  return $select;
+    return rcube_ui::folder_selector($attrib);
 }
 
-
-/**
- * Create a hierarchical array of the mailbox list
- * @access private
- * @return void
- */
-function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
-{
-  global $RCMAIL;
-
-  // Handle namespace prefix
-  $prefix = '';
-  if (!$path) {
-    $n_folder = $folder;
-    $folder = $RCMAIL->storage->mod_folder($folder);
-
-    if ($n_folder != $folder) {
-      $prefix = substr($n_folder, 0, -strlen($folder));
-    }
-  }
-
-  $pos = strpos($folder, $delm);
-
-  if ($pos !== false) {
-    $subFolders = substr($folder, $pos+1);
-    $currentFolder = substr($folder, 0, $pos);
-
-    // sometimes folder has a delimiter as the last character
-    if (!strlen($subFolders))
-      $virtual = false;
-    else if (!isset($arrFolders[$currentFolder]))
-      $virtual = true;
-    else
-      $virtual = $arrFolders[$currentFolder]['virtual'];
-  }
-  else {
-    $subFolders = false;
-    $currentFolder = $folder;
-    $virtual = false;
-  }
-
-  $path .= $prefix.$currentFolder;
-
-  if (!isset($arrFolders[$currentFolder])) {
-    $arrFolders[$currentFolder] = array(
-      'id' => $path,
-      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
-      'virtual' => $virtual,
-      'folders' => array());
-  }
-  else
-    $arrFolders[$currentFolder]['virtual'] = $virtual;
-
-  if (strlen($subFolders))
-    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
-}
-
-
-/**
- * Return html for a structured list &lt;ul&gt; for the mailbox tree
- * @access private
- * @return string
- */
-function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
-{
-  global $RCMAIL, $CONFIG;
-
-  $maxlength = intval($attrib['maxlength']);
-  $realnames = (bool)$attrib['realnames'];
-  $msgcounts = $RCMAIL->storage->get_cache('messagecount');
-
-  $out = '';
-  foreach ($arrFolders as $key => $folder) {
-    $title        = null;
-    $folder_class = rcmail_folder_classname($folder['id']);
-    $collapsed    = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
-    $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
-
-    if ($folder_class && !$realnames) {
-      $foldername = rcube_label($folder_class);
-    }
-    else {
-      $foldername = $folder['name'];
-
-      // shorten the folder name to a given length
-      if ($maxlength && $maxlength > 1) {
-        $fname = abbreviate_string($foldername, $maxlength);
-        if ($fname != $foldername)
-          $title = $foldername;
-        $foldername = $fname;
-      }
-    }
-
-    // make folder name safe for ids and class names
-    $folder_id = html_identifier($folder['id'], true);
-    $classes = array('mailbox');
-
-    // set special class for Sent, Drafts, Trash and Junk
-    if ($folder_class)
-      $classes[] = $folder_class;
-
-    if ($folder['id'] == $mbox_name)
-      $classes[] = 'selected';
-
-    if ($folder['virtual'])
-      $classes[] = 'virtual';
-    else if ($unread)
-      $classes[] = 'unread';
-
-    $js_name = JQ($folder['id']);
-    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
-    $link_attrib = $folder['virtual'] ? array() : array(
-      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
-      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
-      'rel' => $folder['id'],
-      'title' => $title,
-    );
-
-    $out .= html::tag('li', array(
-        'id' => "rcmli".$folder_id,
-        'class' => join(' ', $classes),
-        'noclose' => true),
-      html::a($link_attrib, $html_name) .
-      (!empty($folder['folders']) ? html::div(array(
-        'class' => ($collapsed ? 'collapsed' : 'expanded'),
-        'style' => "position:absolute",
-        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
-      ), '&nbsp;') : ''));
-
-    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
-
-    if (!empty($folder['folders'])) {
-      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
-        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
-    }
-
-    $out .= "</li>\n";
-  }
-
-  return $out;
-}
-
-
-/**
- * Return html for a flat list <select> for the mailbox tree
- * @access private
- * @return string
- */
-function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array())
-{
-  global $RCMAIL;
-
-  $out = '';
-
-  foreach ($arrFolders as $key => $folder) {
-    // skip exceptions (and its subfolders)
-    if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
-      continue;
-    }
-
-    // skip folders in which it isn't possible to create subfolders
-    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->storage->folder_attributes($folder['id']))
-        && in_array('\\Noinferiors', $attrs)
-    ) {
-      continue;
-    }
-
-    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
-      $foldername = rcube_label($folder_class);
-    else {
-      $foldername = $folder['name'];
-
-      // shorten the folder name to a given length
-      if ($maxlength && $maxlength>1)
-        $foldername = abbreviate_string($foldername, $maxlength);
-    }
-
-    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
-
-    if (!empty($folder['folders']))
-      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
-        $select, $realnames, $nestLevel+1, $opts);
-  }
-
-  return $out;
-}
-
-
-/**
- * Return internal name for the given folder if it matches the configured special folders
- * @access private
- * @return string
- */
-function rcmail_folder_classname($folder_id)
-{
-  global $CONFIG;
-
-  if ($folder_id == 'INBOX')
-    return 'inbox';
-
-  // for these mailboxes we have localized labels and css classes
-  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
-  {
-    if ($folder_id == $CONFIG[$smbx.'_mbox'])
-      return $smbx;
-  }
-}
-
-
-/**
- * Try to localize the given IMAP folder name.
- * UTF-7 decode it in case no localized text was found
- *
- * @param string Folder name
- * @return string Localized folder name in UTF-8 encoding
- */
 function rcmail_localize_foldername($name)
 {
-  if ($folder_class = rcmail_folder_classname($name))
-    return rcube_label($folder_class);
-  else
-    return rcube_charset_convert($name, 'UTF7-IMAP');
+    return rcube_ui::localize_foldername($name);
 }
-
 
 function rcmail_localize_folderpath($path)
 {
-    global $RCMAIL;
-
-    $protect_folders = $RCMAIL->config->get('protect_default_folders');
-    $default_folders = (array) $RCMAIL->config->get('default_folders');
-    $delimiter       = $RCMAIL->storage->get_hierarchy_delimiter();
-    $path            = explode($delimiter, $path);
-    $result          = array();
-
-    foreach ($path as $idx => $dir) {
-        $directory = implode($delimiter, array_slice($path, 0, $idx+1));
-        if ($protect_folders && in_array($directory, $default_folders)) {
-            unset($result);
-            $result[] = rcmail_localize_foldername($directory);
-        }
-        else {
-            $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
-        }
-    }
-
-    return implode($delimiter, $result);
+    return rcube_ui::localize_folderpath($path);
 }
-
 
 function rcmail_quota_display($attrib)
 {
-  global $OUTPUT;
-
-  if (!$attrib['id'])
-    $attrib['id'] = 'rcmquotadisplay';
-
-  $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
-
-  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
-
-  $quota = rcmail_quota_content($attrib);
-
-  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
-
-  return html::span($attrib, '');
+    return rcube_ui::quota_display($attrib);
 }
 
-
-function rcmail_quota_content($attrib=NULL)
+function rcmail_quota_content($attrib = null)
 {
-  global $RCMAIL;
-
-  $quota = $RCMAIL->storage->get_quota();
-  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
-
-  $quota_result = (array) $quota;
-  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
-
-  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
-    $quota_result['title'] = rcube_label('unlimited');
-    $quota_result['percent'] = 0;
-  }
-  else if ($quota['total']) {
-    if (!isset($quota['percent']))
-      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
-
-    $title = sprintf('%s / %s (%.0f%%)',
-        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
-        $quota_result['percent']);
-
-    $quota_result['title'] = $title;
-
-    if ($attrib['width'])
-      $quota_result['width'] = $attrib['width'];
-    if ($attrib['height'])
-      $quota_result['height']	= $attrib['height'];
-  }
-  else {
-    $quota_result['title'] = rcube_label('unknown');
-    $quota_result['percent'] = 0;
-  }
-
-  return $quota_result;
+    return rcube_ui::quota_content($attrib);
 }
 
-
-/**
- * Outputs error message according to server error/response codes
- *
- * @param string Fallback message label
- * @param string Fallback message label arguments
- *
- * @return void
- */
 function rcmail_display_server_error($fallback=null, $fallback_args=null)
 {
-    global $RCMAIL;
-
-    $err_code = $RCMAIL->storage->get_error_code();
-    $res_code = $RCMAIL->storage->get_response_code();
-
-    if ($err_code < 0) {
-        $RCMAIL->output->show_message('storageerror', 'error');
-    }
-    else if ($res_code == rcube_storage::NOPERM) {
-        $RCMAIL->output->show_message('errornoperm', 'error');
-    }
-    else if ($res_code == rcube_storage::READONLY) {
-        $RCMAIL->output->show_message('errorreadonly', 'error');
-    }
-    else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) {
-        // try to detect access rights problem and display appropriate message
-        if (stripos($err_str, 'Permission denied') !== false)
-            $RCMAIL->output->show_message('errornoperm', 'error');
-        else
-            $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
-    }
-    else if ($fallback) {
-        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
-    }
-
-    return true;
+    rcube_ui::display_server_error($fallback, $fallback_args);
 }
 
-
-/**
- * Generate CSS classes from mimetype and filename extension
- *
- * @param string Mimetype
- * @param string The filename
- * @return string CSS classes separated by space
- */
 function rcmail_filetype2classname($mimetype, $filename)
 {
-  list($primary, $secondary) = explode('/', $mimetype);
-
-  $classes = array($primary ? $primary : 'unknown');
-  if ($secondary) {
-    $classes[] = $secondary;
-  }
-  if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
-    $classes[] = $m[1];
-  }
-
-  return strtolower(join(" ", $classes));
+    return rcube_ui::file2class($mimetype, $filename);
 }
 
-/**
- * Output HTML editor scripts
- *
- * @param string Editor mode
- * @return void
- */
 function rcube_html_editor($mode='')
 {
-  global $RCMAIL;
-
-  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
-
-  if ($hook['abort'])
-    return;
-
-  $lang = strtolower($_SESSION['language']);
-
-  // TinyMCE uses two-letter lang codes, with exception of Chinese
-  if (strpos($lang, 'zh_') === 0)
-    $lang = str_replace('_', '-', $lang);
-  else
-    $lang = substr($lang, 0, 2);
-
-  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
-    $lang = 'en';
-
-  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
-  $RCMAIL->output->include_script('editor.js');
-  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
-    json_encode(array(
-        'mode'       => $mode,
-        'lang'       => $lang,
-        'skin_path'  => $RCMAIL->output->get_skin_path(),
-        'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
-        'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary')),
-    ))), 'docready');
+    rcube_ui::html_editor($mode);
 }
 
-
-/**
- * Replaces TinyMCE's emoticon images with plain-text representation
- *
- * @param string HTML content
- * @return string HTML content
- */
 function rcmail_replace_emoticons($html)
 {
-  $emoticons = array(
-    '8-)' => 'smiley-cool',
-    ':-#' => 'smiley-foot-in-mouth',
-    ':-*' => 'smiley-kiss',
-    ':-X' => 'smiley-sealed',
-    ':-P' => 'smiley-tongue-out',
-    ':-@' => 'smiley-yell',
-    ":'(" => 'smiley-cry',
-    ':-(' => 'smiley-frown',
-    ':-D' => 'smiley-laughing',
-    ':-)' => 'smiley-smile',
-    ':-S' => 'smiley-undecided',
-    ':-$' => 'smiley-embarassed',
-    'O:-)' => 'smiley-innocent',
-    ':-|' => 'smiley-money-mouth',
-    ':-O' => 'smiley-surprised',
-    ';-)' => 'smiley-wink',
-  );
-
-  foreach ($emoticons as $idx => $file) {
-    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
-    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
-    $replace[] = $idx;
-  }
-
-  return preg_replace($search, $replace, $html);
+    return rcube_ui::replace_emoticons($html);
 }
 
-
-/**
- * Send the given message using the configured method
- *
- * @param object $message    Reference to Mail_MIME object
- * @param string $from       Sender address string
- * @param array  $mailto     Array of recipient address strings
- * @param array  $smtp_error SMTP error array (reference)
- * @param string $body_file  Location of file with saved message body (reference),
- *                           used when delay_file_io is enabled
- * @param array  $smtp_opts  SMTP options (e.g. DSN request)
- *
- * @return boolean Send status.
- */
 function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
 {
-  global $CONFIG, $RCMAIL;
-
-  $headers = $message->headers();
-
-  // send thru SMTP server using custom SMTP library
-  if ($CONFIG['smtp_server']) {
-    // generate list of recipients
-    $a_recipients = array($mailto);
-
-    if (strlen($headers['Cc']))
-      $a_recipients[] = $headers['Cc'];
-    if (strlen($headers['Bcc']))
-      $a_recipients[] = $headers['Bcc'];
-
-    // clean Bcc from header for recipients
-    $send_headers = $headers;
-    unset($send_headers['Bcc']);
-    // here too, it because txtHeaders() below use $message->_headers not only $send_headers
-    unset($message->_headers['Bcc']);
-
-    $smtp_headers = $message->txtHeaders($send_headers, true);
-
-    if ($message->getParam('delay_file_io')) {
-      // use common temp dir
-      $temp_dir = $RCMAIL->config->get('temp_dir');
-      $body_file = tempnam($temp_dir, 'rcmMsg');
-      if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
-        raise_error(array('code' => 650, 'type' => 'php',
-            'file' => __FILE__, 'line' => __LINE__,
-            'message' => "Could not create message: ".$mime_result->getMessage()),
-            TRUE, FALSE);
-        return false;
-      }
-      $msg_body = fopen($body_file, 'r');
-    } else {
-      $msg_body = $message->get();
-    }
-
-    // send message
-    if (!is_object($RCMAIL->smtp))
-      $RCMAIL->smtp_init(true);
-
-    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
-    $smtp_response = $RCMAIL->smtp->get_response();
-    $smtp_error = $RCMAIL->smtp->get_error();
-
-    // log error
-    if (!$sent)
-      raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
-                        'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
-  }
-  // send mail using PHP's mail() function
-  else {
-    // unset some headers because they will be added by the mail() function
-    $headers_enc = $message->headers($headers);
-    $headers_php = $message->_headers;
-    unset($headers_php['To'], $headers_php['Subject']);
-
-    // reset stored headers and overwrite
-    $message->_headers = array();
-    $header_str = $message->txtHeaders($headers_php);
-
-    // #1485779
-    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
-      if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
-        $headers_enc['To'] = implode(', ', $m[1]);
-      }
-    }
-
-    $msg_body = $message->get();
-
-    if (PEAR::isError($msg_body))
-      raise_error(array('code' => 650, 'type' => 'php',
-            'file' => __FILE__, 'line' => __LINE__,
-            'message' => "Could not create message: ".$msg_body->getMessage()),
-            TRUE, FALSE);
-    else {
-      $delim   = $RCMAIL->config->header_delimiter();
-      $to      = $headers_enc['To'];
-      $subject = $headers_enc['Subject'];
-      $header_str = rtrim($header_str);
-
-      if ($delim != "\r\n") {
-        $header_str = str_replace("\r\n", $delim, $header_str);
-        $msg_body   = str_replace("\r\n", $delim, $msg_body);
-        $to         = str_replace("\r\n", $delim, $to);
-        $subject    = str_replace("\r\n", $delim, $subject);
-      }
-
-      if (ini_get('safe_mode'))
-        $sent = mail($to, $subject, $msg_body, $header_str);
-      else
-        $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
-    }
-  }
-
-  if ($sent) {
-    $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
-
-    // remove MDN headers after sending
-    unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
-
-    // get all recipients
-    if ($headers['Cc'])
-      $mailto .= $headers['Cc'];
-    if ($headers['Bcc'])
-      $mailto .= $headers['Bcc'];
-    if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
-      $mailto = implode(', ', array_unique($m[1]));
-
-    if ($CONFIG['smtp_log']) {
-      write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
-        $RCMAIL->user->get_username(),
-        $_SERVER['REMOTE_ADDR'],
-        $mailto,
-        !empty($smtp_response) ? join('; ', $smtp_response) : ''));
-    }
-  }
-
-  if (is_resource($msg_body)) {
-    fclose($msg_body);
-  }
-
-  $message->_headers = array();
-  $message->headers($headers);
-
-  return $sent;
+    return rcmail::get_instance()->deliver_message($message, $from, $mailto, $smtp_error, $body_file, $smtp_opts);
 }
 
-
-// Returns unique Message-ID
 function rcmail_gen_message_id()
 {
-  global $RCMAIL;
-
-  $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
-  $domain_part = $RCMAIL->user->get_username('domain');
-
-  // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
-  if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
-    if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
-      && preg_match('/\.[a-z]+$/i', $host)) {
-        $domain_part = $host;
-    }
-    else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
-      && preg_match('/\.[a-z]+$/i', $host)) {
-        $domain_part = $host;
-    }
-  }
-
-  return sprintf('<%s@%s>', $local_part, $domain_part);
+    return rcmail::get_instance()->gen_message_id();
 }
 
-
-// Returns RFC2822 formatted current date in user's timezone
 function rcmail_user_date()
 {
-  global $RCMAIL;
-
-  // get user's timezone
-  try {
-    $tz   = new DateTimeZone($RCMAIL->config->get('timezone'));
-    $date = new DateTime('now', $tz);
-  }
-  catch (Exception $e) {
-    $date = new DateTime();
-  }
-
-  return $date->format('r');
+    return rcmail::get_instance()->user_date();
 }
 
-
-/**
- * Check if we can process not exceeding memory_limit
- *
- * @param integer Required amount of memory
- * @return boolean
- */
 function rcmail_mem_check($need)
 {
-  $mem_limit = parse_bytes(ini_get('memory_limit'));
-  $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
-
-  return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
+    return rcube_ui::mem_check($need);
 }
 
-
-/**
- * Check if working in SSL mode
- *
- * @param integer HTTPS port number
- * @param boolean Enables 'use_https' option checking
- * @return boolean
- */
 function rcube_https_check($port=null, $use_https=true)
 {
-  global $RCMAIL;
-
-  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
-    return true;
-  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
-    return true;
-  if ($port && $_SERVER['SERVER_PORT'] == $port)
-    return true;
-  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
-    return true;
-
-  return false;
+    return rcube_ui::https_check($port, $use_https);
 }
 
-
-/**
- * For backward compatibility.
- *
- * @global rcmail $RCMAIL
- * @param string $var_name Variable name.
- * @return void
- */
 function rcube_sess_unset($var_name=null)
 {
-  global $RCMAIL;
-
-  $RCMAIL->session->remove($var_name);
+    rcmail::get_instance()->session->remove($var_name);
 }
 
-
-/**
- * Replaces hostname variables
- *
- * @param string $name Hostname
- * @param string $host Optional IMAP hostname
- * @return string
- */
 function rcube_parse_host($name, $host='')
 {
-  // %n - host
-  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
-  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
-  $d = preg_replace('/^[^\.]+\./', '', $n);
-  // %h - IMAP host
-  $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host;
-  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
-  $z = preg_replace('/^[^\.]+\./', '', $h);
-  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
-  if ( strpos($name, '%s') !== false ){
-    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
-    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
-      return false;
-  }
-
-  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
-  return $name;
+    return rcmail::parse_host($name, $host);
 }
 
-
-/**
- * E-mail address validation
- *
- * @param string $email Email address
- * @param boolean $dns_check True to check dns
- * @return boolean
- */
 function check_email($email, $dns_check=true)
 {
-  // Check for invalid characters
-  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
-    return false;
-
-  // Check for length limit specified by RFC 5321 (#1486453)
-  if (strlen($email) > 254) 
-    return false;
-
-  $email_array = explode('@', $email);
-
-  // Check that there's one @ symbol
-  if (count($email_array) < 2)
-    return false;
-
-  $domain_part = array_pop($email_array);
-  $local_part = implode('@', $email_array);
-
-  // from PEAR::Validate
-  $regexp = '&^(?:
-	("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| 			 	#1 quoted name
-	([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) 	#2 OR dot-atom (RFC5322)
-	$&xi';
-
-  if (!preg_match($regexp, $local_part))
-    return false;
-
-  // Check domain part
-  if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part))
-    return true; // IP address
-  else {
-    // If not an IP address
-    $domain_array = explode('.', $domain_part);
-    if (sizeof($domain_array) < 2)
-      return false; // Not enough parts to be a valid domain
-
-    foreach ($domain_array as $part)
-      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
-        return false;
-
-    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
-      return true;
-
-    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
-      $lookup = array();
-      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
-      foreach ($lookup as $line) {
-        if (strpos($line, 'MX preference'))
-          return true;
-      }
-      return false;
-    }
-
-    // find MX record(s)
-    if (getmxrr($domain_part, $mx_records))
-      return true;
-
-    // find any DNS record
-    if (checkdnsrr($domain_part, 'ANY'))
-      return true;
-  }
-
-  return false;
+    return rcmail::get_instance()->check_email($email, $dns_check);
 }
 
-/*
- * Idn_to_ascii wrapper.
- * Intl/Idn modules version of this function doesn't work with e-mail address
- */
-function rcube_idn_to_ascii($str)
-{
-  return rcube_idn_convert($str, true);
-}
-
-/*
- * Idn_to_ascii wrapper.
- * Intl/Idn modules version of this function doesn't work with e-mail address
- */
-function rcube_idn_to_utf8($str)
-{
-  return rcube_idn_convert($str, false);
-}
-
-function rcube_idn_convert($input, $is_utf=false)
-{
-  if ($at = strpos($input, '@')) {
-    $user   = substr($input, 0, $at);
-    $domain = substr($input, $at+1);
-  }
-  else {
-    $domain = $input;
-  }
-
-  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
-
-  if ($domain === false) {
-    return '';
-  }
-
-  return $at ? $user . '@' . $domain : $domain;
-}
-
-
-/**
- * Helper class to turn relative urls into absolute ones
- * using a predefined base
- */
-class rcube_base_replacer
-{
-  private $base_url;
-
-  public function __construct($base)
-  {
-    $this->base_url = $base;
-  }
-
-  public function callback($matches)
-  {
-    return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
-  }
-
-  public function replace($body)
-  {
-    return preg_replace_callback(array(
-      '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
-      '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
-      ),
-      array($this, 'callback'), $body);
-  }
-
-  /**
-   * Convert paths like ../xxx to an absolute path using a base url
-   *
-   * @param string $path     Relative path
-   * @param string $base_url Base URL
-   *
-   * @return string Absolute URL
-   */
-  public static function absolute_url($path, $base_url)
-  {
-    $host_url = $base_url;
-    $abs_path = $path;
-
-    // check if path is an absolute URL
-    if (preg_match('/^[fhtps]+:\/\//', $path)) {
-      return $path;
-    }
-
-    // check if path is a content-id scheme
-    if (strpos($path, 'cid:') === 0) {
-      return $path;
-    }
-
-    // cut base_url to the last directory
-    if (strrpos($base_url, '/') > 7) {
-      $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
-      $base_url = substr($base_url, 0, strrpos($base_url, '/'));
-    }
-
-    // $path is absolute
-    if ($path[0] == '/') {
-      $abs_path = $host_url.$path;
-    }
-    else {
-      // strip './' because its the same as ''
-      $path = preg_replace('/^\.\//', '', $path);
-
-      if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
-        foreach ($matches as $a_match) {
-          if (strrpos($base_url, '/')) {
-            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
-          }
-          $path = substr($path, 3);
-        }
-      }
-
-      $abs_path = $base_url.'/'.$path;
-    }
-
-    return $abs_path;
-  }
-}
-
-
-/****** debugging and logging functions ********/
-
-/**
- * Print or write debug messages
- *
- * @param mixed Debug message or data
- * @return void
- */
 function console()
 {
-    $args = func_get_args();
-
-    if (class_exists('rcmail', false)) {
-        $rcmail = rcmail::get_instance();
-        if (is_object($rcmail->plugins)) {
-            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
-            if ($plugin['abort'])
-                return;
-            $args = $plugin['args'];
-        }
-    }
-
-    $msg = array();
-    foreach ($args as $arg)
-        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
-
-    write_log('console', join(";\n", $msg));
+    call_user_func_array(array('rcmail', 'console'), func_get_args());
 }
 
-
-/**
- * Append a line to a logfile in the logs directory.
- * Date will be added automatically to the line.
- *
- * @param $name name of log file
- * @param line Line to append
- * @return void
- */
 function write_log($name, $line)
 {
-  global $CONFIG, $RCMAIL;
-
-  if (!is_string($line))
-    $line = var_export($line, true);
- 
-  if (empty($CONFIG['log_date_format']))
-    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
-  
-  $date = date($CONFIG['log_date_format']);
-  
-  // trigger logging hook
-  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
-    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
-    $name = $log['name'];
-    $line = $log['line'];
-    $date = $log['date'];
-    if ($log['abort'])
-      return true;
-  }
- 
-  if ($CONFIG['log_driver'] == 'syslog') {
-    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
-    syslog($prio, $line);
-    return true;
-  }
-  else {
-    $line = sprintf("[%s]: %s\n", $date, $line);
-
-    // log_driver == 'file' is assumed here
-    if (empty($CONFIG['log_dir']))
-      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
-
-    // try to open specific log file for writing
-    $logfile = $CONFIG['log_dir'].'/'.$name;
-    if ($fp = @fopen($logfile, 'a')) {
-      fwrite($fp, $line);
-      fflush($fp);
-      fclose($fp);
-      return true;
-    }
-    else
-      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
-  }
-
-  return false;
+    return rcmail::write_log($name, $line);
 }
 
-
-/**
- * Write login data (name, ID, IP address) to the 'userlogins' log file.
- *
- * @return void
- */
 function rcmail_log_login()
 {
-  global $RCMAIL;
-
-  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
-    return;
-
-  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
-    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
+    return rcmail::get_instance()->log_login();
 }
 
-
-/**
- * Returns remote IP address and forwarded addresses if found
- *
- * @return string Remote IP address(es)
- */
 function rcmail_remote_ip()
 {
-    $address = $_SERVER['REMOTE_ADDR'];
-
-    // append the NGINX X-Real-IP header, if set
-    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
-        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
-    }
-    // append the X-Forwarded-For header, if set
-    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
-        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
-    }
-
-    if (!empty($remote_ip))
-        $address .= '(' . implode(',', $remote_ip) . ')';
-
-    return $address;
+    return rcmail::remote_ip();
 }
 
-
-/**
- * Check whether the HTTP referer matches the current request
- *
- * @return boolean True if referer is the same host+path, false if not
- */
 function rcube_check_referer()
 {
-  $uri = parse_url($_SERVER['REQUEST_URI']);
-  $referer = parse_url(rc_request_header('Referer'));
-  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
+    return rcmail::check_referer();
 }
 
-
-/**
- * @access private
- * @return mixed
- */
 function rcube_timer()
 {
-  return microtime(true);
+    return rcmail::timer();
 }
 
-
-/**
- * @access private
- * @return void
- */
 function rcube_print_time($timer, $label='Timer', $dest='console')
 {
-  static $print_count = 0;
-
-  $print_count++;
-  $now = rcube_timer();
-  $diff = $now-$timer;
-
-  if (empty($label))
-    $label = 'Timer '.$print_count;
-
-  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
+    rcmail::print_timer($timer, $label, $dest);
 }
 
-
-/**
- * Throw system error and show error page
- *
- * @param array Named parameters
- *  - code: Error code (required)
- *  - type: Error type [php|db|imap|javascript] (required)
- *  - message: Error message
- *  - file: File where error occured
- *  - line: Line where error occured
- * @param boolean True to log the error
- * @param boolean Terminate script execution
- */
-// may be defined in Installer
-if (!function_exists('raise_error')) {
 function raise_error($arg=array(), $log=false, $terminate=false)
 {
-    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
-
-    // report bug (if not incompatible browser)
-    if ($log && $arg['type'] && $arg['message'])
-        rcube_log_bug($arg);
-
-    // display error page and terminate script
-    if ($terminate) {
-        $ERROR_CODE = $arg['code'];
-        $ERROR_MESSAGE = $arg['message'];
-        include INSTALL_PATH . 'program/steps/utils/error.inc';
-        exit;
-    }
-}
+    rcmail::raise_error($arg, $log, $terminate);
 }
 
-
-/**
- * Report error according to configured debug_level
- *
- * @param array Named parameters
- * @return void
- * @see raise_error()
- */
 function rcube_log_bug($arg_arr)
 {
-    global $CONFIG;
-
-    $program = strtoupper($arg_arr['type']);
-    $level   = $CONFIG['debug_level'];
-
-    // disable errors for ajax requests, write to log instead (#1487831)
-    if (($level & 4) && !empty($_REQUEST['_remote'])) {
-        $level = ($level ^ 4) | 1;
-    }
-
-    // write error to local log file
-    if ($level & 1) {
-        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
-        $log_entry = sprintf("%s Error: %s%s (%s %s)",
-            $program,
-            $arg_arr['message'],
-            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
-            $_SERVER['REQUEST_METHOD'],
-            $_SERVER['REQUEST_URI'] . $post_query);
-
-        if (!write_log('errors', $log_entry)) {
-            // send error to PHPs error handler if write_log didn't succeed
-            trigger_error($arg_arr['message']);
-        }
-    }
-
-    // report the bug to the global bug reporting system
-    if ($level & 2) {
-        // TODO: Send error via HTTP
-    }
-
-    // show error if debug_mode is on
-    if ($level & 4) {
-        print "<b>$program Error";
-
-        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
-            print " in $arg_arr[file] ($arg_arr[line])";
-
-        print ':</b>&nbsp;';
-        print nl2br($arg_arr['message']);
-        print '<br />';
-        flush();
-    }
+    rcmail::log_bug($arg_arr);
 }
 
 function rcube_upload_progress()
 {
-    global $RCMAIL;
-
-    $prefix = ini_get('apc.rfc1867_prefix');
-    $params = array(
-        'action' => $RCMAIL->action,
-        'name' => get_input_value('_progress', RCUBE_INPUT_GET),
-    );
-
-    if (function_exists('apc_fetch')) {
-        $status = apc_fetch($prefix . $params['name']);
-
-        if (!empty($status)) {
-            $status['percent'] = round($status['current']/$status['total']*100);
-            $params = array_merge($status, $params);
-        }
-    }
-
-    if (isset($params['percent']))
-        $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
-            'percent' => $params['percent'] . '%',
-            'current' => show_bytes($params['current']),
-            'total'   => show_bytes($params['total'])
-        )));
-
-    $RCMAIL->output->command('upload_progress_update', $params);
-    $RCMAIL->output->send();
+    rcube_ui::upload_progress();
 }
 
 function rcube_upload_init()
 {
-    global $RCMAIL;
-
-    // Enable upload progress bar
-    if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
-        if ($field_name = ini_get('apc.rfc1867_name')) {
-            $RCMAIL->output->set_env('upload_progress_name', $field_name);
-            $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
-        }
-    }
-
-    // find max filesize value
-    $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
-    $max_postsize = parse_bytes(ini_get('post_max_size'));
-    if ($max_postsize && $max_postsize < $max_filesize)
-        $max_filesize = $max_postsize;
-
-    $RCMAIL->output->set_env('max_filesize', $max_filesize);
-    $max_filesize = show_bytes($max_filesize);
-    $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
-        'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
-
-    return $max_filesize;
+    return rcube_ui::upload_init();
 }
 
-/**
- * Initializes client-side autocompletion
- */
 function rcube_autocomplete_init()
 {
-    global $RCMAIL;
-    static $init;
-
-    if ($init)
-        return;
-
-    $init = 1;
-
-    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
-      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
-      if (count($book_types) > 1) {
-        $RCMAIL->output->set_env('autocomplete_threads', $threads);
-        $RCMAIL->output->set_env('autocomplete_sources', $book_types);
-      }
-    }
-
-    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
-    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
-    $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
+    rcube_ui::autocomplete_init();
 }
 
 function rcube_fontdefs($font = null)
 {
-  $fonts = array(
-    'Andale Mono'   => '"Andale Mono",Times,monospace',
-    'Arial'         => 'Arial,Helvetica,sans-serif',
-    'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
-    'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
-    'Courier New'   => '"Courier New",Courier,monospace',
-    'Georgia'       => 'Georgia,Palatino,serif',
-    'Helvetica'     => 'Helvetica,Arial,sans-serif',
-    'Impact'        => 'Impact,Chicago,sans-serif',
-    'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
-    'Terminal'      => 'Terminal,Monaco,monospace',
-    'Times New Roman' => '"Times New Roman",Times,serif',
-    'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
-    'Verdana'       => 'Verdana,Geneva,sans-serif',
-  );
+    return rcube_ui::font_defs($font);
+}
 
-  if ($font)
-    return $fonts[$font];
+function send_nocacheing_headers()
+{
+    return rcmail::get_instance()->output->nocacheing_headers();
+}
 
-  return $fonts;
+function show_bytes($bytes)
+{
+    return rcube_ui::show_bytes($bytes);
+}
+
+function rc_wordwrap($string, $width=75, $break="\n", $cut=false)
+{
+    return rcube_mime::wordwrap($string, $width, $break, $cut);
+}
+
+function rc_request_header($name)
+{
+    return rcube_request_header($name);
+}
+
+function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false)
+{
+    return rcube_mime::file_content_type($path, $name, $failover, $is_stream);
+}
+
+function rc_image_content_type($data)
+{
+    return rcube_mime::image_content_type($data);
 }
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index a867291..bca91e1 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -5,8 +5,8 @@
  | program/include/rcmail.php                                            |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
- | Copyright (C) 2011, Kolab Systems AG                                  |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2011-2012, Kolab Systems AG                             |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -30,7 +30,7 @@
  *
  * @package Core
  */
-class rcmail
+class rcmail extends rcube
 {
   /**
    * Main tasks.
@@ -38,76 +38,6 @@
    * @var array
    */
   static public $main_tasks = array('mail','settings','addressbook','login','logout','utils','dummy');
-
-  /**
-   * Singleton instace of rcmail
-   *
-   * @var rcmail
-   */
-  static private $instance;
-
-  /**
-   * Stores instance of rcube_config.
-   *
-   * @var rcube_config
-   */
-  public $config;
-
-  /**
-   * Stores rcube_user instance.
-   *
-   * @var rcube_user
-   */
-  public $user;
-
-  /**
-   * Instace of database class.
-   *
-   * @var rcube_mdb2
-   */
-  public $db;
-
-  /**
-   * Instace of Memcache class.
-   *
-   * @var rcube_mdb2
-   */
-  public $memcache;
-
-  /**
-   * Instace of rcube_session class.
-   *
-   * @var rcube_session
-   */
-  public $session;
-
-  /**
-   * Instance of rcube_smtp class.
-   *
-   * @var rcube_smtp
-   */
-  public $smtp;
-
-  /**
-   * Instance of rcube_storage class.
-   *
-   * @var rcube_storage
-   */
-  public $storage;
-
-  /**
-   * Instance of rcube_template class.
-   *
-   * @var rcube_template
-   */
-  public $output;
-
-  /**
-   * Instance of rcube_plugin_api.
-   *
-   * @var rcube_plugin_api
-   */
-  public $plugins;
 
   /**
    * Current task.
@@ -124,12 +54,8 @@
   public $action = '';
   public $comm_path = './';
 
-  private $texts;
   private $address_books = array();
-  private $caches = array();
   private $action_map = array();
-  private $shutdown_functions = array();
-  private $expunge_cache = false;
 
 
   /**
@@ -139,7 +65,7 @@
    */
   static function get_instance()
   {
-    if (!self::$instance) {
+    if (!self::$instance || !is_a(self::$instance, 'rcmail')) {
       self::$instance = new rcmail();
       self::$instance->startup();  // init AFTER object was linked with self::$instance
     }
@@ -149,32 +75,12 @@
 
 
   /**
-   * Private constructor
-   */
-  private function __construct()
-  {
-    // load configuration
-    $this->config = new rcube_config();
-
-    register_shutdown_function(array($this, 'shutdown'));
-  }
-
-
-  /**
    * Initial startup function
    * to register session, create database and imap connections
    */
-  private function startup()
+  protected function startup()
   {
-    // initialize syslog
-    if ($this->config->get('log_driver') == 'syslog') {
-      $syslog_id = $this->config->get('syslog_id', 'roundcube');
-      $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
-      openlog($syslog_id, LOG_ODELAY, $syslog_facility);
-    }
-
-    // connect to database
-    $this->get_dbh();
+    $this->init(self::INIT_WITH_DB | self::INIT_WITH_PLUGINS);
 
     // start session
     $this->session_init();
@@ -186,8 +92,8 @@
     $this->session_configure();
 
     // set task and action properties
-    $this->set_task(get_input_value('_task', RCUBE_INPUT_GPC));
-    $this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
+    $this->set_task(rcube_ui::get_input_value('_task', rcube_ui::INPUT_GPC));
+    $this->action = asciiwords(rcube_ui::get_input_value('_action', rcube_ui::INPUT_GPC));
 
     // reset some session parameters when changing task
     if ($this->task != 'utils') {
@@ -203,11 +109,9 @@
     else
       $GLOBALS['OUTPUT'] = $this->load_gui(!empty($_REQUEST['_framed']));
 
-    // create plugin API and load plugins
-    $this->plugins = rcube_plugin_api::get_instance();
-
-    // init plugins
-    $this->plugins->init();
+    // load plugins
+    $this->plugins->init($this, $this->task);
+    $this->plugins->load_plugins((array)$this->config->get('plugins', array()), array('filesystem_attachments', 'jqueryui'));
   }
 
 
@@ -259,144 +163,6 @@
 
 
   /**
-   * Check the given string and return a valid language code
-   *
-   * @param string Language code
-   * @return string Valid language code
-   */
-  private function language_prop($lang)
-  {
-    static $rcube_languages, $rcube_language_aliases;
-
-    // user HTTP_ACCEPT_LANGUAGE if no language is specified
-    if (empty($lang) || $lang == 'auto') {
-       $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
-       $lang = str_replace('-', '_', $accept_langs[0]);
-     }
-
-    if (empty($rcube_languages)) {
-      @include(INSTALL_PATH . 'program/localization/index.inc');
-    }
-
-    // check if we have an alias for that language
-    if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
-      $lang = $rcube_language_aliases[$lang];
-    }
-    // try the first two chars
-    else if (!isset($rcube_languages[$lang])) {
-      $short = substr($lang, 0, 2);
-
-      // check if we have an alias for the short language code
-      if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
-        $lang = $rcube_language_aliases[$short];
-      }
-      // expand 'nn' to 'nn_NN'
-      else if (!isset($rcube_languages[$short])) {
-        $lang = $short.'_'.strtoupper($short);
-      }
-    }
-
-    if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
-      $lang = 'en_US';
-    }
-
-    return $lang;
-  }
-
-
-  /**
-   * Get the current database connection
-   *
-   * @return rcube_mdb2  Database connection object
-   */
-  public function get_dbh()
-  {
-    if (!$this->db) {
-      $config_all = $this->config->all();
-
-      $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
-      $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
-      $this->db->set_debug((bool)$config_all['sql_debug']);
-    }
-
-    return $this->db;
-  }
-
-
-  /**
-   * Get global handle for memcache access
-   *
-   * @return object Memcache
-   */
-  public function get_memcache()
-  {
-    if (!isset($this->memcache)) {
-      // no memcache support in PHP
-      if (!class_exists('Memcache')) {
-        $this->memcache = false;
-        return false;
-      }
-
-      $this->memcache = new Memcache;
-      $this->mc_available = 0;
-      
-      // add alll configured hosts to pool
-      $pconnect = $this->config->get('memcache_pconnect', true);
-      foreach ($this->config->get('memcache_hosts', array()) as $host) {
-        list($host, $port) = explode(':', $host);
-        if (!$port) $port = 11211;
-        $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
-      }
-      
-      // test connection and failover (will result in $this->mc_available == 0 on complete failure)
-      $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
-
-      if (!$this->mc_available)
-        $this->memcache = false;
-    }
-
-    return $this->memcache;
-  }
-  
-  /**
-   * Callback for memcache failure
-   */
-  public function memcache_failure($host, $port)
-  {
-    static $seen = array();
-    
-    // only report once
-    if (!$seen["$host:$port"]++) {
-      $this->mc_available--;
-      raise_error(array('code' => 604, 'type' => 'db',
-        'line' => __LINE__, 'file' => __FILE__,
-        'message' => "Memcache failure on host $host:$port"),
-        true, false);
-    }
-  }
-
-
-  /**
-   * Initialize and get cache object
-   *
-   * @param string $name   Cache identifier
-   * @param string $type   Cache type ('db', 'apc' or 'memcache')
-   * @param int    $ttl    Expiration time for cache items in seconds
-   * @param bool   $packed Enables/disables data serialization
-   *
-   * @return rcube_cache Cache object
-   */
-  public function get_cache($name, $type='db', $ttl=0, $packed=true)
-  {
-    if (!isset($this->caches[$name])) {
-      $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
-    }
-
-    return $this->caches[$name];
-  }
-
-
-  /**
    * Return instance of the internal address book class
    *
    * @param string  Address book identifier
@@ -425,7 +191,7 @@
       $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['storage_host']));
     }
     else if ($id === '0') {
-      $contacts = new rcube_contacts($this->db, $this->user->ID);
+      $contacts = new rcube_contacts($this->db, $this->get_user_id());
     }
     else {
       $plugin = $this->plugins->exec_hook('addressbook_get', array('id' => $id, 'writeable' => $writeable));
@@ -446,7 +212,7 @@
     }
 
     if (!$contacts) {
-      raise_error(array(
+      self::raise_error(array(
         'code' => 700, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Addressbook source ($id) not found!"),
@@ -481,10 +247,10 @@
     // We are using the DB address book
     if ($abook_type != 'ldap') {
       if (!isset($this->address_books['0']))
-        $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
+        $this->address_books['0'] = new rcube_contacts($this->db, $this->get_user_id());
       $list['0'] = array(
         'id'       => '0',
-        'name'     => rcube_label('personaladrbook'),
+        'name'     => $this->gettext('personaladrbook'),
         'groups'   => $this->address_books['0']->groups,
         'readonly' => $this->address_books['0']->readonly,
         'autocomplete' => in_array('sql', $autocomplete),
@@ -532,13 +298,13 @@
    * environment vars according to the current session and configuration
    *
    * @param boolean True if this request is loaded in a (i)frame
-   * @return rcube_template Reference to HTML output object
+   * @return rcube_output_html Reference to HTML output object
    */
   public function load_gui($framed = false)
   {
     // init output page
-    if (!($this->output instanceof rcube_template))
-      $this->output = new rcube_template($this->task, $framed);
+    if (!($this->output instanceof rcube_output_html))
+      $this->output = new rcube_output_html($this->task, $framed);
 
     // set keep-alive/check-recent interval
     if ($this->session && ($keep_alive = $this->session->get_keep_alive())) {
@@ -565,173 +331,14 @@
   /**
    * Create an output object for JSON responses
    *
-   * @return rcube_json_output Reference to JSON output object
+   * @return rcube_output_json Reference to JSON output object
    */
   public function json_init()
   {
-    if (!($this->output instanceof rcube_json_output))
-      $this->output = new rcube_json_output($this->task);
+    if (!($this->output instanceof rcube_output_json))
+      $this->output = new rcube_output_json($this->task);
 
     return $this->output;
-  }
-
-
-  /**
-   * Create SMTP object and connect to server
-   *
-   * @param boolean True if connection should be established
-   */
-  public function smtp_init($connect = false)
-  {
-    $this->smtp = new rcube_smtp();
-
-    if ($connect)
-      $this->smtp->connect();
-  }
-
-
-  /**
-   * Initialize and get storage object
-   *
-   * @return rcube_storage Storage object
-   */
-  public function get_storage()
-  {
-    // already initialized
-    if (!is_object($this->storage)) {
-      $this->storage_init();
-    }
-
-    return $this->storage;
-  }
-
-
-  /**
-   * Connect to the IMAP server with stored session data.
-   *
-   * @return bool True on success, False on error
-   * @deprecated
-   */
-  public function imap_connect()
-  {
-    return $this->storage_connect();
-  }
-
-
-  /**
-   * Initialize IMAP object.
-   *
-   * @deprecated
-   */
-  public function imap_init()
-  {
-    $this->storage_init();
-  }
-
-
-  /**
-   * Initialize storage object
-   */
-  public function storage_init()
-  {
-    // already initialized
-    if (is_object($this->storage)) {
-      return;
-    }
-
-    $driver = $this->config->get('storage_driver', 'imap');
-    $driver_class = "rcube_{$driver}";
-
-    if (!class_exists($driver_class)) {
-      raise_error(array(
-        'code' => 700, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Storage driver class ($driver) not found!"),
-        true, true);
-    }
-
-    // Initialize storage object
-    $this->storage = new $driver_class;
-
-    // for backward compat. (deprecated, will be removed)
-    $this->imap = $this->storage;
-
-    // enable caching of mail data
-    $storage_cache  = $this->config->get("{$driver}_cache");
-    $messages_cache = $this->config->get('messages_cache');
-    // for backward compatybility
-    if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
-        $storage_cache  = 'db';
-        $messages_cache = true;
-    }
-
-    if ($storage_cache)
-        $this->storage->set_caching($storage_cache);
-    if ($messages_cache)
-        $this->storage->set_messages_caching(true);
-
-    // set pagesize from config
-    $pagesize = $this->config->get('mail_pagesize');
-    if (!$pagesize) {
-        $pagesize = $this->config->get('pagesize', 50);
-    }
-    $this->storage->set_pagesize($pagesize);
-
-    // set class options
-    $options = array(
-      'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
-      'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
-      'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
-      'debug'       => (bool) $this->config->get("{$driver}_debug"),
-      'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
-      'timeout'     => (int) $this->config->get("{$driver}_timeout"),
-      'skip_deleted' => (bool) $this->config->get('skip_deleted'),
-      'driver'      => $driver,
-    );
-
-    if (!empty($_SESSION['storage_host'])) {
-      $options['host']     = $_SESSION['storage_host'];
-      $options['user']     = $_SESSION['username'];
-      $options['port']     = $_SESSION['storage_port'];
-      $options['ssl']      = $_SESSION['storage_ssl'];
-      $options['password'] = $this->decrypt($_SESSION['password']);
-      // set 'imap_host' for backwards compatibility
-      $_SESSION[$driver.'_host'] = &$_SESSION['storage_host'];
-    }
-
-    $options = $this->plugins->exec_hook("storage_init", $options);
-
-    $this->storage->set_options($options);
-    $this->set_storage_prop();
-  }
-
-
-  /**
-   * Connect to the mail storage server with stored session data
-   *
-   * @return bool True on success, False on error
-   */
-  public function storage_connect()
-  {
-    $storage = $this->get_storage();
-
-    if ($_SESSION['storage_host'] && !$storage->is_connected()) {
-      $host = $_SESSION['storage_host'];
-      $user = $_SESSION['username'];
-      $port = $_SESSION['storage_port'];
-      $ssl  = $_SESSION['storage_ssl'];
-      $pass = $this->decrypt($_SESSION['password']);
-
-      if (!$storage->connect($host, $user, $pass, $port, $ssl)) {
-        if ($this->output)
-          $this->output->show_message($storage->get_error_code() == -1 ? 'storageerror' : 'sessionerror', 'error');
-      }
-      else {
-        $this->set_storage_prop();
-      }
-    }
-
-    return $storage->is_connected();
   }
 
 
@@ -757,7 +364,7 @@
       ini_set('session.gc_maxlifetime', $lifetime * 2);
     }
 
-    ini_set('session.cookie_secure', rcube_https_check());
+    ini_set('session.cookie_secure', rcube_ui::https_check());
     ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
     ini_set('session.use_cookies', 1);
     ini_set('session.use_only_cookies', 1);
@@ -766,7 +373,7 @@
     // use database for storing session data
     $this->session = new rcube_session($this->get_dbh(), $this->config);
 
-    $this->session->register_gc_handler('rcmail_temp_gc');
+    $this->session->register_gc_handler(array($this, 'temp_gc'));
     $this->session->register_gc_handler(array($this, 'cache_gc'));
 
     // start PHP session (if not in CLI mode)
@@ -842,7 +449,7 @@
       if (!$allowed)
         return false;
       }
-    else if (!empty($config['default_host']) && $host != rcube_parse_host($config['default_host']))
+    else if (!empty($config['default_host']) && $host != self::parse_host($config['default_host']))
       return false;
 
     // parse $host URL
@@ -866,9 +473,9 @@
     // Check if we need to add domain
     if (!empty($config['username_domain']) && strpos($username, '@') === false) {
       if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
-        $username .= '@'.rcube_parse_host($config['username_domain'][$host], $host);
+        $username .= '@'.self::parse_host($config['username_domain'][$host], $host);
       else if (is_string($config['username_domain']))
-        $username .= '@'.rcube_parse_host($config['username_domain'], $host);
+        $username .= '@'.self::parse_host($config['username_domain'], $host);
     }
 
     // Convert username to lowercase. If storage backend
@@ -929,7 +536,7 @@
         $user = $created;
       }
       else {
-        raise_error(array(
+        self::raise_error(array(
           'code' => 620, 'type' => 'php',
           'file' => __FILE__, 'line' => __LINE__,
           'message' => "Failed to create a user record. Maybe aborted by a plugin?"
@@ -937,7 +544,7 @@
       }
     }
     else {
-      raise_error(array(
+      self::raise_error(array(
         'code' => 621, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Access denied for new user $username. 'auto_create_user' is disabled"
@@ -984,28 +591,6 @@
 
 
   /**
-   * Set storage parameters.
-   * This must be done AFTER connecting to the server!
-   */
-  private function set_storage_prop()
-  {
-    $storage = $this->get_storage();
-
-    $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
-
-    if ($default_folders = $this->config->get('default_folders')) {
-      $storage->set_default_folders($default_folders);
-    }
-    if (isset($_SESSION['mbox'])) {
-      $storage->set_folder($_SESSION['mbox']);
-    }
-    if (isset($_SESSION['page'])) {
-      $storage->set_page($_SESSION['page']);
-    }
-  }
-
-
-  /**
    * Auto-select IMAP host based on the posted login information
    *
    * @return string Selected IMAP host
@@ -1016,7 +601,7 @@
     $host = null;
 
     if (is_array($default_host)) {
-      $post_host = get_input_value('_host', RCUBE_INPUT_POST);
+      $post_host = rcube_ui::get_input_value('_host', rcube_ui::INPUT_POST);
 
       // direct match in default_host array
       if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
@@ -1024,7 +609,7 @@
       }
 
       // try to select host by mail domain
-      list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
+      list($user, $domain) = explode('@', rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST));
       if (!empty($domain)) {
         foreach ($default_host as $storage_host => $mail_domains) {
           if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
@@ -1045,174 +630,12 @@
       }
     }
     else if (empty($default_host)) {
-      $host = get_input_value('_host', RCUBE_INPUT_POST);
+      $host = rcube_ui::get_input_value('_host', rcube_ui::INPUT_POST);
     }
     else
-      $host = rcube_parse_host($default_host);
+      $host = self::parse_host($default_host);
 
     return $host;
-  }
-
-
-  /**
-   * Get localized text in the desired language
-   *
-   * @param mixed   $attrib  Named parameters array or label name
-   * @param string  $domain  Label domain (plugin) name
-   *
-   * @return string Localized text
-   */
-  public function gettext($attrib, $domain=null)
-  {
-    // load localization files if not done yet
-    if (empty($this->texts))
-      $this->load_language();
-
-    // extract attributes
-    if (is_string($attrib))
-      $attrib = array('name' => $attrib);
-
-    $name = $attrib['name'] ? $attrib['name'] : '';
-
-    // attrib contain text values: use them from now
-    if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
-        $this->texts[$name] = $setval;
-
-    // check for text with domain
-    if ($domain && ($text = $this->texts[$domain.'.'.$name]))
-      ;
-    // text does not exist
-    else if (!($text = $this->texts[$name])) {
-      return "[$name]";
-    }
-
-    // replace vars in text
-    if (is_array($attrib['vars'])) {
-      foreach ($attrib['vars'] as $var_key => $var_value)
-        $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
-    }
-
-    // format output
-    if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
-      return ucfirst($text);
-    else if ($attrib['uppercase'])
-      return mb_strtoupper($text);
-    else if ($attrib['lowercase'])
-      return mb_strtolower($text);
-
-    return strtr($text, array('\n' => "\n"));
-  }
-
-
-  /**
-   * Check if the given text label exists
-   *
-   * @param string  $name       Label name
-   * @param string  $domain     Label domain (plugin) name or '*' for all domains
-   * @param string  $ref_domain Sets domain name if label is found
-   *
-   * @return boolean True if text exists (either in the current language or in en_US)
-   */
-  public function text_exists($name, $domain = null, &$ref_domain = null)
-  {
-    // load localization files if not done yet
-    if (empty($this->texts))
-      $this->load_language();
-
-    if (isset($this->texts[$name])) {
-        $ref_domain = '';
-        return true;
-    }
-
-    // any of loaded domains (plugins)
-    if ($domain == '*') {
-      foreach ($this->plugins->loaded_plugins() as $domain)
-        if (isset($this->texts[$domain.'.'.$name])) {
-          $ref_domain = $domain;
-          return true;
-        }
-    }
-    // specified domain
-    else if ($domain) {
-      $ref_domain = $domain;
-      return isset($this->texts[$domain.'.'.$name]);
-    }
-
-    return false;
-  }
-
-  /**
-   * Load a localization package
-   *
-   * @param string Language ID
-   */
-  public function load_language($lang = null, $add = array())
-  {
-    $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
-
-    // load localized texts
-    if (empty($this->texts) || $lang != $_SESSION['language']) {
-      $this->texts = array();
-
-      // handle empty lines after closing PHP tag in localization files
-      ob_start();
-
-      // get english labels (these should be complete)
-      @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
-      @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
-
-      if (is_array($labels))
-        $this->texts = $labels;
-      if (is_array($messages))
-        $this->texts = array_merge($this->texts, $messages);
-
-      // include user language files
-      if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
-        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
-        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
-
-        if (is_array($labels))
-          $this->texts = array_merge($this->texts, $labels);
-        if (is_array($messages))
-          $this->texts = array_merge($this->texts, $messages);
-      }
-
-      ob_end_clean();
-
-      $_SESSION['language'] = $lang;
-    }
-
-    // append additional texts (from plugin)
-    if (is_array($add) && !empty($add))
-      $this->texts += $add;
-  }
-
-
-  /**
-   * Read directory program/localization and return a list of available languages
-   *
-   * @return array List of available localizations
-   */
-  public function list_languages()
-  {
-    static $sa_languages = array();
-
-    if (!sizeof($sa_languages)) {
-      @include(INSTALL_PATH . 'program/localization/index.inc');
-
-      if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
-        while (($name = readdir($dh)) !== false) {
-          if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
-            continue;
-
-          if ($label = $rcube_languages[$name])
-            $sa_languages[$name] = $label;
-        }
-        closedir($dh);
-      }
-    }
-
-    return $sa_languages;
   }
 
 
@@ -1260,68 +683,6 @@
 
 
   /**
-   * Function to be executed in script shutdown
-   * Registered with register_shutdown_function()
-   */
-  public function shutdown()
-  {
-    foreach ($this->shutdown_functions as $function)
-      call_user_func($function);
-
-    if (is_object($this->smtp))
-      $this->smtp->disconnect();
-
-    foreach ($this->address_books as $book) {
-      if (is_object($book) && is_a($book, 'rcube_addressbook'))
-        $book->close();
-    }
-
-    foreach ($this->caches as $cache) {
-        if (is_object($cache))
-            $cache->close();
-    }
-
-    if (is_object($this->storage)) {
-        if ($this->expunge_cache)
-            $this->storage->expunge_cache();
-      $this->storage->close();
-  }
-
-    // before closing the database connection, write session data
-    if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
-      session_write_close();
-    }
-
-    // write performance stats to logs/console
-    if ($this->config->get('devel_mode')) {
-      if (function_exists('memory_get_usage'))
-        $mem = show_bytes(memory_get_usage());
-      if (function_exists('memory_get_peak_usage'))
-        $mem .= '/'.show_bytes(memory_get_peak_usage());
-
-      $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
-      if (defined('RCMAIL_START'))
-        rcube_print_time(RCMAIL_START, $log);
-      else
-        console($log);
-    }
-  }
-
-
-  /**
-   * Registers shutdown function to be executed on shutdown.
-   * The functions will be executed before destroying any
-   * objects like smtp, imap, session, etc.
-   *
-   * @param callback Function callback
-   */
-  public function add_shutdown_function($function)
-  {
-    $this->shutdown_functions[] = $function;
-  }
-
-
-  /**
    * Garbage collector for cache entries.
    * Set flag to expunge caches on shutdown
    */
@@ -1342,7 +703,7 @@
   {
     $sess_id = $_COOKIE[ini_get('session.name')];
     if (!$sess_id) $sess_id = session_id();
-    $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->user->ID . $this->config->get('des_key') . $sess_id)));
+    $plugin = $this->plugins->exec_hook('request_token', array('value' => md5('RT' . $this->get_user_id() . $this->config->get('des_key') . $sess_id)));
     return $plugin['value'];
   }
 
@@ -1353,9 +714,9 @@
    * @param int Request method
    * @return boolean True if request token is valid false if not
    */
-  public function check_request($mode = RCUBE_INPUT_POST)
+  public function check_request($mode = rcube_ui::INPUT_POST)
   {
-    $token = get_input_value('_token', $mode);
+    $token = rcube_ui::get_input_value('_token', $mode);
     $sess_id = $_COOKIE[ini_get('session.name')];
     return !empty($sess_id) && $token == $this->get_request_token();
   }
@@ -1382,130 +743,6 @@
       return md5($auth_string);
   }
 
-
-  /**
-   * Encrypt using 3DES
-   *
-   * @param string $clear clear text input
-   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
-   * @param boolean $base64 whether or not to base64_encode() the result before returning
-   *
-   * @return string encrypted text
-   */
-  public function encrypt($clear, $key = 'des_key', $base64 = true)
-  {
-    if (!$clear)
-      return '';
-    /*-
-     * Add a single canary byte to the end of the clear text, which
-     * will help find out how much of padding will need to be removed
-     * upon decryption; see http://php.net/mcrypt_generic#68082
-     */
-    $clear = pack("a*H2", $clear, "80");
-
-    if (function_exists('mcrypt_module_open') &&
-        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
-    {
-      $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
-      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
-      $cipher = $iv . mcrypt_generic($td, $clear);
-      mcrypt_generic_deinit($td);
-      mcrypt_module_close($td);
-    }
-    else {
-      @include_once 'des.inc';
-
-      if (function_exists('des')) {
-        $des_iv_size = 8;
-        $iv = $this->create_iv($des_iv_size);
-        $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
-      }
-      else {
-        raise_error(array(
-          'code' => 500, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
-        ), true, true);
-      }
-    }
-
-    return $base64 ? base64_encode($cipher) : $cipher;
-  }
-
-  /**
-   * Decrypt 3DES-encrypted string
-   *
-   * @param string $cipher encrypted text
-   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
-   * @param boolean $base64 whether or not input is base64-encoded
-   *
-   * @return string decrypted text
-   */
-  public function decrypt($cipher, $key = 'des_key', $base64 = true)
-  {
-    if (!$cipher)
-      return '';
-
-    $cipher = $base64 ? base64_decode($cipher) : $cipher;
-
-    if (function_exists('mcrypt_module_open') &&
-        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")))
-    {
-      $iv_size = mcrypt_enc_get_iv_size($td);
-      $iv = substr($cipher, 0, $iv_size);
-
-      // session corruption? (#1485970)
-      if (strlen($iv) < $iv_size)
-        return '';
-
-      $cipher = substr($cipher, $iv_size);
-      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
-      $clear = mdecrypt_generic($td, $cipher);
-      mcrypt_generic_deinit($td);
-      mcrypt_module_close($td);
-    }
-    else {
-      @include_once 'des.inc';
-
-      if (function_exists('des')) {
-        $des_iv_size = 8;
-        $iv = substr($cipher, 0, $des_iv_size);
-        $cipher = substr($cipher, $des_iv_size);
-        $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
-      }
-      else {
-        raise_error(array(
-          'code' => 500, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
-        ), true, true);
-      }
-    }
-
-    /*-
-     * Trim PHP's padding and the canary byte; see note in
-     * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
-     */
-    $clear = substr(rtrim($clear, "\0"), 0, -1);
-
-    return $clear;
-  }
-
-  /**
-   * Generates encryption initialization vector (IV)
-   *
-   * @param int Vector size
-   * @return string Vector string
-   */
-  private function create_iv($size)
-  {
-    // mcrypt_create_iv() can be slow when system lacks entrophy
-    // we'll generate IV vector manually
-    $iv = '';
-    for ($i = 0; $i < $size; $i++)
-        $iv .= chr(mt_rand(0, 255));
-    return $iv;
-  }
 
   /**
    * Build a valid URL to this instance of Roundcube
@@ -1536,50 +773,36 @@
 
 
   /**
-   * Construct shell command, execute it and return output as string.
-   * Keywords {keyword} are replaced with arguments
-   *
-   * @param $cmd Format string with {keywords} to be replaced
-   * @param $values (zero, one or more arrays can be passed)
-   * @return output of command. shell errors not detectable
+   * Function to be executed in script shutdown
    */
-  public static function exec(/* $cmd, $values1 = array(), ... */)
+  public function shutdown()
   {
-    $args = func_get_args();
-    $cmd = array_shift($args);
-    $values = $replacements = array();
+    parent::shutdown();
 
-    // merge values into one array
-    foreach ($args as $arg)
-      $values += (array)$arg;
-
-    preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
-    foreach ($matches as $tags) {
-      list(, $tag, $option, $key) = $tags;
-      $parts = array();
-
-      if ($option) {
-        foreach ((array)$values["-$key"] as $key => $value) {
-          if ($value === true || $value === false || $value === null)
-            $parts[] = $value ? $key : "";
-          else foreach ((array)$value as $val)
-            $parts[] = "$key " . escapeshellarg($val);
-        }
-      }
-      else {
-        foreach ((array)$values[$key] as $value)
-          $parts[] = escapeshellarg($value);
-      }
-
-      $replacements[$tag] = join(" ", $parts);
+    foreach ($this->address_books as $book) {
+      if (is_object($book) && is_a($book, 'rcube_addressbook'))
+        $book->close();
     }
 
-    // use strtr behaviour of going through source string once
-    $cmd = strtr($cmd, $replacements);
+    // before closing the database connection, write session data
+    if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
+      session_write_close();
+    }
 
-    return (string)shell_exec($cmd);
+    // write performance stats to logs/console
+    if ($this->config->get('devel_mode')) {
+      if (function_exists('memory_get_usage'))
+        $mem = rcube_ui::show_bytes(memory_get_usage());
+      if (function_exists('memory_get_peak_usage'))
+        $mem .= '/'.rcube_ui::show_bytes(memory_get_peak_usage());
+
+      $log = $this->task . ($this->action ? '/'.$this->action : '') . ($mem ? " [$mem]" : '');
+      if (defined('RCMAIL_START'))
+        self::print_timer(RCMAIL_START, $log);
+      else
+        self::console($log);
+    }
   }
-
 
   /**
    * Helper method to set a cookie with the current path and host settings
@@ -1596,7 +819,7 @@
     $cookie = session_get_cookie_params();
 
     setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
-      rcube_https_check(), true);
+      rcube_ui::https_check(), true);
   }
 
   /**
@@ -1727,4 +950,356 @@
     $this->set_storage_prop();
   }
 
+
+    /**
+     * Overwrite action variable
+     *
+     * @param string New action value
+     */
+    public function overwrite_action($action)
+    {
+        $this->action = $action;
+        $this->output->set_env('action', $action);
+    }
+
+
+    /**
+     * Send the given message using the configured method.
+     *
+     * @param object $message    Reference to Mail_MIME object
+     * @param string $from       Sender address string
+     * @param array  $mailto     Array of recipient address strings
+     * @param array  $smtp_error SMTP error array (reference)
+     * @param string $body_file  Location of file with saved message body (reference),
+     *                           used when delay_file_io is enabled
+     * @param array  $smtp_opts  SMTP options (e.g. DSN request)
+     *
+     * @return boolean Send status.
+     */
+    public function deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file = null, $smtp_opts = null)
+    {
+        $headers = $message->headers();
+
+        // send thru SMTP server using custom SMTP library
+        if ($this->config->get('smtp_server')) {
+            // generate list of recipients
+            $a_recipients = array($mailto);
+
+            if (strlen($headers['Cc']))
+                $a_recipients[] = $headers['Cc'];
+            if (strlen($headers['Bcc']))
+                $a_recipients[] = $headers['Bcc'];
+
+            // clean Bcc from header for recipients
+            $send_headers = $headers;
+            unset($send_headers['Bcc']);
+            // here too, it because txtHeaders() below use $message->_headers not only $send_headers
+            unset($message->_headers['Bcc']);
+
+            $smtp_headers = $message->txtHeaders($send_headers, true);
+
+            if ($message->getParam('delay_file_io')) {
+                // use common temp dir
+                $temp_dir = $this->config->get('temp_dir');
+                $body_file = tempnam($temp_dir, 'rcmMsg');
+                if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
+                    self::raise_error(array('code' => 650, 'type' => 'php',
+                        'file' => __FILE__, 'line' => __LINE__,
+                        'message' => "Could not create message: ".$mime_result->getMessage()),
+                        TRUE, FALSE);
+                    return false;
+                }
+                $msg_body = fopen($body_file, 'r');
+            }
+            else {
+                $msg_body = $message->get();
+            }
+
+            // send message
+            if (!is_object($this->smtp)) {
+                $this->smtp_init(true);
+            }
+
+            $sent = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
+            $smtp_response = $this->smtp->get_response();
+            $smtp_error = $this->smtp->get_error();
+
+            // log error
+            if (!$sent) {
+                self::raise_error(array('code' => 800, 'type' => 'smtp',
+                    'line' => __LINE__, 'file' => __FILE__,
+                    'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
+            }
+        }
+        // send mail using PHP's mail() function
+        else {
+            // unset some headers because they will be added by the mail() function
+            $headers_enc = $message->headers($headers);
+            $headers_php = $message->_headers;
+            unset($headers_php['To'], $headers_php['Subject']);
+
+            // reset stored headers and overwrite
+            $message->_headers = array();
+            $header_str = $message->txtHeaders($headers_php);
+
+            // #1485779
+            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+                if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
+                    $headers_enc['To'] = implode(', ', $m[1]);
+                }
+            }
+
+            $msg_body = $message->get();
+
+            if (PEAR::isError($msg_body)) {
+                self::raise_error(array('code' => 650, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Could not create message: ".$msg_body->getMessage()),
+                    TRUE, FALSE);
+            }
+            else {
+                $delim   = $this->config->header_delimiter();
+                $to      = $headers_enc['To'];
+                $subject = $headers_enc['Subject'];
+                $header_str = rtrim($header_str);
+
+                if ($delim != "\r\n") {
+                    $header_str = str_replace("\r\n", $delim, $header_str);
+                    $msg_body   = str_replace("\r\n", $delim, $msg_body);
+                    $to         = str_replace("\r\n", $delim, $to);
+                    $subject    = str_replace("\r\n", $delim, $subject);
+                }
+
+                if (ini_get('safe_mode'))
+                    $sent = mail($to, $subject, $msg_body, $header_str);
+                else
+                    $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
+            }
+        }
+
+        if ($sent) {
+            $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
+
+            // remove MDN headers after sending
+            unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
+
+            // get all recipients
+            if ($headers['Cc'])
+                $mailto .= $headers['Cc'];
+            if ($headers['Bcc'])
+                $mailto .= $headers['Bcc'];
+            if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
+                $mailto = implode(', ', array_unique($m[1]));
+
+            if ($this->config->get('smtp_log')) {
+                self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
+                    $this->user->get_username(),
+                    $_SERVER['REMOTE_ADDR'],
+                    $mailto,
+                    !empty($smtp_response) ? join('; ', $smtp_response) : ''));
+            }
+        }
+
+        if (is_resource($msg_body)) {
+            fclose($msg_body);
+        }
+
+        $message->_headers = array();
+        $message->headers($headers);
+
+        return $sent;
+    }
+
+
+    /**
+     * Unique Message-ID generator.
+     *
+     * @return string Message-ID
+     */
+    public function gen_message_id()
+    {
+        $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
+        $domain_part = $this->user->get_username('domain');
+
+        // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
+        if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
+            foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
+                $host = preg_replace('/:[0-9]+$/', '', $host);
+                if ($host && preg_match('/\.[a-z]+$/i', $host)) {
+                    $domain_part = $host;
+                }
+            }
+        }
+
+        return sprintf('<%s@%s>', $local_part, $domain_part);
+    }
+
+
+    /**
+     * Returns RFC2822 formatted current date in user's timezone
+     *
+     * @return string Date
+     */
+    public function user_date()
+    {
+        // get user's timezone
+        try {
+            $tz   = new DateTimeZone($this->config->get('timezone'));
+            $date = new DateTime('now', $tz);
+        }
+        catch (Exception $e) {
+            $date = new DateTime();
+        }
+
+        return $date->format('r');
+    }
+
+
+    /**
+     * E-mail address validation.
+     *
+     * @param string $email Email address
+     * @param boolean $dns_check True to check dns
+     *
+     * @return boolean True on success, False if address is invalid
+     */
+    public function check_email($email, $dns_check=true)
+    {
+        // Check for invalid characters
+        if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email)) {
+            return false;
+        }
+
+        // Check for length limit specified by RFC 5321 (#1486453)
+        if (strlen($email) > 254) {
+            return false;
+        }
+
+        $email_array = explode('@', $email);
+
+        // Check that there's one @ symbol
+        if (count($email_array) < 2) {
+            return false;
+        }
+
+        $domain_part = array_pop($email_array);
+        $local_part  = implode('@', $email_array);
+
+        // from PEAR::Validate
+        $regexp = '&^(?:
+	        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| 			 	            #1 quoted name
+	        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) 	#2 OR dot-atom (RFC5322)
+	        $&xi';
+
+        if (!preg_match($regexp, $local_part)) {
+            return false;
+        }
+
+        // Check domain part
+        if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) {
+            return true; // IP address
+        }
+        else {
+            // If not an IP address
+            $domain_array = explode('.', $domain_part);
+            // Not enough parts to be a valid domain
+            if (sizeof($domain_array) < 2) {
+                return false;
+            }
+
+            foreach ($domain_array as $part) {
+                if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) {
+                    return false;
+                }
+            }
+
+            if (!$dns_check || !$this->config->get('email_dns_check')) {
+                return true;
+            }
+
+            if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
+                $lookup = array();
+                @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
+                foreach ($lookup as $line) {
+                    if (strpos($line, 'MX preference')) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            // find MX record(s)
+            if (getmxrr($domain_part, $mx_records)) {
+                return true;
+            }
+
+            // find any DNS record
+            if (checkdnsrr($domain_part, 'ANY')) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Write login data (name, ID, IP address) to the 'userlogins' log file.
+     */
+    public function log_login()
+    {
+        if (!$this->config->get('log_logins')) {
+            return;
+        }
+
+        $user_name = $this->get_user_name();
+        $user_id   = $this->get_user_id();
+
+        if (!$user_id) {
+            return;
+        }
+
+        self::write_log('userlogins',
+            sprintf('Successful login for %s (ID: %d) from %s in session %s',
+                $user_name, $user_id, self::remote_ip(), session_id()));
+    }
+
+
+    /**
+     * Check whether the HTTP referer matches the current request
+     *
+     * @return boolean True if referer is the same host+path, false if not
+     */
+    public static function check_referer()
+    {
+        $uri = parse_url($_SERVER['REQUEST_URI']);
+        $referer = parse_url(rcube_request_header('Referer'));
+        return $referer['host'] == rcube_request_header('Host') && $referer['path'] == $uri['path'];
+    }
+
+
+    /**
+     * Garbage collector function for temp files.
+     * Remove temp files older than two days
+     */
+    public function temp_gc()
+    {
+        $tmp = unslashify($this->config->get('temp_dir'));
+        $expire = mktime() - 172800;  // expire in 48 hours
+
+        if ($dir = opendir($tmp)) {
+            while (($fname = readdir($dir)) !== false) {
+                if ($fname{0} == '.') {
+                    continue;
+                }
+
+                if (filemtime($tmp.'/'.$fname) < $expire) {
+                    @unlink($tmp.'/'.$fname);
+                }
+            }
+
+            closedir($dir);
+        }
+    }
+
 }
diff --git a/program/include/rcube.php b/program/include/rcube.php
new file mode 100644
index 0000000..bee2986
--- /dev/null
+++ b/program/include/rcube.php
@@ -0,0 +1,1226 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcmail.php                                            |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2011-2012, Kolab Systems AG                             |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Framework base class providing core functions and holding           |
+ |   instances of all 'global' objects like db- and storage-connections  |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+
+/**
+ * Base class of the Roundcube Framework
+ * implemented as singleton
+ *
+ * @package Core
+ */
+class rcube
+{
+  const INIT_WITH_DB = 1;
+  const INIT_WITH_PLUGINS = 2;
+
+  /**
+   * Singleton instace of rcmail
+   *
+   * @var rcmail
+   */
+  static protected $instance;
+
+  /**
+   * Stores instance of rcube_config.
+   *
+   * @var rcube_config
+   */
+  public $config;
+
+  /**
+   * Instace of database class.
+   *
+   * @var rcube_mdb2
+   */
+  public $db;
+
+  /**
+   * Instace of Memcache class.
+   *
+   * @var rcube_mdb2
+   */
+  public $memcache;
+
+  /**
+   * Instace of rcube_session class.
+   *
+   * @var rcube_session
+   */
+  public $session;
+
+  /**
+   * Instance of rcube_smtp class.
+   *
+   * @var rcube_smtp
+   */
+  public $smtp;
+
+  /**
+   * Instance of rcube_storage class.
+   *
+   * @var rcube_storage
+   */
+  public $storage;
+
+  /**
+   * Instance of rcube_output class.
+   *
+   * @var rcube_output
+   */
+  public $output;
+
+  /**
+   * Instance of rcube_plugin_api.
+   *
+   * @var rcube_plugin_api
+   */
+  public $plugins;
+
+
+  /* private/protected vars */
+  protected $texts;
+  protected $caches = array();
+  protected $shutdown_functions = array();
+  protected $expunge_cache = false;
+
+
+  /**
+   * This implements the 'singleton' design pattern
+   *
+   * @return rcmail The one and only instance
+   */
+  static function get_instance()
+  {
+    if (!self::$instance) {
+      self::$instance = new rcube();
+    }
+
+    return self::$instance;
+  }
+
+
+  /**
+   * Private constructor
+   */
+  protected function __construct()
+  {
+    // load configuration
+    $this->config = new rcube_config();
+    $this->plugins = new rcube_dummy_plugin_api;
+
+    register_shutdown_function(array($this, 'shutdown'));
+  }
+
+
+  /**
+   * Initial startup function
+   */
+  protected function init($mode = 0)
+  {
+    // initialize syslog
+    if ($this->config->get('log_driver') == 'syslog') {
+      $syslog_id = $this->config->get('syslog_id', 'roundcube');
+      $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
+      openlog($syslog_id, LOG_ODELAY, $syslog_facility);
+    }
+
+    // connect to database
+    if ($mode & self::INIT_WITH_DB) {
+      $this->get_dbh();
+    }
+
+    // create plugin API and load plugins
+    if ($mode & self::INIT_WITH_PLUGINS) {
+      $this->plugins = rcube_plugin_api::get_instance();
+    }
+  }
+
+
+  /**
+   * Get the current database connection
+   *
+   * @return rcube_mdb2  Database connection object
+   */
+  public function get_dbh()
+  {
+    if (!$this->db) {
+      $config_all = $this->config->all();
+
+      $this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
+      $this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
+      $this->db->set_debug((bool)$config_all['sql_debug']);
+    }
+
+    return $this->db;
+  }
+
+
+  /**
+   * Get global handle for memcache access
+   *
+   * @return object Memcache
+   */
+  public function get_memcache()
+  {
+    if (!isset($this->memcache)) {
+      // no memcache support in PHP
+      if (!class_exists('Memcache')) {
+        $this->memcache = false;
+        return false;
+      }
+
+      $this->memcache = new Memcache;
+      $this->mc_available = 0;
+
+      // add alll configured hosts to pool
+      $pconnect = $this->config->get('memcache_pconnect', true);
+      foreach ($this->config->get('memcache_hosts', array()) as $host) {
+        list($host, $port) = explode(':', $host);
+        if (!$port) $port = 11211;
+        $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
+      }
+
+      // test connection and failover (will result in $this->mc_available == 0 on complete failure)
+      $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
+
+      if (!$this->mc_available)
+        $this->memcache = false;
+    }
+
+    return $this->memcache;
+  }
+
+
+  /**
+   * Callback for memcache failure
+   */
+  public function memcache_failure($host, $port)
+  {
+    static $seen = array();
+
+    // only report once
+    if (!$seen["$host:$port"]++) {
+      $this->mc_available--;
+      self::raise_error(array('code' => 604, 'type' => 'db',
+        'line' => __LINE__, 'file' => __FILE__,
+        'message' => "Memcache failure on host $host:$port"),
+        true, false);
+    }
+  }
+
+
+  /**
+   * Initialize and get cache object
+   *
+   * @param string $name   Cache identifier
+   * @param string $type   Cache type ('db', 'apc' or 'memcache')
+   * @param int    $ttl    Expiration time for cache items in seconds
+   * @param bool   $packed Enables/disables data serialization
+   *
+   * @return rcube_cache Cache object
+   */
+  public function get_cache($name, $type='db', $ttl=0, $packed=true)
+  {
+    if (!isset($this->caches[$name])) {
+      $this->caches[$name] = new rcube_cache($type, $_SESSION['user_id'], $name, $ttl, $packed);
+    }
+
+    return $this->caches[$name];
+  }
+
+
+  /**
+   * Create SMTP object and connect to server
+   *
+   * @param boolean True if connection should be established
+   */
+  public function smtp_init($connect = false)
+  {
+    $this->smtp = new rcube_smtp();
+
+    if ($connect)
+      $this->smtp->connect();
+  }
+
+
+  /**
+   * Initialize and get storage object
+   *
+   * @return rcube_storage Storage object
+   */
+  public function get_storage()
+  {
+    // already initialized
+    if (!is_object($this->storage)) {
+      $this->storage_init();
+    }
+
+    return $this->storage;
+  }
+
+
+  /**
+   * Initialize storage object
+   */
+  public function storage_init()
+  {
+    // already initialized
+    if (is_object($this->storage)) {
+      return;
+    }
+
+    $driver = $this->config->get('storage_driver', 'imap');
+    $driver_class = "rcube_{$driver}";
+
+    if (!class_exists($driver_class)) {
+      self::raise_error(array(
+        'code' => 700, 'type' => 'php',
+        'file' => __FILE__, 'line' => __LINE__,
+        'message' => "Storage driver class ($driver) not found!"),
+        true, true);
+    }
+
+    // Initialize storage object
+    $this->storage = new $driver_class;
+
+    // for backward compat. (deprecated, will be removed)
+    $this->imap = $this->storage;
+
+    // enable caching of mail data
+    $storage_cache  = $this->config->get("{$driver}_cache");
+    $messages_cache = $this->config->get('messages_cache');
+    // for backward compatybility
+    if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
+        $storage_cache  = 'db';
+        $messages_cache = true;
+    }
+
+    if ($storage_cache)
+        $this->storage->set_caching($storage_cache);
+    if ($messages_cache)
+        $this->storage->set_messages_caching(true);
+
+    // set pagesize from config
+    $pagesize = $this->config->get('mail_pagesize');
+    if (!$pagesize) {
+        $pagesize = $this->config->get('pagesize', 50);
+    }
+    $this->storage->set_pagesize($pagesize);
+
+    // set class options
+    $options = array(
+      'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
+      'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
+      'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
+      'debug'       => (bool) $this->config->get("{$driver}_debug"),
+      'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
+      'timeout'     => (int) $this->config->get("{$driver}_timeout"),
+      'skip_deleted' => (bool) $this->config->get('skip_deleted'),
+      'driver'      => $driver,
+    );
+
+    if (!empty($_SESSION['storage_host'])) {
+      $options['host']     = $_SESSION['storage_host'];
+      $options['user']     = $_SESSION['username'];
+      $options['port']     = $_SESSION['storage_port'];
+      $options['ssl']      = $_SESSION['storage_ssl'];
+      $options['password'] = $this->decrypt($_SESSION['password']);
+    }
+
+    $options = $this->plugins->exec_hook("storage_init", $options);
+
+    // for backward compat. (deprecated, to be removed)
+    $options = $this->plugins->exec_hook("imap_init", $options);
+
+    $this->storage->set_options($options);
+    $this->set_storage_prop();
+  }
+
+
+  /**
+   * Connect to the mail storage server with stored session data
+   *
+   * @return bool True on success, False on error
+   */
+  public function storage_connect()
+  {
+    $storage = $this->get_storage();
+
+    if ($_SESSION['storage_host'] && !$storage->is_connected()) {
+      $host = $_SESSION['storage_host'];
+      $user = $_SESSION['username'];
+      $port = $_SESSION['storage_port'];
+      $ssl  = $_SESSION['storage_ssl'];
+      $pass = $this->decrypt($_SESSION['password']);
+
+      if (!$storage->connect($host, $user, $pass, $port, $ssl)) {
+        if (is_object($this->output))
+          $this->output->show_message($storage->get_error_code() == -1 ? 'storageerror' : 'sessionerror', 'error');
+      }
+      else {
+        $this->set_storage_prop();
+        return $storage->is_connected();
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Set storage parameters.
+   * This must be done AFTER connecting to the server!
+   */
+  protected function set_storage_prop()
+  {
+    $storage = $this->get_storage();
+
+    $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
+
+    if ($default_folders = $this->config->get('default_folders')) {
+      $storage->set_default_folders($default_folders);
+    }
+    if (isset($_SESSION['mbox'])) {
+      $storage->set_folder($_SESSION['mbox']);
+    }
+    if (isset($_SESSION['page'])) {
+      $storage->set_page($_SESSION['page']);
+    }
+  }
+
+
+  /**
+   * Get localized text in the desired language
+   *
+   * @param mixed   $attrib  Named parameters array or label name
+   * @param string  $domain  Label domain (plugin) name
+   *
+   * @return string Localized text
+   */
+  public function gettext($attrib, $domain=null)
+  {
+    // load localization files if not done yet
+    if (empty($this->texts))
+      $this->load_language();
+
+    // extract attributes
+    if (is_string($attrib))
+      $attrib = array('name' => $attrib);
+
+    $name = $attrib['name'] ? $attrib['name'] : '';
+
+    // attrib contain text values: use them from now
+    if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
+        $this->texts[$name] = $setval;
+
+    // check for text with domain
+    if ($domain && ($text = $this->texts[$domain.'.'.$name]))
+      ;
+    // text does not exist
+    else if (!($text = $this->texts[$name])) {
+      return "[$name]";
+    }
+
+    // replace vars in text
+    if (is_array($attrib['vars'])) {
+      foreach ($attrib['vars'] as $var_key => $var_value)
+        $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
+    }
+
+    // format output
+    if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
+      return ucfirst($text);
+    else if ($attrib['uppercase'])
+      return mb_strtoupper($text);
+    else if ($attrib['lowercase'])
+      return mb_strtolower($text);
+
+    return strtr($text, array('\n' => "\n"));
+  }
+
+
+  /**
+   * Check if the given text label exists
+   *
+   * @param string  $name       Label name
+   * @param string  $domain     Label domain (plugin) name or '*' for all domains
+   * @param string  $ref_domain Sets domain name if label is found
+   *
+   * @return boolean True if text exists (either in the current language or in en_US)
+   */
+  public function text_exists($name, $domain = null, &$ref_domain = null)
+  {
+    // load localization files if not done yet
+    if (empty($this->texts))
+      $this->load_language();
+
+    if (isset($this->texts[$name])) {
+        $ref_domain = '';
+        return true;
+    }
+
+    // any of loaded domains (plugins)
+    if ($domain == '*') {
+      foreach ($this->plugins->loaded_plugins() as $domain)
+        if (isset($this->texts[$domain.'.'.$name])) {
+          $ref_domain = $domain;
+          return true;
+        }
+    }
+    // specified domain
+    else if ($domain) {
+      $ref_domain = $domain;
+      return isset($this->texts[$domain.'.'.$name]);
+    }
+
+    return false;
+  }
+
+  /**
+   * Load a localization package
+   *
+   * @param string Language ID
+   */
+  public function load_language($lang = null, $add = array())
+  {
+    $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
+
+    // load localized texts
+    if (empty($this->texts) || $lang != $_SESSION['language']) {
+      $this->texts = array();
+
+      // handle empty lines after closing PHP tag in localization files
+      ob_start();
+
+      // get english labels (these should be complete)
+      @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
+      @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
+
+      if (is_array($labels))
+        $this->texts = $labels;
+      if (is_array($messages))
+        $this->texts = array_merge($this->texts, $messages);
+
+      // include user language files
+      if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
+        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
+        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
+
+        if (is_array($labels))
+          $this->texts = array_merge($this->texts, $labels);
+        if (is_array($messages))
+          $this->texts = array_merge($this->texts, $messages);
+      }
+
+      ob_end_clean();
+
+      $_SESSION['language'] = $lang;
+    }
+
+    // append additional texts (from plugin)
+    if (is_array($add) && !empty($add))
+      $this->texts += $add;
+  }
+
+
+  /**
+   * Check the given string and return a valid language code
+   *
+   * @param string Language code
+   * @return string Valid language code
+   */
+  protected function language_prop($lang)
+  {
+    static $rcube_languages, $rcube_language_aliases;
+
+    // user HTTP_ACCEPT_LANGUAGE if no language is specified
+    if (empty($lang) || $lang == 'auto') {
+       $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+       $lang = str_replace('-', '_', $accept_langs[0]);
+     }
+
+    if (empty($rcube_languages)) {
+      @include(INSTALL_PATH . 'program/localization/index.inc');
+    }
+
+    // check if we have an alias for that language
+    if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
+      $lang = $rcube_language_aliases[$lang];
+    }
+    // try the first two chars
+    else if (!isset($rcube_languages[$lang])) {
+      $short = substr($lang, 0, 2);
+
+      // check if we have an alias for the short language code
+      if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
+        $lang = $rcube_language_aliases[$short];
+      }
+      // expand 'nn' to 'nn_NN'
+      else if (!isset($rcube_languages[$short])) {
+        $lang = $short.'_'.strtoupper($short);
+      }
+    }
+
+    if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
+      $lang = 'en_US';
+    }
+
+    return $lang;
+  }
+
+
+  /**
+   * Read directory program/localization and return a list of available languages
+   *
+   * @return array List of available localizations
+   */
+  public function list_languages()
+  {
+    static $sa_languages = array();
+
+    if (!sizeof($sa_languages)) {
+      @include(INSTALL_PATH . 'program/localization/index.inc');
+
+      if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
+        while (($name = readdir($dh)) !== false) {
+          if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
+            continue;
+
+          if ($label = $rcube_languages[$name])
+            $sa_languages[$name] = $label;
+        }
+        closedir($dh);
+      }
+    }
+
+    return $sa_languages;
+  }
+
+
+  /**
+   * Encrypt using 3DES
+   *
+   * @param string $clear clear text input
+   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
+   * @param boolean $base64 whether or not to base64_encode() the result before returning
+   *
+   * @return string encrypted text
+   */
+  public function encrypt($clear, $key = 'des_key', $base64 = true)
+  {
+    if (!$clear)
+      return '';
+
+    /*-
+     * Add a single canary byte to the end of the clear text, which
+     * will help find out how much of padding will need to be removed
+     * upon decryption; see http://php.net/mcrypt_generic#68082
+     */
+    $clear = pack("a*H2", $clear, "80");
+
+    if (function_exists('mcrypt_module_open') &&
+        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
+      $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
+      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
+      $cipher = $iv . mcrypt_generic($td, $clear);
+      mcrypt_generic_deinit($td);
+      mcrypt_module_close($td);
+    }
+    else {
+      @include_once 'des.inc';
+
+      if (function_exists('des')) {
+        $des_iv_size = 8;
+        $iv = $this->create_iv($des_iv_size);
+        $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
+      }
+      else {
+        self::raise_error(array(
+          'code' => 500, 'type' => 'php',
+          'file' => __FILE__, 'line' => __LINE__,
+          'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
+        ), true, true);
+      }
+    }
+
+    return $base64 ? base64_encode($cipher) : $cipher;
+  }
+
+  /**
+   * Decrypt 3DES-encrypted string
+   *
+   * @param string $cipher encrypted text
+   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
+   * @param boolean $base64 whether or not input is base64-encoded
+   *
+   * @return string decrypted text
+   */
+  public function decrypt($cipher, $key = 'des_key', $base64 = true)
+  {
+    if (!$cipher)
+      return '';
+
+    $cipher = $base64 ? base64_decode($cipher) : $cipher;
+
+    if (function_exists('mcrypt_module_open') &&
+        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
+      $iv_size = mcrypt_enc_get_iv_size($td);
+      $iv = substr($cipher, 0, $iv_size);
+
+      // session corruption? (#1485970)
+      if (strlen($iv) < $iv_size)
+        return '';
+
+      $cipher = substr($cipher, $iv_size);
+      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
+      $clear = mdecrypt_generic($td, $cipher);
+      mcrypt_generic_deinit($td);
+      mcrypt_module_close($td);
+    }
+    else {
+      @include_once 'des.inc';
+
+      if (function_exists('des')) {
+        $des_iv_size = 8;
+        $iv = substr($cipher, 0, $des_iv_size);
+        $cipher = substr($cipher, $des_iv_size);
+        $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
+      }
+      else {
+        self::raise_error(array(
+          'code' => 500, 'type' => 'php',
+          'file' => __FILE__, 'line' => __LINE__,
+          'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
+        ), true, true);
+      }
+    }
+
+    /*-
+     * Trim PHP's padding and the canary byte; see note in
+     * rcmail::encrypt() and http://php.net/mcrypt_generic#68082
+     */
+    $clear = substr(rtrim($clear, "\0"), 0, -1);
+
+    return $clear;
+  }
+
+  /**
+   * Generates encryption initialization vector (IV)
+   *
+   * @param int Vector size
+   * @return string Vector string
+   */
+  private function create_iv($size)
+  {
+    // mcrypt_create_iv() can be slow when system lacks entrophy
+    // we'll generate IV vector manually
+    $iv = '';
+    for ($i = 0; $i < $size; $i++)
+        $iv .= chr(mt_rand(0, 255));
+    return $iv;
+  }
+
+
+  /**
+   * Build a valid URL to this instance of Roundcube
+   *
+   * @param mixed Either a string with the action or url parameters as key-value pairs
+   * @return string Valid application URL
+   */
+  public function url($p)
+  {
+      // STUB: should be overloaded by the application
+      return '';
+  }
+
+
+  /**
+   * Function to be executed in script shutdown
+   * Registered with register_shutdown_function()
+   */
+  public function shutdown()
+  {
+    foreach ($this->shutdown_functions as $function)
+      call_user_func($function);
+
+    if (is_object($this->smtp))
+      $this->smtp->disconnect();
+
+    foreach ($this->caches as $cache) {
+        if (is_object($cache))
+            $cache->close();
+    }
+
+    if (is_object($this->storage)) {
+      if ($this->expunge_cache)
+        $this->storage->expunge_cache();
+      $this->storage->close();
+    }
+  }
+
+
+  /**
+   * Registers shutdown function to be executed on shutdown.
+   * The functions will be executed before destroying any
+   * objects like smtp, imap, session, etc.
+   *
+   * @param callback Function callback
+   */
+  public function add_shutdown_function($function)
+  {
+    $this->shutdown_functions[] = $function;
+  }
+
+
+  /**
+   * Use imagemagick or GD lib to read image properties
+   *
+   * @param string Absolute file path
+   * @return mixed Hash array with image props like type, width, height or False on error
+   */
+  public static function imageprops($filepath)
+  {
+    $rcube = self::get_instance();
+    if ($cmd = $rcube->config->get('im_identify_path', false)) {
+      list(, $type, $size) = explode(' ', strtolower(self::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
+      if ($size)
+        list($width, $height) = explode('x', $size);
+    }
+    else if (function_exists('getimagesize')) {
+      $imsize = @getimagesize($filepath);
+      $width = $imsize[0];
+      $height = $imsize[1];
+      $type = preg_replace('!image/!', '', $imsize['mime']);
+    }
+
+    return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
+  }
+
+
+  /**
+   * Convert an image to a given size and type using imagemagick (ensures input is an image)
+   *
+   * @param $p['in']  Input filename (mandatory)
+   * @param $p['out'] Output filename (mandatory)
+   * @param $p['size']  Width x height of resulting image, e.g. "160x60"
+   * @param $p['type']  Output file type, e.g. "jpg"
+   * @param $p['-opts'] Custom command line options to ImageMagick convert
+   * @return Success of convert as true/false
+   */
+  public static function imageconvert($p)
+  {
+    $result = false;
+    $rcube = self::get_instance();
+    $convert  = $rcube->config->get('im_convert_path', false);
+    $identify = $rcube->config->get('im_identify_path', false);
+
+    // imagemagick is required for this
+    if (!$convert)
+        return false;
+
+    if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
+      list(, $type) = explode(' ', strtolower(self::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
+
+    $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
+    $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
+    $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
+
+    if (in_array($type, explode(',', $p['types']))) # Valid type?
+      $result = self::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
+
+    return $result;
+  }
+
+
+  /**
+   * Construct shell command, execute it and return output as string.
+   * Keywords {keyword} are replaced with arguments
+   *
+   * @param $cmd Format string with {keywords} to be replaced
+   * @param $values (zero, one or more arrays can be passed)
+   * @return output of command. shell errors not detectable
+   */
+  public static function exec(/* $cmd, $values1 = array(), ... */)
+  {
+    $args = func_get_args();
+    $cmd = array_shift($args);
+    $values = $replacements = array();
+
+    // merge values into one array
+    foreach ($args as $arg)
+      $values += (array)$arg;
+
+    preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
+    foreach ($matches as $tags) {
+      list(, $tag, $option, $key) = $tags;
+      $parts = array();
+
+      if ($option) {
+        foreach ((array)$values["-$key"] as $key => $value) {
+          if ($value === true || $value === false || $value === null)
+            $parts[] = $value ? $key : "";
+          else foreach ((array)$value as $val)
+            $parts[] = "$key " . escapeshellarg($val);
+        }
+      }
+      else {
+        foreach ((array)$values[$key] as $value)
+          $parts[] = escapeshellarg($value);
+      }
+
+      $replacements[$tag] = join(" ", $parts);
+    }
+
+    // use strtr behaviour of going through source string once
+    $cmd = strtr($cmd, $replacements);
+
+    return (string)shell_exec($cmd);
+  }
+
+
+    /**
+     * Replaces hostname variables.
+     *
+     * @param string $name Hostname
+     * @param string $host Optional IMAP hostname
+     *
+     * @return string Hostname
+     */
+    public static function parse_host($name, $host = '')
+    {
+        // %n - host
+        $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
+        // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
+        $d = preg_replace('/^[^\.]+\./', '', $n);
+        // %h - IMAP host
+        $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host;
+        // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
+        $z = preg_replace('/^[^\.]+\./', '', $h);
+        // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
+        if (strpos($name, '%s') !== false) {
+            $user_email = rcube_ui::get_input_value('_user', rcube_ui::INPUT_POST);
+            $user_email = rcube_idn_convert($user_email, true);
+            $matches    = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s);
+            if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) {
+                return false;
+            }
+        }
+
+        $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
+        return $name;
+    }
+
+
+    /**
+     * Print or write debug messages
+     *
+     * @param mixed Debug message or data
+     */
+    public static function console()
+    {
+        $args = func_get_args();
+
+        if (class_exists('rcmail', false)) {
+            $rcube = self::get_instance();
+            if (is_object($rcube->plugins)) {
+                $plugin = $rcube->plugins->exec_hook('console', array('args' => $args));
+                if ($plugin['abort']) {
+                    return;
+                }
+               $args = $plugin['args'];
+            }
+        }
+
+        $msg = array();
+        foreach ($args as $arg) {
+            $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
+        }
+
+        self::write_log('console', join(";\n", $msg));
+    }
+
+
+    /**
+     * Append a line to a logfile in the logs directory.
+     * Date will be added automatically to the line.
+     *
+     * @param $name name of log file
+     * @param line Line to append
+     */
+    public static function write_log($name, $line)
+    {
+        if (!is_string($line)) {
+            $line = var_export($line, true);
+        }
+
+        $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null;
+        $log_driver  = self::$instance ? self::$instance->config->get('log_driver') : null;
+
+        if (empty($date_format)) {
+            $date_format = 'd-M-Y H:i:s O';
+        }
+
+        $date = date($date_format);
+
+        // trigger logging hook
+        if (is_object(self::$instance) && is_object(self::$instance->plugins)) {
+            $log  = self::$instance->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
+            $name = $log['name'];
+            $line = $log['line'];
+            $date = $log['date'];
+            if ($log['abort'])
+                return true;
+        }
+
+        if ($log_driver == 'syslog') {
+            $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
+            syslog($prio, $line);
+            return true;
+        }
+
+        // log_driver == 'file' is assumed here
+
+        $line = sprintf("[%s]: %s\n", $date, $line);
+        $log_dir  = self::$instance ? self::$instance->config->get('log_dir') : null;
+
+        if (empty($log_dir)) {
+            $log_dir = INSTALL_PATH . 'logs';
+        }
+
+        // try to open specific log file for writing
+        $logfile = $log_dir.'/'.$name;
+
+        if ($fp = @fopen($logfile, 'a')) {
+            fwrite($fp, $line);
+            fflush($fp);
+            fclose($fp);
+            return true;
+        }
+
+        trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
+        return false;
+    }
+
+
+    /**
+     * Throw system error (and show error page).
+     *
+     * @param array Named parameters
+     *      - code:    Error code (required)
+     *      - type:    Error type [php|db|imap|javascript] (required)
+     *      - message: Error message
+     *      - file:    File where error occured
+     *      - line:    Line where error occured
+     * @param boolean True to log the error
+     * @param boolean Terminate script execution
+     */
+    public static function raise_error($arg = array(), $log = false, $terminate = false)
+    {
+        // installer
+        if (class_exists('rcube_install', false)) {
+            $rci = rcube_install::get_instance();
+            $rci->raise_error($arg);
+            return;
+        }
+
+        if ($log && $arg['type'] && $arg['message']) {
+            self::log_bug($arg);
+        }
+
+        // display error page and terminate script
+        if ($terminate && is_object(self::$instance->output)) {
+            self::$instance->output->raise_error($arg['code'], $arg['message']);
+        }
+    }
+
+
+    /**
+     * Report error according to configured debug_level
+     *
+     * @param array Named parameters
+     * @see self::raise_error()
+     */
+    public static function log_bug($arg_arr)
+    {
+        $program = strtoupper($arg_arr['type']);
+        $level   = self::get_instance()->config->get('debug_level');
+
+        // disable errors for ajax requests, write to log instead (#1487831)
+        if (($level & 4) && !empty($_REQUEST['_remote'])) {
+            $level = ($level ^ 4) | 1;
+        }
+
+        // write error to local log file
+        if ($level & 1) {
+            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+                $post_query = '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']);
+            }
+            else {
+                $post_query = '';
+            }
+
+            $log_entry = sprintf("%s Error: %s%s (%s %s)",
+                $program,
+                $arg_arr['message'],
+                $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
+                $_SERVER['REQUEST_METHOD'],
+                $_SERVER['REQUEST_URI'] . $post_query);
+
+            if (!self::write_log('errors', $log_entry)) {
+                // send error to PHPs error handler if write_log didn't succeed
+                trigger_error($arg_arr['message']);
+            }
+        }
+
+        // report the bug to the global bug reporting system
+        if ($level & 2) {
+            // TODO: Send error via HTTP
+        }
+
+        // show error if debug_mode is on
+        if ($level & 4) {
+            print "<b>$program Error";
+
+            if (!empty($arg_arr['file']) && !empty($arg_arr['line'])) {
+                print " in $arg_arr[file] ($arg_arr[line])";
+            }
+
+            print ':</b>&nbsp;';
+            print nl2br($arg_arr['message']);
+            print '<br />';
+            flush();
+        }
+    }
+
+
+    /**
+     * Returns remote IP address and forwarded addresses if found
+     *
+     * @return string Remote IP address(es)
+     */
+    public static function remote_ip()
+    {
+        $address = $_SERVER['REMOTE_ADDR'];
+
+        // append the NGINX X-Real-IP header, if set
+        if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
+            $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
+        }
+        // append the X-Forwarded-For header, if set
+        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+            $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
+        }
+
+        if (!empty($remote_ip)) {
+            $address .= '(' . implode(',', $remote_ip) . ')';
+        }
+
+        return $address;
+    }
+
+
+    /**
+     * Returns current time (with microseconds).
+     *
+     * @return float Current time in seconds since the Unix
+     */
+    public static function timer()
+    {
+        return microtime(true);
+    }
+
+
+    /**
+     * Logs time difference according to provided timer
+     *
+     * @param float  $timer  Timer (self::timer() result)
+     * @param string $label  Log line prefix
+     * @param string $dest   Log file name
+     *
+     * @see self::timer()
+     */
+    public static function print_timer($timer, $label = 'Timer', $dest = 'console')
+    {
+        static $print_count = 0;
+
+        $print_count++;
+        $now  = self::timer();
+        $diff = $now - $timer;
+
+        if (empty($label)) {
+            $label = 'Timer '.$print_count;
+        }
+
+        self::write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
+    }
+
+
+    /**
+     * Getter for logged user ID.
+     *
+     * @return mixed User identifier
+     */
+    public function get_user_id()
+    {
+        if (is_object($this->user)) {
+            return $this->user->ID;
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Getter for logged user name.
+     *
+     * @return string User name
+     */
+    public function get_user_name()
+    {
+        if (is_object($this->user)) {
+            return $this->user->get_username();
+        }
+
+        return null;
+    }
+}
+
+
+/**
+ * Lightweight plugin API class serving as a dummy if plugins are not enabled
+ *
+ * @package Core
+ */
+class rcube_dummy_plugin_api
+{
+    /**
+     * Triggers a plugin hook.
+     * @see rcube_plugin_api::exec_hook()
+     */
+    public function exec_hook($hook, $args = array())
+    {
+        return $args;
+    }
+}
+
diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php
index b56b58a..ca2f1e7 100644
--- a/program/include/rcube_addressbook.php
+++ b/program/include/rcube_addressbook.php
@@ -211,11 +211,14 @@
      */
     public function validate(&$save_data, $autofix = false)
     {
+        $rcmail = rcmail::get_instance();
+
         // check validity of email addresses
         foreach ($this->get_col_values('email', $save_data, true) as $email) {
             if (strlen($email)) {
-                if (!check_email(rcube_idn_to_ascii($email))) {
-                    $this->set_error(self::ERROR_VALIDATE, rcube_label(array('name' => 'emailformaterror', 'vars' => array('email' => $email))));
+                if (!$rcmail->check_email(rcube_idn_to_ascii($email))) {
+                    $error = $rcmail->gettext(array('name' => 'emailformaterror', 'vars' => array('email' => $email)));
+                    $this->set_error(self::ERROR_VALIDATE, $error);
                     return false;
                 }
             }
diff --git a/program/include/rcube_base_replacer.php b/program/include/rcube_base_replacer.php
new file mode 100644
index 0000000..f97a2ee
--- /dev/null
+++ b/program/include/rcube_base_replacer.php
@@ -0,0 +1,110 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_base_replacer.php                               |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide basic functions for base URL replacement                    |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+/**
+ * Helper class to turn relative urls into absolute ones
+ * using a predefined base
+ *
+ * @package Core
+ * @author Thomas Bruederli <roundcube@gmail.com>
+ */
+class rcube_base_replacer
+{
+    private $base_url;
+
+
+    public function __construct($base)
+    {
+        $this->base_url = $base;
+    }
+
+
+    public function callback($matches)
+    {
+        return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
+    }
+
+
+    public function replace($body)
+    {
+        return preg_replace_callback(array(
+            '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
+            '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
+        ),
+        array($this, 'callback'), $body);
+    }
+
+
+    /**
+     * Convert paths like ../xxx to an absolute path using a base url
+     *
+     * @param string $path     Relative path
+     * @param string $base_url Base URL
+     *
+     * @return string Absolute URL
+     */
+    public static function absolute_url($path, $base_url)
+    {
+        $host_url = $base_url;
+        $abs_path = $path;
+
+        // check if path is an absolute URL
+        if (preg_match('/^[fhtps]+:\/\//', $path)) {
+            return $path;
+        }
+
+        // check if path is a content-id scheme
+        if (strpos($path, 'cid:') === 0) {
+            return $path;
+        }
+
+        // cut base_url to the last directory
+        if (strrpos($base_url, '/') > 7) {
+            $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
+            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
+        }
+
+        // $path is absolute
+        if ($path[0] == '/') {
+            $abs_path = $host_url.$path;
+        }
+        else {
+            // strip './' because its the same as ''
+            $path = preg_replace('/^\.\//', '', $path);
+
+            if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
+                foreach ($matches as $a_match) {
+                    if (strrpos($base_url, '/')) {
+                        $base_url = substr($base_url, 0, strrpos($base_url, '/'));
+                    }
+                    $path = substr($path, 3);
+                }
+            }
+
+            $abs_path = $base_url.'/'.$path;
+        }
+
+        return $abs_path;
+    }
+}
diff --git a/program/include/rcube_cache.php b/program/include/rcube_cache.php
index e501185..3e6bc0f 100644
--- a/program/include/rcube_cache.php
+++ b/program/include/rcube_cache.php
@@ -66,7 +66,7 @@
      */
     function __construct($type, $userid, $prefix='', $ttl=0, $packed=true)
     {
-        $rcmail = rcmail::get_instance();
+        $rcmail = rcube::get_instance();
         $type   = strtolower($type);
 
         if ($type == 'memcache') {
@@ -197,7 +197,7 @@
     {
         if ($this->type == 'db' && $this->db) {
             $this->db->query(
-                "DELETE FROM ".get_table_name('cache').
+                "DELETE FROM ".$this->db->table_name('cache').
                 " WHERE user_id = ?".
                 " AND cache_key LIKE ?".
                 " AND " . $this->db->unixtimestamp('created')." < ?",
@@ -274,7 +274,7 @@
         else {
             $sql_result = $this->db->limitquery(
                 "SELECT cache_id, data, cache_key".
-                " FROM ".get_table_name('cache').
+                " FROM ".$this->db->table_name('cache').
                 " WHERE user_id = ?".
                 " AND cache_key = ?".
                 // for better performance we allow more records for one key
@@ -330,7 +330,7 @@
         // Remove NULL rows (here we don't need to check if the record exist)
         if ($data == 'N;') {
             $this->db->query(
-                "DELETE FROM ".get_table_name('cache').
+                "DELETE FROM ".$this->db->table_name('cache').
                 " WHERE user_id = ?".
                 " AND cache_key = ?",
                 $this->userid, $key);
@@ -341,7 +341,7 @@
         // update existing cache record
         if ($key_exists) {
             $result = $this->db->query(
-                "UPDATE ".get_table_name('cache').
+                "UPDATE ".$this->db->table_name('cache').
                 " SET created = ". $this->db->now().", data = ?".
                 " WHERE user_id = ?".
                 " AND cache_key = ?",
@@ -352,7 +352,7 @@
             // for better performance we allow more records for one key
             // so, no need to check if record exist (see rcube_cache::read_record())
             $result = $this->db->query(
-                "INSERT INTO ".get_table_name('cache').
+                "INSERT INTO ".$this->db->table_name('cache').
                 " (created, user_id, cache_key, data)".
                 " VALUES (".$this->db->now().", ?, ?, ?)",
                 $this->userid, $key, $data);
@@ -416,7 +416,7 @@
         }
 
         $this->db->query(
-            "DELETE FROM ".get_table_name('cache').
+            "DELETE FROM ".$this->db->table_name('cache').
             " WHERE user_id = ?" . $where,
             $this->userid);
     }
diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php
index 46906dd..e14dcb8 100644
--- a/program/include/rcube_config.php
+++ b/program/include/rcube_config.php
@@ -85,11 +85,11 @@
 
         // fix default imap folders encoding
         foreach (array('drafts_mbox', 'junk_mbox', 'sent_mbox', 'trash_mbox') as $folder)
-            $this->prop[$folder] = rcube_charset_convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP');
+            $this->prop[$folder] = rcube_charset::convert($this->prop[$folder], RCMAIL_CHARSET, 'UTF7-IMAP');
 
         if (!empty($this->prop['default_folders']))
             foreach ($this->prop['default_folders'] as $n => $folder)
-                $this->prop['default_folders'][$n] = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP');
+                $this->prop['default_folders'][$n] = rcube_charset::convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP');
 
         // set PHP error logging according to config
         if ($this->prop['debug_level'] & 1) {
@@ -186,7 +186,7 @@
             $result = $def;
         }
 
-        $rcmail = rcmail::get_instance();
+        $rcmail = rcube::get_instance();
 
         if ($name == 'timezone' && isset($this->prop['_timezone_value']))
             $result = $this->prop['_timezone_value'];
@@ -300,7 +300,7 @@
     {
         // Bomb out if the requested key does not exist
         if (!array_key_exists($key, $this->prop)) {
-            raise_error(array(
+            rcube::raise_error(array(
                 'code' => 500, 'type' => 'php',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Request for unconfigured crypto key \"$key\""
@@ -311,7 +311,7 @@
 
         // Bomb out if the configured key is not exactly 24 bytes long
         if (strlen($key) != 24) {
-            raise_error(array(
+            rcube::raise_error(array(
                 'code' => 500, 'type' => 'php',
 	            'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
@@ -335,7 +335,7 @@
             if ($delim == "\n" || $delim == "\r\n")
                 return $delim;
             else
-                raise_error(array(
+                rcube::raise_error(array(
                     'code' => 500, 'type' => 'php',
 	                'file' => __FILE__, 'line' => __LINE__,
                     'message' => "Invalid mail_header_delimiter setting"
@@ -370,7 +370,7 @@
                 $domain = $this->prop['mail_domain'][$host];
         }
         else if (!empty($this->prop['mail_domain']))
-            $domain = rcube_parse_host($this->prop['mail_domain']);
+            $domain = rcmail::parse_host($this->prop['mail_domain']);
 
         if ($encode)
             $domain = rcube_idn_to_ascii($domain);
diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php
index c30751f..8834a7d 100644
--- a/program/include/rcube_contacts.php
+++ b/program/include/rcube_contacts.php
@@ -153,7 +153,7 @@
         $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : '';
 
         $sql_result = $this->db->query(
-            "SELECT * FROM ".get_table_name($this->db_groups).
+            "SELECT * FROM ".$this->db->table_name($this->db_groups).
             " WHERE del<>1".
             " AND user_id=?".
             $sql_filter.
@@ -178,7 +178,7 @@
     function get_group($group_id)
     {
         $sql_result = $this->db->query(
-            "SELECT * FROM ".get_table_name($this->db_groups).
+            "SELECT * FROM ".$this->db->table_name($this->db_groups).
             " WHERE del<>1".
             " AND contactgroup_id=?".
             " AND user_id=?",
@@ -214,7 +214,7 @@
         $length = $subset != 0 ? abs($subset) : $this->page_size;
 
         if ($this->group_id)
-            $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m".
+            $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m".
                 " ON (m.contact_id = c.".$this->primary_key.")";
 
         $order_col = (in_array($this->sort_col, $this->table_cols) ? $this->sort_col : 'name');
@@ -228,7 +228,7 @@
         $order_cols[] = 'c.email';
 
         $sql_result = $this->db->limitquery(
-            "SELECT * FROM ".get_table_name($this->db_name)." AS c" .
+            "SELECT * FROM ".$this->db->table_name($this->db_name)." AS c" .
             $join .
             " WHERE c.del<>1" .
                 " AND c.user_id=?" .
@@ -488,13 +488,13 @@
     private function _count()
     {
         if ($this->group_id)
-            $join = " LEFT JOIN ".get_table_name($this->db_groupmembers)." AS m".
+            $join = " LEFT JOIN ".$this->db->table_name($this->db_groupmembers)." AS m".
                 " ON (m.contact_id=c.".$this->primary_key.")";
 
         // count contacts for this user
         $sql_result = $this->db->query(
             "SELECT COUNT(c.contact_id) AS rows".
-            " FROM ".get_table_name($this->db_name)." AS c".
+            " FROM ".$this->db->table_name($this->db_name)." AS c".
                 $join.
             " WHERE c.del<>1".
             " AND c.user_id=?".
@@ -536,7 +536,7 @@
             return $assoc ? $first : $this->result;
 
         $this->db->query(
-            "SELECT * FROM ".get_table_name($this->db_name).
+            "SELECT * FROM ".$this->db->table_name($this->db_name).
             " WHERE contact_id=?".
                 " AND user_id=?".
                 " AND del<>1",
@@ -568,8 +568,8 @@
           return $results;
 
       $sql_result = $this->db->query(
-        "SELECT cgm.contactgroup_id, cg.name FROM " . get_table_name($this->db_groupmembers) . " AS cgm" .
-        " LEFT JOIN " . get_table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" .
+        "SELECT cgm.contactgroup_id, cg.name FROM " . $this->db->table_name($this->db_groupmembers) . " AS cgm" .
+        " LEFT JOIN " . $this->db->table_name($this->db_groups) . " AS cg ON (cgm.contactgroup_id = cg.contactgroup_id AND cg.del<>1)" .
         " WHERE cgm.contact_id=?",
         $id
       );
@@ -638,7 +638,7 @@
 
         if (!$existing->count && !empty($a_insert_cols)) {
             $this->db->query(
-                "INSERT INTO ".get_table_name($this->db_name).
+                "INSERT INTO ".$this->db->table_name($this->db_name).
                 " (user_id, changed, del, ".join(', ', $a_insert_cols).")".
                 " VALUES (".intval($this->user_id).", ".$this->db->now().", 0, ".join(', ', $a_insert_values).")"
             );
@@ -676,7 +676,7 @@
 
         if (!empty($write_sql)) {
             $this->db->query(
-                "UPDATE ".get_table_name($this->db_name).
+                "UPDATE ".$this->db->table_name($this->db_name).
                 " SET changed=".$this->db->now().", ".join(', ', $write_sql).
                 " WHERE contact_id=?".
                     " AND user_id=?".
@@ -772,7 +772,7 @@
 
         // flag record as deleted (always)
         $this->db->query(
-            "UPDATE ".get_table_name($this->db_name).
+            "UPDATE ".$this->db->table_name($this->db_name).
             " SET del=1, changed=".$this->db->now().
             " WHERE user_id=?".
                 " AND contact_id IN ($ids)",
@@ -799,7 +799,7 @@
 
         // clear deleted flag
         $this->db->query(
-            "UPDATE ".get_table_name($this->db_name).
+            "UPDATE ".$this->db->table_name($this->db_name).
             " SET del=0, changed=".$this->db->now().
             " WHERE user_id=?".
                 " AND contact_id IN ($ids)",
@@ -819,7 +819,7 @@
     {
         $this->cache = null;
 
-        $this->db->query("UPDATE ".get_table_name($this->db_name).
+        $this->db->query("UPDATE ".$this->db->table_name($this->db_name).
             " SET del=1, changed=".$this->db->now().
             " WHERE user_id = ?", $this->user_id);
 
@@ -841,7 +841,7 @@
         $name = $this->unique_groupname($name);
 
         $this->db->query(
-            "INSERT INTO ".get_table_name($this->db_groups).
+            "INSERT INTO ".$this->db->table_name($this->db_groups).
             " (user_id, changed, name)".
             " VALUES (".intval($this->user_id).", ".$this->db->now().", ".$this->db->quote($name).")"
         );
@@ -863,7 +863,7 @@
     {
         // flag group record as deleted
         $sql_result = $this->db->query(
-            "UPDATE ".get_table_name($this->db_groups).
+            "UPDATE ".$this->db->table_name($this->db_groups).
             " SET del=1, changed=".$this->db->now().
             " WHERE contactgroup_id=?".
             " AND user_id=?",
@@ -889,7 +889,7 @@
         $name = $this->unique_groupname($newname);
 
         $sql_result = $this->db->query(
-            "UPDATE ".get_table_name($this->db_groups).
+            "UPDATE ".$this->db->table_name($this->db_groups).
             " SET name=?, changed=".$this->db->now().
             " WHERE contactgroup_id=?".
             " AND user_id=?",
@@ -917,7 +917,7 @@
 
         // get existing assignments ...
         $sql_result = $this->db->query(
-            "SELECT contact_id FROM ".get_table_name($this->db_groupmembers).
+            "SELECT contact_id FROM ".$this->db->table_name($this->db_groupmembers).
             " WHERE contactgroup_id=?".
                 " AND contact_id IN (".$this->db->array2list($ids, 'integer').")",
             $group_id
@@ -930,7 +930,7 @@
 
         foreach ($ids as $contact_id) {
             $this->db->query(
-                "INSERT INTO ".get_table_name($this->db_groupmembers).
+                "INSERT INTO ".$this->db->table_name($this->db_groupmembers).
                 " (contactgroup_id, contact_id, created)".
                 " VALUES (?, ?, ".$this->db->now().")",
                 $group_id,
@@ -960,7 +960,7 @@
         $ids = $this->db->array2list($ids, 'integer');
 
         $sql_result = $this->db->query(
-            "DELETE FROM ".get_table_name($this->db_groupmembers).
+            "DELETE FROM ".$this->db->table_name($this->db_groupmembers).
             " WHERE contactgroup_id=?".
                 " AND contact_id IN ($ids)",
             $group_id
@@ -983,7 +983,7 @@
 
         do {
             $sql_result = $this->db->query(
-                "SELECT 1 FROM ".get_table_name($this->db_groups).
+                "SELECT 1 FROM ".$this->db->table_name($this->db_groups).
                 " WHERE del<>1".
                     " AND user_id=?".
                     " AND name=?",
diff --git a/program/include/rcube_html_page.php b/program/include/rcube_html_page.php
deleted file mode 100644
index fffe490..0000000
--- a/program/include/rcube_html_page.php
+++ /dev/null
@@ -1,323 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_html_page.php                                   |
- |                                                                       |
- | This file is part of the Roundcube PHP suite                          |
- | Copyright (C) 2005-2011 The Roundcube Dev Team                       |
- |                                                                       |
- | Licensed under the GNU General Public License version 3 or            |
- | any later version with exceptions for skins & plugins.                |
- | See the README file for a full license statement.                     |
- |                                                                       |
- | CONTENTS:                                                             |
- |   Class to build XHTML page output                                    |
- |                                                                       |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com>                        |
- +-----------------------------------------------------------------------+
-
- $Id$
-
-*/
-
-/**
- * Class for HTML page creation
- *
- * @package HTML
- */
-class rcube_html_page
-{
-    protected $scripts_path = '';
-    protected $script_files = array();
-    protected $css_files = array();
-    protected $scripts = array();
-    protected $charset = RCMAIL_CHARSET;
-    protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
-
-    protected $title = '';
-    protected $header = '';
-    protected $footer = '';
-    protected $body = '';
-    protected $base_path = '';
-
-
-    /** Constructor */
-    public function __construct() {}
-
-    /**
-     * Link an external script file
-     *
-     * @param string File URL
-     * @param string Target position [head|foot]
-     */
-    public function include_script($file, $position='head')
-    {
-        static $sa_files = array();
-
-        if (!preg_match('|^https?://|i', $file) && $file[0] != '/') {
-            $file = $this->scripts_path . $file;
-            if ($fs = @filemtime($file)) {
-                $file .= '?s=' . $fs;
-            }
-        }
-
-        if (in_array($file, $sa_files)) {
-            return;
-        }
-
-        $sa_files[] = $file;
-
-        if (!is_array($this->script_files[$position])) {
-            $this->script_files[$position] = array();
-        }
-
-        $this->script_files[$position][] = $file;
-    }
-
-    /**
-     * Add inline javascript code
-     *
-     * @param string JS code snippet
-     * @param string Target position [head|head_top|foot]
-     */
-    public function add_script($script, $position='head')
-    {
-        if (!isset($this->scripts[$position])) {
-            $this->scripts[$position] = "\n" . rtrim($script);
-        }
-        else {
-            $this->scripts[$position] .= "\n" . rtrim($script);
-        }
-    }
-
-    /**
-     * Link an external css file
-     *
-     * @param string File URL
-     */
-    public function include_css($file)
-    {
-        $this->css_files[] = $file;
-    }
-
-    /**
-     * Add HTML code to the page header
-     *
-     * @param string $str HTML code
-     */
-    public function add_header($str)
-    {
-        $this->header .= "\n" . $str;
-    }
-
-    /**
-     * Add HTML code to the page footer
-     * To be added right befor </body>
-     *
-     * @param string $str HTML code
-     */
-    public function add_footer($str)
-    {
-        $this->footer .= "\n" . $str;
-    }
-
-    /**
-     * Setter for page title
-     *
-     * @param string $t Page title
-     */
-    public function set_title($t)
-    {
-        $this->title = $t;
-    }
-
-    /**
-     * Setter for output charset.
-     * To be specified in a meta tag and sent as http-header
-     *
-     * @param string $charset Charset
-     */
-    public function set_charset($charset)
-    {
-        $this->charset = $charset;
-    }
-
-    /**
-     * Getter for output charset
-     *
-     * @return string Output charset
-     */
-    public function get_charset()
-    {
-        return $this->charset;
-    }
-
-    /**
-     * Reset all saved properties
-     */
-    public function reset()
-    {
-        $this->script_files = array();
-        $this->scripts      = array();
-        $this->title        = '';
-        $this->header       = '';
-        $this->footer       = '';
-        $this->body         = '';
-    }
-
-    /**
-     * Process template and write to stdOut
-     *
-     * @param string HTML template
-     * @param string Base for absolute paths
-     */
-    public function write($templ='', $base_path='')
-    {
-        $output = empty($templ) ? $this->default_template : trim($templ);
-
-        // set default page title
-        if (empty($this->title)) {
-            $this->title = 'Roundcube Mail';
-        }
-
-        // replace specialchars in content
-        $page_title  = Q($this->title, 'show', FALSE);
-        $page_header = '';
-        $page_footer = '';
-
-        // include meta tag with charset
-        if (!empty($this->charset)) {
-            if (!headers_sent()) {
-                header('Content-Type: text/html; charset=' . $this->charset);
-            }
-            $page_header = '<meta http-equiv="content-type"';
-            $page_header.= ' content="text/html; charset=';
-            $page_header.= $this->charset . '" />'."\n";
-        }
-
-        // 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 .= html::script($file);
-            }
-        }
-
-        $head_script = $this->scripts['head_top'] . $this->scripts['head'];
-        if (!empty($head_script)) {
-            $page_header .= html::script(array(), $head_script);
-        }
-
-        if (!empty($this->header)) {
-            $page_header .= $this->header;
-        }
-
-        // put docready commands into page footer
-        if (!empty($this->scripts['docready'])) {
-            $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot');
-        }
-
-        if (is_array($this->script_files['foot'])) {
-            foreach ($this->script_files['foot'] as $file) {
-                $page_footer .= html::script($file);
-            }
-        }
-
-        if (!empty($this->footer)) {
-            $page_footer .= $this->footer . "\n";
-        }
-
-        if (!empty($this->scripts['foot'])) {
-            $page_footer .= html::script(array(), $this->scripts['foot']);
-        }
-
-        // find page header
-        if ($hpos = stripos($output, '</head>')) {
-            $page_header .= "\n";
-        }
-        else {
-            if (!is_numeric($hpos)) {
-                $hpos = stripos($output, '<body');
-            }
-            if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) {
-                while ($output[$hpos] != '>') {
-                    $hpos++;
-                }
-                $hpos++;
-            }
-            $page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\n";
-        }
-
-        // add page hader
-        if ($hpos) {
-            $output = substr_replace($output, $page_header, $hpos, 0);
-        }
-        else {
-            $output = $page_header . $output;
-        }
-
-        // add page footer
-        if (($fpos = strripos($output, '</body>')) || ($fpos = strripos($output, '</html>'))) {
-            $output = substr_replace($output, $page_footer."\n", $fpos, 0);
-        }
-        else {
-            $output .= "\n".$page_footer;
-        }
-
-        // add css files in head, before scripts, for speed up with parallel downloads
-        if (!empty($this->css_files) && 
-            (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>')))
-        ) {
-            $css = '';
-            foreach ($this->css_files as $file) {
-                $css .= html::tag('link', array('rel' => 'stylesheet',
-                    'type' => 'text/css', 'href' => $file, 'nl' => true));
-            }
-            $output = substr_replace($output, $css, $pos, 0);
-        }
-
-        $this->base_path = $base_path;
-
-        // correct absolute paths in images and other tags
-        // add timestamp to .js and .css filename
-        $output = preg_replace_callback(
-            '!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
-            array($this, 'file_callback'), $output);
-
-        // trigger hook with final HTML content to be sent
-        $hook = rcmail::get_instance()->plugins->exec_hook("send_page", array('content' => $output));
-        if (!$hook['abort']) {
-            if ($this->charset != RCMAIL_CHARSET) {
-                echo rcube_charset_convert($hook['content'], RCMAIL_CHARSET, $this->charset);
-            }
-            else {
-                echo $hook['content'];
-            }
-        }
-    }
-
-    /**
-     * Callback function for preg_replace_callback in write()
-     *
-     * @return string Parsed string
-     */
-    private function file_callback($matches)
-    {
-	    $file = $matches[3];
-
-        // correct absolute paths
-	    if ($file[0] == '/') {
-	        $file = $this->base_path . $file;
-        }
-
-        // add file modification timestamp
-	    if (preg_match('/\.(js|css)$/', $file)) {
-            if ($fs = @filemtime($file)) {
-                $file .= '?s=' . $fs;
-            }
-        }
-
-	    return $matches[1] . '=' . $matches[2] . $file . $matches[4];
-    }
-}
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index bd8f351..966fc54 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -132,7 +132,7 @@
             $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
         }
         else if ($use_ssl) {
-            raise_error(array('code' => 403, 'type' => 'imap',
+            rcube::raise_error(array('code' => 403, 'type' => 'imap',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "OpenSSL not available"), true, false);
             $port = 143;
@@ -154,7 +154,7 @@
 
         $attempt = 0;
         do {
-            $data = rcmail::get_instance()->plugins->exec_hook('imap_connect',
+            $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
                 array_merge($this->options, array('host' => $host, 'user' => $user,
                     'attempt' => ++$attempt)));
 
@@ -185,9 +185,9 @@
         else if ($this->conn->error) {
             if ($pass && $user) {
                 $message = sprintf("Login failed for %s from %s. %s",
-                    $user, rcmail_remote_ip(), $this->conn->error);
+                    $user, rcmail::remote_ip(), $this->conn->error);
 
-                raise_error(array('code' => 403, 'type' => 'imap',
+                rcube::raise_error(array('code' => 403, 'type' => 'imap',
                     'file' => __FILE__, 'line' => __LINE__,
                     'message' => $message), true, false);
             }
@@ -457,7 +457,7 @@
             return;
         }
 
-        $config = rcmail::get_instance()->config;
+        $config = rcube::get_instance()->config;
         $imap_personal  = $config->get('imap_ns_personal');
         $imap_other     = $config->get('imap_ns_other');
         $imap_shared    = $config->get('imap_ns_shared');
@@ -546,7 +546,7 @@
             $folder = $this->folder;
         }
 
-        return $this->messagecount($folder, $mode, $force, $status);
+        return $this->countmessages($folder, $mode, $force, $status);
     }
 
 
@@ -562,7 +562,7 @@
      * @return int Number of messages
      * @see rcube_imap::count()
      */
-    protected function messagecount($folder, $mode='ALL', $force=false, $status=true)
+    protected function countmessages($folder, $mode='ALL', $force=false, $status=true)
     {
         $mode = strtoupper($mode);
 
@@ -834,8 +834,8 @@
      * protected method for setting threaded messages flags:
      * depth, has_children and unread_children
      *
-     * @param  array             $headers Reference to headers array indexed by message UID
-     * @param  rcube_imap_result $threads Threads data object
+     * @param  array               $headers  Reference to headers array indexed by message UID
+     * @param  rcube_result_thread $threads  Threads data object
      *
      * @return array Message headers array indexed by message UID
      */
@@ -1048,7 +1048,7 @@
 
         if ($sort) {
             // use this class for message sorting
-            $sorter = new rcube_header_sorter();
+            $sorter = new rcube_message_header_sorter();
             $sorter->set_index($msgs);
             $sorter->sort_headers($a_msg_headers);
         }
@@ -1075,7 +1075,7 @@
         $old = $this->get_folder_stats($folder);
 
         // refresh message count -> will update
-        $this->messagecount($folder, 'ALL', true);
+        $this->countmessages($folder, 'ALL', true);
 
         $result = 0;
 
@@ -1456,7 +1456,7 @@
             foreach ($matches[1] as $m) {
                 $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
                 $string = substr($str, $string_offset - 1, $m[0]);
-                $string = rcube_charset_convert($string, $charset, $dest_charset);
+                $string = rcube_charset::convert($string, $charset, $dest_charset);
                 if ($string === false) {
                     continue;
                 }
@@ -1498,7 +1498,7 @@
      * @param string  $folder   Folder to read from
      * @param bool    $force    True to skip cache
      *
-     * @return rcube_mail_header Message headers
+     * @return rcube_message_header Message headers
      */
     public function get_message_headers($uid, $folder = null, $force = false)
     {
@@ -1529,7 +1529,7 @@
      * @param int     $uid      Message UID to fetch
      * @param string  $folder   Folder to read from
      *
-     * @return object rcube_mail_header Message data
+     * @return object rcube_message_header Message data
      */
     public function get_message($uid, $folder = null)
     {
@@ -1948,7 +1948,7 @@
                 $charset = $this->struct_charset;
             }
             else {
-                $charset = rc_detect_encoding($filename_mime, $this->default_charset);
+                $charset = rcube_charset::detect($filename_mime, $this->default_charset);
             }
 
             $part->filename = rcube_mime::decode_mime_string($filename_mime, $charset);
@@ -1960,7 +1960,7 @@
                 $filename_encoded = $fmatches[2];
             }
 
-            $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset);
+            $part->filename = rcube_charset::convert(urldecode($filename_encoded), $filename_charset);
         }
     }
 
@@ -2039,7 +2039,7 @@
                         $o_part->charset = $this->default_charset;
                     }
                 }
-                $body = rcube_charset_convert($body, $o_part->charset);
+                $body = rcube_charset::convert($body, $o_part->charset);
             }
         }
 
@@ -2227,7 +2227,7 @@
             }
         }
 
-        $config = rcmail::get_instance()->config;
+        $config = rcube::get_instance()->config;
         $to_trash = $to_mbox == $config->get('trash_mbox');
 
         // flag messages as read before moving them
@@ -2510,7 +2510,7 @@
         $a_defaults = $a_out = array();
 
         // Give plugins a chance to provide a list of folders
-        $data = rcmail::get_instance()->plugins->exec_hook('storage_folders',
+        $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
             array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB'));
 
         if (isset($data['folders'])) {
@@ -2521,7 +2521,7 @@
         }
         else {
             // Server supports LIST-EXTENDED, we can use selection options
-            $config = rcmail::get_instance()->config;
+            $config = rcube::get_instance()->config;
             // #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
             if (!$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED')) {
                 // This will also set folder options, LSUB doesn't do that
@@ -3530,7 +3530,7 @@
     protected function get_cache_engine()
     {
         if ($this->caching && !$this->cache) {
-            $rcmail = rcmail::get_instance();
+            $rcmail = rcube::get_instance();
             $ttl = $rcmail->config->get('message_cache_lifetime', '10d') - mktime();
             $this->cache = $rcmail->get_cache('IMAP', $this->caching, $ttl);
         }
@@ -3589,8 +3589,9 @@
             $this->mcache->expunge($ttl);
         }
 
-        if ($this->cache)
+        if ($this->cache) {
             $this->cache->expunge();
+        }
     }
 
 
@@ -3624,10 +3625,10 @@
     protected function get_mcache_engine()
     {
         if ($this->messages_caching && !$this->mcache) {
-            $rcmail = rcmail::get_instance();
+            $rcmail = rcube::get_instance();
             if ($dbh = $rcmail->get_dbh()) {
                 $this->mcache = new rcube_imap_cache(
-                    $dbh, $this, $rcmail->user->ID, $this->options['skip_deleted']);
+                    $dbh, $this, $rcmail->get_user_id(), $this->options['skip_deleted']);
             }
         }
 
@@ -3691,7 +3692,7 @@
                 $a_defaults[$p] = $folder;
             }
             else {
-                $folders[$folder] = rcube_charset_convert($folder, 'UTF7-IMAP');
+                $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP');
             }
         }
 
@@ -3851,7 +3852,7 @@
      */
     public function debug_handler(&$imap, $message)
     {
-        write_log('imap', $message);
+        rcmail::write_log('imap', $message);
     }
 
 
diff --git a/program/include/rcube_imap_cache.php b/program/include/rcube_imap_cache.php
index ee53dc2..eaa8a80 100644
--- a/program/include/rcube_imap_cache.php
+++ b/program/include/rcube_imap_cache.php
@@ -95,7 +95,7 @@
     {
         $this->db           = $db;
         $this->imap         = $imap;
-        $this->userid       = (int)$userid;
+        $this->userid       = $userid;
         $this->skip_deleted = $skip_deleted;
     }
 
@@ -290,7 +290,7 @@
      * @param string $mailbox  Folder name
      * @param array  $msgs     Message UIDs
      *
-     * @return array The list of messages (rcube_mail_header) indexed by UID
+     * @return array The list of messages (rcube_message_header) indexed by UID
      */
     function get_messages($mailbox, $msgs = array())
     {
@@ -301,7 +301,7 @@
         // Fetch messages from cache
         $sql_result = $this->db->query(
             "SELECT uid, data, flags"
-            ." FROM ".get_table_name('cache_messages')
+            ." FROM ".$this->db->table_name('cache_messages')
             ." WHERE user_id = ?"
                 ." AND mailbox = ?"
                 ." AND uid IN (".$this->db->array2list($msgs, 'integer').")",
@@ -348,7 +348,7 @@
      *                         from IMAP server
      * @param bool   $no_cache Enables internal cache usage
      *
-     * @return rcube_mail_header Message data
+     * @return rcube_message_header Message data
      */
     function get_message($mailbox, $uid, $update = true, $cache = true)
     {
@@ -362,7 +362,7 @@
 
         $sql_result = $this->db->query(
             "SELECT flags, data"
-            ." FROM ".get_table_name('cache_messages')
+            ." FROM ".$this->db->table_name('cache_messages')
             ." WHERE user_id = ?"
                 ." AND mailbox = ?"
                 ." AND uid = ?",
@@ -404,9 +404,9 @@
     /**
      * Saves the message in cache.
      *
-     * @param string            $mailbox  Folder name
-     * @param rcube_mail_header $message  Message data
-     * @param bool              $force    Skips message in-cache existance check
+     * @param string               $mailbox  Folder name
+     * @param rcube_message_header $message  Message data
+     * @param bool                 $force    Skips message in-cache existance check
      */
     function add_message($mailbox, $message, $force = false)
     {
@@ -430,7 +430,7 @@
         // here will work as select, assume row exist if affected_rows=0)
         if (!$force) {
             $res = $this->db->query(
-                "UPDATE ".get_table_name('cache_messages')
+                "UPDATE ".$this->db->table_name('cache_messages')
                 ." SET flags = ?, data = ?, changed = ".$this->db->now()
                 ." WHERE user_id = ?"
                     ." AND mailbox = ?"
@@ -444,7 +444,7 @@
 
         // insert new record
         $this->db->query(
-            "INSERT INTO ".get_table_name('cache_messages')
+            "INSERT INTO ".$this->db->table_name('cache_messages')
             ." (user_id, mailbox, uid, flags, changed, data)"
             ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)",
             $this->userid, $mailbox, (int) $message->uid, $flags, $msg);
@@ -479,7 +479,7 @@
         }
 
         $this->db->query(
-            "UPDATE ".get_table_name('cache_messages')
+            "UPDATE ".$this->db->table_name('cache_messages')
             ." SET changed = ".$this->db->now()
             .", flags = flags ".($enabled ? "+ $idx" : "- $idx")
             ." WHERE user_id = ?"
@@ -500,7 +500,7 @@
     {
         if (!strlen($mailbox)) {
             $this->db->query(
-                "DELETE FROM ".get_table_name('cache_messages')
+                "DELETE FROM ".$this->db->table_name('cache_messages')
                 ." WHERE user_id = ?",
                 $this->userid);
         }
@@ -513,11 +513,11 @@
             }
 
             $this->db->query(
-                "DELETE FROM ".get_table_name('cache_messages')
+                "DELETE FROM ".$this->db->table_name('cache_messages')
                 ." WHERE user_id = ?"
-                    ." AND mailbox = ".$this->db->quote($mailbox)
+                    ." AND mailbox = ?"
                     .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
-                $this->userid);
+                $this->userid, $mailbox);
         }
 
     }
@@ -536,17 +536,19 @@
         // otherwise use 'valid' flag to not loose HIGHESTMODSEQ value
         if ($remove) {
             $this->db->query(
-                "DELETE FROM ".get_table_name('cache_index')
-                ." WHERE user_id = ".intval($this->userid)
-                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
+                "DELETE FROM ".$this->db->table_name('cache_index')
+                ." WHERE user_id = ?"
+                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
+                $this->userid
             );
         }
         else {
             $this->db->query(
-                "UPDATE ".get_table_name('cache_index')
+                "UPDATE ".$this->db->table_name('cache_index')
                 ." SET valid = 0"
-                ." WHERE user_id = ".intval($this->userid)
-                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
+                ." WHERE user_id = ?"
+                    .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
+                $this->userid
             );
         }
 
@@ -569,9 +571,10 @@
     function remove_thread($mailbox = null)
     {
         $this->db->query(
-            "DELETE FROM ".get_table_name('cache_thread')
-            ." WHERE user_id = ".intval($this->userid)
-                .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
+            "DELETE FROM ".$this->db->table_name('cache_thread')
+            ." WHERE user_id = ?"
+                .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : ""),
+            $this->userid
         );
 
         if (strlen($mailbox)) {
@@ -628,7 +631,7 @@
         // Get index from DB
         $sql_result = $this->db->query(
             "SELECT data, valid"
-            ." FROM ".get_table_name('cache_index')
+            ." FROM ".$this->db->table_name('cache_index')
             ." WHERE user_id = ?"
                 ." AND mailbox = ?",
             $this->userid, $mailbox);
@@ -665,7 +668,7 @@
         // Get thread from DB
         $sql_result = $this->db->query(
             "SELECT data"
-            ." FROM ".get_table_name('cache_thread')
+            ." FROM ".$this->db->table_name('cache_thread')
             ." WHERE user_id = ?"
                 ." AND mailbox = ?",
             $this->userid, $mailbox);
@@ -709,7 +712,7 @@
 
         if ($exists) {
             $sql_result = $this->db->query(
-                "UPDATE ".get_table_name('cache_index')
+                "UPDATE ".$this->db->table_name('cache_index')
                 ." SET data = ?, valid = 1, changed = ".$this->db->now()
                 ." WHERE user_id = ?"
                     ." AND mailbox = ?",
@@ -717,7 +720,7 @@
         }
         else {
             $sql_result = $this->db->query(
-                "INSERT INTO ".get_table_name('cache_index')
+                "INSERT INTO ".$this->db->table_name('cache_index')
                 ." (user_id, mailbox, data, valid, changed)"
                 ." VALUES (?, ?, ?, 1, ".$this->db->now().")",
                 $this->userid, $mailbox, $data);
@@ -740,7 +743,7 @@
 
         if ($exists) {
             $sql_result = $this->db->query(
-                "UPDATE ".get_table_name('cache_thread')
+                "UPDATE ".$this->db->table_name('cache_thread')
                 ." SET data = ?, changed = ".$this->db->now()
                 ." WHERE user_id = ?"
                     ." AND mailbox = ?",
@@ -748,7 +751,7 @@
         }
         else {
             $sql_result = $this->db->query(
-                "INSERT INTO ".get_table_name('cache_thread')
+                "INSERT INTO ".$this->db->table_name('cache_thread')
                 ." (user_id, mailbox, data, changed)"
                 ." VALUES (?, ?, ?, ".$this->db->now().")",
                 $this->userid, $mailbox, $data);
@@ -956,7 +959,7 @@
         $uids = array();
         $sql_result = $this->db->query(
             "SELECT uid"
-            ." FROM ".get_table_name('cache_messages')
+            ." FROM ".$this->db->table_name('cache_messages')
             ." WHERE user_id = ?"
                 ." AND mailbox = ?",
             $this->userid, $mailbox);
@@ -1003,7 +1006,7 @@
                 }
 
                 $this->db->query(
-                    "UPDATE ".get_table_name('cache_messages')
+                    "UPDATE ".$this->db->table_name('cache_messages')
                     ." SET flags = ?, changed = ".$this->db->now()
                     ." WHERE user_id = ?"
                         ." AND mailbox = ?"
@@ -1058,7 +1061,7 @@
      *
      * @param array $sql_arr Message row data
      *
-     * @return rcube_mail_header Message object
+     * @return rcube_message_header Message object
      */
     private function build_message($sql_arr)
     {
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index a664c5b..fb2ded1 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -29,42 +29,9 @@
 
 */
 
-/**
- * Struct representing an e-mail message header
- *
- * @package Mail
- * @author  Aleksander Machniak <alec@alec.pl>
- */
-class rcube_mail_header
-{
-    public $id;
-    public $uid;
-    public $subject;
-    public $from;
-    public $to;
-    public $cc;
-    public $replyto;
-    public $in_reply_to;
-    public $date;
-    public $messageID;
-    public $size;
-    public $encoding;
-    public $charset;
-    public $ctype;
-    public $timestamp;
-    public $bodystructure;
-    public $internaldate;
-    public $references;
-    public $priority;
-    public $mdn_to;
-    public $others = array();
-    public $flags = array();
-}
+// for backward copat.
+class rcube_mail_header extends rcube_message_header { }
 
-// For backward compatibility with cached messages (#1486602)
-class iilBasicHeader extends rcube_mail_header
-{
-}
 
 /**
  * PHP based wrapper class to connect to an IMAP server
@@ -1545,8 +1512,6 @@
      */
     function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII')
     {
-        require_once dirname(__FILE__) . '/rcube_result_index.php';
-
         $field = strtoupper($field);
         if ($field == 'INTERNALDATE') {
             $field = 'ARRIVAL';
@@ -1595,8 +1560,6 @@
      */
     function thread($mailbox, $algorithm='REFERENCES', $criteria='', $return_uid=false, $encoding='US-ASCII')
     {
-        require_once dirname(__FILE__) . '/rcube_result_thread.php';
-
         $old_sel = $this->selected;
 
         if (!$this->select($mailbox)) {
@@ -1635,8 +1598,6 @@
      */
     function search($mailbox, $criteria, $return_uid=false, $items=array())
     {
-        require_once dirname(__FILE__) . '/rcube_result_index.php';
-
         $old_sel = $this->selected;
 
         if (!$this->select($mailbox)) {
@@ -1696,8 +1657,6 @@
     function index($mailbox, $message_set, $index_field='', $skip_deleted=true,
         $uidfetch=false, $return_uid=false)
     {
-        require_once dirname(__FILE__) . '/rcube_result_index.php';
-
         $msg_index = $this->fetchHeaderIndex($mailbox, $message_set,
             $index_field, $skip_deleted, $uidfetch, $return_uid);
 
@@ -2034,7 +1993,7 @@
      * @param string $mod_seq     Modification sequence for CHANGEDSINCE (RFC4551) query
      * @param bool   $vanished    Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query
      *
-     * @return array List of rcube_mail_header elements, False on error
+     * @return array List of rcube_message_header elements, False on error
      * @since 0.6
      */
     function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(),
@@ -2074,7 +2033,7 @@
             if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
                 $id = intval($m[1]);
 
-                $result[$id]            = new rcube_mail_header;
+                $result[$id]            = new rcube_message_header;
                 $result[$id]->id        = $id;
                 $result[$id]->subject   = '';
                 $result[$id]->messageID = 'mid:' . $id;
diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php
index 08b7cd9..545fef9 100644
--- a/program/include/rcube_ldap.php
+++ b/program/include/rcube_ldap.php
@@ -63,12 +63,11 @@
     /**
     * Object constructor
     *
-    * @param array 	    LDAP connection properties
-    * @param boolean 	Enables debug mode
-    * @param string 	Current user mail domain name
-    * @param integer User-ID
+    * @param array 	 $p            LDAP connection properties
+    * @param boolean $debug        Enables debug mode
+    * @param string  $mail_domain  Current user mail domain name
     */
-    function __construct($p, $debug=false, $mail_domain=NULL)
+    function __construct($p, $debug = false, $mail_domain = null)
     {
         $this->prop = $p;
 
@@ -176,10 +175,10 @@
     */
     private function _connect()
     {
-        global $RCMAIL;
+        $RCMAIL = rcmail::get_instance();
 
         if (!function_exists('ldap_connect'))
-            raise_error(array('code' => 100, 'type' => 'ldap',
+            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "No ldap support in this installation of PHP"),
                 true, true);
@@ -195,7 +194,7 @@
 
         foreach ($this->prop['hosts'] as $host)
         {
-            $host     = idn_to_ascii(rcube_parse_host($host));
+            $host     = idn_to_ascii(rcmail::parse_host($host));
             $hostname = $host.($this->prop['port'] ? ':'.$this->prop['port'] : '');
 
             $this->_debug("C: Connect [$hostname] [{$this->prop['name']}]");
@@ -225,7 +224,7 @@
         }
 
         if (!is_resource($this->conn)) {
-            raise_error(array('code' => 100, 'type' => 'ldap',
+            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Could not connect to any LDAP server, last tried $hostname"), true);
 
@@ -248,7 +247,7 @@
             }
 
             // Get the pieces needed for variable replacement.
-            if ($fu = $RCMAIL->user->get_username())
+            if ($fu = $RCMAIL->get_user_name())
                 list($u, $d) = explode('@', $fu);
             else
                 $d = $this->mail_domain;
@@ -287,7 +286,7 @@
                     if (!empty($this->prop['search_dn_default']))
                         $replaces['%dn'] = $this->prop['search_dn_default'];
                     else {
-                        raise_error(array(
+                        rcube::raise_error(array(
                             'code' => 100, 'type' => 'ldap',
                             'file' => __FILE__, 'line' => __LINE__,
                             'message' => "DN not found using LDAP search."), true);
@@ -341,7 +340,7 @@
         }
 
         if (!function_exists('ldap_sasl_bind')) {
-            raise_error(array('code' => 100, 'type' => 'ldap',
+            rcube::raise_error(array('code' => 100, 'type' => 'ldap',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Unable to bind: ldap_sasl_bind() not exists"),
                 true, true);
@@ -367,7 +366,7 @@
 
         $this->_debug("S: ".ldap_error($this->conn));
 
-        raise_error(array(
+        rcube::raise_error(array(
             'code' => ldap_errno($this->conn), 'type' => 'ldap',
             'file' => __FILE__, 'line' => __LINE__,
             'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)),
@@ -400,7 +399,7 @@
 
         $this->_debug("S: ".ldap_error($this->conn));
 
-        raise_error(array(
+        rcube::raise_error(array(
             'code' => ldap_errno($this->conn), 'type' => 'ldap',
             'file' => __FILE__, 'line' => __LINE__,
             'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
@@ -1562,8 +1561,9 @@
      */
     private function _debug($str)
     {
-        if ($this->debug)
-            write_log('ldap', $str);
+        if ($this->debug) {
+            rcmail::write_log('ldap', $str);
+        }
     }
 
 
diff --git a/program/include/rcube_mdb2.php b/program/include/rcube_mdb2.php
index c103f9a..0139bdc 100644
--- a/program/include/rcube_mdb2.php
+++ b/program/include/rcube_mdb2.php
@@ -59,10 +59,11 @@
      * @param  string $db_dsnw DSN for read/write operations
      * @param  string $db_dsnr Optional DSN for read only operations
      */
-    function __construct($db_dsnw, $db_dsnr='', $pconn=false)
+    public function __construct($db_dsnw, $db_dsnr='', $pconn=false)
     {
-        if (empty($db_dsnr))
+        if (empty($db_dsnr)) {
             $db_dsnr = $db_dsnw;
+        }
 
         $this->db_dsnw = $db_dsnw;
         $this->db_dsnr = $db_dsnr;
@@ -88,7 +89,8 @@
             'emulate_prepared' => $this->debug_mode,
             'debug'            => $this->debug_mode,
             'debug_handler'    => array($this, 'debug_handler'),
-            'portability'      => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL);
+            'portability'      => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL,
+        );
 
         if ($this->db_provider == 'pgsql') {
             $db_options['disable_smart_seqname'] = true;
@@ -103,17 +105,19 @@
             $this->db_error = true;
             $this->db_error_msg = $dbh->getMessage();
 
-            raise_error(array('code' => 500, 'type' => 'db',
+            rcube::raise_error(array('code' => 500, 'type' => 'db',
                 'line' => __LINE__, 'file' => __FILE__,
                 'message' => $dbh->getUserInfo()), true, false);
         }
         else if ($this->db_provider == 'sqlite') {
             $dsn_array = MDB2::parseDSN($dsn);
-            if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials))
-                $this->_sqlite_create_database($dbh, $this->sqlite_initials);
+            if (!filesize($dsn_array['database']) && !empty($this->sqlite_initials)) {
+                $this->sqlite_create_database($dbh, $this->sqlite_initials);
+            }
         }
-        else if ($this->db_provider!='mssql' && $this->db_provider!='sqlsrv')
+        else if ($this->db_provider != 'mssql' && $this->db_provider != 'sqlsrv') {
             $dbh->setCharset('utf8');
+        }
 
         return $dbh;
     }
@@ -123,9 +127,8 @@
      * Connect to appropiate database depending on the operation
      *
      * @param  string $mode Connection mode (r|w)
-     * @access public
      */
-    function db_connect($mode)
+    public function db_connect($mode)
     {
         // previous connection failed, don't attempt to connect again
         if ($this->conn_failure) {
@@ -157,10 +160,12 @@
             $this->db_connected = !PEAR::isError($this->db_handle);
         }
 
-        if ($this->db_connected)
+        if ($this->db_connected) {
             $this->db_mode = $mode;
-        else
+        }
+        else {
             $this->conn_failure = true;
+        }
     }
 
 
@@ -168,9 +173,8 @@
      * Activate/deactivate debug mode
      *
      * @param boolean $dbg True if SQL queries should be logged
-     * @access public
      */
-    function set_debug($dbg = true)
+    public function set_debug($dbg = true)
     {
         $this->debug_mode = $dbg;
         if ($this->db_connected) {
@@ -184,9 +188,8 @@
      * Getter for error state
      *
      * @param  boolean  True on error
-     * @access public
      */
-    function is_error()
+    public function is_error()
     {
         return $this->db_error ? $this->db_error_msg : false;
     }
@@ -196,9 +199,8 @@
      * Connection state checker
      *
      * @param  boolean  True if in connected state
-     * @access public
      */
-    function is_connected()
+    public function is_connected()
     {
         return PEAR::isError($this->db_handle) ? false : $this->db_connected;
     }
@@ -208,7 +210,7 @@
      * Is database replication configured?
      * This returns true if dsnw != dsnr
      */
-    function is_replicated()
+    public function is_replicated()
     {
       return !empty($this->db_dsnr) && $this->db_dsnw != $this->db_dsnr;
     }
@@ -219,17 +221,18 @@
      *
      * @param  string  SQL query to execute
      * @param  mixed   Values to be inserted in query
+     *
      * @return number  Query handle identifier
-     * @access public
      */
-    function query()
+    public function query()
     {
         $params = func_get_args();
         $query = array_shift($params);
 
         // Support one argument of type array, instead of n arguments
-        if (count($params) == 1 && is_array($params[0]))
+        if (count($params) == 1 && is_array($params[0])) {
             $params = $params[0];
+        }
 
         return $this->_query($query, 0, 0, $params);
     }
@@ -242,10 +245,10 @@
      * @param  number  Offset for LIMIT statement
      * @param  number  Number of rows for LIMIT statement
      * @param  mixed   Values to be inserted in query
+     *
      * @return number  Query handle identifier
-     * @access public
      */
-    function limitquery()
+    public function limitquery()
     {
         $params  = func_get_args();
         $query   = array_shift($params);
@@ -274,17 +277,21 @@
         $this->db_connect($mode);
 
         // check connection before proceeding
-        if (!$this->is_connected())
+        if (!$this->is_connected()) {
             return null;
+        }
 
-        if ($this->db_provider == 'sqlite')
-            $this->_sqlite_prepare();
+        if ($this->db_provider == 'sqlite') {
+            $this->sqlite_prepare();
+        }
 
-        if ($numrows || $offset)
+        if ($numrows || $offset) {
             $result = $this->db_handle->setLimit($numrows,$offset);
+        }
 
-        if (empty($params))
+        if (empty($params)) {
             $result = $mode == 'r' ? $this->db_handle->query($query) : $this->db_handle->exec($query);
+        }
         else {
             $params = (array)$params;
             $q = $this->db_handle->prepare($query, null, $mode=='w' ? MDB2_PREPARE_MANIP : null);
@@ -292,7 +299,7 @@
                 $this->db_error = true;
                 $this->db_error_msg = $q->userinfo;
 
-                raise_error(array('code' => 500, 'type' => 'db',
+                rcube::raise_error(array('code' => 500, 'type' => 'db',
                     'line' => __LINE__, 'file' => __FILE__,
                     'message' => $this->db_error_msg), true, false);
 
@@ -315,17 +322,18 @@
      *
      * @param  number $res_id  Optional query handle identifier
      * @return mixed   Number of rows or false on failure
-     * @access public
      */
-    function num_rows($res_id=null)
+    public function num_rows($res_id=null)
     {
-        if (!$this->db_connected)
+        if (!$this->db_connected) {
             return false;
+        }
 
-        if ($result = $this->_get_result($res_id))
+        if ($result = $this->_get_result($res_id)) {
             return $result->numRows();
-        else
-            return false;
+        }
+
+        return false;
     }
 
 
@@ -334,12 +342,12 @@
      *
      * @param  number $res_id Optional query handle identifier
      * @return mixed   Number of rows or false on failure
-     * @access public
      */
-    function affected_rows($res_id = null)
+    public function affected_rows($res_id = null)
     {
-        if (!$this->db_connected)
+        if (!$this->db_connected) {
             return false;
+        }
 
         return $this->_get_result($res_id);
     }
@@ -350,21 +358,24 @@
      * For Postgres databases, a sequence name is required
      *
      * @param  string $table  Table name (to find the incremented sequence)
+     *
      * @return mixed   ID or false on failure
-     * @access public
      */
-    function insert_id($table = '')
+    public function insert_id($table = '')
     {
-        if (!$this->db_connected || $this->db_mode == 'r')
+        if (!$this->db_connected || $this->db_mode == 'r') {
             return false;
+        }
 
         if ($table) {
-            if ($this->db_provider == 'pgsql')
+            if ($this->db_provider == 'pgsql') {
                 // find sequence name
-                $table = get_sequence_name($table);
-            else
+                $table = $this->sequence_name($table);
+            }
+            else {
                 // resolve table name
-                $table = get_table_name($table);
+                $table = $this->table_name($table);
+            }
         }
 
         $id = $this->db_handle->lastInsertID($table);
@@ -378,10 +389,10 @@
      * If no query handle is specified, the last query will be taken as reference
      *
      * @param  number $res_id Optional query handle identifier
+     *
      * @return mixed   Array with col values or false on failure
-     * @access public
      */
-    function fetch_assoc($res_id=null)
+    public function fetch_assoc($res_id = null)
     {
         $result = $this->_get_result($res_id);
         return $this->_fetch_row($result, MDB2_FETCHMODE_ASSOC);
@@ -393,10 +404,10 @@
      * If no query handle is specified, the last query will be taken as reference
      *
      * @param  number $res_id  Optional query handle identifier
+     *
      * @return mixed   Array with col values or false on failure
-     * @access public
      */
-    function fetch_array($res_id=null)
+    public function fetch_array($res_id = null)
     {
         $result = $this->_get_result($res_id);
         return $this->_fetch_row($result, MDB2_FETCHMODE_ORDERED);
@@ -408,13 +419,14 @@
      *
      * @param  MDB2_Result_Common Query $result result handle
      * @param  number                   $mode   Fetch mode identifier
-     * @return mixed   Array with col values or false on failure
-     * @access private
+     *
+     * @return mixed  Array with col values or false on failure
      */
     private function _fetch_row($result, $mode)
     {
-        if ($result === false || PEAR::isError($result) || !$this->is_connected())
+        if ($result === false || PEAR::isError($result) || !$this->is_connected()) {
             return false;
+        }
 
         return $result->fetchRow($mode);
     }
@@ -424,18 +436,19 @@
      * Wrapper for the SHOW TABLES command
      *
      * @return array List of all tables of the current database
-     * @access public
      * @since 0.4-beta
      */
-    function list_tables()
+    public function list_tables()
     {
         // get tables if not cached
         if (!$this->tables) {
             $this->db_handle->loadModule('Manager');
-            if (!PEAR::isError($result = $this->db_handle->listTables()))
+            if (!PEAR::isError($result = $this->db_handle->listTables())) {
                 $this->tables = $result;
-            else
+            }
+            else {
                 $this->tables = array();
+            }
         }
 
         return $this->tables;
@@ -446,9 +459,10 @@
      * Wrapper for SHOW COLUMNS command
      *
      * @param string Table name
+     *
      * @return array List of table cols
      */
-    function list_cols($table)
+    public function list_cols($table)
     {
         $this->db_handle->loadModule('Manager');
         if (!PEAR::isError($result = $this->db_handle->listTableFields($table))) {
@@ -464,18 +478,20 @@
      *
      * @param  mixed  $input  Value to quote
      * @param  string $type   Type of data
+     *
      * @return string  Quoted/converted string for use in query
-     * @access public
      */
-    function quote($input, $type = null)
+    public function quote($input, $type = null)
     {
         // handle int directly for better performance
-        if ($type == 'integer')
+        if ($type == 'integer') {
             return intval($input);
+        }
 
         // create DB handle if not available
-        if (!$this->db_handle)
+        if (!$this->db_handle) {
             $this->db_connect('r');
+        }
 
         return $this->db_connected ? $this->db_handle->quote($input, $type) : addslashes($input);
     }
@@ -485,12 +501,12 @@
      * Quotes a string so it can be safely used as a table or column name
      *
      * @param  string $str Value to quote
+     *
      * @return string  Quoted string for use in query
      * @deprecated     Replaced by rcube_MDB2::quote_identifier
      * @see            rcube_mdb2::quote_identifier
-     * @access public
      */
-    function quoteIdentifier($str)
+    public function quoteIdentifier($str)
     {
         return $this->quote_identifier($str);
     }
@@ -500,13 +516,14 @@
      * Quotes a string so it can be safely used as a table or column name
      *
      * @param  string $str Value to quote
+     *
      * @return string  Quoted string for use in query
-     * @access public
      */
-    function quote_identifier($str)
+    public function quote_identifier($str)
     {
-        if (!$this->db_handle)
+        if (!$this->db_handle) {
             $this->db_connect('r');
+        }
 
         return $this->db_connected ? $this->db_handle->quoteIdentifier($str) : $str;
     }
@@ -516,14 +533,15 @@
      * Escapes a string
      *
      * @param  string $str The string to be escaped
+     *
      * @return string  The escaped string
-     * @access public
      * @since  0.1.1
      */
-    function escapeSimple($str)
+    public function escapeSimple($str)
     {
-        if (!$this->db_handle)
+        if (!$this->db_handle) {
             $this->db_connect('r');
+        }
 
         return $this->db_handle->escape($str);
     }
@@ -533,9 +551,8 @@
      * Return SQL function for current time and date
      *
      * @return string SQL function to use in query
-     * @access public
      */
-    function now()
+    public function now()
     {
         switch ($this->db_provider) {
             case 'mssql':
@@ -553,16 +570,18 @@
      *
      * @param  array  $arr  Input array
      * @param  string $type Type of data
+     *
      * @return string Comma-separated list of quoted values for use in query
-     * @access public
      */
-    function array2list($arr, $type = null)
+    public function array2list($arr, $type = null)
     {
-        if (!is_array($arr))
+        if (!is_array($arr)) {
             return $this->quote($arr, $type);
+        }
 
-        foreach ($arr as $idx => $item)
+        foreach ($arr as $idx => $item) {
             $arr[$idx] = $this->quote($item, $type);
+        }
 
         return implode(',', $arr);
     }
@@ -575,10 +594,11 @@
      * of timestamp functions in Mysql (year 2038 problem)
      *
      * @param  string $field Field name
+     *
      * @return string  SQL statement to use in query
      * @deprecated
      */
-    function unixtimestamp($field)
+    public function unixtimestamp($field)
     {
         switch($this->db_provider) {
             case 'pgsql':
@@ -598,10 +618,10 @@
      * Return SQL statement to convert from a unix timestamp
      *
      * @param  string $timestamp Field name
+     *
      * @return string  SQL statement to use in query
-     * @access public
      */
-    function fromunixtime($timestamp)
+    public function fromunixtime($timestamp)
     {
         return date("'Y-m-d H:i:s'", $timestamp);
     }
@@ -612,13 +632,13 @@
      *
      * @param  string $column  Field name
      * @param  string $value   Search value
+     *
      * @return string  SQL statement to use in query
-     * @access public
      */
-    function ilike($column, $value)
+    public function ilike($column, $value)
     {
         // TODO: use MDB2's matchPattern() function
-        switch($this->db_provider) {
+        switch ($this->db_provider) {
             case 'pgsql':
                 return $this->quote_identifier($column).' ILIKE '.$this->quote($value);
             default:
@@ -626,20 +646,20 @@
         }
     }
 
+
     /**
      * Abstract SQL statement for value concatenation
      *
      * @return string SQL statement to be used in query
-     * @access public
      */
-    function concat(/* col1, col2, ... */)
+    public function concat(/* col1, col2, ... */)
     {
         $func = '';
         $args = func_get_args();
         if (is_array($args[0]))
             $args = $args[0];
 
-        switch($this->db_provider) {
+        switch ($this->db_provider) {
             case 'mysql':
             case 'mysqli':
                 $func = 'CONCAT';
@@ -661,20 +681,22 @@
      * Encodes non-UTF-8 characters in string/array/object (recursive)
      *
      * @param  mixed  $input Data to fix
+     *
      * @return mixed  Properly UTF-8 encoded data
-     * @access public
      */
-    function encode($input)
+    public static function encode($input)
     {
         if (is_object($input)) {
-            foreach (get_object_vars($input) as $idx => $value)
-                $input->$idx = $this->encode($value);
+            foreach (get_object_vars($input) as $idx => $value) {
+                $input->$idx = self::encode($value);
+            }
             return $input;
         }
         else if (is_array($input)) {
-            foreach ($input as $idx => $value)
-                $input[$idx] = $this->encode($value);
-            return $input;	
+            foreach ($input as $idx => $value) {
+                $input[$idx] = self::encode($value);
+            }
+            return $input;
         }
 
         return utf8_encode($input);
@@ -685,20 +707,22 @@
      * Decodes encoded UTF-8 string/object/array (recursive)
      *
      * @param  mixed $input Input data
+     *
      * @return mixed  Decoded data
-     * @access public
      */
-    function decode($input)
+    public static function decode($input)
     {
         if (is_object($input)) {
-            foreach (get_object_vars($input) as $idx => $value)
-                $input->$idx = $this->decode($value);
+            foreach (get_object_vars($input) as $idx => $value) {
+                $input->$idx = self::decode($value);
+            }
             return $input;
         }
         else if (is_array($input)) {
-            foreach ($input as $idx => $value)
-                $input[$idx] = $this->decode($value);
-            return $input;	
+            foreach ($input as $idx => $value) {
+                $input[$idx] = self::decode($value);
+            }
+            return $input;
         }
 
         return utf8_decode($input);
@@ -709,8 +733,8 @@
      * Adds a query result and returns a handle ID
      *
      * @param  object $res Query handle
+     *
      * @return mixed   Handle ID
-     * @access private
      */
     private function _add_result($res)
     {
@@ -718,7 +742,7 @@
         if (PEAR::isError($res)) {
             $this->db_error = true;
             $this->db_error_msg = $res->getMessage();
-            raise_error(array('code' => 500, 'type' => 'db',
+            rcube::raise_error(array('code' => 500, 'type' => 'db',
                 'line' => __LINE__, 'file' => __FILE__,
                 'message' => $res->getMessage() . " Query: " 
                 . substr(preg_replace('/[\r\n]+\s*/', ' ', $res->userinfo), 0, 512)),
@@ -737,17 +761,20 @@
      * If no ID is specified, the last resource handle will be returned
      *
      * @param  number $res_id Handle ID
+     *
      * @return mixed   Resource handle or false on failure
-     * @access private
      */
     private function _get_result($res_id = null)
     {
-        if ($res_id == null)
+        if ($res_id == null) {
             $res_id = $this->last_res_id;
+        }
 
-        if (isset($this->a_query_results[$res_id]))
-            if (!PEAR::isError($this->a_query_results[$res_id]))
+        if (isset($this->a_query_results[$res_id])) {
+            if (!PEAR::isError($this->a_query_results[$res_id])) {
                 return $this->a_query_results[$res_id];
+            }
+        }
 
         return false;
     }
@@ -758,42 +785,36 @@
      *
      * @param  MDB2   $dbh       SQLite database handle
      * @param  string $file_name File path to use for DB creation
-     * @access private
      */
-    private function _sqlite_create_database($dbh, $file_name)
+    private function sqlite_create_database($dbh, $file_name)
     {
-        if (empty($file_name) || !is_string($file_name))
+        if (empty($file_name) || !is_string($file_name)) {
             return;
+        }
 
         $data = file_get_contents($file_name);
 
-        if (strlen($data))
-            if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) 
-                raise_error(array('code' => 500, 'type' => 'db',
+        if (strlen($data)) {
+            if (!sqlite_exec($dbh->connection, $data, $error) || MDB2::isError($dbh)) {
+                rcube::raise_error(array('code' => 500, 'type' => 'db',
                     'line' => __LINE__, 'file' => __FILE__,
-                    'message' => $error), true, false); 
+                    'message' => $error), true, false);
+            }
+        }
     }
 
 
     /**
      * Add some proprietary database functions to the current SQLite handle
      * in order to make it MySQL compatible
-     *
-     * @access private
      */
-    private function _sqlite_prepare()
+    private function sqlite_prepare()
     {
-        include_once(INSTALL_PATH . 'program/include/rcube_sqlite.inc');
-
-        // we emulate via callback some missing MySQL function
+        // we emulate via callback some missing MySQL functions
         sqlite_create_function($this->db_handle->connection,
-            'from_unixtime', 'rcube_sqlite_from_unixtime');
+            'unix_timestamp', array('rcube_mdb2', 'sqlite_unix_timestamp'));
         sqlite_create_function($this->db_handle->connection,
-            'unix_timestamp', 'rcube_sqlite_unix_timestamp');
-        sqlite_create_function($this->db_handle->connection,
-            'now', 'rcube_sqlite_now');
-        sqlite_create_function($this->db_handle->connection,
-            'md5', 'rcube_sqlite_md5');
+            'now', array('rcube_mdb2', 'sqlite_now'));
     }
 
 
@@ -805,8 +826,82 @@
         if ($scope != 'prepare') {
             $debug_output = sprintf('%s(%d): %s;',
                 $scope, $db->db_index, rtrim($message, ';'));
-            write_log('sql', $debug_output);
+            rcmail::write_log('sql', $debug_output);
         }
     }
 
-}  // end class rcube_db
+
+    /**
+     * Return correct name for a specific database table
+     *
+     * @param string $table Table name
+     *
+     * @return string Translated table name
+     */
+    public function table_name($table)
+    {
+        $rcmail = rcube::get_instance();
+
+        // return table name if configured
+        $config_key = 'db_table_'.$table;
+
+        if ($name = $rcmail->config->get($config_key)) {
+            return $name;
+        }
+
+        return $table;
+    }
+
+
+    /**
+     * Return correct name for a specific database sequence
+     * (used for Postgres only)
+     *
+     * @param string $sequence Secuence name
+     *
+     * @return string Translated sequence name
+     */
+    public function sequence_name($sequence)
+    {
+        $rcmail = rcube::get_instance();
+
+        // return sequence name if configured
+        $config_key = 'db_sequence_'.$sequence;
+
+        if ($name = $rcmail->config->get($config_key)) {
+            return $name;
+        }
+
+        return $sequence;
+    }
+
+
+    /**
+     * Callback for sqlite: unix_timestamp()
+     */
+    public static function sqlite_unix_timestamp($timestamp = '')
+    {
+        $timestamp = trim($timestamp);
+        if (!$timestamp) {
+            $ret = time();
+        }
+        else if (!preg_match('/^[0-9]+$/s', $timestamp)) {
+            $ret = strtotime($timestamp);
+        }
+        else {
+            $ret = $timestamp;
+        }
+
+        return $ret;
+    }
+
+
+    /**
+     * Callback for sqlite: now()
+     */
+    public static function sqlite_now()
+    {
+        return date("Y-m-d H:i:s");
+    }
+
+}
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index 76246a2..295b06e 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -78,7 +78,7 @@
     function __construct($uid)
     {
         $this->uid  = $uid;
-        $this->app  = rcmail::get_instance();
+        $this->app  = rcube::get_instance();
         $this->storage = $this->app->get_storage();
         $this->storage->set_options(array('all_headers' => true));
 
@@ -96,8 +96,10 @@
         $this->opt = array(
             'safe' => $this->is_safe,
             'prefer_html' => $this->app->config->get('prefer_html'),
-            'get_url' => rcmail_url('get', array(
-                '_mbox' => $this->storage->get_folder(), '_uid' => $uid))
+            'get_url' => $this->app->url(array(
+                'action' => 'get',
+                'mbox'   => $this->storage->get_folder(),
+                'uid'    => $uid))
         );
 
         if (!empty($this->headers->structure)) {
@@ -380,7 +382,8 @@
                 $c->type            = 'content';
                 $c->ctype_primary   = 'text';
                 $c->ctype_secondary = 'plain';
-                $c->body            = rcube_label('htmlmessage');
+                $c->mimetype        = 'text/plain';
+                $c->realtype        = 'text/html';
 
                 $this->parts[] = $c;
             }
@@ -388,7 +391,6 @@
             // add html part as attachment
             if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
                 $html_part = &$structure->parts[$html_part];
-                $html_part->filename = rcube_label('htmlmessage');
                 $html_part->mimetype = 'text/html';
 
                 $this->attachments[] = $html_part;
@@ -400,8 +402,8 @@
             $p->type            = 'content';
             $p->ctype_primary   = 'text';
             $p->ctype_secondary = 'plain';
-            $p->body            = rcube_label('encryptedmessage');
-            $p->size            = strlen($p->body);
+            $p->mimetype        = 'text/plain';
+            $p->realtype        = 'multipart/encrypted';
 
             $this->parts[] = $p;
         }
@@ -671,7 +673,7 @@
                 $uupart->size     = strlen($uupart->body);
                 $uupart->mime_id  = 'uu.' . $part->mime_id . '.' . $pid;
 
-                $ctype = rc_mime_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
+                $ctype = rcube_mime::content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
                 $uupart->mimetype = $ctype;
                 list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype);
 
diff --git a/program/include/rcube_message_header.php b/program/include/rcube_message_header.php
new file mode 100644
index 0000000..ee7e1e3
--- /dev/null
+++ b/program/include/rcube_message_header.php
@@ -0,0 +1,238 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_message_header.php                              |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2011-2012, Kolab Systems AG                             |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   E-mail message headers representation                               |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+/**
+ * Struct representing an e-mail message header
+ *
+ * @package Mail
+ * @author  Aleksander Machniak <alec@alec.pl>
+ */
+class rcube_message_header
+{
+    /**
+     * Message sequence number
+     *
+     * @var int
+     */
+    public $id;
+
+    /**
+     * Message unique identifier
+     *
+     * @var int
+     */
+    public $uid;
+
+    /**
+     * Message subject
+     *
+     * @var string
+     */
+    public $subject;
+
+    /**
+     * Message sender (From)
+     *
+     * @var string
+     */
+    public $from;
+
+    /**
+     * Message recipient (To)
+     *
+     * @var string
+     */
+    public $to;
+
+    /**
+     * Message additional recipients (Cc)
+     *
+     * @var string
+     */
+    public $cc;
+
+    /**
+     * Message Reply-To header
+     *
+     * @var string
+     */
+    public $replyto;
+
+    /**
+     * Message In-Reply-To header
+     *
+     * @var string
+     */
+    public $in_reply_to;
+
+    /**
+     * Message date (Date)
+     *
+     * @var string
+     */
+    public $date;
+
+    /**
+     * Message identifier (Message-ID)
+     *
+     * @var string
+     */
+    public $messageID;
+
+    /**
+     * Message size
+     *
+     * @var int
+     */
+    public $size;
+
+    /**
+     * Message encoding
+     *
+     * @var string
+     */
+    public $encoding;
+
+    /**
+     * Message charset
+     *
+     * @var string
+     */
+    public $charset;
+
+    /**
+     * Message Content-type
+     *
+     * @var string
+     */
+    public $ctype;
+
+    /**
+     * Message timestamp (based on message date)
+     *
+     * @var int
+     */
+    public $timestamp;
+
+    /**
+     * IMAP bodystructure string
+     *
+     * @var string
+     */
+    public $bodystructure;
+
+    /**
+     * IMAP internal date
+     *
+     * @var string
+     */
+    public $internaldate;
+
+    /**
+     * Message References header
+     *
+     * @var string
+     */
+    public $references;
+
+    /**
+     * Message priority (X-Priority)
+     *
+     * @var int
+     */
+    public $priority;
+
+    /**
+     * Message receipt recipient
+     *
+     * @var string
+     */
+    public $mdn_to;
+
+    /**
+     * Other message headers
+     *
+     * @var array
+     */
+    public $others = array();
+
+    /**
+     * Message flags
+     *
+     * @var array
+     */
+    public $flags = array();
+}
+
+
+/**
+ * Class for sorting an array of rcube_message_header objects in a predetermined order.
+ *
+ * @package Mail
+ * @author  Aleksander Machniak <alec@alec.pl>
+ */
+class rcube_message_header_sorter
+{
+    private $uids = array();
+
+
+    /**
+     * Set the predetermined sort order.
+     *
+     * @param array $index  Numerically indexed array of IMAP UIDs
+     */
+    function set_index($index)
+    {
+        $index = array_flip($index);
+
+        $this->uids = $index;
+    }
+
+    /**
+     * Sort the array of header objects
+     *
+     * @param array $headers Array of rcube_message_header objects indexed by UID
+     */
+    function sort_headers(&$headers)
+    {
+        uksort($headers, array($this, "compare_uids"));
+    }
+
+    /**
+     * Sort method called by uksort()
+     *
+     * @param int $a Array key (UID)
+     * @param int $b Array key (UID)
+     */
+    function compare_uids($a, $b)
+    {
+        // then find each sequence number in my ordered list
+        $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1;
+        $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1;
+
+        // return the relative position as the comparison value
+        return $posa - $posb;
+    }
+}
diff --git a/program/include/rcube_message_part.php b/program/include/rcube_message_part.php
new file mode 100644
index 0000000..799dc0f
--- /dev/null
+++ b/program/include/rcube_message_part.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_message_part.php                                |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2011-2012, Kolab Systems AG                             |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Class representing a message part                                   |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+
+/**
+ * Class representing a message part
+ *
+ * @package    Mail
+ * @author     Thomas Bruederli <roundcube@gmail.com>
+ * @author     Aleksander Machniak <alec@alec.pl>
+ * @version    2.0
+ */
+class rcube_message_part
+{
+    /**
+     * Part MIME identifier
+     *
+     * @var string
+     */
+    public $mime_id = '';
+
+    /**
+     * Content main type
+     *
+     * @var string
+     */
+    public $ctype_primary = 'text';
+
+    /**
+     * Content subtype
+     *
+     * @var string
+     */
+    public $ctype_secondary = 'plain';
+
+    /**
+     * Complete content type
+     *
+     * @var string
+     */
+    public $mimetype = 'text/plain';
+
+    public $disposition = '';
+    public $filename = '';
+    public $encoding = '8bit';
+    public $charset = '';
+
+    /**
+     * Part size in bytes
+     *
+     * @var int
+     */
+    public $size = 0;
+
+    /**
+     * Part headers
+     *
+     * @var array
+     */
+    public $headers = array();
+
+    public $d_parameters = array();
+    public $ctype_parameters = array();
+
+
+    /**
+     * Clone handler.
+     */
+    function __clone()
+    {
+        if (isset($this->parts)) {
+            foreach ($this->parts as $idx => $part) {
+                if (is_object($part)) {
+                    $this->parts[$idx] = clone $part;
+                }
+            }
+        }
+    }
+
+}
diff --git a/program/include/rcube_output.php b/program/include/rcube_output.php
new file mode 100644
index 0000000..575f106
--- /dev/null
+++ b/program/include/rcube_output.php
@@ -0,0 +1,259 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_output.php                                      |
+ |                                                                       |
+ | This file is part of the Roundcube PHP suite                          |
+ | Copyright (C) 2005-2012 The Roundcube Dev Team                        |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ | CONTENTS:                                                             |
+ |   Abstract class for output generation                                |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+/**
+ * Class for output generation
+ *
+ * @package HTML
+ */
+abstract class rcube_output
+{
+    public $browser;
+    public $type = 'html';
+    public $ajax_call = false;
+    public $framed = false;
+
+    protected $app;
+    protected $config;
+    protected $charset = RCMAIL_CHARSET;
+    protected $env = array();
+    protected $pagetitle = '';
+    protected $object_handlers = array();
+
+
+    /**
+     * Object constructor
+     */
+    public function __construct($task = null, $framed = false)
+    {
+        $this->app     = rcmail::get_instance();
+        $this->config  = $this->app->config;
+        $this->browser = new rcube_browser();
+    }
+
+
+    /**
+     * Setter for page title
+     *
+     * @param string $title Page title
+     */
+    public function set_pagetitle($title)
+    {
+        $this->pagetitle = $title;
+    }
+
+
+    /**
+     * Setter for output charset.
+     * To be specified in a meta tag and sent as http-header
+     *
+     * @param string $charset Charset name
+     */
+    public function set_charset($charset)
+    {
+        $this->charset = $charset;
+    }
+
+
+    /**
+     * Getter for output charset
+     *
+     * @return string Output charset name
+     */
+    public function get_charset()
+    {
+        return $this->charset;
+    }
+
+
+    /**
+     * Getter for the current skin path property
+     */
+    public function get_skin_path()
+    {
+        return $this->config->get('skin_path');
+    }
+
+
+    /**
+     * Set environment variable
+     *
+     * @param string $name   Property name
+     * @param mixed  $value  Property value
+     */
+    public function set_env($name, $value)
+    {
+        $this->env[$name] = $value;
+    }
+
+
+    /**
+     * Environment variable getter.
+     *
+     * @param string $name  Property name
+     *
+     * @return mixed Property value
+     */
+    public function get_env($name)
+    {
+        return $this->env[$name];
+    }
+
+
+    /**
+     * Delete all stored env variables and commands
+     */
+    public function reset()
+    {
+        $this->env = array();
+        $this->object_handlers = array();
+        $this->pagetitle = '';
+    }
+
+
+    /**
+     * Call a client method
+     *
+     * @param string Method to call
+     * @param ... Additional arguments
+     */
+    abstract function command();
+
+
+    /**
+     * Add a localized label to the client environment
+     */
+    abstract function add_label();
+
+
+    /**
+     * Invoke display_message command
+     *
+     * @param string  $message  Message to display
+     * @param string  $type     Message type [notice|confirm|error]
+     * @param array   $vars     Key-value pairs to be replaced in localized text
+     * @param boolean $override Override last set message
+     * @param int     $timeout  Message displaying time in seconds
+     */
+    abstract function show_message($message, $type = 'notice', $vars = null, $override = true, $timeout = 0);
+
+
+    /**
+     * Redirect to a certain url.
+     *
+     * @param mixed $p     Either a string with the action or url parameters as key-value pairs
+     * @param int   $delay Delay in seconds
+     */
+    abstract function redirect($p = array(), $delay = 1);
+
+
+    /**
+     * Send output to the client.
+     */
+    abstract function send();
+
+
+    /**
+     * Register a template object handler
+     *
+     * @param  string Object name
+     * @param  string Function name to call
+     * @return void
+     */
+    public function add_handler($obj, $func)
+    {
+        $this->object_handlers[$obj] = $func;
+    }
+
+
+    /**
+     * Register a list of template object handlers
+     *
+     * @param  array Hash array with object=>handler pairs
+     * @return void
+     */
+    public function add_handlers($arr)
+    {
+        $this->object_handlers = array_merge($this->object_handlers, $arr);
+    }
+
+
+    /**
+     * Send HTTP headers to prevent caching a page
+     */
+    public function nocacheing_headers()
+    {
+        if (headers_sent()) {
+            return;
+        }
+
+        header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
+        header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
+
+        // Request browser to disable DNS prefetching (CVE-2010-0464)
+        header("X-DNS-Prefetch-Control: off");
+
+        // We need to set the following headers to make downloads work using IE in HTTPS mode.
+        if ($this->browser->ie && rcube_ui::https_check()) {
+            header('Pragma: private');
+            header("Cache-Control: private, must-revalidate");
+        }
+        else {
+            header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0");
+            header("Pragma: no-cache");
+        }
+    }
+
+
+    /**
+     * Show error page and terminate script execution
+     *
+     * @param int    $code     Error code
+     * @param string $message  Error message
+     */
+    public function raise_error($code, $message)
+    {
+        // STUB: to be overloaded by specific output classes
+        fputs(STDERR, "Error $code: $message\n");
+        exit(-1);
+    }
+
+
+    /**
+     * Convert a variable into a javascript object notation
+     *
+     * @param mixed Input value
+     *
+     * @return string Serialized JSON string
+     */
+    public static function json_serialize($input)
+    {
+        $input = rcube_charset::clean($input);
+
+        // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
+        // that's why we have @ here
+        return @json_encode($input);
+    }
+
+}
diff --git a/program/include/rcube_template.php b/program/include/rcube_output_html.php
similarity index 69%
rename from program/include/rcube_template.php
rename to program/include/rcube_output_html.php
index b2bdda4..a047aba 100644
--- a/program/include/rcube_template.php
+++ b/program/include/rcube_output_html.php
@@ -2,10 +2,10 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_template.php                                    |
+ | program/include/rcubeoutput_html.php                                  |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2006-2011, The Roundcube Dev Team                       |
+ | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -13,7 +13,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class to handle HTML page output using a skin template.             |
- |   Extends rcube_html_page class from rcube_shared.inc                 |
  |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
@@ -28,33 +27,32 @@
  * Class to create HTML page output using a skin template
  *
  * @package View
- * @todo Documentation
- * @uses rcube_html_page
  */
-class rcube_template extends rcube_html_page
+class rcube_output_html extends rcube_output
 {
-    private $app;
-    private $config;
-    private $pagetitle = '';
-    private $message = null;
-    private $js_env = array();
-    private $js_labels = array();
-    private $js_commands = array();
-    private $object_handlers = array();
-    private $plugin_skin_path;
-    private $template_name;
-
-    public $browser;
-    public $framed = false;
-    public $env = array();
     public $type = 'html';
-    public $ajax_call = false;
+
+    protected $message = null;
+    protected $js_env = array();
+    protected $js_labels = array();
+    protected $js_commands = array();
+    protected $plugin_skin_path;
+    protected $template_name;
+    protected $scripts_path = '';
+    protected $script_files = array();
+    protected $css_files = array();
+    protected $scripts = array();
+    protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
+    protected $header = '';
+    protected $footer = '';
+    protected $body = '';
+    protected $base_path = '';
 
     // deprecated names of templates used before 0.5
-    private $deprecated_templates = array(
-        'contact' => 'showcontact',
-        'contactadd' => 'addcontact',
-        'contactedit' => 'editcontact',
+    protected $deprecated_templates = array(
+        'contact'      => 'showcontact',
+        'contactadd'   => 'addcontact',
+        'contactedit'  => 'editcontact',
         'identityedit' => 'editidentity',
         'messageprint' => 'printmessage',
     );
@@ -64,20 +62,16 @@
      *
      * @todo   Replace $this->config with the real rcube_config object
      */
-    public function __construct($task, $framed = false)
+    public function __construct($task = null, $framed = false)
     {
         parent::__construct();
 
-        $this->app = rcmail::get_instance();
-        $this->config = $this->app->config->all();
-        $this->browser = new rcube_browser();
-
         //$this->framed = $framed;
         $this->set_env('task', $task);
-        $this->set_env('x_frame_options', $this->app->config->get('x_frame_options', 'sameorigin'));
+        $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
 
         // load the correct skin (in case user-defined)
-        $this->set_skin($this->config['skin']);
+        $this->set_skin($this->config->get('skin'));
 
         // add common javascripts
         $this->add_script('var '.JS_OBJECT_NAME.' = new rcube_webmail();', 'head_top');
@@ -101,6 +95,7 @@
         ));
     }
 
+
     /**
      * Set environment variable
      *
@@ -116,26 +111,22 @@
         }
     }
 
-    /**
-     * Set page title variable
-     */
-    public function set_pagetitle($title)
-    {
-        $this->pagetitle = $title;
-    }
 
     /**
      * Getter for the current page title
      *
      * @return string The page title
      */
-    public function get_pagetitle()
+    protected function get_pagetitle()
     {
         if (!empty($this->pagetitle)) {
             $title = $this->pagetitle;
         }
         else if ($this->env['task'] == 'login') {
-            $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $this->config['product_name'])));
+            $title = $this->app->gettext(array(
+                'name' => 'welcome',
+                'vars' => array('product' => $this->config->get('product_name')
+            )));
         }
         else {
             $title = ucfirst($this->env['task']);
@@ -143,6 +134,7 @@
 
         return $title;
     }
+
 
     /**
      * Set skin
@@ -156,23 +148,18 @@
             $valid = true;
         }
         else {
-            $skin_path = $this->config['skin_path'] ? $this->config['skin_path'] : 'skins/default';
+            $skin_path = $this->config->get('skin_path');
+            if (!$skin_path) {
+                $skin_path = 'skins/default';
+            }
             $valid = !$skin;
         }
 
-        $this->app->config->set('skin_path', $skin_path);
-        $this->config['skin_path'] = $skin_path;
+        $this->config->set('skin_path', $skin_path);
 
         return $valid;
     }
 
-    /**
-     * Getter for the current skin path property
-     */
-    public function get_skin_path()
-    {
-        return $this->config['skin_path'];
-    }
 
     /**
      * Check if a specific template exists
@@ -182,32 +169,10 @@
      */
     public function template_exists($name)
     {
-        $filename = $this->config['skin_path'] . '/templates/' . $name . '.html';
+        $filename = $this->config->get('skin_path') . '/templates/' . $name . '.html';
         return (is_file($filename) && is_readable($filename)) || ($this->deprecated_templates[$name] && $this->template_exists($this->deprecated_templates[$name]));
     }
 
-    /**
-     * Register a template object handler
-     *
-     * @param  string Object name
-     * @param  string Function name to call
-     * @return void
-     */
-    public function add_handler($obj, $func)
-    {
-        $this->object_handlers[$obj] = $func;
-    }
-
-    /**
-     * Register a list of template object handlers
-     *
-     * @param  array Hash array with object=>handler pairs
-     * @return void
-     */
-    public function add_handlers($arr)
-    {
-        $this->object_handlers = array_merge($this->object_handlers, $arr);
-    }
 
     /**
      * Register a GUI object to the client script
@@ -220,6 +185,7 @@
     {
         $this->add_script(JS_OBJECT_NAME.".gui_object('$obj', '$id');");
     }
+
 
     /**
      * Call a client method
@@ -236,6 +202,7 @@
           $this->js_commands[] = $cmd;
     }
 
+
     /**
      * Add a localized label to the client environment
      */
@@ -246,9 +213,10 @@
           $args = $args[0];
 
         foreach ($args as $name) {
-            $this->js_labels[$name] = rcube_label($name);
+            $this->js_labels[$name] = $this->app->gettext($name);
         }
     }
+
 
     /**
      * Invoke display_message command
@@ -263,10 +231,10 @@
     public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
     {
         if ($override || !$this->message) {
-            if (rcube_label_exists($message)) {
+            if ($this->app->text_exists($message)) {
                 if (!empty($vars))
                     $vars = array_map('Q', $vars);
-                $msgtext = rcube_label(array('name' => $message, 'vars' => $vars));
+                $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
             }
             else
                 $msgtext = $message;
@@ -276,38 +244,37 @@
         }
     }
 
+
     /**
      * Delete all stored env variables and commands
-     *
-     * @return void
-     * @uses   rcube_html::reset()
-     * @uses   self::$env
-     * @uses   self::$js_env
-     * @uses   self::$js_commands
-     * @uses   self::$object_handlers
      */
     public function reset()
     {
-        $this->env = array();
+        parent::reset();
         $this->js_env = array();
         $this->js_labels = array();
         $this->js_commands = array();
-        $this->object_handlers = array();
-        parent::reset();
+        $this->script_files = array();
+        $this->scripts      = array();
+        $this->header       = '';
+        $this->footer       = '';
+        $this->body         = '';
     }
+
 
     /**
      * Redirect to a certain url
      *
-     * @param mixed Either a string with the action or url parameters as key-value pairs
-     * @see rcmail::url()
+     * @param mixed $p     Either a string with the action or url parameters as key-value pairs
+     * @param int   $delay Delay in seconds
      */
-    public function redirect($p = array())
+    public function redirect($p = array(), $delay = 1)
     {
         $location = $this->app->url($p);
         header('Location: ' . $location);
         exit;
     }
+
 
     /**
      * Send the request output to the client.
@@ -321,7 +288,7 @@
         if ($templ != 'iframe') {
             // prevent from endless loops
             if ($exit != 'recur' && $this->app->plugins->is_processing('render_page')) {
-                raise_error(array('code' => 505, 'type' => 'php',
+                rcube::raise_error(array('code' => 505, 'type' => 'php',
                   'file' => __FILE__, 'line' => __LINE__,
                   'message' => 'Recursion alert: ignoring output->send()'), true, false);
                 return;
@@ -342,12 +309,11 @@
         }
     }
 
+
     /**
      * Process template and write to stdOut
      *
-     * @param string HTML template
-     * @see rcube_html_page::write()
-     * @override
+     * @param string $template HTML template content
      */
     public function write($template = '')
     {
@@ -374,8 +340,9 @@
             header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe));
 
         // call super method
-        parent::write($template, $this->config['skin_path']);
+        $this->_write($template, $this->config->get('skin_path'));
     }
+
 
     /**
      * Parse a specific skin template and deliver to stdout (or return)
@@ -388,7 +355,7 @@
      */
     function parse($name = 'main', $exit = true, $write = true)
     {
-        $skin_path = $this->config['skin_path'];
+        $skin_path = $this->config->get('skin_path');
         $plugin    = false;
         $realname  = $name;
         $temp      = explode('.', $name, 2);
@@ -399,7 +366,7 @@
         if (count($temp) > 1) {
             $plugin    = $temp[0];
             $name      = $temp[1];
-            $skin_dir  = $plugin . '/skins/' . $this->config['skin'];
+            $skin_dir  = $plugin . '/skins/' . $this->config->get('skin');
             $skin_path = $this->plugin_skin_path = $this->app->plugins->dir . $skin_dir;
 
             // fallback to default skin
@@ -414,16 +381,16 @@
         if (!is_readable($path) && $this->deprecated_templates[$realname]) {
             $path = "$skin_path/templates/".$this->deprecated_templates[$realname].".html";
             if (is_readable($path))
-                raise_error(array('code' => 502, 'type' => 'php',
+                rcube::raise_error(array('code' => 502, 'type' => 'php',
                     'file' => __FILE__, 'line' => __LINE__,
                     'message' => "Using deprecated template '".$this->deprecated_templates[$realname]
-                        ."' in ".$this->config['skin_path']."/templates. Please rename to '".$realname."'"),
+                        ."' in $skin_path/templates. Please rename to '".$realname."'"),
                 true, false);
         }
 
         // read template file
         if (($templ = @file_get_contents($path)) === false) {
-            raise_error(array(
+            rcube::raise_error(array(
                 'code' => 501,
                 'type' => 'php',
                 'line' => __LINE__,
@@ -458,7 +425,7 @@
 
         if ($write) {
             // add debug console
-            if ($realname != 'error' && ($this->config['debug_level'] & 8)) {
+            if ($realname != 'error' && ($this->config->get('debug_level') & 8)) {
                 $this->add_footer('<div id="console" style="position:absolute;top:5px;left:5px;width:405px;padding:2px;background:white;z-index:9000;display:none">
                     <a href="#toggle" onclick="con=$(\'#dbgconsole\');con[con.is(\':visible\')?\'hide\':\'show\']();return false">console</a>
                     <textarea name="console" id="dbgconsole" rows="20" cols="40" style="display:none;width:400px;border:none;font-size:10px" spellcheck="false"></textarea></div>'
@@ -480,16 +447,17 @@
         }
     }
 
+
     /**
      * Return executable javascript code for all registered commands
      *
      * @return string $out
      */
-    private function get_js_commands()
+    protected function get_js_commands()
     {
         $out = '';
         if (!$this->framed && !empty($this->js_env)) {
-            $out .= JS_OBJECT_NAME . '.set_env('.json_serialize($this->js_env).");\n";
+            $out .= JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
         }
         if (!empty($this->js_labels)) {
             $this->command('add_label', $this->js_labels);
@@ -497,7 +465,7 @@
         foreach ($this->js_commands as $i => $args) {
             $method = array_shift($args);
             foreach ($args as $i => $arg) {
-                $args[$i] = json_serialize($arg);
+                $args[$i] = self::json_serialize($arg);
             }
             $parent = $this->framed || preg_match('/^parent\./', $method);
             $out .= sprintf(
@@ -511,6 +479,7 @@
         return $out;
     }
 
+
     /**
      * Make URLs starting with a slash point to skin directory
      *
@@ -520,9 +489,27 @@
     public function abs_url($str)
     {
         if ($str[0] == '/')
-            return $this->config['skin_path'] . $str;
+            return $this->config->get('skin_path') . $str;
         else
             return $str;
+    }
+
+
+    /**
+     * Show error page and terminate script execution
+     *
+     * @param int    $code     Error code
+     * @param string $message  Error message
+     */
+    public function raise_error($code, $message)
+    {
+        global $__page_content, $ERROR_CODE, $ERROR_MESSAGE;
+
+        $ERROR_CODE    = $code;
+        $ERROR_MESSAGE = $message;
+
+        include INSTALL_PATH . 'program/steps/utils/error.inc';
+        exit;
     }
 
 
@@ -532,29 +519,32 @@
      * Replace all strings ($varname)
      * with the content of the according global variable.
      */
-    private function parse_with_globals($input)
+    protected function parse_with_globals($input)
     {
-        $GLOBALS['__version'] = Q(RCMAIL_VERSION);
-        $GLOBALS['__comm_path'] = Q($this->app->comm_path);
-        $GLOBALS['__skin_path'] = Q($this->config['skin_path']);
+        $GLOBALS['__version']   = html::quote(RCMAIL_VERSION);
+        $GLOBALS['__comm_path'] = html::quote($this->app->comm_path);
+        $GLOBALS['__skin_path'] = Q($this->config->get('skin_path'));
+
         return preg_replace_callback('/\$(__[a-z0-9_\-]+)/',
-	    array($this, 'globals_callback'), $input);
+            array($this, 'globals_callback'), $input);
     }
+
 
     /**
      * Callback funtion for preg_replace_callback() in parse_with_globals()
      */
-    private function globals_callback($matches)
+    protected function globals_callback($matches)
     {
         return $GLOBALS[$matches[1]];
     }
+
 
     /**
      * Public wrapper to dipp into template parsing.
      *
      * @param  string $input
      * @return string
-     * @uses   rcube_template::parse_xml()
+     * @uses   rcube_output_html::parse_xml()
      * @since  0.1-rc1
      */
     public function just_parse($input)
@@ -562,20 +552,21 @@
         return $this->parse_xml($input);
     }
 
+
     /**
      * Parse for conditional tags
      *
      * @param  string $input
      * @return string
      */
-    private function parse_conditions($input)
+    protected function parse_conditions($input)
     {
         $matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>\n?/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE);
         if ($matches && count($matches) == 4) {
             if (preg_match('/^(else|endif)$/i', $matches[1])) {
                 return $matches[0] . $this->parse_conditions($matches[3]);
             }
-            $attrib = parse_attrib_string($matches[2]);
+            $attrib = html::parse_attrib_string($matches[2]);
             if (isset($attrib['condition'])) {
                 $condmet = $this->check_condition($attrib['condition']);
                 $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>\n?/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
@@ -588,7 +579,7 @@
                 }
                 return $matches[0] . $this->parse_conditions($result);
             }
-            raise_error(array(
+            rcube::raise_error(array(
                 'code' => 500,
                 'type' => 'php',
                 'line' => __LINE__,
@@ -608,7 +599,7 @@
      * @param  string Condition statement
      * @return boolean True if condition is met, False if not
      */
-    private function check_condition($condition)
+    protected function check_condition($condition)
     {
         return eval("return (".$this->parse_expression($condition).");");
     }
@@ -617,10 +608,10 @@
     /**
      * Inserts hidden field with CSRF-prevention-token into POST forms
      */
-    private function alter_form_tag($matches)
+    protected function alter_form_tag($matches)
     {
-        $out = $matches[0];
-        $attrib  = parse_attrib_string($matches[1]);
+        $out    = $matches[0];
+        $attrib = html::parse_attrib_string($matches[1]);
 
         if (strtolower($attrib['method']) == 'post') {
             $hidden = new html_hiddenfield(array('name' => '_token', 'value' => $this->app->get_request_token()));
@@ -637,7 +628,7 @@
      * @param  string Expression statement
      * @return string Expression value
      */
-    private function parse_expression($expression)
+    protected function parse_expression($expression)
     {
         return preg_replace(
             array(
@@ -653,7 +644,7 @@
                 "\$_SESSION['\\1']",
                 "\$this->app->config->get('\\1',get_boolean('\\3'))",
                 "\$this->env['\\1']",
-                "get_input_value('\\1', RCUBE_INPUT_GPC)",
+                "rcube_ui::get_input_value('\\1', rcube_ui::INPUT_GPC)",
                 "\$_COOKIE['\\1']",
                 "\$this->browser->{'\\1'}",
                 $this->template_name,
@@ -671,7 +662,7 @@
      * @todo   Use DOM-parser to traverse template HTML
      * @todo   Maybe a cache.
      */
-    private function parse_xml($input)
+    protected function parse_xml($input)
     {
         return preg_replace_callback('/<roundcube:([-_a-z]+)\s+((?:[^>]|\\\\>)+)(?<!\\\\)>/Ui', array($this, 'xml_command'), $input);
     }
@@ -684,10 +675,10 @@
      * @param  array Matches array of preg_replace_callback
      * @return string Tag/Object content
      */
-    private function xml_command($matches)
+    protected function xml_command($matches)
     {
         $command = strtolower($matches[1]);
-        $attrib  = parse_attrib_string($matches[2]);
+        $attrib  = html::parse_attrib_string($matches[2]);
 
         // empty output if required condition is not met
         if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition'])) {
@@ -706,20 +697,20 @@
             // show a label
             case 'label':
                 if ($attrib['name'] || $attrib['command']) {
-                    $vars = $attrib + array('product' => $this->config['product_name']);
+                    $vars = $attrib + array('product' => $this->config->get('product_name'));
                     unset($vars['name'], $vars['command']);
-                    $label = rcube_label($attrib + array('vars' => $vars));
-                    return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : Q($label)) : '';
+                    $label = $this->app->gettext($attrib + array('vars' => $vars));
+                    return !$attrib['noshow'] ? (get_boolean((string)$attrib['html']) ? $label : html::quote($label)) : '';
                 }
                 break;
 
             // include a file
             case 'include':
                 if (!$this->plugin_skin_path || !is_file($path = realpath($this->plugin_skin_path . $attrib['file'])))
-                    $path = realpath(($attrib['skin_path'] ? $attrib['skin_path'] : $this->config['skin_path']).$attrib['file']);
-                
+                    $path = realpath(($attrib['skin_path'] ? $attrib['skin_path'] : $this->config->get('skin_path')).$attrib['file']);
+
                 if (is_readable($path)) {
-                    if ($this->config['skin_include_php']) {
+                    if ($this->config->get('skin_include_php')) {
                         $incl = $this->include_php($path);
                     }
                     else {
@@ -765,13 +756,13 @@
                 }
                 else if ($object == 'logo') {
                     $attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));
-                    if ($this->config['skin_logo'])
-                        $attrib['src'] = $this->config['skin_logo'];
+                    if ($logo = $this->config->get('skin_logo'))
+                        $attrib['src'] = $logo;
                     $content = html::img($attrib);
                 }
                 else if ($object == 'productname') {
-                    $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'Roundcube Webmail';
-                    $content = Q($name);
+                    $name = $this->config->get('product_name', 'Roundcube Webmail');
+                    $content = html::quote($name);
                 }
                 else if ($object == 'version') {
                     $ver = (string)RCMAIL_VERSION;
@@ -779,20 +770,20 @@
                         if (preg_match('/Revision:\s(\d+)/', @shell_exec('svn info'), $regs))
                           $ver .= ' [SVN r'.$regs[1].']';
                     }
-                    $content = Q($ver);
+                    $content = html::quote($ver);
                 }
                 else if ($object == 'steptitle') {
-                  $content = Q($this->get_pagetitle());
+                  $content = html::quote($this->get_pagetitle());
                 }
                 else if ($object == 'pagetitle') {
-                    if (!empty($this->config['devel_mode']) && !empty($_SESSION['username']))
-                      $title = $_SESSION['username'].' :: ';
-                    else if (!empty($this->config['product_name']))
-                      $title = $this->config['product_name'].' :: ';
+                    if ($this->config->get('devel_mode') && !empty($_SESSION['username']))
+                        $title = $_SESSION['username'].' :: ';
+                    else if ($prod_name = $this->config->get('product_name'))
+                        $title = $prod_name . ' :: ';
                     else
-                      $title = '';
+                        $title = '';
                     $title .= $this->get_pagetitle();
-                    $content = Q($title);
+                    $content = html::quote($title);
                 }
 
                 // exec plugin hooks for this template object
@@ -802,7 +793,7 @@
             // return code for a specified eval expression
             case 'exp':
                 $value = $this->parse_expression($attrib['expression']);
-                return eval("return Q($value);");
+                return eval("return html::quote($value);");
 
             // return variable
             case 'var':
@@ -815,13 +806,13 @@
                         $value = $this->env[$name];
                         break;
                     case 'config':
-                        $value = $this->config[$name];
+                        $value = $this->config->get($name);
                         if (is_array($value) && $value[$_SESSION['storage_host']]) {
                             $value = $value[$_SESSION['storage_host']];
                         }
                         break;
                     case 'request':
-                        $value = get_input_value($name, RCUBE_INPUT_GPC);
+                        $value = rcube_ui::get_input_value($name, rcube_ui::INPUT_GPC);
                         break;
                     case 'session':
                         $value = $_SESSION[$name];
@@ -838,11 +829,12 @@
                     $value = implode(', ', $value);
                 }
 
-                return Q($value);
+                return html::quote($value);
                 break;
         }
         return '';
     }
+
 
     /**
      * Include a specific file and return it's contents
@@ -850,7 +842,7 @@
      * @param string File path
      * @return string Contents of the processed file
      */
-    private function include_php($file)
+    protected function include_php($file)
     {
         ob_start();
         include $file;
@@ -859,6 +851,7 @@
 
         return $out;
     }
+
 
     /**
      * Create and register a button
@@ -901,13 +894,13 @@
         }
         // get localized text for labels and titles
         if ($attrib['title']) {
-            $attrib['title'] = Q(rcube_label($attrib['title'], $attrib['domain']));
+            $attrib['title'] = html::quote($this->app->gettext($attrib['title'], $attrib['domain']));
         }
         if ($attrib['label']) {
-            $attrib['label'] = Q(rcube_label($attrib['label'], $attrib['domain']));
+            $attrib['label'] = html::quote($this->app->gettext($attrib['label'], $attrib['domain']));
         }
         if ($attrib['alt']) {
-            $attrib['alt'] = Q(rcube_label($attrib['alt'], $attrib['domain']));
+            $attrib['alt'] = html::quote($this->app->gettext($attrib['alt'], $attrib['domain']));
         }
 
         // set title to alt attribute for IE browsers
@@ -935,14 +928,14 @@
 
             // make valid href to specific buttons
             if (in_array($attrib['command'], rcmail::$main_tasks)) {
-                $attrib['href'] = rcmail_url(null, null, $attrib['command']);
+                $attrib['href']    = $this->app->url(array('task' => $attrib['command']));
                 $attrib['onclick'] = sprintf("%s.command('switch-task','%s');return false", JS_OBJECT_NAME, $attrib['command']);
             }
             else if ($attrib['task'] && in_array($attrib['task'], rcmail::$main_tasks)) {
-                $attrib['href'] = rcmail_url($attrib['command'], null, $attrib['task']);
+                $attrib['href'] = $this->app->url(array('action' => $attrib['command'], 'task' => $attrib['task']));
             }
             else if (in_array($attrib['command'], $a_static_commands)) {
-                $attrib['href'] = rcmail_url($attrib['command']);
+                $attrib['href'] = $this->app->url(array('action' => $attrib['command']));
             }
             else if ($attrib['command'] == 'permaurl' && !empty($this->env['permaurl'])) {
               $attrib['href'] = $this->env['permaurl'];
@@ -969,7 +962,7 @@
         $out = '';
 
         // generate image tag
-        if ($attrib['type']=='image') {
+        if ($attrib['type'] == 'image') {
             $attrib_str = html::attrib_string(
                 $attrib,
                 array(
@@ -983,13 +976,13 @@
             }
             $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'target');
         }
-        else if ($attrib['type']=='link') {
+        else if ($attrib['type'] == 'link') {
             $btn_content = isset($attrib['content']) ? $attrib['content'] : ($attrib['label'] ? $attrib['label'] : $attrib['command']);
             $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style', 'tabindex', 'target');
             if ($attrib['innerclass'])
                 $btn_content = html::span($attrib['innerclass'], $btn_content);
         }
-        else if ($attrib['type']=='input') {
+        else if ($attrib['type'] == 'input') {
             $attrib['type'] = 'button';
 
             if ($attrib['label']) {
@@ -1009,6 +1002,244 @@
         }
 
         return $out;
+    }
+
+
+    /**
+     * Link an external script file
+     *
+     * @param string File URL
+     * @param string Target position [head|foot]
+     */
+    public function include_script($file, $position='head')
+    {
+        static $sa_files = array();
+
+        if (!preg_match('|^https?://|i', $file) && $file[0] != '/') {
+            $file = $this->scripts_path . $file;
+            if ($fs = @filemtime($file)) {
+                $file .= '?s=' . $fs;
+            }
+        }
+
+        if (in_array($file, $sa_files)) {
+            return;
+        }
+
+        $sa_files[] = $file;
+
+        if (!is_array($this->script_files[$position])) {
+            $this->script_files[$position] = array();
+        }
+
+        $this->script_files[$position][] = $file;
+    }
+
+
+    /**
+     * Add inline javascript code
+     *
+     * @param string JS code snippet
+     * @param string Target position [head|head_top|foot]
+     */
+    public function add_script($script, $position='head')
+    {
+        if (!isset($this->scripts[$position])) {
+            $this->scripts[$position] = "\n" . rtrim($script);
+        }
+        else {
+            $this->scripts[$position] .= "\n" . rtrim($script);
+        }
+    }
+
+
+    /**
+     * Link an external css file
+     *
+     * @param string File URL
+     */
+    public function include_css($file)
+    {
+        $this->css_files[] = $file;
+    }
+
+
+    /**
+     * Add HTML code to the page header
+     *
+     * @param string $str HTML code
+     */
+    public function add_header($str)
+    {
+        $this->header .= "\n" . $str;
+    }
+
+
+    /**
+     * Add HTML code to the page footer
+     * To be added right befor </body>
+     *
+     * @param string $str HTML code
+     */
+    public function add_footer($str)
+    {
+        $this->footer .= "\n" . $str;
+    }
+
+
+    /**
+     * Process template and write to stdOut
+     *
+     * @param string HTML template
+     * @param string Base for absolute paths
+     */
+    public function _write($templ = '', $base_path = '')
+    {
+        $output = empty($templ) ? $this->default_template : trim($templ);
+
+        // set default page title
+        if (empty($this->pagetitle)) {
+            $this->pagetitle = 'Roundcube Mail';
+        }
+
+        // replace specialchars in content
+        $page_title  = html::quote($this->pagetitle);
+        $page_header = '';
+        $page_footer = '';
+
+        // include meta tag with charset
+        if (!empty($this->charset)) {
+            if (!headers_sent()) {
+                header('Content-Type: text/html; charset=' . $this->charset);
+            }
+            $page_header = '<meta http-equiv="content-type"';
+            $page_header.= ' content="text/html; charset=';
+            $page_header.= $this->charset . '" />'."\n";
+        }
+
+        // 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 .= html::script($file);
+            }
+        }
+
+        $head_script = $this->scripts['head_top'] . $this->scripts['head'];
+        if (!empty($head_script)) {
+            $page_header .= html::script(array(), $head_script);
+        }
+
+        if (!empty($this->header)) {
+            $page_header .= $this->header;
+        }
+
+        // put docready commands into page footer
+        if (!empty($this->scripts['docready'])) {
+            $this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot');
+        }
+
+        if (is_array($this->script_files['foot'])) {
+            foreach ($this->script_files['foot'] as $file) {
+                $page_footer .= html::script($file);
+            }
+        }
+
+        if (!empty($this->footer)) {
+            $page_footer .= $this->footer . "\n";
+        }
+
+        if (!empty($this->scripts['foot'])) {
+            $page_footer .= html::script(array(), $this->scripts['foot']);
+        }
+
+        // find page header
+        if ($hpos = stripos($output, '</head>')) {
+            $page_header .= "\n";
+        }
+        else {
+            if (!is_numeric($hpos)) {
+                $hpos = stripos($output, '<body');
+            }
+            if (!is_numeric($hpos) && ($hpos = stripos($output, '<html'))) {
+                while ($output[$hpos] != '>') {
+                    $hpos++;
+                }
+                $hpos++;
+            }
+            $page_header = "<head>\n<title>$page_title</title>\n$page_header\n</head>\n";
+        }
+
+        // add page hader
+        if ($hpos) {
+            $output = substr_replace($output, $page_header, $hpos, 0);
+        }
+        else {
+            $output = $page_header . $output;
+        }
+
+        // add page footer
+        if (($fpos = strripos($output, '</body>')) || ($fpos = strripos($output, '</html>'))) {
+            $output = substr_replace($output, $page_footer."\n", $fpos, 0);
+        }
+        else {
+            $output .= "\n".$page_footer;
+        }
+
+        // add css files in head, before scripts, for speed up with parallel downloads
+        if (!empty($this->css_files) && 
+            (($pos = stripos($output, '<script ')) || ($pos = stripos($output, '</head>')))
+        ) {
+            $css = '';
+            foreach ($this->css_files as $file) {
+                $css .= html::tag('link', array('rel' => 'stylesheet',
+                    'type' => 'text/css', 'href' => $file, 'nl' => true));
+            }
+            $output = substr_replace($output, $css, $pos, 0);
+        }
+
+        $this->base_path = $base_path;
+
+        // correct absolute paths in images and other tags
+        // add timestamp to .js and .css filename
+        $output = preg_replace_callback(
+            '!(src|href|background)=(["\']?)([a-z0-9/_.-]+)(["\'\s>])!i',
+            array($this, 'file_callback'), $output);
+
+        // trigger hook with final HTML content to be sent
+        $hook = rcmail::get_instance()->plugins->exec_hook("send_page", array('content' => $output));
+        if (!$hook['abort']) {
+            if ($this->charset != RCMAIL_CHARSET) {
+                echo rcube_charset::convert($hook['content'], RCMAIL_CHARSET, $this->charset);
+            }
+            else {
+                echo $hook['content'];
+            }
+        }
+    }
+
+
+    /**
+     * Callback function for preg_replace_callback in write()
+     *
+     * @return string Parsed string
+     */
+    protected function file_callback($matches)
+    {
+	    $file = $matches[3];
+
+        // correct absolute paths
+	    if ($file[0] == '/') {
+	        $file = $this->base_path . $file;
+        }
+
+        // add file modification timestamp
+	    if (preg_match('/\.(js|css)$/', $file)) {
+            if ($fs = @filemtime($file)) {
+                $file .= '?s=' . $fs;
+            }
+        }
+
+	    return $matches[1] . '=' . $matches[2] . $file . $matches[4];
     }
 
 
@@ -1108,15 +1339,15 @@
      * @param array Named parameters
      * @return string HTML code for the gui object
      */
-    private function login_form($attrib)
+    protected function login_form($attrib)
     {
-        $default_host = $this->config['default_host'];
-        $autocomplete = (int) $this->config['login_autocomplete'];
+        $default_host = $this->config->get('default_host');
+        $autocomplete = (int) $this->config->get('login_autocomplete');
 
         $_SESSION['temp'] = true;
 
         // save original url
-        $url = get_input_value('_url', RCUBE_INPUT_POST);
+        $url = rcube_ui::get_input_value('_url', rcube_ui::INPUT_POST);
         if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
             $url = $_SERVER['QUERY_STRING'];
 
@@ -1165,16 +1396,16 @@
         // create HTML table with two cols
         $table = new html_table(array('cols' => 2));
 
-        $table->add('title', html::label('rcmloginuser', Q(rcube_label('username'))));
-        $table->add('input', $input_user->show(get_input_value('_user', RCUBE_INPUT_GPC)));
+        $table->add('title', html::label('rcmloginuser', html::quote($this->app->gettext('username'))));
+        $table->add('input', $input_user->show(rcube_ui::get_input_value('_user', rcube_ui::INPUT_GPC)));
 
-        $table->add('title', html::label('rcmloginpwd', Q(rcube_label('password'))));
+        $table->add('title', html::label('rcmloginpwd', html::quote($this->app->gettext('password'))));
         $table->add('input', $input_pass->show());
 
         // add host selection row
         if (is_object($input_host) && !$hide_host) {
-            $table->add('title', html::label('rcmloginhost', Q(rcube_label('server'))));
-            $table->add('input', $input_host->show(get_input_value('_host', RCUBE_INPUT_GPC)));
+            $table->add('title', html::label('rcmloginhost', html::quote($this->app->gettext('server'))));
+            $table->add('input', $input_host->show(rcube_ui::get_input_value('_host', rcube_ui::INPUT_GPC)));
         }
 
         $out  = $input_task->show();
@@ -1204,7 +1435,7 @@
      * @param array Named parameters
      * @return void
      */
-    private function preloader($attrib)
+    protected function preloader($attrib)
     {
         $images = preg_split('/[\s\t\n,]+/', $attrib['images'], -1, PREG_SPLIT_NO_EMPTY);
         $images = array_map(array($this, 'abs_url'), $images);
@@ -1212,7 +1443,7 @@
         if (empty($images) || $this->app->task == 'logout')
             return;
 
-        $this->add_script('var images = ' . json_serialize($images) .';
+        $this->add_script('var images = ' . self::json_serialize($images) .';
             for (var i=0; i<images.length; i++) {
                 img = new Image();
                 img.src = images[i];
@@ -1227,7 +1458,7 @@
      * @param array Named parameters
      * @return string HTML code for the gui object
      */
-    private function search_form($attrib)
+    protected function search_form($attrib)
     {
         // add some labels to client
         $this->add_label('searching');
@@ -1265,14 +1496,15 @@
      * @param array Named tag parameters
      * @return string HTML code for the gui object
      */
-    private function message_container($attrib)
+    protected function message_container($attrib)
     {
         if (isset($attrib['id']) === false) {
             $attrib['id'] = 'rcmMessageContainer';
         }
 
         $this->add_gui_object('message', $attrib['id']);
-        return html::div($attrib, "");
+
+        return html::div($attrib, '');
     }
 
 
@@ -1282,7 +1514,7 @@
      * @param array Named parameters for the select tag
      * @return string HTML code for the gui object
      */
-    function charset_selector($attrib)
+    public function charset_selector($attrib)
     {
         // pass the following attributes to the form class
         $field_attrib = array('name' => '_charset');
@@ -1293,39 +1525,39 @@
         }
 
         $charsets = array(
-            'UTF-8'        => 'UTF-8 ('.rcube_label('unicode').')',
-            'US-ASCII'     => 'ASCII ('.rcube_label('english').')',
-            'ISO-8859-1'   => 'ISO-8859-1 ('.rcube_label('westerneuropean').')',
-            'ISO-8859-2'   => 'ISO-8859-2 ('.rcube_label('easterneuropean').')',
-            'ISO-8859-4'   => 'ISO-8859-4 ('.rcube_label('baltic').')',
-            'ISO-8859-5'   => 'ISO-8859-5 ('.rcube_label('cyrillic').')',
-            'ISO-8859-6'   => 'ISO-8859-6 ('.rcube_label('arabic').')',
-            'ISO-8859-7'   => 'ISO-8859-7 ('.rcube_label('greek').')',
-            'ISO-8859-8'   => 'ISO-8859-8 ('.rcube_label('hebrew').')',
-            'ISO-8859-9'   => 'ISO-8859-9 ('.rcube_label('turkish').')',
-            'ISO-8859-10'   => 'ISO-8859-10 ('.rcube_label('nordic').')',
-            'ISO-8859-11'   => 'ISO-8859-11 ('.rcube_label('thai').')',
-            'ISO-8859-13'   => 'ISO-8859-13 ('.rcube_label('baltic').')',
-            'ISO-8859-14'   => 'ISO-8859-14 ('.rcube_label('celtic').')',
-            'ISO-8859-15'   => 'ISO-8859-15 ('.rcube_label('westerneuropean').')',
-            'ISO-8859-16'   => 'ISO-8859-16 ('.rcube_label('southeasterneuropean').')',
-            'WINDOWS-1250' => 'Windows-1250 ('.rcube_label('easterneuropean').')',
-            'WINDOWS-1251' => 'Windows-1251 ('.rcube_label('cyrillic').')',
-            'WINDOWS-1252' => 'Windows-1252 ('.rcube_label('westerneuropean').')',
-            'WINDOWS-1253' => 'Windows-1253 ('.rcube_label('greek').')',
-            'WINDOWS-1254' => 'Windows-1254 ('.rcube_label('turkish').')',
-            'WINDOWS-1255' => 'Windows-1255 ('.rcube_label('hebrew').')',
-            'WINDOWS-1256' => 'Windows-1256 ('.rcube_label('arabic').')',
-            'WINDOWS-1257' => 'Windows-1257 ('.rcube_label('baltic').')',
-            'WINDOWS-1258' => 'Windows-1258 ('.rcube_label('vietnamese').')',
-            'ISO-2022-JP'  => 'ISO-2022-JP ('.rcube_label('japanese').')',
-            'ISO-2022-KR'  => 'ISO-2022-KR ('.rcube_label('korean').')',
-            'ISO-2022-CN'  => 'ISO-2022-CN ('.rcube_label('chinese').')',
-            'EUC-JP'       => 'EUC-JP ('.rcube_label('japanese').')',
-            'EUC-KR'       => 'EUC-KR ('.rcube_label('korean').')',
-            'EUC-CN'       => 'EUC-CN ('.rcube_label('chinese').')',
-            'BIG5'         => 'BIG5 ('.rcube_label('chinese').')',
-            'GB2312'       => 'GB2312 ('.rcube_label('chinese').')',
+            'UTF-8'        => 'UTF-8 ('.$this->app->gettext('unicode').')',
+            'US-ASCII'     => 'ASCII ('.$this->app->gettext('english').')',
+            'ISO-8859-1'   => 'ISO-8859-1 ('.$this->app->gettext('westerneuropean').')',
+            'ISO-8859-2'   => 'ISO-8859-2 ('.$this->app->gettext('easterneuropean').')',
+            'ISO-8859-4'   => 'ISO-8859-4 ('.$this->app->gettext('baltic').')',
+            'ISO-8859-5'   => 'ISO-8859-5 ('.$this->app->gettext('cyrillic').')',
+            'ISO-8859-6'   => 'ISO-8859-6 ('.$this->app->gettext('arabic').')',
+            'ISO-8859-7'   => 'ISO-8859-7 ('.$this->app->gettext('greek').')',
+            'ISO-8859-8'   => 'ISO-8859-8 ('.$this->app->gettext('hebrew').')',
+            'ISO-8859-9'   => 'ISO-8859-9 ('.$this->app->gettext('turkish').')',
+            'ISO-8859-10'   => 'ISO-8859-10 ('.$this->app->gettext('nordic').')',
+            'ISO-8859-11'   => 'ISO-8859-11 ('.$this->app->gettext('thai').')',
+            'ISO-8859-13'   => 'ISO-8859-13 ('.$this->app->gettext('baltic').')',
+            'ISO-8859-14'   => 'ISO-8859-14 ('.$this->app->gettext('celtic').')',
+            'ISO-8859-15'   => 'ISO-8859-15 ('.$this->app->gettext('westerneuropean').')',
+            'ISO-8859-16'   => 'ISO-8859-16 ('.$this->app->gettext('southeasterneuropean').')',
+            'WINDOWS-1250' => 'Windows-1250 ('.$this->app->gettext('easterneuropean').')',
+            'WINDOWS-1251' => 'Windows-1251 ('.$this->app->gettext('cyrillic').')',
+            'WINDOWS-1252' => 'Windows-1252 ('.$this->app->gettext('westerneuropean').')',
+            'WINDOWS-1253' => 'Windows-1253 ('.$this->app->gettext('greek').')',
+            'WINDOWS-1254' => 'Windows-1254 ('.$this->app->gettext('turkish').')',
+            'WINDOWS-1255' => 'Windows-1255 ('.$this->app->gettext('hebrew').')',
+            'WINDOWS-1256' => 'Windows-1256 ('.$this->app->gettext('arabic').')',
+            'WINDOWS-1257' => 'Windows-1257 ('.$this->app->gettext('baltic').')',
+            'WINDOWS-1258' => 'Windows-1258 ('.$this->app->gettext('vietnamese').')',
+            'ISO-2022-JP'  => 'ISO-2022-JP ('.$this->app->gettext('japanese').')',
+            'ISO-2022-KR'  => 'ISO-2022-KR ('.$this->app->gettext('korean').')',
+            'ISO-2022-CN'  => 'ISO-2022-CN ('.$this->app->gettext('chinese').')',
+            'EUC-JP'       => 'EUC-JP ('.$this->app->gettext('japanese').')',
+            'EUC-KR'       => 'EUC-KR ('.$this->app->gettext('korean').')',
+            'EUC-CN'       => 'EUC-CN ('.$this->app->gettext('chinese').')',
+            'BIG5'         => 'BIG5 ('.$this->app->gettext('chinese').')',
+            'GB2312'       => 'GB2312 ('.$this->app->gettext('chinese').')',
         );
 
         if (!empty($_POST['_charset']))
@@ -1348,7 +1580,7 @@
     /**
      * Include content from config/about.<LANG>.html if available
      */
-    private function about_content($attrib)
+    protected function about_content($attrib)
     {
         $content = '';
         $filenames = array(
@@ -1369,6 +1601,4 @@
         return $content;
     }
 
-}  // end class rcube_template
-
-
+}
diff --git a/program/include/rcube_json_output.php b/program/include/rcube_output_json.php
similarity index 77%
rename from program/include/rcube_json_output.php
rename to program/include/rcube_output_json.php
index f062d4b..73cf767 100644
--- a/program/include/rcube_json_output.php
+++ b/program/include/rcube_output_json.php
@@ -2,21 +2,20 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_json_output.php                                 |
+ | program/include/rcube_output_json.php                                 |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2008-2010, The Roundcube Dev Team                       |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
  | See the README file for a full license statement.                     |
  |                                                                       |
  | PURPOSE:                                                              |
- |   Class to handle HTML page output using a skin template.             |
- |   Extends rcube_html_page class from rcube_shared.inc                 |
- |                                                                       |
+ |   Class to handle JSON (AJAX) output                                  |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 
  $Id$
@@ -29,34 +28,15 @@
  *
  * @package View
  */
-class rcube_json_output
+class rcube_output_json extends rcube_output
 {
-    /**
-     * Stores configuration object.
-     *
-     * @var rcube_config
-     */
-    private $config;
-    private $charset = RCMAIL_CHARSET;
-    private $texts = array();
-    private $commands = array();
-    private $callbacks = array();
-    private $message = null;
+    protected $texts = array();
+    protected $commands = array();
+    protected $callbacks = array();
+    protected $message = null;
 
-    public $browser;
-    public $env = array();
     public $type = 'js';
     public $ajax_call = true;
-
-
-    /**
-     * Constructor
-     */
-    public function __construct($task=null)
-    {
-        $this->config  = rcmail::get_instance()->config;
-        $this->browser = new rcube_browser();
-    }
 
 
     /**
@@ -88,31 +68,10 @@
 
 
     /**
-     * @ignore
-     */
-    function set_charset($charset)
-    {
-        // ignore: $this->charset = $charset;
-    }
-
-
-    /**
-     * Get charset for output
-     *
-     * @return string Output charset
-     */
-    function get_charset()
-    {
-        return $this->charset;
-    }
-
-
-    /**
      * Register a template object handler
      *
      * @param  string $obj Object name
      * @param  string $func Function name to call
-     * @return void
      */
     public function add_handler($obj, $func)
     {
@@ -124,7 +83,6 @@
      * Register a list of template object handlers
      *
      * @param  array $arr Hash array with object=>handler pairs
-     * @return void
      */
     public function add_handlers($arr)
     {
@@ -159,7 +117,7 @@
             $args = $args[0];
 
         foreach ($args as $name) {
-            $this->texts[$name] = rcube_label($name);
+            $this->texts[$name] = $this->app->gettext($name);
         }
     }
 
@@ -177,10 +135,11 @@
     public function show_message($message, $type='notice', $vars=null, $override=true, $timeout=0)
     {
         if ($override || !$this->message) {
-            if (rcube_label_exists($message)) {
-                if (!empty($vars))
-                    $vars = array_map('Q', $vars);
-                $msgtext = rcube_label(array('name' => $message, 'vars' => $vars));
+            if ($this->app->text_exists($message)) {
+                if (!empty($vars)) {
+                    $vars = array_map(array('rcube_ui', 'Q'), $vars);
+                }
+                $msgtext = $this->app->gettext(array('name' => $message, 'vars' => $vars));
             }
             else
                 $msgtext = $message;
@@ -196,7 +155,7 @@
      */
     public function reset()
     {
-        $this->env = array();
+        parent::reset();
         $this->texts = array();
         $this->commands = array();
     }
@@ -228,6 +187,20 @@
 
 
     /**
+     * Show error page and terminate script execution
+     *
+     * @param int    $code     Error code
+     * @param string $message  Error message
+     */
+    public function raise_error($code, $message)
+    {
+        $this->show_message("Application Error ($code): $message", 'error');
+        $this->remote_response();
+        exit;
+    }
+
+
+    /**
      * Send an AJAX response with executable JS code
      *
      * @param  string  $add Additional JS code
@@ -235,13 +208,13 @@
      * @return void
      * @deprecated
      */
-    public function remote_response($add='')
+    protected function remote_response($add='')
     {
         static $s_header_sent = false;
 
         if (!$s_header_sent) {
             $s_header_sent = true;
-            send_nocacheing_headers();
+            $this->nocacheing_headers();
             header('Content-Type: text/plain; charset=' . $this->get_charset());
         }
 
@@ -251,7 +224,7 @@
         $rcmail = rcmail::get_instance();
         $response['action'] = $rcmail->action;
 
-        if ($unlock = get_input_value('_unlock', RCUBE_INPUT_GPC)) {
+        if ($unlock = rcube_ui::get_input_value('_unlock', rcube_ui::INPUT_GPC)) {
             $response['unlock'] = $unlock;
         }
 
@@ -267,7 +240,7 @@
         if (!empty($this->callbacks))
             $response['callbacks'] = $this->callbacks;
 
-        echo json_serialize($response);
+        echo self::json_serialize($response);
     }
 
 
@@ -276,14 +249,14 @@
      *
      * @return string $out
      */
-    private function get_js_commands()
+    protected function get_js_commands()
     {
         $out = '';
 
         foreach ($this->commands as $i => $args) {
             $method = array_shift($args);
             foreach ($args as $i => $arg) {
-                $args[$i] = json_serialize($arg);
+                $args[$i] = self::json_serialize($arg);
             }
 
             $out .= sprintf(
diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php
index aeb05af..e20f7ce 100644
--- a/program/include/rcube_plugin.php
+++ b/program/include/rcube_plugin.php
@@ -110,9 +110,10 @@
   public function load_config($fname = 'config.inc.php')
   {
     $fpath = $this->home.'/'.$fname;
-    $rcmail = rcmail::get_instance();
+    $rcmail = rcube::get_instance();
     if (is_file($fpath) && !$rcmail->config->load_from_file($fpath)) {
-      raise_error(array('code' => 527, 'type' => 'php',
+      rcube::raise_error(array(
+        'code' => 527, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Failed to load config from $fpath"), true, false);
       return false;
@@ -176,7 +177,7 @@
       foreach ($texts as $key => $value)
         $add[$domain.'.'.$key] = $value;
 
-      $rcmail = rcmail::get_instance();
+      $rcmail = rcube::get_instance();
       $rcmail->load_language($lang, $add);
 
       // add labels to client
@@ -196,7 +197,7 @@
    */
   public function gettext($p)
   {
-    return rcmail::get_instance()->gettext($p, $this->ID);
+    return rcube::get_instance()->gettext($p, $this->ID);
   }
 
   /**
@@ -309,9 +310,10 @@
    */
   public function local_skin_path()
   {
-      $skin_path = 'skins/'.$this->api->config->get('skin');
-      if (!is_dir(realpath(slashify($this->home) . $skin_path)))
-        $skin_path = 'skins/default';
+    $rcmail = rcube::get_instance();
+    $skin_path = 'skins/' . $rcmail->config->get('skin');
+    if (!is_dir(realpath(slashify($this->home) . $skin_path)))
+      $skin_path = 'skins/default';
     return $skin_path;
   }
 
diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php
index be12f11..95fd3b6 100644
--- a/program/include/rcube_plugin_api.php
+++ b/program/include/rcube_plugin_api.php
@@ -22,6 +22,11 @@
 
 */
 
+// location where plugins are loade from
+if (!defined('RCMAIL_PLUGINS_DIR'))
+  define('RCMAIL_PLUGINS_DIR', INSTALL_PATH . 'plugins/');
+
+
 /**
  * The plugin loader and global API
  *
@@ -33,8 +38,8 @@
   
   public $dir;
   public $url = 'plugins/';
+  public $task = '';
   public $output;
-  public $config;
   
   public $handlers = array();
   private $plugins = array();
@@ -43,7 +48,6 @@
   private $actionmap = array();
   private $objectsmap = array();
   private $template_contents = array();
-  private $required_plugins = array('filesystem_attachments', 'jqueryui');
   private $active_hook = false;
 
   // Deprecated names of hooks, will be removed after 0.5-stable release
@@ -99,7 +103,28 @@
    */
   private function __construct()
   {
-    $this->dir = INSTALL_PATH . $this->url;
+    $this->dir = slashify(RCMAIL_PLUGINS_DIR);
+  }
+
+
+  /**
+   * Initialize plugin engine
+   *
+   * This has to be done after rcmail::load_gui() or rcmail::json_init()
+   * was called because plugins need to have access to rcmail->output
+   *
+   * @param object rcube Instance of the rcube base class
+   * @param string Current application task (used for conditional plugin loading)
+   */
+  public function init($app, $task = '')
+  {
+    $this->task = $task;
+    $this->output = $app->output;
+
+    // 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
   }
 
 
@@ -108,20 +133,18 @@
    *
    * This has to be done after rcmail::load_gui() or rcmail::json_init()
    * was called because plugins need to have access to rcmail->output
+   *
+   * @param array List of configured plugins to load
+   * @param array List of plugins required by the application
    */
-  public function init()
+  public function load_plugins($plugins_enabled, $required_plugins = array())
   {
-    $rcmail = rcmail::get_instance();
-    $this->output = $rcmail->output;
-    $this->config = $rcmail->config;
-
-    $plugins_enabled = (array)$rcmail->config->get('plugins', array());
     foreach ($plugins_enabled as $plugin_name) {
       $this->load_plugin($plugin_name);
     }
 
     // check existance of all required core plugins
-    foreach ($this->required_plugins as $plugin_name) {
+    foreach ($required_plugins as $plugin_name) {
       $loaded = false;
       foreach ($this->plugins as $plugin) {
         if ($plugin instanceof $plugin_name) {
@@ -136,18 +159,12 @@
 
       // trigger fatal error if still not loaded
       if (!$loaded) {
-        raise_error(array('code' => 520, 'type' => 'php',
+        rcube::raise_error(array('code' => 520, 'type' => 'php',
           'file' => __FILE__, 'line' => __LINE__,
           '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
   }
-
 
   /**
    * Load the specified plugin
@@ -158,8 +175,6 @@
   public function load_plugin($plugin_name)
   {
     static $plugins_dir;
-
-    $rcmail = rcmail::get_instance();
 
     if (!$plugins_dir) {
       $dir = dir($this->dir);
@@ -181,8 +196,8 @@
         // check inheritance...
         if (is_subclass_of($plugin, 'rcube_plugin')) {
           // ... task, request type and framed mode
-          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task))
-              && (!$plugin->noajax || (is_object($rcmail->output) && is_a($rcmail->output, 'rcube_template')))
+          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
+              && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
               && (!$plugin->noframe || empty($_REQUEST['_framed']))
           ) {
             $plugin->init();
@@ -192,13 +207,13 @@
         }
       }
       else {
-        raise_error(array('code' => 520, 'type' => 'php',
+        rcube::raise_error(array('code' => 520, 'type' => 'php',
           'file' => __FILE__, 'line' => __LINE__,
           'message' => "No plugin class $plugin_name found in $fn"), true, false);
       }
     }
     else {
-      raise_error(array('code' => 520, 'type' => 'php',
+      rcube::raise_error(array('code' => 520, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Failed to load plugin file $fn"), true, false);
     }
@@ -217,7 +232,7 @@
   {
     if (is_callable($callback)) {
       if (isset($this->deprecated_hooks[$hook])) {
-        raise_error(array('code' => 522, 'type' => 'php',
+        rcube::raise_error(array('code' => 522, 'type' => 'php',
           'file' => __FILE__, 'line' => __LINE__,
           'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false);
         $hook = $this->deprecated_hooks[$hook];
@@ -225,7 +240,7 @@
       $this->handlers[$hook][] = $callback;
     }
     else
-      raise_error(array('code' => 521, 'type' => 'php',
+      rcube::raise_error(array('code' => 521, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Invalid callback function for $hook"), true, false);
   }
@@ -297,7 +312,7 @@
       $this->actionmap[$action] = $owner;
     }
     else {
-      raise_error(array('code' => 523, 'type' => 'php',
+      rcube::raise_error(array('code' => 523, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Cannot register action $action; already taken by another plugin"), true, false);
     }
@@ -316,7 +331,7 @@
       call_user_func($this->actions[$action]);
     }
     else {
-      raise_error(array('code' => 524, 'type' => 'php',
+      rcube::raise_error(array('code' => 524, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "No handler found for action $action"), true, true);
     }
@@ -337,14 +352,14 @@
       $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) {
+    if (is_object($this->output) && (!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',
+      rcube::raise_error(array('code' => 525, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Cannot register template handler $name; already taken by another plugin"), true, false);
+        'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false);
     }
   }
 
@@ -358,12 +373,12 @@
   public function register_task($task, $owner)
   {
     if ($task != asciiwords($task)) {
-      raise_error(array('code' => 526, 'type' => 'php',
+      rcube::raise_error(array('code' => 526, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false);
     }
     else if (in_array($task, rcmail::$main_tasks)) {
-      raise_error(array('code' => 526, 'type' => 'php',
+      rcube::raise_error(array('code' => 526, 'type' => 'php',
         'file' => __FILE__, 'line' => __LINE__,
         'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false);
     }
@@ -408,7 +423,7 @@
    */
   public function include_script($fn)
   {
-    if ($this->output->type == 'html') {
+    if (is_object($this->output) && $this->output->type == 'html') {
       $src = $this->resource_url($fn);
       $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
     }
@@ -422,7 +437,7 @@
    */
   public function include_stylesheet($fn)
   {
-    if ($this->output->type == 'html') {
+    if (is_object($this->output) && $this->output->type == 'html') {
       $src = $this->resource_url($fn);
       $this->output->include_css($src);
     }
diff --git a/program/include/rcube_session.php b/program/include/rcube_session.php
index e6e636e..da221c3 100644
--- a/program/include/rcube_session.php
+++ b/program/include/rcube_session.php
@@ -78,7 +78,7 @@
           array($this, 'gc'));
       }
       else {
-        raise_error(array('code' => 604, 'type' => 'db',
+        rcube::raise_error(array('code' => 604, 'type' => 'db',
           'line' => __LINE__, 'file' => __FILE__,
           'message' => "Failed to connect to memcached. Please check configuration"),
           true, true);
@@ -129,7 +129,7 @@
   public function db_read($key)
   {
     $sql_result = $this->db->query(
-      "SELECT vars, ip, changed FROM ".get_table_name('session')
+      "SELECT vars, ip, changed FROM ".$this->db->table_name('session')
       ." WHERE sess_id = ?", $key);
 
     if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
@@ -177,18 +177,18 @@
       if ($newvars !== $oldvars) {
         $this->db->query(
           sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?",
-            get_table_name('session'), $now),
+            $this->db->table_name('session'), $now),
           base64_encode($newvars), $key);
       }
       else if ($ts - $this->changed > $this->lifetime / 2) {
-        $this->db->query("UPDATE ".get_table_name('session')." SET changed=$now WHERE sess_id=?", $key);
+        $this->db->query("UPDATE ".$this->db->table_name('session')." SET changed=$now WHERE sess_id=?", $key);
       }
     }
     else {
       $this->db->query(
         sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ".
           "VALUES (?, ?, ?, %s, %s)",
-          get_table_name('session'), $now, $now),
+          $this->db->table_name('session'), $now, $now),
         $key, base64_encode($vars), (string)$this->ip);
     }
 
@@ -228,7 +228,7 @@
   public function db_destroy($key)
   {
     $this->db->query(
-      sprintf("DELETE FROM %s WHERE sess_id = ?", get_table_name('session')),
+      sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')),
       $key);
 
     return true;
@@ -246,7 +246,7 @@
     // just delete all expired sessions
     $this->db->query(
       sprintf("DELETE FROM %s WHERE changed < %s",
-        get_table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
+        $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime)));
 
     $this->gc();
 
@@ -322,8 +322,9 @@
    */
   public function gc()
   {
-    foreach ($this->gc_handlers as $fct)
+    foreach ($this->gc_handlers as $fct) {
       call_user_func($fct);
+    }
   }
 
 
@@ -624,14 +625,14 @@
     $auth_string = "$this->key,$this->secret,$timeslot";
     return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
   }
-  
+
   /**
    * 
    */
   function log($line)
   {
     if ($this->logging)
-      write_log('session', $line);
+      rcmail::write_log('session', $line);
   }
 
 }
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
index 9340091..2e94447 100644
--- a/program/include/rcube_shared.inc
+++ b/program/include/rcube_shared.inc
@@ -2,17 +2,17 @@
 
 /*
  +-----------------------------------------------------------------------+
- | rcube_shared.inc                                                      |
+ | program/include/rcube_shared.inc                                      |
  |                                                                       |
  | This file is part of the Roundcube PHP suite                          |
- | Copyright (C) 2005-2007, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
  | See the README file for a full license statement.                     |
  |                                                                       |
  | CONTENTS:                                                             |
- |   Shared functions and classes used in PHP projects                   |
+ |   Shared functions used by Roundcube Framework                        |
  |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
@@ -31,213 +31,95 @@
 
 
 /**
- * Send HTTP headers to prevent caching this page
- */
-function send_nocacheing_headers()
-{
-  global $OUTPUT;
-
-  if (headers_sent())
-    return;
-
-  header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
-  header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
-  // Request browser to disable DNS prefetching (CVE-2010-0464)
-  header("X-DNS-Prefetch-Control: off");
-
-  // We need to set the following headers to make downloads work using IE in HTTPS mode.
-  if ($OUTPUT->browser->ie && rcube_https_check()) {
-    header('Pragma: private');
-    header("Cache-Control: private, must-revalidate");
-  } else {
-    header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0");
-    header("Pragma: no-cache");
-  }
-}
-
-
-/**
- * Send header with expire date 30 days in future
- *
- * @param int Expiration time in seconds
- */
-function send_future_expire_header($offset=2600000)
-{
-  if (headers_sent())
-    return;
-
-  header("Expires: ".gmdate("D, d M Y H:i:s", mktime()+$offset)." GMT");
-  header("Cache-Control: max-age=$offset");
-  header("Pragma: ");
-}
-
-
-/**
  * Similar function as in_array() but case-insensitive
  *
- * @param mixed Needle value
- * @param array Array to search in
+ * @param string $needle    Needle value
+ * @param array  $heystack  Array to search in
+ *
  * @return boolean True if found, False if not
  */
 function in_array_nocase($needle, $haystack)
 {
-  $needle = mb_strtolower($needle);
-  foreach ($haystack as $value)
-    if ($needle===mb_strtolower($value))
-      return true;
+    $needle = mb_strtolower($needle);
+    foreach ($haystack as $value) {
+        if ($needle === mb_strtolower($value)) {
+            return true;
+        }
+    }
 
-  return false;
+    return false;
 }
 
 
 /**
- * Find out if the string content means TRUE or FALSE
+ * Find out if the string content means true or false
  *
- * @param string Input value
- * @return boolean Imagine what!
+ * @param string $str  Input value
+ *
+ * @return boolean Boolean value
  */
 function get_boolean($str)
 {
-  $str = strtolower($str);
-  if (in_array($str, array('false', '0', 'no', 'off', 'nein', ''), TRUE))
-    return FALSE;
-  else
-    return TRUE;
+    $str = strtolower($str);
+
+    return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true);
 }
 
 
 /**
- * Parse a human readable string for a number of bytes
+ * Parse a human readable string for a number of bytes.
  *
- * @param string Input string
+ * @param string $str  Input string
+ *
  * @return float Number of bytes
  */
 function parse_bytes($str)
 {
-  if (is_numeric($str))
-    return floatval($str);
-
-  if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs))
-  {
-    $bytes = floatval($regs[1]);
-    switch (strtolower($regs[2]))
-    {
-      case 'g':
-      case 'gb':
-        $bytes *= 1073741824;
-        break;
-      case 'm':
-      case 'mb':
-        $bytes *= 1048576;
-        break;
-      case 'k':
-      case 'kb':
-        $bytes *= 1024;
-        break;
+    if (is_numeric($str)) {
+        return floatval($str);
     }
-  }
 
-  return floatval($bytes);
-}
-
-/**
- * Create a human readable string for a number of bytes
- *
- * @param int Number of bytes
- * @return string Byte string
- */
-function show_bytes($bytes)
-{
-  if ($bytes >= 1073741824)
-  {
-    $gb = $bytes/1073741824;
-    $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . rcube_label('GB');
-  }
-  else if ($bytes >= 1048576)
-  {
-    $mb = $bytes/1048576;
-    $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . rcube_label('MB');
-  }
-  else if ($bytes >= 1024)
-    $str = sprintf("%d ",  round($bytes/1024)) . rcube_label('KB');
-  else
-    $str = sprintf('%d ', $bytes) . rcube_label('B');
-
-  return $str;
-}
-
-/**
- * Wrapper function for wordwrap
- */
-function rc_wordwrap($string, $width=75, $break="\n", $cut=false)
-{
-  $para = explode($break, $string);
-  $string = '';
-  while (count($para)) {
-    $line = array_shift($para);
-    if ($line[0] == '>') {
-      $string .= $line.$break;
-      continue;
-    }
-    $list = explode(' ', $line);
-    $len = 0;
-    while (count($list)) {
-      $line = array_shift($list);
-      $l = mb_strlen($line);
-      $newlen = $len + $l + ($len ? 1 : 0);
-
-      if ($newlen <= $width) {
-        $string .= ($len ? ' ' : '').$line;
-        $len += (1 + $l);
-      } else {
-        if ($l > $width) {
-          if ($cut) {
-            $start = 0;
-            while ($l) {
-              $str = mb_substr($line, $start, $width);
-              $strlen = mb_strlen($str);
-              $string .= ($len ? $break : '').$str;
-              $start += $strlen;
-              $l -= $strlen;
-              $len = $strlen;
-            }
-          } else {
-                $string .= ($len ? $break : '').$line;
-            if (count($list)) $string .= $break;
-            $len = 0;
-          }
-        } else {
-          $string .= $break.$line;
-          $len = $l;
+    if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) {
+        $bytes = floatval($regs[1]);
+        switch (strtolower($regs[2])) {
+        case 'g':
+        case 'gb':
+            $bytes *= 1073741824;
+            break;
+        case 'm':
+        case 'mb':
+            $bytes *= 1048576;
+            break;
+        case 'k':
+        case 'kb':
+            $bytes *= 1024;
+            break;
         }
-      }
     }
-    if (count($para)) $string .= $break;
-  }
-  return $string;
+
+    return floatval($bytes);
 }
 
+
 /**
- * Read a specific HTTP request header
+ * Read a specific HTTP request header.
  *
- * @access static
  * @param  string $name Header name
+ *
  * @return mixed  Header value or null if not available
  */
-function rc_request_header($name)
+function rcube_request_header($name)
 {
-  if (function_exists('getallheaders'))
-  {
-    $hdrs = array_change_key_case(getallheaders(), CASE_UPPER);
-    $key  = strtoupper($name);
-  }
-  else
-  {
-    $key  = 'HTTP_' . strtoupper(strtr($name, '-', '_'));
-    $hdrs = array_change_key_case($_SERVER, CASE_UPPER);
-  }
+    if (function_exists('getallheaders')) {
+        $hdrs = array_change_key_case(getallheaders(), CASE_UPPER);
+        $key  = strtoupper($name);
+    }
+    else {
+        $key  = 'HTTP_' . strtoupper(strtr($name, '-', '_'));
+        $hdrs = array_change_key_case($_SERVER, CASE_UPPER);
+    }
 
-  return $hdrs[$key];
+    return $hdrs[$key];
 }
 
 
@@ -263,219 +145,251 @@
  * Delete all files within a folder
  *
  * @param string Path to directory
+ *
  * @return boolean True on success, False if directory was not found
  */
 function clear_directory($dir_path)
 {
-  $dir = @opendir($dir_path);
-  if(!$dir) return FALSE;
+    $dir = @opendir($dir_path);
+    if (!$dir) {
+        return false;
+    }
 
-  while ($file = readdir($dir))
-    if (strlen($file)>2)
-      unlink("$dir_path/$file");
+    while ($file = readdir($dir)) {
+        if (strlen($file) > 2) {
+            unlink("$dir_path/$file");
+        }
+    }
 
-  closedir($dir);
-  return TRUE;
+    closedir($dir);
+
+    return true;
 }
 
 
 /**
- * Create a unix timestamp with a specified offset from now
+ * Create a unix timestamp with a specified offset from now.
  *
- * @param string String representation of the offset (e.g. 20min, 5h, 2days)
- * @param int Factor to multiply with the offset
+ * @param string $offset_str  String representation of the offset (e.g. 20min, 5h, 2days)
+ * @param int    $factor      Factor to multiply with the offset
+ *
  * @return int Unix timestamp
  */
 function get_offset_time($offset_str, $factor=1)
 {
-  if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs))
-  {
-    $amount = (int)$regs[1];
-    $unit = strtolower($regs[2]);
-  }
-  else
-  {
-    $amount = (int)$offset_str;
-    $unit = 's';
-  }
-
-  $ts = mktime();
-  switch ($unit)
-  {
-    case 'w':
-      $amount *= 7;
-    case 'd':
-      $amount *= 24;
-    case 'h':
-      $amount *= 60;
-    case 'm':
-      $amount *= 60;
-    case 's':
-      $ts += $amount * $factor;
-  }
-
-  return $ts;
-}
-
-
-/**
- * Truncate string if it is longer than the allowed length
- * Replace the middle or the ending part of a string with a placeholder
- *
- * @param string Input string
- * @param int    Max. length
- * @param string Replace removed chars with this
- * @param bool   Set to True if string should be truncated from the end
- * @return string Abbreviated string
- */
-function abbreviate_string($str, $maxlength, $place_holder='...', $ending=false)
-{
-  $length = mb_strlen($str);
-
-  if ($length > $maxlength)
-  {
-    if ($ending)
-      return mb_substr($str, 0, $maxlength) . $place_holder;
-
-    $place_holder_length = mb_strlen($place_holder);
-    $first_part_length = floor(($maxlength - $place_holder_length)/2);
-    $second_starting_location = $length - $maxlength + $first_part_length + $place_holder_length;
-    $str = mb_substr($str, 0, $first_part_length) . $place_holder . mb_substr($str, $second_starting_location);
-  }
-
-  return $str;
-}
-
-
-/**
- * A method to guess the mime_type of an attachment.
- *
- * @param string $path      Path to the file.
- * @param string $name      File name (with suffix)
- * @param string $failover  Mime type supplied for failover.
- * @param string $is_stream Set to True if $path contains file body
- *
- * @return string
- * @author Till Klampaeckel <till@php.net>
- * @see    http://de2.php.net/manual/en/ref.fileinfo.php
- * @see    http://de2.php.net/mime_content_type
- */
-function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false)
-{
-    $mime_type = null;
-    $mime_magic = rcmail::get_instance()->config->get('mime_magic');
-    $mime_ext = @include(RCMAIL_CONFIG_DIR . '/mimetypes.php');
-
-    // use file name suffix with hard-coded mime-type map
-    if (is_array($mime_ext) && $name) {
-        if ($suffix = substr($name, strrpos($name, '.')+1)) {
-            $mime_type = $mime_ext[strtolower($suffix)];
-        }
-    }
-
-    // try fileinfo extension if available
-    if (!$mime_type && function_exists('finfo_open')) {
-        if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) {
-            if ($is_stream)
-                $mime_type = finfo_buffer($finfo, $path);
-            else
-                $mime_type = finfo_file($finfo, $path);
-            finfo_close($finfo);
-        }
-    }
-
-    // try PHP's mime_content_type
-    if (!$mime_type && !$is_stream && function_exists('mime_content_type')) {
-      $mime_type = @mime_content_type($path);
-    }
-
-    // fall back to user-submitted string
-    if (!$mime_type) {
-        $mime_type = $failover;
+    if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs)) {
+        $amount = (int)$regs[1];
+        $unit   = strtolower($regs[2]);
     }
     else {
-        // Sometimes (PHP-5.3?) content-type contains charset definition,
-        // Remove it (#1487122) also "charset=binary" is useless
-        $mime_type = array_shift(preg_split('/[; ]/', $mime_type));
+        $amount = (int)$offset_str;
+        $unit   = 's';
     }
 
-    return $mime_type;
+    $ts = mktime();
+    switch ($unit) {
+    case 'w':
+        $amount *= 7;
+    case 'd':
+        $amount *= 24;
+    case 'h':
+        $amount *= 60;
+    case 'm':
+        $amount *= 60;
+    case 's':
+        $ts += $amount * $factor;
+    }
+
+    return $ts;
 }
 
 
 /**
- * Detect image type of the given binary data by checking magic numbers
+ * Truncate string if it is longer than the allowed length.
+ * Replace the middle or the ending part of a string with a placeholder.
  *
- * @param string  Binary file content
- * @return string Detected mime-type or jpeg as fallback
+ * @param string $str         Input string
+ * @param int    $maxlength   Max. length
+ * @param string $placeholder Replace removed chars with this
+ * @param bool   $ending      Set to True if string should be truncated from the end
+ *
+ * @return string Abbreviated string
  */
-function rc_image_content_type($data)
+function abbreviate_string($str, $maxlength, $placeholder='...', $ending=false)
 {
-    $type = 'jpeg';
-    if      (preg_match('/^\x89\x50\x4E\x47/', $data)) $type = 'png';
-    else if (preg_match('/^\x47\x49\x46\x38/', $data)) $type = 'gif';
-    else if (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico';
-//  else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg';
+    $length = mb_strlen($str);
 
-    return 'image/' . $type;
+    if ($length > $maxlength) {
+        if ($ending) {
+            return mb_substr($str, 0, $maxlength) . $placeholder;
+        }
+
+        $placeholder_length = mb_strlen($placeholder);
+        $first_part_length  = floor(($maxlength - $placeholder_length)/2);
+        $second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length;
+
+        $str = mb_substr($str, 0, $first_part_length) . $placeholder . mb_substr($str, $second_starting_location);
+    }
+
+    return $str;
 }
 
 
 /**
  * Explode quoted string
- * 
+ *
  * @param string Delimiter expression string for preg_match()
  * @param string Input string
  */
 function rcube_explode_quoted_string($delimiter, $string)
 {
-  $result = array();
-  $strlen = strlen($string);
+    $result = array();
+    $strlen = strlen($string);
 
-  for ($q=$p=$i=0; $i < $strlen; $i++) {
-    if ($string[$i] == "\"" && $string[$i-1] != "\\") {
-      $q = $q ? false : true;
-    } 
-    else if (!$q && preg_match("/$delimiter/", $string[$i])) {
-      $result[] = substr($string, $p, $i - $p);
-      $p = $i + 1;
+    for ($q=$p=$i=0; $i < $strlen; $i++) {
+        if ($string[$i] == "\"" && $string[$i-1] != "\\") {
+            $q = $q ? false : true;
+        }
+        else if (!$q && preg_match("/$delimiter/", $string[$i])) {
+            $result[] = substr($string, $p, $i - $p);
+            $p = $i + 1;
+        }
     }
-  }
 
-  $result[] = substr($string, $p);
-  return $result;
+    $result[] = substr($string, $p);
+
+    return $result;
 }
 
 
 /**
- * Get all keys from array (recursive)
- * 
- * @param array Input array
- * @return array
+ * Get all keys from array (recursive).
+ *
+ * @param array $array  Input array
+ *
+ * @return array List of array keys
  */
 function array_keys_recursive($array)
 {
-  $keys = array();
+    $keys = array();
 
-  if (!empty($array))
-    foreach ($array as $key => $child) {
-      $keys[] = $key;
-      foreach (array_keys_recursive($child) as $val)
-        $keys[] = $val;
+    if (!empty($array)) {
+        foreach ($array as $key => $child) {
+            $keys[] = $key;
+            foreach (array_keys_recursive($child) as $val) {
+                $keys[] = $val;
+            }
+        }
     }
-  return $keys;
+
+    return $keys;
+}
+
+
+/**
+ * Remove all non-ascii and non-word chars except ., -, _
+ */
+function asciiwords($str, $css_id = false, $replace_with = '')
+{
+    $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
+    return preg_replace("/[^$allowed]/i", $replace_with, $str);
+}
+
+
+/**
+ * Remove single and double quotes from given string
+ *
+ * @param string Input value
+ *
+ * @return string Dequoted string
+ */
+function strip_quotes($str)
+{
+    return str_replace(array("'", '"'), '', $str);
+}
+
+
+/**
+ * Remove new lines characters from given string
+ *
+ * @param string $str  Input value
+ *
+ * @return string Stripped string
+ */
+function strip_newlines($str)
+{
+    return preg_replace('/[\r\n]/', '', $str);
+}
+
+
+/**
+ * Improved equivalent to strtotime()
+ *
+ * @param string $date  Date string
+ *
+ * @return int Unix timestamp
+ */
+function rcube_strtotime($date)
+{
+    // check for MS Outlook vCard date format YYYYMMDD
+    if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
+        return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
+    }
+    else if (is_numeric($date)) {
+        return $date;
+    }
+
+    // support non-standard "GMTXXXX" literal
+    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
+
+    // if date parsing fails, we have a date in non-rfc format.
+    // remove token from the end and try again
+    while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
+        $d = explode(' ', $date);
+        array_pop($d);
+        if (!$d) {
+            break;
+        }
+        $date = implode(' ', $d);
+    }
+
+    return $ts;
+}
+
+
+/**
+ * Compose a valid representation of name and e-mail address
+ *
+ * @param string $email  E-mail address
+ * @param string $name   Person name
+ *
+ * @return string Formatted string
+ */
+function format_email_recipient($email, $name = '')
+{
+    $email = trim($email);
+
+    if ($name && $name != $email) {
+        // Special chars as defined by RFC 822 need to in quoted string (or escaped).
+        if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) {
+            $name = '"'.addcslashes($name, '"').'"';
+        }
+
+        return "$name <$email>";
+    }
+
+    return $email;
 }
 
 
 /**
  * mbstring replacement functions
  */
-
 if (!extension_loaded('mbstring'))
 {
     function mb_strlen($str)
     {
-	return strlen($str);
+        return strlen($str);
     }
 
     function mb_strtolower($str)
@@ -552,3 +466,89 @@
     }
 }
 
+
+/*
+ * Idn_to_ascii wrapper.
+ * Intl/Idn modules version of this function doesn't work with e-mail address
+ */
+function rcube_idn_to_ascii($str)
+{
+    return rcube_idn_convert($str, true);
+}
+
+/*
+ * Idn_to_ascii wrapper.
+ * Intl/Idn modules version of this function doesn't work with e-mail address
+ */
+function rcube_idn_to_utf8($str)
+{
+    return rcube_idn_convert($str, false);
+}
+
+function rcube_idn_convert($input, $is_utf=false)
+{
+    if ($at = strpos($input, '@')) {
+        $user   = substr($input, 0, $at);
+        $domain = substr($input, $at+1);
+    }
+    else {
+        $domain = $input;
+    }
+
+    $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
+
+    if ($domain === false) {
+        return '';
+    }
+
+    return $at ? $user . '@' . $domain : $domain;
+}
+
+
+/**
+ * Use PHP5 autoload for dynamic class loading
+ *
+ * @todo Make Zend, PEAR etc play with this
+ * @todo Make our classes conform to a more straight forward CS.
+ */
+function rcube_autoload($classname)
+{
+    $filename = preg_replace(
+        array(
+            '/MDB2_(.+)/',
+            '/Mail_(.+)/',
+            '/Net_(.+)/',
+            '/Auth_(.+)/',
+            '/^html_.+/',
+            '/^utf8$/',
+        ),
+        array(
+            'MDB2/\\1',
+            'Mail/\\1',
+            'Net/\\1',
+            'Auth/\\1',
+            'html',
+            'utf8.class',
+        ),
+        $classname
+    );
+
+    if ($fp = @fopen("$filename.php", 'r', true)) {
+        fclose($fp);
+        include_once("$filename.php");
+        return true;
+    }
+
+    return false;
+}
+
+/**
+ * Local callback function for PEAR errors
+ */
+function rcube_pear_error($err)
+{
+    error_log(sprintf("%s (%s): %s",
+        $err->getMessage(),
+        $err->getCode(),
+        $err->getUserinfo()), 0);
+}
diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php
index 854505d..0923e3b 100644
--- a/program/include/rcube_smtp.php
+++ b/program/include/rcube_smtp.php
@@ -52,7 +52,7 @@
    */
   public function connect($host=null, $port=null, $user=null, $pass=null)
   {
-    $RCMAIL = rcmail::get_instance();
+    $RCMAIL = rcube::get_instance();
 
     // disconnect/destroy $this->conn
     $this->disconnect();
@@ -74,7 +74,7 @@
       'smtp_auth_callbacks' => array(),
     ));
 
-    $smtp_host = rcube_parse_host($CONFIG['smtp_server']);
+    $smtp_host = rcmail::parse_host($CONFIG['smtp_server']);
     // when called from Installer it's possible to have empty $smtp_host here
     if (!$smtp_host) $smtp_host = 'localhost';
     $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
@@ -338,7 +338,7 @@
    */
   public function debug_handler(&$smtp, $message)
   {
-    write_log('smtp', preg_replace('/\r\n$/', '', $message));
+    rcmail::write_log('smtp', preg_replace('/\r\n$/', '', $message));
   }
 
 
diff --git a/program/include/rcube_spellchecker.php b/program/include/rcube_spellchecker.php
index a6f3913..8dfc3ea 100644
--- a/program/include/rcube_spellchecker.php
+++ b/program/include/rcube_spellchecker.php
@@ -61,7 +61,7 @@
         $this->lang   = $lang ? $lang : 'en';
 
         if ($this->engine == 'pspell' && !extension_loaded('pspell')) {
-            raise_error(array(
+            rcube::raise_error(array(
                 'code' => 500, 'type' => 'php',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Pspell extension not available"), true, true);
@@ -535,7 +535,7 @@
     private function update_dict()
     {
         if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
-            $userid = (int) $this->rc->user->ID;
+            $userid = $this->rc->get_user_id();
         }
 
         $plugin = $this->rc->plugins->exec_hook('spell_dictionary_save', array(
@@ -548,24 +548,24 @@
         if ($this->have_dict) {
             if (!empty($this->dict)) {
                 $this->rc->db->query(
-                    "UPDATE ".get_table_name('dictionary')
+                    "UPDATE ".$this->rc->db->table_name('dictionary')
                     ." SET data = ?"
-                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
+                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                         ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
                     implode(' ', $plugin['dictionary']), $plugin['language']);
             }
             // don't store empty dict
             else {
                 $this->rc->db->query(
-                    "DELETE FROM " . get_table_name('dictionary')
-                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
+                    "DELETE FROM " . $this->rc->db->table_name('dictionary')
+                    ." WHERE user_id " . ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                         ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
                     $plugin['language']);
             }
         }
         else if (!empty($this->dict)) {
             $this->rc->db->query(
-                "INSERT INTO " .get_table_name('dictionary')
+                "INSERT INTO " .$this->rc->db->table_name('dictionary')
                 ." (user_id, " . $this->rc->db->quoteIdentifier('language') . ", data) VALUES (?, ?, ?)",
                 $plugin['userid'], $plugin['language'], implode(' ', $plugin['dictionary']));
         }
@@ -582,7 +582,7 @@
         }
 
         if (strcasecmp($this->options['dictionary'], 'shared') != 0) {
-            $userid = (int) $this->rc->user->ID;
+            $userid = $this->rc->get_user_id();
         }
 
         $plugin = $this->rc->plugins->exec_hook('spell_dictionary_get', array(
@@ -591,8 +591,8 @@
         if (empty($plugin['abort'])) {
             $dict = array();
             $this->rc->db->query(
-                "SELECT data FROM ".get_table_name('dictionary')
-                ." WHERE user_id ". ($plugin['userid'] ? "= ".$plugin['userid'] : "IS NULL")
+                "SELECT data FROM ".$this->rc->db->table_name('dictionary')
+                ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                     ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
                 $plugin['language']);
 
diff --git a/program/include/rcube_sqlite.inc b/program/include/rcube_sqlite.inc
deleted file mode 100644
index 3b74b26..0000000
--- a/program/include/rcube_sqlite.inc
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_sqlite.inc                                      |
- |                                                                       |
- | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
- |                                                                       |
- | Licensed under the GNU General Public License version 3 or            |
- | any later version with exceptions for skins & plugins.                |
- | See the README file for a full license statement.                     |
- |                                                                       |
- | PURPOSE:                                                              |
- |   Provide callback functions for sqlite that will emulate             |
- |   sone MySQL functions                                                |
- |                                                                       |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com>                        |
- +-----------------------------------------------------------------------+
-
- $Id$
-
-*/
-
-/**
- * Callback functions for sqlite database interface
- *
- * @package Database
- */
-
-
-function rcube_sqlite_from_unixtime($timestamp)
-{
-    $timestamp = trim($timestamp);
-    if (!preg_match('/^[0-9]+$/is', $timestamp))
-        $ret = strtotime($timestamp);
-    else
-        $ret = $timestamp;
-    
-    $ret = date('Y-m-d H:i:s', $ret);
-    rcube_sqlite_debug("FROM_UNIXTIME ($timestamp) = $ret");
-    return $ret;
-}
-
-
-function rcube_sqlite_unix_timestamp($timestamp='')
-{
-    $timestamp = trim($timestamp);
-    if (!$timestamp)
-        $ret = time();
-    else if (!preg_match('/^[0-9]+$/is', $timestamp))
-        $ret = strtotime($timestamp);
-    else
-        $ret = $timestamp;
-
-    rcube_sqlite_debug("UNIX_TIMESTAMP ($timestamp) = $ret");
-    return $ret;
-}
-
-
-function rcube_sqlite_now()
-{
-    rcube_sqlite_debug("NOW() = ".date("Y-m-d H:i:s"));
-    return date("Y-m-d H:i:s");
-}
-
-
-function rcube_sqlite_md5($str)
-{
-    return md5($str);
-}
-
-
-function rcube_sqlite_debug($str)
-{
-    //console($str);
-}
-
diff --git a/program/include/rcube_storage.php b/program/include/rcube_storage.php
index 8123e9c..e80ee6a 100644
--- a/program/include/rcube_storage.php
+++ b/program/include/rcube_storage.php
@@ -434,7 +434,7 @@
      * @param int     $uid     Message UID to fetch
      * @param string  $folder  Folder to read from
      *
-     * @return object rcube_mail_header Message data
+     * @return object rcube_message_header Message data
      */
     abstract function get_message($uid, $folder = null);
 
@@ -446,7 +446,7 @@
      * @param string  $folder   Folder to read from
      * @param bool    $force    True to skip cache
      *
-     * @return rcube_mail_header Message headers
+     * @return rcube_message_header Message headers
      */
     abstract function get_message_headers($uid, $folder = null, $force = false);
 
@@ -477,7 +477,7 @@
     public function get_body($uid, $part = 1)
     {
         $headers = $this->get_message_headers($uid);
-        return rcube_charset_convert($this->get_message_part($uid, $part, null),
+        return rcube_charset::convert($this->get_message_part($uid, $part, null),
             $headers->charset ? $headers->charset : $this->default_charset);
     }
 
@@ -970,6 +970,7 @@
      */
     abstract function clear_cache($key = null, $prefix_mode = false);
 
+
     /**
      * Returns cached value
      *
@@ -979,93 +980,10 @@
      */
     abstract function get_cache($key);
 
+
     /**
      * Delete outdated cache entries
      */
     abstract function expunge_cache();
 
 }  // end class rcube_storage
-
-
-/**
- * Class representing a message part
- *
- * @package Mail
- */
-class rcube_message_part
-{
-    var $mime_id = '';
-    var $ctype_primary = 'text';
-    var $ctype_secondary = 'plain';
-    var $mimetype = 'text/plain';
-    var $disposition = '';
-    var $filename = '';
-    var $encoding = '8bit';
-    var $charset = '';
-    var $size = 0;
-    var $headers = array();
-    var $d_parameters = array();
-    var $ctype_parameters = array();
-
-    function __clone()
-    {
-        if (isset($this->parts)) {
-            foreach ($this->parts as $idx => $part) {
-                if (is_object($part)) {
-                    $this->parts[$idx] = clone $part;
-                }
-            }
-        }
-    }
-}
-
-
-/**
- * Class for sorting an array of rcube_mail_header objects in a predetermined order.
- *
- * @package Mail
- * @author Eric Stadtherr
- */
-class rcube_header_sorter
-{
-    private $uids = array();
-
-
-    /**
-     * Set the predetermined sort order.
-     *
-     * @param array $index  Numerically indexed array of IMAP UIDs
-     */
-    function set_index($index)
-    {
-        $index = array_flip($index);
-
-        $this->uids = $index;
-    }
-
-    /**
-     * Sort the array of header objects
-     *
-     * @param array $headers Array of rcube_mail_header objects indexed by UID
-     */
-    function sort_headers(&$headers)
-    {
-        uksort($headers, array($this, "compare_uids"));
-    }
-
-    /**
-     * Sort method called by uksort()
-     *
-     * @param int $a Array key (UID)
-     * @param int $b Array key (UID)
-     */
-    function compare_uids($a, $b)
-    {
-        // then find each sequence number in my ordered list
-        $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1;
-        $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1;
-
-        // return the relative position as the comparison value
-        return $posa - $posb;
-    }
-}
diff --git a/program/include/rcube_string_replacer.php b/program/include/rcube_string_replacer.php
index b3d29eb..320f89a 100644
--- a/program/include/rcube_string_replacer.php
+++ b/program/include/rcube_string_replacer.php
@@ -98,7 +98,7 @@
       $i = $this->add($prefix . html::a(array(
           'href' => $url_prefix . $url,
           'target' => '_blank'
-        ), Q($url)) . $suffix);
+        ), rcube_ui::Q($url)) . $suffix);
     }
 
     // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes.
@@ -118,8 +118,8 @@
 
     $i = $this->add(html::a(array(
         'href' => 'mailto:' . $href,
-        'onclick' => "return ".JS_OBJECT_NAME.".command('compose','".JQ($href)."',this)",
-      ), Q($href)) . $suffix);
+        'onclick' => "return ".JS_OBJECT_NAME.".command('compose','".rcube_ui::JQ($href)."',this)",
+      ), rcube_ui::Q($href)) . $suffix);
 
     return $i >= 0 ? $this->get_replacement($i) : '';
   }
diff --git a/program/include/rcube_ui.php b/program/include/rcube_ui.php
new file mode 100644
index 0000000..2625710
--- /dev/null
+++ b/program/include/rcube_ui.php
@@ -0,0 +1,1468 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_ui.php                                          |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2011-2012, Kolab Systems AG                             |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Provide basic functions for the webmail user interface              |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+/**
+ * Roundcube Webmail functions for user interface
+ *
+ * @package Core
+ * @author Thomas Bruederli <roundcube@gmail.com>
+ * @author Aleksander Machniak <alec@alec.pl>
+ */
+class rcube_ui
+{
+    // define constants for input reading
+    const INPUT_GET  = 0x0101;
+    const INPUT_POST = 0x0102;
+    const INPUT_GPC  = 0x0103;
+
+
+    /**
+     * Get localized text in the desired language
+     * It's a global wrapper for rcube::gettext()
+     *
+     * @param mixed  $p      Named parameters array or label name
+     * @param string $domain Domain to search in (e.g. plugin name)
+     *
+     * @return string Localized text
+     * @see rcube::gettext()
+     */
+    public static function label($p, $domain = null)
+    {
+        return rcube::get_instance()->gettext($p, $domain);
+    }
+
+
+    /**
+     * Global wrapper of rcube::text_exists()
+     * to check whether a text label is defined
+     *
+     * @see rcube::text_exists()
+     */
+    public static function label_exists($name, $domain = null, &$ref_domain = null)
+    {
+        return rcube::get_instance()->text_exists($name, $domain, $ref_domain);
+    }
+
+
+    /**
+     * Compose an URL for a specific action
+     *
+     * @param string  Request action
+     * @param array   More URL parameters
+     * @param string  Request task (omit if the same)
+     *
+     * @return The application URL
+     */
+    public static function url($action, $p = array(), $task = null)
+    {
+        return rcube::get_instance()->url((array)$p + array('_action' => $action, 'task' => $task));
+    }
+
+
+    /**
+     * Replacing specials characters to a specific encoding type
+     *
+     * @param  string  Input string
+     * @param  string  Encoding type: text|html|xml|js|url
+     * @param  string  Replace mode for tags: show|replace|remove
+     * @param  boolean Convert newlines
+     *
+     * @return string  The quoted string
+     */
+    public static function rep_specialchars_output($str, $enctype = '', $mode = '', $newlines = true)
+    {
+        static $html_encode_arr = false;
+        static $js_rep_table = false;
+        static $xml_rep_table = false;
+
+        // encode for HTML output
+        if ($enctype == 'html') {
+            if (!$html_encode_arr) {
+                $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
+                unset($html_encode_arr['?']);
+            }
+
+            $encode_arr = $html_encode_arr;
+
+            // don't replace quotes and html tags
+            if ($mode == 'show' || $mode == '') {
+                $ltpos = strpos($str, '<');
+                if ($ltpos !== false && strpos($str, '>', $ltpos) !== false) {
+                    unset($encode_arr['"']);
+                    unset($encode_arr['<']);
+                    unset($encode_arr['>']);
+                    unset($encode_arr['&']);
+                }
+            }
+            else if ($mode == 'remove') {
+                $str = strip_tags($str);
+            }
+
+            $out = strtr($str, $encode_arr);
+
+            // avoid douple quotation of &
+            $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
+
+            return $newlines ? nl2br($out) : $out;
+        }
+
+        // if the replace tables for XML and JS are not yet defined
+        if ($js_rep_table === false) {
+            $js_rep_table = $xml_rep_table = array();
+            $xml_rep_table['&'] = '&amp;';
+
+            // can be increased to support more charsets
+            for ($c=160; $c<256; $c++) {
+                $xml_rep_table[chr($c)] = "&#$c;";
+            }
+
+            $xml_rep_table['"'] = '&quot;';
+            $js_rep_table['"']  = '\\"';
+            $js_rep_table["'"]  = "\\'";
+            $js_rep_table["\\"] = "\\\\";
+            // Unicode line and paragraph separators (#1486310)
+            $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
+            $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
+        }
+
+        // encode for javascript use
+        if ($enctype == 'js') {
+            return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
+        }
+
+        // encode for plaintext
+        if ($enctype == 'text') {
+            return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
+        }
+
+        if ($enctype == 'url') {
+            return rawurlencode($str);
+        }
+
+        // encode for XML
+        if ($enctype == 'xml') {
+            return strtr($str, $xml_rep_table);
+        }
+
+        // no encoding given -> return original string
+        return $str;
+    }
+
+
+    /**
+     * Quote a given string.
+     * Shortcut function for self::rep_specialchars_output()
+     *
+     * @return string HTML-quoted string
+     * @see self::rep_specialchars_output()
+     */
+    public static function Q($str, $mode = 'strict', $newlines = true)
+    {
+        return self::rep_specialchars_output($str, 'html', $mode, $newlines);
+    }
+
+
+    /**
+     * Quote a given string for javascript output.
+     * Shortcut function for self::rep_specialchars_output()
+     *
+     * @return string JS-quoted string
+     * @see self::rep_specialchars_output()
+     */
+    public static function JQ($str)
+    {
+        return self::rep_specialchars_output($str, 'js');
+    }
+
+
+    /**
+     * Read input value and convert it for internal use
+     * Performs stripslashes() and charset conversion if necessary
+     *
+     * @param  string   Field name to read
+     * @param  int      Source to get value from (GPC)
+     * @param  boolean  Allow HTML tags in field value
+     * @param  string   Charset to convert into
+     *
+     * @return string   Field value or NULL if not available
+     */
+    public static function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
+    {
+        $value = NULL;
+
+        if ($source == self::INPUT_GET) {
+            if (isset($_GET[$fname])) {
+                $value = $_GET[$fname];
+            }
+        }
+        else if ($source == self::INPUT_POST) {
+            if (isset($_POST[$fname])) {
+                $value = $_POST[$fname];
+            }
+        }
+        else if ($source == self::INPUT_GPC) {
+            if (isset($_POST[$fname])) {
+                $value = $_POST[$fname];
+            }
+            else if (isset($_GET[$fname])) {
+                $value = $_GET[$fname];
+            }
+            else if (isset($_COOKIE[$fname])) {
+                $value = $_COOKIE[$fname];
+            }
+        }
+
+        return self::parse_input_value($value, $allow_html, $charset);
+    }
+
+    /**
+     * Parse/validate input value. See self::get_input_value()
+     * Performs stripslashes() and charset conversion if necessary
+     *
+     * @param  string   Input value
+     * @param  boolean  Allow HTML tags in field value
+     * @param  string   Charset to convert into
+     *
+     * @return string   Parsed value
+     */
+    public static function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
+    {
+        global $OUTPUT;
+
+        if (empty($value)) {
+            return $value;
+        }
+
+        if (is_array($value)) {
+            foreach ($value as $idx => $val) {
+                $value[$idx] = self::parse_input_value($val, $allow_html, $charset);
+            }
+            return $value;
+        }
+
+        // strip single quotes if magic_quotes_sybase is enabled
+        if (ini_get('magic_quotes_sybase')) {
+            $value = str_replace("''", "'", $value);
+        }
+        // strip slashes if magic_quotes enabled
+        else if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
+            $value = stripslashes($value);
+        }
+
+        // remove HTML tags if not allowed
+        if (!$allow_html) {
+            $value = strip_tags($value);
+        }
+
+        $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
+
+        // remove invalid characters (#1488124)
+        if ($output_charset == 'UTF-8') {
+            $value = rcube_charset::clean($value);
+        }
+
+        // convert to internal charset
+        if ($charset && $output_charset) {
+            $value = rcube_charset::convert($value, $output_charset, $charset);
+        }
+
+        return $value;
+    }
+
+
+    /**
+     * Convert array of request parameters (prefixed with _)
+     * to a regular array with non-prefixed keys.
+     *
+     * @param int    $mode   Source to get value from (GPC)
+     * @param string $ignore PCRE expression to skip parameters by name
+     *
+     * @return array Hash array with all request parameters
+     */
+    public static function request2param($mode = null, $ignore = 'task|action')
+    {
+        $out = array();
+        $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
+
+        foreach ($src as $key => $value) {
+            $fname = $key[0] == '_' ? substr($key, 1) : $key;
+            if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
+                $out[$fname] = self::get_input_value($key, $mode);
+            }
+        }
+
+        return $out;
+    }
+
+
+    /**
+     * Convert the given string into a valid HTML identifier
+     * Same functionality as done in app.js with rcube_webmail.html_identifier()
+     */
+    public static function html_identifier($str, $encode=false)
+    {
+        if ($encode) {
+            return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
+        }
+        else {
+            return asciiwords($str, true, '_');
+        }
+    }
+
+
+    /**
+     * Create a HTML table based on the given data
+     *
+     * @param  array  Named table attributes
+     * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
+     * @param  array  List of cols to show
+     * @param  string Name of the identifier col
+     *
+     * @return string HTML table code
+     */
+    public static function table_output($attrib, $table_data, $a_show_cols, $id_col)
+    {
+        global $RCMAIL;
+
+        $table = new html_table(/*array('cols' => count($a_show_cols))*/);
+
+        // add table header
+        if (!$attrib['noheader']) {
+            foreach ($a_show_cols as $col) {
+                $table->add_header($col, self::Q(self::label($col)));
+            }
+        }
+
+        if (!is_array($table_data)) {
+            $db = $RCMAIL->get_dbh();
+            while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) {
+                $table->add_row(array('id' => 'rcmrow' . self::html_identifier($sql_arr[$id_col])));
+
+                // format each col
+                foreach ($a_show_cols as $col) {
+                    $table->add($col, self::Q($sql_arr[$col]));
+                }
+            }
+        }
+        else {
+            foreach ($table_data as $row_data) {
+                $class = !empty($row_data['class']) ? $row_data['class'] : '';
+                $rowid = 'rcmrow' . self::html_identifier($row_data[$id_col]);
+
+                $table->add_row(array('id' => $rowid, 'class' => $class));
+
+                // format each col
+                foreach ($a_show_cols as $col) {
+                    $table->add($col, self::Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
+                }
+            }
+        }
+
+        return $table->show($attrib);
+    }
+
+
+    /**
+     * Create an edit field for inclusion on a form
+     *
+     * @param string col field name
+     * @param string value field value
+     * @param array attrib HTML element attributes for field
+     * @param string type HTML element type (default 'text')
+     *
+     * @return string HTML field definition
+     */
+    public static function get_edit_field($col, $value, $attrib, $type = 'text')
+    {
+        static $colcounts = array();
+
+        $fname = '_'.$col;
+        $attrib['name']  = $fname . ($attrib['array'] ? '[]' : '');
+        $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
+
+        if ($type == 'checkbox') {
+            $attrib['value'] = '1';
+            $input = new html_checkbox($attrib);
+        }
+        else if ($type == 'textarea') {
+            $attrib['cols'] = $attrib['size'];
+            $input = new html_textarea($attrib);
+        }
+        else if ($type == 'select') {
+            $input = new html_select($attrib);
+            $input->add('---', '');
+            $input->add(array_values($attrib['options']), array_keys($attrib['options']));
+        }
+        else if ($attrib['type'] == 'password') {
+            $input = new html_passwordfield($attrib);
+        }
+        else {
+            if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden') {
+                $attrib['type'] = 'text';
+            }
+            $input = new html_inputfield($attrib);
+        }
+
+        // use value from post
+        if (isset($_POST[$fname])) {
+            $postvalue = self::get_input_value($fname, self::INPUT_POST, true);
+            $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
+        }
+
+        $out = $input->show($value);
+
+        return $out;
+    }
+
+
+    /**
+     * Replace all css definitions with #container [def]
+     * and remove css-inlined scripting
+     *
+     * @param string CSS source code
+     * @param string Container ID to use as prefix
+     *
+     * @return string Modified CSS source
+     * @todo I'm not sure this should belong to rcube_ui class
+     */
+    public static function mod_css_styles($source, $container_id, $allow_remote=false)
+    {
+        $last_pos = 0;
+        $replacements = new rcube_string_replacer;
+
+        // ignore the whole block if evil styles are detected
+        $source   = self::xss_entity_decode($source);
+        $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
+        $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
+        if (preg_match("/$evilexpr/i", $stripped)) {
+            return '/* evil! */';
+        }
+
+        // cut out all contents between { and }
+        while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
+            $styles = substr($source, $pos+1, $pos2-($pos+1));
+
+            // check every line of a style block...
+            if ($allow_remote) {
+                $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
+                foreach ($a_styles as $line) {
+                    $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
+                    // ... and only allow strict url() values
+                    $regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims';
+                    if (stripos($stripped, 'url(') && !preg_match($regexp, $line)) {
+                        $a_styles = array('/* evil! */');
+                        break;
+                    }
+                }
+                $styles = join(";\n", $a_styles);
+            }
+
+            $key = $replacements->add($styles);
+            $source = substr($source, 0, $pos+1)
+                . $replacements->get_replacement($key)
+                . substr($source, $pos2, strlen($source)-$pos2);
+            $last_pos = $pos+2;
+        }
+
+        // remove html comments and add #container to each tag selector.
+        // also replace body definition because we also stripped off the <body> tag
+        $styles = preg_replace(
+            array(
+                '/(^\s*<!--)|(-->\s*$)/',
+                '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
+                '/'.preg_quote($container_id, '/').'\s+body/i',
+            ),
+            array(
+                '',
+                "\\1#$container_id \\2",
+                $container_id,
+            ),
+            $source);
+
+        // put block contents back in
+        $styles = $replacements->resolve($styles);
+
+        return $styles;
+    }
+
+
+    /**
+     * Convert the given date to a human readable form
+     * This uses the date formatting properties from config
+     *
+     * @param mixed  Date representation (string, timestamp or DateTime object)
+     * @param string Date format to use
+     * @param bool   Enables date convertion according to user timezone
+     *
+     * @return string Formatted date string
+     */
+    public static function format_date($date, $format = null, $convert = true)
+    {
+        global $RCMAIL, $CONFIG;
+
+        if (is_object($date) && is_a($date, 'DateTime')) {
+            $timestamp = $date->format('U');
+        }
+        else {
+            if (!empty($date)) {
+                $timestamp = rcube_strtotime($date);
+            }
+
+            if (empty($timestamp)) {
+                return '';
+            }
+
+            try {
+                $date = new DateTime("@".$timestamp);
+            }
+            catch (Exception $e) {
+                return '';
+            }
+        }
+
+        if ($convert) {
+            try {
+                // convert to the right timezone
+                $stz = date_default_timezone_get();
+                $tz = new DateTimeZone($RCMAIL->config->get('timezone'));
+                $date->setTimezone($tz);
+                date_default_timezone_set($tz->getName());
+
+                $timestamp = $date->format('U');
+            }
+            catch (Exception $e) {
+            }
+        }
+
+        // define date format depending on current time
+        if (!$format) {
+            $now         = time();
+            $now_date    = getdate($now);
+            $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
+            $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
+
+            if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
+                $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
+                $today  = true;
+            }
+            else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now) {
+                $format = $RCMAIL->config->get('date_short', 'D H:i');
+            }
+            else {
+                $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
+            }
+        }
+
+        // strftime() format
+        if (preg_match('/%[a-z]+/i', $format)) {
+            $format = strftime($format, $timestamp);
+            if ($stz) {
+                date_default_timezone_set($stz);
+            }
+            return $today ? (self::label('today') . ' ' . $format) : $format;
+        }
+
+        // parse format string manually in order to provide localized weekday and month names
+        // an alternative would be to convert the date() format string to fit with strftime()
+        $out = '';
+        for ($i=0; $i<strlen($format); $i++) {
+            if ($format[$i] == "\\") {  // skip escape chars
+                continue;
+            }
+
+            // write char "as-is"
+            if ($format[$i] == ' ' || $format[$i-1] == "\\") {
+                $out .= $format[$i];
+            }
+            // weekday (short)
+            else if ($format[$i] == 'D') {
+                $out .= self::label(strtolower(date('D', $timestamp)));
+            }
+            // weekday long
+            else if ($format[$i] == 'l') {
+                $out .= self::label(strtolower(date('l', $timestamp)));
+            }
+            // month name (short)
+            else if ($format[$i] == 'M') {
+                $out .= self::label(strtolower(date('M', $timestamp)));
+            }
+            // month name (long)
+            else if ($format[$i] == 'F') {
+                $out .= self::label('long'.strtolower(date('M', $timestamp)));
+            }
+            else if ($format[$i] == 'x') {
+                $out .= strftime('%x %X', $timestamp);
+            }
+            else {
+                $out .= date($format[$i], $timestamp);
+            }
+        }
+
+        if ($today) {
+            $label = self::label('today');
+            // replcae $ character with "Today" label (#1486120)
+            if (strpos($out, '$') !== false) {
+                $out = preg_replace('/\$/', $label, $out, 1);
+            }
+            else {
+                $out = $label . ' ' . $out;
+            }
+        }
+
+        if ($stz) {
+            date_default_timezone_set($stz);
+        }
+
+        return $out;
+    }
+
+
+    /**
+     * Return folders list in HTML
+     *
+     * @param array $attrib Named parameters
+     *
+     * @return string HTML code for the gui object
+     */
+    public static function folder_list($attrib)
+    {
+        global $RCMAIL;
+        static $a_mailboxes;
+
+        $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
+
+        // add some labels to client
+        $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
+
+        $type = $attrib['type'] ? $attrib['type'] : 'ul';
+        unset($attrib['type']);
+
+        if ($type == 'ul' && !$attrib['id']) {
+            $attrib['id'] = 'rcmboxlist';
+        }
+
+        if (empty($attrib['folder_name'])) {
+            $attrib['folder_name'] = '*';
+        }
+
+        // get current folder
+        $mbox_name = $RCMAIL->storage->get_folder();
+
+        // build the folders tree
+        if (empty($a_mailboxes)) {
+            // get mailbox list
+            $a_folders = $RCMAIL->storage->list_folders_subscribed(
+                '', $attrib['folder_name'], $attrib['folder_filter']);
+            $delimiter = $RCMAIL->storage->get_hierarchy_delimiter();
+            $a_mailboxes = array();
+
+            foreach ($a_folders as $folder) {
+                self::build_folder_tree($a_mailboxes, $folder, $delimiter);
+            }
+        }
+
+        // allow plugins to alter the folder tree or to localize folder names
+        $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array(
+            'list'      => $a_mailboxes,
+            'delimiter' => $delimiter,
+            'type'      => $type,
+            'attribs'   => $attrib,
+        ));
+
+        $a_mailboxes = $hook['list'];
+        $attrib      = $hook['attribs'];
+
+        if ($type == 'select') {
+            $select = new html_select($attrib);
+
+            // add no-selection option
+            if ($attrib['noselection']) {
+                $select->add(self::label($attrib['noselection']), '');
+            }
+
+            self::render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
+            $out = $select->show($attrib['default']);
+        }
+        else {
+            $js_mailboxlist = array();
+            $out = html::tag('ul', $attrib, self::render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
+
+            $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
+            $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
+            $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']);
+            $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
+        }
+
+        return $out;
+    }
+
+
+    /**
+     * Return folders list as html_select object
+     *
+     * @param array $p  Named parameters
+     *
+     * @return html_select HTML drop-down object
+     */
+    public static function folder_selector($p = array())
+    {
+        global $RCMAIL;
+
+        $p += array('maxlength' => 100, 'realnames' => false);
+        $a_mailboxes = array();
+        $storage = $RCMAIL->get_storage();
+
+        if (empty($p['folder_name'])) {
+            $p['folder_name'] = '*';
+        }
+
+        if ($p['unsubscribed']) {
+            $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
+        }
+        else {
+            $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
+        }
+
+        $delimiter = $storage->get_hierarchy_delimiter();
+
+        foreach ($list as $folder) {
+            if (empty($p['exceptions']) || !in_array($folder, $p['exceptions'])) {
+                self::build_folder_tree($a_mailboxes, $folder, $delimiter);
+            }
+        }
+
+        $select = new html_select($p);
+
+        if ($p['noselection']) {
+            $select->add($p['noselection'], '');
+        }
+
+        self::render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
+
+        return $select;
+    }
+
+
+    /**
+     * Create a hierarchical array of the mailbox list
+     */
+    private static function build_folder_tree(&$arrFolders, $folder, $delm = '/', $path = '')
+    {
+        global $RCMAIL;
+
+        // Handle namespace prefix
+        $prefix = '';
+        if (!$path) {
+            $n_folder = $folder;
+            $folder = $RCMAIL->storage->mod_folder($folder);
+
+            if ($n_folder != $folder) {
+                $prefix = substr($n_folder, 0, -strlen($folder));
+            }
+        }
+
+        $pos = strpos($folder, $delm);
+
+        if ($pos !== false) {
+            $subFolders    = substr($folder, $pos+1);
+            $currentFolder = substr($folder, 0, $pos);
+
+            // sometimes folder has a delimiter as the last character
+            if (!strlen($subFolders)) {
+                $virtual = false;
+            }
+            else if (!isset($arrFolders[$currentFolder])) {
+                $virtual = true;
+            }
+            else {
+                $virtual = $arrFolders[$currentFolder]['virtual'];
+            }
+        }
+        else {
+            $subFolders    = false;
+            $currentFolder = $folder;
+            $virtual       = false;
+        }
+
+        $path .= $prefix . $currentFolder;
+
+        if (!isset($arrFolders[$currentFolder])) {
+            $arrFolders[$currentFolder] = array(
+                'id' => $path,
+                'name' => rcube_charset::convert($currentFolder, 'UTF7-IMAP'),
+                'virtual' => $virtual,
+                'folders' => array());
+        }
+        else {
+            $arrFolders[$currentFolder]['virtual'] = $virtual;
+        }
+
+        if (strlen($subFolders)) {
+            self::build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
+        }
+    }
+
+
+    /**
+     * Return html for a structured list &lt;ul&gt; for the mailbox tree
+     */
+    private static function render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel = 0)
+    {
+        global $RCMAIL;
+
+        $maxlength = intval($attrib['maxlength']);
+        $realnames = (bool)$attrib['realnames'];
+        $msgcounts = $RCMAIL->storage->get_cache('messagecount');
+        $collapsed = $RCMAIL->config->get('collapsed_folders');
+
+        $out = '';
+        foreach ($arrFolders as $key => $folder) {
+            $title        = null;
+            $folder_class = self::folder_classname($folder['id']);
+            $collapsed    = strpos($collapsed, '&'.rawurlencode($folder['id']).'&') !== false;
+            $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
+
+            if ($folder_class && !$realnames) {
+                $foldername = $RCMAIL->gettext($folder_class);
+            }
+            else {
+                $foldername = $folder['name'];
+
+                // shorten the folder name to a given length
+                if ($maxlength && $maxlength > 1) {
+                    $fname = abbreviate_string($foldername, $maxlength);
+                    if ($fname != $foldername) {
+                        $title = $foldername;
+                    }
+                    $foldername = $fname;
+                }
+            }
+
+            // make folder name safe for ids and class names
+            $folder_id = self::html_identifier($folder['id'], true);
+            $classes   = array('mailbox');
+
+            // set special class for Sent, Drafts, Trash and Junk
+            if ($folder_class) {
+                $classes[] = $folder_class;
+            }
+
+            if ($folder['id'] == $mbox_name) {
+                $classes[] = 'selected';
+            }
+
+            if ($folder['virtual']) {
+                $classes[] = 'virtual';
+            }
+            else if ($unread) {
+                $classes[] = 'unread';
+            }
+
+            $js_name = self::JQ($folder['id']);
+            $html_name = self::Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $unread)) : '');
+            $link_attrib = $folder['virtual'] ? array() : array(
+                'href' => self::url('', array('_mbox' => $folder['id'])),
+                'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
+                'rel' => $folder['id'],
+                'title' => $title,
+            );
+
+            $out .= html::tag('li', array(
+                'id' => "rcmli".$folder_id,
+                'class' => join(' ', $classes),
+                'noclose' => true),
+                html::a($link_attrib, $html_name) .
+                (!empty($folder['folders']) ? html::div(array(
+                    'class' => ($collapsed ? 'collapsed' : 'expanded'),
+                    'style' => "position:absolute",
+                    'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
+                ), '&nbsp;') : ''));
+
+            $jslist[$folder_id] = array(
+                'id'      => $folder['id'],
+                'name'    => $foldername,
+                'virtual' => $folder['virtual']
+            );
+
+            if (!empty($folder['folders'])) {
+                $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
+                    self::render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
+            }
+
+            $out .= "</li>\n";
+        }
+
+        return $out;
+    }
+
+
+    /**
+     * Return html for a flat list <select> for the mailbox tree
+     */
+    private static function render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames = false, $nestLevel = 0, $opts = array())
+    {
+        global $RCMAIL;
+
+        $out = '';
+
+        foreach ($arrFolders as $key => $folder) {
+            // skip exceptions (and its subfolders)
+            if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
+                continue;
+            }
+
+            // skip folders in which it isn't possible to create subfolders
+            if (!empty($opts['skip_noinferiors'])) {
+                $attrs = $RCMAIL->storage->folder_attributes($folder['id']);
+                if ($attrs && in_array('\\Noinferiors', $attrs)) {
+                    continue;
+                }
+            }
+
+            if (!$realnames && ($folder_class = self::folder_classname($folder['id']))) {
+                $foldername = self::label($folder_class);
+            }
+            else {
+                $foldername = $folder['name'];
+
+                // shorten the folder name to a given length
+                if ($maxlength && $maxlength > 1) {
+                    $foldername = abbreviate_string($foldername, $maxlength);
+                }
+
+                 $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
+
+                if (!empty($folder['folders'])) {
+                    $out .= self::render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
+                        $select, $realnames, $nestLevel+1, $opts);
+                }
+            }
+        }
+
+        return $out;
+    }
+
+
+    /**
+     * Return internal name for the given folder if it matches the configured special folders
+     */
+    private static function folder_classname($folder_id)
+    {
+        global $CONFIG;
+
+        if ($folder_id == 'INBOX') {
+            return 'inbox';
+        }
+
+        // for these mailboxes we have localized labels and css classes
+        foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
+        {
+            if ($folder_id == $CONFIG[$smbx.'_mbox']) {
+                return $smbx;
+            }
+        }
+    }
+
+
+    /**
+     * Try to localize the given IMAP folder name.
+     * UTF-7 decode it in case no localized text was found
+     *
+     * @param string $name  Folder name
+     *
+     * @return string Localized folder name in UTF-8 encoding
+     */
+    public static function localize_foldername($name)
+    {
+        if ($folder_class = self::folder_classname($name)) {
+            return self::label($folder_class);
+        }
+        else {
+            return rcube_charset::convert($name, 'UTF7-IMAP');
+        }
+    }
+
+
+    public static function localize_folderpath($path)
+    {
+        global $RCMAIL;
+
+        $protect_folders = $RCMAIL->config->get('protect_default_folders');
+        $default_folders = (array) $RCMAIL->config->get('default_folders');
+        $delimiter       = $RCMAIL->storage->get_hierarchy_delimiter();
+        $path            = explode($delimiter, $path);
+        $result          = array();
+
+        foreach ($path as $idx => $dir) {
+            $directory = implode($delimiter, array_slice($path, 0, $idx+1));
+            if ($protect_folders && in_array($directory, $default_folders)) {
+                unset($result);
+                $result[] = self::localize_foldername($directory);
+            }
+            else {
+                $result[] = rcube_charset::convert($dir, 'UTF7-IMAP');
+            }
+        }
+
+        return implode($delimiter, $result);
+    }
+
+
+    public static function quota_display($attrib)
+    {
+        global $OUTPUT;
+
+        if (!$attrib['id']) {
+            $attrib['id'] = 'rcmquotadisplay';
+        }
+
+        $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
+
+        $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
+
+        $quota = self::quota_content($attrib);
+
+        $OUTPUT->add_script('rcmail.set_quota('.rcube_output::json_serialize($quota).');', 'docready');
+
+        return html::span($attrib, '');
+    }
+
+
+    public static function quota_content($attrib = null)
+    {
+        global $RCMAIL;
+
+        $quota = $RCMAIL->storage->get_quota();
+        $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
+
+        $quota_result = (array) $quota;
+        $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
+
+        if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
+            $quota_result['title']   = self::label('unlimited');
+            $quota_result['percent'] = 0;
+        }
+        else if ($quota['total']) {
+            if (!isset($quota['percent'])) {
+                $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
+            }
+
+            $title = sprintf('%s / %s (%.0f%%)',
+                self::show_bytes($quota['used'] * 1024), self::show_bytes($quota['total'] * 1024),
+                $quota_result['percent']);
+
+            $quota_result['title'] = $title;
+
+            if ($attrib['width']) {
+                $quota_result['width'] = $attrib['width'];
+            }
+            if ($attrib['height']) {
+                $quota_result['height']	= $attrib['height'];
+            }
+        }
+        else {
+            $quota_result['title']   = self::label('unknown');
+            $quota_result['percent'] = 0;
+        }
+
+        return $quota_result;
+    }
+
+
+    /**
+     * Outputs error message according to server error/response codes
+     *
+     * @param string $fallback       Fallback message label
+     * @param array  $fallback_args  Fallback message label arguments
+     */
+    public static function display_server_error($fallback = null, $fallback_args = null)
+    {
+        global $RCMAIL;
+
+        $err_code = $RCMAIL->storage->get_error_code();
+        $res_code = $RCMAIL->storage->get_response_code();
+
+        if ($err_code < 0) {
+            $RCMAIL->output->show_message('storageerror', 'error');
+        }
+        else if ($res_code == rcube_storage::NOPERM) {
+            $RCMAIL->output->show_message('errornoperm', 'error');
+        }
+        else if ($res_code == rcube_storage::READONLY) {
+            $RCMAIL->output->show_message('errorreadonly', 'error');
+        }
+        else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) {
+            // try to detect access rights problem and display appropriate message
+            if (stripos($err_str, 'Permission denied') !== false) {
+                $RCMAIL->output->show_message('errornoperm', 'error');
+            }
+            else {
+                $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
+            }
+        }
+        else if ($fallback) {
+            $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
+        }
+    }
+
+
+    /**
+     * Generate CSS classes from mimetype and filename extension
+     *
+     * @param string $mimetype  Mimetype
+     * @param string $filename  Filename
+     *
+     * @return string CSS classes separated by space
+     */
+    public static function file2class($mimetype, $filename)
+    {
+        list($primary, $secondary) = explode('/', $mimetype);
+
+        $classes = array($primary ? $primary : 'unknown');
+        if ($secondary) {
+            $classes[] = $secondary;
+        }
+        if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
+            $classes[] = $m[1];
+        }
+
+        return strtolower(join(" ", $classes));
+    }
+
+
+    /**
+     * Output HTML editor scripts
+     *
+     * @param string $mode  Editor mode
+     */
+    public static function html_editor($mode = '')
+    {
+        global $RCMAIL;
+
+        $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
+
+        if ($hook['abort']) {
+            return;
+        }
+
+        $lang = strtolower($_SESSION['language']);
+
+        // TinyMCE uses two-letter lang codes, with exception of Chinese
+        if (strpos($lang, 'zh_') === 0) {
+            $lang = str_replace('_', '-', $lang);
+        }
+        else {
+            $lang = substr($lang, 0, 2);
+        }
+
+        if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) {
+            $lang = 'en';
+        }
+
+        $script = json_encode(array(
+            'mode'       => $mode,
+            'lang'       => $lang,
+            'skin_path'  => $RCMAIL->output->get_skin_path(),
+            'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
+            'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary'))
+        ));
+
+        $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
+        $RCMAIL->output->include_script('editor.js');
+        $RCMAIL->output->add_script("rcmail_editor_init($script)", 'docready');
+    }
+
+
+    /**
+     * Replaces TinyMCE's emoticon images with plain-text representation
+     *
+     * @param string $html  HTML content
+     *
+     * @return string HTML content
+     */
+    public static function replace_emoticons($html)
+    {
+        $emoticons = array(
+            '8-)' => 'smiley-cool',
+            ':-#' => 'smiley-foot-in-mouth',
+            ':-*' => 'smiley-kiss',
+            ':-X' => 'smiley-sealed',
+            ':-P' => 'smiley-tongue-out',
+            ':-@' => 'smiley-yell',
+            ":'(" => 'smiley-cry',
+            ':-(' => 'smiley-frown',
+            ':-D' => 'smiley-laughing',
+            ':-)' => 'smiley-smile',
+            ':-S' => 'smiley-undecided',
+            ':-$' => 'smiley-embarassed',
+            'O:-)' => 'smiley-innocent',
+            ':-|' => 'smiley-money-mouth',
+            ':-O' => 'smiley-surprised',
+            ';-)' => 'smiley-wink',
+        );
+
+        foreach ($emoticons as $idx => $file) {
+            // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
+            $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
+            $replace[] = $idx;
+        }
+
+        return preg_replace($search, $replace, $html);
+    }
+
+
+    /**
+     * File upload progress handler.
+     */
+    public static function upload_progress()
+    {
+        global $RCMAIL;
+
+        $prefix = ini_get('apc.rfc1867_prefix');
+        $params = array(
+            'action' => $RCMAIL->action,
+            'name' => self::get_input_value('_progress', self::INPUT_GET),
+        );
+
+        if (function_exists('apc_fetch')) {
+            $status = apc_fetch($prefix . $params['name']);
+
+            if (!empty($status)) {
+                $status['percent'] = round($status['current']/$status['total']*100);
+                $params = array_merge($status, $params);
+            }
+        }
+
+        if (isset($params['percent']))
+            $params['text'] = self::label(array('name' => 'uploadprogress', 'vars' => array(
+                'percent' => $params['percent'] . '%',
+                'current' => self::show_bytes($params['current']),
+                'total'   => self::show_bytes($params['total'])
+        )));
+
+        $RCMAIL->output->command('upload_progress_update', $params);
+        $RCMAIL->output->send();
+    }
+
+
+    /**
+     * Initializes file uploading interface.
+     */
+    public static function upload_init()
+    {
+        global $RCMAIL;
+
+        // Enable upload progress bar
+        if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
+            if ($field_name = ini_get('apc.rfc1867_name')) {
+                $RCMAIL->output->set_env('upload_progress_name', $field_name);
+                $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
+            }
+        }
+
+        // find max filesize value
+        $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
+        $max_postsize = parse_bytes(ini_get('post_max_size'));
+        if ($max_postsize && $max_postsize < $max_filesize) {
+            $max_filesize = $max_postsize;
+        }
+
+        $RCMAIL->output->set_env('max_filesize', $max_filesize);
+        $max_filesize = self::show_bytes($max_filesize);
+        $RCMAIL->output->set_env('filesizeerror', self::label(array(
+            'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
+
+        return $max_filesize;
+    }
+
+
+    /**
+     * Initializes client-side autocompletion.
+     */
+    public static function autocomplete_init()
+    {
+        global $RCMAIL;
+        static $init;
+
+        if ($init) {
+            return;
+        }
+
+        $init = 1;
+
+        if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
+            $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
+            if (count($book_types) > 1) {
+                $RCMAIL->output->set_env('autocomplete_threads', $threads);
+                $RCMAIL->output->set_env('autocomplete_sources', $book_types);
+            }
+        }
+
+        $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
+        $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
+        $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
+    }
+
+
+    /**
+     * Returns supported font-family specifications
+     *
+     * @param string $font  Font name
+     *
+     * @param string|array Font-family specification array or string (if $font is used)
+     */
+    public static function font_defs($font = null)
+    {
+        $fonts = array(
+            'Andale Mono'   => '"Andale Mono",Times,monospace',
+            'Arial'         => 'Arial,Helvetica,sans-serif',
+            'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
+            'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
+            'Courier New'   => '"Courier New",Courier,monospace',
+            'Georgia'       => 'Georgia,Palatino,serif',
+            'Helvetica'     => 'Helvetica,Arial,sans-serif',
+            'Impact'        => 'Impact,Chicago,sans-serif',
+            'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
+            'Terminal'      => 'Terminal,Monaco,monospace',
+            'Times New Roman' => '"Times New Roman",Times,serif',
+            'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
+            'Verdana'       => 'Verdana,Geneva,sans-serif',
+        );
+
+        if ($font) {
+            return $fonts[$font];
+        }
+
+        return $fonts;
+    }
+
+
+    /**
+     * Create a human readable string for a number of bytes
+     *
+     * @param int Number of bytes
+     *
+     * @return string Byte string
+     */
+    public static function show_bytes($bytes)
+    {
+        if ($bytes >= 1073741824) {
+            $gb  = $bytes/1073741824;
+            $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . self::label('GB');
+        }
+        else if ($bytes >= 1048576) {
+            $mb  = $bytes/1048576;
+            $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . self::label('MB');
+        }
+        else if ($bytes >= 1024) {
+            $str = sprintf("%d ",  round($bytes/1024)) . self::label('KB');
+        }
+        else {
+            $str = sprintf('%d ', $bytes) . self::label('B');
+        }
+
+        return $str;
+    }
+
+
+    /**
+     * Decode escaped entities used by known XSS exploits.
+     * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
+     *
+     * @param string CSS content to decode
+     *
+     * @return string Decoded string
+     * @todo I'm not sure this should belong to rcube_ui class
+     */
+    public static function xss_entity_decode($content)
+    {
+        $out = html_entity_decode(html_entity_decode($content));
+        $out = preg_replace_callback('/\\\([0-9a-f]{4})/i',
+            array(self, 'xss_entity_decode_callback'), $out);
+        $out = preg_replace('#/\*.*\*/#Ums', '', $out);
+
+        return $out;
+    }
+
+
+    /**
+     * preg_replace_callback callback for xss_entity_decode
+     *
+     * @param array $matches Result from preg_replace_callback
+     *
+     * @return string Decoded entity
+     */
+    public static function xss_entity_decode_callback($matches)
+    {
+        return chr(hexdec($matches[1]));
+    }
+
+
+    /**
+     * Check if we can process not exceeding memory_limit
+     *
+     * @param integer Required amount of memory
+     *
+     * @return boolean True if memory won't be exceeded, False otherwise
+     */
+    public static function mem_check($need)
+    {
+        $mem_limit = parse_bytes(ini_get('memory_limit'));
+        $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
+
+        return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
+    }
+
+
+    /**
+     * Check if working in SSL mode
+     *
+     * @param integer $port      HTTPS port number
+     * @param boolean $use_https Enables 'use_https' option checking
+     *
+     * @return boolean
+     */
+    public static function https_check($port=null, $use_https=true)
+    {
+        global $RCMAIL;
+
+        if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
+            return true;
+        }
+        if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') {
+            return true;
+        }
+        if ($port && $_SERVER['SERVER_PORT'] == $port) {
+            return true;
+        }
+        if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https')) {
+            return true;
+        }
+
+        return false;
+    }
+
+}
diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php
index d1df643..8042674 100644
--- a/program/include/rcube_user.php
+++ b/program/include/rcube_user.php
@@ -66,7 +66,7 @@
 
         if ($id && !$sql_arr) {
             $sql_result = $this->db->query(
-                "SELECT * FROM ".get_table_name('users')." WHERE user_id = ?", $id);
+                "SELECT * FROM ".$this->db->table_name('users')." WHERE user_id = ?", $id);
             $sql_arr = $this->db->fetch_assoc($sql_result);
         }
 
@@ -127,9 +127,9 @@
             if (!empty($_SESSION['preferences'])) {
                 // Check last write attempt time, try to write again (every 5 minutes)
                 if ($_SESSION['preferences_time'] < time() - 5 * 60) {
-		    $saved_prefs = unserialize($_SESSION['preferences']);
+                    $saved_prefs = unserialize($_SESSION['preferences']);
                     $this->rc->session->remove('preferences');
-	            $this->rc->session->remove('preferences_time');
+                    $this->rc->session->remove('preferences_time');
                     $this->save_prefs($saved_prefs);
                 }
                 else {
@@ -173,7 +173,7 @@
         $save_prefs = serialize($save_prefs);
 
         $this->db->query(
-            "UPDATE ".get_table_name('users').
+            "UPDATE ".$this->db->table_name('users').
             " SET preferences = ?".
                 ", language = ?".
             " WHERE user_id = ?",
@@ -232,7 +232,7 @@
         $result = array();
 
         $sql_result = $this->db->query(
-            "SELECT * FROM ".get_table_name('identities').
+            "SELECT * FROM ".$this->db->table_name('identities').
             " WHERE del <> 1 AND user_id = ?".
             ($sql_add ? " ".$sql_add : "").
             " ORDER BY ".$this->db->quoteIdentifier('standard')." DESC, name ASC, identity_id ASC",
@@ -267,7 +267,7 @@
         $query_params[] = $iid;
         $query_params[] = $this->ID;
 
-        $sql = "UPDATE ".get_table_name('identities').
+        $sql = "UPDATE ".$this->db->table_name('identities').
             " SET changed = ".$this->db->now().", ".join(', ', $query_cols).
             " WHERE identity_id = ?".
                 " AND user_id = ?".
@@ -301,7 +301,7 @@
         $insert_cols[]   = 'user_id';
         $insert_values[] = $this->ID;
 
-        $sql = "INSERT INTO ".get_table_name('identities').
+        $sql = "INSERT INTO ".$this->db->table_name('identities').
             " (changed, ".join(', ', $insert_cols).")".
             " VALUES (".$this->db->now().", ".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
 
@@ -324,7 +324,7 @@
             return false;
 
         $sql_result = $this->db->query(
-            "SELECT count(*) AS ident_count FROM ".get_table_name('identities').
+            "SELECT count(*) AS ident_count FROM ".$this->db->table_name('identities').
             " WHERE user_id = ? AND del <> 1",
             $this->ID);
 
@@ -335,7 +335,7 @@
             return -1;
 
         $this->db->query(
-            "UPDATE ".get_table_name('identities').
+            "UPDATE ".$this->db->table_name('identities').
             " SET del = 1, changed = ".$this->db->now().
             " WHERE user_id = ?".
                 " AND identity_id = ?",
@@ -355,7 +355,7 @@
     {
         if ($this->ID && $iid) {
             $this->db->query(
-                "UPDATE ".get_table_name('identities').
+                "UPDATE ".$this->db->table_name('identities').
                 " SET ".$this->db->quoteIdentifier('standard')." = '0'".
                 " WHERE user_id = ?".
                     " AND identity_id <> ?".
@@ -373,7 +373,7 @@
     {
         if ($this->ID) {
             $this->db->query(
-                "UPDATE ".get_table_name('users').
+                "UPDATE ".$this->db->table_name('users').
                 " SET last_login = ".$this->db->now().
                 " WHERE user_id = ?",
                 $this->ID);
@@ -403,7 +403,7 @@
         $dbh = rcmail::get_instance()->get_dbh();
 
         // query for matching user name
-        $query = "SELECT * FROM ".get_table_name('users')." WHERE mail_host = ? AND %s = ?";
+        $query = "SELECT * FROM ".$dbh->table_name('users')." WHERE mail_host = ? AND %s = ?";
         $sql_result = $dbh->query(sprintf($query, 'username'), $host, $user);
 
         // query for matching alias
@@ -451,7 +451,7 @@
         $dbh = $rcmail->get_dbh();
 
         $dbh->query(
-            "INSERT INTO ".get_table_name('users').
+            "INSERT INTO ".$dbh->table_name('users').
             " (created, last_login, username, mail_host, alias, language)".
             " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?, ?)",
             strip_newlines($user),
@@ -507,7 +507,7 @@
             }
         }
         else {
-            raise_error(array(
+            rcube::raise_error(array(
                 'code' => 500,
                 'type' => 'php',
                 'line' => __LINE__,
@@ -573,7 +573,7 @@
 
         $sql_result = $this->db->query(
             "SELECT search_id AS id, ".$this->db->quoteIdentifier('name')
-            ." FROM ".get_table_name('searches')
+            ." FROM ".$this->db->table_name('searches')
             ." WHERE user_id = ?"
                 ." AND ".$this->db->quoteIdentifier('type')." = ?"
             ." ORDER BY ".$this->db->quoteIdentifier('name'),
@@ -607,7 +607,7 @@
             "SELECT ".$this->db->quoteIdentifier('name')
                 .", ".$this->db->quoteIdentifier('data')
                 .", ".$this->db->quoteIdentifier('type')
-            ." FROM ".get_table_name('searches')
+            ." FROM ".$this->db->table_name('searches')
             ." WHERE user_id = ?"
                 ." AND search_id = ?",
             (int) $this->ID, (int) $id);
@@ -638,7 +638,7 @@
             return false;
 
         $this->db->query(
-            "DELETE FROM ".get_table_name('searches')
+            "DELETE FROM ".$this->db->table_name('searches')
             ." WHERE user_id = ?"
                 ." AND search_id = ?",
             (int) $this->ID, $sid);
@@ -668,7 +668,7 @@
         $insert_cols[]   = $this->db->quoteIdentifier('data');
         $insert_values[] = serialize($data['data']);
 
-        $sql = "INSERT INTO ".get_table_name('searches')
+        $sql = "INSERT INTO ".$this->db->table_name('searches')
             ." (".join(', ', $insert_cols).")"
             ." VALUES (".join(', ', array_pad(array(), sizeof($insert_values), '?')).")";
 
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index ad8e35e..7163ef7 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -389,7 +389,7 @@
         if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
           foreach ($subnode as $j => $value) {
             if (is_numeric($j) && is_string($value))
-              $card[$key][$i][$j] = rcube_charset_convert($value, $charset);
+              $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
           }
           unset($card[$key][$i]['charset']);
         }
@@ -425,7 +425,7 @@
       $charset = null;
     // detect charset and convert to utf-8
     else if (($charset = self::detect_encoding($data)) && $charset != RCMAIL_CHARSET) {
-      $data = rcube_charset_convert($data, $charset);
+      $data = rcube_charset::convert($data, $charset);
       $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
       $charset = RCMAIL_CHARSET;
     }
@@ -780,7 +780,7 @@
         )*\z/xs', substr($string, 0, 2048)))
       return 'UTF-8';
 
-    return rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'); # fallback to Latin-1
+    return rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); # fallback to Latin-1
   }
 
 }
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index a31370b..e52da39 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -203,7 +203,7 @@
             'rel' => '%s',
             'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
 
-    $sources = (array) $OUTPUT->env['address_sources'];
+    $sources = (array) $OUTPUT->get_env('address_sources');
     reset($sources);
 
     // currently selected source
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index ebf79be..c0a5bf7 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -1162,10 +1162,22 @@
     $data = $message->get_part_content($pid);
   }
 
+  $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
+  $filename = $part->filename;
+  if (!strlen($filename)) {
+    if ($mimetype == 'text/html') {
+      $filename = rcube_label('htmlmessage');
+    }
+    else {
+      $filename = 'Part_'.$pid;
+    }
+    $filename .= '.' . $part->ctype_secondary;
+  }
+
   $attachment = array(
     'group' => $COMPOSE['id'],
-    'name' => $part->filename ? $part->filename : 'Part_'.$pid.'.'.$part->ctype_secondary,
-    'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
+    'name' => $filename,
+    'mimetype' => $mimetype,
     'content_id' => $part->content_id,
     'data' => $data,
     'path' => $path,
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 8dcd37b..319166c 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -230,7 +230,7 @@
   // Make sure there are no duplicated columns (#1486999)
   $a_show_cols = array_unique($a_show_cols);
 
-  // Plugins may set header's list_cols/list_flags and other rcube_mail_header variables
+  // Plugins may set header's list_cols/list_flags and other rcube_message_header variables
   // and list columns
   $plugin = $RCMAIL->plugins->exec_hook('messages_list',
     array('messages' => $a_headers, 'cols' => $a_show_cols));
@@ -1024,10 +1024,20 @@
     foreach ($MESSAGE->parts as $i => $part) {
       if ($part->type == 'headers')
         $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers);
-      else if ($part->type == 'content' && $part->size) {
+      else if ($part->type == 'content') {
+        // unsapported
+        if ($part->realtype) {
+          if ($part->realtype == 'multipart/encrypted') {
+            $out .= html::span('part-notice', rcube_label('encryptedmessage'));
+          }
+          continue;
+        }
+        else if (!$part->size) {
+          continue;
+        }
         // Check if we have enough memory to handle the message in it
         // #1487424: we need up to 10x more memory than the body
-        if (!rcmail_mem_check($part->size * 10)) {
+        else if (!rcmail_mem_check($part->size * 10)) {
           $out .= html::span('part-notice', rcube_label('messagetoobig'). ' '
             . html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part='.$part->mime_id
               .'&_mbox='. urlencode($RCMAIL->storage->get_folder()), rcube_label('download')));
@@ -1438,9 +1448,14 @@
   $part = $MESSAGE->mime_parts[$part];
   $table = new html_table(array('cols' => 3));
 
-  if (!empty($part->filename)) {
+  $filename = $part->filename;
+  if (empty($filename) && $attach_prop->mimetype == 'text/html') {
+    $filename = rcube_label('htmlmessage');
+  }
+
+  if (!empty($filename)) {
     $table->add('title', Q(rcube_label('filename')));
-    $table->add('header', Q($part->filename));
+    $table->add('header', Q($filename));
     $table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download'))));
   }
 
diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
index 924433d..19be4bd 100644
--- a/program/steps/mail/get.inc
+++ b/program/steps/mail/get.inc
@@ -69,8 +69,15 @@
 
 // show part page
 if (!empty($_GET['_frame'])) {
-  if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id]) && $part->filename)
-    $OUTPUT->set_pagetitle($part->filename);
+  if (($part_id = get_input_value('_part', RCUBE_INPUT_GPC)) && ($part = $MESSAGE->mime_parts[$part_id])) {
+    $filename = $part->filename;
+    if (empty($filename) && $part->mimetype == 'text/html') {
+      $filename = rcube_label('htmlmessage');
+    }
+    if (!empty($filename)) {
+      $OUTPUT->set_pagetitle($filename);
+    }
+  }
 
   $OUTPUT->send('messagepart');
   exit;
@@ -130,15 +137,25 @@
         $out = rcmail_print_body($part, array('safe' => $MESSAGE->is_safe, 'inline_html' => false));
       }
 
-      $OUTPUT = new rcube_html_page();
+      $OUTPUT = new rcube_output_html();
       $OUTPUT->write($out);
     }
     else {
       // don't kill the connection if download takes more than 30 sec.
       @set_time_limit(0);
 
+      if ($part->filename) {
+        $filename = $part->filename;
+      }
+      else if ($part->mimetype == 'text/html') {
+        $filename = rcube_label('htmlmessage');
+      }
+      else {
+        $filename = ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube');
+      }
+
       $ext      = '.' . ($mimetype == 'text/plain' ? 'txt' : $ctype_secondary);
-      $filename = $part->filename ? $part->filename : ($MESSAGE->subject ? $MESSAGE->subject : 'roundcube') . $ext;
+      $filename .= $ext;
       $filename = preg_replace('[\r\n]', '', $filename);
 
       if ($browser->ie && $browser->ver < 7)
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index c6c6d96..076098a 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -126,20 +126,24 @@
 
   if (sizeof($MESSAGE->attachments)) {
     foreach ($MESSAGE->attachments as $attach_prop) {
-      if ($PRINT_MODE) {
-        $ol .= html::tag('li', null, sprintf("%s (%s)", Q($attach_prop->filename), Q(show_bytes($attach_prop->size))));
-      }
-      else {
-        if (mb_strlen($attach_prop->filename) > 50) {
-          $filename = abbreviate_string($attach_prop->filename, 50);
-          $title = $attach_prop->filename;
-      }
-      else {
-        $filename = $attach_prop->filename;
-        $title = '';
+      $filename = $attach_prop->filename;
+      if (empty($filename) && $attach_prop->mimetype == 'text/html') {
+        $filename = rcube_label('htmlmessage');
       }
 
-        $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $attach_prop->filename),
+      if ($PRINT_MODE) {
+        $ol .= html::tag('li', null, sprintf("%s (%s)", Q($filename), Q(show_bytes($attach_prop->size))));
+      }
+      else {
+        if (mb_strlen($filename) > 50) {
+          $filename = abbreviate_string($filename, 50);
+          $title = $filename;
+        }
+        else {
+          $title = '';
+        }
+
+        $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $filename),
           html::a(array(
             'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
             'onclick' => sprintf(
diff --git a/program/steps/utils/error.inc b/program/steps/utils/error.inc
index 050c1f7..0006744 100644
--- a/program/steps/utils/error.inc
+++ b/program/steps/utils/error.inc
@@ -22,6 +22,7 @@
 
 */
 
+$rcmail = rcmail::get_instance();
 
 // browser is not compatible with this application
 if ($ERROR_CODE==409) {
@@ -88,7 +89,7 @@
   $__error_title = "SERVICE CURRENTLY NOT AVAILABLE!";
   $__error_text  = "Please contact your server-administrator.";
 
-  if (($CONFIG['debug_level'] & 4) && $ERROR_MESSAGE)
+  if (($rcmail->config->get('debug_level') & 4) && $ERROR_MESSAGE)
     $__error_text = $ERROR_MESSAGE;
   else
     $__error_text = sprintf('Error No. [%s]', $ERROR_CODE);
@@ -97,7 +98,7 @@
 $HTTP_ERR_CODE = $ERROR_CODE && $ERROR_CODE < 600 ? $ERROR_CODE : 500;
 
 // Ajax request
-if ($OUTPUT && ($OUTPUT instanceof rcube_json_output)) {
+if ($rcmail->output && $rcmail->output->type == 'js') {
   header("HTTP/1.0 $HTTP_ERR_CODE $__error_title");
   die;
 }
@@ -110,13 +111,13 @@
 </div>
 EOF;
 
-if ($OUTPUT && $OUTPUT->template_exists('error')) {
-  $OUTPUT->reset();
-  $OUTPUT->send('error');
+if ($rcmail->output && $rcmail->output->template_exists('error')) {
+  $rcmail->output->reset();
+  $rcmail->output->send('error');
 }
 
-$__skin = $CONFIG->skin ? $CONFIG->skin : 'default';
-$__productname = $CONFIG['product_name'] ? $CONFIG['product_name'] : 'Roundcube Webmail';
+$__skin = $rcmail->config->get('skin', 'default');
+$__productname = $rcmail->config->get('product_name', 'Roundcube Webmail');
 
 // print system error page
 print <<<EOF

--
Gitblit v1.9.1