From f1154163b0a9efb21d722bc658352739040ffd61 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Sat, 28 Apr 2007 14:07:12 -0400
Subject: [PATCH] Merged branch devel-addressbook from r443 back to trunk

---
 skins/default/templates/showcontact.html   |    2 
 program/js/common.js                       |   21 
 program/include/main.inc                   |  668 +-----
 program/steps/mail/compose.inc             |   98 
 program/localization/de_CH/messages.inc    |    2 
 program/steps/addressbook/mailto.inc       |   48 
 skins/default/mail.css                     |   87 
 program/js/editor.js                       |    4 
 program/steps/addressbook/delete.inc       |   73 
 program/steps/mail/show.inc                |   37 
 program/steps/settings/manage_folders.inc  |  100 
 program/steps/mail/quotadisplay.inc        |    3 
 index.php                                  |  168 
 program/steps/settings/delete_identity.inc |   31 
 program/include/rcube_contacts.inc         |  429 ++++
 program/steps/settings/func.inc            |   20 
 program/steps/addressbook/show.inc         |   61 
 program/steps/mail/mark.inc                |   24 
 program/steps/addressbook/copy.inc         |   44 
 program/steps/settings/identities.inc      |   10 
 bin/quotaimg.php                           |   49 
 UPGRADING                                  |    2 
 config/main.inc.php.dist                   |   20 
 program/steps/settings/save_prefs.inc      |    9 
 program/steps/mail/move_del.inc            |   49 
 program/include/rcube_db.inc               |    4 
 program/steps/mail/list.inc                |   24 
 program/steps/error.inc                    |    2 
 program/steps/mail/upload.inc              |   37 
 program/localization/en_US/messages.inc    |    9 
 program/steps/mail/getunread.inc           |   13 
 skins/default/templates/compose.html       |   13 
 program/js/app.js                          |  549 +++--
 program/include/rcube_ldap.inc             |  647 ++++--
 program/steps/addressbook/search.inc       |   53 
 skins/default/addresses.css                |   95 
 program/steps/addressbook/list.inc         |   37 
 skins/default/common.css                   |   84 
 program/include/rcmail_template.inc        |  631 ++++++
 skins/default/templates/addcontact.html    |    1 
 program/localization/en_GB/labels.inc      |    9 
 program/include/rcube_shared.inc           |  190 -
 program/steps/mail/folders.inc             |   34 
 program/steps/mail/check_recent.inc        |   19 
 program/steps/mail/func.inc                |  236 +-
 skins/default/templates/addressbook.html   |   22 
 skins/default/print.css                    |    2 
 program/include/rcube_imap.inc             |   18 
 program/steps/mail/addcontact.inc          |   68 
 program/js/list.js                         |    8 
 program/steps/addressbook/edit.inc         |   75 
 program/steps/addressbook/save.inc         |  274 --
 program/steps/addressbook/func.inc         |  251 +-
 program/steps/settings/save_identity.inc   |   10 
 program/steps/mail/sendmail.inc            |   79 
 program/steps/settings/edit_identity.inc   |   17 
 /dev/null                                  |   31 
 program/steps/mail/search.inc              |   36 
 README                                     |   30 
 program/localization/en_US/labels.inc      |   12 
 bin/html2text.php                          |   16 
 61 files changed, 3,251 insertions(+), 2,444 deletions(-)

diff --git a/README b/README
index 417e59c..2eb7eaa 100644
--- a/README
+++ b/README
@@ -29,6 +29,18 @@
 Gerich for Mozilla.org.
 
 
+Installation:
+-------------
+For detailed instructions on how to install RoundCube webmail on your server,
+please refer to the INSTALL document in the same directory as this document.
+
+
+Licensing: 
+----------
+This product is distributed under the GPL. Please read through the file
+LICENSE for more information about our license.
+
+
 How it works:
 -------------
 The main authority for the RoundCube access is the IMAP server. If
@@ -42,32 +54,20 @@
 
 Code Layout:
 ------------
-
 Basic sequence (index.php):
-  - index.php -> load_gui -> parse_template
+  - index.php -> rcmail_load_gui -> new rcmail_template -> rcmail_template::send
   - authentication details in this sequence
 
 Tasks
   - index.php limits tasks to set list
   - can see task in roundcube link when you mouse over it
   - task templates stored in skins/default/templates
-  - templates "roundcube:" tokens that get replaced in parse_template
+  - templates "roundcube:" tokens that get replaced in rcmail_template class
 
 program/include/rcube_shared.inc
   - defines rcube_html_page, class that lays out a roundcube web page
   - defines form control classes
-
-
-Installation:
--------------
-For detailed instructions on how to install RoundCube webmail on your server,
-please refer to the INSTALL document in the same directory as this document.
-
-
-Licensing: 
-----------
-This product is distributed under the GPL. Please read through the file
-LICENSE for more information about our license.
+  - provides common functions
 
 
 Contact:
diff --git a/UPGRADING b/UPGRADING
index 4fc2576..4dbec2c 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -17,6 +17,8 @@
   $rcmail_config['preview_pane'] = TRUE;
   $rcmail_config['date_today'] = 'H:i';
   $rcmail_config['double_auth'] = TRUE;
+* If you have LDAP servers configured, change prop key 'mail_field'
+  to 'email_field' for each server confgured in /config/main.inc.php
 
 
 form version 0.1-beta
diff --git a/bin/html2text.php b/bin/html2text.php
new file mode 100644
index 0000000..e0e2679
--- /dev/null
+++ b/bin/html2text.php
@@ -0,0 +1,16 @@
+<?php
+
+require_once('../program/lib/html2text.inc');
+
+$htmlText = $HTTP_RAW_POST_DATA;
+$converter = new html2text($htmlText);
+
+header('Content-Type: text/plain; charset=UTF-8');
+$plaintext = $converter->get_text();
+
+if (function_exists('html_entity_decode'))
+	print html_entity_decode($plaintext, ENT_COMPAT, 'UTF-8');
+else
+	print $plaintext;
+
+?>
\ No newline at end of file
diff --git a/program/steps/mail/quotaimg.inc b/bin/quotaimg.php
similarity index 92%
rename from program/steps/mail/quotaimg.inc
rename to bin/quotaimg.php
index 643ae1c..47696ef 100644
--- a/program/steps/mail/quotaimg.inc
+++ b/bin/quotaimg.php
@@ -2,7 +2,7 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/steps/mail/quotaimg.inc                                       |
+ | program/bin/quotaimg.php                                              |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
  | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
@@ -81,32 +81,29 @@
 	 *****	DO NOT EDIT BELOW HERE	*****
 	 ****************************/
 
-	if(ereg("^[^0-9?]*$", $used) || ereg("^[^0-9?]*$", $total))
-		{ 
+	if (ereg("^[^0-9?]*$", $used) || ereg("^[^0-9?]*$", $total))
 		return false; 
-		}
-	if(strpos($used, '?')!==false || strpos($total, '?')!==false && $used != 0)
-		{ 
+
+	if (strpos($used, '?')!==false || strpos($total, '?')!==false && $used != 0)
 		$unknown = true; 
-		}
 
 	$im = imagecreate($width, $height);
 
-	if($border)
-		{
+	if ($border)
+	{
 		list($r, $g, $b) = explode(',', $color['border']);
 		$borderc = imagecolorallocate($im, $r, $g, $b);
 		imageline($im, 0, 0, $width, 0, $borderc);
 		imageline($im, 0, $height-$border, 0, 0, $borderc);
 		imageline($im, $width-1, 0, $width-$border, $height, $borderc);
 		imageline($im, $width, $height-$border, 0, $height-$border, $borderc);
-		}
+	}
 		
 	list($r, $g, $b) = explode(',', $color['text']);
 	$text = imagecolorallocate($im, $r, $g, $b);
 
-	if($unknown)
-		{
+	if ($unknown)
+	{
 		list($r, $g, $b) = explode(',', $color['bg']['Unknown']);
 		$background = imagecolorallocate($im, $r, $g, $b);
 		imagefilledrectangle($im, 0, 0, $width, $height, $background);
@@ -114,9 +111,9 @@
 		$string = 'Unknown';
 		$mid = floor(($width-(strlen($string)*imagefontwidth($font)))/2)+1;
 		imagestring($im, $font, $mid, $padding, $string, $text);
-		}
-	else if($used > $total)
-		{
+	}
+	else if ($used > $total)
+	{
 		list($r, $g, $b) = explode(',', $color['bg']['OL']);
 		$background = imagecolorallocate($im, $r, $g, $b);
 		imagefilledrectangle($im, 0, 0, $width, $height, $background);
@@ -124,30 +121,30 @@
 		$string = 'Over Limit';
 		$mid = floor(($width-(strlen($string)*imagefontwidth($font)))/2)+1;
 		imagestring($im, $font, $mid, $padding, $string, $text);
-		}
+	}
 	else
-		{
+	{
 		list($r, $g, $b) = explode(',', $color['bg']['quota']);
 		$background = imagecolorallocate($im, $r, $b, $g);
 		imagefilledrectangle($im, 0, 0, $width, $height, $background);
 		
 		$quota = ($used==0)?0:(round($used/$total, 2)*100);
 
-		if($quota >= $limit['high'])
-			{
+		if ($quota >= $limit['high'])
+		{
 			list($r, $g, $b) = explode(',', $color['fill']['high']);
 			$fill = imagecolorallocate($im, $r, $g, $b);
-			}
+		}
 		elseif($quota >= $limit['mid'])
-			{
+		{
 			list($r, $g, $b) = explode(',', $color['fill']['mid']);
 			$fill = imagecolorallocate($im, $r, $g, $b);
-			}
+		}
 		else // if($quota >= $limit['low'])
-			{
+		{
 			list($r, $g, $b) = explode(',', $color['fill']['low']);
 			$fill = imagecolorallocate($im, $r, $g, $b);
-			}
+		}
 
 		$quota_width = $quota / 100 * $width;
 		imagefilledrectangle($im, $border, 0, $quota, $height-2*$border, $fill);
@@ -155,10 +152,10 @@
 		$string = $quota.'%';
 		$mid = floor(($width-(strlen($string)*imagefontwidth($font)))/2)+1;
 		imagestring($im, $font, $mid, $padding, $string, $text); // Print percent in black
-		}
+	}
 
 	header('Content-Type: image/gif');
-    header("Expires: ".gmdate("D, d M Y H:i:s", mktime()+86400)." GMT");
+	header("Expires: ".gmdate("D, d M Y H:i:s", mktime()+86400)." GMT");
 	header("Cache-Control: ");
 	header("Pragma: ");
 	
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index c2c9d39..894dfa4 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -190,16 +190,18 @@
 // like the Verisign example below. if you would like to test, 
 // simply uncomment the Verisign example.
 /** 
- *  example config for Verisign directory
+ * example config for Verisign directory
  *
- *  $rcmail_config['ldap_public']['Verisign'] = array('hosts'         => array('directory.verisign.com'),
- *                                                    'port'          => 389,
- *                                                    'base_dn'       => '',
- *                                                    'search_fields' => array('Email' => 'mail', 'Name' => 'cn'),
- *                                                    'name_field'    => 'cn',
- *                                                    'mail_field'    => 'mail',
- *                                                    'scope'         => 'sub',
- *                                                    'fuzzy_search'  => 0);
+ * $rcmail_config['ldap_public']['Verisign'] = array(
+ *  'name'          => 'Verisign.com',
+ *  'hosts'         => array('directory.verisign.com'),
+ *  'port'          => 389,
+ *  'base_dn'       => '',
+ *  'search_fields' => array('mail', 'cn'),  // fields to search in
+ *  'name_field'    => 'cn',    // this field represents the contact's name
+ *  'email_field'   => 'mail',  // this field represents the contact's e-mail
+ *  'scope'         => 'sub',   // search mode: sub|base|list
+ *  'fuzzy_search'  => true);   // server allows wildcard search
  */
 
 // don't allow these settings to be overriden by the user
diff --git a/index.php b/index.php
index 638bc8e..f62e9eb 100644
--- a/index.php
+++ b/index.php
@@ -2,7 +2,7 @@
 /*
  +-----------------------------------------------------------------------+
  | RoundCube Webmail IMAP Client                                         |
- | Version 0.1-20070411                                                  |
+ | Version 0.1-20070428                                                  |
  |                                                                       |
  | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
@@ -40,12 +40,13 @@
 
 */
 
-define('RCMAIL_VERSION', '0.1-20070411');
+// application constants
+define('RCMAIL_VERSION', '0.1-20070428');
+define('RCMAIL_CHARSET', 'UTF-8');
+define('JS_OBJECT_NAME', 'rcmail');
 
 // define global vars
-$CHARSET = 'UTF-8';
 $OUTPUT_TYPE = 'html';
-$JS_OBJECT_NAME = 'rcmail';
 $INSTALL_PATH = dirname(__FILE__);
 $MAIN_TASKS = array('mail','settings','addressbook','logout');
 
@@ -81,7 +82,6 @@
 require_once('include/bugs.inc');
 require_once('include/main.inc');
 require_once('include/cache.inc');
-require_once('lib/html2text.inc');
 require_once('PEAR.php');
 
 
@@ -98,19 +98,16 @@
 if (empty($_task) || !in_array($_task, $MAIN_TASKS))
   $_task = 'mail';
 
-if (!empty($_GET['_remote']))
-  $REMOTE_REQUEST = TRUE;
-  
 
 // set output buffering
 if ($_action != 'get' && $_action != 'viewsource')
-  {
+{
   // use gzip compression if supported
   if (function_exists('ob_gzhandler') && ini_get('zlib.output_compression'))
     ob_start('ob_gzhandler');
   else
     ob_start();
-  }
+}
 
 
 // start session with requested task
@@ -123,59 +120,45 @@
 
 // add framed parameter
 if ($_framed)
-  {
-  $COMM_PATH .= '&amp;_framed=1';
+{
+  $COMM_PATH .= '&_framed=1';
   $SESS_HIDDEN_FIELD .= "\n".'<input type="hidden" name="_framed" value="1" />';
-  }
+}
 
 
 // init necessary objects for GUI
-load_gui();
+rcmail_load_gui();
 
 
 // check DB connections and exit on failure
 if ($err_str = $DB->is_error())
-  {
-  raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
-                    'message' => $err_str), FALSE, TRUE);
-  }
+{
+  raise_error(array(
+    'code' => 603,
+    'type' => 'db',
+    'message' => $err_str), FALSE, TRUE);
+}
 
 
 // error steps
 if ($_action=='error' && !empty($_GET['_code']))
-  {
   raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
-  }
-
-// handle HTML->text conversion
-if ($_action=='html2text')
-  {
-  $htmlText = $HTTP_RAW_POST_DATA;
-  $converter = new html2text($htmlText);
-
-  // TODO possibly replace with rcube_remote_response()
-  header('Content-Type: text/plain');
-  $plaintext = $converter->get_text();
-  print $plaintext;
-
-  exit;
-  }
 
 
 // try to log in
 if ($_action=='login' && $_task=='mail')
-  {
+{
   $host = rcmail_autoselect_host();
   
   // check if client supports cookies
   if (empty($_COOKIE))
-    {
-    show_message("cookiesdisabled", 'warning');
-    }
+  {
+    $OUTPUT->show_message("cookiesdisabled", 'warning');
+  }
   else if ($_SESSION['temp'] && !empty($_POST['_user']) && isset($_POST['_pass']) &&
            rcmail_login(get_input_value('_user', RCUBE_INPUT_POST),
               get_input_value('_pass', RCUBE_INPUT_POST, true, 'ISO-8859-1'), $host))
-    {
+  {
     // create new session ID
     unset($_SESSION['temp']);
     sess_regenerate_id();
@@ -186,87 +169,84 @@
     // send redirect
     header("Location: $COMM_PATH");
     exit;
-    }
-  else
-    {
-    show_message("loginfailed", 'warning');
-    $_SESSION['user_id'] = '';
-    }
   }
+  else
+  {
+    $OUTPUT->show_message("loginfailed", 'warning');
+    $_SESSION['user_id'] = '';
+  }
+}
 
 // end session
-else if ($_action=='logout' && isset($_SESSION['user_id']))
-  {
-  show_message('loggedout');
+else if (($_task=='logout' || $_action=='logout') && isset($_SESSION['user_id']))
+{
+  $OUTPUT->show_message('loggedout');
   rcmail_kill_session();
-  }
+}
 
 // check session and auth cookie
 else if ($_action != 'login' && $_SESSION['user_id'] && $_action != 'send')
-  {
+{
   if (!rcmail_authenticate_session())
-    {
-    $message = show_message('sessionerror', 'error');
+  {
+    $OUTPUT->show_message('sessionerror', 'error');
     rcmail_kill_session();
-    }
   }
+}
 
 
 // log in to imap server
 if (!empty($_SESSION['user_id']) && $_task=='mail')
-  {
+{
   $conn = $IMAP->connect($_SESSION['imap_host'], $_SESSION['username'], decrypt_passwd($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl']);
   if (!$conn)
-    {
-    show_message('imaperror', 'error');
+  {
+    $OUTPUT->show_message('imaperror', 'error');
     $_SESSION['user_id'] = '';
-    }
+  }
   else
     rcmail_set_imap_prop();
-  }
+}
 
 
 // not logged in -> set task to 'login
 if (empty($_SESSION['user_id']))
-  {
-  if ($REMOTE_REQUEST)
-    {
-    $message .= "setTimeout(\"location.href='\"+this.env.comm_path+\"'\", 2000);";
-    rcube_remote_response($message);
-    }
+{
+  if ($OUTPUT->ajax_call)
+    $OUTPUT->remote_response("setTimeout(\"location.href='\"+this.env.comm_path+\"'\", 2000);");
   
   $_task = 'login';
-  }
+}
 
 
 
 // set task and action to client
-$script = sprintf("%s.set_env('task', '%s');", $JS_OBJECT_NAME, $_task);
+$OUTPUT->set_env('task', $_task);
 if (!empty($_action))
-  $script .= sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $_action);
-
-$OUTPUT->add_script($script);
+  $OUTPUT->set_env('action', $_action);
 
 
 
 // not logged in -> show login page
 if (!$_SESSION['user_id'])
-  {
-  parse_template('login');
+{
+  $OUTPUT->task = 'login';
+  $OUTPUT->send('login');
   exit;
-  }
+}
 
 
 // handle keep-alive signal
 if ($_action=='keep-alive')
-  {
-  rcube_remote_response('');
+{
+  $OUTPUT->reset();
+  $OUTPUT->send('');
   exit;
-  }
+}
 
 // include task specific files
 if ($_task=='mail')
-  {
+{
   include_once('program/steps/mail/func.inc');
   
   if ($_action=='show' || $_action=='preview' || $_action=='print')
@@ -317,21 +297,18 @@
   if ($_action=='rss')
     include('program/steps/mail/rss.inc');
     
-  if ($_action=='quotaimg')
-    include('program/steps/mail/quotaimg.inc');
-
   if ($_action=='quotadisplay')
     include('program/steps/mail/quotadisplay.inc');
 
 
   // make sure the message count is refreshed
   $IMAP->messagecount($_SESSION['mbox'], 'ALL', TRUE);
-  }
+}
 
 
 // include task specific files
 if ($_task=='addressbook')
-  {
+{
   include_once('program/steps/addressbook/func.inc');
 
   if ($_action=='save')
@@ -349,14 +326,20 @@
   if ($_action=='list' && $_GET['_remote'])
     include('program/steps/addressbook/list.inc');
 
-  if ($_action=='ldappublicsearch')
-    include('program/steps/addressbook/ldapsearchform.inc');
-  }
+  if ($_action=='search')
+    include('program/steps/addressbook/search.inc');
+
+  if ($_action=='copy')
+    include('program/steps/addressbook/copy.inc');
+
+  if ($_action=='mailto')
+    include('program/steps/addressbook/mailto.inc');
+}
 
 
 // include task specific files
 if ($_task=='settings')
-  {
+{
   include_once('program/steps/settings/func.inc');
 
   if ($_action=='save-identity')
@@ -378,18 +361,19 @@
       $_action=='create-folder' || $_action=='rename-folder' || $_action=='delete-folder')
     include('program/steps/settings/manage_folders.inc');
 
-  }
+}
 
 
 // parse main template
-parse_template($_task);
+$OUTPUT->send($_task);
 
 
 // if we arrive here, something went wrong
-raise_error(array('code' => 404,
-                  'type' => 'php',
-                  'line' => __LINE__,
-                  'file' => __FILE__,
-                  'message' => "Invalid request"), TRUE, TRUE);
+raise_error(array(
+  'code' => 404,
+  'type' => 'php',
+  'line' => __LINE__,
+  'file' => __FILE__,
+  'message' => "Invalid request"), TRUE, TRUE);
                       
 ?>
diff --git a/program/include/main.inc b/program/include/main.inc
index 6d77d5a..71534f6 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -5,7 +5,7 @@
  | program/include/main.inc                                              |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev, - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -22,6 +22,7 @@
 require_once('lib/des.inc');
 require_once('lib/utf7.inc');
 require_once('lib/utf8.class.php');
+require_once('include/rcmail_template.inc');
 
 
 // define constannts for input reading
@@ -34,7 +35,7 @@
 function rcmail_startup($task='mail')
   {
   global $sess_id, $sess_user_lang;
-  global $CONFIG, $INSTALL_PATH, $BROWSER, $OUTPUT, $_SESSION, $IMAP, $DB, $JS_OBJECT_NAME;
+  global $CONFIG, $INSTALL_PATH, $BROWSER, $OUTPUT, $_SESSION, $IMAP, $DB;
 
   // check client
   $BROWSER = rcube_browser();
@@ -103,11 +104,11 @@
 // load roundcube configuration into global var
 function rcmail_load_config()
   {
-	global $INSTALL_PATH;
+  global $INSTALL_PATH;
 
   // load config file
-	include_once('config/main.inc.php');
-	$conf = is_array($rcmail_config) ? $rcmail_config : array();
+  include_once('config/main.inc.php');
+  $conf = is_array($rcmail_config) ? $rcmail_config : array();
 
   // load host-specific configuration
   rcmail_load_host_config($conf);
@@ -208,7 +209,7 @@
 // create IMAP object and connect to server
 function rcmail_imap_init($connect=FALSE)
   {
-  global $CONFIG, $DB, $IMAP;
+  global $CONFIG, $DB, $IMAP, $OUTPUT;
 
   $IMAP = new rcube_imap($DB);
   $IMAP->debug_level = $CONFIG['debug_level'];
@@ -219,7 +220,7 @@
   if ($connect)
     {
     if (!($conn = $IMAP->connect($_SESSION['imap_host'], $_SESSION['username'], decrypt_passwd($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])))
-      show_message('imaperror', 'error');
+      $OUTPUT->show_message('imaperror', 'error');
       
     rcmail_set_imap_prop();
     }
@@ -360,34 +361,22 @@
   
 
 // init output object for GUI and add common scripts
-function load_gui()
+function rcmail_load_gui()
   {
-  global $CONFIG, $OUTPUT, $COMM_PATH, $JS_OBJECT_NAME, $sess_user_lang;
+  global $CONFIG, $OUTPUT, $sess_user_lang;
 
   // init output page
-  $OUTPUT = new rcube_html_page();
-  
-  // add common javascripts
-  $javascript = "var $JS_OBJECT_NAME = new rcube_webmail();\n";
-  $javascript .= sprintf("%s.set_env('comm_path', '%s');\n", $JS_OBJECT_NAME, str_replace('&amp;', '&', $COMM_PATH));
+  $OUTPUT = new rcmail_template($CONFIG, $GLOBALS['_task']);
+  $OUTPUT->set_env('comm_path', $GLOBALS['COMM_PATH']);
 
-  if (isset($CONFIG['javascript_config'] )){
-    foreach ($CONFIG['javascript_config'] as $js_config_var){
-      $javascript .= "$JS_OBJECT_NAME.set_env('$js_config_var', '" . $CONFIG[$js_config_var] . "');\n";
-    }
+  if (is_array($CONFIG['javascript_config']))
+  {
+    foreach ($CONFIG['javascript_config'] as $js_config_var)
+      $OUTPUT->set_env($js_config_var, $CONFIG[$js_config_var]);
   }
 
-  // don't wait for page onload. Call init at the bottom of the page (delayed)
-  $javascript_foot = "if (window.call_init)\n call_init('$JS_OBJECT_NAME');";
-
   if (!empty($GLOBALS['_framed']))
-    $javascript .= "$JS_OBJECT_NAME.set_env('framed', true);\n";
-    
-  $OUTPUT->add_script($javascript, 'head');
-  $OUTPUT->add_script($javascript_foot, 'foot');
-  $OUTPUT->include_script('common.js');
-  $OUTPUT->include_script('app.js');
-  $OUTPUT->scripts_path = 'program/js/';
+    $OUTPUT->set_env('framed', true);
 
   // set locale setting
   rcmail_set_locale($sess_user_lang);
@@ -395,16 +384,25 @@
   // set user-selected charset
   if (!empty($CONFIG['charset']))
     $OUTPUT->set_charset($CONFIG['charset']);
+    
+  // register common UI objects
+  $OUTPUT->add_handlers(array(
+    'loginform' => 'rcmail_login_form',
+    'username'  => 'rcmail_current_username',
+    'message' => 'rcmail_message_container',
+    'charsetselector' => 'rcmail_charset_selector',
+  ));
 
   // add some basic label to client
-  rcube_add_label('loading','checkingmail');
+  if (!$OUTPUT->ajax_call)
+    rcube_add_label('loading');
   }
 
 
 // set localization charset based on the given language
 function rcmail_set_locale($lang)
   {
-  global $OUTPUT, $CHARSET, $MBSTRING;
+  global $OUTPUT, $MBSTRING;
   static $s_mbstring_loaded = NULL;
   
   // settings for mbstring module (by Tadashi Jokagi)
@@ -414,7 +412,7 @@
     $MBSTRING = $s_mbstring_loaded = FALSE;
     
   if ($MBSTRING)
-    mb_internal_encoding($CHARSET);
+    mb_internal_encoding(RCMAIL_CHARSET);
 
   $OUTPUT->set_charset(rcube_language_prop($lang, 'charset'));
   }
@@ -749,30 +747,35 @@
 // overwrite action variable  
 function rcmail_overwrite_action($action)
   {
-  global $OUTPUT, $JS_OBJECT_NAME;
+  global $OUTPUT;
   $GLOBALS['_action'] = $action;
-
-  $OUTPUT->add_script(sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $action));  
+  $OUTPUT->set_env('action', $action);
   }
 
 
+// compose a URL to the given action
+function rcmail_self_url($action, $p=array(), $task=null)
+{
+  global $MAIN_TASKS, $COMM_PATH;
+  $qstring = '';
+  $base = $COMM_PATH;
+  
+  if ($task && in_array($task, $MAIN_TASKS))
+    $base = ereg_replace('_task=[a-z]+', '_task='.$task, $COMM_PATH);
+  
+  if (is_array($p))
+    foreach ($p as $key => $val)
+      $qstring .= '&'.urlencode($key).'='.urlencode($val);
+  
+  return $base . ($action ? '&_action='.$action : '') . $qstring;
+}
+
+
+// @deprecated
 function show_message($message, $type='notice', $vars=NULL)
   {
-  global $OUTPUT, $JS_OBJECT_NAME, $REMOTE_REQUEST;
-  
-  $framed = $GLOBALS['_framed'];
-  $command = sprintf("display_message('%s', '%s');",
-                     JQ(rcube_label(array('name' => $message, 'vars' => $vars))),
-                     $type);
-                     
-  if ($REMOTE_REQUEST)
-    return 'this.'.$command;
-  
-  else
-    $OUTPUT->add_script(sprintf("%s%s.%s\n",
-                                $framed ? sprintf('if(parent.%s)parent.', $JS_OBJECT_NAME) : '',
-                                $JS_OBJECT_NAME,
-                                $command));
+  global $OUTPUT;
+  $OUTPUT->show_message($message, $type, $vars);
   }
 
 
@@ -808,43 +811,6 @@
   }
 
 
-// send correct response on a remote request
-function rcube_remote_response($js_code, $flush=FALSE)
-  {
-  global $OUTPUT, $CHARSET;
-  static $s_header_sent = FALSE;
-  
-  if (!$s_header_sent)
-    {
-    $s_header_sent = TRUE;
-    send_nocacheing_headers();
-    header('Content-Type: application/x-javascript; charset='.$CHARSET);
-    print '/** remote response ['.date('d/M/Y h:i:s O')."] **/\n";
-    }
-
-  // send response code
-  print rcube_charset_convert($js_code, $CHARSET, $OUTPUT->get_charset());
-
-  if ($flush)  // flush the output buffer
-    flush();
-  else         // terminate script
-    exit;
-  }
-
-
-// send correctly formatted response for a request posted to an iframe
-function rcube_iframe_response($js_code='')
-  {
-  global $OUTPUT, $JS_OBJECT_NAME;
-
-  if (!empty($js_code))
-    $OUTPUT->add_script("if(parent.$JS_OBJECT_NAME){\n" . $js_code . "\n}");
-
-  $OUTPUT->write();
-  exit;
-  }
-
-
 // read directory program/localization/ and return a list of available languages
 function rcube_list_languages()
   {
@@ -875,14 +841,11 @@
 // add a localized label to the client environment
 function rcube_add_label()
   {
-  global $OUTPUT, $JS_OBJECT_NAME;
+  global $OUTPUT;
   
   $arg_list = func_get_args();
   foreach ($arg_list as $i => $name)
-    $OUTPUT->add_script(sprintf("%s.add_label('%s', '%s');",
-                                $JS_OBJECT_NAME,
-                                $name,
-                                JQ(rcube_label($name))));
+    $OUTPUT->command('add_label', $name, rcube_label($name));
   }
 
 
@@ -931,7 +894,7 @@
  *
  * @param  string Input string
  * @param  string Suspected charset of the input string
- * @param  string Target charset to convert to; defaults to $GLOBALS['CHARSET']
+ * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
  * @return Converted string
  */
 function rcube_charset_convert($str, $from, $to=NULL)
@@ -939,7 +902,7 @@
   global $MBSTRING;
 
   $from = strtoupper($from);
-  $to = $to==NULL ? strtoupper($GLOBALS['CHARSET']) : strtoupper($to);
+  $to = $to==NULL ? strtoupper(RCMAIL_CHARSET) : strtoupper($to);
 
   if ($from==$to || $str=='' || empty($from))
     return $str;
@@ -1071,15 +1034,15 @@
   if ($enctype=='js')
     {
     if ($OUTPUT->get_charset()!='UTF-8')
-      $str = rcube_charset_convert($str, $GLOBALS['CHARSET'], $OUTPUT->get_charset());
+      $str = rcube_charset_convert($str, RCMAIL_CHARSET, $OUTPUT->get_charset());
       
-    return addslashes(preg_replace(array("/\r\n/", "/\r/"), array('\n', '\n'), strtr($str, $js_rep_table)));
+    return preg_replace(array("/\r?\n/", "/\r/"), array('\n', '\n'), addslashes(strtr($str, $js_rep_table)));
     }
 
   // no encoding given -> return original string
   return $str;
   }
-
+  
 /**
  * Quote a given string. Alias function for rep_specialchars_output
  * @see rep_specialchars_output
@@ -1160,13 +1123,29 @@
 }
 
 
-// ************** template parsing and gui functions **************
+/**
+ * Compose an URL for a specific action
+ *
+ * @param string Request action
+ * @param array  More URL parameters
+ * @return The application URL
+ */
+function rcmail_url($action, $param=NULL)
+{
+  $url = $GLOBALS['COMM_PATH'] . '&'.$action;
+
+  if (is_array($param))
+    foreach ($param as $p => $val)
+      $url .= sprintf('&%s=%s', urlencode($p), urlencode($val));
+
+  return $url;
+}
 
 
 // return boolean if a specific template exists
 function template_exists($name)
   {
-  global $CONFIG, $OUTPUT;
+  global $CONFIG;
   $skin_path = $CONFIG['skin_path'];
 
   // check template file
@@ -1174,412 +1153,11 @@
   }
 
 
-// get page template an replace variable
-// similar function as used in nexImage
-function parse_template($name='main', $exit=TRUE)
+// Wrapper for rcmail_template::parse()
+// @deprecated
+function parse_template($name='main', $exit=true)
   {
-  global $CONFIG, $OUTPUT;
-  $skin_path = $CONFIG['skin_path'];
-
-  // read template file
-  $templ = '';
-  $path = "$skin_path/templates/$name.html";
-
-  if($fp = @fopen($path, 'r'))
-    {
-    $templ = fread($fp, filesize($path));
-    fclose($fp);
-    }
-  else
-    {
-    raise_error(array('code' => 500,
-                      'type' => 'php',
-                      'line' => __LINE__,
-                      'file' => __FILE__,
-                      'message' => "Error loading template for '$name'"), TRUE, TRUE);
-    return FALSE;
-    }
-
-
-  // parse for specialtags
-  $output = parse_rcube_xml(parse_rcube_conditions($templ));
-  
-  // add debug console
-  if ($CONFIG['debug_level'] & 8)
-    $OUTPUT->footer = '<div style="position:absolute;top:5px;left:5px;width:400px;opacity:0.8;z-index:9000;"><form name="debugform"><textarea name="console" rows="15" cols="40" style="width:400px;border:none;font-size:x-small"></textarea></form>';
-
-  $OUTPUT->write(trim(parse_with_globals($output)), $skin_path);
-
-  if ($exit)
-    exit;
-  }
-
-
-
-// replace all strings ($varname) with the content of the according global variable
-function parse_with_globals($input)
-  {
-  $GLOBALS['__comm_path'] = $GLOBALS['COMM_PATH'];
-  $output = preg_replace('/\$(__[a-z0-9_\-]+)/e', '$GLOBALS["\\1"]', $input);
-  return $output;
-  }
-
-
-// parse conditional code
-function parse_rcube_conditions($input)
-  {
-  if (($matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE)) && count($matches)==4)
-    {
-    if (preg_match('/^(else|endif)$/i', $matches[1]))
-      return $matches[0] . parse_rcube_conditions($matches[3]);
-    else
-      {
-      $attrib = parse_attrib_string($matches[2]);
-      if (isset($attrib['condition']))
-        {
-        $condmet = rcube_xml_condition($attrib['condition']);
-        $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
-
-		    if ($condmet)
-			    $result = $submatches[0] . preg_replace('/.*<roundcube:endif\s+[^>]+>/is', '', $submatches[3]);
-        else
-          $result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
-          
-        return $matches[0] . parse_rcube_conditions($result);
-        }
-      else
-        {
-        raise_error(array('code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
-                          'message' => "Unable to parse conditional tag " . $matches[2]), TRUE, FALSE);
-        }
-      }
-    }
-
-  return $input;
-  }
-
- 
-/**
- * Determines if a given condition is met
- *
- * @return True if condition is valid, False is not
- */
-function rcube_xml_condition($condition)
-  {
-  $condition = preg_replace(
-      array('/session:([a-z0-9_]+)/i', '/config:([a-z0-9_]+)/i', '/request:([a-z0-9_]+)/ie'),
-      array("\$_SESSION['\\1']", "\$GLOBALS['CONFIG']['\\1']", "get_input_value('\\1', RCUBE_INPUT_GPC)"),
-      $condition);
-
-  return @eval("return (".$condition.");");
-  }
-
-
-function parse_rcube_xml($input)
-  {
-  $output = preg_replace('/<roundcube:([-_a-z]+)\s+([^>]+)>/Uie', "rcube_xml_command('\\1', '\\2')", $input);
-  return $output;
-  }
-
-
-/**
- * Convert a xml command tag into real content
- */
-function rcube_xml_command($command, $str_attrib, $add_attrib=array())
-  {
-  global $IMAP, $CONFIG, $OUTPUT;
-  
-  $command = strtolower($command);
-  $attrib = parse_attrib_string($str_attrib) + $add_attrib;
-  
-  // empty output if required condition is not met
-  if (!empty($attrib['condition']) && !rcube_xml_condition($attrib['condition']))
-    return '';
-
-  // execute command
-  switch ($command)
-    {
-    // return a button
-    case 'button':
-      if ($attrib['command'])
-        return rcube_button($attrib);
-      break;
-
-    // show a label
-    case 'label':
-      if ($attrib['name'] || $attrib['command'])
-        return Q(rcube_label($attrib + array('vars' => array('product' => $CONFIG['product_name']))));
-      break;
-
-    // create a menu item
-    case 'menu':
-      if ($attrib['command'] && $attrib['group'])
-        rcube_menu($attrib);
-      break;
-
-    // include a file 
-    case 'include':
-      $path = realpath($CONFIG['skin_path'].$attrib['file']);
-      
-      if($fp = @fopen($path, 'r'))
-        {
-        $incl = fread($fp, filesize($path));
-        fclose($fp);        
-        return parse_rcube_xml($incl);
-        }
-      break;
-
-    // return code for a specific application object
-    case 'object':
-      $object = strtolower($attrib['name']);
-
-      $object_handlers = array(
-        // GENERAL
-        'loginform' => 'rcmail_login_form',
-        'username'  => 'rcmail_current_username',
-        
-        // MAIL
-        'mailboxlist' => 'rcmail_mailbox_list',
-        'message' => 'rcmail_message_container',
-        'messages' => 'rcmail_message_list',
-        'messagecountdisplay' => 'rcmail_messagecount_display',
-        'quotadisplay' => 'rcmail_quota_display',
-        'messageheaders' => 'rcmail_message_headers',
-        'messagebody' => 'rcmail_message_body',
-        'messageattachments' => 'rcmail_message_attachments',
-        'blockedobjects' => 'rcmail_remote_objects_msg',
-        'messagecontentframe' => 'rcmail_messagecontent_frame',
-        'messagepartframe' => 'rcmail_message_part_frame',
-        'messagepartcontrols' => 'rcmail_message_part_controls',
-        'composeheaders' => 'rcmail_compose_headers',
-        'composesubject' => 'rcmail_compose_subject',
-        'composebody' => 'rcmail_compose_body',
-        'composeattachmentlist' => 'rcmail_compose_attachment_list',
-        'composeattachmentform' => 'rcmail_compose_attachment_form',
-        'composeattachment' => 'rcmail_compose_attachment_field',
-        'priorityselector' => 'rcmail_priority_selector',
-        'charsetselector' => 'rcmail_charset_selector',
-        'editorselector' => 'rcmail_editor_selector',
-        'searchform' => 'rcmail_search_form',
-        'receiptcheckbox' => 'rcmail_receipt_checkbox',
-        
-        // ADDRESS BOOK
-        'addresslist' => 'rcmail_contacts_list',
-        'addressframe' => 'rcmail_contact_frame',
-        'recordscountdisplay' => 'rcmail_rowcount_display',
-        'contactdetails' => 'rcmail_contact_details',
-        'contacteditform' => 'rcmail_contact_editform',
-        'ldappublicsearch' => 'rcmail_ldap_public_search_form',
-        'ldappublicaddresslist' => 'rcmail_ldap_public_list',
-
-        // USER SETTINGS
-        'userprefs' => 'rcmail_user_prefs_form',
-        'itentitieslist' => 'rcmail_identities_list',
-        'identityframe' => 'rcmail_identity_frame',
-        'identityform' => 'rcube_identity_form',
-        'foldersubscription' => 'rcube_subscription_form',
-        'createfolder' => 'rcube_create_folder_form',
-        'renamefolder' => 'rcube_rename_folder_form',
-        'composebody' => 'rcmail_compose_body'
-      );
-
-      
-      // execute object handler function
-      if ($object_handlers[$object] && function_exists($object_handlers[$object]))
-        return call_user_func($object_handlers[$object], $attrib);
-        
-      else if ($object=='productname')
-        {
-        $name = !empty($CONFIG['product_name']) ? $CONFIG['product_name'] : 'RoundCube Webmail';
-        return Q($name);
-        }
-      else if ($object=='version')
-        {
-        return (string)RCMAIL_VERSION;
-        }
-      else if ($object=='pagetitle')
-        {
-        $task = $GLOBALS['_task'];
-        $title = !empty($CONFIG['product_name']) ? $CONFIG['product_name'].' :: ' : '';
-        
-        if ($task=='login')
-          $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $CONFIG['product_name'])));
-        else if ($task=='mail' && isset($GLOBALS['MESSAGE']['subject']))
-          $title .= $GLOBALS['MESSAGE']['subject'];
-        else if (isset($GLOBALS['PAGE_TITLE']))
-          $title .= $GLOBALS['PAGE_TITLE'];
-        else if ($task=='mail' && ($mbox_name = $IMAP->get_mailbox_name()))
-          $title .= rcube_charset_convert($mbox_name, 'UTF-7', 'UTF-8');
-        else
-          $title .= ucfirst($task);
-          
-        return Q($title);
-        }
-
-      break;
-    }
-
-  return '';
-  }
-
-
-// create and register a button
-function rcube_button($attrib)
-  {
-  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $BROWSER, $COMM_PATH, $MAIN_TASKS;
-  static $sa_buttons = array();
-  static $s_button_count = 100;
-  
-  // these commands can be called directly via url
-  $a_static_commands = array('compose', 'list');
-  
-  $skin_path = $CONFIG['skin_path'];
-  
-  if (!($attrib['command'] || $attrib['name']))
-    return '';
-
-  // try to find out the button type
-  if ($attrib['type'])
-    $attrib['type'] = strtolower($attrib['type']);
-  else
-    $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
-  
-  $command = $attrib['command'];
-  
-  // take the button from the stack
-  if($attrib['name'] && $sa_buttons[$attrib['name']])
-    $attrib = $sa_buttons[$attrib['name']];
-
-  // add button to button stack
-  else if($attrib['image'] || $attrib['imageact'] || $attrib['imagepas'] || $attrib['class'])
-    {
-    if(!$attrib['name'])
-      $attrib['name'] = $command;
-
-    if (!$attrib['image'])
-      $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
-
-    $sa_buttons[$attrib['name']] = $attrib;
-    }
-
-  // get saved button for this command/name
-  else if ($command && $sa_buttons[$command])
-    $attrib = $sa_buttons[$command];
-
-  //else
-  //  return '';
-
-
-  // set border to 0 because of the link arround the button
-  if ($attrib['type']=='image' && !isset($attrib['border']))
-    $attrib['border'] = 0;
-    
-  if (!$attrib['id'])
-    $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
-
-  // get localized text for labels and titles
-  if ($attrib['title'])
-    $attrib['title'] = Q(rcube_label($attrib['title']));
-  if ($attrib['label'])
-    $attrib['label'] = Q(rcube_label($attrib['label']));
-
-  if ($attrib['alt'])
-    $attrib['alt'] = Q(rcube_label($attrib['alt']));
-
-  // set title to alt attribute for IE browsers
-  if ($BROWSER['ie'] && $attrib['title'] && !$attrib['alt'])
-    {
-    $attrib['alt'] = $attrib['title'];
-    unset($attrib['title']);
-    }
-
-  // add empty alt attribute for XHTML compatibility
-  if (!isset($attrib['alt']))
-    $attrib['alt'] = '';
-
-
-  // register button in the system
-  if ($attrib['command'])
-    {
-    $OUTPUT->add_script(sprintf("%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
-                                $JS_OBJECT_NAME,
-                                $command,
-                                $attrib['id'],
-                                $attrib['type'],
-                                $attrib['imageact'] ? $skin_path.$attrib['imageact'] : $attrib['classact'],
-                                $attrib['imagesel'] ? $skin_path.$attrib['imagesel'] : $attrib['classsel'],
-                                $attrib['imageover'] ? $skin_path.$attrib['imageover'] : ''));
-
-    // make valid href to specific buttons
-    if (in_array($attrib['command'], $MAIN_TASKS))
-      $attrib['href'] = htmlentities(ereg_replace('_task=[a-z]+', '_task='.$attrib['command'], $COMM_PATH));
-    else if (in_array($attrib['command'], $a_static_commands))
-      $attrib['href'] = htmlentities($COMM_PATH.'&_action='.$attrib['command']);
-    }
-
-  // overwrite attributes
-  if (!$attrib['href'])
-    $attrib['href'] = '#';
-
-  if ($command)
-    $attrib['onclick'] = sprintf("return %s.command('%s','%s',this)", $JS_OBJECT_NAME, $command, $attrib['prop']);
-    
-  if ($command && $attrib['imageover'])
-    {
-    $attrib['onmouseover'] = sprintf("return %s.button_over('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
-    $attrib['onmouseout'] = sprintf("return %s.button_out('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
-    }
-
-  if ($command && $attrib['imagesel'])
-    {
-    $attrib['onmousedown'] = sprintf("return %s.button_sel('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
-    $attrib['onmouseup'] = sprintf("return %s.button_out('%s','%s')", $JS_OBJECT_NAME, $command, $attrib['id']);
-    }
-
-  $out = '';
-
-  // generate image tag
-  if ($attrib['type']=='image')
-    {
-    $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'width', 'height', 'border', 'hspace', 'vspace', 'align', 'alt'));
-    $img_tag = sprintf('<img src="%%s"%s />', $attrib_str);
-    $btn_content = sprintf($img_tag, $skin_path.$attrib['image']);
-    if ($attrib['label'])
-      $btn_content .= ' '.$attrib['label'];
-    
-    $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'title');
-    }
-  else if ($attrib['type']=='link')
-    {
-    $btn_content = $attrib['label'] ? $attrib['label'] : $attrib['command'];
-    $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style');
-    }
-  else if ($attrib['type']=='input')
-    {
-    $attrib['type'] = 'button';
-    
-    if ($attrib['label'])
-      $attrib['value'] = $attrib['label'];
-      
-    $attrib_str = create_attrib_string($attrib, array('type', 'value', 'onclick', 'id', 'class', 'style'));
-    $out = sprintf('<input%s disabled />', $attrib_str);
-    }
-
-  // generate html code for button
-  if ($btn_content)
-    {
-    $attrib_str = create_attrib_string($attrib, $link_attrib);
-    $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
-    }
-
-  return $out;
-  }
-
-
-function rcube_menu($attrib)
-  {
-  
-  return '';
+  $GLOBALS['OUTPUT']->parse($name, $exit);
   }
 
 
