thomascube
2009-04-19 cc97ea0559af1a92a54dbcdf738ee4d95e67d3ff
Merged branch devel-api (from r2208 to r2387) back into trunk (omitting some sample plugins)

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