@@ -1685,6 +1263,24 @@
   }
 
 
+// return the mail domain configured for the given host
+function rcmail_mail_domain($host)
+  {
+  global $CONFIG;
+
+  $domain = $host;
+  if (is_array($CONFIG['mail_domain']))
+    {
+    if (isset($CONFIG['mail_domain'][$host]))
+      $domain = $CONFIG['mail_domain'][$host];
+    }
+  else if (!empty($CONFIG['mail_domain']))
+    $domain = $CONFIG['mail_domain'];
+
+  return $domain;
+  }
+
+
 // compose a valid attribute string for HTML tags
 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
   {
@@ -1784,13 +1380,23 @@
   }
 
 
+function format_email_recipient($email, $name='')
+  {
+  if ($name && $name != $email)
+    return sprintf('%s <%s>', strpos($name, ",") ? '"'.$name.'"' : $name, $email);
+  else
+    return $email;
+  }
+
+
+
 // ************** functions delivering gui objects **************
 
 
 
 function rcmail_message_container($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME;
+  global $OUTPUT;
 
   if (!$attrib['id'])
     $attrib['id'] = 'rcmMessageContainer';
@@ -1799,7 +1405,7 @@
   $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
   $out = '<div' . $attrib_str . "></div>";
   
-  $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('message', '$attrib[id]');");
+  $OUTPUT->add_gui_object('message', $attrib['id']);
   
   return $out;
   }
@@ -1837,28 +1443,10 @@
   }
 
 
-// return the mail domain configured for the given host
-function rcmail_mail_domain($host)
-  {
-  global $CONFIG;
-
-  $domain = $host;
-  if (is_array($CONFIG['mail_domain']))
-    {
-    if (isset($CONFIG['mail_domain'][$host]))
-      $domain = $CONFIG['mail_domain'][$host];
-    }
-  else if (!empty($CONFIG['mail_domain']))
-    $domain = $CONFIG['mail_domain'];
-
-  return $domain;
-  }
-
-
 // return code for the webmail login form
 function rcmail_login_form($attrib)
   {
-  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $SESS_HIDDEN_FIELD;
+  global $CONFIG, $OUTPUT, $SESS_HIDDEN_FIELD;
   
   $labels = array();
   $labels['user'] = rcube_label('username');
@@ -1911,7 +1499,7 @@
 
 EOF;
 
-  $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('loginform', '$form_name');");
+  $OUTPUT->add_gui_object('loginform', $form_name);
   
   $out = <<<EOF
 $form_start
@@ -1972,6 +1560,36 @@
   }
 
 
+// return code for search function
+function rcmail_search_form($attrib)
+  {
+  global $OUTPUT;
+
+  // add some labels to client
+  rcube_add_label('searching');
+
+  $attrib['name'] = '_q';
+
+  if (empty($attrib['id']))
+    $attrib['id'] = 'rcmqsearchbox';
+
+  $input_q = new textfield($attrib);
+  $out = $input_q->show();
+
+  $OUTPUT->add_gui_object('qsearchbox', $attrib['id']);
+
+  // add form tag around text field
+  if (empty($attrib['form']))
+    $out = sprintf(
+      '<form name="rcmqsearchform" action="./" '.
+      'onsubmit="%s.command(\'search\');return false" style="display:inline;">%s</form>',
+      JS_OBJECT_NAME,
+      $out);
+
+  return $out;
+  } 
+
+
 /****** debugging functions ********/
 
 
diff --git a/program/include/rcmail_template.inc b/program/include/rcmail_template.inc
new file mode 100644
index 0000000..e2fa682
--- /dev/null
+++ b/program/include/rcmail_template.inc
@@ -0,0 +1,631 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcmail_template.inc                                   |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2007, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | 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>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id:  $
+
+*/
+
+require_once('include/rcube_shared.inc');
+
+
+class rcmail_template extends rcube_html_page
+{
+  var $config;
+  var $task = '';
+  var $framed = false;
+  var $ajax_call = false;
+  var $pagetitle = '';
+  var $env = array();
+  var $js_env = array();
+  var $js_commands = array();
+  var $object_handlers = array();
+
+
+  // PHP 5 constructor
+  function __construct(&$config, $task)
+  {
+    parent::__construct();
+    
+    $this->task = $task;
+    $this->config = $config;
+    $this->ajax_call = !empty($_GET['_remote']) || !empty($_POST['_remote']);
+    
+    // add common javascripts
+    if (!$this->ajax_call)
+    {
+      $javascript = "var ".JS_OBJECT_NAME." = new rcube_webmail();";
+
+      // don't wait for page onload. Call init at the bottom of the page (delayed)
+      $javascript_foot = "if (window.call_init)\n call_init('".JS_OBJECT_NAME."');";
+
+      $this->add_script($javascript, 'head_top');
+      $this->add_script($javascript_foot, 'foot');
+      $this->scripts_path = 'program/js/';
+      $this->include_script('common.js');
+      $this->include_script('app.js');
+    }
+  }
+
+  // PHP 4 compatibility
+  function rcmail_template(&$config, $task)
+  {
+    $this->__construct($config, $task);
+  }
+  
+  
+  /**
+   * Set environment variable
+   */
+  function set_env($name, $value, $addtojs=true)
+  {
+    $this->env[$name] = $value;
+    if ($addtojs || isset($this->js_env[$name]))
+      $this->js_env[$name] = $value;
+  }
+
+
+  /**
+   * Set page title variable
+   */
+  function set_pagetitle($title)
+  {
+    $this->pagetitle = $title;
+  }
+
+
+  /**
+   * Register a template object handler
+   *
+   * @param string Object name
+   * @param string Function name to call
+   */
+  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
+   */
+  function add_handlers($arr)
+  {
+    $this->object_handlers = array_merge($this->object_handlers, $arr);
+  }
+
+  /**
+   * Register a GUI object to the client script
+   *
+   * @param string Object name
+   * @param string Object ID
+   */
+  function add_gui_object($obj, $id)
+  {
+    $this->add_script(JS_OBJECT_NAME.".gui_object('$obj', '$id');");
+  }
+
+
+  /**
+   * Call a client method
+   *
+   * @param string Method to call
+   * @param ... Additional arguments
+   */
+  function command()
+  {
+    $this->js_commands[] = func_get_args();
+  }
+
+
+  /**
+   * Invoke display_message command
+   */
+  function show_message($message, $type='notice', $vars=NULL)
+  {
+    $this->command(
+      'display_message',
+      rcube_label(array('name' => $message, 'vars' => $vars)),
+      $type);
+  }
+
+
+  /**
+   * Delete all stored env variables and commands
+   */
+  function reset()
+  {
+    $this->env = array();
+    $this->js_env = array();
+    $this->js_commands = array();
+    $this->object_handlers = array();    
+    parent::reset();
+  }
+
+  /**
+   * Send the request output to the client.
+   * This will either parse a skin tempalte or send an AJAX response
+   *
+   * @param string  Template name
+   * @param boolean True if script should terminate (default)
+   */
+  function send($templ=null, $exit=true)
+  {
+    if ($this->ajax_call)
+      $this->remote_response('', !$exit);
+    else if ($templ != 'iframe')
+      $this->parse($templ, false);
+    else
+    {
+      $this->framed = $templ == 'iframe' ? true : $this->framed;
+      $this->write();
+    }
+    
+    if ($exit)
+      exit;
+  }
+
+
+  /**
+   * Send an AJAX response with executable JS code
+   * 
+   * @param string  Additional JS code
+   * @param boolean True if output buffer should be flushed
+   */
+  function remote_response($add='', $flush=false)
+  {
+    static $s_header_sent = FALSE;
+
+    if (!$s_header_sent)
+    {
+      $s_header_sent = TRUE;
+      send_nocacheing_headers();
+      header('Content-Type: application/x-javascript; charset='.RCMAIL_CHARSET);
+      print '/** ajax response ['.date('d/M/Y h:i:s O')."] **/\n";
+    }
+    
+    // unset default env vars
+    unset($this->js_env['task'], $this->js_env['action'], $this->js_env['comm_path']);
+
+    // send response code
+    print rcube_charset_convert($this->get_js_commands() . $add, RCMAIL_CHARSET, $this->get_charset());
+
+    if ($flush)  // flush the output buffer
+      flush();
+  }
+  
+  
+  /**
+   * @override
+   */
+  function write($template='')
+  {
+    // write all env variables to client
+    $js = $this->framed ? "if(window.parent) {\n" : '';
+    $js .= $this->get_js_commands() . ($this->framed ? ' }' : '');
+    $this->add_script($js, 'head_top');
+
+    // call super method
+    parent::write($template, $this->config['skin_path']);
+  }
+
+
+  /**
+   * Parse a specific skin template and deliver to stdout
+   *
+   * @param string  Template name
+   * @param boolean Exit script
+   */  
+  function parse($name='main', $exit=true)
+  {
+    $skin_path = $this->config['skin_path'];
+
+    // read template file
+    $templ = '';
+    $path = "$skin_path/templates/$name.html";
+
+    if($fp = @fopen($path, 'r'))
+    {
+      $templ = fread($fp, filesize($path));
+      fclose($fp);
+    }
+    else
+    {
+      raise_error(array(
+        'code' => 501,
+        'type' => 'php',
+        'line' => __LINE__,
+        'file' => __FILE__,
+        'message' => "Error loading template for '$name'"), TRUE, TRUE);
+      return FALSE;
+    }
+
+    // parse for specialtags
+    $output = $this->parse_xml($this->parse_conditions($templ));
+
+    // add debug console
+    if ($this->config['debug_level'] & 8)
+      $this->add_footer('<div style="position:absolute;top:5px;left:5px;width:400px;padding:0.2em;background:white;opacity:0.8;z-index:9000">
+        <a href="#toggle" onclick="con=document.getElementById(\'dbgconsole\');con.style.display=(con.style.display==\'none\'?\'block\':\'none\');return false">console</a>
+        <form action="/" name="debugform"><textarea name="console" id="dbgconsole" rows="20" cols="40" wrap="off" style="display:none;width:400px;border:none;font-size:x-small"></textarea></form></div>');
+
+    $this->write(trim($this->parse_with_globals($output)), $skin_path);
+
+    if ($exit)
+      exit;
+  }
+
+
+  /**
+   * Return executable javascript code for all registered commands
+   * @private
+   */
+  function get_js_commands()
+  {
+    $out = '';
+    if (!$this->framed)
+      $out .= ($this->ajax_call ? 'this' : JS_OBJECT_NAME) . '.set_env('.json_serialize($this->js_env).");\n";
+    
+    foreach ($this->js_commands as $i => $args)
+    {
+      $method = array_shift($args);
+      foreach ($args as $i => $arg)
+        $args[$i] = json_serialize($arg);
+
+      $parent = $this->framed || preg_match('/^parent\./', $method);
+      $out .= sprintf(
+        "%s.%s(%s);\n",
+        $this->ajax_call ? 'this' : ($parent ? 'parent.' : '') . JS_OBJECT_NAME,
+        preg_replace('/^parent\./', '', $method),
+        join(',', $args));
+    }
+    
+    return $out;
+  }
+  
+  /**
+   * Make URLs starting with a slash point to skin directory
+   */
+  function abs_url($str)
+  {
+    return preg_replace('/^\//', $this->config['skin_path'].'/', $str);
+  }
+
+
+
+  /*****  Template parsing methods  *****/
+  
+  /**
+   * Replace all strings ($varname) with the content
+   * of the according global variable.
+   */
+  function parse_with_globals($input)
+  {
+    $GLOBALS['__comm_path'] = $GLOBALS['COMM_PATH'];
+    return preg_replace('/\$(__[a-z0-9_\-]+)/e', '$GLOBALS["\\1"]', $input);
+  }
+  
+  
+  /**
+   * Parse for conditional tags
+   */
+  function parse_conditions($input)
+  {
+    if (($matches = preg_split('/<roundcube:(if|elseif|else|endif)\s+([^>]+)>/is', $input, 2, PREG_SPLIT_DELIM_CAPTURE)) && count($matches)==4)
+    {
+      if (preg_match('/^(else|endif)$/i', $matches[1]))
+        return $matches[0] . $this->parse_conditions($matches[3]);
+      else
+      {
+        $attrib = parse_attrib_string($matches[2]);
+        if (isset($attrib['condition']))
+        {
+          $condmet = $this->check_condition($attrib['condition']);
+          $submatches = preg_split('/<roundcube:(elseif|else|endif)\s+([^>]+)>/is', $matches[3], 2, PREG_SPLIT_DELIM_CAPTURE);
+
+          if ($condmet)
+            $result = $submatches[0] . preg_replace('/.*<roundcube:endif\s+[^>]+>/is', '', $submatches[3]);
+          else
+            $result = "<roundcube:$submatches[1] $submatches[2]>" . $submatches[3];
+
+          return $matches[0] . $this->parse_conditions($result);
+        }
+        else
+        {
+          raise_error(array('code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__,
+                            'message' => "Unable to parse conditional tag " . $matches[2]), TRUE, FALSE);
+        }
+      }
+    }
+
+    return $input;
+  }
+
+
+  /**
+   * Determines if a given condition is met
+   *
+   * @return True if condition is valid, False is not
+   */
+  function check_condition($condition)
+  {
+    $condition = preg_replace(
+        array('/session:([a-z0-9_]+)/i', '/config:([a-z0-9_]+)/i', '/env:([a-z0-9_]+)/i', '/request:([a-z0-9_]+)/ie'),
+        array("\$_SESSION['\\1']", "\$this->config['\\1']", "\$this->env['\\1']", "get_input_value('\\1', RCUBE_INPUT_GPC)"),
+        $condition);
+
+    return @eval("return (".$condition.");");
+  }
+
+
+  /**
+   * Search for special tags in input and replace them
+   * with the appropriate content
+   *
+   * @param string Input string to parse
+   * @return Altered input string
+   */
+  function parse_xml($input)
+  {
+    return preg_replace('/<roundcube:([-_a-z]+)\s+([^>]+)>/Uie', "\$this->xml_command('\\1', '\\2')", $input);
+  }
+
+
+  /**
+   * Convert a xml command tag into real content
+   *
+   * @param string Tag command: object,button,label, etc.
+   * @param string Attribute string
+   * @return Tag/Object content string
+   */
+  function xml_command($command, $str_attrib, $add_attrib=array())
+    {
+    $command = strtolower($command);
+    $attrib = parse_attrib_string($str_attrib) + $add_attrib;
+
+    // empty output if required condition is not met
+    if (!empty($attrib['condition']) && !$this->check_condition($attrib['condition']))
+      return '';
+
+    // execute command
+    switch ($command)
+    {
+      // return a button
+      case 'button':
+        if ($attrib['command'])
+          return $this->button($attrib);
+        break;
+
+      // show a label
+      case 'label':
+        if ($attrib['name'] || $attrib['command'])
+          return Q(rcube_label($attrib + array('vars' => array('product' => $this->config['product_name']))));
+        break;
+
+      // include a file 
+      case 'include':
+        $path = realpath($this->config['skin_path'].$attrib['file']);
+        if ($fp = @fopen($path, 'r'))
+        {
+          $incl = fread($fp, filesize($path));
+          fclose($fp);        
+          return $this->parse_xml($incl);
+        }
+        break;
+
+      // return code for a specific application object
+      case 'object':
+        $object = strtolower($attrib['name']);
+
+        // execute object handler function
+        if ($this->object_handlers[$object] && function_exists($this->object_handlers[$object]))
+          return call_user_func($this->object_handlers[$object], $attrib);
+
+        else if ($object=='productname')
+          {
+          $name = !empty($this->config['product_name']) ? $this->config['product_name'] : 'RoundCube Webmail';
+          return Q($name);
+          }
+        else if ($object=='version')
+          {
+          return (string)RCMAIL_VERSION;
+          }
+        else if ($object=='pagetitle')
+          {
+          $task = $this->task;
+          $title = !empty($this->config['product_name']) ? $this->config['product_name'].' :: ' : '';
+
+          if (!empty($this->pagetitle))
+            $title .= $this->pagetitle;
+          else if ($task == 'login')
+            $title = rcube_label(array('name' => 'welcome', 'vars' => array('product' => $this->config['product_name'])));
+          else
+            $title .= ucfirst($task);
+
+          return Q($title);
+          }
+
+        break;
+      }
+
+    return '';
+    }
+
+
+  /**
+   * Create and register a button
+   *
+   * @param array Button attributes
+   * @return HTML button
+   */
+  function button($attrib)
+    {
+    global $CONFIG, $OUTPUT, $BROWSER, $MAIN_TASKS;
+    static $sa_buttons = array();
+    static $s_button_count = 100;
+
+    // these commands can be called directly via url
+    $a_static_commands = array('compose', 'list');
+
+    $skin_path = $this->config['skin_path'];
+
+    if (!($attrib['command'] || $attrib['name']))
+      return '';
+
+    // try to find out the button type
+    if ($attrib['type'])
+      $attrib['type'] = strtolower($attrib['type']);
+    else
+      $attrib['type'] = ($attrib['image'] || $attrib['imagepas'] || $attrib['imageact']) ? 'image' : 'link';
+
+    $command = $attrib['command'];
+
+    // take the button from the stack
+    if($attrib['name'] && $sa_buttons[$attrib['name']])
+      $attrib = $sa_buttons[$attrib['name']];
+
+    // add button to button stack
+    else if($attrib['image'] || $attrib['imageact'] || $attrib['imagepas'] || $attrib['class'])
+    {
+      if (!$attrib['name'])
+        $attrib['name'] = $command;
+
+      if (!$attrib['image'])
+        $attrib['image'] = $attrib['imagepas'] ? $attrib['imagepas'] : $attrib['imageact'];
+
+      $sa_buttons[$attrib['name']] = $attrib;
+    }
+
+    // get saved button for this command/name
+    else if ($command && $sa_buttons[$command])
+      $attrib = $sa_buttons[$command];
+
+    //else
+    //  return '';
+
+
+    // set border to 0 because of the link arround the button
+    if ($attrib['type']=='image' && !isset($attrib['border']))
+      $attrib['border'] = 0;
+
+    if (!$attrib['id'])
+      $attrib['id'] =  sprintf('rcmbtn%d', $s_button_count++);
+
+    // get localized text for labels and titles
+    if ($attrib['title'])
+      $attrib['title'] = Q(rcube_label($attrib['title']));
+    if ($attrib['label'])
+      $attrib['label'] = Q(rcube_label($attrib['label']));
+
+    if ($attrib['alt'])
+      $attrib['alt'] = Q(rcube_label($attrib['alt']));
+
+    // set title to alt attribute for IE browsers
+    if ($BROWSER['ie'] && $attrib['title'] && !$attrib['alt'])
+    {
+      $attrib['alt'] = $attrib['title'];
+      unset($attrib['title']);
+    }
+
+    // add empty alt attribute for XHTML compatibility
+    if (!isset($attrib['alt']))
+      $attrib['alt'] = '';
+
+
+    // register button in the system
+    if ($attrib['command'])
+    {
+      $this->add_script(sprintf(
+        "%s.register_button('%s', '%s', '%s', '%s', '%s', '%s');",
+        JS_OBJECT_NAME,
+        $command,
+        $attrib['id'],
+        $attrib['type'],
+        $attrib['imageact'] ? $skin_path.$attrib['imageact'] : $attrib['classact'],
+        $attrib['imagesel'] ? $skin_path.$attrib['imagesel'] : $attrib['classsel'],
+        $attrib['imageover'] ? $skin_path.$attrib['imageover'] : '')
+      );
+
+      // make valid href to specific buttons
+      if (in_array($attrib['command'], $MAIN_TASKS))
+        $attrib['href'] = Q(rcmail_self_url(null, null, $attrib['command']));
+      else if (in_array($attrib['command'], $a_static_commands))
+        $attrib['href'] = Q(rcmail_self_url($attrib['command']));
+    }
+
+    // overwrite attributes
+    if (!$attrib['href'])
+      $attrib['href'] = '#';
+
+    if ($command)
+      $attrib['onclick'] = sprintf("return %s.command('%s','%s',this)", JS_OBJECT_NAME, $command, $attrib['prop']);
+
+    if ($command && $attrib['imageover'])
+    {
+      $attrib['onmouseover'] = sprintf("return %s.button_over('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
+      $attrib['onmouseout'] = sprintf("return %s.button_out('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
+    }
+
+    if ($command && $attrib['imagesel'])
+    {
+      $attrib['onmousedown'] = sprintf("return %s.button_sel('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
+      $attrib['onmouseup'] = sprintf("return %s.button_out('%s','%s')", JS_OBJECT_NAME, $command, $attrib['id']);
+    }
+
+    $out = '';
+
+    // generate image tag
+    if ($attrib['type']=='image')
+    {
+      $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'width', 'height', 'border', 'hspace', 'vspace', 'align', 'alt'));
+      $img_tag = sprintf('<img src="%%s"%s />', $attrib_str);
+      $btn_content = sprintf($img_tag, $skin_path.$attrib['image']);
+      if ($attrib['label'])
+        $btn_content .= ' '.$attrib['label'];
+
+      $link_attrib = array('href', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'title');
+    }
+    else if ($attrib['type']=='link')
+    {
+      $btn_content = $attrib['label'] ? $attrib['label'] : $attrib['command'];
+      $link_attrib = array('href', 'onclick', 'title', 'id', 'class', 'style');
+    }
+    else if ($attrib['type']=='input')
+    {
+      $attrib['type'] = 'button';
+
+      if ($attrib['label'])
+        $attrib['value'] = $attrib['label'];
+
+      $attrib_str = create_attrib_string($attrib, array('type', 'value', 'onclick', 'id', 'class', 'style'));
+      $out = sprintf('<input%s disabled />', $attrib_str);
+    }
+
+    // generate html code for button
+    if ($btn_content)
+    {
+      $attrib_str = create_attrib_string($attrib, $link_attrib);
+      $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
+    }
+
+    return $out;
+  }
+
+}
+
+?>
\ No newline at end of file
diff --git a/program/include/rcube_contacts.inc b/program/include/rcube_contacts.inc
new file mode 100644
index 0000000..cc801c6
--- /dev/null
+++ b/program/include/rcube_contacts.inc
@@ -0,0 +1,429 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_contacts.inc                                    |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2006-2007, RoundCube Dev. - Switzerland                 |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Interface to the local address book database                        |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: rcube_contacts.inc 328 2006-08-30 17:41:21Z thomasb $
+
+*/
+
+class rcube_contacts
+{
+  var $db = null;
+  var $db_name = '';
+  var $user_id = 0;
+  var $filter = '1';
+  var $result = null;
+  var $search_fields;
+  var $search_string;
+  var $table_cols = array('name', 'email', 'firstname', 'surname');
+  
+  /** public properties */
+  var $primary_key = 'contact_id';
+  var $readonly = false;
+  var $list_page = 1;
+  var $page_size = 10;
+  var $ready = false;
+
+  
+  /**
+   * Object constructor
+   *
+   * @param object  Instance of the rcube_db class
+   * @param integer User-ID
+   */
+  function __construct($dbconn, $user)
+  {
+    $this->db = $dbconn;
+    $this->db_name = get_table_name('contacts');
+    $this->user_id = $user;
+    $this->ready = $this->db && !$this->db->is_error();
+  }
+
+  /**
+   * PHP 4 object constructor
+   *
+   * @see  rcube_contacts::__construct
+   */
+  function rcube_contacts($dbconn, $user)
+  {
+    $this->__construct($dbconn, $user);
+  }
+
+
+  /**
+   * Set internal list page
+   *
+   * @param  number  Page number to list
+   * @access public
+   */
+  function set_page($page)
+  {
+    $this->list_page = (int)$page;
+  }
+
+
+  /**
+   * Set internal page size
+   *
+   * @param  number  Number of messages to display on one page
+   * @access public
+   */
+  function set_pagesize($size)
+  {
+    $this->page_size = (int)$size;
+  }
+
+
+  /**
+   * Save a search string for future listings
+   *
+   * @param  string SQL params to use in listing method
+   */
+  function set_search_set($filter)
+  {
+    $this->filter = $filter;
+  }
+  
+  
+  /**
+   * Getter for saved search properties
+   *
+   * @return mixed Search properties used by this class
+   */
+  function get_search_set()
+  {
+    return $this->filter;
+  }
+
+
+  /**
+   * Reset all saved results and search parameters
+   */
+  function reset()
+  {
+    $this->result = null;
+    $this->filter = '1';
+    $this->search_fields = null;
+    $this->search_string = null;
+  }
+  
+  
+  /**
+   * List the current set of contact records
+   *
+   * @param  array  List of cols to show
+   * @return array  Indexed list of contact records, each a hash array
+   */
+  function list_records($cols=null, $subset=0)
+  {
+    // count contacts for this user
+    $this->result = $this->count();
+    $sql_result = NULL;
+
+    // get contacts from DB
+    if ($this->result->count)
+    {
+      $start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first;
+      $length = $subset != 0 ? abs($subset) : $this->page_size;
+      
+      $sql_result = $this->db->limitquery(
+        "SELECT * FROM ".$this->db_name."
+         WHERE  del<>1
+         AND    user_id=?
+         AND    (".$this->filter.")
+         ORDER BY name",
+        $start_row,
+        $length,
+        $this->user_id);
+    }
+    
+    while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result)))
+    {
+      $sql_arr['ID'] = $sql_arr[$this->primary_key];
+      // make sure we have a name to display
+      if (empty($sql_arr['name']))
+        $sql_arr['name'] = $sql_arr['email'];
+      $this->result->add($sql_arr);
+    }
+    
+    return $this->result;
+  }
+
+
+  /**
+   * Search contacts
+   *
+   * @param array   List of fields to search in
+   * @param string  Search value
+   * @param boolean True if results are requested, False if count only
+   * @return Indexed list of contact records and 'count' value
+   */
+  function search($fields, $value, $select=true)
+  {
+    if (!is_array($fields))
+      $fields = array($fields);
+      
+    $add_where = array();
+    foreach ($fields as $col)
+    {
+      if ($col == 'ID' || $col == $this->primary_key)
+      {
+        $ids = !is_array($value) ? split(',', $value) : $value;
+        $add_where[] = $this->primary_key." IN (".join(',', $ids).")";
+      }
+      else
+        $add_where[] = $this->db->quoteIdentifier($col)." LIKE ".$this->db->quote(strlen($value)>2 ? "%$value%" : "$value%");
+    }
+    
+    if (!empty($add_where))
+    {
+      $this->set_search_set(join(' OR ', $add_where));
+      if ($select)
+        $this->list_records();
+      else
+        $this->result = $this->count();
+    }
+   
+    return $this->result; 
+  }
+
+
+  /**
+   * Count number of available contacts in database
+   *
+   * @return Result array with values for 'count' and 'first'
+   */
+  function count()
+  {
+    // count contacts for this user
+    $sql_result = $this->db->query(
+      "SELECT COUNT(contact_id) AS rows
+       FROM ".$this->db_name."
+       WHERE  del<>1
+       AND    user_id=?
+       AND    (".$this->filter.")",
+      $this->user_id);
+
+    $sql_arr = $this->db->fetch_assoc($sql_result);
+    return new rcube_result_set($sql_arr['rows'], ($this->list_page-1) * $this->page_size);;
+  }
+
+
+  /**
+   * Return the last result set
+   *
+   * @return Result array or NULL if nothing selected yet
+   */
+  function get_result($as_res=true)
+  {
+    return $this->result;
+  }
+  
+  
+  /**
+   * Get a specific contact record
+   *
+   * @param mixed record identifier(s)
+   * @return Result object with all record fields or False if not found
+   */
+  function get_record($id, $assoc=false)
+  {
+    // return cached result
+    if ($this->result && ($first = $this->result->first()) && $first[$this->primary_key] == $id)
+      return $assoc ? $first : $this->result;
+      
+    $this->db->query(
+      "SELECT * FROM ".$this->db_name."
+       WHERE  contact_id=?
+       AND    user_id=?
+       AND    del<>1",
+      $id,
+      $this->user_id);
+
+    if ($sql_arr = $this->db->fetch_assoc())
+    {
+      $sql_arr['ID'] = $sql_arr[$this->primary_key];
+      $this->result = new rcube_result_set(1);
+      $this->result->add($sql_arr);
+    }
+
+    return $assoc && $sql_arr ? $sql_arr : $this->result;
+  }
+  
+  
+  /**
+   * Create a new contact record
+   *
+   * @param array Assoziative array with save data
+   * @return The created record ID on success, False on error
+   */
+  function insert($save_data, $check=false)
+  {
+    if (is_object($save_data) && is_a($save_data, rcube_result_set))
+      return $this->insert_recset($save_data, $check);
+
+    $insert_id = $existing = false;
+
+    if ($check)
+      $existing = $this->search('email', $save_data['email'], false);
+
+    $a_insert_cols = $a_insert_values = array();
+    foreach ($this->table_cols as $col)
+      if (isset($save_data[$col]))
+      {
+        $a_insert_cols[] = $this->db->quoteIdentifier($col);
+        $a_insert_values[] = $this->db->quote($save_data[$col]);
+      }
+    
+    if (!$existing->count && !empty($a_insert_cols))
+    {
+      $this->db->query(
+        "INSERT INTO ".$this->db_name."
+         (user_id, changed, del, ".join(', ', $a_insert_cols).")
+         VALUES (?, ".$this->db->now().", 0, ".join(', ', $a_insert_values).")",
+        $this->user_id);
+        
+      $insert_id = $this->db->insert_id(get_sequence_name('contacts'));
+    }
+    
+    return $insert_id;
+  }
+
+
+  /**
+   * Insert new contacts for each row in set
+   */
+  function insert_recset($result, $check=false)
+  {
+    $ids = array();
+    while ($row = $result->next())
+    {
+      if ($insert = $this->insert($row, $check))
+        $ids[] = $insert;
+    }
+    return $ids;
+  }
+  
+  
+  /**
+   * Update a specific contact record
+   *
+   * @param mixed Record identifier
+   * @param array Assoziative array with save data
+   * @return True on success, False on error
+   */
+  function update($id, $save_cols)
+  {
+    $updated = false;
+    $write_sql = array();
+    foreach ($this->table_cols as $col)
+      if (isset($save_cols[$col]))
+        $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), $this->db->quote($save_cols[$col]));
+
+    if (!empty($write_sql))
+    {
+      $this->db->query(
+        "UPDATE ".$this->db_name."
+         SET    changed=".$this->db->now().", ".join(', ', $write_sql)."
+         WHERE  contact_id=?
+         AND    user_id=?
+         AND    del<>1",
+        $id,
+        $this->user_id);
+
+      $updated = $this->db->affected_rows();
+    }
+    
+    return $updated;
+  }
+  
+  
+  /**
+   * Mark one or more contact records as deleted
+   *
+   * @param array  Record identifiers
+   */
+  function delete($ids)
+  {
+    if (is_array($ids))
+      $ids = join(',', $ids);
+
+    $this->db->query(
+      "UPDATE ".$this->db_name."
+       SET    del=1
+       WHERE  user_id=?
+       AND    contact_id IN (".$ids.")",
+      $this->user_id);
+
+    return $this->db->affected_rows();
+  }
+
+}
+
+
+/**
+ * RoundCube result set class.
+ * Representing an address directory result set.
+ */
+class rcube_result_set
+{
+  var $count = 0;
+  var $first = 0;
+  var $current = 0;
+  var $records = array();
+  
+  function __construct($c=0, $f=0)
+  {
+    $this->count = (int)$c;
+    $this->first = (int)$f;
+  }
+  
+  function rcube_result_set($c=0, $f=0)
+  {
+    $this->__construct($c, $f);
+  }
+  
+  function add($rec)
+  {
+    $this->records[] = $rec;
+  }
+  
+  function iterate()
+  {
+    return $this->records[$this->current++];
+  }
+  
+  function first()
+  {
+    $this->current = 0;
+    return $this->records[$this->current++];
+  }
+  
+  // alias
+  function next()
+  {
+    return $this->iterate();
+  }
+  
+  function seek($i)
+  {
+    $this->current = $i;
+  }
+  
+}
+
+
+?>
\ No newline at end of file
diff --git a/program/include/rcube_db.inc b/program/include/rcube_db.inc
index 6f362a3..626cb64 100755
--- a/program/include/rcube_db.inc
+++ b/program/include/rcube_db.inc
@@ -5,7 +5,7 @@
  | program/include/rcube_db.inc                                          |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -102,7 +102,7 @@
       $this->db_error = TRUE;
       $this->db_error_msg = $dbh->getMessage();
 
-      raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+      raise_error(array('code' => 603, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
                         'message' => $this->db_error_msg), TRUE, FALSE);
                         
       return FALSE;
diff --git a/program/include/rcube_imap.inc b/program/include/rcube_imap.inc
index 3f4f2eb..c0016d3 100644
--- a/program/include/rcube_imap.inc
+++ b/program/include/rcube_imap.inc
@@ -2107,9 +2107,9 @@
    * --------------------------------*/
 
   
-  function decode_address_list($input, $max=NULL)
+  function decode_address_list($input, $max=null, $decode=true)
     {
-    $a = $this->_parse_address_list($input);
+    $a = $this->_parse_address_list($input, $decode);
     $out = array();
     
     if (!is_array($a))
@@ -2146,9 +2146,7 @@
     {
     $str = $this->decode_mime_string((string)$input);
     if ($str{0}=='"' && $remove_quotes)
-      {
       $str = str_replace('"', '', $str);
-      }
     
     return $str;
     }
@@ -2159,7 +2157,7 @@
    *
    * @access static
    */
-  function decode_mime_string($input, $recursive=false)
+  function decode_mime_string($input, $fallback=null)
     {
     $out = '';
 
@@ -2176,13 +2174,13 @@
       $rest = substr($input, $end_pos+2);
 
       $out .= rcube_imap::_decode_mime_string_part($encstr);
-      $out .= rcube_imap::decode_mime_string($rest);
+      $out .= rcube_imap::decode_mime_string($rest, $fallback);
 
       return $out;
       }
       
-    // no encoding information, defaults to what is specified in the class header
-    return rcube_charset_convert($input, 'ISO-8859-1');
+    // no encoding information, use fallback
+    return rcube_charset_convert($input, !empty($fallback) ? $fallback : 'ISO-8859-1');
     }
 
 
@@ -2473,7 +2471,7 @@
     }
 
 
-  function _parse_address_list($str)
+  function _parse_address_list($str, $decode=true)
     {
     // remove any newlines and carriage returns before
     $a = $this->_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
@@ -2482,7 +2480,7 @@
     foreach ($a as $key => $val)
       {
       $val = preg_replace("/([\"\w])</", "$1 <", $val);
-      $sub_a = $this->_explode_quoted_string(' ', $this->decode_header($val));
+      $sub_a = $this->_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val);
       $result[$key]['name'] = '';
 
       foreach ($sub_a as $k => $v)
diff --git a/program/include/rcube_ldap.inc b/program/include/rcube_ldap.inc
index 7cb9dee..06a99ad 100644
--- a/program/include/rcube_ldap.inc
+++ b/program/include/rcube_ldap.inc
@@ -5,255 +5,432 @@
  | program/include/rcube_ldap.inc                                        |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2006-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
- |   Manage an LDAP connection                                           |
+ |   Interface to an LDAP address directory                              |
  |                                                                       |
  +-----------------------------------------------------------------------+
- | Author: Jeremy Jongsma <jeremy@jongsma.org>                           |
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
 
  $Id$
 
 */
 
-require_once("bugs.inc");
-
 class rcube_ldap
-  {
+{
   var $conn;
-  var $host;
-  var $port;
-  var $protocol;
-  var $base_dn;
-  var $bind_dn;
-  var $bind_pass;
-
-  // PHP 5 constructor
-  function __construct()
-    {
-    }
-
-  // PHP 4 constructor
-  function rcube_ldap()
-    {
-    $this->__construct();
-    }
-
-  function connect($hosts, $port=389, $protocol=3)
-    {
-    if (!function_exists('ldap_connect'))
-      raise_error(array("type" => "ldap",
-                        "message" => "No ldap support in this installation of php."),
-                         TRUE);
-
-    if (is_resource($this->conn))
-      return TRUE;
+  var $prop = array();
+  var $fieldmap = array();
+  
+  var $filter = '';
+  var $result = null;
+  var $ldap_result = null;
+  var $sort_col = '';
+  
+  /** public properties */
+  var $primary_key = 'ID';
+  var $readonly = true;
+  var $list_page = 1;
+  var $page_size = 10;
+  var $ready = false;
+  
+  
+  /**
+   * Object constructor
+   *
+   * @param array LDAP connection properties
+   * @param integer User-ID
+   */
+  function __construct($p)
+  {
+    $this->prop = $p;
     
-    if (!is_array($hosts))
-      $hosts = array($hosts);
-
-    foreach ($hosts as $host)
-      {
-      if ($lc = @ldap_connect($host, $port))
-        {
-        @ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $protocol);
-        $this->host = $host;
-        $this->port = $port;
-        $this->protocol = $protocol;
-        $this->conn = $lc;
-        return TRUE;
-        }
-      }
+    foreach ($p as $prop => $value)
+      if (preg_match('/^(.+)_field$/', $prop, $matches))
+        $this->fieldmap[$matches[1]] = $value;
     
-    if (!is_resource($this->conn))
-      raise_error(array("type" => "ldap",
-                        "message" => "Could not connect to any LDAP server, tried $host:$port last"),
-                         TRUE);
-    }
-
-  function close()
-    {
-    if ($this->conn)
-      {
-      if (@ldap_unbind($this->conn))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Could not close connection to LDAP server: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    return FALSE;
-    }
-
-  // Merge with connect()?
-  function bind($dn=null, $pass=null)
-    {
-    if ($this->conn)
-      {
-      if ($dn)
-        if (@ldap_bind($this->conn, $dn, $pass))
-          return TRUE;
-        else
-          raise_error(array("code" => ldap_errno($this->conn),
-                            "type" => "ldap",
-                            "message" => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
-                      TRUE);
-      else
-        if (@ldap_bind($this->conn))
-          return TRUE;
-        else
-          raise_error(array("code" => ldap_errno($this->conn),
-                            "type" => "ldap",
-                            "message" => "Anonymous bind failed: ".ldap_error($this->conn)),
-                      TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Attempted bind on nonexistent connection"), TRUE);
-    return FALSE;
-    }
-
-  function count($base, $filter=null, $attributes=null, $scope="sub")
-    {
-    if ($this->conn)
-      {
-      if ($scope === 'sub')
-        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'one')
-        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'base')
-        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
-      if ($sr)
-        return @ldap_count_entries($this->conn, $sr);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Attempted count search on nonexistent connection"), TRUE);
-    return FALSE;
-    }
-
-  function search($base, $filter=null, $attributes=null, $scope='sub', $sort=null, $limit=0)
-    {
-    if ($this->conn)
-      {
-      if ($scope === 'sub')
-        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'one')
-        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'base')
-        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
-      if ($sr)
-        {
-        if ($sort && $scope !== "base")
-          {
-          if (is_array($sort))
-            {
-            // Start from the end so first sort field has highest priority
-            $sortfields = array_reverse($sort);
-            foreach ($sortfields as $sortfield)
-              @ldap_sort($this->conn, $sr, $sortfield);
-            }
-          else
-            @ldap_sort($this->conn, $sr, $sort);
-          }
-        return @ldap_get_entries($this->conn, $sr);
-        }
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Attempted search on nonexistent connection"), TRUE);
-    return FALSE;
-    }
-
-  function add($dn, $object)
-    {
-    if ($this->conn)
-      {
-      if (@ldap_add($this->conn, $dn, $object))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Add object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Add object faile: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
-  function modify($dn, $object)
-    {
-    if ($this->conn)
-      {
-      if (@ldap_modify($this->conn, $dn, $object))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Modify object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Modify object failed: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
-  function rename($dn, $newrdn, $parentdn)
-    {
-    if ($this->protocol < 3)
-      {
-      raise_error(array("type" => "ldap",
-                        "message" => "rename() support requires LDAPv3 or above "),
-                  TRUE);
-      return FALSE;
-      }
-
-    if ($this->conn)
-      {
-      if (@ldap_rename($this->conn, $dn, $newrdn, $parentdn, TRUE))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Rename object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Rename object failed: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
-  function delete($dn)
-    {
-    if ($this->conn)
-      {
-      if (@ldap_delete($this->conn, $dn))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Delete object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Delete object failed: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
+    // $this->filter = "(dn=*)";
+    $this->connect();
   }
 
-// vi: et ts=2 sw=2
-?>
+  /**
+   * PHP 4 object constructor
+   *
+   * @see  rcube_ldap::__construct
+   */
+  function rcube_ldap($p)
+  {
+    $this->__construct($p);
+  }
+  
+
+  /**
+   * Establish a connection to the LDAP server
+   */
+  function connect()
+  {
+    if (!function_exists('ldap_connect'))
+      raise_error(array('type' => 'ldap', 'message' => "No ldap support in this installation of PHP"), true);
+
+    if (is_resource($this->conn))
+      return true;
+    
+    if (!is_array($this->prop['hosts']))
+      $this->prop['hosts'] = array($this->prop['hosts']);
+
+    foreach ($this->prop['hosts'] as $host)
+    {
+      if ($lc = @ldap_connect($host, $this->prop['port']))
+      {
+        ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['port']);
+        $this->prop['host'] = $host;
+        $this->conn = $lc;
+        break;
+      }
+    }
+    
+    if (is_resource($this->conn))
+      $this->ready = true;
+    else
+      raise_error(array('type' => 'ldap', 'message' => "Could not connect to any LDAP server, tried $host:{$this->prop[port]} last"), true);
+  }
+
+
+  /**
+   * Merge with connect()?
+   */
+  function bind($dn=null, $pass=null)
+  {
+    if ($this->conn)
+    {
+      if ($dn)
+      {
+        if (@ldap_bind($this->conn, $dn, $pass))
+          return true;
+        else
+          raise_error(array('code' => ldap_errno($this->conn),
+                            'type' => 'ldap',
+                            'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
+                      true);
+      }
+      else
+      {
+        if (@ldap_bind($this->conn))
+          return true;
+        else
+          raise_error(array('code' => ldap_errno($this->conn),
+                            'type' => 'ldap',
+                            'message' => "Anonymous bind failed: ".ldap_error($this->conn)),
+                      true);
+        }
+    }
+    else
+      raise_error(array('type' => 'ldap', 'message' => "Attempted bind on nonexistent connection"), true);
+      
+    return false;
+    }
+
+
+  /**
+   * Close connection to LDAP server
+   */
+  function close()
+  {
+    if ($this->conn)
+      @ldap_unbind($this->conn);
+  }
+
+
+  /**
+   * Set internal list page
+   *
+   * @param  number  Page number to list
+   * @access public
+   */
+  function set_page($page)
+  {
+    $this->list_page = (int)$page;
+  }
+
+
+  /**
+   * Set internal page size
+   *
+   * @param  number  Number of messages to display on one page
+   * @access public
+   */
+  function set_pagesize($size)
+  {
+    $this->page_size = (int)$size;
+  }
+
+
+  /**
+   * Save a search string for future listings
+   *
+   * @param string ??
+   */
+  function set_search_set($filter)
+  {
+    $this->filter = $filter;
+  }
+  
+  
+  /**
+   * Getter for saved search properties
+   *
+   * @return mixed Search properties used by this class
+   */
+  function get_search_set()
+  {
+    return $this->filter;
+  }
+
+
+  /**
+   * Reset all saved results and search parameters
+   */
+  function reset()
+  {
+    $this->result = null;
+    $this->ldap_result = null;
+    $this->filter = '';
+  }
+  
+  
+  /**
+   * List the current set of contact records
+   *
+   * @param  array  List of cols to show
+   * @return array  Indexed list of contact records, each a hash array
+   */
+  function list_records($cols=null, $subset=0)
+  {
+    // exec LDAP search if no result resource is stored
+    if ($this->conn && !$this->ldap_result)
+      $this->_exec_search();
+    
+    // count contacts for this user
+    $this->result = $this->count();
+    
+    // we have a search result resource
+    if ($this->ldap_result && $this->result->count > 0)
+    {
+      if ($this->sort_col && $this->prop['scope'] !== "base")
+        @ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
+        
+      $entries = ldap_get_entries($this->conn, $this->ldap_result);
+      for ($i = $this->result->first; $i < min($entries['count'], $this->result->first + $this->page_size); $i++)
+        $this->result->add($this->_ldap2result($entries[$i]));
+    }
+
+    return $this->result;
+  }
+
+
+  /**
+   * Search contacts
+   *
+   * @param array   List of fields to search in
+   * @param string  Search value
+   * @param boolean True if results are requested, False if count only
+   * @return Indexed list of contact records and 'count' value
+   */
+  function search($fields, $value, $select=true)
+  {
+    // special treatment for ID-based search
+    if ($fields == 'ID' || $fields == $this->primary_key)
+    {
+      $ids = explode(',', $value);
+      $result = new rcube_result_set();
+      foreach ($ids as $id)
+        if ($rec = $this->get_record($id, true))
+        {
+          $result->add($rec);
+          $result->count++;
+        }
+      
+      return $result;
+    }
+    
+    $filter = '(|';
+    $wc = $this->prop['fuzzy_search'] ? '*' : '';
+    if (is_array($this->prop['search_fields']))
+    {
+      foreach ($this->prop['search_fields'] as $k => $field)
+        $filter .= "($field=$wc" . rcube_ldap::quote_string($value) . "$wc)";
+    }
+    else
+    {
+      foreach ((array)$fields as $field)
+        if ($f = $this->_map_field($field))
+          $filter .= "($f=$wc" . rcube_ldap::quote_string($value) . "$wc)";
+    }
+    $filter .= ')';
+
+    // set filter string and execute search
+    $this->set_search_set($filter);
+    $this->_exec_search();
+    
+    if ($select)
+      $this->list_records();
+    else
+      $this->result = $this->count();
+   
+    return $this->result; 
+  }
+
+
+  /**
+   * Count number of available contacts in database
+   *
+   * @return Result array with values for 'count' and 'first'
+   */
+  function count()
+  {
+    $count = 0;
+    if ($this->conn && $this->ldap_result)
+      $count = ldap_count_entries($this->conn, $this->ldap_result);
+
+    return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
+  }
+
+
+  /**
+   * Return the last result set
+   *
+   * @return Result array or NULL if nothing selected yet
+   */
+  function get_result()
+  {
+    return $this->result;
+  }
+  
+  
+  /**
+   * Get a specific contact record
+   *
+   * @param mixed record identifier
+   * @return Hash array with all record fields or False if not found
+   */
+  function get_record($dn, $assoc=false)
+  {
+    $res = null;
+    if ($this->conn && $dn)
+    {
+      $this->ldap_result = @ldap_read($this->conn, base64_decode($dn), "(objectclass=*)", array_values($this->fieldmap));
+      $entry = @ldap_first_entry($this->conn, $this->ldap_result);
+      
+      if ($entry && ($rec = ldap_get_attributes($this->conn, $entry)))
+      {
+        $res = $this->_ldap2result($rec);
+        $this->result = new rcube_result_set(1);
+        $this->result->add($res);
+      }
+    }
+
+    return $assoc ? $res : $this->result;
+  }
+  
+  
+  /**
+   * Create a new contact record
+   *
+   * @param array Assoziative array with save data
+   * @return The create record ID on success, False on error
+   */
+  function insert($save_cols)
+  {
+    // TODO
+    return false;
+  }
+  
+  
+  /**
+   * Update a specific contact record
+   *
+   * @param mixed Record identifier
+   * @param array Assoziative array with save data
+   * @return True on success, False on error
+   */
+  function update($id, $save_cols)
+  {
+    // TODO    
+    return false;
+  }
+  
+  
+  /**
+   * Mark one or more contact records as deleted
+   *
+   * @param array  Record identifiers
+   */
+  function delete($ids)
+  {
+    // TODO
+    return false;
+  }
+
+
+  /**
+   * Execute the LDAP search based on the stored credentials
+   *
+   * @private
+   */
+  function _exec_search()
+  {
+    if ($this->conn && $this->filter)
+    {
+      $function = $this->prop['scope'] == 'sub' ? 'ldap_search' : ($this->prop['scope'] == 'base' ? 'ldap_read' : 'ldap_list');
+      $this->ldap_result = @$function($this->conn, $this->prop['base_dn'], $this->filter, array_values($this->fieldmap), 0, 0);
+      return true;
+    }
+    else
+      return false;
+  }
+  
+  
+  /**
+   * @private
+   */
+  function _ldap2result($rec)
+  {
+    $out = array();
+    
+    if ($rec['dn'])
+      $out[$this->primary_key] = base64_encode($rec['dn']);
+    
+    foreach ($this->fieldmap as $rf => $lf)
+    {
+      if ($rec[$lf]['count'])
+        $out[$rf] = $rec[$lf][0];
+    }
+    
+    return $out;
+  }
+  
+  
+  /**
+   * @private
+   */
+  function _map_field($field)
+  {
+    return $this->fieldmap[$field];
+  }
+  
+  
+  /**
+   * @static
+   */
+  function quote_string($str)
+  {
+    return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c'));
+  }
+
+
+}
+
+?>
\ No newline at end of file
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
index 0d502f8..0f8be06 100644
--- a/program/include/rcube_shared.inc
+++ b/program/include/rcube_shared.inc
@@ -5,7 +5,7 @@
  | rcube_shared.inc                                                      |
  |                                                                       |
  | This file is part of the RoundCube PHP suite                          |
- | Copyright (C) 2005-2006, RoundCube Dev. - Switzerland                 |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | CONTENTS:                                                             |
@@ -84,11 +84,20 @@
   function add_script($script, $position='head')
     {
     if (!isset($this->scripts[$position]))
-      $this->scripts[$position] = "\n$script";
+      $this->scripts[$position] = "\n".rtrim($script);
     else
-      $this->scripts[$position] .= "\n$script";
+      $this->scripts[$position] .= "\n".rtrim($script);
     }
 
+  function add_header($str)
+    {
+    $this->header .= "\n".$str;
+    }
+
+  function add_footer($str)
+    {
+    $this->footer .= "\n".$str;
+    }
 
   function set_title($t)
     {
@@ -121,6 +130,8 @@
     $this->script_files = array();
     $this->scripts = array();
     $this->title = '';
+    $this->header = '';
+    $this->footer = '';
     }
 
 
@@ -150,39 +161,37 @@
       foreach ($this->script_files['head'] as $file)
         $__page_header .= sprintf($this->script_tag_file, $this->scripts_path, $file);
 
-   if (is_array($this->external_scripts['head']))
-   {
+    if (is_array($this->external_scripts['head']))
       foreach ($this->external_scripts['head'] as $xscript)
-      {
-         $__page_header .= sprintf($this->tag_format_external_script, $xscript);
-      }
-   }
+        $__page_header .= sprintf($this->tag_format_external_script, $xscript);
 
-    if (!empty($this->scripts['head']))
-      $__page_header .= sprintf($this->script_tag, $this->scripts['head']);
-          
+    $head_script = $this->scripts['head_top'] . $this->scripts['head'];
+    if (!empty($head_script))
+      $__page_header .= sprintf($this->script_tag, $head_script);
+
+    if (!empty($this->header))
+      $__page_header .= $this->header;
+
     if (is_array($this->script_files['foot']))
-      {
       foreach ($this->script_files['foot'] as $file)
         $__page_footer .= sprintf($this->script_tag_file, $this->scripts_path, $file);
-      }
 
     if (!empty($this->scripts['foot']))
       $__page_footer .= sprintf($this->script_tag, $this->scripts['foot']);
       
-    if ($this->footer)
-      $__page_footer .= "\n" . $this->footer;
+    if (!empty($this->footer))
+      $__page_footer .= $this->footer;
 
     $__page_header .= $this->css->show();
   
     // find page header
-    if($hpos = rc_strpos(rc_strtolower($output), '</head>'))
+    if($hpos = strpos(strtolower($output), '</head>'))
       $__page_header .= "\n";
     else 
       {
       if (!is_numeric($hpos))
-        $hpos = rc_strpos(rc_strtolower($output), '<body');
-      if (!is_numeric($hpos) && ($hpos = rc_strpos(rc_strtolower($output), '<html')))
+        $hpos = strpos(strtolower($output), '<body');
+      if (!is_numeric($hpos) && ($hpos = strpos(strtolower($output), '<html')))
         {
         while($output[$hpos]!='>')
         $hpos++;
@@ -194,27 +203,27 @@
   
     // add page hader
     if($hpos)
-      $output = rc_substr($output,0,$hpos) . $__page_header . rc_substr($output,$hpos,rc_strlen($output));
+      $output = substr($output,0,$hpos) . $__page_header . substr($output,$hpos,strlen($output));
     else
       $output = $__page_header . $output;
   
   
     // find page body
-    if($bpos = rc_strpos(rc_strtolower($output), '<body'))
+    if($bpos = strpos(strtolower($output), '<body'))
       {
       while($output[$bpos]!='>') $bpos++;
       $bpos++;
       }
     else
-      $bpos = rc_strpos(rc_strtolower($output), '</head>')+7;
+      $bpos = strpos(strtolower($output), '</head>')+7;
   
     // add page body
     if($bpos && $__page_body)
-      $output = rc_substr($output,0,$bpos) . "\n$__page_body\n" . rc_substr($output,$bpos,rc_strlen($output));
+      $output = substr($output,0,$bpos) . "\n$__page_body\n" . substr($output,$bpos,strlen($output));
   
   
     // find and add page footer
-    $output_lc = rc_strtolower($output);
+    $output_lc = strtolower($output);
     if(($fpos = strrstr($output_lc, '</body>')) ||
        ($fpos = strrstr($output_lc, '</html>')))
       $output = substr($output, 0, $fpos) . "$__page_footer\n" . substr($output, $fpos);
@@ -1016,10 +1025,10 @@
                     in_array($option['value'], $select, TRUE)) ||
                    (in_array($option['text'], $select, TRUE))) ?
         $this->_conv_case(' selected', 'attrib') : '';
-                  
+                   
       $options_str .= sprintf("<%s%s%s>%s</%s>\n",
                              $this->_conv_case('option', 'tag'),
-                             isset($option['value']) ? sprintf($value_str, $option['value']) : '',
+                             !empty($option['value']) ? sprintf($value_str, Q($option['value'])) : '',
                              $selected, 
                              Q($option['text'], 'strict', FALSE),
                              $this->_conv_case('option', 'tag'));
@@ -1258,89 +1267,71 @@
 }
 
 
-// function to convert an array to a javascript array
-function array2js($arr, $type='')
+/**
+ * Convert a variable into a javascript notation string
+ */
+function json_serialize($var)
   {
-  if (!$type)
-    $type = 'mixed';
+    if (is_object($var))
+      $var = get_object_vars($var);
 
-  if (is_array($arr))
+    if (is_array($var))
     {
-    // no items in array
-    if (!sizeof($arr))
-      return 'new Array()';
-    else
+      // empty array
+      if (!sizeof($var))
+        return '[]';
+      else
       {
-      $a_pairs = array();
-      $keys_arr = array_keys($arr);
-      $is_assoc = $have_numeric = 0;
+        $keys_arr = array_keys($var);
+        $is_assoc = $have_numeric = 0;
 
-      for ($i=0; $i<sizeof($keys_arr); ++$i)
+        for ($i=0; $i<sizeof($keys_arr); ++$i)
         {
-        if(is_numeric($keys_arr[$i]))
-          $have_numeric = 1;
-        if (!is_numeric($keys_arr[$i]) || $keys_arr[$i]!=$i)
-          $is_assoc = 1;
-        if($is_assoc && $have_numeric)
-          break;
+          if (is_numeric($keys_arr[$i]))
+            $have_numeric = 1;
+          if (!is_numeric($keys_arr[$i]) || $keys_arr[$i] != $i)
+            $is_assoc = 1;
+          if ($is_assoc && $have_numeric)
+            break;
+        }
+        
+        $brackets = $is_assoc ? '{}' : '[]';
+        $pairs = array();
+
+        foreach ($var as $key => $value)
+        {
+          // enclose key with quotes if it is not variable-name conform
+          if (!ereg("^[_a-zA-Z]{1}[_a-zA-Z0-9]*$", $key) /* || is_js_reserved_word($key) */)
+            $key = "'$key'";
+
+          $pairs[] = sprintf("%s%s", $is_assoc ? "$key:" : '', json_serialize($value));
         }
 
-      $previous_was_array = false;
-      while (list($key, $value) = each($arr))
-        {
-        // enclose key with quotes if it is not variable-name conform
-        if (!ereg("^[_a-zA-Z]{1}[_a-zA-Z0-9]*$", $key) /* || is_js_reserved_word($key) */)
-          $key = "'$key'";
-
-        if (!is_array($value) && is_string($value))
-          {
-          $value = str_replace("\r\n", '\n', $value);
-          $value = str_replace("\n", '\n', $value);
-          }
-
-        $is_string = false;
-        if (!is_array($value))
-          {
-          if ($type=='string')
-            $is_string = true;
-          else if (($type == 'mixed' && is_bool($value)) || $type == 'bool')
-            {
-            $is_string = false;
-            $value = $value ? "true" : "false";
-            }
-          else if ((($type=='mixed' && is_numeric($value)) || $type=='int') && rc_strlen($value)<16)   // js interprets numbers with digits >15 as ...e+... 
-            $is_string = FALSE;
-          else
-            $is_string = TRUE;
-          }
-
-        if ($is_string)
-          $value = "'".preg_replace("/(?<!\\\)'/", "\'", $value)."'";
-
-        $a_pairs[] = sprintf("%s%s",
-                             $is_assoc ? "$key:" : '',
-                             is_array($value) ? array2js($value, $type) : $value);
-        }
-
-      if ($a_pairs)
-        {
-        if ($is_assoc)
-          $return = '{'.implode(',', $a_pairs).'}';
-        else
-          $return = '['.implode(',', $a_pairs).']';
-        }
-
-      return $return;
+        return $brackets{0} . implode(',', $pairs) . $brackets{1};
       }
     }
-  else
-    {
-    return $arr;
-    }
+    else if (is_numeric($var) && strval(intval($var)) === strval($var))
+      return $var;
+    else if (is_bool($var))
+      return $var ? '1' : '0';
+    else
+      return "'".JQ($var)."'";
+  
+  }
+
+/**
+ * function to convert an array to a javascript array
+ * @deprecated
+ */
+function array2js($arr, $type='')
+  {
+  return json_serialize($arr);
   }
 
 
-// similar function as in_array() ut case-insensitive
+/**
+ * Similar function as in_array() but case-insensitive
+ */
 function in_array_nocase($needle, $haystack)
   {
   foreach ($haystack as $value)
@@ -1353,8 +1344,9 @@
   }
 
 
-
-// find out if the string content means TRUE or FALSE
+/**
+ * Find out if the string content means TRUE or FALSE
+ */
 function get_boolean($str)
   {
   $str = strtolower($str);
@@ -1469,7 +1461,7 @@
   }
 
 // wrapper function for substr
-function rc_substr($str, $start, $len)
+function rc_substr($str, $start, $len=null)
   {
   if (function_exists('mb_substr'))
     return mb_substr($str, $start, $len);
diff --git a/program/js/app.js b/program/js/app.js
index 7406df2..8d97a51 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -37,7 +37,7 @@
   this.dblclick_time = 500;
   this.message_time = 5000;
   
-  this.mbox_expression = new RegExp('[^0-9a-z\-_]', 'gi');
+  this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
   
   // mimetypes supported by the browser (default settings)
   this.mimetypes = new Array('text/plain', 'text/html', 'text/xml',
@@ -49,12 +49,19 @@
   this.env.keep_alive = 60;        // seconds
   this.env.request_timeout = 180;  // seconds
   this.env.draft_autosave = 0;     // seconds
+  this.env.comm_path = './';
+  this.env.bin_path = './bin/';
+  this.env.blankpage = 'program/blank.gif';
 
 
-  // set environment variable
-  this.set_env = function(name, value)
+  // set environment variable(s)
+  this.set_env = function(p, value)
     {
-    this.env[name] = value;
+    if (p != null && typeof(p) == 'object' && !value)
+      for (var n in p)
+        this.env[n] = p[n];
+    else
+      this.env[p] = value;
     };
 
 
@@ -162,7 +169,7 @@
         if (this.env.action == 'preview' && this.env.framed && parent.rcmail)
           {
           this.enable_command('compose', 'add-contact', false);
-          parent.rcmail.show_messageframe(true);
+          parent.rcmail.show_contentframe(true);
           parent.rcmail.mark_message('read', this.uid);
           }
 
@@ -206,7 +213,10 @@
 
         // get unread count for each mailbox
         if (this.gui_objects.mailboxlist)
+        {
+          this.gui_objects.folderlist = this.gui_objects.mailboxlist;
           this.http_request('getunread', '');
+        }
 
         break;
 
@@ -214,9 +224,11 @@
       case 'addressbook':
         if (this.gui_objects.contactslist)
           {
-          this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, {multiselect:true, draggable:false, keyboard:true});
+          this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, {multiselect:true, draggable:true, keyboard:true});
           this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
           this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
+          this.contact_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
+          this.contact_list.addEventListener('dragend', function(o){ p.drag_active = false; });
           this.contact_list.init();
 
           if (this.env.cid)
@@ -232,17 +244,20 @@
           }
 
         this.set_page_buttons();
-
+        
+        var writeable;
+        if ((writeable = this.env.address_sources && !this.env.address_sources[this.env.source].readonly))
+          this.enable_command('add', true);
+        
         if (this.env.cid)
           this.enable_command('show', 'edit', true);
 
-        if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
+        if ((this.env.action=='add' || this.env.action=='edit') && writeable && this.gui_objects.editform)
           this.enable_command('save', true);
+        else
+          this.enable_command('search', 'reset-search', 'moveto', true);
 
-        this.enable_command('list', 'add', true);
-
-        // this.enable_command('ldappublicsearch', this.env.ldappublicsearch);
-
+        this.enable_command('list', true);
         break;
 
 
@@ -380,7 +395,7 @@
       this.set_caret2start(input_message);
 
     // get summary of all field values
-    this.cmp_hash = this.compose_field_hash();
+    this.compose_field_hash(true);
  
     // start the auto-save timer
     this.auto_save_start();
@@ -470,7 +485,13 @@
           this.list_mailbox(props);
           }
         else if (this.task=='addressbook')
-          this.list_contacts();
+          {
+          if (this.env.search_request<0 || (this.env.search_request && props != this.env.source))
+            this.reset_qsearch();
+
+          this.list_contacts(props);
+          this.enable_command('add', (this.env.address_sources && !this.env.address_sources[props].readonly));
+          }
         break;
 
 
@@ -559,18 +580,6 @@
       case 'add':
         if (this.task=='addressbook')
           this.load_contact(0, 'add');
-
-        /* LDAP stuff, has to be re-written with new address book
-          if (!window.frames[this.env.contentframe].rcmail)
-            this.load_contact(0, 'add');
-          else
-            {
-            if (window.frames[this.env.contentframe].rcmail.selection.length)
-              this.add_ldap_contacts();
-            else
-              this.load_contact(0, 'add');
-            }
-        */
         else if (this.task=='settings')
           {
           this.identity_list.clear_selection();
@@ -638,7 +647,10 @@
       // mail task commands
       case 'move':
       case 'moveto':
-        this.move_messages(props);
+        if (this.task == 'mail')
+          this.move_messages(props);
+        else if (this.task == 'addressbook' && this.drag_active)
+          this.copy_contact(null, props);
         break;
         
       case 'toggle_status':
@@ -726,55 +738,42 @@
           var uid;
           if (uid = this.get_single_uid())
             url += '&_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
-          } 
+          }
         // modify url if we're in addressbook
         else if (this.task=='addressbook')
           {
-          url = this.get_task_url('mail', url);            
-          var a_cids = new Array();
+          // switch to mail compose step directly
+          if (props && props.indexOf('@') > 0)
+          {
+            url = this.get_task_url('mail', url);
+            this.redirect(url + '&_to='+urlencode(props));
+            break;
+          }
           
           // use contact_id passed as command parameter
+          var a_cids = new Array();
           if (props)
             a_cids[a_cids.length] = props;
           // get selected contacts
-          else
+          else if (this.contact_list)
             {
             var selection = this.contact_list.get_selection();
             for (var n=0; n<selection.length; n++)
               a_cids[a_cids.length] = selection[n];
-
-            /* LDAP stuff, has to be re-written with new address book
-            if (!window.frames[this.env.contentframe].rcmail.selection.length)
-              {
-              for (var n=0; n<selection.length; n++)
-                a_cids[a_cids.length] = selection[n];
-              }
-            else
-              {
-              var frameRcmail = window.frames[this.env.contentframe].rcmail;
-              // get the email address(es)
-              for (var n=0; n<frameRcmail.selection.length; n++)
-                a_cids[a_cids.length] = frameRcmail.ldap_contact_rows[frameRcmail.selection[n]].obj.cells[1].innerHTML;
-              }
-            */
             }
+            
           if (a_cids.length)
-            url += '&_to='+a_cids.join(',');
-          else
-            break;
+            this.http_request('mailto', '_cid='+urlencode(a_cids.join(','))+'&_source='+urlencode(this.env.source), true);
+
+          break;
           }
         else if (props)
            url += '&_to='+urlencode(props);
 
         // don't know if this is necessary...
         url = url.replace(/&_framed=1/, "");
-        this.set_busy(true);
 
-        // need parent in case we are coming from the contact frame
-        if (this.env.framed)
-          parent.location.href = url;
-        else
-          location.href = url;
+        this.redirect(url);
         break;
         
       case 'spellcheck':
@@ -875,30 +874,26 @@
         this.add_contact(props);
         break;
       
-      // mail quicksearch
+      // quicksearch
       case 'search':
         if (!props && this.gui_objects.qsearchbox)
           props = this.gui_objects.qsearchbox.value;
         if (props)
-          this.qsearch(urlencode(props), this.env.mailbox);
-        break;
+        {
+          this.qsearch(props);
+          break;
+        }
 
       // reset quicksearch        
       case 'reset-search':
         var s = this.env.search_request;
         this.reset_qsearch();
         
-        if (s)
+        if (s && this.env.mailbox)
           this.list_mailbox(this.env.mailbox);
+        else if (s && this.task == 'addressbook')
+          this.list_contacts(this.env.source);
         break;
-
-      // ldap search
-      case 'ldappublicsearch':
-        if (this.gui_objects.ldappublicsearchform) 
-          this.gui_objects.ldappublicsearchform.submit();
-        else 
-          this.ldappublicsearch(command);
-        break; 
 
 
       // user settings commands
@@ -1013,8 +1008,7 @@
     if (task=='mail')
       url += '&_mbox=INBOX';
 
-    this.set_busy(true);
-    location.href = url;
+    this.redirect(url);
     };
 
 
@@ -1048,21 +1042,31 @@
       this.contact_list.blur();
     };
 
+  this.focus_folder = function(id)
+    {
+    var li;
+    if (this.drag_active && this.check_droptarget(id) && (li = this.get_folder_li(id)))
+      this.set_classname(li, 'droptarget', true);
+    }
 
-  // onmouseup handler for mailboxlist item
-  this.mbox_mouse_up = function(mbox)
+  this.unfocus_folder = function(id)
+    {
+    var li;
+    if (this.drag_active && (li = this.get_folder_li(id)))
+      this.set_classname(li, 'droptarget', false);
+    }
+
+  // onmouseup handler for folder list item
+  this.folder_mouse_up = function(id)
     {
     if (this.drag_active)
       {
-      this.unfocus_mailbox(mbox);
-      this.command('moveto', mbox);
+      this.unfocus_folder(id);
+      this.command('moveto', id);
       }
-    else
-      this.command('list', mbox);
   
     return false;
     };
-
 
   this.click_on_list = function(e)
     {
@@ -1072,7 +1076,7 @@
         this.contact_list.focus();
 
     var mbox_li;
-    if (mbox_li = this.get_mailbox_li())
+    if (mbox_li = this.get_folder_li())
       this.set_classname(mbox_li, 'unfocused', true);
 
     rcube_event.cancel(e);
@@ -1100,7 +1104,7 @@
     if (selected && this.env.contentframe)
       this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, this.dblclick_time + 10);
     else if (this.env.contentframe)
-      this.show_messageframe(false);
+      this.show_contentframe(false);
     };
 
 
@@ -1129,10 +1133,19 @@
   this.msglist_get_preview = function()
   {
     var uid = this.get_single_uid();
-    if (uid && this.env.contentframe)
+    if (uid && this.env.contentframe && !this.drag_active)
       this.show_message(uid, false, true);
     else if (this.env.contentframe)
-      this.show_messageframe(false);
+      this.show_contentframe(false);
+  };
+  
+  
+  this.check_droptarget = function(id)
+  {
+    if (this.task == 'mail')
+      return (id != this.env.mailbox);
+    else if (this.task == 'addressbook')
+      return (id != this.env.source && this.env.address_sources[id] && !this.env.address_sources[id].readonly);
   };
 
 
@@ -1160,7 +1173,7 @@
       {
       var url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox)+add_url;
       if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
-        this.show_messageframe(true);
+        this.show_contentframe(true);
       else
         {
         this.set_busy(true, 'loading');
@@ -1170,14 +1183,15 @@
     };
 
 
-  this.show_messageframe = function(show)
+  this.show_contentframe = function(show)
     {
     var frm;
     if (this.env.contentframe && (frm = rcube_find_object(this.env.contentframe)))
       {
-      if (window.frames[this.env.contentframe] && !show)
-        window.frames[this.env.contentframe].location.href = 'program/blank.gif';
-      frm.style.display = show ? 'block' : 'none';
+      if (!show && window.frames[this.env.contentframe] && frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)<0)
+        frames[this.env.contentframe].location.href = this.env.blankpage;
+      if (!bw.safari)
+        frm.style.display = show ? 'block' : 'none';
       }
       
     if (!show && this.busy)
@@ -1204,7 +1218,7 @@
       if (this.task=='mail')
         this.list_mailbox(this.env.mailbox, page);
       else if (this.task=='addressbook')
-        this.list_contacts(page);
+        this.list_contacts(this.env.source, page);
       }
     };
 
@@ -1222,6 +1236,10 @@
     // add sort to url if set
     if (sort)
       add_url += '&_sort=' + sort;
+
+    // also send search request to get the right messages
+    if (this.env.search_request)
+      add_url += '&_search='+this.env.search_request;
       
     // set page=1 if changeing to another mailbox
     if (!page && mbox != this.env.mailbox)
@@ -1231,14 +1249,11 @@
       this.env.current_page = page;
       if (this.message_list)
         this.message_list.clear_selection();
-      this.show_messageframe(false);
+      this.show_contentframe(false);
       }
     
-    // also send search request to get the right messages
-    if (this.env.search_request)
-      add_url += '&_search='+this.env.search_request;
-      
-    this.select_mailbox(mbox);
+    this.select_folder(mbox, this.env.mailbox);
+    this.env.mailbox = mbox;
 
     // load message list remotely
     if (this.gui_objects.messagelist)
@@ -1315,20 +1330,7 @@
     this.http_request('purge', url+add_url, lock);
     return true;
     };
-    
-  this.focus_mailbox = function(mbox)
-    {
-    var mbox_li;
-    if (this.drag_active && mbox != this.env.mailbox && (mbox_li = this.get_mailbox_li(mbox)))
-      this.set_classname(mbox_li, 'droptarget', true);
-    }
-    
-  this.unfocus_mailbox = function(mbox)
-    {
-    var mbox_li;
-    if (this.drag_active && (mbox_li = this.get_mailbox_li(mbox)))
-      this.set_classname(mbox_li, 'droptarget', false);
-    }
+
   
   // move selected messages to the specified mailbox
   this.move_messages = function(mbox)
@@ -1350,7 +1352,7 @@
       this.set_busy(true, 'movingmessage');
       }
     else
-      this.show_messageframe(false);
+      this.show_contentframe(false);
 
     this._with_selected_messages('moveto', lock, add_url);
     };
@@ -1401,7 +1403,7 @@
     if (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))
       return;
       
-    this.show_messageframe(false);
+    this.show_contentframe(false);
     this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : ''));
     };
 
@@ -1425,9 +1427,9 @@
 
       this.message_list.select_next();
       }
-
-    // also send search request to get the right messages
-    if (this.env.search_request)
+      
+    // also send search request to get the right messages 
+    if (this.env.search_request) 
       add_url += '&_search='+this.env.search_request;
 
     // send request to server
@@ -1619,18 +1621,6 @@
   };
 
 
-  this.get_mailbox_li = function(mbox)
-    {
-    if (this.gui_objects.mailboxlist)
-      {
-      mbox = String((mbox ? mbox : this.env.mailbox)).toLowerCase().replace(this.mbox_expression, '');
-      return document.getElementById('rcmbx'+mbox);
-      }
-    
-    return null;
-    };
-
-
   /*********************************************************/
   /*********           login form methods          *********/
   /*********************************************************/
@@ -1648,7 +1638,7 @@
       return false;
     }
   };
-    
+
 
   /*********************************************************/
   /*********        message compose methods        *********/
@@ -1709,6 +1699,13 @@
     };
 
 
+  this.set_draft_id = function(id)
+    {
+    var f;
+    if (f = rcube_find_object('_draft_saveid'))
+      f.value = id;
+    };
+
   this.auto_save_start = function()
     {
     if (this.env.draft_autosave)
@@ -1716,7 +1713,7 @@
     };
 
 
-  this.compose_field_hash = function()
+  this.compose_field_hash = function(save)
     {
     // check input fields
     var input_to = rcube_find_object('_to');
@@ -1736,7 +1733,10 @@
       str += input_subject.value+':';
     if (input_message && input_message.value)
       str += input_message.value;
-
+    
+    if (save)
+      this.cmp_hash = str;
+    
     return str;
     };
     
@@ -1936,19 +1936,27 @@
   this.add_contact = function(value)
     {
     if (value)
-      this.http_request('addcontact', '_address='+value);
+      this.http_post('addcontact', '_address='+value);
     
     return true;
     };
 
-  // send remote request to search mail
-  this.qsearch = function(value, mbox)
+  // send remote request to search mail or contacts
+  this.qsearch = function(value)
     {
-    if (value && mbox)
+    if (value != '')
       {
-      this.message_list.clear();
+      if (this.message_list)
+        this.message_list.clear();
+      else if (this.contact_list) {
+        this.contact_list.clear(true);
+        this.show_contentframe(false);
+      }
+
+      // reset vars
+      this.env.current_page = 1;
       this.set_busy(true, 'searching');
-      this.http_request('search', '_search='+value+'&_mbox='+mbox, true);
+      this.http_request('search', '_q='+urlencode(value)+(this.env.mailbox ? '&_mbox='+this.env.mailbox : '')+(this.env.source ? '&_source='+urlencode(this.env.source) : ''), true);
       }
     return true;
     };
@@ -2209,33 +2217,47 @@
 
   this.contactlist_select = function(list)
     {
-      var id, frame;
+      if (this.preview_timer)
+        clearTimeout(this.preview_timer);
+
+      var id, frame, ref = this;
       if (id = list.get_single_selection())
-        this.load_contact(id, 'show');
-      else if (frame = document.getElementById(this.env.contentframe))
-        frame.style.visibility = 'hidden';
+        this.preview_timer = setTimeout(function(){ ref.load_contact(id, 'show'); }, this.dblclick_time + 10);
+      else if (this.env.contentframe)
+        this.show_contentframe(false);
 
       this.enable_command('edit', id?true:false);
+      this.enable_command('compose', list.selection.length > 0);
+      this.enable_command('delete', list.selection.length && this.env.address_sources && !this.env.address_sources[this.env.source].readonly);
 
-      if (list.selection.length)
-        this.enable_command('delete', 'compose', true);
-
-      return false; 
+      return false;
     };
 
 
-  this.list_contacts = function(page)
+  this.list_contacts = function(src, page)
     {
     var add_url = '';
     var target = window;
     
-    if (page && this.current_page==page)
+    if (!src)
+      src = this.env.source;
+    
+    if (page && this.current_page==page && src == this.env.source)
       return false;
+      
+    if (src != this.env.source)
+      {
+      page = 1;
+      this.env.current_page = page;
+      }
+
+    this.select_folder(src, this.env.source);
+    this.env.source = src;
 
     // load contacts remotely
     if (this.gui_objects.contactslist)
       {
-      this.list_contacts_remote(page);
+      this.list_contacts_remote(src, page);
       return;
       }
 
@@ -2245,19 +2267,31 @@
       add_url = '&_framed=1';
       }
 
+    // also send search request to get the correct listing
+    if (this.env.search_request)
+      add_url += '&_search='+this.env.search_request;
+
     this.set_busy(true, 'loading');
-    target.location.href = this.env.comm_path+(page ? '&_page='+page : '')+add_url;
+    target.location.href = this.env.comm_path+(src ? '&_source='+urlencode(src) : '')+(page ? '&_page='+page : '')+add_url;
     };
 
 
   // send remote request to load contacts list
-  this.list_contacts_remote = function(page)
+  this.list_contacts_remote = function(src, page)
     {
     // clear message list first
-    this.contact_list.clear();
+    this.contact_list.clear(true);
+    this.show_contentframe(false);
+    this.enable_command('delete', 'compose', false);
 
     // send request to server
-    var url = page ? '&_page='+page : '';
+    var url = (src ? '&_source='+urlencode(src) : '') + (page ? '&_page='+page : '');
+    this.env.source = src;
+    
+    // also send search request to get the right messages 
+    if (this.env.search_request) 
+      url += '&_search='+this.env.search_request;
+
     this.set_busy(true, 'loading');
     this.http_request('list', url, true);
     };
@@ -2272,18 +2306,28 @@
       {
       add_url = '&_framed=1';
       target = window.frames[this.env.contentframe];
-      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
+      this.show_contentframe(true);
       }
     else if (framed)
       return false;
-
-    if (action && (cid || action=='add'))
+      
+    if (action && (cid || action=='add') && !this.drag_active)
       {
       this.set_busy(true);
-      target.location.href = this.env.comm_path+'&_action='+action+'&_cid='+cid+add_url;
+      target.location.href = this.env.comm_path+'&_action='+action+'&_source='+urlencode(this.env.source)+'&_cid='+urlencode(cid) + add_url;
       }
     return true;
     };
+
+  // copy a contact to the specified target (group or directory)
+  this.copy_contact = function(cid, to)
+  {
+    if (!cid)
+      cid = this.contact_list.get_selection().join(',');
+
+    if (to != this.env.source && cid && this.env.address_sources[to] && !this.env.address_sources[to].readonly)
+      this.http_post('copy', '_cid='+urlencode(cid)+'&_source='+urlencode(this.env.source)+'&_to='+urlencode(to));
+  };
 
 
   this.delete_contacts = function()
@@ -2308,15 +2352,12 @@
         }
 
       // hide content frame if we delete the currently displayed contact
-      if (selection.length==1 && this.env.contentframe)
-        {
-        var elm = document.getElementById(this.env.contentframe);
-        elm.style.visibility = 'hidden';
-        }
+      if (selection.length == 1)
+        this.show_contentframe(false);
       }
 
     // send request to server
-    this.http_request('delete', '_cid='+a_cids.join(',')+'&_from='+(this.env.action ? this.env.action : ''));
+    this.http_request('delete', '_cid='+urlencode(a_cids.join(','))+'&_from='+(this.env.action ? this.env.action : ''));
     return true;
     };
 
@@ -2336,51 +2377,6 @@
 
     return false;
     };
-
-  // load ldap search form
-  // deprecated
-  this.ldappublicsearch = function(action)
-    {
-    var add_url = '';
-    var target = window;
-    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
-      {
-      add_url = '&_framed=1';
-      target = window.frames[this.env.contentframe];
-      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
-      }
-    else
-      return false;
-
-    if (action == 'ldappublicsearch')
-      target.location.href = this.env.comm_path+'&_action='+action+add_url;
-      
-    return true;
-    };
- 
-  // add ldap contacts to address book
-  this.add_ldap_contacts = function()
-    {
-    if (window.frames[this.env.contentframe].rcmail)
-      {
-      var frame = window.frames[this.env.contentframe];
-
-      // build the url
-      var url    = '&_framed=1';
-      var emails = '&_emails=';
-      var names  = '&_names=';
-      var end    = '';
-      for (var n=0; n<frame.rcmail.selection.length; n++)
-        {
-        end = n < frame.rcmail.selection.length - 1 ? ',' : '';
-        emails += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[1].innerHTML + end;
-        names  += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[0].innerHTML + end;
-        }
-       
-      frame.location.href = this.env.comm_path + '&_action=save&_framed=1' + emails + names;
-      }
-    return false;
-    }
 
 
   /*********************************************************/
@@ -2418,7 +2414,6 @@
     };
 
 
-
   this.delete_identity = function(id)
     {
     // exit if no mailbox specified or if selection is empty
@@ -2428,15 +2423,6 @@
     
     if (!id)
       id = this.env.iid ? this.env.iid : selection[0];
-
-/*
-    // 'remove' row from list (just hide it)
-    if (this.identity_rows && this.identity_rows[id].obj)
-      {
-      this.clear_selection();
-      this.identity_rows[id].obj.style.display = 'none';
-      }
-*/
 
     // if (this.env.framed && id)
     this.goto_url('delete-identity', '_iid='+id, true);
@@ -2928,6 +2914,10 @@
   // display a system message
   this.display_message = function(msg, type, hold)
     {
+    // pass command to parent window
+    if (this.env.framed && parent.rcmail )
+      return parent.rcmail.display_message(msg, type, hold);
+
     this.set_busy(false);
     if (!this.loaded)  // save message in order to display after page loaded
       {
@@ -2969,38 +2959,37 @@
 
 
   // mark a mailbox as selected and set environment variable
-  this.select_mailbox = function(mbox)
+  this.select_folder = function(name, old)
+  {
+    if (this.gui_objects.folderlist)
     {
-    if (this.gui_objects.mailboxlist )
-      {
-      var item, reg, text_obj;      
-      var current_li = this.get_mailbox_li();
-      var mbox_li = this.get_mailbox_li(mbox);
+      var current_li, target_li;
       
-      if (current_li)
-        {
+      if ((current_li = this.get_folder_li(old)))
+      {
         this.set_classname(current_li, 'selected', false);
         this.set_classname(current_li, 'unfocused', false);
-        }
-
-      if (mbox_li || this.env.mailbox == mbox)
-        {
-        this.set_classname(mbox_li, 'unfocused', false);
-        this.set_classname(mbox_li, 'selected', true);
-        }
       }
 
-    // also update mailbox name in window title
-    if (document.title)
+      if ((target_li = this.get_folder_li(name)))
       {
-      var doc_title = String(document.title);
-      var reg = new RegExp(this.env.mailbox.toLowerCase(), 'i');
-      if (this.env.mailbox && doc_title.match(reg))
-        document.title = doc_title.replace(reg, mbox).replace(/^\([0-9]+\)\s+/i, '');
+        this.set_classname(target_li, 'unfocused', false);
+        this.set_classname(target_li, 'selected', true);
       }
-    
-    this.env.mailbox = mbox;
-    };
+    }
+  };
+
+  // helper method to find a folder list item
+  this.get_folder_li = function(name)
+  {
+    if (this.gui_objects.folderlist)
+    {
+      name = String(name).replace(this.identifier_expr, '');
+      return document.getElementById('rcmli'+name);
+    }
+
+    return null;
+  };
 
 
   // for reordering column array, Konqueror workaround
@@ -3092,14 +3081,14 @@
     };
 
   // replace content of quota display
-   this.set_quota = function()
-     {
-     if (this.gui_objects.quotadisplay &&
-         this.gui_objects.quotadisplay.attributes.getNamedItem('display') &&
-         this.gui_objects.quotadisplay.attributes.getNamedItem('id'))
-       this.http_request('quotadisplay', '_display='+
-         this.gui_objects.quotadisplay.attributes.getNamedItem('display').nodeValue+
-         '&_id='+this.gui_objects.quotadisplay.attributes.getNamedItem('id').nodeValue, false);
+  this.set_quota = function()
+    {
+    if (this.gui_objects.quotadisplay &&
+        this.gui_objects.quotadisplay.attributes.getNamedItem('display') &&
+        this.gui_objects.quotadisplay.attributes.getNamedItem('id'))
+      this.http_request('quotadisplay', '_display='+
+      this.gui_objects.quotadisplay.attributes.getNamedItem('display').nodeValue+
+      '&_id='+this.gui_objects.quotadisplay.attributes.getNamedItem('id').nodeValue, false);
      };
 
 
@@ -3113,8 +3102,8 @@
       set_title = true;
 
     var reg, text_obj;
-    var item = this.get_mailbox_li(mbox);
-    mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
+    var item = this.get_folder_li(mbox);
+    mbox = String(mbox).toLowerCase().replace(this.identifier_expr, '');
 
     if (item && item.className && item.className.indexOf('mailbox '+mbox)>=0)
       {
@@ -3198,6 +3187,14 @@
   /*********        remote request methods        *********/
   /********************************************************/
 
+  this.redirect = function(url)
+    {
+    this.set_busy(true);
+    if (this.env.framed && window.parent)
+      parent.location.href = url;
+    else  
+      location.href = url;
+    };
 
   this.goto_url = function(action, query, lock)
     {
@@ -3205,7 +3202,7 @@
     this.set_busy(true);
 
     var querystring = query ? '&'+query : '';
-    location.href = this.env.comm_path+'&_action='+action+querystring;
+    this.redirect(this.env.comm_path+'&_action='+action+querystring);
     };
 
 
@@ -3241,12 +3238,12 @@
     // send request
     if (request_obj)
       {
-      // prompt('request', this.env.comm_path+'&_action='+urlencode(action)+'&'+querystring);
-      console('HTTP request: '+this.env.comm_path+'&_action='+action+'&'+querystring);
+      console.log('HTTP request: '+this.env.comm_path+'&_action='+action+'&'+querystring);
 
       if (lock)
         this.set_busy(true);
 
+      var rcm = this;
       request_obj.__lock = lock ? true : false;
       request_obj.__action = action;
       request_obj.onerror = function(o){ ref.http_error(o); };
@@ -3255,6 +3252,31 @@
       }
     };
 
+    // send a http POST request to the server
+    this.http_post = function(action, postdata, lock)
+      {
+      var request_obj;
+      if (postdata && typeof(postdata) == 'object')
+        postdata._remote = 1;
+      else
+        postdata += (postdata ? '&' : '') + '_remote=1';
+
+      // send request
+      if (request_obj = this.get_request_obj())
+        {
+        console.log('HTTP POST: '+this.env.comm_path+'&_action='+action);
+
+        if (lock)
+          this.set_busy(true);
+
+        var rcm = this;
+        request_obj.__lock = lock ? true : false;
+        request_obj.__action = action;
+        request_obj.onerror = function(o){ rcm.http_error(o); };
+        request_obj.oncomplete = function(o){ rcm.http_response(o); };
+        request_obj.POST(this.env.comm_path+'&_action='+action, postdata);
+        }
+      };
 
   // handle HTTP response
   this.http_response = function(request_obj)
@@ -3268,7 +3290,7 @@
 
     this.set_busy(false);
 
-  console(request_obj.get_text());
+    console.log(request_obj.get_text());
 
     // if we get javascript code from server -> execute it
     if (request_obj.get_text() && (ctype=='text/javascript' || ctype=='application/x-javascript'))
@@ -3450,7 +3472,10 @@
     if (window.XMLHttpRequest)
       this.xmlhttp = new XMLHttpRequest();
     else if (window.ActiveXObject)
-      this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+      {
+      try { this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
+      catch(e) { this.xmlhttp = null; }
+      }
     else
       {
       
@@ -3481,8 +3506,8 @@
   this.POST = function(url, body, contentType)
     {
     // default value for contentType if not provided
-    contentType = typeof(contentType) != 'undefined' ?
-      contentType : 'application/x-www-form-urlencoded';
+    if (typeof(contentType) == 'undefined')
+      contentType = 'application/x-www-form-urlencoded';
 
     this.build();
     
@@ -3491,15 +3516,23 @@
        this.onerror(this);
        return false;
     }
+    
+    var req_body = body;
+    if (typeof(body) == 'object')
+    {
+      req_body = '';
+      for (var p in body)
+        req_body += (req_body ? '&' : '') + p+'='+urlencode(body[p]);
+    }
 
-    var ref=this;
+    var ref = this;
     this.url = url;
     this.busy = true;
     
     this.xmlhttp.onreadystatechange = function() { ref.xmlhttp_onreadystatechange(); };
     this.xmlhttp.open('POST', url, true);
     this.xmlhttp.setRequestHeader('Content-Type', contentType);
-    this.xmlhttp.send(body);
+    this.xmlhttp.send(req_body);
     };
 
 
@@ -3561,11 +3594,5 @@
   {
   if (window[o] && window[o].init)
     setTimeout(o+'.init()', 200);
-  }
-
-function console(str)
-  {
-  if (document.debugform && document.debugform.console)
-    document.debugform.console.value += str+'\n--------------------------------------\n';
   }
 
diff --git a/program/js/common.js b/program/js/common.js
index be9a131..d9216a5 100644
--- a/program/js/common.js
+++ b/program/js/common.js
@@ -602,4 +602,25 @@
 roundcube_browser.prototype.get_cookie = getCookie;
 
 
+// tiny replacement for Firebox functionality
+function rcube_console()
+{
+  this.box = rcube_find_object('console');
+  
+  this.log = function(msg)
+  {
+    if (this.box)
+      this.box.value += str+'\n--------------------------------------\n';
+  };
+  
+  this.reset = function()
+  {
+    if (this.box)
+      this.box.value = '';
+  };
+}
+
 var bw = new roundcube_browser();
+
+if (!window.console)
+  console = new rcube_console();
diff --git a/program/js/editor.js b/program/js/editor.js
index 590388c..536ad2a 100644
--- a/program/js/editor.js
+++ b/program/js/editor.js
@@ -99,8 +99,8 @@
 
    http_request.onerror = function(o) { rcmail_handle_toggle_error(o); };
    http_request.oncomplete = function(o) { rcmail_set_text_value(o); };
-   var url=rcmail.env.comm_path+'&_action=html2text';
-   console('HTTP request: ' + url);
+   var url = rcmail.env.bin_path+'html2text.php';
+   console.log('HTTP request: ' + url);
    http_request.POST(url, htmlText, 'application/octet-stream');
 }
 
diff --git a/program/js/list.js b/program/js/list.js
index 0797295..7195858 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -95,7 +95,7 @@
 init_row: function(row)
 {
   // make references in internal array and set event handlers
-  if (row && String(row.id).match(/rcmrow([0-9]+)/))
+  if (row && String(row.id).match(/rcmrow([a-z0-9\-_=]+)/i))
   {
     var p = this;
     var uid = RegExp.$1;
@@ -117,12 +117,14 @@
 /**
  *
  */
-clear: function()
+clear: function(sel)
 {
   var tbody = document.createElement('TBODY');
   this.list.insertBefore(tbody, this.list.tBodies[0]);
   this.list.removeChild(this.list.tBodies[1]);
-  this.rows = new Array();  
+  this.rows = new Array();
+  
+  if (sel) this.clear_selection();
 },
 
 
diff --git a/program/localization/de_CH/messages.inc b/program/localization/de_CH/messages.inc
index dab8057..3cf497f 100644
--- a/program/localization/de_CH/messages.inc
+++ b/program/localization/de_CH/messages.inc
@@ -37,6 +37,8 @@
 $messages['blockedimages'] = 'Um Ihre Privatsphäre zur schützen, wurden externe Bilder blockiert.';
 $messages['encryptedmessage'] = 'Dies ist eine verschlüsselte Nachricht und kann leider nicht angezeigt werden.';
 $messages['nocontactsfound'] = 'Keine Kontakte gefunden';
+$messages['contactnotfound'] = 'Die gewählte Adresse wurde nicht gefunden';
+
 $messages['sendingfailed'] = 'Versand der Nachricht fehlgeschlagen';
 $messages['errorsaving'] = 'Beim Speichern ist ein Fehler aufgetreten';
 $messages['errormoving'] = 'Nachricht konnte nicht verschoben werden';
diff --git a/program/localization/en_GB/labels.inc b/program/localization/en_GB/labels.inc
index e478016..71e73c8 100644
--- a/program/localization/en_GB/labels.inc
+++ b/program/localization/en_GB/labels.inc
@@ -158,15 +158,6 @@
 $labels['print']          = 'Print';
 $labels['export']         = 'Export';
 
-// LDAP search
-$labels['ldapsearch'] = 'LDAP directory search';
-
-$labels['ldappublicsearchname']    = 'Contact name';
-$labels['ldappublicsearchtype'] = 'Exact match?';
-$labels['ldappublicserverselect'] = 'Select servers';
-$labels['ldappublicsearchfield'] = 'Search on';
-$labels['ldappublicsearchform'] = 'Look for a contact';
-$labels['ldappublicsearch'] = 'Search';
 
 // settings
 $labels['settingsfor']  = 'Settings for';
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 530a8ec..201af0b 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -182,16 +182,8 @@
 $labels['nextpage']       = 'Show next set';
 $labels['lastpage']       = 'Show last set';
 
-
-// LDAP search
-$labels['ldapsearch'] = 'LDAP directory search';
-
-$labels['ldappublicsearchname']    = 'Contact name';
-$labels['ldappublicsearchtype'] = 'Exact match?';
-$labels['ldappublicserverselect'] = 'Select servers';
-$labels['ldappublicsearchfield'] = 'Search on';
-$labels['ldappublicsearchform'] = 'Look for a contact';
-$labels['ldappublicsearch'] = 'Search';
+$labels['groups'] = 'Groups';
+$labels['personaladrbook'] = 'Personal Addresses';
 
 
 // settings
diff --git a/program/localization/en_US/messages.inc b/program/localization/en_US/messages.inc
index 51cc313..bb132a5 100644
--- a/program/localization/en_US/messages.inc
+++ b/program/localization/en_US/messages.inc
@@ -38,6 +38,8 @@
 $messages['blockedimages'] = 'To protect your privacy, remote images are blocked in this message.';
 $messages['encryptedmessage'] = 'This is an encrypted message and can not be displayed. Sorry!';
 $messages['nocontactsfound'] = 'No contacts found';
+$messages['contactnotfound'] = 'The requested contact was not found';
+
 $messages['sendingfailed'] = 'Failed to send message';
 $messages['errorsaving'] = 'An error occured while saving';
 $messages['errormoving'] = 'Could not move the message';
@@ -69,4 +71,9 @@
 $messages['fileuploaderror'] = 'File upload failed';
 $messages['filesizeerror'] = 'The uploaded file exceeds the maximum size of $size';
 
-?>
\ No newline at end of file
+$messages['copysuccess'] = 'Successfully copied $nr addresses';
+$messages['copyerror'] = 'Could not copy any addresses';
+$messages['sourceisreadonly'] = 'This address source is read only';
+$messages['errorsavingcontact'] = 'Could not save the contact address';
+
+?>
diff --git a/program/steps/addressbook/copy.inc b/program/steps/addressbook/copy.inc
new file mode 100644
index 0000000..e6d917e
--- /dev/null
+++ b/program/steps/addressbook/copy.inc
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/copy.inc                                    |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2007, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Copy a contact record from one direcotry to another                 |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: copy.inc 471 2007-02-09 21:25:50Z thomasb $
+
+*/
+
+$cid = get_input_value('_cid', RCUBE_INPUT_POST);
+$target = get_input_value('_to', RCUBE_INPUT_POST);
+if ($cid && preg_match('/^[a-z0-9\-_=]+(,[a-z0-9\-_=]+)*$/i', $cid) && strlen($target) && $target != $source)
+{
+  if ($target != '0')
+    $TARGET = new rcube_ldap($CONFIG['ldap_public'][$target]);
+  else
+    $TARGET = new rcube_contacts($DB, $_SESSION['user_id']);
+    
+  $success = false;  
+  if ($TARGET && $TARGET->ready && !$TARGET->readonly)
+    $success = $TARGET->insert($CONTACTS->search($CONTACTS->primary_key, $cid), true);
+
+  if (empty($success))
+    $OUTPUT->show_message('copyerror', 'error');
+  else
+    $OUTPUT->show_message('copysuccess', 'notice', array('nr' => count($success)));
+}
+  
+// send response
+$OUTPUT->send();
+
+?>
\ No newline at end of file
diff --git a/program/steps/addressbook/delete.inc b/program/steps/addressbook/delete.inc
index 3e33cd8..e5c7628 100644
--- a/program/steps/addressbook/delete.inc
+++ b/program/steps/addressbook/delete.inc
@@ -5,7 +5,7 @@
  | program/steps/addressbook/delete.inc                                  |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -19,80 +19,29 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
-
-if ($_GET['_cid'] && preg_match('/^[0-9]+(,[0-9]+)*$/',$_GET['_cid']))
+if (($cid = get_input_value('_cid', RCUBE_INPUT_GPC)) && preg_match('/^[0-9]+(,[0-9]+)*$/', $cid))
   {
-  $DB->query("UPDATE ".get_table_name('contacts')."
-              SET    del=1
-              WHERE  user_id=?
-              AND    contact_id IN (".$_GET['_cid'].")",
-              $_SESSION['user_id']);
-                     
-  $count = $DB->affected_rows();
-  if (!$count)
+  $deleted = $CONTACTS->delete($cid);
+  if (!$deleted)
     {
     // send error message
     exit;
     }
 
-
   // count contacts for this user
-  $sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
-                            FROM ".get_table_name('contacts')."
-                            WHERE  del<>1
-                            AND    user_id=?",
-                            $_SESSION['user_id']);
-                                   
-  $sql_arr = $DB->fetch_assoc($sql_result);
-  $rowcount = $sql_arr['rows'];    
+  $result = $CONTACTS->count();
 
   // update message count display
-  $pages = ceil($rowcount/$CONFIG['pagesize']);
-  $commands = sprintf("this.set_rowcount('%s');\n", rcmail_get_rowcount_text($rowcount));
-  $commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
-
+  $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
+  $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result->count));
 
   // add new rows from next page (if any)
-  if ($_GET['_from']!='show' && $pages>1 && $_SESSION['page'] < $pages)
-    {
-    $start_row = ($_SESSION['page'] * $CONFIG['pagesize']) - $count;
-
-    // get contacts from DB
-    $sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
-                                   WHERE  del<>1
-                                   AND    user_id=?
-                                   ORDER BY name",
-                                   $start_row,
-                                   $count,
-                                   $_SESSION['user_id']);
-                                     
-    $commands .= rcmail_js_contacts_list($sql_result);
-
-/*
-    // define list of cols to be displayed
-    $a_show_cols = array('name', 'email');
-    
-    while ($sql_arr = $DB->fetch_assoc($sql_result))
-      {
-      $a_row_cols = array();
-            
-      // format each col
-      foreach ($a_show_cols as $col)
-        {
-        $cont = rep_specialchars_output($sql_arr[$col]);
-        $a_row_cols[$col] = $cont;
-        }
-  
-      $commands .= sprintf("this.add_contact_row(%s, %s);\n",
-                           $sql_arr['contact_id'],
-                           array2js($a_row_cols));
-      }
-*/
-    }
+  $pages = ceil(($result->count + $deleted) / $CONTACTS->page_size);
+  if ($_GET['_from'] != 'show' && $pages > 1 && $CONTACTS->list_page < $pages)
+    rcmail_js_contacts_list($CONTACTS->list_records(null, -$deleted));
 
   // send response
-  rcube_remote_response($commands);
+  $OUTPUT->send();
   }
 
 exit;
diff --git a/program/steps/addressbook/edit.inc b/program/steps/addressbook/edit.inc
index 245c024..47db719 100644
--- a/program/steps/addressbook/edit.inc
+++ b/program/steps/addressbook/edit.inc
@@ -5,7 +5,7 @@
  | program/steps/addressbook/edit.inc                                    |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -20,30 +20,27 @@
 */
 
 
-if (($_GET['_cid'] || $_POST['_cid']) && $_action=='edit')
-  {
-  $cid = $_POST['_cid'] ? $_POST['_cid'] : $_GET['_cid'];
-  $DB->query("SELECT * FROM ".get_table_name('contacts')."
-             WHERE  contact_id=?
-             AND    user_id=?
-             AND    del<>1",
-             $cid,
-             $_SESSION['user_id']);
-  
-  $CONTACT_RECORD = $DB->fetch_assoc();
+if (($cid = get_input_value('_cid', RCUBE_INPUT_GPC)) && ($record = $CONTACTS->get_record($cid, true)))
+  $OUTPUT->set_env('cid', $record['ID']);
 
-  if (is_array($CONTACT_RECORD))
-    $OUTPUT->add_script(sprintf("%s.set_env('cid', '%s');", $JS_OBJECT_NAME, $CONTACT_RECORD['contact_id']));
-  }
-
-
+// adding not allowed here
+if ($CONTACTS->readonly)
+{
+  $OUTPUT->show_message('sourceisreadonly');
+  rcmail_overwrite_action('show');
+  return;
+}
 
 function rcmail_contact_editform($attrib)
-  {
-  global $CONTACT_RECORD, $JS_OBJECT_NAME;
+{
+  global $CONTACTS, $OUTPUT;
 
-  if (!$CONTACT_RECORD && $GLOBALS['_action']!='add')
-    return rcube_label('contactnotfound');
+  // check if we have a valid result
+  if ($GLOBALS['_action'] != 'add' && !(($result = $CONTACTS->get_result()) && ($record = $result->first())))
+  {
+    $OUTPUT->show_message('contactnotfound');
+    return false;
+  }
 
   // add some labels to client
   rcube_add_label('noemailwarning');
@@ -51,15 +48,14 @@
 
   list($form_start, $form_end) = get_form_tags($attrib);
   unset($attrib['form']);
-  
 
   // a specific part is requested
   if ($attrib['part'])
-    {
+  {
     $out = $form_start;
-    $out .= rcmail_get_edit_field($attrib['part'], $CONTACT_RECORD[$attrib['part']], $attrib); 
+    $out .= rcmail_get_edit_field($attrib['part'], $record[$attrib['part']], $attrib); 
     return $out;
-    }
+  }
 
 
   // return the complete address edit form as table
@@ -67,38 +63,37 @@
 
   $a_show_cols = array('name', 'firstname', 'surname', 'email');
   foreach ($a_show_cols as $col)
-    {
+  {
     $attrib['id'] = 'rcmfd_'.$col;
-    $title = rcube_label($col);
-    $value = rcmail_get_edit_field($col, $CONTACT_RECORD[$col], $attrib);
+    $value = rcmail_get_edit_field($col, $record[$col], $attrib);
     $out .= sprintf("<tr><td class=\"title\"><label for=\"%s\">%s</label></td><td>%s</td></tr>\n",
                     $attrib['id'],
-                    $title,
+                    Q(rcube_label($col)),
                     $value);
-    }
+  }
 
   $out .= "\n</table>$form_end";
 
   return $out;  
-  }
+}
+
+$OUTPUT->add_handler('contacteditform', 'rcmail_contact_editform');
 
 
 // similar function as in /steps/settings/edit_identity.inc
 function get_form_tags($attrib)
   {
-  global $CONTACT_RECORD, $OUTPUT, $JS_OBJECT_NAME, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
+  global $CONTACTS, $OUTPUT, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
 
+  $result = $CONTACTS->get_result();
   $form_start = '';
   if (!strlen($EDIT_FORM))
     {
     $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
-    $hiddenfields->add(array('name' => '_action', 'value' => 'save'));
+    $hiddenfields->add(array('name' => '_action', 'value' => 'save', 'source' => get_input_value('_source', RCUBE_INPUT_GPC)));
     
-    if ($GLOBALS['_framed'])
-      $hiddenfields->add(array('name' => '_framed', 'value' => 1));
-    
-    if ($CONTACT_RECORD['contact_id'])
-      $hiddenfields->add(array('name' => '_cid', 'value' => $CONTACT_RECORD['contact_id']));
+    if (($result = $CONTACTS->get_result()) && ($record = $result->first()))
+      $hiddenfields->add(array('name' => '_cid', 'value' => $record['ID']));
     
     $form_start = !strlen($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
     $form_start .= "\n$SESS_HIDDEN_FIELD\n";
@@ -109,7 +104,7 @@
   $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
   
   if (!strlen($EDIT_FORM))
-    $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('editform', '$form_name');");
+    $OUTPUT->add_gui_object('editform', $form_name);
   
   $EDIT_FORM = $form_name;
 
@@ -118,7 +113,7 @@
 
 
 
-if (!$CONTACT_RECORD && template_exists('addcontact'))
+if (!$CONTACTS->get_result() && template_exists('addcontact'))
   parse_template('addcontact');
 
 // this will be executed if no template for addcontact exists
diff --git a/program/steps/addressbook/func.inc b/program/steps/addressbook/func.inc
index 1f993a9..5329270 100644
--- a/program/steps/addressbook/func.inc
+++ b/program/steps/addressbook/func.inc
@@ -5,7 +5,7 @@
  | program/steps/addressbook/func.inc                                    |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -19,152 +19,182 @@
 
 */
 
-$CONTACTS_LIST = array();
+require_once('include/rcube_contacts.inc');
+require_once('include/rcube_ldap.inc');
+
+// instantiate a contacts object according to the given source
+if (($source = get_input_value('_source', RCUBE_INPUT_GPC)) && isset($CONFIG['ldap_public'][$source]))
+  $CONTACTS = new rcube_ldap($CONFIG['ldap_public'][$source]);
+else
+  $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']);
+
+$CONTACTS->set_pagesize($CONFIG['pagesize']);
 
 // set list properties and session vars
-if (strlen($_GET['_page']))
+if (!empty($_GET['_page']))
   {
-  $CONTACTS_LIST['page'] = $_GET['_page'];
+  $CONTACTS->set_page(intval($_GET['_page']));
   $_SESSION['page'] = $_GET['_page'];
   }
 else
-  $CONTACTS_LIST['page'] = $_SESSION['page'] ? $_SESSION['page'] : 1;
+  $CONTACTS->set_page(isset($_SESSION['page']) ?$_SESSION['page'] : 1);
 
-// disable the ldap public search button if there's no servers configured
-$enable_ldap = 'true';
-if (!$CONFIG['ldap_public'])
-  $enable_ldap = 'false';
+// set message set for search result
+if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
+  $CONTACTS->set_search_set($_SESSION['search'][$_REQUEST['_search']]);
+
+// set data source env
+$OUTPUT->set_env('source', $source ? $source : '0');
+$OUTPUT->set_env('readonly', $CONTACTS->readonly, false);
+
+
+function rcmail_directory_list($attrib)
+{
+  global $CONFIG, $OUTPUT;
   
-$OUTPUT->add_script("$JS_OBJECT_NAME.set_env('ldappublicsearch', $enable_ldap);");  
+  if (!$attrib['id'])
+    $attrib['id'] = 'rcmdirectorylist';
+
+  $local_id = '0';
+  $current = get_input_value('_source', RCUBE_INPUT_GPC);
+  $line_templ = '<li id="%s" class="%s"><a href="%s"' .
+    ' onclick="return %s.command(\'list\',\'%s\',this)"' .
+    ' onmouseover="return %s.focus_folder(\'%s\')"' .
+    ' onmouseout="return %s.unfocus_folder(\'%s\')"' .
+    ' onmouseup="return %s.folder_mouse_up(\'%s\')">%s'.
+    "</a></li>\n";
+    
+  $js_list = array("$local_id" => array('id' => $local_id, 'readonly' => false));
+
+  // allow the following attributes to be added to the <ul> tag
+  $out = '<ul' . create_attrib_string($attrib, array('style', 'class', 'id')) . ">\n";
+  $out .= sprintf($line_templ,
+    'rcmli'.$local_id,
+    !$current ? 'selected' : '',
+    Q(rcmail_self_url('list', array('_source' => 0))),
+    JS_OBJECT_NAME,
+    $local_id,
+    JS_OBJECT_NAME,
+    $local_id,
+    JS_OBJECT_NAME,
+    $local_id,
+    JS_OBJECT_NAME,
+    $local_id,
+    rcube_label('personaladrbook'));
+  
+  foreach ((array)$CONFIG['ldap_public'] as $id => $prop)
+  {
+    $js_id = JQ($id);
+    $dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id);
+    $out .= sprintf($line_templ,
+      'rcmli'.$dom_id,
+      $current == $id ? 'selected' : '',
+      Q(rcmail_self_url('list', array('_source' => $id))),
+      JS_OBJECT_NAME,
+      $js_id,
+      JS_OBJECT_NAME,
+      $js_id,
+      JS_OBJECT_NAME,
+      $js_id,
+      JS_OBJECT_NAME,
+      $js_id,
+      !empty($prop['name']) ? Q($prop['name']) : Q($id));
+      
+    $js_list[$id] = array('id' => $id, 'readonly' => !$prop['writeable']);
+  }
+  
+  $out .= '</ul>';
+
+  $OUTPUT->add_gui_object('folderlist', $attrib['id']);
+  $OUTPUT->set_env('address_sources', $js_list);
+  
+  return $out;
+}
+
 
 // return the message list as HTML table
 function rcmail_contacts_list($attrib)
   {
-  global $DB, $CONFIG, $OUTPUT, $CONTACTS_LIST, $JS_OBJECT_NAME;
-  
-  //$skin_path = $CONFIG['skin_path'];
-  //$image_tag = '<img src="%s%s" alt="%s" border="0" />';
+  global $CONTACTS, $OUTPUT;
   
   // count contacts for this user
-  $sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
-                            FROM ".get_table_name('contacts')."
-                            WHERE  del<>1
-                            AND    user_id=?",
-                            $_SESSION['user_id']);
-
-  $sql_arr = $DB->fetch_assoc($sql_result);
-  $rowcount = $sql_arr['rows'];
-
-  if ($rowcount)
-    {
-    $start_row = ($CONTACTS_LIST['page']-1) * $CONFIG['pagesize'];
-
-    // get contacts from DB
-    $sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
-                                   WHERE  del<>1
-                                   AND    user_id= ?
-                                   ORDER BY name",
-                                   $start_row,
-                                   $CONFIG['pagesize'],
-                                   $_SESSION['user_id']);
-    }
-  else
-    $sql_result = NULL;
-
-
+  $result = $CONTACTS->list_records();
+  
   // add id to message list table if not specified
   if (!strlen($attrib['id']))
     $attrib['id'] = 'rcmAddressList';
-
-  // define list of cols to be displayed
-  $a_show_cols = array('name', 'email');
-
-  // create XHTML table  
-  $out = rcube_table_output($attrib, $sql_result, $a_show_cols, 'contact_id');
-
-  // set client env
-  $javascript = sprintf("%s.gui_object('contactslist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
-  $javascript .= sprintf("%s.set_env('current_page', %d);\n", $JS_OBJECT_NAME, $CONTACTS_LIST['page']);
-  $javascript .= sprintf("%s.set_env('pagecount', %d);\n", $JS_OBJECT_NAME, ceil($rowcount/$CONFIG['pagesize']));
-  $javascript .= "rcmail.set_env('newcontact', '" . rcube_label('newcontact') . "');";
-  //$javascript .= sprintf("%s.set_env('contacts', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
   
-  $OUTPUT->add_script($javascript);  
-  $OUTPUT->include_script('list.js');
+  // define list of cols to be displayed
+  $a_show_cols = array('name');
 
+  // create XHTML table
+  $out = rcube_table_output($attrib, $result->records, $a_show_cols, $CONTACTS->primary_key);
+  
+  // set client env
+  $OUTPUT->add_gui_object('contactslist', $attrib['id']);
+  $OUTPUT->set_env('current_page', (int)$CONTACTS->list_page);
+  $OUTPUT->set_env('pagecount', ceil($result->count/$CONTACTS->page_size));
+  $OUTPUT->include_script('list.js');
+  
   // add some labels to client
   rcube_add_label('deletecontactconfirm');
-
+  
   return $out;
   }
 
 
-
-function rcmail_js_contacts_list($sql_result, $obj_name='this')
+function rcmail_js_contacts_list($result, $prefix='')
   {
-  global $DB;
+  global $OUTPUT;
 
-  $commands = '';
-  
-  if (!$sql_result)
-    return '';
+  if (empty($result) || $result->count == 0)
+    return;
 
   // define list of cols to be displayed
-  $a_show_cols = array('name', 'email');
-    
-  while ($sql_arr = $DB->fetch_assoc($sql_result))
+  $a_show_cols = array('name');
+  
+  while ($row = $result->next())
     {
     $a_row_cols = array();
-            
+    
     // format each col
     foreach ($a_show_cols as $col)
-      {
-      $cont = Q($sql_arr[$col]);
-      $a_row_cols[$col] = $cont;
-      }
-  
-    $commands .= sprintf("%s.add_contact_row(%s, %s);\n",
-                         $obj_name,
-                         $sql_arr['contact_id'],
-                         array2js($a_row_cols));
-    }
+      $a_row_cols[$col] = $row[$col];
     
-  return $commands;
+    $OUTPUT->command($prefix.'add_contact_row', $row['ID'], $a_row_cols);
+    }
   }
 
 
 // similar function as /steps/settings/identities.inc::rcmail_identity_frame()
 function rcmail_contact_frame($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME;
+  global $OUTPUT;
 
   if (!$attrib['id'])
     $attrib['id'] = 'rcmcontactframe';
     
   $attrib['name'] = $attrib['id'];
-
-  $OUTPUT->add_script(sprintf("%s.set_env('contentframe', '%s');", $JS_OBJECT_NAME, $attrib['name']));
-
   $attrib_str = create_attrib_string($attrib, array('name', 'id', 'class', 'style', 'src', 'width', 'height', 'frameborder'));
-  $out = '<iframe'. $attrib_str . '></iframe>';
-    
-  return $out;
+
+  $OUTPUT->set_env('contentframe', $attrib['name']);
+  $OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/blank.gif');
+  return '<iframe'. $attrib_str . '></iframe>';
   }
 
 
 function rcmail_rowcount_display($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME;
+  global $OUTPUT;
   
   if (!$attrib['id'])
     $attrib['id'] = 'rcmcountdisplay';
 
-  $OUTPUT->add_script(sprintf("%s.gui_object('countdisplay', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+  $OUTPUT->add_gui_object('countdisplay', $attrib['id']);
 
   // allow the following attributes to be added to the <span> tag
   $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
 
-  
   $out = '<span' . $attrib_str . '>';
   $out .= rcmail_get_rowcount_text();
   $out .= '</span>';
@@ -173,32 +203,37 @@
 
 
 
-function rcmail_get_rowcount_text($max=NULL)
+function rcmail_get_rowcount_text()
   {
-  global $CONTACTS_LIST, $CONFIG, $DB;
+  global $CONTACTS;
   
-  $start_row = ($CONTACTS_LIST['page']-1) * $CONFIG['pagesize'] + 1;
-
-  // get nr of contacts
-  if ($max===NULL)
-    {
-    $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
-                              WHERE  del<>1
-                              AND    user_id=?",
-                              $_SESSION['user_id']);
-
-    $max = $DB->num_rows($sql_result);
-    }
-
-  if ($max==0)
+  // read nr of contacts
+  $result = $CONTACTS->get_result();
+  if (!$result)
+    $result = $CONTACTS->count();
+  
+  if ($result->count == 0)
     $out = rcube_label('nocontactsfound');
   else
-    $out = rcube_label(array('name' => 'contactsfromto',
-                             'vars' => array('from'  => $start_row,
-                                             'to'    => min($max, $start_row + $CONFIG['pagesize'] - 1),
-                                             'count' => $max)));
+    $out = rcube_label(array(
+      'name' => 'contactsfromto',
+      'vars' => array(
+        'from'  => $result->first + 1,
+        'to'    => min($result->count, $result->first + $CONTACTS->page_size),
+        'count' => $result->count)
+      ));
 
   return $out;
   }
+  
+  
+// register UI objects
+$OUTPUT->add_handlers(array(
+  'directorylist' => 'rcmail_directory_list',
+  'addresslist' => 'rcmail_contacts_list',
+  'addressframe' => 'rcmail_contact_frame',
+  'recordscountdisplay' => 'rcmail_rowcount_display',
+  'searchform' => 'rcmail_search_form'
+));
 
 ?>
diff --git a/program/steps/addressbook/ldapsearchform.inc b/program/steps/addressbook/ldapsearchform.inc
deleted file mode 100644
index a4e08dc..0000000
--- a/program/steps/addressbook/ldapsearchform.inc
+++ /dev/null
@@ -1,277 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/steps/addressbook/ldapsearch.inc                              |
- |                                                                       |
- | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
- | Licensed under the GNU GPL                                            |
- |                                                                       |
- | PURPOSE:                                                              |
- |   Show an ldap search form in the addressbook                         |
- |                                                                       |
- +-----------------------------------------------------------------------+
- | Author: Justin Randell <justin.randell@gmail.com>                     |
- +-----------------------------------------------------------------------+
-
- $Id$
-
-*/
-require_once 'include/rcube_ldap.inc';
-
-/**
- * draw the ldap public search form
- */
-function rcmail_ldap_public_search_form($attrib)
-  {
-  global $CONFIG, $JS_OBJECT_NAME, $OUTPUT; 
-  if (!isset($CONFIG['ldap_public']))
-    {
-    // no ldap servers to search
-    show_message('noldapserver', 'warning');
-    rcmail_overwrite_action('add');
-    return false;
-    }
-  else
-    {
-    // store some information in the session
-    $_SESSION['ldap_public']['server_count'] = $server_count = count($CONFIG['ldap_public']);
-    $_SESSION['ldap_public']['server_names'] = $server_names = array_keys($CONFIG['ldap_public']);
-    }
-  
-  list($form_start, $form_end) = get_form_tags($attrib);
-  $out = "$form_start<table id=\"ldap_public_search_table\">\n\n";
-  
-  // search name field
-  $search_name = new textfield(array('name' => '_ldap_public_search_name',
-                                     'id'   => 'rcmfd_ldap_public_search_name'));
-  $out .= "<tr><td class=\"title\"><label for=\"rcmfd_ldap_public_search_name\">" . 
-          rep_specialchars_output(rcube_label('ldappublicsearchname')) . 
-          "</label></td><td>" . $search_name->show() . "</td></tr>\n";
-
-
-  // there's more than one server to search for, show a dropdown menu
-  if ($server_count > 1)
-    {
-    $select_server = new select(array('name' => '_ldap_public_servers', 
-                                      'id'   => 'rcfmd_ldap_public_servers'));
-     
-    $select_server->add($server_names, $server_names);
-
-    $out .= '<tr><td class="title"><label for="rcfmd_ldap_public_servers">' .
-            rep_specialchars_output(rcube_label('ldappublicserverselect')) .
-            "</label></td><td>" . $select_server->show() . "</td></tr>\n";
-    }
-  
-  // foreach configured ldap server, set up the search fields
-  for ($i = 0; $i < $server_count; $i++)
-    {
-    $server = $CONFIG['ldap_public'][$server_names[$i]];
-    
-    // only display one search fields select - js takes care of the rest
-    if (!$i)
-      {
-      $field_name = '_ldap_public_search_field';
-      $field_id   = 'rcfmd_ldap_public_search_field';
-
-      $search_fields = new select(array('name' => $field_name, 
-                                        'id'   => $field_id));
-
-      $search_fields->add(array_keys($server['search_fields']), array_values($server['search_fields']));
-      $out .= '<tr><td class="title"><label for="' . $field_id . '">' .
-              rep_specialchars_output(rcube_label('ldappublicsearchfield')) . 
-              "</label></td><td>" . $search_fields->show() . "</td></tr>\n";
-      
-      $attributes = array('name'  => '_ldap_public_search_type', 
-                          'id'    => 'rcmfd_ldap_public_search_type');
-
-      // if there's only one server, and it doesn't accept fuzzy searches,
-      // then check and disable the check box - thanks pieter
-      if ($server_count == 1 && !$server['fuzzy_search'])
-        {
-        $attributes['CHECKED'] = 'CHECKED'; 
-        $attributes['disabled'] = 'disabled'; 
-        }
-
-      $search_type = new checkbox($attributes);
-
-      $out .= '<tr id="ldap_fuzzy_search"><td class="title"><label for="rcmfd_ldap_public_search_type">' .
-              rep_specialchars_output(rcube_label('ldappublicsearchtype')) .
-              "</label></td><td>" . $search_type->show() . "</td></tr>\n";
-      }
-    
-    if ($server_count > 1)
-      {
-      // store the search fields in a js array for each server
-      $js = '';
-      foreach ($server['search_fields'] as $search_name => $search_value)
-        $js .= "['$search_name', '$search_value'], ";
-
-      // store whether this server accepts fuzzy search as last item in array
-      $js .= $server['fuzzy_search'] ? "'fuzzy'" : "'exact'";
-      $OUTPUT->add_script("rcmail.set_env('{$server_names[$i]}_search_fields', new Array($js));");
-      }
-    }
-
-  // add contact button label text
-  $OUTPUT->add_script("rcmail.set_env('addcontact', '" . rcube_label('addcontact') . "');");
-
-  $out .= "\n</table>$form_end";
-  return $out;  
-  }
-
-/**
- * get search values and return ldap contacts
- */
-function rcmail_ldap_public_list()
-  {
-  // just return if we are not being called from a search form
-  if (!isset($_POST['_action']))
-    return null;
-
-  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
-  
-  // show no search name warning and exit
-  if (empty($_POST['_ldap_public_search_name']) || trim($_POST['_ldap_public_search_name']) == '')
-    {
-    show_message('nosearchname', 'warning');
-    return false;
-    }
-  
-  // set up ldap server(s) array or bail
-  if ($_SESSION['ldap_public']['server_count'] > 1)
-    // show no ldap server warning and exit
-    if (empty($_POST['_ldap_public_servers']))
-      {
-      show_message('noldappublicserver', 'warning');
-      return false;
-      }
-    else
-      $server_name = $_POST['_ldap_public_servers'];
-  else if ($_SESSION['ldap_public']['server_count'] == 1)
-    $server_name = $_SESSION['ldap_public']['server_names'][0];
-  else
-    return false;
-
-  // get search parameters
-  $search_value = $_POST['_ldap_public_search_name'];
-  $search_field = $_POST['_ldap_public_search_field'];
-
-  // only use the post var for search type if the ldap server allows 'like'
-  $exact = true;
-  if ($CONFIG['ldap_public'][$server_name]['fuzzy_search'])
-    $exact = isset($_POST['_ldap_public_search_type']) ? true : false; 
-  
-  // perform an ldap search
-  $contacts = rcmail_ldap_contact_search($search_value, 
-                                         $search_field, 
-                                         $CONFIG['ldap_public'][$server_name], 
-                                         $exact);
-  
-  // if no results, show a warning and return
-  if (!$contacts)
-    {
-    show_message('nocontactsreturned', 'warning');
-    return false;
-    }
-
-  // add id to message list table if not specified
-  if (!strlen($attrib['id']))
-    $attrib['id'] = 'ldapAddressList';
-  
-  // define table class
-  $attrib['class'] = 'records-table';
-  $attrib['cellspacing'] = 0;
-
-  // define list of cols to be displayed
-  $a_show_cols = array('name', 'email');
-
-  // create XHTML table  
-  $out = rcube_table_output($attrib, $contacts, $a_show_cols, 'row_id');
-
-  // set client env
-  $javascript = "$JS_OBJECT_NAME.gui_object('ldapcontactslist', '{$attrib['id']}');\n";
-  $OUTPUT->add_script($javascript);  
-  
-  return $out;  
-  }
-
-/**
- * perform search for contacts from given public ldap server
- */
-function rcmail_ldap_contact_search($search_value, $search_field, $server, $exact=true)
-  {
-  global $CONFIG;
-  
-  $attributes = array($server['name_field'], $server['mail_field']); 
-
-  $LDAP = new rcube_ldap();
-  if ($LDAP->connect($server['hosts'], $server['port'], $server['protocol']))
-    {
-    $filter = "$search_field=" . ($exact ? $search_value : "*$search_value*"); 
-    $result = $LDAP->search($server['base_dn'],
-                            $filter, 
-                            $attributes, 
-                            $server['scope'], 
-                            $sort=null);
-         
-    // add any results to contact array
-    if ($result['count'])
-      {
-      for ($n = 0; $n < $result['count']; $n++)
-        {
-        $contacts[$n]['name']   = $result[$n][$server['name_field']][0];
-        $contacts[$n]['email']  = $result[$n][$server['mail_field']][0];
-        $contacts[$n]['row_id'] = $n + 1;
-        }
-      }
-    }
-  else
-    return false;
-
-  // cleanup
-  $LDAP->close();
-
-  if (!$result['count'])
-    return false;
- 
-  // weed out duplicate emails
-  for ($n = 0; $n < $result['count']; $n++)
-    for ($i = 0; $i < $result['count']; $i++)
-      if ($contacts[$i]['email'] == $contacts[$n]['email'] && $i != $n)
-        unset($contacts[$n]);
-
-  return $contacts;
-  }
-
-function get_form_tags($attrib)
-  {
-  global $OUTPUT, $JS_OBJECT_NAME, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
-
-  $form_start = '';
-  if (!strlen($EDIT_FORM))
-    {
-    $hiddenfields = new hiddenfield(array('name' => '_task', 'value' => $GLOBALS['_task']));
-    $hiddenfields->add(array('name' => '_action', 'value' => 'ldappublicsearch'));
-    
-    if ($_framed)
-      $hiddenfields->add(array('name' => '_framed', 'value' => 1));
-    
-    $form_start .= !strlen($attrib['form']) ? '<form name="form" action="./" method="post">' : '';
-    $form_start .= "\n$SESS_HIDDEN_FIELD\n";
-    $form_start .= $hiddenfields->show();
-    }
-    
-  $form_end = (strlen($EDIT_FORM) && !strlen($attrib['form'])) ? '</form>' : '';
-  $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
-  
-  $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('ldappublicsearchform', '$form_name');");
-  
-  $EDIT_FORM = $form_name;
-
-  return array($form_start, $form_end);  
-  }
-
-parse_template('ldappublicsearch');
-?>
diff --git a/program/steps/addressbook/list.inc b/program/steps/addressbook/list.inc
index 0aa4873..71b9379 100644
--- a/program/steps/addressbook/list.inc
+++ b/program/steps/addressbook/list.inc
@@ -5,7 +5,7 @@
  | program/steps/addressbook/list.inc                                    |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -19,38 +19,17 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
-
-// count contacts for this user
-$sql_result = $DB->query("SELECT COUNT(contact_id) AS rows
-                          FROM ".get_table_name('contacts')."
-                          WHERE  del<>1
-                          AND    user_id=?",
-                          $_SESSION['user_id']);
-                                   
-$sql_arr = $DB->fetch_assoc($sql_result);
-$rowcount = $sql_arr['rows'];    
+// get contacts for this user
+$result = $CONTACTS->list_records();
 
 // update message count display
-$pages = ceil($rowcount/$CONFIG['pagesize']);
-$commands = sprintf("this.set_rowcount('%s');\n", rcmail_get_rowcount_text($rowcount));
-$commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
+$OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
+$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($rowcount));
 
-$start_row = ($CONTACTS_LIST['page']-1) * $CONFIG['pagesize'];
-
-// get contacts from DB
-$sql_result = $DB->limitquery("SELECT * FROM ".get_table_name('contacts')."
-                               WHERE  del<>1
-                               AND    user_id=?
-                               ORDER BY name",
-                               $start_row,
-                               $CONFIG['pagesize'],
-                               $_SESSION['user_id']);
-                                 
-$commands .= rcmail_js_contacts_list($sql_result);
+// create javascript list
+rcmail_js_contacts_list($result);
   
 // send response
-rcube_remote_response($commands);
+$OUTPUT->send();
 
-exit;
 ?>
\ No newline at end of file
diff --git a/program/steps/addressbook/mailto.inc b/program/steps/addressbook/mailto.inc
new file mode 100644
index 0000000..ddbec30
--- /dev/null
+++ b/program/steps/addressbook/mailto.inc
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/mailto.inc                                  |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2007, RoundCube Dev. - Switzerland                      |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Compose a recipient list with all selected contacts                 |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: copy.inc 471 2007-02-09 21:25:50Z thomasb $
+
+*/
+
+$cid = get_input_value('_cid', RCUBE_INPUT_GET);
+$recipients = null;
+$mailto = array();
+
+if ($cid && preg_match('/^[a-z0-9\-_=]+(,[a-z0-9\-_=]+)*$/i', $cid) && $CONTACTS->ready)
+{
+  $recipients = $CONTACTS->search($CONTACTS->primary_key, $cid);
+
+  while (is_object($recipients) && ($rec = $recipients->iterate()))
+    $mailto[] = format_email_recipient($rec['email'], $rec['name']);
+}
+
+if (!empty($mailto))
+{
+  $mailto_str = join(', ', $mailto);
+  $mailto_id = substr(md5($mailto_str), 0, 16);
+  $_SESSION['mailto'][$mailto_id] = $mailto_str;
+  $OUTPUT->command('redirect', rcmail_self_url('compose', array('_mailto' => $mailto_id), 'mail'));
+}
+else
+  $OUTPUT->show_message('nocontactsfound', 'warning');
+
+
+// send response
+$OUTPUT->send();
+
+?>
\ No newline at end of file
diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc
index a50d038..72e54b1 100644
--- a/program/steps/addressbook/save.inc
+++ b/program/steps/addressbook/save.inc
@@ -5,7 +5,7 @@
  | program/steps/addressbook/save.inc                                    |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -19,240 +19,108 @@
 
 */
 
-// check input
-if ((empty($_POST['_name']) || empty($_POST['_email'])) && empty($_GET['_framed']))
-  {
-  show_message('formincomplete', 'warning');
+// cannot edit record
+if ($CONTACTS->readonly)
+{
+  $OUTPUT->show_message('contactreadonly', 'error');
   rcmail_overwrite_action(empty($_POST['_cid']) ? 'add' : 'show');
   return;
-  }
+}
+
+// check input
+if ((!get_input_value('_name', RCUBE_INPUT_POST) || !get_input_value('_email', RCUBE_INPUT_POST)) && $_framed)
+{
+  $OUTPUT->show_message('formincomplete', 'warning');
+  rcmail_overwrite_action(empty($_POST['_cid']) ? 'add' : 'show');
+  return;
+}
+
 
 // setup some vars we need
 $a_save_cols = array('name', 'firstname', 'surname', 'email');
-$contacts_table = get_table_name('contacts');
+$a_record = array();
+$cid = get_input_value('_cid', RCUBE_INPUT_POST);
+
+// read POST values into hash array
+foreach ($a_save_cols as $col)
+{
+  $fname = '_'.$col;
+  if (isset($_POST[$fname]))
+    $a_record[$col] = get_input_value($fname, RCUBE_INPUT_POST);
+}
 
 // update an existing contact
-if (!empty($_POST['_cid']))
+if (!empty($cid))
+{
+  if ($CONTACTS->update($cid, $a_record))
   {
-  $a_write_sql = array();
-
-  foreach ($a_save_cols as $col)
-    {
-    $fname = '_'.$col;
-    if (!isset($_POST[$fname]))
-      continue;
-    
-    $a_write_sql[] = sprintf("%s=%s",
-                             $DB->quoteIdentifier($col),
-                             $DB->quote(get_input_value($fname, RCUBE_INPUT_POST)));
-    }
-
-  if (sizeof($a_write_sql))
-    {
-    $DB->query("UPDATE $contacts_table
-                SET    changed=".$DB->now().", ".join(', ', $a_write_sql)."
-                WHERE  contact_id=?
-                AND    user_id=?
-                AND    del<>1",
-                $_POST['_cid'],
-                $_SESSION['user_id']);
-                       
-    $updated = $DB->affected_rows();
-    }
-       
-  if ($updated)
-    {
     if ($_framed)
-      {
+    {
       // define list of cols to be displayed
-      $a_show_cols = array('name', 'email');
       $a_js_cols = array();
-  
-      $sql_result = $DB->query("SELECT * FROM $contacts_table
-                                WHERE  contact_id=?
-                                AND    user_id=?
-                                AND    del<>1",
-                               $_POST['_cid'],
-                               $_SESSION['user_id']);
-                         
-      $sql_arr = $DB->fetch_assoc($sql_result);
-      foreach ($a_show_cols as $col)
-        $a_js_cols[] = (string)$sql_arr[$col];
+      $record = $CONTACTS->get_record($cid, true);
+
+      foreach (array('name', 'email') as $col)
+        $a_js_cols[] = (string)$record[$col];
 
       // update the changed col in list
-      $OUTPUT->add_script(sprintf("if(parent.%s)parent.%s.update_contact_row('%d', %s);",
-                          $JS_OBJECT_NAME,
-                          $JS_OBJECT_NAME,
-                          $_POST['_cid'],
-                          array2js($a_js_cols)));
-
-      }
+      $OUTPUT->command('parent.update_contact_row', $cid, $a_js_cols);
+    }
       
     // show confirmation
-    show_message('successfullysaved', 'confirmation');    
+    $OUTPUT->show_message('successfullysaved', 'confirmation');    
     rcmail_overwrite_action('show');
-    }
-  else
-    {
-    // show error message
-    show_message('errorsaving', 'error');
-    rcmail_overwrite_action('show');
-    }
   }
+  else
+  {
+    // show error message
+    $OUTPUT->show_message('errorsaving', 'error');
+    rcmail_overwrite_action('show');
+  }
+}
 
 // insert a new contact
 else
-  {
-  $a_insert_cols = $a_insert_values = array();
-
+{
   // check for existing contacts
-  $sql = "SELECT 1 FROM $contacts_table
-          WHERE  user_id = {$_SESSION['user_id']}
-          AND del <> '1' ";
-
-  // get email and name, build sql for existing user check
-  if (isset($_GET['_emails']) && isset($_GET['_names']))
-    {
-    $sql   .= "AND email IN (";
-    $emails = explode(',', get_input_value('_emails', RCUBE_INPUT_GET));
-    $names  = explode(',', get_input_value('_names', RCUBE_INPUT_GET));
-    $count  = count($emails);
-    $n = 0;
-    foreach ($emails as $email)
-      {
-      $end  = (++$n == $count) ? '' : ',';
-      $sql .= $DB->quote($email) . $end;
-      }
-    $sql .= ")";
-    $ldap_form = true; 
-    }
-  else if (isset($_POST['_email'])) 
-    $sql  .= "AND email = " . $DB->quote(get_input_value('_email', RCUBE_INPUT_POST));
-
-  $sql_result = $DB->query($sql);
-
+  $existing = $CONTACTS->search('email', $a_record['email'], false);
+  
   // show warning message
-  if ($DB->num_rows($sql_result))
-    {
-    show_message('contactexists', 'warning');
-
-    if ($ldap_form)
-      rcmail_overwrite_action('ldappublicsearch');
-    else
-      rcmail_overwrite_action('add');
-
+  if ($existing->count)
+  {
+    $OUTPUT->show_message('contactexists', 'warning');
+    rcmail_overwrite_action('add');
     return;
-    }
+  }
 
-  if ($ldap_form)
+  // insert record and send response
+  if ($insert_id = $CONTACTS->insert($a_record))
+  {
+    if ($_framed)
     {
-    $n = 0; 
-    foreach ($emails as $email) 
-      {
-      $DB->query("INSERT INTO $contacts_table 
-                 (user_id, name, email)
-                 VALUES ({$_SESSION['user_id']}," . $DB->quote($names[$n++]) . "," . 
-                                      $DB->quote($email) . ")");
-      $insert_id[] = $DB->insert_id();
-      }
-    }
-  else
-    {
-    foreach ($a_save_cols as $col)
-      {
-      $fname = '_'.$col;
-      if (!isset($_POST[$fname]))
-        continue;
-    
-      $a_insert_cols[] = $col;
-      $a_insert_values[] = $DB->quote(get_input_value($fname, RCUBE_INPUT_POST));
-      }
-    
-    if (sizeof($a_insert_cols))
-      {
-      $DB->query("INSERT INTO $contacts_table
-                  (user_id, changed, del, ".join(', ', $a_insert_cols).")
-                  VALUES (?, ".$DB->now().", 0, ".join(', ', $a_insert_values).")",
-                $_SESSION['user_id']);
-                       
-      $insert_id = $DB->insert_id(get_sequence_name('contacts'));
-      }
-    }
-    
-  if ($insert_id)
-    {
-    if (!$ldap_form)
-      {
-      if ($_framed)
-        {
-        // add contact row or jump to the page where it should appear
-        $commands = sprintf("if(parent.%s)parent.", $JS_OBJECT_NAME);
-        $sql_result = $DB->query("SELECT * FROM $contacts_table
-                                  WHERE  contact_id=?
-                                  AND    user_id=?",
-                                  $insert_id,
-                                  $_SESSION['user_id']);
-        $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
-
-        $commands .= sprintf("if(parent.%s)parent.%s.contact_list.select('%d');\n",
-                             $JS_OBJECT_NAME, 
-                             $JS_OBJECT_NAME,
-                             $insert_id);
-
-        // update record count display
-        $commands .= sprintf("if(parent.%s)parent.%s.set_rowcount('%s');\n",
-                             $JS_OBJECT_NAME, 
-                             $JS_OBJECT_NAME,
-                             rcmail_get_rowcount_text());
-
-        $OUTPUT->add_script($commands);
-        }
-
-      // show confirmation
-      show_message('successfullysaved', 'confirmation');
-      $_GET['_cid'] = $insert_id;
-      }
-    else 
-      {
       // add contact row or jump to the page where it should appear
-      $commands = '';
-      foreach ($insert_id as $id) 
-        {
-        $sql_result = $DB->query("SELECT * FROM $contacts_table
-                                  WHERE  contact_id = $id
-                                  AND    user_id    = {$_SESSION['user_id']}");
-        
-        $commands .= sprintf("if(parent.%s)parent.", $JS_OBJECT_NAME);
-        $commands .= rcmail_js_contacts_list($sql_result, $JS_OBJECT_NAME);
-        $last_id = $id;
-        }
+      $CONTACTS->reset();
+      $result = $CONTACTS->search($CONTACTS->primary_key, $insert_id);
 
-      // display the last insert id
-      $commands .= sprintf("if(parent.%s)parent.%s.contact_list.select('%d');\n",
-                            $JS_OBJECT_NAME, 
-                            $JS_OBJECT_NAME,
-                            $last_id);
+      rcmail_js_contacts_list($result, 'parent.');
+      $OUTPUT->command('parent.contact_list.select', $insert_id);
 
       // update record count display
-      $commands .= sprintf("if(parent.%s)parent.%s.set_rowcount('%s');\n",
-                           $JS_OBJECT_NAME, 
-                           $JS_OBJECT_NAME,
-                           rcmail_get_rowcount_text());
-
-      $OUTPUT->add_script($commands);
-      rcmail_overwrite_action('ldappublicsearch');
-      }
+      $CONTACTS->reset();
+      $OUTPUT->command('parent.set_rowcount', rcmail_get_rowcount_text());
+    }
 
     // show confirmation
-    show_message('successfullysaved', 'confirmation');      
+    $OUTPUT->show_message('successfullysaved', 'confirmation');
     rcmail_overwrite_action('show');
-    }
-  else
-    {
-    // show error message
-    show_message('errorsaving', 'error');
-    rcmail_overwrite_action('add');
-    }
+    $_GET['_cid'] = $insert_id;
   }
+  else
+  {
+    // show error message
+    $OUTPUT->show_message('errorsaving', 'error');
+    rcmail_overwrite_action('add');
+  }
+}
 
 ?>
diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc
new file mode 100644
index 0000000..01233a0
--- /dev/null
+++ b/program/steps/addressbook/search.inc
@@ -0,0 +1,53 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/steps/addressbook/search.inc                                  |
+ |                                                                       |
+ | This file is part of the RoundCube Webmail client                     |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
+ | Licensed under the GNU GPL                                            |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Search step for address book contacts                               |
+ |                                                                       |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ +-----------------------------------------------------------------------+
+
+ $Id: search.inc 456 2007-01-10 12:34:33Z thomasb $
+
+*/
+
+$CONTACTS->set_page(1);
+$_SESSION['page'] = 1;
+
+$search = trim(get_input_value('_q', RCUBE_INPUT_GET));
+$search_request = md5('addr'.$search);
+
+// get contacts for this user
+$result = $CONTACTS->search(array('name','email'), $search);
+
+if ($result->count > 0)
+{
+  // save search settings in session
+  $_SESSION['search'][$search_request] = $CONTACTS->get_search_set();
+  
+  // create javascript list
+  rcmail_js_contacts_list($result);
+}
+else
+{
+  $OUTPUT->show_message('nocontactsfound', 'warning');
+  $search_request = -1;
+}
+
+// update message count display
+$OUTPUT->set_env('search_request', $search_request);
+$OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
+$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text());
+  
+// send response
+$OUTPUT->send();
+
+?>
\ No newline at end of file
diff --git a/program/steps/addressbook/show.inc b/program/steps/addressbook/show.inc
index 960ea1c..75f1e74 100644
--- a/program/steps/addressbook/show.inc
+++ b/program/steps/addressbook/show.inc
@@ -5,7 +5,7 @@
  | program/steps/addressbook/show.inc                                    |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -20,35 +20,25 @@
 */
 
 
-if ($_GET['_cid'] || $_POST['_cid'])
-  {
-  $cid = $_POST['_cid'] ? $_POST['_cid'] : $_GET['_cid'];
-  $DB->query("SELECT * FROM ".get_table_name('contacts')."
-              WHERE  contact_id=?
-              AND    user_id=?
-              AND    del<>1",
-              $cid,
-              $_SESSION['user_id']);
-  
-  $CONTACT_RECORD = $DB->fetch_assoc();
-  
-  if (is_array($CONTACT_RECORD))
-    $OUTPUT->add_script(sprintf("%s.set_env('cid', '%s');", $JS_OBJECT_NAME, $CONTACT_RECORD['contact_id']));
-  }
-
+// read contact record
+if (($cid = get_input_value('_cid', RCUBE_INPUT_GPC)) && ($record = $CONTACTS->get_record($cid, true)))
+  $OUTPUT->set_env('cid', $record['ID']);
 
 
 function rcmail_contact_details($attrib)
   {
-  global $CONTACT_RECORD, $JS_OBJECT_NAME;
+  global $CONTACTS, $OUTPUT;
 
-  if (!$CONTACT_RECORD)
-    return show_message('contactnotfound');
+  // check if we have a valid result
+  if (!(($result = $CONTACTS->get_result()) && ($record = $result->first())))
+  {
+    $OUTPUT->show_message('contactnotfound');
+    return false;
+  }
   
   // a specific part is requested
   if ($attrib['part'])
-    return rep_specialchars_output($CONTACT_RECORD[$attrib['part']]);
-
+    return Q($record[$attrib['part']]);
 
   // return the complete address record as table
   $out = "<table>\n\n";
@@ -56,25 +46,28 @@
   $a_show_cols = array('name', 'firstname', 'surname', 'email');
   foreach ($a_show_cols as $col)
     {
-    if ($col=='email' && $CONTACT_RECORD[$col])
-      $value = sprintf('<a href="#compose" onclick="%s.command(\'compose\', %d)" title="%s">%s</a>',
-                       $JS_OBJECT_NAME,
-                       $CONTACT_RECORD['contact_id'],
-                       rcube_label('composeto'),
-                       $CONTACT_RECORD[$col]);
+    if ($col=='email' && !empty($record[$col]))
+      $value = sprintf(
+        '<a href="#compose" onclick="%s.command(\'compose\', \'%s\')" title="%s">%s</a>',
+        JS_OBJECT_NAME,
+        JQ($record[$col]),
+        rcube_label('composeto'),
+        Q($record[$col]));
     else
-      $value = rep_specialchars_output($CONTACT_RECORD[$col]);
+      $value = Q($record[$col]);
     
-    $title = rcube_label($col);
-    $out .= sprintf("<tr><td class=\"title\">%s</td><td>%s</td></tr>\n", $title, $value);
+    $out .= sprintf("<tr><td class=\"title\">%s</td><td>%s</td></tr>\n",
+                    Q(rcube_label($col)),
+                    $value);
     }
-
-
+  
   $out .= "\n</table>";
   
   return $out;  
   }
 
 
-parse_template('showcontact');
+//$OUTPUT->framed = $_framed;
+$OUTPUT->add_handler('contactdetails', 'rcmail_contact_details');
+$OUTPUT->send('showcontact');
 ?>
\ No newline at end of file
diff --git a/program/steps/error.inc b/program/steps/error.inc
index 2eb50eb..dd53306 100644
--- a/program/steps/error.inc
+++ b/program/steps/error.inc
@@ -64,7 +64,7 @@
   }
 
 // database connection error
-else if ($ERROR_CODE==0x01f4)
+else if ($ERROR_CODE==603)
   {
   $__error_title = "DATABASE ERROR: CONNECTION FAILED!";
   $__error_text  =  <<<EOF
diff --git a/program/steps/mail/addcontact.inc b/program/steps/mail/addcontact.inc
index b040581..484b0d4 100644
--- a/program/steps/mail/addcontact.inc
+++ b/program/steps/mail/addcontact.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/addcontact.inc                                     |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -19,49 +19,37 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
+require_once('include/rcube_contacts.inc');
 
-if (!empty($_GET['_address']))
+$done = false;
+
+if (!empty($_POST['_address']))
+{
+  $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']);
+  $contact_arr = $IMAP->decode_address_list(get_input_value('_address', RCUBE_INPUT_POST, true), 1, false);
+  
+  if (!empty($contact_arr[1]['mailto']))
   {
-  $contact_arr = $IMAP->decode_address_list(get_input_value('_address', RCUBE_INPUT_GET, TRUE));
-  if (sizeof($contact_arr))
-    {
-    $contact = $contact_arr[1];
+    $contact = array(
+      'email' => $contact_arr[1]['mailto'],
+      'name' => $contact_arr[1]['name']
+    );
+    
+    // use email address part for name
+    if (empty($contact['name']) || $contact['name'] == $contact['email'])
+      $contact['name'] = ucfirst(preg_replace('/[\.\-]/', ' ', substr($contact['email'], 0, strpos($contact['email'], '@'))));
 
-    if ($contact['mailto'])
-      $sql_result = $DB->query("SELECT 1 FROM ".get_table_name('contacts')."
-                                WHERE  user_id=?
-                                AND    email=?
-                                AND    del<>1",
-                                $_SESSION['user_id'],$contact['mailto']);
-
-    // contact entry with this mail address exists
-    if ($sql_result && $DB->num_rows($sql_result))
-      $existing_contact = TRUE;
-
-    else if ($contact['mailto'])
-      {
-      $DB->query("INSERT INTO ".get_table_name('contacts')."
-                  (user_id, changed, del, name, email)
-                  VALUES (?, ".$DB->now().", 0, ?, ?)",
-                  $_SESSION['user_id'],
-                  $contact['name'],
-                  $contact['mailto']);
-
-      $added = $DB->insert_id(get_sequence_name('contacts'));
-      }
-    }
-
-  if ($added)
-    $commands = show_message('addedsuccessfully', 'confirmation');
-  else if ($existing_contact)
-    $commands = show_message('contactexists', 'warning');
+    // check for existing contacts
+    $existing = $CONTACTS->search('email', $contact['email'], false);
+    if ($done = $existing->count)
+      $OUTPUT->show_message('contactexists', 'warning');
+    else if ($done = $CONTACTS->insert($contact))
+      $OUTPUT->show_message('addedsuccessfully', 'confirmation');
   }
+}
 
+if (!$done)
+  $OUTPUT->show_message('errorsavingcontact', 'warning');
 
-if (!$commands)
-  $commands = show_message('errorsavingcontact', 'warning');
-
-rcube_remote_response($commands);  
-exit;
+$OUTPUT->send();
 ?>
\ No newline at end of file
diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc
index ca35725..119d481 100644
--- a/program/steps/mail/check_recent.inc
+++ b/program/steps/mail/check_recent.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/check_recent.inc                                   |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -19,8 +19,6 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
-
 $a_mailboxes = $IMAP->list_mailboxes();
 
 foreach ($a_mailboxes as $mbox_name)
@@ -32,10 +30,10 @@
       $count = $IMAP->messagecount(NULL, 'ALL', TRUE);
       $unread_count = $IMAP->messagecount(NULL, 'UNSEEN', TRUE);
 
-      $commands .= sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_name), $unread_count);
-      $commands .= sprintf("this.set_env('messagecount', %d);\n", $count);
-      $commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text($count));
-      $commands .= sprintf("this.set_quota('%s');\n", $IMAP->get_quota());
+      $OUTPUT->set_env('messagecount', $count);
+      $OUTPUT->command('set_unread_count', $mbox_name, $unread_count);
+      $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text());
+      $OUTPUT->command('set_quota', $IMAP->get_quota());
 
       // add new message headers to list
       $a_headers = array();
@@ -46,15 +44,16 @@
           $a_headers[] = $header;
         }
 
-      $commands .= rcmail_js_message_list($a_headers, TRUE);
+      rcmail_js_message_list($a_headers, TRUE);
       }
     }
   else
     {
     if ($IMAP->messagecount($mbox_name, 'RECENT'))
-      $commands .= sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_name), $IMAP->messagecount($mbox_name, 'UNSEEN'));
+      $OUTPUT->command('set_unread_count', $mbox_name, $IMAP->messagecount($mbox_name, 'UNSEEN'));
     }
   }
 
-rcube_remote_response($commands);
+$OUTPUT->send();
+
 ?>
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 24057a2..4e73b4b 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -36,8 +36,8 @@
     {
     @unlink($_SESSION['compose']['attachments'][$id]['path']);
     $_SESSION['compose']['attachments'][$id] = NULL;
-    $commands = sprintf("parent.%s.remove_from_attachment_list('rcmfile%d');\n", $JS_OBJECT_NAME, $id);
-    rcube_remote_response($commands);  
+    $OUTPUT->command('remove_from_attachment_list', "rcmfile$id");
+    $OUTPUT->send();
     exit;
     }
   }
@@ -61,7 +61,7 @@
 rcube_add_label('nosubject', 'norecipientwarning', 'nosubjectwarning', 'nobodywarning', 'notsentwarning', 'savingmessage', 'sendingmessage', 'messagesaved', 'converting');
 
 // add config parameter to client script
-$OUTPUT->add_script(sprintf("%s.set_env('draft_autosave', %d);", $JS_OBJECT_NAME, !empty($CONFIG['drafts_mbox']) ? $CONFIG['draft_autosave'] : 0));
+$OUTPUT->set_env('draft_autosave', !empty($CONFIG['drafts_mbox']) ? $CONFIG['draft_autosave'] : 0);
 
 
 // get reference message and set compose mode
@@ -125,24 +125,10 @@
     case 'to':
       $fname = '_to';
       $header = 'to';
-
-      // we have contact id's as get parameters
-      if (!empty($_GET['_to']) && preg_match('/^[0-9]+(,[0-9]+)*$/', $_GET['_to']))
-        {
-        $a_recipients = array();
-        $sql_result = $DB->query("SELECT name, email
-                                  FROM ".get_table_name('contacts')."
-                                  WHERE user_id=?
-                                  AND    del<>1
-                                  AND    contact_id IN (".$_GET['_to'].")",
-                                  $_SESSION['user_id']);
-                                         
-        while ($sql_arr = $DB->fetch_assoc($sql_result))
-          $a_recipients[] = format_email_recipient($sql_arr['email'], $sql_arr['name']);
-          
-        if (sizeof($a_recipients))
-          $fvalue = join(', ', $a_recipients);
-        }
+      
+      // we have a set of recipients stored is session
+      if (($mailto_id = get_input_value('_mailto', RCUBE_INPUT_GET)) && $_SESSION['mailto'][$mailto_id])
+        $fvalue = $_SESSION['mailto'][$mailto_id];
       else if (!empty($_GET['_to']))
         $fvalue = get_input_value('_to', RCUBE_INPUT_GET);
         
@@ -246,7 +232,7 @@
 
 function rcmail_compose_header_from($attrib)
   {
-  global $IMAP, $MESSAGE, $DB, $OUTPUT, $JS_OBJECT_NAME, $compose_mode;
+  global $IMAP, $MESSAGE, $DB, $OUTPUT, $compose_mode;
     
   // pass the following attributes to the form class
   $field_attrib = array('name' => '_from');
@@ -291,7 +277,7 @@
     $from_id = 0;
     $a_signatures = array();
 
-    $field_attrib['onchange'] = "$JS_OBJECT_NAME.change_identity(this)";
+    $field_attrib['onchange'] = JS_OBJECT_NAME.".change_identity(this)";
     $select_from = new select($field_attrib);
 
     while ($sql_arr = $DB->fetch_assoc($sql_result))
@@ -330,7 +316,7 @@
     $out = $select_from->show($from_id);
 
     // add signatures to client
-    $OUTPUT->add_script(sprintf("%s.set_env('signatures', %s);", $JS_OBJECT_NAME, array2js($a_signatures)));  
+    $OUTPUT->set_env('signatures', $a_signatures);
     }
   else
     {
@@ -348,7 +334,7 @@
 
 function rcmail_compose_body($attrib)
   {
-  global $CONFIG, $OUTPUT, $MESSAGE, $JS_OBJECT_NAME, $compose_mode;
+  global $CONFIG, $OUTPUT, $MESSAGE, $compose_mode;
   
   list($form_start, $form_end) = get_form_tags($attrib);
   unset($attrib['form']);
@@ -470,7 +456,7 @@
       $lang_set,
       substr($_SESSION['user_lang'], 0, 2),
       $attrib['id'],
-      $JS_OBJECT_NAME), 'foot');
+      JS_OBJECT_NAME), 'foot');
 
     rcube_add_label('checking');
     }
@@ -666,7 +652,7 @@
 
 function rcmail_compose_attachment_list($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME, $CONFIG;
+  global $OUTPUT, $CONFIG;
   
   // add ID if not given
   if (!$attrib['id'])
@@ -690,14 +676,14 @@
     foreach ($_SESSION['compose']['attachments'] as $id => $a_prop)
       $out .= sprintf('<li id="rcmfile%d"><a href="#delete" onclick="return %s.command(\'remove-attachment\',\'rcmfile%d\', this)" title="%s">%s</a>%s</li>',
                       $id,
-                      $JS_OBJECT_NAME,
+                      JS_OBJECT_NAME,
                       $id,
                       Q(rcube_label('delete')),
                       $button,
                       Q($a_prop['name']));
     }
 
-  $OUTPUT->add_script(sprintf("%s.gui_object('attachmentlist', '%s');", $JS_OBJECT_NAME, $attrib['id']));  
+  $OUTPUT->add_gui_object('attachmentlist', $attrib['id']);
     
   $out .= '</ul>';
   return $out;
@@ -707,7 +693,7 @@
 
 function rcmail_compose_attachment_form($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME, $SESS_HIDDEN_FIELD;
+  global $OUTPUT, $SESS_HIDDEN_FIELD;
 
   // add ID if not given
   if (!$attrib['id'])
@@ -718,6 +704,7 @@
   $input_field = rcmail_compose_attachment_field(array());
   $label_send = rcube_label('upload');
   $label_close = rcube_label('close');
+  $js_instance = JS_OBJECT_NAME;
   
   $out = <<<EOF
 <div$attrib_str>
@@ -725,13 +712,13 @@
 $SESS_HIDDEN_FIELD
 $input_field<br />
 <input type="button" value="$label_close" class="button" onclick="document.getElementById('$attrib[id]').style.visibility='hidden'" />
-<input type="button" value="$label_send" class="button" onclick="$JS_OBJECT_NAME.command('send-attachment', this.form)" />
+<input type="button" value="$label_send" class="button" onclick="$js_instance.command('send-attachment', this.form)" />
 </form>
 </div>
 EOF;
 
   
-  $OUTPUT->add_script(sprintf("%s.gui_object('uploadbox', '%s');", $JS_OBJECT_NAME, $attrib['id']));  
+  $OUTPUT->add_gui_object('uploadbox', $attrib['id']);
   return $out;
   }
 
@@ -829,7 +816,7 @@
 
     $attrib['id'] = '_' . $value;
     $rb = new radiobutton($attrib);
-    $selector .= sprintf("<td>%s</td><td class=\"title\"><label for=\"%s\">%s</label></td>",
+    $selector .= sprintf("%s<label for=\"%s\">%s</label>",
                          $rb->show($value),
                          $attrib['id'],
                          rcube_label($text));
@@ -841,7 +828,7 @@
 
 function get_form_tags($attrib)
   {
-  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $MESSAGE_FORM, $SESS_HIDDEN_FIELD;  
+  global $CONFIG, $OUTPUT, $MESSAGE_FORM, $SESS_HIDDEN_FIELD;  
 
   $form_start = '';
   if (!strlen($MESSAGE_FORM))
@@ -858,7 +845,7 @@
   $form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
   
   if (!strlen($MESSAGE_FORM))
-    $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('messageform', '$form_name');");
+    $OUTPUT->add_gui_object('messageform', $form_name);
   
   $MESSAGE_FORM = $form_name;
 
@@ -866,38 +853,35 @@
   }
 
 
-function format_email_recipient($email, $name='')
-  {
-  if ($name && $name != $email)
-    return sprintf('%s <%s>', strpos($name, ",") ? '"'.$name.'"' : $name, $email);
-  else
-    return $email;
-  }
-
-
-function rcmail_charset_pulldown($selected='ISO-8859-1')
-  {
-  $select = new select();
-  
-  
-  return $select->show($selected);
-  }
+// register UI objects
+$OUTPUT->add_handlers(array(
+  'composeheaders' => 'rcmail_compose_headers',
+  'composesubject' => 'rcmail_compose_subject',
+  'composebody' => 'rcmail_compose_body',
+  'composeattachmentlist' => 'rcmail_compose_attachment_list',
+  'composeattachmentform' => 'rcmail_compose_attachment_form',
+  'composeattachment' => 'rcmail_compose_attachment_field',
+  'priorityselector' => 'rcmail_priority_selector',
+  'editorselector' => 'rcmail_editor_selector',
+  'receiptcheckbox' => 'rcmail_receipt_checkbox',
+));
 
 
 /****** get contacts for this user and add them to client scripts ********/
 
-$sql_result = $DB->query("SELECT name, email
-                          FROM ".get_table_name('contacts')." WHERE  user_id=?
-                          AND  del<>1",$_SESSION['user_id']);
+require_once('include/rcube_contacts.inc');
+
+$CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']);
+$CONTACTS->set_pagesize(1000);
                                    
-if ($DB->num_rows($sql_result))
+if ($result = $CONTACTS->list_records())
   {        
   $a_contacts = array();
-  while ($sql_arr = $DB->fetch_assoc($sql_result))
+  while ($sql_arr = $result->iterate())
     if ($sql_arr['email'])
       $a_contacts[] = format_email_recipient($sql_arr['email'], JQ($sql_arr['name']));
   
-  $OUTPUT->add_script(sprintf("$JS_OBJECT_NAME.set_env('contacts', %s);", array2js($a_contacts)));
+  $OUTPUT->set_env('contacts', $a_contacts);
   }
 
 
diff --git a/program/steps/mail/folders.inc b/program/steps/mail/folders.inc
index 1b7007c..a97057e 100644
--- a/program/steps/mail/folders.inc
+++ b/program/steps/mail/folders.inc
@@ -18,44 +18,40 @@
  $Id$
 */
 
-$REMOTE_REQUEST = TRUE;
 $mbox_name = $IMAP->get_mailbox_name();
-
 
 // send EXPUNGE command
 if ($_action=='expunge')
-  {
+{
   $success = $IMAP->expunge(get_input_value('_mbox', RCUBE_INPUT_GET));
 
   // reload message list if current mailbox  
   if ($success && !empty($_GET['_reload']))
-    {
-    rcube_remote_response('this.message_list.clear();', TRUE);
+  {
+    $OUTPUT->command('message_list.clear');
     $_action = 'list';
     return;
-    }
+  }
   else
     $commands = "// expunged: $success\n";
-  }
+}
 
 // clear mailbox
 else if ($_action=='purge')
-  {
+{
   $success = $IMAP->clear_mailbox(get_input_value('_mbox', RCUBE_INPUT_GET));
   
   if ($success && !empty($_GET['_reload']))
-    {
-    $commands = "this.message_list.clear();\n";
-    $commands .= "this.set_env('messagecount', 0);\n";
-    $commands .= "this.set_env('pagecount', 0);\n";
-    $commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text());
-    $commands .= sprintf("this.set_unread_count('%s', 0);\n", addslashes($mbox_name));
-    }
+  {
+    $OUTPUT->set_env('messagecount', 0);
+    $OUTPUT->set_env('pagecount', 0);
+    $OUTPUT->command('message_list.clear');
+    $OUTPUT->command('set_rowcount', rcmail_get_messagecount_text());
+    $OUTPUT->command('set_unread_count', $mbox_name, 0);
+  }
   else
     $commands = "// purged: $success";
-  }
+}
 
-
-
-rcube_remote_response($commands);
+$OUTPUT->send($commands);
 ?>
\ No newline at end of file
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index a0bdf09..7416fb5 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/func.inc                                           |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -25,9 +25,8 @@
 
 $EMAIL_ADDRESS_PATTERN = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i';
 
-if (empty($_SESSION['mbox'])){
+if (empty($_SESSION['mbox']))
   $_SESSION['mbox'] = $IMAP->get_mailbox_name();
-}
 
 // set imap properties and session vars
 if ($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC))
@@ -63,21 +62,24 @@
 
 
 // set current mailbox in client environment
-$OUTPUT->add_script(sprintf("%s.set_env('mailbox', '%s');", $JS_OBJECT_NAME, $IMAP->get_mailbox_name()));
+$OUTPUT->set_env('mailbox', $IMAP->get_mailbox_name());
+//$OUTPUT->set_pagetitle(rcube_charset_convert($IMAP->get_mailbox_name(), 'UTF-7', 'UTF-8'));
 
 if ($CONFIG['trash_mbox'])
-  $OUTPUT->add_script(sprintf("%s.set_env('trash_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['trash_mbox']));
-
+  $OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']);
 if ($CONFIG['drafts_mbox'])
-  $OUTPUT->add_script(sprintf("%s.set_env('drafts_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['drafts_mbox']));
-
+  $OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']);
 if ($CONFIG['junk_mbox'])
-  $OUTPUT->add_script(sprintf("%s.set_env('junk_mailbox', '%s');", $JS_OBJECT_NAME, $CONFIG['junk_mbox']));
+  $OUTPUT->set_env('junk_mailbox', $CONFIG['junk_mbox']);
+
+if (!$OUTPUT->ajax_call)
+  rcube_add_label('checkingmail');
+
 
 // return the mailboxlist in HTML
 function rcmail_mailbox_list($attrib)
   {
-  global $IMAP, $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $COMM_PATH;
+  global $IMAP, $CONFIG, $OUTPUT, $COMM_PATH;
   static $s_added_script = FALSE;
   static $a_mailboxes;
 
@@ -136,7 +138,7 @@
 
 
   if ($type=='ul')
-    $OUTPUT->add_script(sprintf("%s.gui_object('mailboxlist', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+    $OUTPUT->add_gui_object('mailboxlist', $attrib['id']);
 
   return $out . "</$type>";
   }
@@ -176,7 +178,7 @@
 // return html for a structured list <ul> for the mailbox tree
 function rcmail_render_folder_tree_html(&$arrFolders, &$special, &$mbox_name, $maxlength, $nestLevel=0)
   {
-  global $JS_OBJECT_NAME, $COMM_PATH, $IMAP, $CONFIG, $OUTPUT;
+  global $COMM_PATH, $IMAP, $CONFIG, $OUTPUT;
 
   $idx = 0;
   $out = '';
@@ -207,7 +209,8 @@
       $foldername .= sprintf(' (%d)', $unread_count);
 
     // make folder name safe for ids and class names
-    $folder_css = $class_name = preg_replace('/[^a-z0-9\-_]/', '', $folder_lc);
+    $folder_id = preg_replace('/[^A-Za-z0-9\-_]/', '', $folder['id']);
+    $class_name = preg_replace('/[^a-z0-9\-_]/', '', $folder_lc);
 
     // set special class for Sent, Drafts, Trash and Junk
     if ($folder['id']==$CONFIG['sent_mbox'])
@@ -220,25 +223,24 @@
       $class_name = 'junk';
 
     $js_name = htmlspecialchars(JQ($folder['id']));
-    $out .= sprintf('<li id="rcmbx%s" class="mailbox %s %s%s%s"><a href="%s&amp;_mbox=%s"'.
-                    ' onclick="return %s.command(\'list\',\'%s\')"'.
-                    ' onmouseover="return %s.focus_mailbox(\'%s\')"' .            
-                    ' onmouseout="return %s.unfocus_mailbox(\'%s\')"' .
-                    ' onmouseup="return %s.mbox_mouse_up(\'%s\')"%s>%s</a>',
-                    $folder_css,
+    $out .= sprintf('<li id="rcmli%s" class="mailbox %s %s%s%s"><a href="%s"'.
+                    ' onclick="return %s.command(\'list\',\'%s\',this)"'.
+                    ' onmouseover="return %s.focus_folder(\'%s\')"' .
+                    ' onmouseout="return %s.unfocus_folder(\'%s\')"' .
+                    ' onmouseup="return %s.folder_mouse_up(\'%s\')"%s>%s</a>',
+                    $folder_id,
                     $class_name,
                     $zebra_class,
                     $unread_count ? ' unread' : '',
                     $folder['id']==$mbox_name ? ' selected' : '',
-                    $COMM_PATH,
-                    urlencode($folder['id']),
-                    $JS_OBJECT_NAME,
+                    Q(rcmail_self_url('', array('_mbox' => $folder['id']))),
+                    JS_OBJECT_NAME,
                     $js_name,
-                    $JS_OBJECT_NAME,
+                    JS_OBJECT_NAME,
                     $js_name,
-                    $JS_OBJECT_NAME,
+                    JS_OBJECT_NAME,
                     $js_name,
-                    $JS_OBJECT_NAME,
+                    JS_OBJECT_NAME,
                     $js_name,
                     $title,
                     Q($foldername));
@@ -293,7 +295,7 @@
 // return the message list as HTML table
 function rcmail_message_list($attrib)
   {
-  global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT, $JS_OBJECT_NAME;
+  global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT;
 
   $skin_path = $CONFIG['skin_path'];
   $image_tag = '<img src="%s%s" alt="%s" border="0" />';
@@ -358,28 +360,30 @@
         // asc link
         if (!empty($attrib['sortascbutton']))
           {
-          $sort .= rcube_button(array('command' => 'sort',
-                                      'prop' => $col.'_ASC',
-                                      'image' => $attrib['sortascbutton'],
-                                      'align' => 'absmiddle',
-                                      'title' => 'sortasc'));
+          $sort .= $OUTPUT->button(array(
+            'command' => 'sort',
+            'prop' => $col.'_ASC',
+            'image' => $attrib['sortascbutton'],
+            'align' => 'absmiddle',
+            'title' => 'sortasc'));
           }       
         
         // desc link
         if (!empty($attrib['sortdescbutton']))
           {
-          $sort .= rcube_button(array('command' => 'sort',
-                                      'prop' => $col.'_DESC',
-                                      'image' => $attrib['sortdescbutton'],
-                                      'align' => 'absmiddle',
-                                      'title' => 'sortdesc'));        
+          $sort .= $OUTPUT->button(array(
+            'command' => 'sort',
+            'prop' => $col.'_DESC',
+            'image' => $attrib['sortdescbutton'],
+            'align' => 'absmiddle',
+            'title' => 'sortdesc'));
           }
         }
       // just add a link tag to the header
       else
         {
         $col_name = sprintf('<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>',
-                            $JS_OBJECT_NAME,
+                            JS_OBJECT_NAME,
                             $col,
                             rcube_label('sortby'),
                             $col_name);
@@ -430,7 +434,7 @@
     else if ($attrib['messageicon'])
       $message_icon = $attrib['messageicon'];
     
-	// set attachment icon
+    // set attachment icon
     if ($attrib['attachmenticon'] && preg_match("/multipart\/[mr]/i", $header->ctype))
       $attach_icon = $attrib['attachmenticon'];
         
@@ -441,7 +445,7 @@
                     $zebra_class);    
     
     $out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : '');
-        
+    
     // format each col
     foreach ($a_show_cols as $col)
       {
@@ -449,15 +453,13 @@
         $cont = Q(rcmail_address_string($header->$col, 3, $attrib['addicon']), 'show');
       else if ($col=='subject')
         {
-        $cont = Q($IMAP->decode_header($header->$col));
-        if (!$cont) $cont = Q(rcube_label('nosubject'));
-        // firefox/mozilla temporary workaround to pad subject with content so that whitespace in rows responds to drag+drop
-        $cont .= '<img src="./program/blank.gif" height="5" width="1000" alt="" />';
+        $cont = Q(rcube_imap::decode_mime_string($header->$col, $header->charset));
+        if (empty($cont)) $cont = Q(rcube_label('nosubject'));
         }
       else if ($col=='size')
         $cont = show_bytes($header->$col);
       else if ($col=='date')
-        $cont = format_date($header->date); //date('m.d.Y G:i:s', strtotime($header->date));
+        $cont = format_date($header->date);
       else
         $cont = Q($header->$col);
         
@@ -478,50 +480,46 @@
   $message_count = $IMAP->messagecount();
   
   // set client env
-  $javascript .= sprintf("%s.gui_object('mailcontframe', '%s');\n", $JS_OBJECT_NAME, 'mailcontframe');
-  $javascript .= sprintf("%s.gui_object('messagelist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
-  $javascript .= sprintf("%s.set_env('messagecount', %d);\n", $JS_OBJECT_NAME, $message_count);
-  $javascript .= sprintf("%s.set_env('current_page', %d);\n", $JS_OBJECT_NAME, $IMAP->list_page);
-  $javascript .= sprintf("%s.set_env('pagecount', %d);\n", $JS_OBJECT_NAME, ceil($message_count/$IMAP->page_size));
-  $javascript .= sprintf("%s.set_env('sort_col', '%s');\n", $JS_OBJECT_NAME, $sort_col);
-  $javascript .= sprintf("%s.set_env('sort_order', '%s');\n", $JS_OBJECT_NAME, $sort_order);
+  $OUTPUT->add_gui_object('mailcontframe', 'mailcontframe');
+  $OUTPUT->add_gui_object('messagelist', $attrib['id']);
+  $OUTPUT->set_env('messagecount', $message_count);
+  $OUTPUT->set_env('current_page', $IMAP->list_page);
+  $OUTPUT->set_env('pagecount', ceil($message_count/$IMAP->page_size));
+  $OUTPUT->set_env('sort_col', $sort_col);
+  $OUTPUT->set_env('sort_order', $sort_order);
   
   if ($attrib['messageicon'])
-    $javascript .= sprintf("%s.set_env('messageicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['messageicon']);
+    $OUTPUT->set_env('messageicon', $skin_path . $attrib['messageicon']);
   if ($attrib['deletedicon'])
-    $javascript .= sprintf("%s.set_env('deletedicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['deletedicon']);
+    $OUTPUT->set_env('deletedicon', $skin_path . $attrib['deletedicon']);
   if ($attrib['unreadicon'])
-    $javascript .= sprintf("%s.set_env('unreadicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['unreadicon']);
+    $OUTPUT->set_env('unreadicon', $skin_path . $attrib['unreadicon']);
   if ($attrib['repliedicon'])
-    $javascript .= sprintf("%s.set_env('repliedicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['repliedicon']);
+    $OUTPUT->set_env('repliedicon', $skin_path . $attrib['repliedicon']);
   if ($attrib['attachmenticon'])
-    $javascript .= sprintf("%s.set_env('attachmenticon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['attachmenticon']);
-    
-  $javascript .= sprintf("%s.set_env('messages', %s);", $JS_OBJECT_NAME, array2js($a_js_message_arr));
+    $OUTPUT->set_env('attachmenticon', $skin_path . $attrib['attachmenticon']);
   
-  $OUTPUT->add_script($javascript);  
+  $OUTPUT->set_env('messages', array2js($a_js_message_arr));
+  
   $OUTPUT->include_script('list.js');
   
   return $out;
   }
 
 
-
-
 // return javascript commands to add rows to the message list
 function rcmail_js_message_list($a_headers, $insert_top=FALSE)
   {
-  global $CONFIG, $IMAP;
+  global $CONFIG, $IMAP, $OUTPUT;
 
-  $commands = '';
   $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
 
   // show 'to' instead of from in sent messages
-  if (($IMAP->get_mailbox_name()==$CONFIG['sent_mbox'] || $IMAP->get_mailbox_name()==$CONFIG['drafts_mbox'])
-      && ($f = array_search('from', $a_show_cols)) && !array_search('to', $a_show_cols))
+  if (($IMAP->get_mailbox_name() == $CONFIG['sent_mbox'] || $IMAP->get_mailbox_name() == $CONFIG['drafts_mbox'])
+      && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
     $a_show_cols[$f] = 'to';
 
-  $commands .= sprintf("this.set_message_coltypes(%s);\n", array2js($a_show_cols)); 
+  $OUTPUT->command('set_message_coltypes', $a_show_cols);
 
   // loop through message headers
   for ($n=0; $a_headers[$n]; $n++)
@@ -529,7 +527,7 @@
     $header = $a_headers[$n];
     $a_msg_cols = array();
     $a_msg_flags = array();
-      
+
     // format each col; similar as in rcmail_message_list()
     foreach ($a_show_cols as $col)
       {
@@ -537,13 +535,13 @@
         $cont = Q(rcmail_address_string($header->$col, 3), 'show');
       else if ($col=='subject')
         {
-        $cont = Q($IMAP->decode_header($header->$col));
+        $cont = Q(rcube_imap::decode_mime_string($header->$col, $header->charset));
         if (!$cont) $cont = Q(rcube_label('nosubject'));
         }
       else if ($col=='size')
         $cont = show_bytes($header->$col);
       else if ($col=='date')
-        $cont = format_date($header->date); //date('m.d.Y G:i:s', strtotime($header->date));
+        $cont = format_date($header->date);
       else
         $cont = Q($header->$col);
           
@@ -553,22 +551,20 @@
     $a_msg_flags['deleted'] = $header->deleted ? 1 : 0;
     $a_msg_flags['unread'] = $header->seen ? 0 : 1;
     $a_msg_flags['replied'] = $header->answered ? 1 : 0;
-    $commands .= sprintf("this.add_message_row(%s, %s, %s, %b, %b);\n",
-                         $header->uid,
-                         array2js($a_msg_cols),
-                         array2js($a_msg_flags),
-                         preg_match("/multipart\/m/i", $header->ctype),
-                         $insert_top);
+    $OUTPUT->command('add_message_row',
+      $header->uid,
+      $a_msg_cols,
+      $a_msg_flags,
+      preg_match("/multipart\/m/i", $header->ctype),
+      $insert_top);
     }
-
-  return $commands;
   }
 
 
 // return an HTML iframe for loading mail content
 function rcmail_messagecontent_frame($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME;
+  global $OUTPUT;
   
   if (empty($attrib['id']))
     $attrib['id'] = 'rcmailcontentwindow';
@@ -581,52 +577,21 @@
          $framename,
          $attrib_str);
 
-  $OUTPUT->add_script("$JS_OBJECT_NAME.set_env('contentframe', '$framename');");
+  $OUTPUT->set_env('contentframe', $framename);
+  $OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/blank.gif');
 
   return $out;
   }
 
-// return code for search function
-function rcmail_search_form($attrib)
-  {
-  global $OUTPUT, $JS_OBJECT_NAME;
-
-  // add some labels to client
-  rcube_add_label('searching');
-
-  $attrib['name'] = '_q';
-  
-  if (empty($attrib['id']))
-    $attrib['id'] = 'rcmqsearchbox';
-  
-  $input_q = new textfield($attrib);
-  $out = $input_q->show();
-
-  $OUTPUT->add_script(sprintf("%s.gui_object('qsearchbox', '%s');",
-                              $JS_OBJECT_NAME,
-                              $attrib['id']));
-
-  // add form tag around text field
-  if (empty($attrib['form']))
-    $out = sprintf('<form name="rcmqsearchform" action="./" '.
-                   'onsubmit="%s.command(\'search\');return false" style="display:inline;">%s</form>',
-                   $JS_OBJECT_NAME,
-                   $out);
-
-  return $out;
-  } 
-
 
 function rcmail_messagecount_display($attrib)
   {
-  global $IMAP, $OUTPUT, $JS_OBJECT_NAME;
+  global $IMAP, $OUTPUT;
   
   if (!$attrib['id'])
     $attrib['id'] = 'rcmcountdisplay';
 
-  $OUTPUT->add_script(sprintf("%s.gui_object('countdisplay', '%s');",
-                              $JS_OBJECT_NAME,
-                              $attrib['id']));
+  $OUTPUT->add_gui_object('countdisplay', $attrib['id']);
 
   // allow the following attributes to be added to the <span> tag
   $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
@@ -641,12 +606,12 @@
 
 function rcmail_quota_display($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME, $COMM_PATH;
+  global $OUTPUT, $COMM_PATH;
 
   if (!$attrib['id'])
     $attrib['id'] = 'rcmquotadisplay';
 
-  $OUTPUT->add_script(sprintf("%s.gui_object('quotadisplay', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
 
   // allow the following attributes to be added to the <span> tag
   $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id'));
@@ -675,8 +640,7 @@
     if ($display == 'image' && function_exists('imagegif'))
       {
       $attrib = array('width' => 100, 'height' => 14);
-      $quota_text = sprintf('<img src="%s&amp;_action=quotaimg&amp;u=%s&amp;q=%d&amp;w=%d&amp;h=%d" width="%d" height="%d" alt="%s" title="%s / %s" />',
-                            $COMM_PATH,
+      $quota_text = sprintf('<img src="./bin/quotaimg.php?u=%s&amp;q=%d&amp;w=%d&amp;h=%d" width="%d" height="%d" alt="%s" title="%s / %s" />',
                             $quota['used'], $quota['total'],
                             $attrib['width'], $attrib['height'],
                             $attrib['width'], $attrib['height'],
@@ -723,7 +687,7 @@
 
 function rcmail_print_body($part, $safe=FALSE, $plain=FALSE)
   {
-  global $IMAP, $REMOTE_OBJECTS, $JS_OBJECT_NAME;
+  global $IMAP, $REMOTE_OBJECTS;
   
   $body = is_array($part->replaces) ? strtr($part->body, $part->replaces) : $part->body;
 
@@ -795,7 +759,7 @@
     $convert_replaces[] = "rcmail_str_replacement('\\1<a href=\"http://\\2\\3\" target=\"_blank\">\\2\\3</a>', \$replace_strings)";
     
     $convert_patterns[] = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/ie';
-    $convert_replaces[] = "rcmail_str_replacement('<a href=\"mailto:\\1\" onclick=\"return $JS_OBJECT_NAME.command(\'compose\',\'\\1\',this)\">\\1</a>', \$replace_strings)";
+    $convert_replaces[] = "rcmail_str_replacement('<a href=\"mailto:\\1\" onclick=\"return ".JS_OBJECT_NAME.".command(\'compose\',\'\\1\',this)\">\\1</a>', \$replace_strings)";
     
     if ($part->ctype_parameters['format'] != 'flowed')
       $body = wordwrap(trim($body), 80);
@@ -1052,7 +1016,7 @@
   // get associative array of headers object
   if (!$headers)
     $headers = is_object($MESSAGE['headers']) ? get_object_vars($MESSAGE['headers']) : $MESSAGE['headers'];
-    
+  
   $header_count = 0;
   
   // allow the following attributes to be added to the <table> tag
@@ -1072,7 +1036,7 @@
     else if (in_array($hkey, array('from', 'to', 'cc', 'bcc', 'reply-to')))
       $header_value = Q(rcmail_address_string($headers[$hkey], NULL, $attrib['addicon']), 'show');
     else
-      $header_value = Q($IMAP->decode_header($headers[$hkey]));
+      $header_value = Q(rcube_imap::decode_mime_string($headers[$hkey], $headers['charset']));
 
     $out .= "\n<tr>\n";
     $out .= '<td class="header-title">'.Q(rcube_label($hkey)).":&nbsp;</td>\n";
@@ -1089,7 +1053,7 @@
 
 function rcmail_message_body($attrib)
   {
-  global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $GET_URL, $REMOTE_OBJECTS, $JS_OBJECT_NAME;
+  global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $GET_URL, $REMOTE_OBJECTS;
   
   if (!is_array($MESSAGE['parts']) && !$MESSAGE['body'])
     return '';
@@ -1169,7 +1133,7 @@
   
   // tell client that there are blocked remote objects
   if ($REMOTE_OBJECTS && !$safe_mode)
-    $OUTPUT->add_script(sprintf("%s.set_env('blockedobjects', true);", $JS_OBJECT_NAME));
+    $OUTPUT->set_env('blockedobjects', true);
 
   $out .= "\n</div>";
   return $out;
@@ -1261,7 +1225,7 @@
 
   if (stristr((string)$attrib['href'], 'mailto:'))
     $attrib['onclick'] = sprintf("return %s.command('compose','%s',this)",
-                                 $GLOBALS['JS_OBJECT_NAME'],
+                                 JS_OBJECT_NAME,
                                  JQ(substr($attrib['href'], 7)));
   else if (!empty($attrib['href']) && $attrib['href']{0}!='#')
     $attrib['target'] = '_blank';
@@ -1383,7 +1347,7 @@
 // decode address string and re-format it as HTML links
 function rcmail_address_string($input, $max=NULL, $addicon=NULL)
   {
-  global $IMAP, $PRINT_MODE, $CONFIG, $OUTPUT, $JS_OBJECT_NAME, $EMAIL_ADDRESS_PATTERN;
+  global $IMAP, $PRINT_MODE, $CONFIG, $OUTPUT, $EMAIL_ADDRESS_PATTERN;
   
   $a_parts = $IMAP->decode_address_list($input);
 
@@ -1403,14 +1367,14 @@
       {
       $out .= sprintf('<a href="mailto:%s" onclick="return %s.command(\'compose\',\'%s\',this)" class="rcmContactAddress" title="%s">%s</a>',
                       Q($part['mailto']),
-                      $JS_OBJECT_NAME,
+                      JS_OBJECT_NAME,
                       JQ($part['mailto']),
                       Q($part['mailto']),
                       Q($part['name']));
                       
       if ($addicon)
         $out .= sprintf('&nbsp;<a href="#add" onclick="return %s.command(\'add-contact\',\'%s\',this)" title="%s"><img src="%s%s" alt="add" border="0" /></a>',
-                        $JS_OBJECT_NAME,
+                        JS_OBJECT_NAME,
                         urlencode($part['string']),
                         rcube_label('addtoaddressbook'),
                         $CONFIG['skin_path'],
@@ -1500,6 +1464,20 @@
   
   unset($_SESSION['compose']);
   }
-  
-  
+
+
+// register UI objects
+$OUTPUT->add_handlers(array(
+  'mailboxlist' => 'rcmail_mailbox_list',
+  'messages' => 'rcmail_message_list',
+  'messagecountdisplay' => 'rcmail_messagecount_display',
+  'quotadisplay' => 'rcmail_quota_display',
+  'messageheaders' => 'rcmail_message_headers',
+  'messagebody' => 'rcmail_message_body',
+  'messagecontentframe' => 'rcmail_messagecontent_frame',
+  'messagepartframe' => 'rcmail_message_part_frame',
+  'messagepartcontrols' => 'rcmail_message_part_controls',
+  'searchform' => 'rcmail_search_form'
+));
+
 ?>
\ No newline at end of file
diff --git a/program/steps/mail/getunread.inc b/program/steps/mail/getunread.inc
index ed4a5fb..e6b3843 100644
--- a/program/steps/mail/getunread.inc
+++ b/program/steps/mail/getunread.inc
@@ -19,18 +19,13 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
-
 $a_folders = $IMAP->list_mailboxes();
 
 if (!empty($a_folders))
-  {
+{
   foreach ($a_folders as $mbox_row)
-    {
-    $commands = sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_row), $IMAP->messagecount($mbox_row, 'UNSEEN'));
-    rcube_remote_response($commands, TRUE);
-    }
-  }
+    $OUTPUT->command('set_unread_count', $mbox_row, $IMAP->messagecount($mbox_row, 'UNSEEN'));
+}
 
-exit;
+$OUTPUT->send();
 ?>
diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc
index 6e06374..162624c 100644
--- a/program/steps/mail/list.inc
+++ b/program/steps/mail/list.inc
@@ -19,25 +19,23 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
 $OUTPUT_TYPE = 'js';
-
 // is there a sort type for this request?
 if ($sort = get_input_value('_sort', RCUBE_INPUT_GET))
-  {
+{
   // yes, so set the sort vars
   list($sort_col, $sort_order) = explode('_', $sort);
 
   // set session vars for sort (so next page and task switch know how to sort)
   $_SESSION['sort_col'] = $sort_col;
   $_SESSION['sort_order'] = $sort_order;
-  }
+}
 else
-  {
+{
   // use session settings if set, defaults if not
   $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col'];
   $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];
-  }
+}
 
 
 // fetch message headers
@@ -48,22 +46,20 @@
 
 // update message count display
 $pages = ceil($count/$IMAP->page_size);
-$commands = sprintf("this.set_env('messagecount', %d);\n", $count);
-$commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
-$commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text($count));
+$OUTPUT->set_env('messagecount', $count);
+$OUTPUT->set_env('pagecount', $pages);
+$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count));
 
 // update mailboxlist
-$mbox_name = $IMAP->get_mailbox_name();
-$commands .= sprintf("this.set_unread_count('%s', %d);\n", addslashes($mbox_name), $unseen);
+$OUTPUT->command('set_unread_count', $IMAP->get_mailbox_name(), $unseen);
 
 
 // add message rows
 if (isset($a_headers) && count($a_headers))
-  $commands .= rcmail_js_message_list($a_headers);
+  rcmail_js_message_list($a_headers);
 
   
 // send response
-rcube_remote_response($commands);
+$OUTPUT->send();
 
-exit;
 ?>
\ No newline at end of file
diff --git a/program/steps/mail/mark.inc b/program/steps/mail/mark.inc
index 780bf5c..0dd781e 100644
--- a/program/steps/mail/mark.inc
+++ b/program/steps/mail/mark.inc
@@ -4,7 +4,7 @@
  | program/steps/mail/mark.inc                                           |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -18,24 +18,24 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
-
-$a_flags_map = array('undelete' => 'UNDELETED',
-                     'delete' => 'DELETED',
-                     'read' => 'SEEN',
-                     'unread' => 'UNSEEN');
+$a_flags_map = array(
+  'undelete' => 'UNDELETED',
+  'delete' => 'DELETED',
+  'read' => 'SEEN',
+  'unread' => 'UNSEEN');
 
 if (($uids = get_input_value('_uid', RCUBE_INPUT_GET)) && ($flag = get_input_value('_flag', RCUBE_INPUT_GET)))
-  {
+{
   $flag = $a_flags_map[$flag] ? $a_flags_map[$flag] : strtoupper($flag);
   $marked = $IMAP->set_flag($uids, $flag);
+
   if ($marked != -1)
-    {
+  {
     $mbox_name = $IMAP->get_mailbox_name();
-    $commands = sprintf("this.set_unread_count('%s', %d);\n", $mbox_name, $IMAP->messagecount($mbox_name, 'UNSEEN'));
-    rcube_remote_response($commands);
-    }
+    $OUTPUT->command('set_unread_count', $mbox_name, $IMAP->messagecount($mbox_name, 'UNSEEN'));
+    $OUTPUT->send();
   }
+}
   
 exit;
 ?>
\ No newline at end of file
diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc
index 8d31e3a..fb8a0af 100644
--- a/program/steps/mail/move_del.inc
+++ b/program/steps/mail/move_del.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/move_del.inc                                       |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -19,46 +19,42 @@
 
 */
 
-$REMOTE_REQUEST = TRUE;
-
 // move messages
 if ($_action=='moveto' && !empty($_GET['_uid']) && !empty($_GET['_target_mbox']))
-  {
+{
   $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_GET))));
   $target = get_input_value('_target_mbox', RCUBE_INPUT_GET);
   $moved = $IMAP->move_message($uids, $target, get_input_value('_mbox', RCUBE_INPUT_GET));
   
   if (!$moved)
-    {
+  {
     // send error message
-    $commands = "this.list_mailbox();\n";
-    $commands .= show_message('errormoving', 'error');
-    rcube_remote_response($commands);
+    $OUTPUT->command('list_mailbox');
+    $OUTPUT->show_message('errormoving', 'error');
+    $OUTPUT->send();
     exit;
-    }
   }
+}
 
 // delete messages 
 else if ($_action=='delete' && !empty($_GET['_uid']))
-  {
+{
   $count = sizeof(explode(',', ($uids = get_input_value('_uid', RCUBE_INPUT_GET))));
   $del = $IMAP->delete_message($uids, get_input_value('_mbox', RCUBE_INPUT_GET));
   
   if (!$del)
-    {
+  {
     // send error message
-    $commands = "this.list_mailbox();\n";
-    $commands .= show_message('errordeleting', 'error');
-    rcube_remote_response($commands);
+    $OUTPUT->command('list_mailbox');
+    $OUTPUT->show_message('errordeleting', 'error');
+    $OUTPUT->send();
     exit;
-    }
   }
+}
   
 // unknown action or missing query param
 else
-  {
   exit;
-  }
 
 // refresh saved seach set after moving some messages
 if (($search_request = get_input_value('_search', RCUBE_INPUT_GPC)) && $IMAP->search_set)
@@ -68,34 +64,33 @@
 // update message count display
 $msg_count = $IMAP->messagecount();
 $pages = ceil($msg_count / $IMAP->page_size);
-$commands = sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text($msg_count));
-$commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
+$OUTPUT->set_env('pagecount', $pages);
+$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($msg_count));
 
 
 // update mailboxlist
 $mbox = $IMAP->get_mailbox_name();
-$commands .= sprintf("this.set_unread_count('%s', %d);\n", $mbox, $IMAP->messagecount($mbox, 'UNSEEN'));
+$OUTPUT->command('set_unread_count', $mbox, $IMAP->messagecount($mbox, 'UNSEEN'));
 
 if ($_action=='moveto' && $target)
-  $commands .= sprintf("this.set_unread_count('%s', %d);\n", $target, $IMAP->messagecount($target, 'UNSEEN'));
+  $OUTPUT->command('set_unread_count', $target, $IMAP->messagecount($target, 'UNSEEN'));
 
-$commands .= sprintf("this.set_quota('%s');\n", $IMAP->get_quota()); 
+$OUTPUT->command('set_quota', $IMAP->get_quota());
 
 // add new rows from next page (if any)
 if ($_GET['_from']!='show' && $pages>1 && $IMAP->list_page < $pages)
-  {
+{
   $sort_col   = isset($_SESSION['sort_col'])   ? $_SESSION['sort_col']   : $CONFIG['message_sort_col'];
   $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order'];
   
   $a_headers = $IMAP->list_headers($mbox, NULL, $sort_col, $sort_order);
   $a_headers = array_slice($a_headers, -$count, $count);
 
-  $commands .= rcmail_js_message_list($a_headers);
-  }
+  rcmail_js_message_list($a_headers);
+}
 
 
 // send response
-rcube_remote_response($commands);
+$OUTPUT->send();
 
-exit;
 ?>
diff --git a/program/steps/mail/quotadisplay.inc b/program/steps/mail/quotadisplay.inc
index c96a7a7..accecdb 100644
--- a/program/steps/mail/quotadisplay.inc
+++ b/program/steps/mail/quotadisplay.inc
@@ -22,8 +22,7 @@
 $display = isset($_GET['_display']) ? $_GET['_display'] : 'text';
 $id = isset($_GET['_id']) ? $_GET['_id'] : 'rcmquotadisplay';
 $quota = rcmail_quota_content($display);
-$command = sprintf("this.gui_objects.%s.innerHTML = '%s';\n", $id, $quota);
-rcube_remote_response($command);
+$OUTPUT->remote_response(sprintf("this.gui_objects.%s.innerHTML = '%s';\n", $id, $quota));
 
 exit;
 ?>
diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc
index c4b843b..bef821b 100644
--- a/program/steps/mail/search.inc
+++ b/program/steps/mail/search.inc
@@ -25,7 +25,7 @@
 $imap_charset = 'UTF-8';
 
 // get search string
-$str = get_input_value('_search', RCUBE_INPUT_GET);
+$str = get_input_value('_q', RCUBE_INPUT_GET);
 $mbox = get_input_value('_mbox', RCUBE_INPUT_GET);
 $search_request = md5($mbox.$str);
 
@@ -71,16 +71,14 @@
 
 // execute IMAP search
 $result = $IMAP->search($mbox, $subject, $search, $imap_charset);
-
-$commands = '';
 $count = 0;
   
 // Make sure our $result is legit..
 if (is_array($result) && $result[0] != '')
-  {
+{
   // Get the headers
   $result_h = $IMAP->list_header_set($mbox, $result, 1, $_SESSION['sort_col'], $_SESSION['sort_order']);
-  $count = count($result);
+  $count = count($result_h);
 
   // save search results in session
   if (!is_array($_SESSION['search']))
@@ -88,24 +86,24 @@
 
   // Make sure we got the headers
   if ($result_h != NULL)
-    {
-    $_SESSION['search'][$search_request] = $IMAP->get_search_set();
-    $commands = rcmail_js_message_list($result_h);
-    $commands .= show_message('searchsuccessful', 'confirmation', array('nr' => $count));
-    }
-  }
-else
   {
-  $commands = show_message('searchnomatch', 'warning');
-  $search_request = -1;
+    $_SESSION['search'][$search_request] = $IMAP->get_search_set();
+    rcmail_js_message_list($result_h);
+    $OUTPUT->show_message('searchsuccessful', 'confirmation', array('nr' => $count));
   }
+}
+else
+{
+  $OUTPUT->show_message('searchnomatch', 'warning');
+  $search_request = -1;
+}
 
 // update message count display
 $pages = ceil($count/$IMAP->page_size);
-$commands .= sprintf("\nthis.set_env('search_request', '%s')\n", $search_request);
-$commands .= sprintf("this.set_env('messagecount', %d);\n", $count);
-$commands .= sprintf("this.set_env('pagecount', %d);\n", $pages);
-$commands .= sprintf("this.set_rowcount('%s');\n", rcmail_get_messagecount_text($count, 1));
-rcube_remote_response($commands);
+$OUTPUT->set_env('search_request', $search_request);
+$OUTPUT->set_env('messagecount', $count);
+$OUTPUT->set_env('pagecount', $pages);
+$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count, 1));
+$OUTPUT->send();
 
 ?>
\ No newline at end of file
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 4a65ee3..bc454b3 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/sendmail.inc                                       |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -39,7 +39,7 @@
 
 function rcmail_get_identity($id)
   {
-  global $DB, $CHARSET, $OUTPUT;
+  global $DB, $OUTPUT;
   
   // get identity record
   $sql_result = $DB->query("SELECT *, email AS mailto
@@ -55,7 +55,7 @@
     $out = $sql_arr;
     $name = strpos($sql_arr['name'], ",") ? '"'.$sql_arr['name'].'"' : $sql_arr['name'];
     $out['string'] = sprintf('%s <%s>',
-                             rcube_charset_convert($name, $CHARSET, $OUTPUT->get_charset()),
+                             rcube_charset_convert($name, RCMAIL_CHARSET, $OUTPUT->get_charset()),
                              $sql_arr['mailto']);
     return $out;
     }
@@ -110,9 +110,8 @@
       // add the image to the MIME message
       $img_file = $INSTALL_PATH . '/' . $searchstr . $image_name;
       if(! $mime_message->addHTMLImage($img_file, 'image/gif', '', true, '_' . $image_name))
-        {
-        show_message("emoticonerror", 'error');
-        }
+        $OUTPUT->show_message("emoticonerror", 'error');
+
       array_push($included_images, $image_name);
       }
 
@@ -132,7 +131,7 @@
 
 // remove all scripts and act as called in frame
 $OUTPUT->reset();
-$_framed = TRUE;
+$OUTPUT->framed = TRUE;
 
 
 /****** check submission and compose message ********/
@@ -140,8 +139,8 @@
 
 if (!$savedraft && empty($_POST['_to']) && empty($_POST['_subject']) && $_POST['_message'])
   {
-  show_message("sendingfailed", 'error'); 
-  rcube_iframe_response();
+  $OUTPUT->show_message("sendingfailed", 'error');
+  $OUTPUT->send('iframe');
   return;
   }
 
@@ -168,7 +167,7 @@
 
 // compose headers array
 $headers = array('Date' => date('D, j M Y H:i:s O'),
-                 'From' => rcube_charset_convert($identity_arr['string'], $CHARSET, $message_charset),
+                 'From' => rcube_charset_convert($identity_arr['string'], RCMAIL_CHARSET, $message_charset),
                  'To'   => $mailto);
 
 // additional recipients
@@ -304,7 +303,7 @@
 {
   mb_internal_encoding($message_charset);
   $mb_subject = mb_encode_mimeheader($headers['Subject'], $message_charset, 'Q');
-  mb_internal_encoding($CHARSET);
+  mb_internal_encoding(RCMAIL_CHARSET);
 }
 
 // Begin SMTP Delivery Block 
@@ -334,13 +333,8 @@
   
     // log error
     if (!$sent)
-      {
-      raise_error(array('code' => 800,
-                        'type' => 'smtp',
-                        'line' => __LINE__,
-                        'file' => __FILE__,
+      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
@@ -368,8 +362,8 @@
   // return to compose page if sending failed
   if (!$sent)
     {
-    show_message("sendingfailed", 'error'); 
-    rcube_iframe_response();
+    $OUTPUT->show_message("sendingfailed", 'error'); 
+    $OUTPUT->send('iframe');
     return;
     }
   
@@ -409,13 +403,11 @@
   // raise error if saving failed
   if (!$saved)
     {
-    raise_error(array('code' => 800,
-                      'type' => 'imap',
-                      'file' => __FILE__,
+    raise_error(array('code' => 800, 'type' => 'imap', 'file' => __FILE__,
                       'message' => "Could not save message in $CONFIG[$store_target]"), TRUE, FALSE);
     
-    show_message('errorsaving', 'error');
-    rcube_iframe_response($errorout);
+    $OUTPUT->show_message('errorsaving', 'error');
+    $OUTPUT->send('iframe');
     }
 
   if ($olddraftmessageid)
@@ -426,41 +418,36 @@
 
     // raise error if deletion of old draft failed
     if (!$deleted)
-      raise_error(array('code' => 800,
-                        'type' => 'imap',
-                        'file' => __FILE__,
+      raise_error(array('code' => 800, 'type' => 'imap', 'file' => __FILE__,
                         'message' => "Could not delete message from ".$CONFIG['drafts_mbox']), TRUE, FALSE);
     }
   }
 
 if ($savedraft)
   {
-  // clear the "saving message" busy status, and display success
-  show_message('messagesaved', 'confirmation');
+  // display success
+  $OUTPUT->show_message('messagesaved', 'confirmation');
 
-  // update "_draft_saveid" on the page, which is used to delete a previous draft
-  $frameout = "var foundid = parent.rcube_find_object('_draft_saveid', parent.document);\n";
-  $frameout .= sprintf("foundid.value = '%s';\n", str_replace(array('<','>'), "", $message_id));
-
-  // update the "cmp_hash" to prevent "Unsaved changes" warning
-  $frameout .= sprintf("parent.%s.cmp_hash = parent.%s.compose_field_hash();\n", $JS_OBJECT_NAME, $JS_OBJECT_NAME);
+  // update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
+  $OUTPUT->command('set_draft_id', str_replace(array('<','>'), "", $message_id));
+  $OUTPUT->command('compose_field_hash', true);
 
   // start the auto-save timer again
-  $frameout .= sprintf("parent.%s.auto_save_start();", $JS_OBJECT_NAME);
+  $OUTPUT->command('auto_save_start');
 
-  // send html page with JS calls as response
-  rcube_iframe_response($frameout);
+  $OUTPUT->send('iframe');
   }
 else
   {
   if ($CONFIG['smtp_log'])
     {
-    $log_entry = sprintf("[%s] User: %d on %s; Message for %s; %s\n",
-                 date("d-M-Y H:i:s O", mktime()),
-                 $_SESSION['user_id'],
-                 $_SERVER['REMOTE_ADDR'],
-                 $mailto,
-                 !empty($smtp_response) ? join('; ', $smtp_response) : '');
+    $log_entry = sprintf(
+      "[%s] User: %d on %s; Message for %s; %s\n",
+      date("d-M-Y H:i:s O", mktime()),
+      $_SESSION['user_id'],
+      $_SERVER['REMOTE_ADDR'],
+      $mailto,
+      !empty($smtp_response) ? join('; ', $smtp_response) : '');
 
     if ($fp = @fopen($CONFIG['log_dir'].'/sendmail', 'a'))
       {
@@ -470,8 +457,8 @@
     }
 
   rcmail_compose_cleanup();
-  rcube_iframe_response(sprintf("parent.$JS_OBJECT_NAME.sent_successfully('%s');",
-                                JQ(rcube_label('messagesent'))));
+  $OUTPUT->command('sent_successfully', rcube_label('messagesent'));
+  $OUTPUT->send('iframe');
   }
 
 
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 19e6e56..6c247ae 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -5,7 +5,7 @@
  | program/steps/mail/show.inc                                           |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -32,7 +32,7 @@
   // go back to list if message not found (wrong UID)
   if (!$MESSAGE['headers'])
     {
-    show_message('messageopenerror', 'error');
+    $OUTPUT->show_message('messageopenerror', 'error');
     if ($_action=='preview' && template_exists('messagepreview'))
         parse_template('messagepreview');
     else
@@ -51,8 +51,8 @@
   else
     send_modified_header($_SESSION['login_time'], $etag);
 
-
-  $MESSAGE['subject'] = $IMAP->decode_header($MESSAGE['headers']->subject);
+  $MESSAGE['subject'] = rcube_imap::decode_mime_string($MESSAGE['headers']->subject, $MESSAGE['headers']->charset);
+  $OUTPUT->set_pagetitle($MESSAGE['subject']);
   
   if ($MESSAGE['structure'] = $IMAP->get_structure($MESSAGE['UID']))
     list($MESSAGE['parts'], $MESSAGE['attachments']) = rcmail_parse_message(
@@ -70,8 +70,8 @@
     $IMAP->set_flag($MESSAGE['UID'], 'SEEN');
 
   // give message uid to the client
-  $javascript = sprintf("%s.set_env('uid', '%s');\n", $JS_OBJECT_NAME, $MESSAGE['UID']);
-  $javascript .= sprintf("%s.set_env('safemode', '%b');", $JS_OBJECT_NAME, $_GET['_safe']);
+  $OUTPUT->set_env('uid', $MESSAGE['UID']);
+  $OUTPUT->set_env('safemode', (bool)$_GET['_safe']);
 
   $next = $prev = -1;
   // get previous, first, next and last message UID
@@ -99,22 +99,20 @@
     }
   
   if ($prev > 0)
-    $javascript .= sprintf("\n%s.set_env('prev_uid', '%s');", $JS_OBJECT_NAME, $prev);
+    $OUTPUT->set_env('prev_uid', $prev);
   if ($first >0)
-    $javascript .= sprintf("\n%s.set_env('first_uid', '%s');", $JS_OBJECT_NAME, $first);
+    $OUTPUT->set_env('first_uid', $first);
   if ($next > 0)
-    $javascript .= sprintf("\n%s.set_env('next_uid', '%s');", $JS_OBJECT_NAME, $next);
+    $OUTPUT->set_env('next_uid', $next);
   if ($last >0)
-    $javascript .= sprintf("\n%s.set_env('last_uid', '%s');", $JS_OBJECT_NAME, $last);
-
-  $OUTPUT->add_script($javascript);
+    $OUTPUT->set_env('last_uid', $last);
   }
 
 
 
 function rcmail_message_attachments($attrib)
   {
-  global $CONFIG, $OUTPUT, $PRINT_MODE, $MESSAGE, $GET_URL, $JS_OBJECT_NAME;
+  global $CONFIG, $OUTPUT, $PRINT_MODE, $MESSAGE, $GET_URL;
 
   if (sizeof($MESSAGE['attachments']))
     {
@@ -132,7 +130,7 @@
         $out .= sprintf('<li><a href="%s&amp;_part=%s" onclick="return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)">%s</a></li>'."\n",
                         htmlspecialchars($GET_URL),
                         $attach_prop->mime_id,
-                        $JS_OBJECT_NAME,
+                        JS_OBJECT_NAME,
                         $attach_prop->mime_id,
                         $attach_prop->mimetype,
                         $attach_prop->filename);
@@ -147,7 +145,7 @@
 
 function rcmail_remote_objects_msg($attrib)
   {
-  global $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+  global $CONFIG, $OUTPUT;
   
   if (!$attrib['id'])
     $attrib['id'] = 'rcmremoteobjmsg';
@@ -158,16 +156,21 @@
   
   $out .= sprintf('%s&nbsp;<a href="#loadimages" onclick="%s.command(\'load-images\')">%s</a>',
                   Q(rcube_label('blockedimages')),
-                  $JS_OBJECT_NAME,
+                  JS_OBJECT_NAME,
                   Q(rcube_label('showimages')));
   
   $out .= '</div>';
   
-  $OUTPUT->add_script(sprintf("%s.gui_object('remoteobjectsmsg', '%s');", $JS_OBJECT_NAME, $attrib['id']));
+  $OUTPUT->add_gui_object('remoteobjectsmsg', $attrib['id']);
   return $out;
   }
 
 
+$OUTPUT->add_handlers(array(
+  'messageattachments' => 'rcmail_message_attachments',
+  'blockedobjects' => 'rcmail_remote_objects_msg'));
+
+
 if ($_action=='print' && template_exists('printmessage'))
   parse_template('printmessage');
 else if ($_action=='preview' && template_exists('messagepreview'))
diff --git a/program/steps/mail/upload.inc b/program/steps/mail/upload.inc
index 06ed265..a28c3e8 100644
--- a/program/steps/mail/upload.inc
+++ b/program/steps/mail/upload.inc
@@ -47,23 +47,22 @@
                                                   'path' => $tmpfname);
 
     if (is_file($CONFIG['skin_path'] . '/images/icons/remove-attachment.png'))
-      $button = sprintf('<img src="%s/images/icons/remove-attachment.png" alt="%s" border="0" style="padding-right:2px;vertical-align:middle" />',
-                        $CONFIG['skin_path'],
-                        Q(rcube_label('delete')));
+      $button = sprintf(
+        '<img src="%s/images/icons/remove-attachment.png" alt="%s" border="0" style="padding-right:2px;vertical-align:middle" />',
+        $CONFIG['skin_path'],
+        Q(rcube_label('delete')));
     else
       $button = Q(rcube_label('delete'));
 
-    $content = sprintf('<a href="#delete" onclick="return %s.command(\\\'remove-attachment\\\', \\\'rcmfile%d\\\', this)" title="%s">%s</a>%s',
-                       $JS_OBJECT_NAME,
-                       $id,
-                       Q(rcube_label('delete')),
-                       $button,
-                       Q($_FILES['_attachments']['name'][$i]));
+    $content = sprintf(
+      '<a href="#delete" onclick="return %s.command(\'remove-attachment\', \'rcmfile%d\', this)" title="%s">%s</a>%s',
+      JS_OBJECT_NAME,
+      $id,
+      Q(rcube_label('delete')),
+      $button,
+      Q($_FILES['_attachments']['name'][$i]));
 
-    $response .= sprintf('parent.%s.add2attachment_list(\'rcmfile%d\',\'%s\');',
-                         $JS_OBJECT_NAME,
-                         $id,
-                         $content);
+    $OUTPUT->command('add2attachment_list', "rcmfile$id", $content);
     }
   else // upload failed
     {
@@ -73,18 +72,14 @@
     else
       $msg = rcube_label('fileuploaderror');
     
-    $response = sprintf("parent.%s.display_message('%s', 'error');", $JS_OBJECT_NAME, JQ($msg));
+    $OUTPUT->command('display_message', $msg, 'error');
     }
   }
 
 
 // send html page with JS calls as response
-$frameout = <<<EOF
-$response
-parent.$JS_OBJECT_NAME.show_attachment_form(false);
-parent.$JS_OBJECT_NAME.auto_save_start();
-EOF;
-
-rcube_iframe_response($frameout);
+$OUTPUT->command('show_attachment_form', false);
+$OUTPUT->command('auto_save_start', false);
+$OUTPUT->send('iframe');
 
 ?>
diff --git a/program/steps/settings/delete_identity.inc b/program/steps/settings/delete_identity.inc
index 560a2b1..3614a3e 100644
--- a/program/steps/settings/delete_identity.inc
+++ b/program/steps/settings/delete_identity.inc
@@ -5,7 +5,7 @@
  | program/steps/settings/delete_identity.inc                            |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -20,32 +20,29 @@
 */
 
 if (($ids = get_input_value('_iid', RCUBE_INPUT_GET)) && preg_match('/^[0-9]+(,[0-9]+)*$/', $ids))
-  {
-  $DB->query("UPDATE ".get_table_name('identities')."
-              SET    del=1
-              WHERE  user_id=?
-              AND    identity_id IN (".$ids.")",
-              $_SESSION['user_id']);
+{
+  $DB->query(
+    "UPDATE ".get_table_name('identities')."
+     SET    del=1
+     WHERE  user_id=?
+     AND    identity_id IN (".$ids.")",
+    $_SESSION['user_id']);
 
   $count = $DB->affected_rows();
   if ($count)
-    {
-    $commands = show_message('deletedsuccessfully', 'confirmation');
-    }
+    $OUTPUT->show_message('deletedsuccessfully', 'confirmation');
 
   // send response
-  if ($REMOTE_REQUEST)
-    rcube_remote_response($commands);
-  }
+  if ($OUTPUT->ajax_call)
+    $OUTPUT->send();
+}
 
 
-if ($REMOTE_REQUEST)
+if ($OUTPUT->ajax_call)
   exit;
 
 
 // go to identities page
-$_action = 'identities';
+rcmail_overwrite_action('identities');
 
-// overwrite action variable  
-$OUTPUT->add_script(sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $_action));
 ?>
diff --git a/program/steps/settings/edit_identity.inc b/program/steps/settings/edit_identity.inc
index 5fa531a..51a0cd0 100644
--- a/program/steps/settings/edit_identity.inc
+++ b/program/steps/settings/edit_identity.inc
@@ -5,7 +5,7 @@
  | program/steps/settings/edit_identity.inc                              |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -31,12 +31,12 @@
   $IDENTITY_RECORD = $DB->fetch_assoc();
   
   if (is_array($IDENTITY_RECORD))
-    $OUTPUT->add_script(sprintf("%s.set_env('iid', '%s');", $JS_OBJECT_NAME, $IDENTITY_RECORD['identity_id']));
-    
-  $PAGE_TITLE = rcube_label('edititem');
+    $OUTPUT->set_env('iid', $IDENTITY_RECORD['identity_id']);
+
+  $OUTPUT->set_pagetitle(rcube_label('edititem'));
   }
 else
-  $PAGE_TITLE = rcube_label('newitem');
+  $OUTPUT->set_pagetitle(rcube_label('newitem'));
 
 
 $OUTPUT->include_script('list.js');
@@ -44,7 +44,7 @@
 
 function rcube_identity_form($attrib)
   {
-  global $IDENTITY_RECORD, $JS_OBJECT_NAME, $OUTPUT;
+  global $IDENTITY_RECORD, $OUTPUT;
 
   $OUTPUT->include_script('tiny_mce/tiny_mce_src.js');
   $OUTPUT->add_script("tinyMCE.init({ mode : 'specific_textareas'," .
@@ -63,8 +63,7 @@
     return rcube_label('notfound');
 
   // add some labels to client
-  rcube_add_label('noemailwarning');
-  rcube_add_label('nonamewarning');
+  rcube_add_label('noemailwarning', 'nonamewarning');
 
 
   list($form_start, $form_end) = get_form_tags($attrib, 'save-identity', array('name' => '_iid', 'value' => $IDENTITY_RECORD['identity_id']));
@@ -136,7 +135,7 @@
   return $out;  
   }
 
-
+$OUTPUT->add_handler('identityform', 'rcube_identity_form');
 
 if ($_action=='add-identity' && template_exists('addidentity'))
   parse_template('addidentity');
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index e51f683..91212e1 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -5,7 +5,7 @@
  | program/steps/settings/func.inc                                       |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -26,7 +26,7 @@
                           $_SESSION['user_id']);
                                  
 if ($USER_DATA = $DB->fetch_assoc($sql_result))
-  $PAGE_TITLE = sprintf('%s %s@%s', rcube_label('settingsfor'), $USER_DATA['username'], $USER_DATA['mail_host']);
+  $OUTPUT->set_pagetitle(sprintf('%s %s@%s', rcube_label('settingsfor'), $USER_DATA['username'], $USER_DATA['mail_host']));
 
 
 
@@ -202,7 +202,7 @@
 
 function rcmail_identities_list($attrib)
   {
-  global $DB, $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+  global $DB, $CONFIG, $OUTPUT;
 
 
   // get contacts from DB
@@ -224,8 +224,7 @@
   $out = rcube_table_output($attrib, $sql_result, $a_show_cols, 'identity_id');
   
   // set client env
-  $javascript = sprintf("%s.gui_object('identitieslist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
-  $OUTPUT->add_script($javascript);    
+  $OUTPUT->add_gui_object('identitieslist', $attrib['id']);
 
   return $out;
   }
@@ -235,7 +234,7 @@
 // similar function as in /steps/addressbook/edit.inc
 function get_form_tags($attrib, $action, $add_hidden=array())
   {
-  global $OUTPUT, $JS_OBJECT_NAME, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
+  global $OUTPUT, $EDIT_FORM, $SESS_HIDDEN_FIELD;  
 
   $form_start = '';
   if (!strlen($EDIT_FORM))
@@ -258,7 +257,7 @@
   $form_name = strlen($attrib['form']) ? $attrib['form'] : 'form';
 
   if (!strlen($EDIT_FORM))
-    $OUTPUT->add_script("$JS_OBJECT_NAME.gui_object('editform', '$form_name');");
+    $OUTPUT->add_gui_object('editform', $form_name);
   
   $EDIT_FORM = $form_name;
 
@@ -266,4 +265,11 @@
   }
 
 
+// register UI objects
+$OUTPUT->add_handlers(array(
+  'userprefs' => 'rcmail_user_prefs_form',
+  'itentitieslist' => 'rcmail_identities_list'
+));
+
+
 ?>
\ No newline at end of file
diff --git a/program/steps/settings/identities.inc b/program/steps/settings/identities.inc
index caaa453..9284e52 100644
--- a/program/steps/settings/identities.inc
+++ b/program/steps/settings/identities.inc
@@ -5,7 +5,7 @@
  | program/steps/settings/identities.inc                                 |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -20,7 +20,7 @@
 */
 
 if ($USER_DATA = $DB->fetch_assoc($sql_result))
-  $PAGE_TITLE = sprintf('%s (%s@%s)', rcube_label('identities'), $USER_DATA['username'], $USER_DATA['mail_host']);
+  $OUTPUT->set_pagetitle(sprintf('%s (%s@%s)', rcube_label('identities'), $USER_DATA['username'], $USER_DATA['mail_host']));
 
 $OUTPUT->include_script('list.js');
 
@@ -28,14 +28,14 @@
 // similar function as /steps/addressbook/func.inc::rcmail_contact_frame()
 function rcmail_identity_frame($attrib)
   {
-  global $OUTPUT, $JS_OBJECT_NAME;
+  global $OUTPUT;
 
   if (!$attrib['id'])
     $attrib['id'] = 'rcmIdentityFrame';
 
   $attrib['name'] = $attrib['id'];
 
-  $OUTPUT->add_script(sprintf("%s.set_env('contentframe', '%s');", $JS_OBJECT_NAME, $attrib['name']));
+  $OUTPUT->set_env('contentframe', $attrib['name']);
 
   $attrib_str = create_attrib_string($attrib, array('name', 'id', 'class', 'style', 'src', 'width', 'height', 'frameborder'));
   $out = '<iframe'. $attrib_str . '></iframe>';
@@ -43,7 +43,7 @@
   return $out;
   }
 
-
+$OUTPUT->add_handler('identityframe', 'rcmail_identity_frame');
 
 parse_template('identities');
 ?>
\ No newline at end of file
diff --git a/program/steps/settings/manage_folders.inc b/program/steps/settings/manage_folders.inc
index 8dd898d..7499fe3 100644
--- a/program/steps/settings/manage_folders.inc
+++ b/program/steps/settings/manage_folders.inc
@@ -5,7 +5,7 @@
  | program/steps/settings/manage_folders.inc                             |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -29,8 +29,8 @@
   if ($mboxes = get_input_value('_mboxes', RCUBE_INPUT_GET))
     $IMAP->subscribe(array($mboxes));
 
-  if ($REMOTE_REQUEST)
-    rcube_remote_response('// subscribed');
+  if ($OUTPUT->ajax_call)
+    $OUTPUT->remote_response('// subscribed');
   }
 
 // unsubscribe one or more mailboxes
@@ -39,8 +39,8 @@
   if ($mboxes = get_input_value('_mboxes', RCUBE_INPUT_GET))
     $IMAP->unsubscribe(array($mboxes));
 
-  if ($REMOTE_REQUEST)
-    rcube_remote_response('// unsubscribed');
+  if ($OUTPUT->ajax_call)
+    $OUTPUT->remote_response('// unsubscribed');
   }
 
 // create a new mailbox
@@ -49,47 +49,40 @@
   if (!empty($_GET['_name']))
     $create = $IMAP->create_mailbox(trim(get_input_value('_name', RCUBE_INPUT_GET, FALSE, 'UTF-7')), TRUE);
 
-  if ($create && $REMOTE_REQUEST)
+  if ($create && $OUTPUT->ajax_call)
     {
-    $commands = sprintf("this.add_folder_row('%s','%s')",
-                        JQ($create),
-                        JQ(rcube_charset_convert($create, 'UTF-7')));
-    rcube_remote_response($commands);
+    $OUTPUT->command('add_folder_row', $create, rcube_charset_convert($create, 'UTF-7'));
+    $OUTPUT->send();
     }
-  else if (!$create && $REMOTE_REQUEST)
+  else if (!$create && $OUTPUT->ajax_call)
     {
-    $commands = show_message('errorsaving', 'error');
-    rcube_remote_response($commands);
+    $OUTPUT->show_message('errorsaving', 'error');
+    $OUTPUT->remote_response();
     }
   else if (!$create)
-    show_message('errorsaving', 'error');
+    $OUTPUT->show_message('errorsaving', 'error');
   }
 
 // rename a mailbox
 else if ($_action=='rename-folder')
   {
   if (!empty($_GET['_folder_oldname']) && !empty($_GET['_folder_newname']))
-    $rename = $IMAP->rename_mailbox(get_input_value('_folder_oldname', RCUBE_INPUT_GET), trim(get_input_value('_folder_newname', RCUBE_INPUT_GET, FALSE, 'UTF-7')));
+    $rename = $IMAP->rename_mailbox(($oldname = get_input_value('_folder_oldname', RCUBE_INPUT_GET)), trim(get_input_value('_folder_newname', RCUBE_INPUT_GET, FALSE, 'UTF-7')));
     
-  if ($rename && $REMOTE_REQUEST)
+  if ($rename && $OUTPUT->ajax_call)
     {
-    $commands = sprintf("this.replace_folder_row('%s','%s','%s');\n",
-                        JQ(get_input_value('_folder_oldname', RCUBE_INPUT_GET)),
-                        JQ($rename),
-                        JQ(rcube_charset_convert($rename, 'UTF-7')));
-
-    $commands .= "this.reset_folder_rename();\n";
-                        
-    rcube_remote_response($commands);
+    $OUTPUT->command('replace_folder_row', $oldname, $rename, rcube_charset_convert($rename, 'UTF-7'));
+    $OUTPUT->command('reset_folder_rename');
+    $OUTPUT->send();
     }
-  else if (!$rename && $REMOTE_REQUEST)
+  else if (!$rename && $OUTPUT->ajax_call)
     {
-    $commands = "this.reset_folder_rename();\n";
-    $commands .= show_message('errorsaving', 'error');
-    rcube_remote_response($commands);
+    $OUTPUT->command('reset_folder_rename');
+    $OUTPUT->show_message('errorsaving', 'error');
+    $OUTPUT->send();
     }
   else if (!$rename)
-    show_message('errorsaving', 'error');
+    $OUTPUT->show_message('errorsaving', 'error');
   }
 
 // delete an existing IMAP mailbox
@@ -98,16 +91,16 @@
   if ($mboxes = get_input_value('_mboxes', RCUBE_INPUT_GET))
     $deleted = $IMAP->delete_mailbox(array($mboxes));
 
-  if ($REMOTE_REQUEST && $deleted)
+  if ($OUTPUT->ajax_call && $deleted)
     {
-    $commands = sprintf("this.remove_folder_row('%s');\n", JQ(get_input_value('_mboxes', RCUBE_INPUT_GET)));
-    $commands .= show_message('folderdeleted', 'confirmation');
-    rcube_remote_response($commands);
+    $OUTPUT->command('remove_folder_row', get_input_value('_mboxes', RCUBE_INPUT_GET));
+    $OUTPUT->show_message('folderdeleted', 'confirmation');
+    $OUTPUT->send();
     }
-  else if ($REMOTE_REQUEST)
+  else if ($OUTPUT->ajax_call)
     {
-    $commands = show_message('errorsaving', 'error');
-    rcube_remote_response($commands);
+    $OUTPUT->show_message('errorsaving', 'error');
+    $OUTPUT->send();
     }
   }
 
@@ -116,7 +109,7 @@
 // build table with all folders listed by server
 function rcube_subscription_form($attrib)
   {
-  global $IMAP, $CONFIG, $OUTPUT, $JS_OBJECT_NAME;
+  global $IMAP, $CONFIG, $OUTPUT;
 
   list($form_start, $form_end) = get_form_tags($attrib, 'folders');
   unset($attrib['form']);
@@ -147,7 +140,7 @@
   $a_subscribed = $IMAP->list_mailboxes();
   $a_js_folders = array();
  
-  $checkbox_subscribe = new checkbox(array('name' => '_subscribed[]', 'onclick' => "$JS_OBJECT_NAME.command(this.checked?'subscribe':'unsubscribe',this.value)"));
+  $checkbox_subscribe = new checkbox(array('name' => '_subscribed[]', 'onclick' => JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)"));
   
   if (!empty($attrib['deleteicon']))
     $del_button = sprintf('<img src="%s%s" alt="%s" border="0" />', $CONFIG['skin_path'], $attrib['deleteicon'], rcube_label('delete'));
@@ -166,11 +159,10 @@
     $protected = ($CONFIG['protect_default_folders'] == TRUE && in_array($folder,$CONFIG['default_imap_folders']));
     $zebra_class = $i%2 ? 'even' : 'odd';
     $folder_js = JQ($folder);
-    $folder_js_enc = JQ(rcube_charset_convert($folder, 'UTF-7'));
     $folder_html = $CONFIG['protect_default_folders'] && in_array($folder, $CONFIG['default_imap_folders']) ? rcube_label(strtolower($folder)) : rcube_charset_convert($folder, 'UTF-7');
     
     if (!$protected)
-      $a_js_folders['rcmrow'.($i+1)] = array($folder_js, $folder_js_enc);
+      $a_js_folders['rcmrow'.($i+1)] = array($folder, rcube_charset_convert($folder, 'UTF-7'));
 
     $out .= sprintf('<tr id="rcmrow%d" class="%s"><td>%s</td>',
                     $i+1,
@@ -186,11 +178,11 @@
     if (!$protected)
       $out .= sprintf('<td><a href="#rename" onclick="%s.command(\'rename-folder\',\'%s\')" title="%s">%s</a>'.
                       '<td><a href="#delete" onclick="%s.command(\'delete-folder\',\'%s\')" title="%s">%s</a></td>',
-                      $JS_OBJECT_NAME,
+                      JS_OBJECT_NAME,
                       $folder_js,
                       rcube_label('renamefolder'),
                       $edit_button,
-                      $JS_OBJECT_NAME,
+                      JS_OBJECT_NAME,
                       $folder_js,
                       rcube_label('deletefolder'),
                       $del_button);
@@ -203,10 +195,8 @@
   $out .= "</tbody>\n</table>";
   $out .= "\n$form_end";
 
-
-  $javascript = sprintf("%s.gui_object('subscriptionlist', '%s');\n", $JS_OBJECT_NAME, $attrib['id']);
-  $javascript .= sprintf("%s.set_env('subscriptionrows', %s);", $JS_OBJECT_NAME, array2js($a_js_folders));
-  $OUTPUT->add_script($javascript);
+  $OUTPUT->add_gui_object('subscriptionlist', $attrib['id']);
+  $OUTPUT->set_env('subscriptionrows', $a_js_folders);
 
   return $out;  
   }
@@ -214,8 +204,6 @@
 
 function rcube_create_folder_form($attrib)
   {
-  global $JS_OBJECT_NAME;
-
   list($form_start, $form_end) = get_form_tags($attrib, 'create-folder');
   unset($attrib['form']);
 
@@ -230,7 +218,7 @@
     {
     $button = new input_field(array('type' => 'button',
                                     'value' => rcube_label('create'),
-                                    'onclick' => "$JS_OBJECT_NAME.command('create-folder',this.form)"));
+                                    'onclick' => JS_OBJECT_NAME.".command('create-folder',this.form)"));
     $out .= $button->show();
     }
 
@@ -241,7 +229,7 @@
 
 function rcube_rename_folder_form($attrib)
   {
-  global $CONFIG, $IMAP, $JS_OBJECT_NAME;
+  global $CONFIG, $IMAP;
 
   list($form_start, $form_end) = get_form_tags($attrib, 'rename-folder');
   unset($attrib['form']);
@@ -270,7 +258,7 @@
     {
     $button = new input_field(array('type' => 'button',
                                     'value' => rcube_label('rename'),
-                                    'onclick' => "$JS_OBJECT_NAME.command('rename-folder',this.form)"));
+                                    'onclick' => JS_OBJECT_NAME.".command('rename-folder',this.form)"));
     $out .= $button->show();
     }
 
@@ -280,9 +268,15 @@
   }
 
 
+// register UI objects
+$OUTPUT->add_handlers(array(
+  'foldersubscription' => 'rcube_subscription_form',
+  'createfolder' => 'rcube_create_folder_form',
+  'renamefolder' => 'rcube_rename_folder_form'
+));
+
 // add some labels to client
 rcube_add_label('deletefolderconfirm');
 
-
-parse_template('managefolders');
+$OUTPUT->send('managefolders');
 ?>
diff --git a/program/steps/settings/save_identity.inc b/program/steps/settings/save_identity.inc
index ce1c6f6..8079b58 100644
--- a/program/steps/settings/save_identity.inc
+++ b/program/steps/settings/save_identity.inc
@@ -27,7 +27,7 @@
 // check input
 if (empty($_POST['_name']) || empty($_POST['_email']))
   {
-  show_message('formincomplete', 'warning');
+  $OUTPUT->show_message('formincomplete', 'warning');
   rcmail_overwrite_action('edit-identitiy');
   return;
   }
@@ -72,7 +72,7 @@
        
   if ($updated)
     {
-    show_message('successfullysaved', 'confirmation');
+    $OUTPUT->show_message('successfullysaved', 'confirmation');
     
     if (!empty($_POST['_standard']))
       $default_id = get_input_value('_iid', RCUBE_INPUT_POST);
@@ -86,7 +86,7 @@
   else if ($DB->is_error())
     {
     // show error message
-    show_message('errorsaving', 'error');
+    $OUTPUT->show_message('errorsaving', 'error');
     rcmail_overwrite_action('edit-identitiy');
     return;
     }
@@ -133,7 +133,7 @@
   else
     {
     // show error message
-    show_message('errorsaving', 'error');
+    $OUTPUT->show_message('errorsaving', 'error');
     rcmail_overwrite_action('edit-identity');
     return;
     }
@@ -152,6 +152,6 @@
     $default_id);
 
 // go to next step
-rcmail_overwrite_action($_POST['_framed'] ? 'edit-identity' : 'identities');
+rcmail_overwrite_action($_framed ? 'edit-identity' : 'identities');
 
 ?>
\ No newline at end of file
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
index fd254f9..4945a4f 100644
--- a/program/steps/settings/save_prefs.inc
+++ b/program/steps/settings/save_prefs.inc
@@ -5,7 +5,7 @@
  | program/steps/settings/save_prefs.inc                                 |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2005-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
@@ -43,13 +43,10 @@
   }
 
 if (rcmail_save_user_prefs($a_user_prefs))
-  show_message('successfullysaved', 'confirmation');
+  $OUTPUT->show_message('successfullysaved', 'confirmation');
 
 
 // go to next step
-$_action = 'preferences';
-
-// overwrite action variable  
-$OUTPUT->add_script(sprintf("\n%s.set_env('action', '%s');", $JS_OBJECT_NAME, $_action));  
+rcmail_overwrite_action('preferences');
 
 ?>
diff --git a/skins/default/addresses.css b/skins/default/addresses.css
index 0ff1b28..363a634 100644
--- a/skins/default/addresses.css
+++ b/skins/default/addresses.css
@@ -17,8 +17,8 @@
 #abookcountbar
 {
   position: absolute;
-  top: 60px;
-  left: 490px;
+  bottom: 16px;
+  left: 185px;
   width: 240px;
   height: 20px;
   text-align: left;
@@ -30,20 +30,69 @@
   color: #333333;
 }
 
-
-#addresslist
+#directorylist, #addresslist
 {
   position: absolute;
   top: 85px;
-  left: 20px;
-  width: 450px;
   bottom: 40px;
   border: 1px solid #999999;
   background-color: #F9F9F9;
   overflow: auto;
   /* css hack for IE */
-  height: expression((parseInt(document.documentElement.clientHeight)-135)+'px');
+  height: expression((parseInt(document.documentElement.clientHeight)-135)+'px');  
 }
+
+#directorylist
+{
+  left: 20px;
+  width: 150px;
+}
+
+#addresslist
+{
+  left: 185px;
+  width: 340px;
+}
+
+#directorylist ul
+{
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+#directorylist ul
+{
+  font-size: 11px;
+  border-bottom: 1px solid #EBEBEB;
+}
+
+#directorylist ul li a
+{
+  display: block;
+  padding-left: 6px;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  text-decoration: none;
+}
+
+#directorylist li.selected
+{
+  background-color: #929292;
+  border-bottom: 1px solid #898989;
+}
+
+#directorylist li.selected a
+{
+  color: #FFF;
+  font-weight: bold;
+}
+
+#directorylist li.droptarget
+{
+  background-color: #FFFFA6;
+}
+
 
 #contacts-table
 {
@@ -64,26 +113,26 @@
 {
   position: absolute;
   top: 85px;
-  left: 490px;
-  right: 40px;
+  left: 540px;
+  right: 30px;
   bottom: 40px;
   border: 1px solid #999999;
   overflow: hidden;
   /* css hack for IE */
-  width: expression((parseInt(document.documentElement.clientWidth)-80-document.getElementById('addresslist').offsetWidth)+'px');
+  width: expression((parseInt(document.documentElement.clientWidth)-70-document.getElementById('addresslist').offsetWidth)+'px');
   height: expression((parseInt(document.documentElement.clientHeight)-135)+'px');
 }
 
 
 #addressviewsplitter
 {
-  background-position: 6px center;
+  background-position: 4px center;
 }
 
 #addressviewsplitter .splitterLine
 {
   margin-left: 3px;
-  width: 9px;
+  width: 6px;
 }
 
 
@@ -99,15 +148,33 @@
 /* visibility: hidden; */
 }
 
+#contact-title, #groups-title
+{
+  height: 12px !important;
+/*  height: 20px; */
+  padding: 4px 5px 3px 5px;
+  border-bottom: 1px solid #999;
+  color: #333;
+  font-size: 11px;
+  font-weight: bold;
+  background-color: #EBEBEB;
+  background-image: url(images/listheader_aqua.gif);
+  white-space: nowrap;
+}
+
+#contact-title
+{
+  padding: 4px 10px 3px 10px;
+}
 
 #contact-details
 {
-  padding: 15px 20px 10px 20px;
+  padding: 15px 10px 10px 10px;
 }
 
 #contact-details table td.title
 {
-  color: #666666;
+  color: #666;
   font-weight: bold;
   text-align: right;
   padding-right: 10px;
diff --git a/skins/default/common.css b/skins/default/common.css
index b3c234f..2ce3dff 100755
--- a/skins/default/common.css
+++ b/skins/default/common.css
@@ -127,7 +127,7 @@
   width: 600px;
   height: 37px;
   background: url(images/taskbar.gif) top right no-repeat;
-  padding: 10px 24px 10px 0px;
+  padding: 10px 14px 10px 0px;
   text-align: right;
   white-space: nowrap;
   z-index: 2;
@@ -251,6 +251,10 @@
   background-image: url(images/listheader_aqua.gif);
 }
 
+.radios-left label
+{
+  padding-left: 0.3em;
+}
 
 /***** common table settings ******/
 
@@ -292,8 +296,6 @@
 
 table.records-table tr.focused td
 {
-  border-bottom: thin dotted;
-  border-top: thin dotted;
 }
 
 table.records-table tr.unfocused td
@@ -304,8 +306,84 @@
 }
 
 
+/***** mac-style quicksearch field *****/
+
+#quicksearchbar
+{
+  position: absolute;
+  top: 60px;
+  right: 30px;
+  width: 182px;
+  height: 20px;
+  text-align: right;
+  background: url('images/searchfield.gif') top left no-repeat;
+}
+
+#quicksearchbar a
+{
+  position: absolute;
+  top: 3px;
+  right: 4px;
+  text-decoration: none;
+}
+
+#quicksearchbar img
+{
+  vertical-align: middle;
+}
+
+#quicksearchbox
+{
+  position: absolute;
+  top: 2px;
+  left: 20px;
+  width: 140px;
+  font-size: 11px;
+  padding: 0px;
+  border: none;
+}
+
+
+/*\*/
+html>body*#quicksearchbar[id$="quicksearchbar"]:not([class="none"]) { background-image: none; }
+html>body*#quicksearchbar[id$="quicksearchbar"]:not([class="none"]) a { top: 5px; }
+html>body*#quicksearchbar[id$="quicksearchbar"]:not([class="none"]) #quicksearchbox { width: 180px; top:0px; right: 1px; left: auto; }
+/**/
+
+
 /***** roundcube webmail pre-defined classes *****/
 
+#rcversion
+{
+  position: absolute;
+  top: 67px;
+  left: 20px;
+  width: 160px;
+  text-align: center;
+
+  font-weight: normal;
+  font-size: x-small;
+  font-variant: small-caps;
+  
+  color: #999999;
+  /*border: 1px solid #308014;
+  background-color: #b4eeb4;*/
+}
+
+#rcmdraglayer
+{
+  width: 300px;
+  border: 1px solid #999999;
+  background-color: #F9F9F9;
+  padding-left: 8px;
+  padding-right: 8px;
+  padding-top: 3px;
+  padding-bottom: 3px;
+  font-size: 11px;
+  opacity: 0.6;
+  -moz-opacity: 0.6;
+}
+
 a.rcmContactAddress
 {
   text-decoration: none;
diff --git a/skins/default/ldapsearchform.css b/skins/default/ldapsearchform.css
deleted file mode 100644
index 9661442..0000000
--- a/skins/default/ldapsearchform.css
+++ /dev/null
@@ -1,54 +0,0 @@
-/***** RoundCube|Mail address book task styles *****/
-
-
-body.iframe,
-{
-  background-color: #F9F9F9;
-}
-
-#ldapsearch-title
-{
-  height: 12px !important;
-/*  height: 20px; */
-  padding: 4px 20px 3px 20px;
-  border-bottom: 1px solid #999999;
-  color: #333333;
-  font-size: 11px;
-  font-weight: bold;
-  background-color: #EBEBEB;
-  background-image: url(images/listheader_aqua.gif); 
-}
-
-#ldapsearch-details
-{
-  padding: 15px 20px 10px 20px;
-}
-
-#ldapsearch-details table td.title
-{
-  color: #666666;
-  font-weight: bold;
-  text-align: right;
-  padding-right: 10px;
-}
-
-#ldapAddressList 
-{
-  width: 100%;
-  table-layout: fixed;
-  /* css hack for IE */
-  width: expression(document.getElementById('addresslist').clientWidth);
-}
-
-#ldapAddressList table 
-{
-  border-top: 1px solid #999999;
-}
-
-#ldap-search-results div
-{
-  width: 100%;
-  color: red;
-  background-color: green;
-}
-
diff --git a/skins/default/mail.css b/skins/default/mail.css
index 11364ba..67f2bbb 100644
--- a/skins/default/mail.css
+++ b/skins/default/mail.css
@@ -83,7 +83,7 @@
 {
   position: absolute;
   bottom: 16px;
-  right: 40px;
+  right: 30px;
   width: 300px;
   height: 20px;
   text-align: right;
@@ -110,13 +110,13 @@
   position: absolute;
   top: 85px;
   left: 200px;
-  right: 40px;
+  right: 30px;
   bottom: 40px;
   border: 1px solid #999999;
   background-color: #F9F9F9;
   overflow: auto;
   /* css hack for IE */
-  width: expression((parseInt(document.documentElement.clientWidth)-240)+'px');
+  width: expression((parseInt(document.documentElement.clientWidth)-230)+'px');
   height: expression((parseInt(document.documentElement.clientHeight)-125)+'px');
 }
 
@@ -125,12 +125,12 @@
   position: absolute;
   top: 305px;
   left: 200px;
-  right: 40px;
+  right: 30px;
   bottom: 40px;
   border: 1px solid #999999;
   background-color: #F9F9F9;
   /* css hack for IE */
-  width: expression((parseInt(document.documentElement.clientWidth)-240)+'px');
+  width: expression((parseInt(document.documentElement.clientWidth)-230)+'px');
   height: expression((parseInt(document.documentElement.clientHeight)-135-document.getElementById('mailcontframe').offsetHeight)+'px');
 }
 
@@ -184,20 +184,6 @@
 {
   color: #666666;
   font-weight: bold;
-}
-
-#rcmdraglayer
-{
-  width: 300px;
-  border: 1px solid #999999;
-  background-color: #F9F9F9;
-  padding-left: 8px;
-  padding-right: 8px;
-  padding-top: 3px;
-  padding-bottom: 3px;
-  font-size: 11px;
-  opacity: 0.6;
-  -moz-opacity: 0.6;
 }
 
 
@@ -524,65 +510,6 @@
   color: #CCCCCC;
 }
 
-#quicksearchbar
-{
-  position: absolute;
-  top: 60px;
-  right: 40px;
-  width: 182px;
-  height: 20px;
-  text-align: right;
-  background: url('images/searchfield.gif') top left no-repeat;
-}
-
-#quicksearchbar a
-{
-  position: absolute;
-  top: 3px;
-  right: 4px;
-  text-decoration: none;
-}
-
-#quicksearchbar img
-{
-  vertical-align: middle;
-}
-
-#quicksearchbox
-{
-  position: absolute;
-  top: 2px;
-  left: 20px;
-  width: 140px;
-  font-size: 11px;
-  padding: 0px;
-  border: none;
-}
-
-
-/*\*/
-html>body*#quicksearchbar[id$="quicksearchbar"]:not([class="none"]) { background-image: none; }
-html>body*#quicksearchbar[id$="quicksearchbar"]:not([class="none"]) a { top: 5px; }
-html>body*#quicksearchbar[id$="quicksearchbar"]:not([class="none"]) #quicksearchbox { width: 180px; top:0px; right: 1px; left: auto; }
-/**/
-
-
-#rcversion
-{
-  position: absolute;
-  top: 67px;
-  left: 20px;
-  width: 160px;
-  text-align: center;
-
-  font-weight: normal;
-  font-size: x-small;
-  font-variant: small-caps;
-  
-  color: #999999;
-  /*border: 1px solid #308014;
-  background-color: #b4eeb4;*/
-}
 
 #quotadisplay
 {
@@ -604,9 +531,9 @@
 #messageframe
 {
   position: absolute;
-  top: 95px;
+  top: 85px;
   left: 200px;
-  right: 40px;
+  right: 30px;
   bottom: 40px;
   border: 1px solid #cccccc;
   background-color: #FFFFFF;
diff --git a/skins/default/print.css b/skins/default/print.css
index 5f0dbdd..a36f87a 100644
--- a/skins/default/print.css
+++ b/skins/default/print.css
@@ -108,7 +108,7 @@
   margin: 0;
   padding: 0;
   white-space: pre;
-  font-size: 9pt;
+  font-family: monospace;
 }
 
 div.message-part blockquote
diff --git a/skins/default/templates/addcontact.html b/skins/default/templates/addcontact.html
index d4fc9cc..e52b8c7 100644
--- a/skins/default/templates/addcontact.html
+++ b/skins/default/templates/addcontact.html
@@ -17,6 +17,7 @@
 <roundcube:button command="save" type="input" class="button" label="save" />
 </p>
 
+</form>
 </div>
 
 
diff --git a/skins/default/templates/addressbook.html b/skins/default/templates/addressbook.html
index e5b5a10..c70b00c 100644
--- a/skins/default/templates/addressbook.html
+++ b/skins/default/templates/addressbook.html
@@ -17,15 +17,15 @@
 <roundcube:button command="compose" imageSel="/images/buttons/compose_sel.png" imageAct="/images/buttons/compose_act.png" imagePas="/images/buttons/compose_pas.png" width="32" height="32" title="composeto" />
 <roundcube:button command="print" imageSel="/images/buttons/print_sel.png" imageAct="/images/buttons/print_act.png" imagePas="/images/buttons/print_pas.png" width="32" height="32" title="print" />
 <roundcube:button command="export" imageSel="/images/buttons/download_sel.png" imageAct="/images/buttons/download_act.png" imagePas="/images/buttons/download_pas.png" width="32" height="32" title="export" />
-<roundcube:button command="ldappublicsearch" imageSel="/images/buttons/contacts_sel.png" imageAct="/images/buttons/contacts_act.png" imagePas="/images/buttons/contacts_pas.png" width="32" height="32" title="ldapsearch" />
 </div>
 
-<div id="abookcountbar">
-<roundcube:button command="firstpage" imageSel="/images/buttons/first_sel.png" imageAct="/images/buttons/first_act.png" imagePas="/images/buttons/first_pas.png" width="11" height="11" title="firstpage" />
-<roundcube:button command="previouspage" imageSel="/images/buttons/previous_sel.png" imageAct="/images/buttons/previous_act.png" imagePas="/images/buttons/previous_pas.png" width="11" height="11" title="previouspage" />
-&nbsp;<roundcube:object name="recordsCountDisplay" />&nbsp;
-<roundcube:button command="nextpage" imageSel="/images/buttons/next_sel.png" imageAct="/images/buttons/next_act.png" imagePas="/images/buttons/next_pas.png" width="11" height="11" title="nextpage" />
-<roundcube:button command="lastpage" imageSel="/images/buttons/last_sel.png" imageAct="/images/buttons/last_act.png" imagePas="/images/buttons/last_pas.png" width="11" height="11" title="lastpage" />
+<div id="quicksearchbar">
+<roundcube:object name="searchform" type="search" results="5" id="quicksearchbox" /><roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
+</div>
+
+<div id="directorylist">
+<div id="groups-title"><roundcube:label name="groups" /></div>
+<roundcube:object name="directorylist" id="directories-list" />
 </div>
 
 <div id="addresslist">
@@ -41,5 +41,13 @@
 <roundcube:object name="addressframe" id="contact-frame" width="100%" height="100%" frameborder="0" src="/watermark.html" />
 </div>
 
+<div id="abookcountbar">
+<roundcube:button command="firstpage" imageSel="/images/buttons/first_sel.png" imageAct="/images/buttons/first_act.png" imagePas="/images/buttons/first_pas.png" width="11" height="11" title="firstpage" />
+<roundcube:button command="previouspage" imageSel="/images/buttons/previous_sel.png" imageAct="/images/buttons/previous_act.png" imagePas="/images/buttons/previous_pas.png" width="11" height="11" title="previouspage" />
+&nbsp;<roundcube:object name="recordsCountDisplay" />&nbsp;
+<roundcube:button command="nextpage" imageSel="/images/buttons/next_sel.png" imageAct="/images/buttons/next_act.png" imagePas="/images/buttons/next_pas.png" width="11" height="11" title="nextpage" />
+<roundcube:button command="lastpage" imageSel="/images/buttons/last_sel.png" imageAct="/images/buttons/last_act.png" imagePas="/images/buttons/last_pas.png" width="11" height="11" title="lastpage" />
+</div>
+
 </body>
 </html>
diff --git a/skins/default/templates/compose.html b/skins/default/templates/compose.html
index 3c83488..63d5b2c 100644
--- a/skins/default/templates/compose.html
+++ b/skins/default/templates/compose.html
@@ -97,19 +97,18 @@
 <td style="width:100%; height:98%; vertical-align:top;">
 <roundcube:object name="composeBody" id="compose-body" form="form" cols="80" rows="20" wrap="virtual" tabindex="7" />
 
-<table border="0" cellspacing="0" width="100%" summary=""><tbody><tr>
-
+<table border="0" cellspacing="0" width="100%" summary=""><tbody>
+<tr>
 <td>
 <roundcube:button type="input" command="send" class="button" label="sendmessage" />
 <roundcube:button type="input" command="list" class="button" label="cancel" />
 </td>
 <td align="right">
-<roundcube:label name="charset" />:&nbsp;<roundcube:object name="charsetSelector" tabindex="8" />
+ <roundcube:label name="editortype" />:&nbsp;
+ <span class="radios-left"><roundcube:object name="editorSelector" tabindex="9" /></span>
 </td>
-<td align="right">
- <roundcube:label name="editortype" />:&nbsp;</td>
-<roundcube:object name="editorSelector" tabindex="9" />
-</tr></tbody></table>
+</tr>
+</tbody></table>
 
 </td>
 
diff --git a/skins/default/templates/ldappublicsearch.html b/skins/default/templates/ldappublicsearch.html
deleted file mode 100644
index d9714cb..0000000
--- a/skins/default/templates/ldappublicsearch.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-<title><roundcube:object name="pagetitle" /></title>
-<link rel="stylesheet" type="text/css" href="/common.css" />
-<link rel="stylesheet" type="text/css" href="/ldapsearchform.css" />
-</head>
-<body class="iframe">
-
-<div id="ldapsearch-title"><roundcube:label name="ldappublicsearchform" /></div>
-
-<div id="ldapsearch-details">
-<roundcube:object name="ldappublicsearch" size="40" />
-<p>
-<roundcube:button command="ldappublicsearch" type="input" class="button" label="ldappublicsearch" />
-<input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp;
-<br /></p>
-</div>
-
-
-<div id="ldapsearch-results">
-<roundcube:object name="ldappublicaddresslist"
-  id="ldappublicaddresslist"
-  cellspacing="0"
-  summary="LDAP email address list" />
-</div>
-
-<roundcube:include file="/includes/ldapscripts.html" />
-
-</body>
-</html>
diff --git a/skins/default/templates/showcontact.html b/skins/default/templates/showcontact.html
index 53bc08b..7930457 100644
--- a/skins/default/templates/showcontact.html
+++ b/skins/default/templates/showcontact.html
@@ -12,7 +12,7 @@
 <div id="contact-details">
 <roundcube:object name="contactdetails" />
 
-<p><br /><roundcube:button command="edit" type="input" class="button" label="editcontact" /></p>
+<p><br /><roundcube:button command="edit" type="input" class="button" label="editcontact" condition="!ENV:readonly" /></p>
 </div>
 
 </body>

--
Gitblit v1.9.1